aboutsummaryrefslogtreecommitdiffstats
path: root/core/Command
diff options
context:
space:
mode:
Diffstat (limited to 'core/Command')
-rw-r--r--core/Command/App/Disable.php38
-rw-r--r--core/Command/App/Enable.php58
-rw-r--r--core/Command/App/GetPath.php54
-rw-r--r--core/Command/App/Install.php59
-rw-r--r--core/Command/App/ListApps.php99
-rw-r--r--core/Command/App/Remove.php57
-rw-r--r--core/Command/App/Update.php64
-rw-r--r--core/Command/Background/Ajax.php32
-rw-r--r--core/Command/Background/Base.php69
-rw-r--r--core/Command/Background/Cron.php32
-rw-r--r--core/Command/Background/Delete.php65
-rw-r--r--core/Command/Background/Job.php55
-rw-r--r--core/Command/Background/JobBase.php82
-rw-r--r--core/Command/Background/JobWorker.php176
-rw-r--r--core/Command/Background/ListCommand.php38
-rw-r--r--core/Command/Background/Mode.php46
-rw-r--r--core/Command/Background/WebCron.php32
-rw-r--r--core/Command/Base.php94
-rw-r--r--core/Command/Broadcast/Test.php42
-rw-r--r--core/Command/Check.php34
-rw-r--r--core/Command/Config/App/Base.php37
-rw-r--r--core/Command/Config/App/DeleteConfig.php38
-rw-r--r--core/Command/Config/App/GetConfig.php69
-rw-r--r--core/Command/Config/App/SetConfig.php216
-rw-r--r--core/Command/Config/Import.php32
-rw-r--r--core/Command/Config/ListConfigs.php49
-rw-r--r--core/Command/Config/Preset.php69
-rw-r--r--core/Command/Config/System/Base.php29
-rw-r--r--core/Command/Config/System/CastHelper.php76
-rw-r--r--core/Command/Config/System/DeleteConfig.php28
-rw-r--r--core/Command/Config/System/GetConfig.php29
-rw-r--r--core/Command/Config/System/SetConfig.php101
-rw-r--r--core/Command/Db/AddMissingColumns.php92
-rw-r--r--core/Command/Db/AddMissingIndices.php467
-rw-r--r--core/Command/Db/AddMissingPrimaryKeys.php175
-rw-r--r--core/Command/Db/ConvertFilecacheBigInt.php54
-rw-r--r--core/Command/Db/ConvertMysqlToMB4.php48
-rw-r--r--core/Command/Db/ConvertType.php98
-rw-r--r--core/Command/Db/ExpectedSchema.php68
-rw-r--r--core/Command/Db/ExportSchema.php44
-rw-r--r--core/Command/Db/Migrations/ExecuteCommand.php35
-rw-r--r--core/Command/Db/Migrations/GenerateCommand.php77
-rw-r--r--core/Command/Db/Migrations/GenerateMetadataCommand.php79
-rw-r--r--core/Command/Db/Migrations/MigrateCommand.php30
-rw-r--r--core/Command/Db/Migrations/PreviewCommand.php111
-rw-r--r--core/Command/Db/Migrations/StatusCommand.php31
-rw-r--r--core/Command/Db/SchemaEncoder.php115
-rw-r--r--core/Command/Encryption/ChangeKeyStorageRoot.php57
-rw-r--r--core/Command/Encryption/DecryptAll.php64
-rw-r--r--core/Command/Encryption/Disable.php29
-rw-r--r--core/Command/Encryption/Enable.php56
-rw-r--r--core/Command/Encryption/EncryptAll.php56
-rw-r--r--core/Command/Encryption/ListModules.php39
-rw-r--r--core/Command/Encryption/MigrateKeyStorage.php112
-rw-r--r--core/Command/Encryption/SetDefaultModule.php37
-rw-r--r--core/Command/Encryption/ShowKeyStorageRoot.php31
-rw-r--r--core/Command/Encryption/Status.php29
-rw-r--r--core/Command/FilesMetadata/Get.php102
-rw-r--r--core/Command/Group/Add.php30
-rw-r--r--core/Command/Group/AddUser.php33
-rw-r--r--core/Command/Group/Delete.php29
-rw-r--r--core/Command/Group/Info.php28
-rw-r--r--core/Command/Group/ListCommand.php77
-rw-r--r--core/Command/Group/RemoveUser.php33
-rw-r--r--core/Command/Info/File.php188
-rw-r--r--core/Command/Info/FileUtils.php325
-rw-r--r--core/Command/Info/Space.php51
-rw-r--r--core/Command/Info/Storage.php49
-rw-r--r--core/Command/Info/Storages.php43
-rw-r--r--core/Command/Integrity/CheckApp.php96
-rw-r--r--core/Command/Integrity/CheckCore.php33
-rw-r--r--core/Command/Integrity/SignApp.php44
-rw-r--r--core/Command/Integrity/SignCore.php36
-rw-r--r--core/Command/InterruptedException.php23
-rw-r--r--core/Command/L10n/CreateJs.php49
-rw-r--r--core/Command/Log/File.php52
-rw-r--r--core/Command/Log/Manage.php42
-rw-r--r--core/Command/Maintenance/DataFingerprint.php38
-rw-r--r--core/Command/Maintenance/Install.php108
-rw-r--r--core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php47
-rw-r--r--core/Command/Maintenance/Mimetype/UpdateDB.php46
-rw-r--r--core/Command/Maintenance/Mimetype/UpdateJS.php33
-rw-r--r--core/Command/Maintenance/Mode.php36
-rw-r--r--core/Command/Maintenance/Repair.php62
-rw-r--r--core/Command/Maintenance/RepairShareOwnership.php52
-rw-r--r--core/Command/Maintenance/UpdateHtaccess.php29
-rw-r--r--core/Command/Maintenance/UpdateTheme.php31
-rw-r--r--core/Command/Memcache/DistributedClear.php47
-rw-r--r--core/Command/Memcache/DistributedDelete.php43
-rw-r--r--core/Command/Memcache/DistributedGet.php40
-rw-r--r--core/Command/Memcache/DistributedSet.php57
-rw-r--r--core/Command/Memcache/RedisCommand.php56
-rw-r--r--core/Command/Preview/Cleanup.php88
-rw-r--r--core/Command/Preview/Generate.php118
-rw-r--r--core/Command/Preview/Repair.php85
-rw-r--r--core/Command/Preview/ResetRenderedTexts.php49
-rw-r--r--core/Command/Router/ListRoutes.php129
-rw-r--r--core/Command/Router/MatchRoute.php100
-rw-r--r--core/Command/Security/BruteforceAttempts.php65
-rw-r--r--core/Command/Security/BruteforceResetAttempts.php45
-rw-r--r--core/Command/Security/ExportCertificates.php35
-rw-r--r--core/Command/Security/ImportCertificate.php31
-rw-r--r--core/Command/Security/ListCertificates.php33
-rw-r--r--core/Command/Security/RemoveCertificate.php31
-rw-r--r--core/Command/Security/ResetBruteforceAttempts.php62
-rw-r--r--core/Command/SetupChecks.php84
-rw-r--r--core/Command/Status.php43
-rw-r--r--core/Command/SystemTag/Add.php31
-rw-r--r--core/Command/SystemTag/Delete.php29
-rw-r--r--core/Command/SystemTag/Edit.php56
-rw-r--r--core/Command/SystemTag/ListCommand.php29
-rw-r--r--core/Command/TaskProcessing/EnabledCommand.php62
-rw-r--r--core/Command/TaskProcessing/GetCommand.php42
-rw-r--r--core/Command/TaskProcessing/ListCommand.php96
-rw-r--r--core/Command/TaskProcessing/Statistics.php194
-rw-r--r--core/Command/TwoFactorAuth/Base.php32
-rw-r--r--core/Command/TwoFactorAuth/Cleanup.php37
-rw-r--r--core/Command/TwoFactorAuth/Disable.php41
-rw-r--r--core/Command/TwoFactorAuth/Enable.php41
-rw-r--r--core/Command/TwoFactorAuth/Enforce.php32
-rw-r--r--core/Command/TwoFactorAuth/State.php51
-rw-r--r--core/Command/Upgrade.php136
-rw-r--r--core/Command/User/Add.php122
-rw-r--r--core/Command/User/AuthTokens/Add.php (renamed from core/Command/User/AddAppPassword.php)62
-rw-r--r--core/Command/User/AuthTokens/Delete.php104
-rw-r--r--core/Command/User/AuthTokens/ListCommand.php84
-rw-r--r--core/Command/User/ClearGeneratedAvatarCacheCommand.php35
-rw-r--r--core/Command/User/Delete.php36
-rw-r--r--core/Command/User/Disable.php31
-rw-r--r--core/Command/User/Enable.php31
-rw-r--r--core/Command/User/Info.php50
-rw-r--r--core/Command/User/Keys/Verify.php83
-rw-r--r--core/Command/User/LastSeen.php93
-rw-r--r--core/Command/User/ListCommand.php79
-rw-r--r--core/Command/User/Profile.php234
-rw-r--r--core/Command/User/Report.php39
-rw-r--r--core/Command/User/ResetPassword.php56
-rw-r--r--core/Command/User/Setting.php48
-rw-r--r--core/Command/User/SyncAccountDataCommand.php84
-rw-r--r--core/Command/User/Welcome.php78
140 files changed, 5551 insertions, 3826 deletions
diff --git a/core/Command/App/Disable.php b/core/Command/App/Disable.php
index 05d35053b13..121ad3f010c 100644
--- a/core/Command/App/Disable.php
+++ b/core/Command/App/Disable.php
@@ -1,26 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\App;
@@ -33,12 +15,12 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Disable extends Command implements CompletionAwareInterface {
- protected IAppManager $appManager;
protected int $exitCode = 0;
- public function __construct(IAppManager $appManager) {
+ public function __construct(
+ protected IAppManager $appManager,
+ ) {
parent::__construct();
- $this->appManager = $appManager;
}
protected function configure(): void {
@@ -63,14 +45,14 @@ class Disable extends Command implements CompletionAwareInterface {
}
private function disableApp(string $appId, OutputInterface $output): void {
- if ($this->appManager->isInstalled($appId) === false) {
+ if ($this->appManager->isEnabledForAnyone($appId) === false) {
$output->writeln('No such app enabled: ' . $appId);
return;
}
try {
$this->appManager->disableApp($appId);
- $appVersion = \OC_App::getAppVersion($appId);
+ $appVersion = $this->appManager->getAppVersion($appId);
$output->writeln($appId . ' ' . $appVersion . ' disabled');
} catch (\Exception $e) {
$output->writeln($e->getMessage());
@@ -83,7 +65,7 @@ class Disable extends Command implements CompletionAwareInterface {
* @param CompletionContext $context
* @return string[]
*/
- public function completeOptionValues($optionName, CompletionContext $context) {
+ public function completeOptionValues($optionName, CompletionContext $context): array {
return [];
}
@@ -92,7 +74,7 @@ class Disable extends Command implements CompletionAwareInterface {
* @param CompletionContext $context
* @return string[]
*/
- public function completeArgumentValues($argumentName, CompletionContext $context) {
+ public function completeArgumentValues($argumentName, CompletionContext $context): array {
if ($argumentName === 'app-id') {
return array_diff(\OC_App::getEnabledApps(true, true), $this->appManager->getAlwaysEnabledApps());
}
diff --git a/core/Command/App/Enable.php b/core/Command/App/Enable.php
index c7a071e27b5..3936acfbf6e 100644
--- a/core/Command/App/Enable.php
+++ b/core/Command/App/Enable.php
@@ -1,27 +1,10 @@
<?php
+
+declare(strict_types=1);
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Sander Ruitenbeek <s.ruitenbeek@getgoing.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\App;
@@ -39,14 +22,14 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Enable extends Command implements CompletionAwareInterface {
- protected IAppManager $appManager;
- protected IGroupManager $groupManager;
protected int $exitCode = 0;
- public function __construct(IAppManager $appManager, IGroupManager $groupManager) {
+ public function __construct(
+ protected IAppManager $appManager,
+ protected IGroupManager $groupManager,
+ private Installer $installer,
+ ) {
parent::__construct();
- $this->appManager = $appManager;
- $this->groupManager = $groupManager;
}
protected function configure(): void {
@@ -75,7 +58,7 @@ class Enable extends Command implements CompletionAwareInterface {
protected function execute(InputInterface $input, OutputInterface $output): int {
$appIds = $input->getArgument('app-id');
$groups = $this->resolveGroupIds($input->getOption('groups'));
- $forceEnable = (bool) $input->getOption('force');
+ $forceEnable = (bool)$input->getOption('force');
foreach ($appIds as $appId) {
$this->enableApp($appId, $groups, $forceEnable, $output);
@@ -95,21 +78,18 @@ class Enable extends Command implements CompletionAwareInterface {
return $group->getDisplayName();
}, $groupIds);
- if ($this->appManager->isInstalled($appId) && $groupIds === []) {
+ if ($this->appManager->isEnabledForUser($appId) && $groupIds === []) {
$output->writeln($appId . ' already enabled');
return;
}
try {
- /** @var Installer $installer */
- $installer = \OC::$server->query(Installer::class);
-
- if (false === $installer->isDownloaded($appId)) {
- $installer->downloadApp($appId);
+ if ($this->installer->isDownloaded($appId) === false) {
+ $this->installer->downloadApp($appId);
}
- $installer->installApp($appId, $forceEnable);
- $appVersion = \OC_App::getAppVersion($appId);
+ $this->installer->installApp($appId, $forceEnable);
+ $appVersion = $this->appManager->getAppVersion($appId);
if ($groupIds === []) {
$this->appManager->enableApp($appId, $forceEnable);
@@ -147,7 +127,7 @@ class Enable extends Command implements CompletionAwareInterface {
* @param CompletionContext $context
* @return string[]
*/
- public function completeOptionValues($optionName, CompletionContext $context) {
+ public function completeOptionValues($optionName, CompletionContext $context): array {
if ($optionName === 'groups') {
return array_map(function (IGroup $group) {
return $group->getGID();
@@ -161,9 +141,9 @@ class Enable extends Command implements CompletionAwareInterface {
* @param CompletionContext $context
* @return string[]
*/
- public function completeArgumentValues($argumentName, CompletionContext $context) {
+ public function completeArgumentValues($argumentName, CompletionContext $context): array {
if ($argumentName === 'app-id') {
- $allApps = \OC_App::getAllApps();
+ $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
index 2ec72385191..3ba4ed7781b 100644
--- a/core/Command/App/GetPath.php
+++ b/core/Command/App/GetPath.php
@@ -1,35 +1,29 @@
<?php
+
+declare(strict_types=1);
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Victor Dubiniuk <dubiniuk@owncloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * 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 {
- protected function configure() {
+ public function __construct(
+ protected IAppManager $appManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
parent::configure();
$this
@@ -46,20 +40,20 @@ class GetPath extends Base {
/**
* Executes the current command.
*
- * @param InputInterface $input An InputInterface instance
+ * @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');
- $path = \OC_App::getAppPath($appName);
- if ($path !== false) {
- $output->writeln($path);
- return 0;
+ try {
+ $path = $this->appManager->getAppPath($appName);
+ } catch (AppPathNotFoundException) {
+ // App not found, exit with non-zero
+ return self::FAILURE;
}
-
- // App not found, exit with non-zero
- return 1;
+ $output->writeln($path);
+ return self::SUCCESS;
}
/**
@@ -67,9 +61,9 @@ class GetPath extends Base {
* @param CompletionContext $context
* @return string[]
*/
- public function completeArgumentValues($argumentName, CompletionContext $context) {
+ public function completeArgumentValues($argumentName, CompletionContext $context): array {
if ($argumentName === 'app') {
- return \OC_App::getAllApps();
+ return $this->appManager->getAllAppsInAppsFolders();
}
return [];
}
diff --git a/core/Command/App/Install.php b/core/Command/App/Install.php
index a699a2e7af0..c8a396c8e36 100644
--- a/core/Command/App/Install.php
+++ b/core/Command/App/Install.php
@@ -1,33 +1,15 @@
<?php
+
+declare(strict_types=1);
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Maxopoly <max@dermax.org>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author sualko <klaus@jsxc.org>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * 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;
@@ -35,7 +17,14 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Install extends Command {
- protected function configure() {
+ public function __construct(
+ protected IAppManager $appManager,
+ private Installer $installer,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
$this
->setName('app:install')
->setDescription('install an app')
@@ -67,34 +56,26 @@ class Install extends Command {
protected function execute(InputInterface $input, OutputInterface $output): int {
$appId = $input->getArgument('app-id');
- $forceEnable = (bool) $input->getOption('force');
+ $forceEnable = (bool)$input->getOption('force');
- if (\OC_App::getAppPath($appId)) {
+ if ($this->appManager->isEnabledForAnyone($appId)) {
$output->writeln($appId . ' already installed');
return 1;
}
try {
- /** @var Installer $installer */
- $installer = \OC::$server->query(Installer::class);
- $installer->downloadApp($appId, $input->getOption('allow-unstable'));
- $result = $installer->installApp($appId, $forceEnable);
+ $this->installer->downloadApp($appId, $input->getOption('allow-unstable'));
+ $result = $this->installer->installApp($appId, $forceEnable);
} catch (\Exception $e) {
$output->writeln('Error: ' . $e->getMessage());
return 1;
}
- if ($result === false) {
- $output->writeln($appId . ' couldn\'t be installed');
- return 1;
- }
-
- $appVersion = \OC_App::getAppVersion($appId);
+ $appVersion = $this->appManager->getAppVersion($appId);
$output->writeln($appId . ' ' . $appVersion . ' installed');
if (!$input->getOption('keep-disabled')) {
- $appClass = new \OC_App();
- $appClass->enable($appId);
+ $this->appManager->enableApp($appId);
$output->writeln($appId . ' enabled');
}
diff --git a/core/Command/App/ListApps.php b/core/Command/App/ListApps.php
index 365ac48e080..dc947bea55f 100644
--- a/core/Command/App/ListApps.php
+++ b/core/Command/App/ListApps.php
@@ -1,27 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Victor Dubiniuk <dubiniuk@owncloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\App;
@@ -33,14 +14,13 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ListApps extends Base {
- protected IAppManager $manager;
-
- public function __construct(IAppManager $manager) {
+ public function __construct(
+ protected IAppManager $appManager,
+ ) {
parent::__construct();
- $this->manager = $manager;
}
- protected function configure() {
+ protected function configure(): void {
parent::configure();
$this
@@ -52,6 +32,18 @@ class ListApps extends Base {
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'
+ )
;
}
@@ -62,32 +54,43 @@ class ListApps extends Base {
$shippedFilter = null;
}
- $apps = \OC_App::getAllApps();
+ $showEnabledApps = $input->getOption('enabled') || !$input->getOption('disabled');
+ $showDisabledApps = $input->getOption('disabled') || !$input->getOption('enabled');
+
+ $apps = $this->appManager->getAllAppsInAppsFolders();
$enabledApps = $disabledApps = [];
- $versions = \OC_App::getAppVersions();
+ $versions = $this->appManager->getAppInstalledVersions();
//sort enabled apps above disabled apps
foreach ($apps as $app) {
- if ($shippedFilter !== null && $this->manager->isShipped($app) !== $shippedFilter) {
+ if ($shippedFilter !== null && $this->appManager->isShipped($app) !== $shippedFilter) {
continue;
}
- if ($this->manager->isInstalled($app)) {
+ if ($this->appManager->isEnabledForAnyone($app)) {
$enabledApps[] = $app;
} else {
$disabledApps[] = $app;
}
}
- $apps = ['enabled' => [], 'disabled' => []];
+ $apps = [];
- sort($enabledApps);
- foreach ($enabledApps as $app) {
- $apps['enabled'][$app] = $versions[$app] ?? true;
+ if ($showEnabledApps) {
+ $apps['enabled'] = [];
+
+ sort($enabledApps);
+ foreach ($enabledApps as $app) {
+ $apps['enabled'][$app] = $versions[$app] ?? true;
+ }
}
- sort($disabledApps);
- foreach ($disabledApps as $app) {
- $apps['disabled'][$app] = $this->manager->getAppVersion($app) . (isset($versions[$app]) ? ' (installed ' . $versions[$app] . ')' : '');
+ 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);
@@ -99,14 +102,18 @@ class ListApps extends Base {
* @param OutputInterface $output
* @param array $items
*/
- protected function writeAppList(InputInterface $input, OutputInterface $output, $items) {
+ protected function writeAppList(InputInterface $input, OutputInterface $output, $items): void {
switch ($input->getOption('output')) {
case self::OUTPUT_FORMAT_PLAIN:
- $output->writeln('Enabled:');
- parent::writeArrayInOutputFormat($input, $output, $items['enabled']);
-
- $output->writeln('Disabled:');
- parent::writeArrayInOutputFormat($input, $output, $items['disabled']);
+ 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:
@@ -120,7 +127,7 @@ class ListApps extends Base {
* @param CompletionContext $context
* @return array
*/
- public function completeOptionValues($optionName, CompletionContext $context) {
+ public function completeOptionValues($optionName, CompletionContext $context): array {
if ($optionName === 'shipped') {
return ['true', 'false'];
}
@@ -132,7 +139,7 @@ class ListApps extends Base {
* @param CompletionContext $context
* @return string[]
*/
- public function completeArgumentValues($argumentName, CompletionContext $context) {
+ public function completeArgumentValues($argumentName, CompletionContext $context): array {
return [];
}
}
diff --git a/core/Command/App/Remove.php b/core/Command/App/Remove.php
index 2aa453132e4..d43bfa96ccc 100644
--- a/core/Command/App/Remove.php
+++ b/core/Command/App/Remove.php
@@ -1,28 +1,10 @@
<?php
+
+declare(strict_types=1);
+
/**
- * @copyright Copyright (c) 2018, Patrik Kernstock <info@pkern.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Patrik Kernstock <info@pkern.at>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\App;
@@ -39,18 +21,15 @@ use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
class Remove extends Command implements CompletionAwareInterface {
- protected IAppManager $manager;
- private Installer $installer;
- private LoggerInterface $logger;
-
- public function __construct(IAppManager $manager, Installer $installer, LoggerInterface $logger) {
+ public function __construct(
+ protected IAppManager $manager,
+ private Installer $installer,
+ private LoggerInterface $logger,
+ ) {
parent::__construct();
- $this->manager = $manager;
- $this->installer = $installer;
- $this->logger = $logger;
}
- protected function configure() {
+ protected function configure(): void {
$this
->setName('app:remove')
->setDescription('remove an app')
@@ -70,9 +49,9 @@ class Remove extends Command implements CompletionAwareInterface {
protected function execute(InputInterface $input, OutputInterface $output): int {
$appId = $input->getArgument('app-id');
- // Check if the app is installed
- if (!\OC_App::getAppPath($appId)) {
- $output->writeln($appId . ' is not installed');
+ // Check if the app is enabled
+ if (!$this->manager->isEnabledForAnyone($appId)) {
+ $output->writeln($appId . ' is not enabled');
return 1;
}
@@ -116,7 +95,7 @@ class Remove extends Command implements CompletionAwareInterface {
return 1;
}
- $appVersion = \OC_App::getAppVersion($appId);
+ $appVersion = $this->manager->getAppVersion($appId);
$output->writeln($appId . ' ' . $appVersion . ' removed');
return 0;
@@ -127,7 +106,7 @@ class Remove extends Command implements CompletionAwareInterface {
* @param CompletionContext $context
* @return string[]
*/
- public function completeOptionValues($optionName, CompletionContext $context) {
+ public function completeOptionValues($optionName, CompletionContext $context): array {
return [];
}
@@ -136,9 +115,9 @@ class Remove extends Command implements CompletionAwareInterface {
* @param CompletionContext $context
* @return string[]
*/
- public function completeArgumentValues($argumentName, CompletionContext $context) {
+ public function completeArgumentValues($argumentName, CompletionContext $context): array {
if ($argumentName === 'app-id') {
- return \OC_App::getAllApps();
+ return $this->manager->getEnabledApps();
}
return [];
}
diff --git a/core/Command/App/Update.php b/core/Command/App/Update.php
index 6a6d43c28e5..71c7f84e5b0 100644
--- a/core/Command/App/Update.php
+++ b/core/Command/App/Update.php
@@ -1,33 +1,15 @@
<?php
+
+declare(strict_types=1);
+
/**
- * @copyright Copyright (c) 2018, michag86 (michag86@arcor.de)
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author michag86 <micha_g@arcor.de>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * 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;
@@ -37,18 +19,15 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Update extends Command {
- protected IAppManager $manager;
- private Installer $installer;
- private LoggerInterface $logger;
-
- public function __construct(IAppManager $manager, Installer $installer, LoggerInterface $logger) {
+ public function __construct(
+ protected IAppManager $manager,
+ private Installer $installer,
+ private LoggerInterface $logger,
+ ) {
parent::__construct();
- $this->manager = $manager;
- $this->installer = $installer;
- $this->logger = $logger;
}
- protected function configure() {
+ protected function configure(): void {
$this
->setName('app:update')
->setDescription('update an app or all apps')
@@ -80,19 +59,20 @@ class Update extends Command {
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 (\OCP\App\AppPathNotFoundException $e) {
+ } catch (AppPathNotFoundException $e) {
$output->writeln($singleAppId . ' not installed');
return 1;
}
} elseif ($input->getOption('all') || $input->getOption('showonly')) {
- $apps = \OC_App::getAllApps();
+ $apps = $this->manager->getAllAppsInAppsFolders();
} else {
- $output->writeln("<error>Please specify an app to update or \"--all\" to update all updatable apps\"</error>");
+ $output->writeln('<error>Please specify an app to update or "--all" to update all updatable apps"</error>');
return 1;
}
@@ -100,6 +80,7 @@ class Update extends Command {
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')) {
@@ -111,19 +92,28 @@ class Update extends Command {
'exception' => $e,
]);
$output->writeln('Error: ' . $e->getMessage());
+ $result = false;
$return = 1;
}
if ($result === false) {
$output->writeln($appId . ' couldn\'t be updated');
$return = 1;
- } elseif ($result === true) {
+ } 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;
}
}
diff --git a/core/Command/Background/Ajax.php b/core/Command/Background/Ajax.php
deleted file mode 100644
index 5dc94d939d7..00000000000
--- a/core/Command/Background/Ajax.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-/**
- * The MIT License (MIT)
- *
- * Copyright (c) 2015 Christian Kampka <christian@kampka.net>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-namespace OC\Core\Command\Background;
-
-class Ajax extends Base {
- protected function getMode() {
- return 'ajax';
- }
-}
diff --git a/core/Command/Background/Base.php b/core/Command/Background/Base.php
deleted file mode 100644
index dca7b58a5fc..00000000000
--- a/core/Command/Background/Base.php
+++ /dev/null
@@ -1,69 +0,0 @@
-<?php
-/**
- * The MIT License (MIT)
- *
- * Copyright (c) 2015 Christian Kampka <christian@kampka.net>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-namespace OC\Core\Command\Background;
-
-use OCP\IConfig;
-
-use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
-
-/**
- * An abstract base class for configuring the background job mode
- * from the command line interface.
- * Subclasses will override the getMode() function to specify the mode to configure.
- */
-abstract class Base extends Command {
- abstract protected function getMode();
- protected IConfig $config;
-
- public function __construct(IConfig $config) {
- parent::__construct();
- $this->config = $config;
- }
-
- protected function configure() {
- $mode = $this->getMode();
- $this
- ->setName("background:$mode")
- ->setDescription("Use $mode to run background jobs");
- }
-
- /**
- * Executing this command will set the background job mode for owncloud.
- * The mode to set is specified by the concrete sub class by implementing the
- * getMode() function.
- *
- * @param InputInterface $input
- * @param OutputInterface $output
- */
- protected function execute(InputInterface $input, OutputInterface $output): int {
- $mode = $this->getMode();
- $this->config->setAppValue('core', 'backgroundjobs_mode', $mode);
- $output->writeln("Set mode for background jobs to '$mode'");
- return 0;
- }
-}
diff --git a/core/Command/Background/Cron.php b/core/Command/Background/Cron.php
deleted file mode 100644
index 9dbb4f855e5..00000000000
--- a/core/Command/Background/Cron.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-/**
- * The MIT License (MIT)
- *
- * Copyright (c) 2015 Christian Kampka <christian@kampka.net>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-namespace OC\Core\Command\Background;
-
-class Cron extends Base {
- protected function getMode() {
- return 'cron';
- }
-}
diff --git a/core/Command/Background/Delete.php b/core/Command/Background/Delete.php
new file mode 100644
index 00000000000..50ae309065b
--- /dev/null
+++ b/core/Command/Background/Delete.php
@@ -0,0 +1,65 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Background;
+
+use OC\Core\Command\Base;
+use OCP\BackgroundJob\IJobList;
+use Symfony\Component\Console\Helper\QuestionHelper;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ConfirmationQuestion;
+
+class Delete extends Base {
+ public function __construct(
+ protected IJobList $jobList,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('background-job:delete')
+ ->setDescription('Remove a background job from database')
+ ->addArgument(
+ 'job-id',
+ InputArgument::REQUIRED,
+ 'The ID of the job in the database'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $jobId = (int)$input->getArgument('job-id');
+
+ $job = $this->jobList->getById($jobId);
+ if ($job === null) {
+ $output->writeln('<error>Job with ID ' . $jobId . ' could not be found in the database</error>');
+ return 1;
+ }
+
+ $output->writeln('Job class: ' . get_class($job));
+ $output->writeln('Arguments: ' . json_encode($job->getArgument()));
+ $output->writeln('');
+
+ $question = new ConfirmationQuestion(
+ '<comment>Do you really want to delete this background job ? It could create some misbehaviours in Nextcloud.</comment> (y/N) ', false,
+ '/^(y|Y)/i'
+ );
+
+ /** @var QuestionHelper $helper */
+ $helper = $this->getHelper('question');
+ if (!$helper->ask($input, $output, $question)) {
+ $output->writeln('aborted.');
+ return 0;
+ }
+
+ $this->jobList->remove($job, $job->getArgument());
+ return 0;
+ }
+}
diff --git a/core/Command/Background/Job.php b/core/Command/Background/Job.php
index 823498cf8ca..9a862f5a13a 100644
--- a/core/Command/Background/Job.php
+++ b/core/Command/Background/Job.php
@@ -2,32 +2,16 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2021, Joas Schilling <coding@schilljs.com>
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Background;
use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\IJobList;
-use OCP\ILogger;
+use OCP\BackgroundJob\QueuedJob;
+use OCP\BackgroundJob\TimedJob;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -35,14 +19,10 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Job extends Command {
- protected IJobList $jobList;
- protected ILogger $logger;
-
- public function __construct(IJobList $jobList,
- ILogger $logger) {
+ public function __construct(
+ protected IJobList $jobList,
+ ) {
parent::__construct();
- $this->jobList = $jobList;
- $this->logger = $logger;
}
protected function configure(): void {
@@ -64,7 +44,7 @@ class Job extends Command {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $jobId = (int) $input->getArgument('job-id');
+ $jobId = (int)$input->getArgument('job-id');
$job = $this->jobList->getById($jobId);
if ($job === null) {
@@ -89,14 +69,15 @@ class Job extends Command {
$output->writeln('<error>Something went wrong when trying to retrieve Job with ID ' . $jobId . ' from database</error>');
return 1;
}
- $job->execute($this->jobList, $this->logger);
+ /** @psalm-suppress DeprecatedMethod Calling execute until it is removed, then will switch to start */
+ $job->execute($this->jobList);
$job = $this->jobList->getById($jobId);
if (($job === null) || ($lastRun !== $job->getLastRun())) {
$output->writeln('<info>Job executed!</info>');
$output->writeln('');
- if ($job instanceof \OC\BackgroundJob\TimedJob || $job instanceof \OCP\BackgroundJob\TimedJob) {
+ if ($job instanceof TimedJob) {
$this->printJobInfo($jobId, $job, $output);
}
} else {
@@ -107,23 +88,23 @@ class Job extends Command {
return 0;
}
- protected function printJobInfo(int $jobId, IJob $job, OutputInterface$output): void {
+ protected function printJobInfo(int $jobId, IJob $job, OutputInterface $output): void {
$row = $this->jobList->getDetailsById($jobId);
$lastRun = new \DateTime();
- $lastRun->setTimestamp((int) $row['last_run']);
+ $lastRun->setTimestamp((int)$row['last_run']);
$lastChecked = new \DateTime();
- $lastChecked->setTimestamp((int) $row['last_checked']);
+ $lastChecked->setTimestamp((int)$row['last_checked']);
$reservedAt = new \DateTime();
- $reservedAt->setTimestamp((int) $row['reserved_at']);
+ $reservedAt->setTimestamp((int)$row['reserved_at']);
$output->writeln('Job class: ' . get_class($job));
$output->writeln('Arguments: ' . json_encode($job->getArgument()));
- $isTimedJob = $job instanceof \OC\BackgroundJob\TimedJob || $job instanceof \OCP\BackgroundJob\TimedJob;
+ $isTimedJob = $job instanceof TimedJob;
if ($isTimedJob) {
$output->writeln('Type: timed');
- } elseif ($job instanceof \OC\BackgroundJob\QueuedJob || $job instanceof \OCP\BackgroundJob\QueuedJob) {
+ } elseif ($job instanceof QueuedJob) {
$output->writeln('Type: queued');
} else {
$output->writeln('Type: job');
@@ -131,7 +112,7 @@ class Job extends Command {
$output->writeln('');
$output->writeln('Last checked: ' . $lastChecked->format(\DateTimeInterface::ATOM));
- if ((int) $row['reserved_at'] === 0) {
+ if ((int)$row['reserved_at'] === 0) {
$output->writeln('Reserved at: -');
} else {
$output->writeln('Reserved at: <comment>' . $reservedAt->format(\DateTimeInterface::ATOM) . '</comment>');
diff --git a/core/Command/Background/JobBase.php b/core/Command/Background/JobBase.php
new file mode 100644
index 00000000000..81d16f874eb
--- /dev/null
+++ b/core/Command/Background/JobBase.php
@@ -0,0 +1,82 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+
+namespace OC\Core\Command\Background;
+
+use OC\Core\Command\Base;
+use OCP\BackgroundJob\IJob;
+use OCP\BackgroundJob\IJobList;
+use OCP\BackgroundJob\QueuedJob;
+use OCP\BackgroundJob\TimedJob;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+abstract class JobBase extends Base {
+
+ public function __construct(
+ protected IJobList $jobList,
+ protected LoggerInterface $logger,
+ ) {
+ parent::__construct();
+ }
+
+ protected function printJobInfo(int $jobId, IJob $job, OutputInterface $output): void {
+ $row = $this->jobList->getDetailsById($jobId);
+
+ if ($row === null) {
+ return;
+ }
+
+ $lastRun = new \DateTime();
+ $lastRun->setTimestamp((int)$row['last_run']);
+ $lastChecked = new \DateTime();
+ $lastChecked->setTimestamp((int)$row['last_checked']);
+ $reservedAt = new \DateTime();
+ $reservedAt->setTimestamp((int)$row['reserved_at']);
+
+ $output->writeln('Job class: ' . get_class($job));
+ $output->writeln('Arguments: ' . json_encode($job->getArgument()));
+
+ $isTimedJob = $job instanceof TimedJob;
+ if ($isTimedJob) {
+ $output->writeln('Type: timed');
+ } elseif ($job instanceof QueuedJob) {
+ $output->writeln('Type: queued');
+ } else {
+ $output->writeln('Type: job');
+ }
+
+ $output->writeln('');
+ $output->writeln('Last checked: ' . $lastChecked->format(\DateTimeInterface::ATOM));
+ if ((int)$row['reserved_at'] === 0) {
+ $output->writeln('Reserved at: -');
+ } else {
+ $output->writeln('Reserved at: <comment>' . $reservedAt->format(\DateTimeInterface::ATOM) . '</comment>');
+ }
+ $output->writeln('Last executed: ' . $lastRun->format(\DateTimeInterface::ATOM));
+ $output->writeln('Last duration: ' . $row['execution_duration']);
+
+ if ($isTimedJob) {
+ $reflection = new \ReflectionClass($job);
+ $intervalProperty = $reflection->getProperty('interval');
+ $intervalProperty->setAccessible(true);
+ $interval = $intervalProperty->getValue($job);
+
+ $nextRun = new \DateTime();
+ $nextRun->setTimestamp((int)$row['last_run'] + $interval);
+
+ if ($nextRun > new \DateTime()) {
+ $output->writeln('Next execution: <comment>' . $nextRun->format(\DateTimeInterface::ATOM) . '</comment>');
+ } else {
+ $output->writeln('Next execution: <info>' . $nextRun->format(\DateTimeInterface::ATOM) . '</info>');
+ }
+ }
+ }
+}
diff --git a/core/Command/Background/JobWorker.php b/core/Command/Background/JobWorker.php
new file mode 100644
index 00000000000..8289021887b
--- /dev/null
+++ b/core/Command/Background/JobWorker.php
@@ -0,0 +1,176 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Background;
+
+use OC\Core\Command\InterruptedException;
+use OC\Files\SetupManager;
+use OCP\BackgroundJob\IJobList;
+use OCP\ITempManager;
+use Psr\Log\LoggerInterface;
+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 JobWorker extends JobBase {
+
+ public function __construct(
+ protected IJobList $jobList,
+ protected LoggerInterface $logger,
+ private ITempManager $tempManager,
+ private SetupManager $setupManager,
+ ) {
+ parent::__construct($jobList, $logger);
+ }
+ protected function configure(): void {
+ parent::configure();
+
+ $this
+ ->setName('background-job:worker')
+ ->setDescription('Run a background job worker')
+ ->addArgument(
+ 'job-classes',
+ InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
+ 'The classes of the jobs to look for in the database'
+ )
+ ->addOption(
+ 'once',
+ null,
+ InputOption::VALUE_NONE,
+ 'Only execute the worker once (as a regular cron execution would do it)'
+ )
+ ->addOption(
+ 'interval',
+ 'i',
+ InputOption::VALUE_OPTIONAL,
+ 'Interval in seconds in which the worker should repeat already processed jobs (set to 0 for no repeat)',
+ 5
+ )
+ ->addOption(
+ 'stop_after',
+ 't',
+ InputOption::VALUE_OPTIONAL,
+ 'Duration after which the worker should stop and exit. The worker won\'t kill a potential running job, it will exit after this job has finished running (supported values are: "30" or "30s" for 30 seconds, "10m" for 10 minutes and "2h" for 2 hours)'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $startTime = time();
+ $stopAfterOptionValue = $input->getOption('stop_after');
+ $stopAfterSeconds = $stopAfterOptionValue === null
+ ? null
+ : $this->parseStopAfter($stopAfterOptionValue);
+ if ($stopAfterSeconds !== null) {
+ $output->writeln('<info>Background job worker will stop after ' . $stopAfterSeconds . ' seconds</info>');
+ }
+
+ $jobClasses = $input->getArgument('job-classes');
+ $jobClasses = empty($jobClasses) ? null : $jobClasses;
+
+ if ($jobClasses !== null) {
+ // at least one class is invalid
+ foreach ($jobClasses as $jobClass) {
+ if (!class_exists($jobClass)) {
+ $output->writeln('<error>Invalid job class: ' . $jobClass . '</error>');
+ return 1;
+ }
+ }
+ }
+
+ while (true) {
+ // Stop if we exceeded stop_after value
+ if ($stopAfterSeconds !== null && ($startTime + $stopAfterSeconds) < time()) {
+ $output->writeln('stop_after time has been exceeded, exiting...', OutputInterface::VERBOSITY_VERBOSE);
+ break;
+ }
+ // Handle canceling of the process
+ try {
+ $this->abortIfInterrupted();
+ } catch (InterruptedException $e) {
+ $output->writeln('<info>Background job worker stopped</info>');
+ break;
+ }
+
+ $this->printSummary($input, $output);
+
+ usleep(50000);
+ $job = $this->jobList->getNext(false, $jobClasses);
+ if (!$job) {
+ if ($input->getOption('once') === true) {
+ if ($jobClasses === null) {
+ $output->writeln('No job is currently queued', OutputInterface::VERBOSITY_VERBOSE);
+ } else {
+ $output->writeln('No job of classes [' . implode(', ', $jobClasses) . '] is currently queued', OutputInterface::VERBOSITY_VERBOSE);
+ }
+ $output->writeln('Exiting...', OutputInterface::VERBOSITY_VERBOSE);
+ break;
+ }
+
+ $output->writeln('Waiting for new jobs to be queued', OutputInterface::VERBOSITY_VERBOSE);
+ // Re-check interval for new jobs
+ sleep(1);
+ continue;
+ }
+
+ $output->writeln('Running job ' . get_class($job) . ' with ID ' . $job->getId());
+
+ if ($output->isVerbose()) {
+ $this->printJobInfo($job->getId(), $job, $output);
+ }
+
+ /** @psalm-suppress DeprecatedMethod Calling execute until it is removed, then will switch to start */
+ $job->execute($this->jobList);
+
+ $output->writeln('Job ' . $job->getId() . ' has finished', OutputInterface::VERBOSITY_VERBOSE);
+
+ // clean up after unclean jobs
+ $this->setupManager->tearDown();
+ $this->tempManager->clean();
+
+ $this->jobList->setLastJob($job);
+ $this->jobList->unlockJob($job);
+
+ if ($input->getOption('once') === true) {
+ break;
+ }
+ }
+
+ return 0;
+ }
+
+ private function printSummary(InputInterface $input, OutputInterface $output): void {
+ if (!$output->isVeryVerbose()) {
+ return;
+ }
+ $output->writeln('<comment>Summary</comment>');
+
+ $counts = [];
+ foreach ($this->jobList->countByClass() as $row) {
+ $counts[] = $row;
+ }
+ $this->writeTableInOutputFormat($input, $output, $counts);
+ }
+
+ private function parseStopAfter(string $value): ?int {
+ if (is_numeric($value)) {
+ return (int)$value;
+ }
+ if (preg_match("/^(\d+)s$/i", $value, $matches)) {
+ return (int)$matches[0];
+ }
+ if (preg_match("/^(\d+)m$/i", $value, $matches)) {
+ return 60 * ((int)$matches[0]);
+ }
+ if (preg_match("/^(\d+)h$/i", $value, $matches)) {
+ return 60 * 60 * ((int)$matches[0]);
+ }
+ return null;
+ }
+}
diff --git a/core/Command/Background/ListCommand.php b/core/Command/Background/ListCommand.php
index 4116bfa0ff1..c8efbfef5c7 100644
--- a/core/Command/Background/ListCommand.php
+++ b/core/Command/Background/ListCommand.php
@@ -2,25 +2,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2022, Côme Chilliet <come.chilliet@nextcloud.com>
- *
- * @author Côme Chilliet <come.chilliet@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Background;
@@ -32,11 +15,10 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ListCommand extends Base {
- protected IJobList $jobList;
-
- public function __construct(IJobList $jobList) {
+ public function __construct(
+ protected IJobList $jobList,
+ ) {
parent::__construct();
- $this->jobList = $jobList;
}
protected function configure(): void {
@@ -54,7 +36,7 @@ class ListCommand extends Base {
'l',
InputOption::VALUE_OPTIONAL,
'Number of jobs to retrieve',
- '10'
+ '500'
)->addOption(
'offset',
'o',
@@ -67,8 +49,12 @@ class ListCommand extends Base {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $jobs = $this->jobList->getJobsIterator($input->getOption('class'), (int)$input->getOption('limit'), (int)$input->getOption('offset'));
- $this->writeTableInOutputFormat($input, $output, $this->formatJobs($jobs));
+ $limit = (int)$input->getOption('limit');
+ $jobsInfo = $this->formatJobs($this->jobList->getJobsIterator($input->getOption('class'), $limit, (int)$input->getOption('offset')));
+ $this->writeTableInOutputFormat($input, $output, $jobsInfo);
+ if ($input->getOption('output') === self::OUTPUT_FORMAT_PLAIN && count($jobsInfo) >= $limit) {
+ $output->writeln("\n<comment>Output is currently limited to " . $limit . ' jobs. Specify `-l, --limit[=LIMIT]` to override.</comment>');
+ }
return 0;
}
diff --git a/core/Command/Background/Mode.php b/core/Command/Background/Mode.php
new file mode 100644
index 00000000000..4c0f40bb4a2
--- /dev/null
+++ b/core/Command/Background/Mode.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2015 Christian Kampka <christian@kampka.net>
+ * SPDX-License-Identifier: MIT
+ */
+namespace OC\Core\Command\Background;
+
+use OCP\IAppConfig;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Mode extends Command {
+ public function __construct(
+ private IAppConfig $appConfig,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('background:cron')
+ ->setAliases(['background:ajax', 'background:webcron'])
+ ->setDescription('Use cron, ajax or webcron to run background jobs');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ /** @var 'background:cron'|'background:ajax'|'background:webcron' $command */
+ $command = $input->getArgument('command');
+
+ $mode = match ($command) {
+ 'background:cron' => 'cron',
+ 'background:ajax' => 'ajax',
+ 'background:webcron' => 'webcron',
+ };
+
+ $this->appConfig->setValueString('core', 'backgroundjobs_mode', $mode);
+ $output->writeln("Set mode for background jobs to '" . $mode . "'");
+
+ return 0;
+ }
+}
diff --git a/core/Command/Background/WebCron.php b/core/Command/Background/WebCron.php
deleted file mode 100644
index 7da379b6a53..00000000000
--- a/core/Command/Background/WebCron.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-/**
- * The MIT License (MIT)
- *
- * Copyright (c) 2015 Christian Kampka <christian@kampka.net>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-namespace OC\Core\Command\Background;
-
-class WebCron extends Base {
- protected function getMode() {
- return 'webcron';
- }
-}
diff --git a/core/Command/Base.php b/core/Command/Base.php
index abf9f95773a..6ab2765b0f9 100644
--- a/core/Command/Base.php
+++ b/core/Command/Base.php
@@ -1,33 +1,15 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command;
use OC\Core\Command\User\ListCommand;
-use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
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\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
@@ -55,22 +37,24 @@ class Base extends Command implements CompletionAwareInterface {
;
}
- protected function writeArrayInOutputFormat(InputInterface $input, OutputInterface $output, array $items, string $prefix = ' - '): void {
+ protected function writeArrayInOutputFormat(InputInterface $input, OutputInterface $output, iterable $items, string $prefix = ' - '): void {
switch ($input->getOption('output')) {
case self::OUTPUT_FORMAT_JSON:
+ $items = (is_array($items) ? $items : iterator_to_array($items));
$output->writeln(json_encode($items));
break;
case self::OUTPUT_FORMAT_JSON_PRETTY:
+ $items = (is_array($items) ? $items : iterator_to_array($items));
$output->writeln(json_encode($items, JSON_PRETTY_PRINT));
break;
default:
foreach ($items as $key => $item) {
- if (is_array($item)) {
+ if (is_iterable($item)) {
$output->writeln($prefix . $key . ':');
$this->writeArrayInOutputFormat($input, $output, $item, ' ' . $prefix);
continue;
}
- if (!is_int($key) || ListCommand::class === get_class($this)) {
+ if (!is_int($key) || get_class($this) === ListCommand::class) {
$value = $this->valueToString($item);
if (!is_null($value)) {
$output->writeln($prefix . $key . ': ' . $value);
@@ -104,6 +88,58 @@ class Base extends Command implements CompletionAwareInterface {
}
}
+ protected function writeStreamingTableInOutputFormat(InputInterface $input, OutputInterface $output, \Iterator $items, int $tableGroupSize): void {
+ switch ($input->getOption('output')) {
+ case self::OUTPUT_FORMAT_JSON:
+ case self::OUTPUT_FORMAT_JSON_PRETTY:
+ $this->writeStreamingJsonArray($input, $output, $items);
+ break;
+ default:
+ foreach ($this->chunkIterator($items, $tableGroupSize) as $chunk) {
+ $this->writeTableInOutputFormat($input, $output, $chunk);
+ }
+ break;
+ }
+ }
+
+ protected function writeStreamingJsonArray(InputInterface $input, OutputInterface $output, \Iterator $items): void {
+ $first = true;
+ $outputType = $input->getOption('output');
+
+ $output->writeln('[');
+ foreach ($items as $item) {
+ if (!$first) {
+ $output->writeln(',');
+ }
+ if ($outputType === self::OUTPUT_FORMAT_JSON_PRETTY) {
+ $output->write(json_encode($item, JSON_PRETTY_PRINT));
+ } else {
+ $output->write(json_encode($item));
+ }
+ $first = false;
+ }
+ $output->writeln("\n]");
+ }
+
+ public function chunkIterator(\Iterator $iterator, int $count): \Iterator {
+ $chunk = [];
+
+ for ($i = 0; $iterator->valid(); $i++) {
+ $chunk[] = $iterator->current();
+ $iterator->next();
+ if (count($chunk) == $count) {
+ // Got a full chunk, yield and start a new one
+ yield $chunk;
+ $chunk = [];
+ }
+ }
+
+ if (count($chunk)) {
+ // Yield the last chunk even if incomplete
+ yield $chunk;
+ }
+ }
+
/**
* @param mixed $item
@@ -134,6 +170,8 @@ class Base extends Command implements CompletionAwareInterface {
return 'true';
} elseif ($value === null) {
return $returnNull ? null : 'null';
+ } if ($value instanceof \UnitEnum) {
+ return $value->value;
} else {
return $value;
}
@@ -161,11 +199,11 @@ class Base extends Command implements CompletionAwareInterface {
*
* Gives a chance to the command to properly terminate what it's doing
*/
- protected function cancelOperation() {
+ public function cancelOperation(): void {
$this->interrupted = true;
}
- public function run(InputInterface $input, OutputInterface $output) {
+ public function run(InputInterface $input, OutputInterface $output): int {
// check if the php pcntl_signal functions are accessible
$this->php_pcntl_signal = function_exists('pcntl_signal');
if ($this->php_pcntl_signal) {
diff --git a/core/Command/Broadcast/Test.php b/core/Command/Broadcast/Test.php
index 7a67c983f79..eb8b49bc3ee 100644
--- a/core/Command/Broadcast/Test.php
+++ b/core/Command/Broadcast/Test.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Broadcast;
@@ -34,11 +16,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Test extends Command {
- private IEventDispatcher $eventDispatcher;
-
- public function __construct(IEventDispatcher $eventDispatcher) {
+ public function __construct(
+ private IEventDispatcher $eventDispatcher,
+ ) {
parent::__construct();
- $this->eventDispatcher = $eventDispatcher;
}
protected function configure(): void {
@@ -63,16 +44,11 @@ class Test extends Command {
$uid = $input->getArgument('uid');
$event = new class($name, $uid) extends ABroadcastedEvent {
- /** @var string */
- private $name;
- /** @var string */
- private $uid;
-
- public function __construct(string $name,
- string $uid) {
+ public function __construct(
+ private string $name,
+ private string $uid,
+ ) {
parent::__construct();
- $this->name = $name;
- $this->uid = $uid;
}
public function broadcastAs(): string {
diff --git a/core/Command/Check.php b/core/Command/Check.php
index 18c45323f37..dcc9b089e1c 100644
--- a/core/Command/Check.php
+++ b/core/Command/Check.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command;
@@ -29,11 +12,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Check extends Base {
- private SystemConfig $config;
-
- public function __construct(SystemConfig $config) {
+ public function __construct(
+ private SystemConfig $config,
+ ) {
parent::__construct();
- $this->config = $config;
}
protected function configure() {
@@ -49,7 +31,7 @@ class Check extends Base {
$errors = \OC_Util::checkServer($this->config);
if (!empty($errors)) {
$errors = array_map(function ($item) {
- return (string) $item['error'];
+ return (string)$item['error'];
}, $errors);
$this->writeArrayInOutputFormat($input, $output, $errors);
diff --git a/core/Command/Config/App/Base.php b/core/Command/Config/App/Base.php
index b40f7c9e48d..e90a8e78f5b 100644
--- a/core/Command/Config/App/Base.php
+++ b/core/Command/Config/App/Base.php
@@ -1,32 +1,23 @@
<?php
+
+declare(strict_types=1);
/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Config\App;
-use OCP\IConfig;
+use OC\Config\ConfigManager;
+use OCP\IAppConfig;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
abstract class Base extends \OC\Core\Command\Base {
- protected IConfig $config;
+ public function __construct(
+ protected IAppConfig $appConfig,
+ protected readonly ConfigManager $configManager,
+ ) {
+ parent::__construct();
+ }
/**
* @param string $argumentName
@@ -35,12 +26,12 @@ abstract class Base extends \OC\Core\Command\Base {
*/
public function completeArgumentValues($argumentName, CompletionContext $context) {
if ($argumentName === 'app') {
- return \OC_App::getAllApps();
+ return $this->appConfig->getApps();
}
if ($argumentName === 'name') {
$appName = $context->getWordAtIndex($context->getWordIndex() - 1);
- return $this->config->getAppKeys($appName);
+ return $this->appConfig->getKeys($appName);
}
return [];
}
diff --git a/core/Command/Config/App/DeleteConfig.php b/core/Command/Config/App/DeleteConfig.php
index 0da1e965bd0..5a08ecbdc42 100644
--- a/core/Command/Config/App/DeleteConfig.php
+++ b/core/Command/Config/App/DeleteConfig.php
@@ -1,43 +1,19 @@
<?php
+
+declare(strict_types=1);
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Config\App;
-use OCP\IConfig;
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 DeleteConfig extends Base {
- protected IConfig $config;
-
- /**
- * @param IConfig $config
- */
- public function __construct(IConfig $config) {
- parent::__construct();
- $this->config = $config;
- }
-
protected function configure() {
parent::configure();
@@ -67,12 +43,12 @@ class DeleteConfig extends Base {
$appName = $input->getArgument('app');
$configName = $input->getArgument('name');
- if ($input->hasParameterOption('--error-if-not-exists') && !in_array($configName, $this->config->getAppKeys($appName))) {
+ if ($input->hasParameterOption('--error-if-not-exists') && !in_array($configName, $this->appConfig->getKeys($appName), true)) {
$output->writeln('<error>Config ' . $configName . ' of app ' . $appName . ' could not be deleted because it did not exist</error>');
return 1;
}
- $this->config->deleteAppValue($appName, $configName);
+ $this->appConfig->deleteKey($appName, $configName);
$output->writeln('<info>Config value ' . $configName . ' of app ' . $appName . ' deleted</info>');
return 0;
}
diff --git a/core/Command/Config/App/GetConfig.php b/core/Command/Config/App/GetConfig.php
index 7fdff2be732..af0c5648232 100644
--- a/core/Command/Config/App/GetConfig.php
+++ b/core/Command/Config/App/GetConfig.php
@@ -1,40 +1,20 @@
<?php
+
+declare(strict_types=1);
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Config\App;
-use OCP\IConfig;
+use OCP\Exceptions\AppConfigUnknownKeyException;
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 GetConfig extends Base {
- protected IConfig $config;
-
- public function __construct(IConfig $config) {
- parent::__construct();
- $this->config = $config;
- }
-
protected function configure() {
parent::configure();
@@ -52,6 +32,18 @@ class GetConfig extends Base {
'Name of the config to get'
)
->addOption(
+ 'details',
+ null,
+ InputOption::VALUE_NONE,
+ 'returns complete details about the app config value'
+ )
+ ->addOption(
+ '--key-details',
+ null,
+ InputOption::VALUE_NONE,
+ 'returns complete details about the app config key'
+ )
+ ->addOption(
'default-value',
null,
InputOption::VALUE_OPTIONAL,
@@ -63,7 +55,7 @@ class GetConfig extends Base {
/**
* Executes the current command.
*
- * @param InputInterface $input An InputInterface instance
+ * @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
* @return int 0 if everything went fine, or an error code
*/
@@ -72,14 +64,27 @@ class GetConfig extends Base {
$configName = $input->getArgument('name');
$defaultValue = $input->getOption('default-value');
- if (!in_array($configName, $this->config->getAppKeys($appName)) && !$input->hasParameterOption('--default-value')) {
- return 1;
+ if ($input->getOption('details')) {
+ $details = $this->appConfig->getDetails($appName, $configName);
+ $details['type'] = $details['typeString'];
+ unset($details['typeString']);
+ $this->writeArrayInOutputFormat($input, $output, $details);
+ return 0;
+ }
+
+ if ($input->getOption('key-details')) {
+ $details = $this->appConfig->getKeyDetails($appName, $configName);
+ $this->writeArrayInOutputFormat($input, $output, $details);
+ return 0;
}
- if (!in_array($configName, $this->config->getAppKeys($appName))) {
+ try {
+ $configValue = $this->appConfig->getDetails($appName, $configName)['value'];
+ } catch (AppConfigUnknownKeyException $e) {
+ if (!$input->hasParameterOption('--default-value')) {
+ return 1;
+ }
$configValue = $defaultValue;
- } else {
- $configValue = $this->config->getAppValue($appName, $configName);
}
$this->writeMixedInOutputFormat($input, $output, $configValue);
diff --git a/core/Command/Config/App/SetConfig.php b/core/Command/Config/App/SetConfig.php
index 89a5f6ba5d1..c818404fc0e 100644
--- a/core/Command/Config/App/SetConfig.php
+++ b/core/Command/Config/App/SetConfig.php
@@ -1,40 +1,24 @@
<?php
+
+declare(strict_types=1);
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Config\App;
-use OCP\IConfig;
+use OC\AppConfig;
+use OCP\Exceptions\AppConfigUnknownKeyException;
+use OCP\IAppConfig;
+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\Question;
class SetConfig extends Base {
- protected IConfig $config;
-
- public function __construct(IConfig $config) {
- parent::__construct();
- $this->config = $config;
- }
-
protected function configure() {
parent::configure();
@@ -58,6 +42,25 @@ class SetConfig extends Base {
'The new value of the config'
)
->addOption(
+ 'type',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Value type [string, integer, float, boolean, array]',
+ 'string'
+ )
+ ->addOption(
+ 'lazy',
+ null,
+ InputOption::VALUE_NEGATABLE,
+ 'Set value as lazy loaded',
+ )
+ ->addOption(
+ 'sensitive',
+ null,
+ InputOption::VALUE_NEGATABLE,
+ 'Set value as sensitive',
+ )
+ ->addOption(
'update-only',
null,
InputOption::VALUE_NONE,
@@ -70,15 +73,166 @@ class SetConfig extends Base {
$appName = $input->getArgument('app');
$configName = $input->getArgument('name');
- if (!in_array($configName, $this->config->getAppKeys($appName)) && $input->hasParameterOption('--update-only')) {
- $output->writeln('<comment>Config value ' . $configName . ' for app ' . $appName . ' not updated, as it has not been set before.</comment>');
+ if (!($this->appConfig instanceof AppConfig)) {
+ throw new \Exception('Only compatible with OC\AppConfig as it uses internal methods');
+ }
+
+ if ($input->hasParameterOption('--update-only') && !$this->appConfig->hasKey($appName, $configName)) {
+ $output->writeln(
+ '<comment>Config value ' . $configName . ' for app ' . $appName
+ . ' not updated, as it has not been set before.</comment>'
+ );
+
return 1;
}
- $configValue = $input->getOption('value');
- $this->config->setAppValue($appName, $configName, $configValue);
+ $type = $typeString = null;
+ if ($input->hasParameterOption('--type')) {
+ $typeString = $input->getOption('type');
+ $type = $this->appConfig->convertTypeToInt($typeString);
+ }
+
+ /**
+ * If --Value is not specified, returns an exception if no value exists in database
+ * compare with current status in database and displays a reminder that this can break things.
+ * confirmation is required by admin, unless --no-interaction
+ */
+ $updated = false;
+ if (!$input->hasParameterOption('--value')) {
+ if (!$input->getOption('lazy') && $this->appConfig->isLazy($appName, $configName) && $this->ask($input, $output, 'NOT LAZY')) {
+ $updated = $this->appConfig->updateLazy($appName, $configName, false);
+ }
+ if ($input->getOption('lazy') && !$this->appConfig->isLazy($appName, $configName) && $this->ask($input, $output, 'LAZY')) {
+ $updated = $this->appConfig->updateLazy($appName, $configName, true) || $updated;
+ }
+ if (!$input->getOption('sensitive') && $this->appConfig->isSensitive($appName, $configName) && $this->ask($input, $output, 'NOT SENSITIVE')) {
+ $updated = $this->appConfig->updateSensitive($appName, $configName, false) || $updated;
+ }
+ if ($input->getOption('sensitive') && !$this->appConfig->isSensitive($appName, $configName) && $this->ask($input, $output, 'SENSITIVE')) {
+ $updated = $this->appConfig->updateSensitive($appName, $configName, true) || $updated;
+ }
+ if ($type !== null && $type !== $this->appConfig->getValueType($appName, $configName) && $typeString !== null && $this->ask($input, $output, $typeString)) {
+ $updated = $this->appConfig->updateType($appName, $configName, $type) || $updated;
+ }
+ } else {
+ /**
+ * If --type is specified in the command line, we upgrade the type in database
+ * after a confirmation from admin.
+ * If not we get the type from current stored value or VALUE_MIXED as default.
+ */
+ try {
+ $currType = $this->appConfig->getValueType($appName, $configName);
+ if ($type === null || $typeString === null || $type === $currType || !$this->ask($input, $output, $typeString)) {
+ $type = $currType;
+ } else {
+ $updated = $this->appConfig->updateType($appName, $configName, $type);
+ }
+ } catch (AppConfigUnknownKeyException) {
+ $type = $type ?? IAppConfig::VALUE_MIXED;
+ }
+
+ /**
+ * if --lazy/--no-lazy option are set, compare with data stored in database.
+ * If no data in database, or identical, continue.
+ * If different, ask admin for confirmation.
+ */
+ $lazy = $input->getOption('lazy');
+ try {
+ $currLazy = $this->appConfig->isLazy($appName, $configName);
+ if ($lazy === null || $lazy === $currLazy || !$this->ask($input, $output, ($lazy) ? 'LAZY' : 'NOT LAZY')) {
+ $lazy = $currLazy;
+ }
+ } catch (AppConfigUnknownKeyException) {
+ $lazy = $lazy ?? false;
+ }
+
+ /**
+ * same with sensitive status
+ */
+ $sensitive = $input->getOption('sensitive');
+ try {
+ $currSensitive = $this->appConfig->isSensitive($appName, $configName, null);
+ if ($sensitive === null || $sensitive === $currSensitive || !$this->ask($input, $output, ($sensitive) ? 'SENSITIVE' : 'NOT SENSITIVE')) {
+ $sensitive = $currSensitive;
+ }
+ } catch (AppConfigUnknownKeyException) {
+ $sensitive = $sensitive ?? false;
+ }
+
+ $value = (string)$input->getOption('value');
+ switch ($type) {
+ case IAppConfig::VALUE_MIXED:
+ $updated = $this->appConfig->setValueMixed($appName, $configName, $value, $lazy, $sensitive);
+ break;
+
+ case IAppConfig::VALUE_STRING:
+ $updated = $this->appConfig->setValueString($appName, $configName, $value, $lazy, $sensitive);
+ break;
+
+ case IAppConfig::VALUE_INT:
+ $updated = $this->appConfig->setValueInt($appName, $configName, $this->configManager->convertToInt($value), $lazy, $sensitive);
+ break;
+
+ case IAppConfig::VALUE_FLOAT:
+ $updated = $this->appConfig->setValueFloat($appName, $configName, $this->configManager->convertToFloat($value), $lazy, $sensitive);
+ break;
+
+ case IAppConfig::VALUE_BOOL:
+ $updated = $this->appConfig->setValueBool($appName, $configName, $this->configManager->convertToBool($value), $lazy);
+ break;
+
+ case IAppConfig::VALUE_ARRAY:
+ $updated = $this->appConfig->setValueArray($appName, $configName, $this->configManager->convertToArray($value), $lazy, $sensitive);
+ break;
+ }
+ }
+
+ if ($updated) {
+ $current = $this->appConfig->getDetails($appName, $configName);
+ $output->writeln(
+ sprintf(
+ "<info>Config value '%s' for app '%s' is now set to '%s', stored as %s in %s</info>",
+ $configName,
+ $appName,
+ $current['sensitive'] ? '<sensitive>' : $current['value'],
+ $current['typeString'],
+ $current['lazy'] ? 'lazy cache' : 'fast cache'
+ )
+ );
+ $keyDetails = $this->appConfig->getKeyDetails($appName, $configName);
+ if (($keyDetails['note'] ?? '') !== '') {
+ $output->writeln('<comment>Note:</comment> ' . $keyDetails['note']);
+ }
+
+ } else {
+ $output->writeln('<info>Config value were not updated</info>');
+ }
- $output->writeln('<info>Config value ' . $configName . ' for app ' . $appName . ' set to ' . $configValue . '</info>');
return 0;
}
+
+ private function ask(InputInterface $input, OutputInterface $output, string $request): bool {
+ /** @var QuestionHelper $helper */
+ $helper = $this->getHelper('question');
+ if ($input->getOption('no-interaction')) {
+ return true;
+ }
+
+ $output->writeln(sprintf('You are about to set config value %s as <info>%s</info>',
+ '<info>' . $input->getArgument('app') . '</info>/<info>' . $input->getArgument('name') . '</info>',
+ strtoupper($request)
+ ));
+ $output->writeln('');
+ $output->writeln('<comment>This might break thing, affect performance on your instance or its security!</comment>');
+
+ $result = (strtolower((string)$helper->ask(
+ $input,
+ $output,
+ new Question('<comment>Confirm this action by typing \'yes\'</comment>: '))) === 'yes');
+
+ $output->writeln(($result) ? 'done' : 'cancelled');
+ $output->writeln('');
+
+ return $result;
+ }
}
diff --git a/core/Command/Config/Import.php b/core/Command/Config/Import.php
index 227c909038c..b58abec3390 100644
--- a/core/Command/Config/Import.php
+++ b/core/Command/Config/Import.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Config;
@@ -35,11 +19,11 @@ use Symfony\Component\Console\Output\OutputInterface;
class Import extends Command implements CompletionAwareInterface {
protected array $validRootKeys = ['system', 'apps'];
- protected IConfig $config;
- public function __construct(IConfig $config) {
+ public function __construct(
+ protected IConfig $config,
+ ) {
parent::__construct();
- $this->config = $config;
}
protected function configure() {
@@ -65,7 +49,7 @@ class Import extends Command implements CompletionAwareInterface {
try {
$configs = $this->validateFileContent($content);
} catch (\UnexpectedValueException $e) {
- $output->writeln('<error>' . $e->getMessage(). '</error>');
+ $output->writeln('<error>' . $e->getMessage() . '</error>');
return 1;
}
diff --git a/core/Command/Config/ListConfigs.php b/core/Command/Config/ListConfigs.php
index dd8fad72d7c..a7c195276eb 100644
--- a/core/Command/Config/ListConfigs.php
+++ b/core/Command/Config/ListConfigs.php
@@ -1,27 +1,13 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Config;
+use OC\Config\ConfigManager;
use OC\Core\Command\Base;
use OC\SystemConfig;
use OCP\IAppConfig;
@@ -33,13 +19,13 @@ use Symfony\Component\Console\Output\OutputInterface;
class ListConfigs extends Base {
protected string $defaultOutputFormat = self::OUTPUT_FORMAT_JSON_PRETTY;
- protected SystemConfig $systemConfig;
- protected IAppConfig $appConfig;
- public function __construct(SystemConfig $systemConfig, IAppConfig $appConfig) {
+ public function __construct(
+ protected SystemConfig $systemConfig,
+ protected IAppConfig $appConfig,
+ protected ConfigManager $configManager,
+ ) {
parent::__construct();
- $this->systemConfig = $systemConfig;
- $this->appConfig = $appConfig;
}
protected function configure() {
@@ -60,6 +46,7 @@ class ListConfigs extends Base {
InputOption::VALUE_NONE,
'Use this option when you want to include sensitive configs like passwords, salts, ...'
)
+ ->addOption('migrate', null, InputOption::VALUE_NONE, 'Rename config keys of all enabled apps, based on ConfigLexicon')
;
}
@@ -67,6 +54,10 @@ class ListConfigs extends Base {
$app = $input->getArgument('app');
$noSensitiveValues = !$input->getOption('private');
+ if ($input->getOption('migrate')) {
+ $this->configManager->migrateConfigLexiconKeys(($app === 'all') ? null : $app);
+ }
+
if (!is_string($app)) {
$output->writeln('<error>Invalid app value given</error>');
return 1;
@@ -92,9 +83,7 @@ class ListConfigs extends Base {
default:
$configs = [
- 'apps' => [
- $app => $this->getAppConfigs($app, $noSensitiveValues),
- ],
+ 'apps' => [$app => $this->getAppConfigs($app, $noSensitiveValues)],
];
}
@@ -108,7 +97,7 @@ class ListConfigs extends Base {
* @param bool $noSensitiveValues
* @return array
*/
- protected function getSystemConfigs($noSensitiveValues) {
+ protected function getSystemConfigs(bool $noSensitiveValues): array {
$keys = $this->systemConfig->getKeys();
$configs = [];
@@ -134,9 +123,9 @@ class ListConfigs extends Base {
* @param bool $noSensitiveValues
* @return array
*/
- protected function getAppConfigs($app, $noSensitiveValues) {
+ protected function getAppConfigs(string $app, bool $noSensitiveValues) {
if ($noSensitiveValues) {
- return $this->appConfig->getFilteredValues($app, false);
+ return $this->appConfig->getFilteredValues($app);
} else {
return $this->appConfig->getValues($app, false);
}
diff --git a/core/Command/Config/Preset.php b/core/Command/Config/Preset.php
new file mode 100644
index 00000000000..ebd8aaa5cdf
--- /dev/null
+++ b/core/Command/Config/Preset.php
@@ -0,0 +1,69 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\Config;
+
+use OC\Config\PresetManager;
+use OC\Core\Command\Base;
+use OCP\Config\Lexicon\Preset as ConfigLexiconPreset;
+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 Preset extends Base {
+ public function __construct(
+ private readonly PresetManager $presetManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure() {
+ parent::configure();
+ $this->setName('config:preset')
+ ->setDescription('Select a config preset')
+ ->addArgument('preset', InputArgument::OPTIONAL, 'Preset to use for all unset config values', '')
+ ->addOption('list', '', InputOption::VALUE_NONE, 'display available preset');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ if ($input->getOption('list')) {
+ $this->getEnum('', $list);
+ $this->writeArrayInOutputFormat($input, $output, $list);
+ return self::SUCCESS;
+ }
+
+ $presetArg = $input->getArgument('preset');
+ if ($presetArg !== '') {
+ $preset = $this->getEnum($presetArg, $list);
+ if ($preset === null) {
+ $output->writeln('<error>Invalid preset: ' . $presetArg . '</error>');
+ $output->writeln('Available presets: ' . implode(', ', $list));
+ return self::INVALID;
+ }
+
+ $this->presetManager->setLexiconPreset($preset);
+ }
+
+ $current = $this->presetManager->getLexiconPreset();
+ $this->writeArrayInOutputFormat($input, $output, [$current->name], 'current preset: ');
+ return self::SUCCESS;
+ }
+
+ private function getEnum(string $name, ?array &$list = null): ?ConfigLexiconPreset {
+ $list = [];
+ foreach (ConfigLexiconPreset::cases() as $case) {
+ $list[] = $case->name;
+ if (strtolower($case->name) === strtolower($name)) {
+ return $case;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/core/Command/Config/System/Base.php b/core/Command/Config/System/Base.php
index 18bc9cb7ca0..088d902b4fd 100644
--- a/core/Command/Config/System/Base.php
+++ b/core/Command/Config/System/Base.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Config\System;
@@ -26,11 +10,10 @@ use OC\SystemConfig;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
abstract class Base extends \OC\Core\Command\Base {
- protected SystemConfig $systemConfig;
-
- public function __construct(SystemConfig $systemConfig) {
+ public function __construct(
+ protected SystemConfig $systemConfig,
+ ) {
parent::__construct();
- $this->systemConfig = $systemConfig;
}
/**
diff --git a/core/Command/Config/System/CastHelper.php b/core/Command/Config/System/CastHelper.php
new file mode 100644
index 00000000000..f2b838bdf9b
--- /dev/null
+++ b/core/Command/Config/System/CastHelper.php
@@ -0,0 +1,76 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Config\System;
+
+class CastHelper {
+ /**
+ * @return array{value: mixed, readable-value: string}
+ */
+ public function castValue(?string $value, string $type): array {
+ switch ($type) {
+ case 'integer':
+ case 'int':
+ if (!is_numeric($value)) {
+ throw new \InvalidArgumentException('Non-numeric value specified');
+ }
+ return [
+ 'value' => (int)$value,
+ 'readable-value' => 'integer ' . (int)$value,
+ ];
+
+ case 'double':
+ case 'float':
+ if (!is_numeric($value)) {
+ throw new \InvalidArgumentException('Non-numeric value specified');
+ }
+ return [
+ 'value' => (float)$value,
+ 'readable-value' => 'double ' . (float)$value,
+ ];
+
+ case 'boolean':
+ case 'bool':
+ $value = strtolower($value);
+ return match ($value) {
+ 'true' => [
+ 'value' => true,
+ 'readable-value' => 'boolean ' . $value,
+ ],
+ 'false' => [
+ 'value' => false,
+ 'readable-value' => 'boolean ' . $value,
+ ],
+ default => throw new \InvalidArgumentException('Unable to parse value as boolean'),
+ };
+
+ case 'null':
+ return [
+ 'value' => null,
+ 'readable-value' => 'null',
+ ];
+
+ case 'string':
+ $value = (string)$value;
+ return [
+ 'value' => $value,
+ 'readable-value' => ($value === '') ? 'empty string' : 'string ' . $value,
+ ];
+
+ case 'json':
+ $value = json_decode($value, true);
+ return [
+ 'value' => $value,
+ 'readable-value' => 'json ' . json_encode($value),
+ ];
+
+ default:
+ throw new \InvalidArgumentException('Invalid type');
+ }
+ }
+}
diff --git a/core/Command/Config/System/DeleteConfig.php b/core/Command/Config/System/DeleteConfig.php
index f4d49ba8f51..03960136f6f 100644
--- a/core/Command/Config/System/DeleteConfig.php
+++ b/core/Command/Config/System/DeleteConfig.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Config\System;
@@ -30,7 +14,9 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class DeleteConfig extends Base {
- public function __construct(SystemConfig $systemConfig) {
+ public function __construct(
+ SystemConfig $systemConfig,
+ ) {
parent::__construct($systemConfig);
}
diff --git a/core/Command/Config/System/GetConfig.php b/core/Command/Config/System/GetConfig.php
index 01bbf82d5d1..c0a9623a84e 100644
--- a/core/Command/Config/System/GetConfig.php
+++ b/core/Command/Config/System/GetConfig.php
@@ -1,24 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Config\System;
@@ -29,7 +14,9 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GetConfig extends Base {
- public function __construct(SystemConfig $systemConfig) {
+ public function __construct(
+ SystemConfig $systemConfig,
+ ) {
parent::__construct($systemConfig);
}
@@ -56,7 +43,7 @@ class GetConfig extends Base {
/**
* Executes the current command.
*
- * @param InputInterface $input An InputInterface instance
+ * @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
* @return int 0 if everything went fine, or an error code
*/
diff --git a/core/Command/Config/System/SetConfig.php b/core/Command/Config/System/SetConfig.php
index 01a1999bcf9..1b1bdc66a6e 100644
--- a/core/Command/Config/System/SetConfig.php
+++ b/core/Command/Config/System/SetConfig.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Config\System;
@@ -32,7 +15,10 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class SetConfig extends Base {
- public function __construct(SystemConfig $systemConfig) {
+ public function __construct(
+ SystemConfig $systemConfig,
+ private CastHelper $castHelper,
+ ) {
parent::__construct($systemConfig);
}
@@ -72,7 +58,7 @@ class SetConfig extends Base {
protected function execute(InputInterface $input, OutputInterface $output): int {
$configNames = $input->getArgument('name');
$configName = $configNames[0];
- $configValue = $this->castValue($input->getOption('value'), $input->getOption('type'));
+ $configValue = $this->castHelper->castValue($input->getOption('value'), $input->getOption('type'));
$updateOnly = $input->getOption('update-only');
if (count($configNames) > 1) {
@@ -96,73 +82,6 @@ class SetConfig extends Base {
}
/**
- * @param string $value
- * @param string $type
- * @return mixed
- * @throws \InvalidArgumentException
- */
- protected function castValue($value, $type) {
- switch ($type) {
- case 'integer':
- case 'int':
- if (!is_numeric($value)) {
- throw new \InvalidArgumentException('Non-numeric value specified');
- }
- return [
- 'value' => (int) $value,
- 'readable-value' => 'integer ' . (int) $value,
- ];
-
- case 'double':
- case 'float':
- if (!is_numeric($value)) {
- throw new \InvalidArgumentException('Non-numeric value specified');
- }
- return [
- 'value' => (double) $value,
- 'readable-value' => 'double ' . (double) $value,
- ];
-
- case 'boolean':
- case 'bool':
- $value = strtolower($value);
- switch ($value) {
- case 'true':
- return [
- 'value' => true,
- 'readable-value' => 'boolean ' . $value,
- ];
-
- case 'false':
- return [
- 'value' => false,
- 'readable-value' => 'boolean ' . $value,
- ];
-
- default:
- throw new \InvalidArgumentException('Unable to parse value as boolean');
- }
-
- // no break
- case 'null':
- return [
- 'value' => null,
- 'readable-value' => 'null',
- ];
-
- case 'string':
- $value = (string) $value;
- return [
- 'value' => $value,
- 'readable-value' => ($value === '') ? 'empty string' : 'string ' . $value,
- ];
-
- default:
- throw new \InvalidArgumentException('Invalid type');
- }
- }
-
- /**
* @param array $configNames
* @param mixed $existingValues
* @param mixed $value
@@ -198,7 +117,7 @@ class SetConfig extends Base {
*/
public function completeOptionValues($optionName, CompletionContext $context) {
if ($optionName === 'type') {
- return ['string', 'integer', 'double', 'boolean'];
+ return ['string', 'integer', 'double', 'boolean', 'json', 'null'];
}
return parent::completeOptionValues($optionName, $context);
}
diff --git a/core/Command/Db/AddMissingColumns.php b/core/Command/Db/AddMissingColumns.php
index acc05c3b7ff..33b4b24a6cb 100644
--- a/core/Command/Db/AddMissingColumns.php
+++ b/core/Command/Db/AddMissingColumns.php
@@ -3,38 +3,19 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Db;
use OC\DB\Connection;
use OC\DB\SchemaWrapper;
-use OCP\IDBConnection;
+use OCP\DB\Events\AddMissingColumnsEvent;
+use OCP\EventDispatcher\IEventDispatcher;
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\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Class AddMissingColumns
@@ -45,64 +26,53 @@ use Symfony\Component\EventDispatcher\GenericEvent;
* @package OC\Core\Command\Db
*/
class AddMissingColumns extends Command {
- private Connection $connection;
- private EventDispatcherInterface $dispatcher;
-
- public function __construct(Connection $connection, EventDispatcherInterface $dispatcher) {
+ public function __construct(
+ private Connection $connection,
+ private IEventDispatcher $dispatcher,
+ ) {
parent::__construct();
-
- $this->connection = $connection;
- $this->dispatcher = $dispatcher;
}
protected function configure() {
$this
->setName('db:add-missing-columns')
->setDescription('Add missing optional columns to the database tables')
- ->addOption('dry-run', null, InputOption::VALUE_NONE, "Output the SQL queries instead of running them.");
+ ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Output the SQL queries instead of running them.');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->addCoreColumns($output, $input->getOption('dry-run'));
+ $dryRun = $input->getOption('dry-run');
// Dispatch event so apps can also update columns if needed
- $event = new GenericEvent($output);
- $this->dispatcher->dispatch(IDBConnection::ADD_MISSING_COLUMNS_EVENT, $event);
- return 0;
- }
-
- /**
- * add missing indices to the share table
- *
- * @param OutputInterface $output
- * @param bool $dryRun If true, will return the sql queries instead of running them.
- * @throws \Doctrine\DBAL\Schema\SchemaException
- */
- private function addCoreColumns(OutputInterface $output, bool $dryRun): void {
- $output->writeln('<info>Check columns of the comments table.</info>');
-
- $schema = new SchemaWrapper($this->connection);
+ $event = new AddMissingColumnsEvent();
+ $this->dispatcher->dispatchTyped($event);
+ $missingColumns = $event->getMissingColumns();
$updated = false;
- if ($schema->hasTable('comments')) {
- $table = $schema->getTable('comments');
- if (!$table->hasColumn('reference_id')) {
- $output->writeln('<info>Adding additional reference_id column to the comments table, this can take some time...</info>');
- $table->addColumn('reference_id', 'string', [
- 'notnull' => false,
- 'length' => 64,
- ]);
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
+ if (!empty($missingColumns)) {
+ $schema = new SchemaWrapper($this->connection);
+
+ foreach ($missingColumns as $missingColumn) {
+ if ($schema->hasTable($missingColumn['tableName'])) {
+ $table = $schema->getTable($missingColumn['tableName']);
+ if (!$table->hasColumn($missingColumn['columnName'])) {
+ $output->writeln('<info>Adding additional ' . $missingColumn['columnName'] . ' column to the ' . $missingColumn['tableName'] . ' table, this can take some time...</info>');
+ $table->addColumn($missingColumn['columnName'], $missingColumn['typeName'], $missingColumn['options']);
+ $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
+ if ($dryRun && $sqlQueries !== null) {
+ $output->writeln($sqlQueries);
+ }
+ $updated = true;
+ $output->writeln('<info>' . $missingColumn['tableName'] . ' table updated successfully.</info>');
+ }
}
- $updated = true;
- $output->writeln('<info>Comments table updated successfully.</info>');
}
}
if (!$updated) {
$output->writeln('<info>Done.</info>');
}
+
+ return 0;
}
}
diff --git a/core/Command/Db/AddMissingIndices.php b/core/Command/Db/AddMissingIndices.php
index 5799a462ffa..eec0aedce11 100644
--- a/core/Command/Db/AddMissingIndices.php
+++ b/core/Command/Db/AddMissingIndices.php
@@ -3,46 +3,19 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org>
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Mario Danic <mario@lovelyhq.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Citharel <nextcloud@tcit.fr>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Db;
-use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use OC\DB\Connection;
use OC\DB\SchemaWrapper;
-use OCP\IDBConnection;
+use OCP\DB\Events\AddMissingIndicesEvent;
+use OCP\EventDispatcher\IEventDispatcher;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Class AddMissingIndices
@@ -53,412 +26,104 @@ use Symfony\Component\EventDispatcher\GenericEvent;
* @package OC\Core\Command\Db
*/
class AddMissingIndices extends Command {
- private Connection $connection;
- private EventDispatcherInterface $dispatcher;
-
- public function __construct(Connection $connection, EventDispatcherInterface $dispatcher) {
+ public function __construct(
+ private Connection $connection,
+ private IEventDispatcher $dispatcher,
+ ) {
parent::__construct();
-
- $this->connection = $connection;
- $this->dispatcher = $dispatcher;
}
protected function configure() {
$this
->setName('db:add-missing-indices')
->setDescription('Add missing indices to the database tables')
- ->addOption('dry-run', null, InputOption::VALUE_NONE, "Output the SQL queries instead of running them.");
+ ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Output the SQL queries instead of running them.');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->addCoreIndexes($output, $input->getOption('dry-run'));
+ $dryRun = $input->getOption('dry-run');
// Dispatch event so apps can also update indexes if needed
- $event = new GenericEvent($output);
- $this->dispatcher->dispatch(IDBConnection::ADD_MISSING_INDEXES_EVENT, $event);
- return 0;
- }
-
- /**
- * add missing indices to the share table
- *
- * @param OutputInterface $output
- * @param bool $dryRun If true, will return the sql queries instead of running them.
- * @throws \Doctrine\DBAL\Schema\SchemaException
- */
- private function addCoreIndexes(OutputInterface $output, bool $dryRun): void {
- $output->writeln('<info>Check indices of the share table.</info>');
-
- $schema = new SchemaWrapper($this->connection);
- $updated = false;
-
- if ($schema->hasTable('share')) {
- $table = $schema->getTable('share');
- if (!$table->hasIndex('share_with_index')) {
- $output->writeln('<info>Adding additional share_with index to the share table, this can take some time...</info>');
- $table->addIndex(['share_with'], 'share_with_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Share table updated successfully.</info>');
- }
-
- if (!$table->hasIndex('parent_index')) {
- $output->writeln('<info>Adding additional parent index to the share table, this can take some time...</info>');
- $table->addIndex(['parent'], 'parent_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Share table updated successfully.</info>');
- }
-
- if (!$table->hasIndex('owner_index')) {
- $output->writeln('<info>Adding additional owner index to the share table, this can take some time...</info>');
- $table->addIndex(['uid_owner'], 'owner_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Share table updated successfully.</info>');
- }
-
- if (!$table->hasIndex('initiator_index')) {
- $output->writeln('<info>Adding additional initiator index to the share table, this can take some time...</info>');
- $table->addIndex(['uid_initiator'], 'initiator_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Share table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the filecache table.</info>');
- if ($schema->hasTable('filecache')) {
- $table = $schema->getTable('filecache');
- if (!$table->hasIndex('fs_mtime')) {
- $output->writeln('<info>Adding additional mtime index to the filecache table, this can take some time...</info>');
- $table->addIndex(['mtime'], 'fs_mtime');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Filecache table updated successfully.</info>');
- }
- if (!$table->hasIndex('fs_size')) {
- $output->writeln('<info>Adding additional size index to the filecache table, this can take some time...</info>');
- $table->addIndex(['size'], 'fs_size');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Filecache table updated successfully.</info>');
- }
- if (!$table->hasIndex('fs_id_storage_size')) {
- $output->writeln('<info>Adding additional size index to the filecache table, this can take some time...</info>');
- $table->addIndex(['fileid', 'storage', 'size'], 'fs_id_storage_size');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Filecache table updated successfully.</info>');
- }
- if (!$table->hasIndex('fs_storage_path_prefix') && !$schema->getDatabasePlatform() instanceof PostgreSQL94Platform) {
- $output->writeln('<info>Adding additional path index to the filecache table, this can take some time...</info>');
- $table->addIndex(['storage', 'path'], 'fs_storage_path_prefix', [], ['lengths' => [null, 64]]);
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Filecache table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the twofactor_providers table.</info>');
- if ($schema->hasTable('twofactor_providers')) {
- $table = $schema->getTable('twofactor_providers');
- if (!$table->hasIndex('twofactor_providers_uid')) {
- $output->writeln('<info>Adding additional twofactor_providers_uid index to the twofactor_providers table, this can take some time...</info>');
- $table->addIndex(['uid'], 'twofactor_providers_uid');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>Twofactor_providers table updated successfully.</info>');
- }
- }
+ $event = new AddMissingIndicesEvent();
+ $this->dispatcher->dispatchTyped($event);
+
+ $missingIndices = $event->getMissingIndices();
+ $toReplaceIndices = $event->getIndicesToReplace();
+
+ if ($missingIndices !== [] || $toReplaceIndices !== []) {
+ $schema = new SchemaWrapper($this->connection);
+
+ foreach ($missingIndices as $missingIndex) {
+ if ($schema->hasTable($missingIndex['tableName'])) {
+ $table = $schema->getTable($missingIndex['tableName']);
+ if (!$table->hasIndex($missingIndex['indexName'])) {
+ $output->writeln('<info>Adding additional ' . $missingIndex['indexName'] . ' index to the ' . $table->getName() . ' table, this can take some time...</info>');
+
+ if ($missingIndex['dropUnnamedIndex']) {
+ foreach ($table->getIndexes() as $index) {
+ $columns = $index->getColumns();
+ if ($columns === $missingIndex['columns']) {
+ $table->dropIndex($index->getName());
+ }
+ }
+ }
- $output->writeln('<info>Check indices of the login_flow_v2 table.</info>');
- if ($schema->hasTable('login_flow_v2')) {
- $table = $schema->getTable('login_flow_v2');
- if (!$table->hasIndex('poll_token')) {
- $output->writeln('<info>Adding additional indeces to the login_flow_v2 table, this can take some time...</info>');
+ if ($missingIndex['uniqueIndex']) {
+ $table->addUniqueIndex($missingIndex['columns'], $missingIndex['indexName'], $missingIndex['options']);
+ } else {
+ $table->addIndex($missingIndex['columns'], $missingIndex['indexName'], [], $missingIndex['options']);
+ }
- foreach ($table->getIndexes() as $index) {
- $columns = $index->getColumns();
- if ($columns === ['poll_token'] ||
- $columns === ['login_token'] ||
- $columns === ['timestamp']) {
- $table->dropIndex($index->getName());
+ if (!$dryRun) {
+ $this->connection->migrateToSchema($schema->getWrappedSchema());
+ }
+ $output->writeln('<info>' . $table->getName() . ' table updated successfully.</info>');
}
}
-
- $table->addUniqueIndex(['poll_token'], 'poll_token');
- $table->addUniqueIndex(['login_token'], 'login_token');
- $table->addIndex(['timestamp'], 'timestamp');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>login_flow_v2 table updated successfully.</info>');
}
- }
- $output->writeln('<info>Check indices of the whats_new table.</info>');
- if ($schema->hasTable('whats_new')) {
- $table = $schema->getTable('whats_new');
- if (!$table->hasIndex('version')) {
- $output->writeln('<info>Adding version index to the whats_new table, this can take some time...</info>');
+ foreach ($toReplaceIndices as $toReplaceIndex) {
+ if ($schema->hasTable($toReplaceIndex['tableName'])) {
+ $table = $schema->getTable($toReplaceIndex['tableName']);
- foreach ($table->getIndexes() as $index) {
- if ($index->getColumns() === ['version']) {
- $table->dropIndex($index->getName());
+ if ($table->hasIndex($toReplaceIndex['newIndexName'])) {
+ continue;
}
- }
- $table->addUniqueIndex(['version'], 'version');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>whats_new table updated successfully.</info>');
- }
- }
+ $output->writeln('<info>Adding additional ' . $toReplaceIndex['newIndexName'] . ' index to the ' . $table->getName() . ' table, this can take some time...</info>');
- $output->writeln('<info>Check indices of the cards table.</info>');
- $cardsUpdated = false;
- if ($schema->hasTable('cards')) {
- $table = $schema->getTable('cards');
-
- if ($table->hasIndex('addressbookid_uri_index')) {
- if ($table->hasIndex('cards_abiduri')) {
- $table->dropIndex('addressbookid_uri_index');
- } else {
- $output->writeln('<info>Renaming addressbookid_uri_index index to cards_abiduri in the cards table, this can take some time...</info>');
-
- foreach ($table->getIndexes() as $index) {
- if ($index->getColumns() === ['addressbookid', 'uri']) {
- $table->renameIndex('addressbookid_uri_index', 'cards_abiduri');
- }
+ if ($toReplaceIndex['uniqueIndex']) {
+ $table->addUniqueIndex($toReplaceIndex['columns'], $toReplaceIndex['newIndexName'], $toReplaceIndex['options']);
+ } else {
+ $table->addIndex($toReplaceIndex['columns'], $toReplaceIndex['newIndexName'], [], $toReplaceIndex['options']);
}
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $cardsUpdated = true;
- }
-
- if (!$table->hasIndex('cards_abid')) {
- $output->writeln('<info>Adding cards_abid index to the cards table, this can take some time...</info>');
-
- foreach ($table->getIndexes() as $index) {
- if ($index->getColumns() === ['addressbookid']) {
- $table->dropIndex($index->getName());
+ if (!$dryRun) {
+ $this->connection->migrateToSchema($schema->getWrappedSchema());
}
- }
- $table->addIndex(['addressbookid'], 'cards_abid');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $cardsUpdated = true;
- }
-
- if (!$table->hasIndex('cards_abiduri')) {
- $output->writeln('<info>Adding cards_abiduri index to the cards table, this can take some time...</info>');
-
- foreach ($table->getIndexes() as $index) {
- if ($index->getColumns() === ['addressbookid', 'uri']) {
- $table->dropIndex($index->getName());
+ foreach ($toReplaceIndex['oldIndexNames'] as $oldIndexName) {
+ if ($table->hasIndex($oldIndexName)) {
+ $output->writeln('<info>Removing ' . $oldIndexName . ' index from the ' . $table->getName() . ' table</info>');
+ $table->dropIndex($oldIndexName);
+ }
}
- }
-
- $table->addIndex(['addressbookid', 'uri'], 'cards_abiduri');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $cardsUpdated = true;
- }
-
- if ($cardsUpdated) {
- $updated = true;
- $output->writeln('<info>cards table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the cards_properties table.</info>');
- if ($schema->hasTable('cards_properties')) {
- $table = $schema->getTable('cards_properties');
- if (!$table->hasIndex('cards_prop_abid')) {
- $output->writeln('<info>Adding cards_prop_abid index to the cards_properties table, this can take some time...</info>');
- foreach ($table->getIndexes() as $index) {
- if ($index->getColumns() === ['addressbookid']) {
- $table->dropIndex($index->getName());
+ if (!$dryRun) {
+ $this->connection->migrateToSchema($schema->getWrappedSchema());
}
+ $output->writeln('<info>' . $table->getName() . ' table updated successfully.</info>');
}
-
- $table->addIndex(['addressbookid'], 'cards_prop_abid');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>cards_properties table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the calendarobjects_props table.</info>');
- if ($schema->hasTable('calendarobjects_props')) {
- $table = $schema->getTable('calendarobjects_props');
- if (!$table->hasIndex('calendarobject_calid_index')) {
- $output->writeln('<info>Adding calendarobject_calid_index index to the calendarobjects_props table, this can take some time...</info>');
-
- $table->addIndex(['calendarid', 'calendartype'], 'calendarobject_calid_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>calendarobjects_props table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the schedulingobjects table.</info>');
- if ($schema->hasTable('schedulingobjects')) {
- $table = $schema->getTable('schedulingobjects');
- if (!$table->hasIndex('schedulobj_principuri_index')) {
- $output->writeln('<info>Adding schedulobj_principuri_index index to the schedulingobjects table, this can take some time...</info>');
-
- $table->addIndex(['principaluri'], 'schedulobj_principuri_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>schedulingobjects table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the oc_properties table.</info>');
- if ($schema->hasTable('properties')) {
- $table = $schema->getTable('properties');
- $propertiesUpdated = false;
-
- if (!$table->hasIndex('properties_path_index')) {
- $output->writeln('<info>Adding properties_path_index index to the oc_properties table, this can take some time...</info>');
-
- $table->addIndex(['userid', 'propertypath'], 'properties_path_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $propertiesUpdated = true;
- }
- if (!$table->hasIndex('properties_pathonly_index')) {
- $output->writeln('<info>Adding properties_pathonly_index index to the oc_properties table, this can take some time...</info>');
-
- $table->addIndex(['propertypath'], 'properties_pathonly_index');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $propertiesUpdated = true;
- }
-
- if ($propertiesUpdated) {
- $updated = true;
- $output->writeln('<info>oc_properties table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the oc_jobs table.</info>');
- if ($schema->hasTable('jobs')) {
- $table = $schema->getTable('jobs');
- if (!$table->hasIndex('job_lastcheck_reserved')) {
- $output->writeln('<info>Adding job_lastcheck_reserved index to the oc_jobs table, this can take some time...</info>');
-
- $table->addIndex(['last_checked', 'reserved_at'], 'job_lastcheck_reserved');
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>oc_properties table updated successfully.</info>');
}
- }
- $output->writeln('<info>Check indices of the oc_direct_edit table.</info>');
- if ($schema->hasTable('direct_edit')) {
- $table = $schema->getTable('direct_edit');
- if (!$table->hasIndex('direct_edit_timestamp')) {
- $output->writeln('<info>Adding direct_edit_timestamp index to the oc_direct_edit table, this can take some time...</info>');
-
- $table->addIndex(['timestamp'], 'direct_edit_timestamp');
+ if ($dryRun) {
$sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
+ if ($sqlQueries !== null) {
$output->writeln($sqlQueries);
}
- $updated = true;
- $output->writeln('<info>oc_direct_edit table updated successfully.</info>');
}
}
- $output->writeln('<info>Check indices of the oc_preferences table.</info>');
- if ($schema->hasTable('preferences')) {
- $table = $schema->getTable('preferences');
- if (!$table->hasIndex('preferences_app_key')) {
- $output->writeln('<info>Adding preferences_app_key index to the oc_preferences table, this can take some time...</info>');
-
- $table->addIndex(['appid', 'configkey'], 'preferences_app_key');
- $this->connection->migrateToSchema($schema->getWrappedSchema());
- $updated = true;
- $output->writeln('<info>oc_properties table updated successfully.</info>');
- }
- }
-
- $output->writeln('<info>Check indices of the oc_mounts table.</info>');
- if ($schema->hasTable('mounts')) {
- $table = $schema->getTable('mounts');
- if (!$table->hasIndex('mounts_class_index')) {
- $output->writeln('<info>Adding mounts_class_index index to the oc_mounts table, this can take some time...</info>');
-
- $table->addIndex(['mount_provider_class'], 'mounts_class_index');
- $this->connection->migrateToSchema($schema->getWrappedSchema());
- $updated = true;
- $output->writeln('<info>oc_mounts table updated successfully.</info>');
- }
- }
-
- if (!$updated) {
- $output->writeln('<info>Done.</info>');
- }
+ return 0;
}
}
diff --git a/core/Command/Db/AddMissingPrimaryKeys.php b/core/Command/Db/AddMissingPrimaryKeys.php
index 8262cf37e77..1eb11c894fa 100644
--- a/core/Command/Db/AddMissingPrimaryKeys.php
+++ b/core/Command/Db/AddMissingPrimaryKeys.php
@@ -3,38 +3,19 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Db;
use OC\DB\Connection;
use OC\DB\SchemaWrapper;
-use OCP\IDBConnection;
+use OCP\DB\Events\AddMissingPrimaryKeyEvent;
+use OCP\EventDispatcher\IEventDispatcher;
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\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Class AddMissingPrimaryKeys
@@ -45,149 +26,59 @@ use Symfony\Component\EventDispatcher\GenericEvent;
* @package OC\Core\Command\Db
*/
class AddMissingPrimaryKeys extends Command {
- private Connection $connection;
- private EventDispatcherInterface $dispatcher;
-
- public function __construct(Connection $connection, EventDispatcherInterface $dispatcher) {
+ public function __construct(
+ private Connection $connection,
+ private IEventDispatcher $dispatcher,
+ ) {
parent::__construct();
-
- $this->connection = $connection;
- $this->dispatcher = $dispatcher;
}
protected function configure() {
$this
->setName('db:add-missing-primary-keys')
->setDescription('Add missing primary keys to the database tables')
- ->addOption('dry-run', null, InputOption::VALUE_NONE, "Output the SQL queries instead of running them.");
+ ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Output the SQL queries instead of running them.');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->addCorePrimaryKeys($output, $input->getOption('dry-run'));
+ $dryRun = $input->getOption('dry-run');
// Dispatch event so apps can also update indexes if needed
- $event = new GenericEvent($output);
- $this->dispatcher->dispatch(IDBConnection::ADD_MISSING_PRIMARY_KEYS_EVENT, $event);
- return 0;
- }
-
- /**
- * add missing indices to the share table
- *
- * @param OutputInterface $output
- * @param bool $dryRun If true, will return the sql queries instead of running them.
- * @throws \Doctrine\DBAL\Schema\SchemaException
- */
- private function addCorePrimaryKeys(OutputInterface $output, bool $dryRun): void {
- $output->writeln('<info>Check primary keys.</info>');
-
- $schema = new SchemaWrapper($this->connection);
+ $event = new AddMissingPrimaryKeyEvent();
+ $this->dispatcher->dispatchTyped($event);
+ $missingKeys = $event->getMissingPrimaryKeys();
$updated = false;
- if ($schema->hasTable('federated_reshares')) {
- $table = $schema->getTable('federated_reshares');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the federated_reshares table, this can take some time...</info>');
- $table->setPrimaryKey(['share_id'], 'federated_res_pk');
- if ($table->hasIndex('share_id_index')) {
- $table->dropIndex('share_id_index');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>federated_reshares table updated successfully.</info>');
- }
- }
-
- if ($schema->hasTable('systemtag_object_mapping')) {
- $table = $schema->getTable('systemtag_object_mapping');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the systemtag_object_mapping table, this can take some time...</info>');
- $table->setPrimaryKey(['objecttype', 'objectid', 'systemtagid'], 'som_pk');
- if ($table->hasIndex('mapping')) {
- $table->dropIndex('mapping');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>systemtag_object_mapping table updated successfully.</info>');
- }
- }
+ if (!empty($missingKeys)) {
+ $schema = new SchemaWrapper($this->connection);
- if ($schema->hasTable('comments_read_markers')) {
- $table = $schema->getTable('comments_read_markers');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the comments_read_markers table, this can take some time...</info>');
- $table->setPrimaryKey(['user_id', 'object_type', 'object_id'], 'crm_pk');
- if ($table->hasIndex('comments_marker_index')) {
- $table->dropIndex('comments_marker_index');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>comments_read_markers table updated successfully.</info>');
- }
- }
+ foreach ($missingKeys as $missingKey) {
+ if ($schema->hasTable($missingKey['tableName'])) {
+ $table = $schema->getTable($missingKey['tableName']);
+ if (!$table->hasPrimaryKey()) {
+ $output->writeln('<info>Adding primary key to the ' . $missingKey['tableName'] . ' table, this can take some time...</info>');
+ $table->setPrimaryKey($missingKey['columns'], $missingKey['primaryKeyName']);
- if ($schema->hasTable('collres_resources')) {
- $table = $schema->getTable('collres_resources');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the collres_resources table, this can take some time...</info>');
- $table->setPrimaryKey(['collection_id', 'resource_type', 'resource_id'], 'crr_pk');
- if ($table->hasIndex('collres_unique_res')) {
- $table->dropIndex('collres_unique_res');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>collres_resources table updated successfully.</info>');
- }
- }
+ if ($missingKey['formerIndex'] && $table->hasIndex($missingKey['formerIndex'])) {
+ $table->dropIndex($missingKey['formerIndex']);
+ }
- if ($schema->hasTable('collres_accesscache')) {
- $table = $schema->getTable('collres_accesscache');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the collres_accesscache table, this can take some time...</info>');
- $table->setPrimaryKey(['user_id', 'collection_id', 'resource_type', 'resource_id'], 'cra_pk');
- if ($table->hasIndex('collres_unique_user')) {
- $table->dropIndex('collres_unique_user');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
- }
- $updated = true;
- $output->writeln('<info>collres_accesscache table updated successfully.</info>');
- }
- }
+ $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
+ if ($dryRun && $sqlQueries !== null) {
+ $output->writeln($sqlQueries);
+ }
- if ($schema->hasTable('filecache_extended')) {
- $table = $schema->getTable('filecache_extended');
- if (!$table->hasPrimaryKey()) {
- $output->writeln('<info>Adding primary key to the filecache_extended table, this can take some time...</info>');
- $table->setPrimaryKey(['fileid'], 'fce_pk');
- if ($table->hasIndex('fce_fileid_idx')) {
- $table->dropIndex('fce_fileid_idx');
- }
- $sqlQueries = $this->connection->migrateToSchema($schema->getWrappedSchema(), $dryRun);
- if ($dryRun && $sqlQueries !== null) {
- $output->writeln($sqlQueries);
+ $updated = true;
+ $output->writeln('<info>' . $missingKey['tableName'] . ' table updated successfully.</info>');
+ }
}
- $updated = true;
- $output->writeln('<info>filecache_extended table updated successfully.</info>');
}
}
if (!$updated) {
$output->writeln('<info>Done.</info>');
}
+
+ return 0;
}
}
diff --git a/core/Command/Db/ConvertFilecacheBigInt.php b/core/Command/Db/ConvertFilecacheBigInt.php
index 9d77ac9a5a0..0d96d139701 100644
--- a/core/Command/Db/ConvertFilecacheBigInt.php
+++ b/core/Command/Db/ConvertFilecacheBigInt.php
@@ -1,51 +1,26 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
- *
- * @author Alecks Gates <alecks.g@gmail.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Georg Ehrke <oc.list@georgehrke.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author timm2k <timm2k@gmx.de>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Db;
-use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Types\Type;
-use OCP\DB\Types;
use OC\DB\Connection;
use OC\DB\SchemaWrapper;
+use OCP\DB\Types;
+use OCP\IDBConnection;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class ConvertFilecacheBigInt extends Command {
- private Connection $connection;
-
- public function __construct(Connection $connection) {
- $this->connection = $connection;
+ public function __construct(
+ private Connection $connection,
+ ) {
parent::__construct();
}
@@ -55,8 +30,10 @@ class ConvertFilecacheBigInt extends Command {
->setDescription('Convert the ID columns of the filecache to BigInt');
}
- protected function getColumnsByTable() {
- // also update in CheckSetupController::hasBigIntConversionPendingColumns()
+ /**
+ * @return array<string,string[]>
+ */
+ public static function getColumnsByTable(): array {
return [
'activity' => ['activity_id', 'object_id'],
'activity_mq' => ['mail_id'],
@@ -78,10 +55,10 @@ class ConvertFilecacheBigInt extends Command {
protected function execute(InputInterface $input, OutputInterface $output): int {
$schema = new SchemaWrapper($this->connection);
- $isSqlite = $this->connection->getDatabasePlatform() instanceof SqlitePlatform;
+ $isSqlite = $this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_SQLITE;
$updates = [];
- $tables = $this->getColumnsByTable();
+ $tables = static::getColumnsByTable();
foreach ($tables as $tableName => $columns) {
if (!$schema->hasTable($tableName)) {
continue;
@@ -114,6 +91,7 @@ class ConvertFilecacheBigInt extends Command {
$output->writeln('<comment>This can take up to hours, depending on the number of files in your instance!</comment>');
if ($input->isInteractive()) {
+ /** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Continue with the conversion (y/n)? [n] ', false);
diff --git a/core/Command/Db/ConvertMysqlToMB4.php b/core/Command/Db/ConvertMysqlToMB4.php
index 19a9532d910..926e56c4300 100644
--- a/core/Command/Db/ConvertMysqlToMB4.php
+++ b/core/Command/Db/ConvertMysqlToMB4.php
@@ -1,30 +1,11 @@
<?php
+
/**
- * @copyright Copyright (c) 2017, ownCloud GmbH
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db;
-use Doctrine\DBAL\Platforms\MySQLPlatform;
use OC\DB\MySqlTools;
use OC\Migration\ConsoleOutput;
use OC\Repair\Collation;
@@ -37,21 +18,12 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ConvertMysqlToMB4 extends Command {
- private IConfig $config;
- private IDBConnection $connection;
- private IURLGenerator $urlGenerator;
- private LoggerInterface $logger;
-
public function __construct(
- IConfig $config,
- IDBConnection $connection,
- IURLGenerator $urlGenerator,
- LoggerInterface $logger
+ private IConfig $config,
+ private IDBConnection $connection,
+ private IURLGenerator $urlGenerator,
+ private LoggerInterface $logger,
) {
- $this->config = $config;
- $this->connection = $connection;
- $this->urlGenerator = $urlGenerator;
- $this->logger = $logger;
parent::__construct();
}
@@ -62,15 +34,15 @@ class ConvertMysqlToMB4 extends Command {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- if (!$this->connection->getDatabasePlatform() instanceof MySQLPlatform) {
- $output->writeln("This command is only valid for MySQL/MariaDB databases.");
+ if ($this->connection->getDatabaseProvider() !== IDBConnection::PLATFORM_MYSQL) {
+ $output->writeln('This command is only valid for MySQL/MariaDB databases.');
return 1;
}
$tools = new MySqlTools();
if (!$tools->supports4ByteCharset($this->connection)) {
$url = $this->urlGenerator->linkToDocs('admin-mysql-utf8mb4');
- $output->writeln("The database is not properly setup to use the charset utf8mb4.");
+ $output->writeln('The database is not properly setup to use the charset utf8mb4.');
$output->writeln("For more information please read the documentation at $url");
return 1;
}
diff --git a/core/Command/Db/ConvertType.php b/core/Command/Db/ConvertType.php
index f7638e3024f..0067bec4d9e 100644
--- a/core/Command/Db/ConvertType.php
+++ b/core/Command/Db/ConvertType.php
@@ -1,46 +1,24 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Andreas Fischer <bantu@owncloud.com>
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Bernhard Ostertag <bernieo.code@gmx.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Łukasz Buśko <busko.lukasz@pm.me>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Sander Ruitenbeek <sander@grids.be>
- * @author Simon Spannagel <simonspa@kth.se>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Schema\AbstractAsset;
use Doctrine\DBAL\Schema\Table;
-use OCP\DB\Types;
use OC\DB\Connection;
use OC\DB\ConnectionFactory;
use OC\DB\MigrationService;
+use OC\DB\PgSqlTools;
+use OCP\App\IAppManager;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\DB\Types;
use OCP\IConfig;
+use OCP\Server;
use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
use Symfony\Component\Console\Command\Command;
@@ -56,13 +34,13 @@ use function preg_match;
use function preg_quote;
class ConvertType extends Command implements CompletionAwareInterface {
- protected IConfig $config;
- protected ConnectionFactory $connectionFactory;
- protected array $columnTypes;
+ protected array $columnTypes = [];
- public function __construct(IConfig $config, ConnectionFactory $connectionFactory) {
- $this->config = $config;
- $this->connectionFactory = $connectionFactory;
+ public function __construct(
+ protected IConfig $config,
+ protected ConnectionFactory $connectionFactory,
+ protected IAppManager $appManager,
+ ) {
parent::__construct();
}
@@ -168,10 +146,13 @@ class ConvertType extends Command implements CompletionAwareInterface {
if ($input->isInteractive()) {
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
- $question = new Question('What is the database password?');
+ $question = new Question('What is the database password (press <enter> for none)? ');
$question->setHidden(true);
$question->setHiddenFallback(false);
$password = $helper->ask($input, $output, $question);
+ if ($password === null) {
+ $password = ''; // possibly unnecessary
+ }
$input->setOption('password', $password);
return;
}
@@ -182,7 +163,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
$this->readPassword($input, $output);
/** @var Connection $fromDB */
- $fromDB = \OC::$server->get(Connection::class);
+ $fromDB = Server::get(Connection::class);
$toDB = $this->getToDBConnection($input, $output);
if ($input->getOption('clear-schema')) {
@@ -200,7 +181,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
$output->writeln('<comment>The following tables will not be converted:</comment>');
$output->writeln($extraFromTables);
if (!$input->getOption('all-apps')) {
- $output->writeln('<comment>Please note that tables belonging to available but currently not installed apps</comment>');
+ $output->writeln('<comment>Please note that tables belonging to disabled (but not removed) apps</comment>');
$output->writeln('<comment>can be included by specifying the --all-apps option.</comment>');
}
@@ -229,11 +210,13 @@ class ConvertType extends Command implements CompletionAwareInterface {
$toMS->migrate($currentMigration);
}
- $apps = $input->getOption('all-apps') ? \OC_App::getAllApps() : \OC_App::getEnabledApps();
+ $apps = $input->getOption('all-apps')
+ ? $this->appManager->getAllAppsInAppsFolders()
+ : $this->appManager->getEnabledApps();
foreach ($apps as $app) {
- $output->writeln('<info> - '.$app.'</info>');
+ $output->writeln('<info> - ' . $app . '</info>');
// Make sure autoloading works...
- \OC_App::loadApp($app);
+ $this->appManager->loadApp($app);
$fromMS = new MigrationService($app, $fromDB);
$currentMigration = $fromMS->getMigration('current');
if ($currentMigration !== '0') {
@@ -245,16 +228,31 @@ class ConvertType extends Command implements CompletionAwareInterface {
protected function getToDBConnection(InputInterface $input, OutputInterface $output) {
$type = $input->getArgument('type');
- $connectionParams = $this->connectionFactory->createConnectionParams();
+ $connectionParams = $this->connectionFactory->createConnectionParams(type: $type);
$connectionParams = array_merge($connectionParams, [
'host' => $input->getArgument('hostname'),
'user' => $input->getArgument('username'),
'password' => $input->getOption('password'),
'dbname' => $input->getArgument('database'),
]);
+
+ // parse port
if ($input->getOption('port')) {
$connectionParams['port'] = $input->getOption('port');
}
+
+ // parse hostname for unix socket
+ if (preg_match('/^(.+)(:(\d+|[^:]+))?$/', $input->getArgument('hostname'), $matches)) {
+ $connectionParams['host'] = $matches[1];
+ if (isset($matches[3])) {
+ if (is_numeric($matches[3])) {
+ $connectionParams['port'] = $matches[3];
+ } else {
+ $connectionParams['unix_socket'] = $matches[3];
+ }
+ }
+ }
+
return $this->connectionFactory->getConnection($type, $connectionParams);
}
@@ -264,7 +262,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
$output->writeln('<info>Clearing schema in new database</info>');
}
foreach ($toTables as $table) {
- $db->getSchemaManager()->dropTable($table);
+ $db->createSchemaManager()->dropTable($table);
}
}
@@ -277,7 +275,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
}
return preg_match($filterExpression, $asset) !== false;
});
- return $db->getSchemaManager()->listTableNames();
+ return $db->createSchemaManager()->listTableNames();
}
/**
@@ -299,7 +297,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
$query->automaticTablePrefix(false);
$query->select($query->func()->count('*', 'num_entries'))
->from($table->getName());
- $result = $query->execute();
+ $result = $query->executeQuery();
$count = $result->fetchOne();
$result->closeCursor();
@@ -338,7 +336,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
for ($chunk = 0; $chunk < $numChunks; $chunk++) {
$query->setFirstResult($chunk * $chunkSize);
- $result = $query->execute();
+ $result = $query->executeQuery();
try {
$toDB->beginTransaction();
@@ -405,11 +403,11 @@ class ConvertType extends Command implements CompletionAwareInterface {
try {
// copy table rows
foreach ($tables as $table) {
- $output->writeln('<info> - '.$table.'</info>');
+ $output->writeln('<info> - ' . $table . '</info>');
$this->copyTable($fromDB, $toDB, $schema->getTable($table), $input, $output);
}
if ($input->getArgument('type') === 'pgsql') {
- $tools = new \OC\DB\PgSqlTools($this->config);
+ $tools = new PgSqlTools($this->config);
$tools->resynchronizeDatabaseSequences($toDB);
}
// save new database config
@@ -428,7 +426,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
$dbName = $input->getArgument('database');
$password = $input->getOption('password');
if ($input->getOption('port')) {
- $dbHost .= ':'.$input->getOption('port');
+ $dbHost .= ':' . $input->getOption('port');
}
$this->config->setSystemValues([
diff --git a/core/Command/Db/ExpectedSchema.php b/core/Command/Db/ExpectedSchema.php
new file mode 100644
index 00000000000..1f35daba089
--- /dev/null
+++ b/core/Command/Db/ExpectedSchema.php
@@ -0,0 +1,68 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Robin Appelman <robin@icewind.nl>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Db;
+
+use Doctrine\DBAL\Schema\Schema;
+use OC\Core\Command\Base;
+use OC\DB\Connection;
+use OC\DB\MigrationService;
+use OC\DB\SchemaWrapper;
+use OC\Migration\NullOutput;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ExpectedSchema extends Base {
+ public function __construct(
+ protected Connection $connection,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('db:schema:expected')
+ ->setDescription('Export the expected database schema for a fresh installation')
+ ->setHelp("Note that the expected schema might not exactly match the exported live schema as the expected schema doesn't take into account any database wide settings or defaults.")
+ ->addOption('sql', null, InputOption::VALUE_NONE, 'Dump the SQL statements for creating the expected schema');
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $schema = new Schema();
+
+ $this->applyMigrations('core', $schema);
+
+ $apps = \OC_App::getEnabledApps();
+ foreach ($apps as $app) {
+ $this->applyMigrations($app, $schema);
+ }
+
+ $sql = $input->getOption('sql');
+ if ($sql) {
+ $output->writeln($schema->toSql($this->connection->getDatabasePlatform()));
+ } else {
+ $encoder = new SchemaEncoder();
+ $this->writeArrayInOutputFormat($input, $output, $encoder->encodeSchema($schema, $this->connection->getDatabasePlatform()));
+ }
+
+ return 0;
+ }
+
+ private function applyMigrations(string $app, Schema $schema): void {
+ $output = new NullOutput();
+ $ms = new MigrationService($app, $this->connection, $output);
+ foreach ($ms->getAvailableVersions() as $version) {
+ $migration = $ms->createInstance($version);
+ $migration->changeSchema($output, function () use (&$schema) {
+ return new SchemaWrapper($this->connection, $schema);
+ }, []);
+ }
+ }
+}
diff --git a/core/Command/Db/ExportSchema.php b/core/Command/Db/ExportSchema.php
new file mode 100644
index 00000000000..581824eea5f
--- /dev/null
+++ b/core/Command/Db/ExportSchema.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Robin Appelman <robin@icewind.nl>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Db;
+
+use OC\Core\Command\Base;
+use OCP\IDBConnection;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ExportSchema extends Base {
+ public function __construct(
+ protected IDBConnection $connection,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('db:schema:export')
+ ->setDescription('Export the current database schema')
+ ->addOption('sql', null, InputOption::VALUE_NONE, 'Dump the SQL statements for creating a copy of the schema');
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $schema = $this->connection->createSchema();
+ $sql = $input->getOption('sql');
+ if ($sql) {
+ $output->writeln($schema->toSql($this->connection->getDatabasePlatform()));
+ } else {
+ $encoder = new SchemaEncoder();
+ $this->writeArrayInOutputFormat($input, $output, $encoder->encodeSchema($schema, $this->connection->getDatabasePlatform()));
+ }
+
+ return 0;
+ }
+}
diff --git a/core/Command/Db/Migrations/ExecuteCommand.php b/core/Command/Db/Migrations/ExecuteCommand.php
index e87e133fa31..a89072c1ad1 100644
--- a/core/Command/Db/Migrations/ExecuteCommand.php
+++ b/core/Command/Db/Migrations/ExecuteCommand.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
- * @copyright Copyright (c) 2017, ownCloud GmbH
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db\Migrations;
@@ -35,13 +19,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ExecuteCommand extends Command implements CompletionAwareInterface {
- private Connection $connection;
- private IConfig $config;
-
- public function __construct(Connection $connection, IConfig $config) {
- $this->connection = $connection;
- $this->config = $config;
-
+ public function __construct(
+ private Connection $connection,
+ private IConfig $config,
+ ) {
parent::__construct();
}
diff --git a/core/Command/Db/Migrations/GenerateCommand.php b/core/Command/Db/Migrations/GenerateCommand.php
index aa93adaebb4..a75280fa8b1 100644
--- a/core/Command/Db/Migrations/GenerateCommand.php
+++ b/core/Command/Db/Migrations/GenerateCommand.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
- * @copyright Copyright (c) 2017, ownCloud GmbH
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db\Migrations;
@@ -33,37 +16,21 @@ use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareI
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class GenerateCommand extends Command implements CompletionAwareInterface {
- protected static $_templateSimple =
- '<?php
+ protected static $_templateSimple
+ = '<?php
declare(strict_types=1);
/**
- * @copyright Copyright (c) {{year}} Your name <your@email.com>
- *
- * @author Your name <your@email.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: {{year}} Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace {{namespace}};
@@ -72,9 +39,10 @@ use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
+use Override;
/**
- * Auto-generated migration step: Please modify to your needs!
+ * FIXME Auto-generated migration step: Please modify to your needs!
*/
class {{classname}} extends SimpleMigrationStep {
@@ -83,6 +51,7 @@ class {{classname}} extends SimpleMigrationStep {
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
*/
+ #[Override]
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
}
@@ -92,6 +61,7 @@ class {{classname}} extends SimpleMigrationStep {
* @param array $options
* @return null|ISchemaWrapper
*/
+ #[Override]
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
{{schemabody}}
}
@@ -99,20 +69,18 @@ class {{classname}} extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
-g * @param array $options
+ * @param array $options
*/
+ #[Override]
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
}
}
';
- protected Connection $connection;
- protected IAppManager $appManager;
-
- public function __construct(Connection $connection, IAppManager $appManager) {
- $this->connection = $connection;
- $this->appManager = $appManager;
-
+ public function __construct(
+ protected Connection $connection,
+ protected IAppManager $appManager,
+ ) {
parent::__construct();
}
@@ -147,7 +115,7 @@ g * @param array $options
if ($fullVersion) {
[$major, $minor] = explode('.', $fullVersion);
- $shouldVersion = (string) ((int)$major * 1000 + (int)$minor);
+ $shouldVersion = (string)((int)$major * 1000 + (int)$minor);
if ($version !== $shouldVersion) {
$output->writeln('<comment>Unexpected migration version for current version: ' . $fullVersion . '</comment>');
$output->writeln('<comment> - Pattern: XYYY </comment>');
@@ -155,6 +123,7 @@ g * @param array $options
$output->writeln('<comment> - Actual: ' . $version . '</comment>');
if ($input->isInteractive()) {
+ /** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Continue with your given version? (y/n) [n] ', false);
@@ -190,7 +159,7 @@ g * @param array $options
*/
public function completeArgumentValues($argumentName, CompletionContext $context) {
if ($argumentName === 'app') {
- $allApps = \OC_App::getAllApps();
+ $allApps = $this->appManager->getAllAppsInAppsFolders();
return array_diff($allApps, \OC_App::getEnabledApps(true, true));
}
@@ -235,7 +204,7 @@ g * @param array $options
$path = $dir . '/' . $className . '.php';
if (file_put_contents($path, $code) === false) {
- throw new RuntimeException('Failed to generate new migration step.');
+ throw new RuntimeException('Failed to generate new migration step. Could not write to ' . $path);
}
return $path;
diff --git a/core/Command/Db/Migrations/GenerateMetadataCommand.php b/core/Command/Db/Migrations/GenerateMetadataCommand.php
new file mode 100644
index 00000000000..581259c99df
--- /dev/null
+++ b/core/Command/Db/Migrations/GenerateMetadataCommand.php
@@ -0,0 +1,79 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\Db\Migrations;
+
+use OC\Migration\MetadataManager;
+use OCP\App\IAppManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @since 30.0.0
+ */
+class GenerateMetadataCommand extends Command {
+ public function __construct(
+ private readonly MetadataManager $metadataManager,
+ private readonly IAppManager $appManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this->setName('migrations:generate-metadata')
+ ->setHidden(true)
+ ->setDescription('Generate metadata from DB migrations - internal and should not be used');
+
+ parent::configure();
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $output->writeln(
+ json_encode(
+ [
+ 'migrations' => $this->extractMigrationMetadata()
+ ],
+ JSON_PRETTY_PRINT
+ )
+ );
+
+ return 0;
+ }
+
+ private function extractMigrationMetadata(): array {
+ return [
+ 'core' => $this->extractMigrationMetadataFromCore(),
+ 'apps' => $this->extractMigrationMetadataFromApps()
+ ];
+ }
+
+ private function extractMigrationMetadataFromCore(): array {
+ return $this->metadataManager->extractMigrationAttributes('core');
+ }
+
+ /**
+ * get all apps and extract attributes
+ *
+ * @return array
+ * @throws \Exception
+ */
+ private function extractMigrationMetadataFromApps(): array {
+ $allApps = $this->appManager->getAllAppsInAppsFolders();
+ $metadata = [];
+ foreach ($allApps as $appId) {
+ // We need to load app before being able to extract Migrations
+ $alreadyLoaded = $this->appManager->isAppLoaded($appId);
+ if (!$alreadyLoaded) {
+ $this->appManager->loadApp($appId);
+ }
+ $metadata[$appId] = $this->metadataManager->extractMigrationAttributes($appId);
+ }
+ return $metadata;
+ }
+}
diff --git a/core/Command/Db/Migrations/MigrateCommand.php b/core/Command/Db/Migrations/MigrateCommand.php
index f0f35716997..2e02f031479 100644
--- a/core/Command/Db/Migrations/MigrateCommand.php
+++ b/core/Command/Db/Migrations/MigrateCommand.php
@@ -1,24 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2017, ownCloud GmbH
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db\Migrations;
@@ -33,10 +18,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MigrateCommand extends Command implements CompletionAwareInterface {
- private Connection $connection;
-
- public function __construct(Connection $connection) {
- $this->connection = $connection;
+ public function __construct(
+ private Connection $connection,
+ ) {
parent::__construct();
}
diff --git a/core/Command/Db/Migrations/PreviewCommand.php b/core/Command/Db/Migrations/PreviewCommand.php
new file mode 100644
index 00000000000..f5b850fff76
--- /dev/null
+++ b/core/Command/Db/Migrations/PreviewCommand.php
@@ -0,0 +1,111 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\Db\Migrations;
+
+use OC\Migration\MetadataManager;
+use OC\Updater\ReleaseMetadata;
+use OCP\Migration\Attributes\MigrationAttribute;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Helper\TableCell;
+use Symfony\Component\Console\Helper\TableCellStyle;
+use Symfony\Component\Console\Helper\TableSeparator;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @since 30.0.0
+ */
+class PreviewCommand extends Command {
+ private bool $initiated = false;
+ public function __construct(
+ private readonly MetadataManager $metadataManager,
+ private readonly ReleaseMetadata $releaseMetadata,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('migrations:preview')
+ ->setDescription('Get preview of available DB migrations in case of initiating an upgrade')
+ ->addArgument('version', InputArgument::REQUIRED, 'The destination version number');
+
+ parent::configure();
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $version = $input->getArgument('version');
+ if (filter_var($version, FILTER_VALIDATE_URL)) {
+ $metadata = $this->releaseMetadata->downloadMetadata($version);
+ } elseif (str_starts_with($version, '/')) {
+ $metadata = json_decode(file_get_contents($version), true, flags: JSON_THROW_ON_ERROR);
+ } else {
+ $metadata = $this->releaseMetadata->getMetadata($version);
+ }
+
+ $parsed = $this->metadataManager->getMigrationsAttributesFromReleaseMetadata($metadata['migrations'] ?? [], true);
+
+ $table = new Table($output);
+ $this->displayMigrations($table, 'core', $parsed['core'] ?? []);
+ foreach ($parsed['apps'] as $appId => $migrations) {
+ if (!empty($migrations)) {
+ $this->displayMigrations($table, $appId, $migrations);
+ }
+ }
+ $table->render();
+
+ $unsupportedApps = $this->metadataManager->getUnsupportedApps($metadata['migrations']);
+ if (!empty($unsupportedApps)) {
+ $output->writeln('');
+ $output->writeln('Those apps are not supporting metadata yet and might initiate migrations on upgrade: <info>' . implode(', ', $unsupportedApps) . '</info>');
+ }
+
+ return 0;
+ }
+
+ private function displayMigrations(Table $table, string $appId, array $data): void {
+ if (empty($data)) {
+ return;
+ }
+
+ if ($this->initiated) {
+ $table->addRow(new TableSeparator());
+ }
+ $this->initiated = true;
+
+ $table->addRow(
+ [
+ new TableCell(
+ $appId,
+ [
+ 'colspan' => 2,
+ 'style' => new TableCellStyle(['cellFormat' => '<info>%s</info>'])
+ ]
+ )
+ ]
+ )->addRow(new TableSeparator());
+
+ /** @var MigrationAttribute[] $attributes */
+ foreach ($data as $migration => $attributes) {
+ $attributesStr = [];
+ if (empty($attributes)) {
+ $attributesStr[] = '<comment>(metadata not set)</comment>';
+ }
+ foreach ($attributes as $attribute) {
+ $definition = '<info>' . $attribute->definition() . '</info>';
+ $definition .= empty($attribute->getDescription()) ? '' : "\n " . $attribute->getDescription();
+ $definition .= empty($attribute->getNotes()) ? '' : "\n <comment>" . implode("</comment>\n <comment>", $attribute->getNotes()) . '</comment>';
+ $attributesStr[] = $definition;
+ }
+ $table->addRow([$migration, implode("\n", $attributesStr)]);
+ }
+ }
+}
diff --git a/core/Command/Db/Migrations/StatusCommand.php b/core/Command/Db/Migrations/StatusCommand.php
index 725ee075215..97ecc76a924 100644
--- a/core/Command/Db/Migrations/StatusCommand.php
+++ b/core/Command/Db/Migrations/StatusCommand.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2017, ownCloud GmbH
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Db\Migrations;
@@ -34,10 +18,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class StatusCommand extends Command implements CompletionAwareInterface {
- private Connection $connection;
-
- public function __construct(Connection $connection) {
- $this->connection = $connection;
+ public function __construct(
+ private Connection $connection,
+ ) {
parent::__construct();
}
diff --git a/core/Command/Db/SchemaEncoder.php b/core/Command/Db/SchemaEncoder.php
new file mode 100644
index 00000000000..beae3a81264
--- /dev/null
+++ b/core/Command/Db/SchemaEncoder.php
@@ -0,0 +1,115 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Robin Appelman <robin@icewind.nl>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Db;
+
+use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\DBAL\Schema\Table;
+use Doctrine\DBAL\Types\PhpIntegerMappingType;
+use Doctrine\DBAL\Types\Type;
+
+class SchemaEncoder {
+ /**
+ * Encode a DBAL schema to json, performing some normalization based on the database platform
+ *
+ * @param Schema $schema
+ * @param AbstractPlatform $platform
+ * @return array
+ */
+ public function encodeSchema(Schema $schema, AbstractPlatform $platform): array {
+ $encoded = ['tables' => [], 'sequences' => []];
+ foreach ($schema->getTables() as $table) {
+ $encoded[$table->getName()] = $this->encodeTable($table, $platform);
+ }
+ ksort($encoded);
+ return $encoded;
+ }
+
+ /**
+ * @psalm-type ColumnArrayType =
+ */
+ private function encodeTable(Table $table, AbstractPlatform $platform): array {
+ $encoded = ['columns' => [], 'indexes' => []];
+ foreach ($table->getColumns() as $column) {
+ /**
+ * @var array{
+ * name: string,
+ * default: mixed,
+ * notnull: bool,
+ * length: ?int,
+ * precision: int,
+ * scale: int,
+ * unsigned: bool,
+ * fixed: bool,
+ * autoincrement: bool,
+ * comment: string,
+ * columnDefinition: ?string,
+ * collation?: string,
+ * charset?: string,
+ * jsonb?: bool,
+ * } $data
+ **/
+ $data = $column->toArray();
+ $data['type'] = Type::getTypeRegistry()->lookupName($column->getType());
+ $data['default'] = $column->getType()->convertToPHPValue($column->getDefault(), $platform);
+ if ($platform instanceof PostgreSQLPlatform) {
+ $data['unsigned'] = false;
+ if ($column->getType() instanceof PhpIntegerMappingType) {
+ $data['length'] = null;
+ }
+ unset($data['jsonb']);
+ } elseif ($platform instanceof AbstractMySqlPlatform) {
+ if ($column->getType() instanceof PhpIntegerMappingType) {
+ $data['length'] = null;
+ } elseif (in_array($data['type'], ['text', 'blob', 'datetime', 'float', 'json'])) {
+ $data['length'] = 0;
+ }
+ unset($data['collation']);
+ unset($data['charset']);
+ }
+ if ($data['type'] === 'string' && $data['length'] === null) {
+ $data['length'] = 255;
+ }
+ $encoded['columns'][$column->getName()] = $data;
+ }
+ ksort($encoded['columns']);
+ foreach ($table->getIndexes() as $index) {
+ $options = $index->getOptions();
+ if (isset($options['lengths']) && count(array_filter($options['lengths'])) === 0) {
+ unset($options['lengths']);
+ }
+ if ($index->isPrimary()) {
+ if ($platform instanceof PostgreSqlPlatform) {
+ $name = $table->getName() . '_pkey';
+ } elseif ($platform instanceof AbstractMySQLPlatform) {
+ $name = 'PRIMARY';
+ } else {
+ $name = $index->getName();
+ }
+ } else {
+ $name = $index->getName();
+ }
+ if ($platform instanceof PostgreSqlPlatform) {
+ $name = strtolower($name);
+ }
+ $encoded['indexes'][$name] = [
+ 'name' => $name,
+ 'columns' => $index->getColumns(),
+ 'unique' => $index->isUnique(),
+ 'primary' => $index->isPrimary(),
+ 'flags' => $index->getFlags(),
+ 'options' => $options,
+ ];
+ }
+ ksort($encoded['indexes']);
+ return $encoded;
+ }
+}
diff --git a/core/Command/Encryption/ChangeKeyStorageRoot.php b/core/Command/Encryption/ChangeKeyStorageRoot.php
index 6ae59421a69..3049fd2ca08 100644
--- a/core/Command/Encryption/ChangeKeyStorageRoot.php
+++ b/core/Command/Encryption/ChangeKeyStorageRoot.php
@@ -1,27 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Björn Schießle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Encryption;
@@ -40,19 +22,14 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class ChangeKeyStorageRoot extends Command {
- protected View $rootView;
- protected IUserManager $userManager;
- protected IConfig $config;
- protected Util $util;
- protected QuestionHelper $questionHelper;
-
- public function __construct(View $view, IUserManager $userManager, IConfig $config, Util $util, QuestionHelper $questionHelper) {
+ public function __construct(
+ protected View $rootView,
+ protected IUserManager $userManager,
+ protected IConfig $config,
+ protected Util $util,
+ protected QuestionHelper $questionHelper,
+ ) {
parent::__construct();
- $this->rootView = $view;
- $this->userManager = $userManager;
- $this->config = $config;
- $this->util = $util;
- $this->questionHelper = $questionHelper;
}
protected function configure() {
@@ -102,10 +79,10 @@ class ChangeKeyStorageRoot extends Command {
* @throws \Exception
*/
protected function moveAllKeys($oldRoot, $newRoot, OutputInterface $output) {
- $output->writeln("Start to move keys:");
+ $output->writeln('Start to move keys:');
if ($this->rootView->is_dir($oldRoot) === false) {
- $output->writeln("No old keys found: Nothing needs to be moved");
+ $output->writeln('No old keys found: Nothing needs to be moved');
return false;
}
@@ -146,8 +123,8 @@ class ChangeKeyStorageRoot extends Command {
*/
protected function moveSystemKeys($oldRoot, $newRoot) {
if (
- $this->rootView->is_dir($oldRoot . '/files_encryption') &&
- $this->targetExists($newRoot . '/files_encryption') === false
+ $this->rootView->is_dir($oldRoot . '/files_encryption')
+ && $this->targetExists($newRoot . '/files_encryption') === false
) {
$this->rootView->rename($oldRoot . '/files_encryption', $newRoot . '/files_encryption');
}
@@ -206,8 +183,8 @@ class ChangeKeyStorageRoot extends Command {
$source = $oldRoot . '/' . $user . '/files_encryption';
$target = $newRoot . '/' . $user . '/files_encryption';
if (
- $this->rootView->is_dir($source) &&
- $this->targetExists($target) === false
+ $this->rootView->is_dir($source)
+ && $this->targetExists($target) === false
) {
$this->prepareParentFolder($newRoot . '/' . $user);
$this->rootView->rename($source, $target);
diff --git a/core/Command/Encryption/DecryptAll.php b/core/Command/Encryption/DecryptAll.php
index ce17f787abd..92e2ba787e2 100644
--- a/core/Command/Encryption/DecryptAll.php
+++ b/core/Command/Encryption/DecryptAll.php
@@ -1,32 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Björn Schießle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author davitol <dtoledo@solidgear.es>
- * @author Evgeny Golyshev <eugulixes@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Marius Blüm <marius@lineone.io>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Ruben Homs <ruben@homs.codes>
- * @author Sergio Bertolín <sbertolin@solidgear.es>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Encryption;
@@ -41,28 +18,17 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class DecryptAll extends Command {
- protected IManager $encryptionManager;
- protected IAppManager $appManager;
- protected IConfig $config;
- protected QuestionHelper $questionHelper;
- protected bool $wasTrashbinEnabled;
- protected bool $wasMaintenanceModeEnabled;
- protected \OC\Encryption\DecryptAll $decryptAll;
+ protected bool $wasTrashbinEnabled = false;
+ protected bool $wasMaintenanceModeEnabled = false;
public function __construct(
- IManager $encryptionManager,
- IAppManager $appManager,
- IConfig $config,
- \OC\Encryption\DecryptAll $decryptAll,
- QuestionHelper $questionHelper
+ protected IManager $encryptionManager,
+ protected IAppManager $appManager,
+ protected IConfig $config,
+ protected \OC\Encryption\DecryptAll $decryptAll,
+ protected QuestionHelper $questionHelper,
) {
parent::__construct();
-
- $this->appManager = $appManager;
- $this->encryptionManager = $encryptionManager;
- $this->config = $config;
- $this->decryptAll = $decryptAll;
- $this->questionHelper = $questionHelper;
}
/**
@@ -115,10 +81,10 @@ class DecryptAll extends Command {
$isMaintenanceModeEnabled = $this->config->getSystemValue('maintenance', false);
if ($isMaintenanceModeEnabled) {
- $output->writeln("Maintenance mode must be disabled when starting decryption,");
- $output->writeln("in order to load the relevant encryption modules correctly.");
- $output->writeln("Your instance will automatically be put to maintenance mode");
- $output->writeln("during the actual decryption of the files.");
+ $output->writeln('Maintenance mode must be disabled when starting decryption,');
+ $output->writeln('in order to load the relevant encryption modules correctly.');
+ $output->writeln('Your instance will automatically be put to maintenance mode');
+ $output->writeln('during the actual decryption of the files.');
return 1;
}
diff --git a/core/Command/Encryption/Disable.php b/core/Command/Encryption/Disable.php
index 446601a1b4f..91d4ac82d23 100644
--- a/core/Command/Encryption/Disable.php
+++ b/core/Command/Encryption/Disable.php
@@ -1,23 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Encryption;
@@ -27,11 +13,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Disable extends Command {
- protected IConfig $config;
-
- public function __construct(IConfig $config) {
+ public function __construct(
+ protected IConfig $config,
+ ) {
parent::__construct();
- $this->config = $config;
}
protected function configure() {
diff --git a/core/Command/Encryption/Enable.php b/core/Command/Encryption/Enable.php
index 9d680144e60..2c476185692 100644
--- a/core/Command/Encryption/Enable.php
+++ b/core/Command/Encryption/Enable.php
@@ -1,24 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Encryption;
@@ -29,14 +14,11 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Enable extends Command {
- protected IConfig $config;
- protected IManager $encryptionManager;
-
- public function __construct(IConfig $config, IManager $encryptionManager) {
+ public function __construct(
+ protected IConfig $config,
+ protected IManager $encryptionManager,
+ ) {
parent::__construct();
-
- $this->encryptionManager = $encryptionManager;
- $this->config = $config;
}
protected function configure() {
@@ -59,18 +41,18 @@ class Enable extends Command {
if (empty($modules)) {
$output->writeln('<error>No encryption module is loaded</error>');
return 1;
- } else {
- $defaultModule = $this->config->getAppValue('core', 'default_encryption_module', null);
- if ($defaultModule === null) {
- $output->writeln('<error>No default module is set</error>');
- return 1;
- } elseif (!isset($modules[$defaultModule])) {
- $output->writeln('<error>The current default module does not exist: ' . $defaultModule . '</error>');
- return 1;
- } else {
- $output->writeln('Default module: ' . $defaultModule);
- }
}
+ $defaultModule = $this->config->getAppValue('core', 'default_encryption_module', null);
+ if ($defaultModule === null) {
+ $output->writeln('<error>No default module is set</error>');
+ return 1;
+ }
+ if (!isset($modules[$defaultModule])) {
+ $output->writeln('<error>The current default module does not exist: ' . $defaultModule . '</error>');
+ return 1;
+ }
+ $output->writeln('Default module: ' . $defaultModule);
+
return 0;
}
}
diff --git a/core/Command/Encryption/EncryptAll.php b/core/Command/Encryption/EncryptAll.php
index 11e33ae9e2e..f2c991471b6 100644
--- a/core/Command/Encryption/EncryptAll.php
+++ b/core/Command/Encryption/EncryptAll.php
@@ -1,28 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Björn Schießle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Evgeny Golyshev <eugulixes@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Matthew Setter <matthew@matthewsetter.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Encryption;
@@ -36,24 +17,15 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class EncryptAll extends Command {
- protected IManager $encryptionManager;
- protected IAppManager $appManager;
- protected IConfig $config;
- protected QuestionHelper $questionHelper;
protected bool $wasTrashbinEnabled = false;
- protected bool $wasMaintenanceModeEnabled;
public function __construct(
- IManager $encryptionManager,
- IAppManager $appManager,
- IConfig $config,
- QuestionHelper $questionHelper
+ protected IManager $encryptionManager,
+ protected IAppManager $appManager,
+ protected IConfig $config,
+ protected QuestionHelper $questionHelper,
) {
parent::__construct();
- $this->appManager = $appManager;
- $this->encryptionManager = $encryptionManager;
- $this->config = $config;
- $this->questionHelper = $questionHelper;
}
/**
@@ -61,7 +33,6 @@ class EncryptAll extends Command {
*/
protected function forceMaintenanceAndTrashbin(): void {
$this->wasTrashbinEnabled = (bool)$this->appManager->isEnabledForUser('files_trashbin');
- $this->wasMaintenanceModeEnabled = $this->config->getSystemValueBool('maintenance');
$this->config->setSystemValue('maintenance', true);
$this->appManager->disableApp('files_trashbin');
}
@@ -70,7 +41,7 @@ class EncryptAll extends Command {
* Reset the maintenance mode and re-enable the trashbin app
*/
protected function resetMaintenanceAndTrashbin(): void {
- $this->config->setSystemValue('maintenance', $this->wasMaintenanceModeEnabled);
+ $this->config->setSystemValue('maintenance', false);
if ($this->wasTrashbinEnabled) {
$this->appManager->enableApp('files_trashbin');
}
@@ -101,6 +72,11 @@ class EncryptAll extends Command {
throw new \Exception('Server side encryption is not enabled');
}
+ if ($this->config->getSystemValueBool('maintenance')) {
+ $output->writeln('<error>This command cannot be run with maintenance mode enabled.</error>');
+ return self::FAILURE;
+ }
+
$output->writeln("\n");
$output->writeln('You are about to encrypt all files stored in your Nextcloud installation.');
$output->writeln('Depending on the number of available files, and their size, this may take quite some time.');
@@ -120,9 +96,9 @@ class EncryptAll extends Command {
}
$this->resetMaintenanceAndTrashbin();
- return 0;
+ return self::SUCCESS;
}
$output->writeln('aborted');
- return 1;
+ return self::FAILURE;
}
}
diff --git a/core/Command/Encryption/ListModules.php b/core/Command/Encryption/ListModules.php
index 88ad9875073..bf02c29f432 100644
--- a/core/Command/Encryption/ListModules.php
+++ b/core/Command/Encryption/ListModules.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Ruben Homs <ruben@homs.codes>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Encryption;
@@ -30,16 +14,11 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ListModules extends Base {
- protected IManager $encryptionManager;
- protected IConfig $config;
-
public function __construct(
- IManager $encryptionManager,
- IConfig $config
+ protected IManager $encryptionManager,
+ protected IConfig $config,
) {
parent::__construct();
- $this->encryptionManager = $encryptionManager;
- $this->config = $config;
}
protected function configure() {
@@ -54,8 +33,8 @@ class ListModules extends Base {
protected function execute(InputInterface $input, OutputInterface $output): int {
$isMaintenanceModeEnabled = $this->config->getSystemValue('maintenance', false);
if ($isMaintenanceModeEnabled) {
- $output->writeln("Maintenance mode must be disabled when listing modules");
- $output->writeln("in order to list the relevant encryption modules correctly.");
+ $output->writeln('Maintenance mode must be disabled when listing modules');
+ $output->writeln('in order to list the relevant encryption modules correctly.');
return 1;
}
@@ -78,7 +57,7 @@ class ListModules extends Base {
*/
protected function writeModuleList(InputInterface $input, OutputInterface $output, $items) {
if ($input->getOption('output') === self::OUTPUT_FORMAT_PLAIN) {
- array_walk($items, function (&$item) {
+ array_walk($items, function (&$item): void {
if (!$item['default']) {
$item = $item['displayName'];
} else {
diff --git a/core/Command/Encryption/MigrateKeyStorage.php b/core/Command/Encryption/MigrateKeyStorage.php
index 8d9c7910769..937b17cde5f 100644
--- a/core/Command/Encryption/MigrateKeyStorage.php
+++ b/core/Command/Encryption/MigrateKeyStorage.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Encryption;
@@ -33,28 +16,21 @@ use OCP\IUserManager;
use OCP\Security\ICrypto;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
-use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MigrateKeyStorage extends Command {
- protected View $rootView;
- protected IUserManager $userManager;
- protected IConfig $config;
- protected Util $util;
- protected QuestionHelper $questionHelper;
- private ICrypto $crypto;
-
- public function __construct(View $view, IUserManager $userManager, IConfig $config, Util $util, ICrypto $crypto) {
+ public function __construct(
+ protected View $rootView,
+ protected IUserManager $userManager,
+ protected IConfig $config,
+ protected Util $util,
+ private ICrypto $crypto,
+ ) {
parent::__construct();
- $this->rootView = $view;
- $this->userManager = $userManager;
- $this->config = $config;
- $this->util = $util;
- $this->crypto = $crypto;
}
- protected function configure() {
+ protected function configure(): void {
parent::configure();
$this
->setName('encryption:migrate-key-storage-format')
@@ -64,9 +40,9 @@ class MigrateKeyStorage extends Command {
protected function execute(InputInterface $input, OutputInterface $output): int {
$root = $this->util->getKeyStorageRoot();
- $output->writeln("Updating key storage format");
+ $output->writeln('Updating key storage format');
$this->updateKeys($root, $output);
- $output->writeln("Key storage format successfully updated");
+ $output->writeln('Key storage format successfully updated');
return 0;
}
@@ -74,15 +50,12 @@ class MigrateKeyStorage extends Command {
/**
* Move keys to new key storage root
*
- * @param string $root
- * @param OutputInterface $output
- * @return bool
* @throws \Exception
*/
protected function updateKeys(string $root, OutputInterface $output): bool {
- $output->writeln("Start to update the keys:");
+ $output->writeln('Start to update the keys:');
- $this->updateSystemKeys($root);
+ $this->updateSystemKeys($root, $output);
$this->updateUsersKeys($root, $output);
$this->config->deleteSystemValue('encryption.key_storage_migrated');
return true;
@@ -91,15 +64,15 @@ class MigrateKeyStorage extends Command {
/**
* Move system key folder
*/
- protected function updateSystemKeys(string $root): void {
+ protected function updateSystemKeys(string $root, OutputInterface $output): void {
if (!$this->rootView->is_dir($root . '/files_encryption')) {
return;
}
- $this->traverseKeys($root . '/files_encryption', null);
+ $this->traverseKeys($root . '/files_encryption', null, $output);
}
- private function traverseKeys(string $folder, ?string $uid) {
+ private function traverseKeys(string $folder, ?string $uid, OutputInterface $output): void {
$listing = $this->rootView->getDirectoryContent($folder);
foreach ($listing as $node) {
@@ -107,14 +80,19 @@ class MigrateKeyStorage extends Command {
continue;
}
- if ($node['name'] === 'fileKey' ||
- str_ends_with($node['name'], '.privateKey') ||
- str_ends_with($node['name'], '.publicKey') ||
- str_ends_with($node['name'], '.shareKey')) {
+ if ($node['name'] === 'fileKey'
+ || str_ends_with($node['name'], '.privateKey')
+ || str_ends_with($node['name'], '.publicKey')
+ || str_ends_with($node['name'], '.shareKey')) {
$path = $folder . '/' . $node['name'];
$content = $this->rootView->file_get_contents($path);
+ if ($content === false) {
+ $output->writeln("<error>Failed to open path $path</error>");
+ continue;
+ }
+
try {
$this->crypto->decrypt($content);
continue;
@@ -133,14 +111,14 @@ class MigrateKeyStorage extends Command {
}
}
- private function traverseFileKeys(string $folder) {
+ private function traverseFileKeys(string $folder, OutputInterface $output): void {
$listing = $this->rootView->getDirectoryContent($folder);
foreach ($listing as $node) {
if ($node['mimetype'] === 'httpd/unix-directory') {
- $this->traverseFileKeys($folder . '/' . $node['name']);
+ $this->traverseFileKeys($folder . '/' . $node['name'], $output);
} else {
- $endsWith = function ($haystack, $needle) {
+ $endsWith = function (string $haystack, string $needle): bool {
$length = strlen($needle);
if ($length === 0) {
return true;
@@ -149,14 +127,19 @@ class MigrateKeyStorage extends Command {
return (substr($haystack, -$length) === $needle);
};
- if ($node['name'] === 'fileKey' ||
- $endsWith($node['name'], '.privateKey') ||
- $endsWith($node['name'], '.publicKey') ||
- $endsWith($node['name'], '.shareKey')) {
+ if ($node['name'] === 'fileKey'
+ || $endsWith($node['name'], '.privateKey')
+ || $endsWith($node['name'], '.publicKey')
+ || $endsWith($node['name'], '.shareKey')) {
$path = $folder . '/' . $node['name'];
$content = $this->rootView->file_get_contents($path);
+ if ($content === false) {
+ $output->writeln("<error>Failed to open path $path</error>");
+ continue;
+ }
+
try {
$this->crypto->decrypt($content);
continue;
@@ -178,10 +161,8 @@ class MigrateKeyStorage extends Command {
/**
* setup file system for the given user
- *
- * @param string $uid
*/
- protected function setupUserFS($uid) {
+ protected function setupUserFS(string $uid): void {
\OC_Util::tearDownFS();
\OC_Util::setupFS($uid);
}
@@ -189,11 +170,8 @@ class MigrateKeyStorage extends Command {
/**
* iterate over each user and move the keys to the new storage
- *
- * @param string $root
- * @param OutputInterface $output
*/
- protected function updateUsersKeys(string $root, OutputInterface $output) {
+ protected function updateUsersKeys(string $root, OutputInterface $output): void {
$progress = new ProgressBar($output);
$progress->start();
@@ -205,7 +183,7 @@ class MigrateKeyStorage extends Command {
foreach ($users as $user) {
$progress->advance();
$this->setupUserFS($user);
- $this->updateUserKeys($root, $user);
+ $this->updateUserKeys($root, $user, $output);
}
$offset += $limit;
} while (count($users) >= $limit);
@@ -216,20 +194,18 @@ class MigrateKeyStorage extends Command {
/**
* move user encryption folder to new root folder
*
- * @param string $root
- * @param string $user
* @throws \Exception
*/
- protected function updateUserKeys(string $root, string $user) {
+ protected function updateUserKeys(string $root, string $user, OutputInterface $output): void {
if ($this->userManager->userExists($user)) {
$source = $root . '/' . $user . '/files_encryption/OC_DEFAULT_MODULE';
if ($this->rootView->is_dir($source)) {
- $this->traverseKeys($source, $user);
+ $this->traverseKeys($source, $user, $output);
}
$source = $root . '/' . $user . '/files_encryption/keys';
if ($this->rootView->is_dir($source)) {
- $this->traverseFileKeys($source);
+ $this->traverseFileKeys($source, $output);
}
}
}
diff --git a/core/Command/Encryption/SetDefaultModule.php b/core/Command/Encryption/SetDefaultModule.php
index b50e004867f..d10872afd38 100644
--- a/core/Command/Encryption/SetDefaultModule.php
+++ b/core/Command/Encryption/SetDefaultModule.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Ruben Homs <ruben@homs.codes>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Encryption;
@@ -31,16 +15,11 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class SetDefaultModule extends Command {
- protected IManager $encryptionManager;
- protected IConfig $config;
-
public function __construct(
- IManager $encryptionManager,
- IConfig $config
+ protected IManager $encryptionManager,
+ protected IConfig $config,
) {
parent::__construct();
- $this->encryptionManager = $encryptionManager;
- $this->config = $config;
}
protected function configure() {
@@ -60,8 +39,8 @@ class SetDefaultModule extends Command {
protected function execute(InputInterface $input, OutputInterface $output): int {
$isMaintenanceModeEnabled = $this->config->getSystemValue('maintenance', false);
if ($isMaintenanceModeEnabled) {
- $output->writeln("Maintenance mode must be disabled when setting default module,");
- $output->writeln("in order to load the relevant encryption modules correctly.");
+ $output->writeln('Maintenance mode must be disabled when setting default module,');
+ $output->writeln('in order to load the relevant encryption modules correctly.');
return 1;
}
diff --git a/core/Command/Encryption/ShowKeyStorageRoot.php b/core/Command/Encryption/ShowKeyStorageRoot.php
index 1c4f2b4cb4a..8cf97076249 100644
--- a/core/Command/Encryption/ShowKeyStorageRoot.php
+++ b/core/Command/Encryption/ShowKeyStorageRoot.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Björn Schießle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Encryption;
@@ -29,11 +13,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ShowKeyStorageRoot extends Command {
- protected Util $util;
-
- public function __construct(Util $util) {
+ public function __construct(
+ protected Util $util,
+ ) {
parent::__construct();
- $this->util = $util;
}
protected function configure() {
diff --git a/core/Command/Encryption/Status.php b/core/Command/Encryption/Status.php
index 34ebabe1b73..6e4f7d16d0c 100644
--- a/core/Command/Encryption/Status.php
+++ b/core/Command/Encryption/Status.php
@@ -1,23 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Encryption;
@@ -27,11 +13,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Status extends Base {
- protected IManager $encryptionManager;
-
- public function __construct(IManager $encryptionManager) {
+ public function __construct(
+ protected IManager $encryptionManager,
+ ) {
parent::__construct();
- $this->encryptionManager = $encryptionManager;
}
protected function configure() {
diff --git a/core/Command/FilesMetadata/Get.php b/core/Command/FilesMetadata/Get.php
new file mode 100644
index 00000000000..0b022d0951b
--- /dev/null
+++ b/core/Command/FilesMetadata/Get.php
@@ -0,0 +1,102 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\FilesMetadata;
+
+use OC\User\NoUserException;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\IFilesMetadataManager;
+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 Get extends Command {
+ public function __construct(
+ private IRootFolder $rootFolder,
+ private IFilesMetadataManager $filesMetadataManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this->setName('metadata:get')
+ ->setDescription('get stored metadata about a file, by its id')
+ ->addArgument(
+ 'fileId',
+ InputArgument::REQUIRED,
+ 'id of the file document'
+ )
+ ->addArgument(
+ 'userId',
+ InputArgument::OPTIONAL,
+ 'file owner'
+ )
+ ->addOption(
+ 'as-array',
+ '',
+ InputOption::VALUE_NONE,
+ 'display metadata as a simple key=>value array'
+ )
+ ->addOption(
+ 'refresh',
+ '',
+ InputOption::VALUE_NONE,
+ 'refresh metadata'
+ )
+ ->addOption(
+ 'reset',
+ '',
+ InputOption::VALUE_NONE,
+ 'refresh metadata from scratch'
+ );
+ }
+
+ /**
+ * @throws NotPermittedException
+ * @throws FilesMetadataNotFoundException
+ * @throws NoUserException
+ * @throws NotFoundException
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $fileId = (int)$input->getArgument('fileId');
+
+ if ($input->getOption('reset')) {
+ $this->filesMetadataManager->deleteMetadata($fileId);
+ if (!$input->getOption('refresh')) {
+ return self::SUCCESS;
+ }
+ }
+
+ if ($input->getOption('refresh')) {
+ $node = $this->rootFolder->getUserFolder($input->getArgument('userId'))->getFirstNodeById($fileId);
+ if (!$node) {
+ throw new NotFoundException();
+ }
+ $metadata = $this->filesMetadataManager->refreshMetadata(
+ $node,
+ IFilesMetadataManager::PROCESS_LIVE | IFilesMetadataManager::PROCESS_BACKGROUND
+ );
+ } else {
+ $metadata = $this->filesMetadataManager->getMetadata($fileId);
+ }
+
+ if ($input->getOption('as-array')) {
+ $output->writeln(json_encode($metadata->asArray(), JSON_PRETTY_PRINT));
+ } else {
+ $output->writeln(json_encode($metadata, JSON_PRETTY_PRINT));
+ }
+
+ return self::SUCCESS;
+ }
+}
diff --git a/core/Command/Group/Add.php b/core/Command/Group/Add.php
index d205cef0696..26d44c7ea83 100644
--- a/core/Command/Group/Add.php
+++ b/core/Command/Group/Add.php
@@ -3,27 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2018 Denis Mosolov <denismosolov@gmail.com>
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Denis Mosolov <denismosolov@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Group;
@@ -36,10 +17,9 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Add extends Base {
- protected IGroupManager $groupManager;
-
- public function __construct(IGroupManager $groupManager) {
- $this->groupManager = $groupManager;
+ public function __construct(
+ protected IGroupManager $groupManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/Group/AddUser.php b/core/Command/Group/AddUser.php
index 6638bcd4c6d..999113390af 100644
--- a/core/Command/Group/AddUser.php
+++ b/core/Command/Group/AddUser.php
@@ -1,25 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl>
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Group;
@@ -34,12 +17,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class AddUser extends Base {
- protected IUserManager $userManager;
- protected IGroupManager $groupManager;
-
- public function __construct(IUserManager $userManager, IGroupManager $groupManager) {
- $this->userManager = $userManager;
- $this->groupManager = $groupManager;
+ public function __construct(
+ protected IUserManager $userManager,
+ protected IGroupManager $groupManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/Group/Delete.php b/core/Command/Group/Delete.php
index fd1074d6f61..a2736476920 100644
--- a/core/Command/Group/Delete.php
+++ b/core/Command/Group/Delete.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2018 Denis Mosolov <denismosolov@gmail.com>
- *
- * @author Denis Mosolov <denismosolov@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Group;
@@ -35,10 +17,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Delete extends Base {
- protected IGroupManager $groupManager;
-
- public function __construct(IGroupManager $groupManager) {
- $this->groupManager = $groupManager;
+ public function __construct(
+ protected IGroupManager $groupManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/Group/Info.php b/core/Command/Group/Info.php
index dc475581ac5..d42d2a64577 100644
--- a/core/Command/Group/Info.php
+++ b/core/Command/Group/Info.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2021, hosting.de, Johannes Leuker <developers@hosting.de>
- *
- * @author Johannes Leuker <j.leuker@hosting.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Group;
@@ -35,10 +18,9 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Info extends Base {
- protected IGroupManager $groupManager;
-
- public function __construct(IGroupManager $groupManager) {
- $this->groupManager = $groupManager;
+ public function __construct(
+ protected IGroupManager $groupManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/Group/ListCommand.php b/core/Command/Group/ListCommand.php
index 5100a00c60a..01522a23f7f 100644
--- a/core/Command/Group/ListCommand.php
+++ b/core/Command/Group/ListCommand.php
@@ -1,41 +1,23 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl>
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Johannes Leuker <j.leuker@hosting.de>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Group;
use OC\Core\Command\Base;
use OCP\IGroup;
use OCP\IGroupManager;
+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 ListCommand extends Base {
- protected IGroupManager $groupManager;
-
- public function __construct(IGroupManager $groupManager) {
- $this->groupManager = $groupManager;
+ public function __construct(
+ protected IGroupManager $groupManager,
+ ) {
parent::__construct();
}
@@ -43,6 +25,12 @@ class ListCommand extends Base {
$this
->setName('group:list')
->setDescription('list configured groups')
+ ->addArgument(
+ 'searchstring',
+ InputArgument::OPTIONAL,
+ 'Filter the groups to only those matching the search string',
+ ''
+ )
->addOption(
'limit',
'l',
@@ -70,32 +58,37 @@ class ListCommand extends Base {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $groups = $this->groupManager->search('', (int)$input->getOption('limit'), (int)$input->getOption('offset'));
+ $groups = $this->groupManager->search((string)$input->getArgument('searchstring'), (int)$input->getOption('limit'), (int)$input->getOption('offset'));
$this->writeArrayInOutputFormat($input, $output, $this->formatGroups($groups, (bool)$input->getOption('info')));
return 0;
}
/**
- * @param IGroup[] $groups
- * @return array
+ * @param IGroup $group
+ * @return string[]
*/
- private function formatGroups(array $groups, bool $addInfo = false) {
- $keys = array_map(function (IGroup $group) {
- return $group->getGID();
- }, $groups);
+ public function usersForGroup(IGroup $group) {
+ $users = array_keys($group->getUsers());
+ return array_map(function ($userId) {
+ return (string)$userId;
+ }, $users);
+ }
- if ($addInfo) {
- $values = array_map(function (IGroup $group) {
- return [
+ /**
+ * @param IGroup[] $groups
+ */
+ private function formatGroups(array $groups, bool $addInfo = false): \Generator {
+ foreach ($groups as $group) {
+ if ($addInfo) {
+ $value = [
+ 'displayName' => $group->getDisplayName(),
'backends' => $group->getBackendNames(),
- 'users' => array_keys($group->getUsers()),
+ 'users' => $this->usersForGroup($group),
];
- }, $groups);
- } else {
- $values = array_map(function (IGroup $group) {
- return array_keys($group->getUsers());
- }, $groups);
+ } else {
+ $value = $this->usersForGroup($group);
+ }
+ yield $group->getGID() => $value;
}
- return array_combine($keys, $values);
}
}
diff --git a/core/Command/Group/RemoveUser.php b/core/Command/Group/RemoveUser.php
index c7b3a2d84e7..952fc6e7712 100644
--- a/core/Command/Group/RemoveUser.php
+++ b/core/Command/Group/RemoveUser.php
@@ -1,25 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl>
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Group;
@@ -34,12 +17,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class RemoveUser extends Base {
- protected IUserManager $userManager;
- protected IGroupManager $groupManager;
-
- public function __construct(IUserManager $userManager, IGroupManager $groupManager) {
- $this->userManager = $userManager;
- $this->groupManager = $groupManager;
+ public function __construct(
+ protected IUserManager $userManager,
+ protected IGroupManager $groupManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/Info/File.php b/core/Command/Info/File.php
new file mode 100644
index 00000000000..287bd0e29cb
--- /dev/null
+++ b/core/Command/Info/File.php
@@ -0,0 +1,188 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\Info;
+
+use OC\Files\ObjectStore\ObjectStoreStorage;
+use OC\Files\Storage\Wrapper\Encryption;
+use OC\Files\Storage\Wrapper\Wrapper;
+use OC\Files\View;
+use OCA\Files_External\Config\ExternalMountPoint;
+use OCA\GroupFolders\Mount\GroupMountPoint;
+use OCP\Files\File as OCPFile;
+use OCP\Files\Folder;
+use OCP\Files\IHomeStorage;
+use OCP\Files\Mount\IMountPoint;
+use OCP\Files\Node;
+use OCP\Files\NotFoundException;
+use OCP\IL10N;
+use OCP\L10N\IFactory;
+use OCP\Util;
+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 File extends Command {
+ private IL10N $l10n;
+ private View $rootView;
+
+ public function __construct(
+ IFactory $l10nFactory,
+ private FileUtils $fileUtils,
+ private \OC\Encryption\Util $encryptionUtil,
+ ) {
+ $this->l10n = $l10nFactory->get('core');
+ parent::__construct();
+ $this->rootView = new View();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('info:file')
+ ->setDescription('get information for a file')
+ ->addArgument('file', InputArgument::REQUIRED, 'File id or path')
+ ->addOption('children', 'c', InputOption::VALUE_NONE, 'List children of folders')
+ ->addOption('storage-tree', null, InputOption::VALUE_NONE, 'Show storage and cache wrapping tree');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $fileInput = $input->getArgument('file');
+ $showChildren = $input->getOption('children');
+ $node = $this->fileUtils->getNode($fileInput);
+ if (!$node) {
+ $output->writeln("<error>file $fileInput not found</error>");
+ return 1;
+ }
+
+ $output->writeln($node->getName());
+ $output->writeln(' fileid: ' . $node->getId());
+ $output->writeln(' mimetype: ' . $node->getMimetype());
+ $output->writeln(' modified: ' . (string)$this->l10n->l('datetime', $node->getMTime()));
+
+ if ($node instanceof OCPFile && $node->isEncrypted()) {
+ $output->writeln(' ' . 'server-side encrypted: yes');
+ $keyPath = $this->encryptionUtil->getFileKeyDir('', $node->getPath());
+ if ($this->rootView->file_exists($keyPath)) {
+ $output->writeln(' encryption key at: ' . $keyPath);
+ } else {
+ $output->writeln(' <error>encryption key not found</error> should be located at: ' . $keyPath);
+ }
+ $storage = $node->getStorage();
+ if ($storage->instanceOfStorage(Encryption::class)) {
+ /** @var Encryption $storage */
+ if (!$storage->hasValidHeader($node->getInternalPath())) {
+ $output->writeln(' <error>file doesn\'t have a valid encryption header</error>');
+ }
+ } else {
+ $output->writeln(' <error>file is marked as encrypted, but encryption doesn\'t seem to be setup</error>');
+ }
+ }
+
+ if ($node instanceof Folder && $node->isEncrypted() || $node instanceof OCPFile && $node->getParent()->isEncrypted()) {
+ $output->writeln(' ' . 'end-to-end encrypted: yes');
+ }
+
+ $output->writeln(' size: ' . Util::humanFileSize($node->getSize()));
+ $output->writeln(' etag: ' . $node->getEtag());
+ $output->writeln(' permissions: ' . $this->fileUtils->formatPermissions($node->getType(), $node->getPermissions()));
+ if ($node instanceof Folder) {
+ $children = $node->getDirectoryListing();
+ $childSize = array_sum(array_map(function (Node $node) {
+ return $node->getSize();
+ }, $children));
+ if ($childSize != $node->getSize()) {
+ $output->writeln(' <error>warning: folder has a size of ' . Util::humanFileSize($node->getSize()) . " but it's children sum up to " . Util::humanFileSize($childSize) . '</error>.');
+ $output->writeln(' Run <info>occ files:scan --path ' . $node->getPath() . '</info> to attempt to resolve this.');
+ }
+ if ($showChildren) {
+ $output->writeln(' children: ' . count($children) . ':');
+ foreach ($children as $child) {
+ $output->writeln(' - ' . $child->getName());
+ }
+ } else {
+ $output->writeln(' children: ' . count($children) . ' (use <info>--children</info> option to list)');
+ }
+ }
+ $this->outputStorageDetails($node->getMountPoint(), $node, $input, $output);
+
+ $filesPerUser = $this->fileUtils->getFilesByUser($node);
+ $output->writeln('');
+ $output->writeln('The following users have access to the file');
+ $output->writeln('');
+ foreach ($filesPerUser as $user => $files) {
+ $output->writeln("$user:");
+ foreach ($files as $userFile) {
+ $output->writeln(' ' . $userFile->getPath() . ': ' . $this->fileUtils->formatPermissions($userFile->getType(), $userFile->getPermissions()));
+ $mount = $userFile->getMountPoint();
+ $output->writeln(' ' . $this->fileUtils->formatMountType($mount));
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * @psalm-suppress UndefinedClass
+ * @psalm-suppress UndefinedInterfaceMethod
+ */
+ private function outputStorageDetails(IMountPoint $mountPoint, Node $node, InputInterface $input, OutputInterface $output): void {
+ $storage = $mountPoint->getStorage();
+ if (!$storage) {
+ return;
+ }
+ if (!$storage->instanceOfStorage(IHomeStorage::class)) {
+ $output->writeln(' mounted at: ' . $mountPoint->getMountPoint());
+ }
+ if ($storage->instanceOfStorage(ObjectStoreStorage::class)) {
+ /** @var ObjectStoreStorage $storage */
+ $objectStoreId = $storage->getObjectStore()->getStorageId();
+ $parts = explode(':', $objectStoreId);
+ /** @var string $bucket */
+ $bucket = array_pop($parts);
+ $output->writeln(' bucket: ' . $bucket);
+ if ($node instanceof \OC\Files\Node\File) {
+ $output->writeln(' object id: ' . $storage->getURN($node->getId()));
+ try {
+ $fh = $node->fopen('r');
+ if (!$fh) {
+ throw new NotFoundException();
+ }
+ $stat = fstat($fh);
+ fclose($fh);
+ if ($stat['size'] !== $node->getSize()) {
+ $output->writeln(' <error>warning: object had a size of ' . $stat['size'] . ' but cache entry has a size of ' . $node->getSize() . '</error>. This should have been automatically repaired');
+ }
+ } catch (\Exception $e) {
+ $output->writeln(' <error>warning: object not found in bucket</error>');
+ }
+ }
+ } else {
+ if (!$storage->file_exists($node->getInternalPath())) {
+ $output->writeln(' <error>warning: file not found in storage</error>');
+ }
+ }
+ if ($mountPoint instanceof ExternalMountPoint) {
+ $storageConfig = $mountPoint->getStorageConfig();
+ $output->writeln(' external storage id: ' . $storageConfig->getId());
+ $output->writeln(' external type: ' . $storageConfig->getBackend()->getText());
+ } elseif ($mountPoint instanceof GroupMountPoint) {
+ $output->writeln(' groupfolder id: ' . $mountPoint->getFolderId());
+ }
+ if ($input->getOption('storage-tree')) {
+ $storageTmp = $storage;
+ $storageClass = get_class($storageTmp) . ' (cache:' . get_class($storageTmp->getCache()) . ')';
+ while ($storageTmp instanceof Wrapper) {
+ $storageTmp = $storageTmp->getWrapperStorage();
+ $storageClass .= "\n\t" . '> ' . get_class($storageTmp) . ' (cache:' . get_class($storageTmp->getCache()) . ')';
+ }
+ $output->writeln(' storage wrapping: ' . $storageClass);
+ }
+
+ }
+}
diff --git a/core/Command/Info/FileUtils.php b/core/Command/Info/FileUtils.php
new file mode 100644
index 00000000000..bc07535a289
--- /dev/null
+++ b/core/Command/Info/FileUtils.php
@@ -0,0 +1,325 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Info;
+
+use OC\User\NoUserException;
+use OCA\Circles\MountManager\CircleMount;
+use OCA\Files_External\Config\ExternalMountPoint;
+use OCA\Files_Sharing\SharedMount;
+use OCA\GroupFolders\Mount\GroupMountPoint;
+use OCP\Constants;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Files\Config\IUserMountCache;
+use OCP\Files\FileInfo;
+use OCP\Files\Folder;
+use OCP\Files\IHomeStorage;
+use OCP\Files\IRootFolder;
+use OCP\Files\Mount\IMountPoint;
+use OCP\Files\Node;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\IDBConnection;
+use OCP\Share\IShare;
+use OCP\Util;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @psalm-type StorageInfo array{numeric_id: int, id: string, available: bool, last_checked: ?\DateTime, files: int, mount_id: ?int}
+ */
+class FileUtils {
+ public function __construct(
+ private IRootFolder $rootFolder,
+ private IUserMountCache $userMountCache,
+ private IDBConnection $connection,
+ ) {
+ }
+
+ /**
+ * @param FileInfo $file
+ * @return array<string, Node[]>
+ * @throws NotPermittedException
+ * @throws NoUserException
+ */
+ public function getFilesByUser(FileInfo $file): array {
+ $id = $file->getId();
+ if (!$id) {
+ return [];
+ }
+
+ $mounts = $this->userMountCache->getMountsForFileId($id);
+ $result = [];
+ foreach ($mounts as $cachedMount) {
+ $mount = $this->rootFolder->getMount($cachedMount->getMountPoint());
+ $cache = $mount->getStorage()->getCache();
+ $cacheEntry = $cache->get($id);
+ $node = $this->rootFolder->getNodeFromCacheEntryAndMount($cacheEntry, $mount);
+ $result[$cachedMount->getUser()->getUID()][] = $node;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get file by either id of path
+ *
+ * @param string $fileInput
+ * @return Node|null
+ */
+ public function getNode(string $fileInput): ?Node {
+ if (is_numeric($fileInput)) {
+ $mounts = $this->userMountCache->getMountsForFileId((int)$fileInput);
+ if (!$mounts) {
+ return null;
+ }
+ $mount = reset($mounts);
+ $userFolder = $this->rootFolder->getUserFolder($mount->getUser()->getUID());
+ return $userFolder->getFirstNodeById((int)$fileInput);
+ } else {
+ try {
+ return $this->rootFolder->get($fileInput);
+ } catch (NotFoundException $e) {
+ return null;
+ }
+ }
+ }
+
+ public function formatPermissions(string $type, int $permissions): string {
+ if ($permissions == Constants::PERMISSION_ALL || ($type === 'file' && $permissions == (Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE))) {
+ return 'full permissions';
+ }
+
+ $perms = [];
+ $allPerms = [Constants::PERMISSION_READ => 'read', Constants::PERMISSION_UPDATE => 'update', Constants::PERMISSION_CREATE => 'create', Constants::PERMISSION_DELETE => 'delete', Constants::PERMISSION_SHARE => 'share'];
+ foreach ($allPerms as $perm => $name) {
+ if (($permissions & $perm) === $perm) {
+ $perms[] = $name;
+ }
+ }
+
+ return implode(', ', $perms);
+ }
+
+ /**
+ * @psalm-suppress UndefinedClass
+ * @psalm-suppress UndefinedInterfaceMethod
+ */
+ public function formatMountType(IMountPoint $mountPoint): string {
+ $storage = $mountPoint->getStorage();
+ if ($storage && $storage->instanceOfStorage(IHomeStorage::class)) {
+ return 'home storage';
+ } elseif ($mountPoint instanceof SharedMount) {
+ $share = $mountPoint->getShare();
+ $shares = $mountPoint->getGroupedShares();
+ $sharedBy = array_map(function (IShare $share) {
+ $shareType = $this->formatShareType($share);
+ if ($shareType) {
+ return $share->getSharedBy() . ' (via ' . $shareType . ' ' . $share->getSharedWith() . ')';
+ } else {
+ return $share->getSharedBy();
+ }
+ }, $shares);
+ $description = 'shared by ' . implode(', ', $sharedBy);
+ if ($share->getSharedBy() !== $share->getShareOwner()) {
+ $description .= ' owned by ' . $share->getShareOwner();
+ }
+ return $description;
+ } elseif ($mountPoint instanceof GroupMountPoint) {
+ return 'groupfolder ' . $mountPoint->getFolderId();
+ } elseif ($mountPoint instanceof ExternalMountPoint) {
+ return 'external storage ' . $mountPoint->getStorageConfig()->getId();
+ } elseif ($mountPoint instanceof CircleMount) {
+ return 'circle';
+ }
+ return get_class($mountPoint);
+ }
+
+ public function formatShareType(IShare $share): ?string {
+ switch ($share->getShareType()) {
+ case IShare::TYPE_GROUP:
+ return 'group';
+ case IShare::TYPE_CIRCLE:
+ return 'circle';
+ case IShare::TYPE_DECK:
+ return 'deck';
+ case IShare::TYPE_ROOM:
+ return 'room';
+ case IShare::TYPE_USER:
+ return null;
+ default:
+ return 'Unknown (' . $share->getShareType() . ')';
+ }
+ }
+
+ /**
+ * Print out the largest count($sizeLimits) files in the directory tree
+ *
+ * @param OutputInterface $output
+ * @param Folder $node
+ * @param string $prefix
+ * @param array $sizeLimits largest items that are still in the queue to be printed, ordered ascending
+ * @return int how many items we've printed
+ */
+ public function outputLargeFilesTree(
+ OutputInterface $output,
+ Folder $node,
+ string $prefix,
+ array &$sizeLimits,
+ bool $all,
+ ): int {
+ /**
+ * Algorithm to print the N largest items in a folder without requiring to query or sort the entire three
+ *
+ * This is done by keeping a list ($sizeLimits) of size N that contain the largest items outside of this
+ * folders that are could be printed if there aren't enough items in this folder that are larger.
+ *
+ * We loop over the items in this folder by size descending until the size of the item falls before the smallest
+ * size in $sizeLimits (at that point there are enough items outside this folder to complete the N items).
+ *
+ * When encountering a folder, we create an updated $sizeLimits with the largest items in the current folder still
+ * remaining which we pass into the recursion. (We don't update the current $sizeLimits because that should only
+ * hold items *outside* of the current folder.)
+ *
+ * For every item printed we remove the first item of $sizeLimits are there is no longer room in the output to print
+ * items that small.
+ */
+
+ $count = 0;
+ $children = $node->getDirectoryListing();
+ usort($children, function (Node $a, Node $b) {
+ return $b->getSize() <=> $a->getSize();
+ });
+ foreach ($children as $i => $child) {
+ if (!$all) {
+ if (count($sizeLimits) === 0 || $child->getSize() < $sizeLimits[0]) {
+ return $count;
+ }
+ array_shift($sizeLimits);
+ }
+ $count += 1;
+
+ /** @var Node $child */
+ $output->writeln("$prefix- " . $child->getName() . ': <info>' . Util::humanFileSize($child->getSize()) . '</info>');
+ if ($child instanceof Folder) {
+ $recurseSizeLimits = $sizeLimits;
+ if (!$all) {
+ for ($j = 0; $j < count($recurseSizeLimits); $j++) {
+ if (isset($children[$i + $j + 1])) {
+ $nextChildSize = $children[$i + $j + 1]->getSize();
+ if ($nextChildSize > $recurseSizeLimits[0]) {
+ array_shift($recurseSizeLimits);
+ $recurseSizeLimits[] = $nextChildSize;
+ }
+ }
+ }
+ sort($recurseSizeLimits);
+ }
+ $recurseCount = $this->outputLargeFilesTree($output, $child, $prefix . ' ', $recurseSizeLimits, $all);
+ $sizeLimits = array_slice($sizeLimits, $recurseCount);
+ $count += $recurseCount;
+ }
+ }
+ return $count;
+ }
+
+ public function getNumericStorageId(string $id): ?int {
+ if (is_numeric($id)) {
+ return (int)$id;
+ }
+ $query = $this->connection->getQueryBuilder();
+ $query->select('numeric_id')
+ ->from('storages')
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
+ $result = $query->executeQuery()->fetchOne();
+ return $result ? (int)$result : null;
+ }
+
+ /**
+ * @param int|null $limit
+ * @return ?StorageInfo
+ * @throws \OCP\DB\Exception
+ */
+ public function getStorage(int $id): ?array {
+ $query = $this->connection->getQueryBuilder();
+ $query->select('numeric_id', 's.id', 'available', 'last_checked', 'mount_id')
+ ->selectAlias($query->func()->count('fileid'), 'files')
+ ->from('storages', 's')
+ ->innerJoin('s', 'filecache', 'f', $query->expr()->eq('f.storage', 's.numeric_id'))
+ ->leftJoin('s', 'mounts', 'm', $query->expr()->eq('s.numeric_id', 'm.storage_id'))
+ ->where($query->expr()->eq('s.numeric_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
+ ->groupBy('s.numeric_id', 's.id', 's.available', 's.last_checked', 'mount_id');
+ $row = $query->executeQuery()->fetch();
+ if ($row) {
+ return [
+ 'numeric_id' => $row['numeric_id'],
+ 'id' => $row['id'],
+ 'files' => $row['files'],
+ 'available' => (bool)$row['available'],
+ 'last_checked' => $row['last_checked'] ? new \DateTime('@' . $row['last_checked']) : null,
+ 'mount_id' => $row['mount_id'],
+ ];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @param int|null $limit
+ * @return \Iterator<StorageInfo>
+ * @throws \OCP\DB\Exception
+ */
+ public function listStorages(?int $limit): \Iterator {
+ $query = $this->connection->getQueryBuilder();
+ $query->select('numeric_id', 's.id', 'available', 'last_checked', 'mount_id')
+ ->selectAlias($query->func()->count('fileid'), 'files')
+ ->from('storages', 's')
+ ->innerJoin('s', 'filecache', 'f', $query->expr()->eq('f.storage', 's.numeric_id'))
+ ->leftJoin('s', 'mounts', 'm', $query->expr()->eq('s.numeric_id', 'm.storage_id'))
+ ->groupBy('s.numeric_id', 's.id', 's.available', 's.last_checked', 'mount_id')
+ ->orderBy('files', 'DESC');
+ if ($limit !== null) {
+ $query->setMaxResults($limit);
+ }
+ $result = $query->executeQuery();
+ while ($row = $result->fetch()) {
+ yield [
+ 'numeric_id' => $row['numeric_id'],
+ 'id' => $row['id'],
+ 'files' => $row['files'],
+ 'available' => (bool)$row['available'],
+ 'last_checked' => $row['last_checked'] ? new \DateTime('@' . $row['last_checked']) : null,
+ 'mount_id' => $row['mount_id'],
+ ];
+ }
+ }
+
+ /**
+ * @param StorageInfo $storage
+ * @return array
+ */
+ public function formatStorage(array $storage): array {
+ return [
+ 'numeric_id' => $storage['numeric_id'],
+ 'id' => $storage['id'],
+ 'files' => $storage['files'],
+ 'available' => $storage['available'] ? 'true' : 'false',
+ 'last_checked' => $storage['last_checked']?->format(\DATE_ATOM),
+ 'external_mount_id' => $storage['mount_id'],
+ ];
+ }
+
+ /**
+ * @param \Iterator<StorageInfo> $storages
+ * @return \Iterator
+ */
+ public function formatStorages(\Iterator $storages): \Iterator {
+ foreach ($storages as $storage) {
+ yield $this->formatStorage($storage);
+ }
+ }
+}
diff --git a/core/Command/Info/Space.php b/core/Command/Info/Space.php
new file mode 100644
index 00000000000..35c1d5c3228
--- /dev/null
+++ b/core/Command/Info/Space.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Info;
+
+use OCP\Files\Folder;
+use OCP\Util;
+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 Space extends Command {
+ public function __construct(
+ private FileUtils $fileUtils,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('info:file:space')
+ ->setDescription('Summarize space usage of specified folder')
+ ->addArgument('file', InputArgument::REQUIRED, 'File id or path')
+ ->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'Number of items to display', 25)
+ ->addOption('all', 'a', InputOption::VALUE_NONE, 'Display all items');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $fileInput = $input->getArgument('file');
+ $count = (int)$input->getOption('count');
+ $all = $input->getOption('all');
+ $node = $this->fileUtils->getNode($fileInput);
+ if (!$node) {
+ $output->writeln("<error>file $fileInput not found</error>");
+ return 1;
+ }
+ $output->writeln($node->getName() . ': <info>' . Util::humanFileSize($node->getSize()) . '</info>');
+ if ($node instanceof Folder) {
+ $limits = $all ? [] : array_fill(0, $count - 1, 0);
+ $this->fileUtils->outputLargeFilesTree($output, $node, '', $limits, $all);
+ }
+ return 0;
+ }
+}
diff --git a/core/Command/Info/Storage.php b/core/Command/Info/Storage.php
new file mode 100644
index 00000000000..c1d0e1725ca
--- /dev/null
+++ b/core/Command/Info/Storage.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Info;
+
+use OC\Core\Command\Base;
+use OCP\IDBConnection;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Storage extends Base {
+ public function __construct(
+ private readonly IDBConnection $connection,
+ private readonly FileUtils $fileUtils,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('info:storage')
+ ->setDescription('Get information a single storage')
+ ->addArgument('storage', InputArgument::REQUIRED, 'Storage to get information for');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $storage = $input->getArgument('storage');
+ $storageId = $this->fileUtils->getNumericStorageId($storage);
+ if (!$storageId) {
+ $output->writeln('<error>No storage with id ' . $storage . ' found</error>');
+ return 1;
+ }
+
+ $info = $this->fileUtils->getStorage($storageId);
+ if (!$info) {
+ $output->writeln('<error>No storage with id ' . $storage . ' found</error>');
+ return 1;
+ }
+ $this->writeArrayInOutputFormat($input, $output, $this->fileUtils->formatStorage($info));
+ return 0;
+ }
+}
diff --git a/core/Command/Info/Storages.php b/core/Command/Info/Storages.php
new file mode 100644
index 00000000000..ff767a2ff5d
--- /dev/null
+++ b/core/Command/Info/Storages.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Info;
+
+use OC\Core\Command\Base;
+use OCP\IDBConnection;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Storages extends Base {
+ public function __construct(
+ private readonly IDBConnection $connection,
+ private readonly FileUtils $fileUtils,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('info:storages')
+ ->setDescription('List storages ordered by the number of files')
+ ->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'Number of storages to display', 25)
+ ->addOption('all', 'a', InputOption::VALUE_NONE, 'Display all storages');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $count = (int)$input->getOption('count');
+ $all = $input->getOption('all');
+
+ $limit = $all ? null : $count;
+ $storages = $this->fileUtils->listStorages($limit);
+ $this->writeStreamingTableInOutputFormat($input, $output, $this->fileUtils->formatStorages($storages), 100);
+ return 0;
+ }
+}
diff --git a/core/Command/Integrity/CheckApp.php b/core/Command/Integrity/CheckApp.php
index ebd502c3d29..0145a3f8070 100644
--- a/core/Command/Integrity/CheckApp.php
+++ b/core/Command/Integrity/CheckApp.php
@@ -1,34 +1,17 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Carla Schroder <carla@owncloud.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Georg Ehrke <oc.list@georgehrke.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Victor Dubiniuk <dubiniuk@owncloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Integrity;
use OC\Core\Command\Base;
use OC\IntegrityCheck\Checker;
+use OC\IntegrityCheck\Helpers\AppLocator;
+use OC\IntegrityCheck\Helpers\FileAccessHelper;
+use OCP\App\IAppManager;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -40,11 +23,13 @@ use Symfony\Component\Console\Output\OutputInterface;
* @package OC\Core\Command\Integrity
*/
class CheckApp extends Base {
- private Checker $checker;
-
- public function __construct(Checker $checker) {
+ public function __construct(
+ private Checker $checker,
+ private AppLocator $appLocator,
+ private FileAccessHelper $fileAccessHelper,
+ private IAppManager $appManager,
+ ) {
parent::__construct();
- $this->checker = $checker;
}
/**
@@ -55,23 +40,58 @@ class CheckApp extends Base {
$this
->setName('integrity:check-app')
->setDescription('Check integrity of an app using a signature.')
- ->addArgument('appid', InputArgument::REQUIRED, 'Application to check')
- ->addOption('path', null, InputOption::VALUE_OPTIONAL, 'Path to application. If none is given it will be guessed.');
+ ->addArgument('appid', InputArgument::OPTIONAL, 'Application to check')
+ ->addOption('path', null, InputOption::VALUE_OPTIONAL, 'Path to application. If none is given it will be guessed.')
+ ->addOption('all', null, InputOption::VALUE_NONE, 'Check integrity of all apps.');
}
/**
* {@inheritdoc }
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
- $appid = $input->getArgument('appid');
- $path = (string)$input->getOption('path');
- $result = $this->checker->verifyAppSignature($appid, $path, true);
- $this->writeArrayInOutputFormat($input, $output, $result);
- if (count($result) > 0) {
- $output->writeln('<error>' . count($result) . ' errors found</error>', OutputInterface::VERBOSITY_VERBOSE);
+ if ($input->getOption('all') && $input->getArgument('appid')) {
+ $output->writeln('<error>Option "--all" cannot be combined with an appid</error>');
+ return 1;
+ }
+
+ if (!$input->getArgument('appid') && !$input->getOption('all')) {
+ $output->writeln('<error>Please specify an appid, or "--all" to verify all apps</error>');
return 1;
}
- $output->writeln('<info>No errors found</info>', OutputInterface::VERBOSITY_VERBOSE);
- return 0;
+
+ if ($input->getArgument('appid')) {
+ $appIds = [$input->getArgument('appid')];
+ } else {
+ $appIds = $this->appManager->getAllAppsInAppsFolders();
+ }
+
+ $errorsFound = false;
+
+ foreach ($appIds as $appId) {
+ $path = (string)$input->getOption('path');
+ if ($path === '') {
+ $path = $this->appLocator->getAppPath($appId);
+ }
+
+ if ($this->appManager->isShipped($appId) || $this->fileAccessHelper->file_exists($path . '/appinfo/signature.json')) {
+ // Only verify if the application explicitly ships a signature.json file
+ $result = $this->checker->verifyAppSignature($appId, $path, true);
+
+ if (count($result) > 0) {
+ $output->writeln('<error>' . $appId . ': ' . count($result) . ' errors found:</error>');
+ $this->writeArrayInOutputFormat($input, $output, $result);
+ $errorsFound = true;
+ }
+ } else {
+ $output->writeln('<comment>' . $appId . ': ' . 'App signature not found, skipping app integrity check</comment>');
+ }
+ }
+
+ if (!$errorsFound) {
+ $output->writeln('<info>No errors found</info>', OutputInterface::VERBOSITY_VERBOSE);
+ return 0;
+ }
+
+ return 1;
}
}
diff --git a/core/Command/Integrity/CheckCore.php b/core/Command/Integrity/CheckCore.php
index 9436786cad9..49086e94d26 100644
--- a/core/Command/Integrity/CheckCore.php
+++ b/core/Command/Integrity/CheckCore.php
@@ -1,27 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Carla Schroder <carla@owncloud.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Victor Dubiniuk <dubiniuk@owncloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Integrity;
@@ -36,11 +18,10 @@ use Symfony\Component\Console\Output\OutputInterface;
* @package OC\Core\Command\Integrity
*/
class CheckCore extends Base {
- private Checker $checker;
-
- public function __construct(Checker $checker) {
+ public function __construct(
+ private Checker $checker,
+ ) {
parent::__construct();
- $this->checker = $checker;
}
/**
diff --git a/core/Command/Integrity/SignApp.php b/core/Command/Integrity/SignApp.php
index 8492511d597..d307bc58985 100644
--- a/core/Command/Integrity/SignApp.php
+++ b/core/Command/Integrity/SignApp.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Victor Dubiniuk <dubiniuk@owncloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Integrity;
@@ -40,17 +23,12 @@ use Symfony\Component\Console\Output\OutputInterface;
* @package OC\Core\Command\Integrity
*/
class SignApp extends Command {
- private Checker $checker;
- private FileAccessHelper $fileAccessHelper;
- private IURLGenerator $urlGenerator;
-
- public function __construct(Checker $checker,
- FileAccessHelper $fileAccessHelper,
- IURLGenerator $urlGenerator) {
+ public function __construct(
+ private Checker $checker,
+ private FileAccessHelper $fileAccessHelper,
+ private IURLGenerator $urlGenerator,
+ ) {
parent::__construct(null);
- $this->checker = $checker;
- $this->fileAccessHelper = $fileAccessHelper;
- $this->urlGenerator = $urlGenerator;
}
protected function configure() {
@@ -73,7 +51,7 @@ class SignApp extends Command {
$documentationUrl = $this->urlGenerator->linkToDocs('developer-code-integrity');
$output->writeln('This command requires the --path, --privateKey and --certificate.');
$output->writeln('Example: ./occ integrity:sign-app --path="/Users/lukasreschke/Programming/myapp/" --privateKey="/Users/lukasreschke/private/myapp.key" --certificate="/Users/lukasreschke/public/mycert.crt"');
- $output->writeln('For more information please consult the documentation: '. $documentationUrl);
+ $output->writeln('For more information please consult the documentation: ' . $documentationUrl);
return 1;
}
@@ -97,7 +75,7 @@ class SignApp extends Command {
$x509->setPrivateKey($rsa);
try {
$this->checker->writeAppSignature($path, $x509, $rsa);
- $output->writeln('Successfully signed "'.$path.'"');
+ $output->writeln('Successfully signed "' . $path . '"');
} catch (\Exception $e) {
$output->writeln('Error: ' . $e->getMessage());
return 1;
diff --git a/core/Command/Integrity/SignCore.php b/core/Command/Integrity/SignCore.php
index 55d356fcd6b..ed80091ec38 100644
--- a/core/Command/Integrity/SignCore.php
+++ b/core/Command/Integrity/SignCore.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Victor Dubiniuk <dubiniuk@owncloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Integrity;
@@ -39,14 +22,11 @@ use Symfony\Component\Console\Output\OutputInterface;
* @package OC\Core\Command\Integrity
*/
class SignCore extends Command {
- private Checker $checker;
- private FileAccessHelper $fileAccessHelper;
-
- public function __construct(Checker $checker,
- FileAccessHelper $fileAccessHelper) {
+ public function __construct(
+ private Checker $checker,
+ private FileAccessHelper $fileAccessHelper,
+ ) {
parent::__construct(null);
- $this->checker = $checker;
- $this->fileAccessHelper = $fileAccessHelper;
}
protected function configure() {
diff --git a/core/Command/InterruptedException.php b/core/Command/InterruptedException.php
index 2e2b5b979fc..661e6672577 100644
--- a/core/Command/InterruptedException.php
+++ b/core/Command/InterruptedException.php
@@ -1,24 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2017, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2017 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command;
diff --git a/core/Command/L10n/CreateJs.php b/core/Command/L10n/CreateJs.php
index 6472e7aec1c..64a21e6d48c 100644
--- a/core/Command/L10n/CreateJs.php
+++ b/core/Command/L10n/CreateJs.php
@@ -1,31 +1,18 @@
<?php
+
+declare(strict_types=1);
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\L10n;
use DirectoryIterator;
+use OCP\App\AppPathNotFoundException;
+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;
@@ -35,6 +22,12 @@ use Symfony\Component\Console\Output\OutputInterface;
use UnexpectedValueException;
class CreateJs extends Command implements CompletionAwareInterface {
+ public function __construct(
+ protected IAppManager $appManager,
+ ) {
+ parent::__construct();
+ }
+
protected function configure() {
$this
->setName('l10n:createjs')
@@ -55,11 +48,7 @@ class CreateJs extends Command implements CompletionAwareInterface {
$app = $input->getArgument('app');
$lang = $input->getArgument('lang');
- $path = \OC_App::getAppPath($app);
- if ($path === false) {
- $output->writeln("The app <$app> is unknown.");
- return 1;
- }
+ $path = $this->appManager->getAppPath($app);
$languages = $lang;
if (empty($lang)) {
$languages = $this->getAllLanguages($path);
@@ -159,10 +148,14 @@ class CreateJs extends Command implements CompletionAwareInterface {
*/
public function completeArgumentValues($argumentName, CompletionContext $context) {
if ($argumentName === 'app') {
- return \OC_App::getAllApps();
+ return $this->appManager->getAllAppsInAppsFolders();
} elseif ($argumentName === 'lang') {
$appName = $context->getWordAtIndex($context->getWordIndex() - 1);
- return $this->getAllLanguages(\OC_App::getAppPath($appName));
+ try {
+ return $this->getAllLanguages($this->appManager->getAppPath($appName));
+ } catch (AppPathNotFoundException) {
+ return [];
+ }
}
return [];
}
diff --git a/core/Command/Log/File.php b/core/Command/Log/File.php
index f2c77e20174..ba5dad956e9 100644
--- a/core/Command/Log/File.php
+++ b/core/Command/Log/File.php
@@ -1,31 +1,14 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Pulzer <t.pulzer@kniel.de>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Log;
use OCP\IConfig;
+use OCP\Util;
use Stecman\Component\Symfony\Console\BashCompletion\Completion;
use Stecman\Component\Symfony\Console\BashCompletion\Completion\ShellPathCompletion;
@@ -36,10 +19,9 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class File extends Command implements Completion\CompletionAwareInterface {
- protected IConfig $config;
-
- public function __construct(IConfig $config) {
- $this->config = $config;
+ public function __construct(
+ protected IConfig $config,
+ ) {
parent::__construct();
}
@@ -80,7 +62,7 @@ class File extends Command implements Completion\CompletionAwareInterface {
}
if (($rotateSize = $input->getOption('rotate-size')) !== null) {
- $rotateSize = \OCP\Util::computerFileSize($rotateSize);
+ $rotateSize = Util::computerFileSize($rotateSize);
$this->validateRotateSize($rotateSize);
$toBeSet['log_rotate_size'] = $rotateSize;
}
@@ -98,31 +80,29 @@ class File extends Command implements Completion\CompletionAwareInterface {
} else {
$enabledText = 'disabled';
}
- $output->writeln('Log backend file: '.$enabledText);
+ $output->writeln('Log backend file: ' . $enabledText);
- $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data');
- $defaultLogFile = rtrim($dataDir, '/').'/nextcloud.log';
- $output->writeln('Log file: '.$this->config->getSystemValue('logfile', $defaultLogFile));
+ $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data');
+ $defaultLogFile = rtrim($dataDir, '/') . '/nextcloud.log';
+ $output->writeln('Log file: ' . $this->config->getSystemValue('logfile', $defaultLogFile));
$rotateSize = $this->config->getSystemValue('log_rotate_size', 100 * 1024 * 1024);
if ($rotateSize) {
- $rotateString = \OCP\Util::humanFileSize($rotateSize);
+ $rotateString = Util::humanFileSize($rotateSize);
} else {
$rotateString = 'disabled';
}
- $output->writeln('Rotate at: '.$rotateString);
+ $output->writeln('Rotate at: ' . $rotateString);
return 0;
}
/**
- * @param mixed $rotateSize
* @throws \InvalidArgumentException
*/
- protected function validateRotateSize(&$rotateSize) {
+ protected function validateRotateSize(false|int|float $rotateSize): void {
if ($rotateSize === false) {
throw new \InvalidArgumentException('Error parsing log rotation file size');
}
- $rotateSize = (int) $rotateSize;
if ($rotateSize < 0) {
throw new \InvalidArgumentException('Log rotation file size must be non-negative');
}
diff --git a/core/Command/Log/Manage.php b/core/Command/Log/Manage.php
index 63a8efde370..f67f0d969f6 100644
--- a/core/Command/Log/Manage.php
+++ b/core/Command/Log/Manage.php
@@ -1,28 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Johannes Ernst <jernst@indiecomputing.com>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Tim Terhorst <mynamewastaken+gitlab@gmail.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Log;
@@ -39,10 +20,9 @@ class Manage extends Command implements CompletionAwareInterface {
public const DEFAULT_LOG_LEVEL = 2;
public const DEFAULT_TIMEZONE = 'UTC';
- protected IConfig $config;
-
- public function __construct(IConfig $config) {
- $this->config = $config;
+ public function __construct(
+ protected IConfig $config,
+ ) {
parent::__construct();
}
@@ -104,14 +84,14 @@ class Manage extends Command implements CompletionAwareInterface {
// display configuration
$backend = $this->config->getSystemValue('log_type', self::DEFAULT_BACKEND);
- $output->writeln('Enabled logging backend: '.$backend);
+ $output->writeln('Enabled logging backend: ' . $backend);
$levelNum = $this->config->getSystemValue('loglevel', self::DEFAULT_LOG_LEVEL);
$level = $this->convertLevelNumber($levelNum);
- $output->writeln('Log level: '.$level.' ('.$levelNum.')');
+ $output->writeln('Log level: ' . $level . ' (' . $levelNum . ')');
$timezone = $this->config->getSystemValue('logtimezone', self::DEFAULT_TIMEZONE);
- $output->writeln('Log timezone: '.$timezone);
+ $output->writeln('Log timezone: ' . $timezone);
return 0;
}
@@ -120,7 +100,7 @@ class Manage extends Command implements CompletionAwareInterface {
* @throws \InvalidArgumentException
*/
protected function validateBackend($backend) {
- if (!class_exists('OC\\Log\\'.ucfirst($backend))) {
+ if (!class_exists('OC\\Log\\' . ucfirst($backend))) {
throw new \InvalidArgumentException('Invalid backend');
}
}
diff --git a/core/Command/Maintenance/DataFingerprint.php b/core/Command/Maintenance/DataFingerprint.php
index a57dc307b18..014d6c411a4 100644
--- a/core/Command/Maintenance/DataFingerprint.php
+++ b/core/Command/Maintenance/DataFingerprint.php
@@ -1,24 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Maintenance;
@@ -29,13 +14,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class DataFingerprint extends Command {
- protected IConfig $config;
- protected ITimeFactory $timeFactory;
-
- public function __construct(IConfig $config,
- ITimeFactory $timeFactory) {
- $this->config = $config;
- $this->timeFactory = $timeFactory;
+ public function __construct(
+ protected IConfig $config,
+ protected ITimeFactory $timeFactory,
+ ) {
parent::__construct();
}
@@ -46,7 +28,9 @@ class DataFingerprint extends Command {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->config->setSystemValue('data-fingerprint', md5($this->timeFactory->getTime()));
+ $fingerPrint = md5($this->timeFactory->getTime());
+ $this->config->setSystemValue('data-fingerprint', $fingerPrint);
+ $output->writeln('<info>Updated data-fingerprint to ' . $fingerPrint . '</info>');
return 0;
}
}
diff --git a/core/Command/Maintenance/Install.php b/core/Command/Maintenance/Install.php
index fa93a661906..6170c5a2638 100644
--- a/core/Command/Maintenance/Install.php
+++ b/core/Command/Maintenance/Install.php
@@ -1,42 +1,21 @@
<?php
+
+declare(strict_types=1);
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Hansson <daniel@techandme.se>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Thomas Pulzer <t.pulzer@kniel.de>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Maintenance;
use bantu\IniGetWrapper\IniGetWrapper;
use InvalidArgumentException;
-use OC\Installer;
+use OC\Console\TimestampFormatter;
+use OC\Migration\ConsoleOutput;
use OC\Setup;
use OC\SystemConfig;
-use OCP\Defaults;
-use Psr\Log\LoggerInterface;
+use OCP\Server;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
@@ -47,16 +26,14 @@ use Throwable;
use function get_class;
class Install extends Command {
- private SystemConfig $config;
- private IniGetWrapper $iniGetWrapper;
-
- public function __construct(SystemConfig $config, IniGetWrapper $iniGetWrapper) {
+ public function __construct(
+ private SystemConfig $config,
+ private IniGetWrapper $iniGetWrapper,
+ ) {
parent::__construct();
- $this->config = $config;
- $this->iniGetWrapper = $iniGetWrapper;
}
- protected function configure() {
+ protected function configure(): void {
$this
->setName('maintenance:install')
->setDescription('install Nextcloud')
@@ -64,35 +41,27 @@ class Install extends Command {
->addOption('database-name', null, InputOption::VALUE_REQUIRED, 'Name of the database')
->addOption('database-host', null, InputOption::VALUE_REQUIRED, 'Hostname of the database', 'localhost')
->addOption('database-port', null, InputOption::VALUE_REQUIRED, 'Port the database is listening on')
- ->addOption('database-user', null, InputOption::VALUE_REQUIRED, 'User name to connect to the database')
+ ->addOption('database-user', null, InputOption::VALUE_REQUIRED, 'Login to connect to the database')
->addOption('database-pass', null, InputOption::VALUE_OPTIONAL, 'Password of the database user', null)
->addOption('database-table-space', null, InputOption::VALUE_OPTIONAL, 'Table space of the database (oci only)', null)
- ->addOption('admin-user', null, InputOption::VALUE_REQUIRED, 'User name of the admin account', 'admin')
+ ->addOption('disable-admin-user', null, InputOption::VALUE_NONE, 'Disable the creation of an admin user')
+ ->addOption('admin-user', null, InputOption::VALUE_REQUIRED, 'Login of the admin account', 'admin')
->addOption('admin-pass', null, InputOption::VALUE_REQUIRED, 'Password of the admin account')
->addOption('admin-email', null, InputOption::VALUE_OPTIONAL, 'E-Mail of the admin account')
- ->addOption('data-dir', null, InputOption::VALUE_REQUIRED, 'Path to data directory', \OC::$SERVERROOT."/data");
+ ->addOption('data-dir', null, InputOption::VALUE_REQUIRED, 'Path to data directory', \OC::$SERVERROOT . '/data');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
// validate the environment
- $server = \OC::$server;
- $setupHelper = new Setup(
- $this->config,
- $this->iniGetWrapper,
- $server->getL10N('lib'),
- $server->query(Defaults::class),
- $server->get(LoggerInterface::class),
- $server->getSecureRandom(),
- \OC::$server->query(Installer::class)
- );
+ $setupHelper = Server::get(Setup::class);
$sysInfo = $setupHelper->getSystemInfo(true);
$errors = $sysInfo['errors'];
if (count($errors) > 0) {
$this->printErrors($output, $errors);
// ignore the OS X setup warning
- if (count($errors) !== 1 ||
- (string)$errors[0]['error'] !== 'Mac OS X is not supported and Nextcloud will not work properly on this platform. Use it at your own risk! ') {
+ if (count($errors) !== 1
+ || (string)$errors[0]['error'] !== 'Mac OS X is not supported and Nextcloud will not work properly on this platform. Use it at your own risk!') {
return 1;
}
}
@@ -100,8 +69,17 @@ class Install extends Command {
// validate user input
$options = $this->validateInput($input, $output, array_keys($sysInfo['databases']));
+ if ($output->isVerbose()) {
+ // Prepend each line with a little timestamp
+ $timestampFormatter = new TimestampFormatter(null, $output->getFormatter());
+ $output->setFormatter($timestampFormatter);
+ $migrationOutput = new ConsoleOutput($output);
+ } else {
+ $migrationOutput = null;
+ }
+
// perform installation
- $errors = $setupHelper->install($options);
+ $errors = $setupHelper->install($options, $migrationOutput);
if (count($errors) > 0) {
$this->printErrors($output, $errors);
return 1;
@@ -109,7 +87,7 @@ class Install extends Command {
if ($setupHelper->shouldRemoveCanInstallFile()) {
$output->writeln('<warn>Could not remove CAN_INSTALL from the config folder. Please remove this file manually.</warn>');
}
- $output->writeln("Nextcloud was successfully installed");
+ $output->writeln('Nextcloud was successfully installed');
return 0;
}
@@ -123,7 +101,7 @@ class Install extends Command {
$db = strtolower($input->getOption('database'));
if (!in_array($db, $supportedDatabases)) {
- throw new InvalidArgumentException("Database <$db> is not supported.");
+ throw new InvalidArgumentException("Database <$db> is not supported. " . implode(', ', $supportedDatabases) . ' are supported.');
}
$dbUser = $input->getOption('database-user');
@@ -141,8 +119,9 @@ class Install extends Command {
$dbHost .= ':' . $dbPort;
}
if ($input->hasParameterOption('--database-pass')) {
- $dbPass = (string) $input->getOption('database-pass');
+ $dbPass = (string)$input->getOption('database-pass');
}
+ $disableAdminUser = (bool)$input->getOption('disable-admin-user');
$adminLogin = $input->getOption('admin-user');
$adminPassword = $input->getOption('admin-pass');
$adminEmail = $input->getOption('admin-email');
@@ -150,31 +129,31 @@ class Install extends Command {
if ($db !== 'sqlite') {
if (is_null($dbUser)) {
- throw new InvalidArgumentException("Database user not provided.");
+ throw new InvalidArgumentException('Database account not provided.');
}
if (is_null($dbName)) {
- throw new InvalidArgumentException("Database name not provided.");
+ throw new InvalidArgumentException('Database name not provided.');
}
if (is_null($dbPass)) {
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
- $question = new Question('What is the password to access the database with user <'.$dbUser.'>?');
+ $question = new Question('What is the password to access the database with user <' . $dbUser . '>?');
$question->setHidden(true);
$question->setHiddenFallback(false);
$dbPass = $helper->ask($input, $output, $question);
}
}
- if (is_null($adminPassword)) {
+ if (!$disableAdminUser && $adminPassword === null) {
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
- $question = new Question('What is the password you like to use for the admin account <'.$adminLogin.'>?');
+ $question = new Question('What is the password you like to use for the admin account <' . $adminLogin . '>?');
$question->setHidden(true);
$question->setHiddenFallback(false);
$adminPassword = $helper->ask($input, $output, $question);
}
- if ($adminEmail !== null && !filter_var($adminEmail, FILTER_VALIDATE_EMAIL)) {
+ if (!$disableAdminUser && $adminEmail !== null && !filter_var($adminEmail, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid e-mail-address <' . $adminEmail . '> for <' . $adminLogin . '>.');
}
@@ -184,6 +163,7 @@ class Install extends Command {
'dbpass' => $dbPass,
'dbname' => $dbName,
'dbhost' => $dbHost,
+ 'admindisable' => $disableAdminUser,
'adminlogin' => $adminLogin,
'adminpass' => $adminPassword,
'adminemail' => $adminEmail,
@@ -197,9 +177,9 @@ class Install extends Command {
/**
* @param OutputInterface $output
- * @param $errors
+ * @param array<string|array> $errors
*/
- protected function printErrors(OutputInterface $output, $errors) {
+ protected function printErrors(OutputInterface $output, array $errors): void {
foreach ($errors as $error) {
if (is_array($error)) {
$output->writeln('<error>' . $error['error'] . '</error>');
diff --git a/core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php b/core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php
index 873744e6f94..f8f19a61993 100644
--- a/core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php
+++ b/core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php
@@ -3,27 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2019 Xheni Myrtaj <xheni@protonmail.com>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Xheni Myrtaj <myrtajxheni@gmail.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Maintenance\Mimetype;
@@ -31,17 +12,21 @@ class GenerateMimetypeFileBuilder {
/**
* Generate mime type list file
*
- * @param array $aliases
+ * @param array<string,string> $aliases
* @return string
*/
- public function generateFile(array $aliases): string {
+ public function generateFile(array $aliases, array $names): string {
// Remove comments
$aliases = array_filter($aliases, static function ($key) {
+ // Single digit extensions will be treated as integers
+ // Let's make sure they are strings
+ // https://github.com/nextcloud/server/issues/42902
+ $key = (string)$key;
return !($key === '' || $key[0] === '_');
}, ARRAY_FILTER_USE_KEY);
// Fetch all files
- $dir = new \DirectoryIterator(\OC::$SERVERROOT.'/core/img/filetypes');
+ $dir = new \DirectoryIterator(\OC::$SERVERROOT . '/core/img/filetypes');
$files = [];
foreach ($dir as $fileInfo) {
@@ -57,7 +42,7 @@ class GenerateMimetypeFileBuilder {
// Fetch all themes!
$themes = [];
- $dirs = new \DirectoryIterator(\OC::$SERVERROOT.'/themes/');
+ $dirs = new \DirectoryIterator(\OC::$SERVERROOT . '/themes/');
foreach ($dirs as $dir) {
//Valid theme dir
if ($dir->isFile() || $dir->isDot()) {
@@ -86,6 +71,15 @@ class GenerateMimetypeFileBuilder {
sort($themes[$theme]);
}
+ $namesOutput = '';
+ foreach ($names as $key => $name) {
+ if (str_starts_with($key, '_') || trim($name) === '') {
+ // Skip internal names
+ continue;
+ }
+ $namesOutput .= "'$key': t('core', " . json_encode($name) . "),\n";
+ }
+
//Generate the JS
return '/**
* This file is automatically generated
@@ -98,7 +92,8 @@ class GenerateMimetypeFileBuilder {
OC.MimeTypeList={
aliases: ' . json_encode($aliases, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . ',
files: ' . json_encode($files, JSON_PRETTY_PRINT) . ',
- themes: ' . json_encode($themes, JSON_PRETTY_PRINT) . '
+ themes: ' . json_encode($themes, JSON_PRETTY_PRINT) . ',
+ names: {' . $namesOutput . '},
};
';
}
diff --git a/core/Command/Maintenance/Mimetype/UpdateDB.php b/core/Command/Maintenance/Mimetype/UpdateDB.php
index edc42c0fdcd..4467e89eb32 100644
--- a/core/Command/Maintenance/Mimetype/UpdateDB.php
+++ b/core/Command/Maintenance/Mimetype/UpdateDB.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Maintenance\Mimetype;
@@ -35,16 +18,11 @@ use Symfony\Component\Console\Output\OutputInterface;
class UpdateDB extends Command {
public const DEFAULT_MIMETYPE = 'application/octet-stream';
- protected IMimeTypeDetector $mimetypeDetector;
- protected IMimeTypeLoader $mimetypeLoader;
-
public function __construct(
- IMimeTypeDetector $mimetypeDetector,
- IMimeTypeLoader $mimetypeLoader
+ protected IMimeTypeDetector $mimetypeDetector,
+ protected IMimeTypeLoader $mimetypeLoader,
) {
parent::__construct();
- $this->mimetypeDetector = $mimetypeDetector;
- $this->mimetypeLoader = $mimetypeLoader;
}
protected function configure() {
@@ -67,6 +45,10 @@ class UpdateDB extends Command {
$totalNewMimetypes = 0;
foreach ($mappings as $ext => $mimetypes) {
+ // Single digit extensions will be treated as integers
+ // Let's make sure they are strings
+ // https://github.com/nextcloud/server/issues/42902
+ $ext = (string)$ext;
if ($ext[0] === '_') {
// comment
continue;
@@ -77,21 +59,21 @@ class UpdateDB extends Command {
$mimetypeId = $this->mimetypeLoader->getId($mimetype);
if (!$existing) {
- $output->writeln('Added mimetype "'.$mimetype.'" to database');
+ $output->writeln('Added mimetype "' . $mimetype . '" to database');
$totalNewMimetypes++;
}
if (!$existing || $input->getOption('repair-filecache')) {
$touchedFilecacheRows = $this->mimetypeLoader->updateFilecache($ext, $mimetypeId);
if ($touchedFilecacheRows > 0) {
- $output->writeln('Updated '.$touchedFilecacheRows.' filecache rows for mimetype "'.$mimetype.'"');
+ $output->writeln('Updated ' . $touchedFilecacheRows . ' filecache rows for mimetype "' . $mimetype . '"');
}
$totalFilecacheUpdates += $touchedFilecacheRows;
}
}
- $output->writeln('Added '.$totalNewMimetypes.' new mimetypes');
- $output->writeln('Updated '.$totalFilecacheUpdates.' filecache rows');
+ $output->writeln('Added ' . $totalNewMimetypes . ' new mimetypes');
+ $output->writeln('Updated ' . $totalFilecacheUpdates . ' filecache rows');
return 0;
}
}
diff --git a/core/Command/Maintenance/Mimetype/UpdateJS.php b/core/Command/Maintenance/Mimetype/UpdateJS.php
index 6a5a3d0ac61..2132ff54c6d 100644
--- a/core/Command/Maintenance/Mimetype/UpdateJS.php
+++ b/core/Command/Maintenance/Mimetype/UpdateJS.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Xheni Myrtaj <myrtajxheni@gmail.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Maintenance\Mimetype;
@@ -31,13 +14,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class UpdateJS extends Command {
- protected IMimeTypeDetector $mimetypeDetector;
-
public function __construct(
- IMimeTypeDetector $mimetypeDetector
+ protected IMimeTypeDetector $mimetypeDetector,
) {
parent::__construct();
- $this->mimetypeDetector = $mimetypeDetector;
}
protected function configure() {
@@ -52,7 +32,8 @@ class UpdateJS extends Command {
// Output the JS
$generatedMimetypeFile = new GenerateMimetypeFileBuilder();
- file_put_contents(\OC::$SERVERROOT.'/core/js/mimetypelist.js', $generatedMimetypeFile->generateFile($aliases));
+ $namings = $this->mimetypeDetector->getAllNamings();
+ file_put_contents(\OC::$SERVERROOT . '/core/js/mimetypelist.js', $generatedMimetypeFile->generateFile($aliases, $namings));
$output->writeln('<info>mimetypelist.js is updated');
return 0;
diff --git a/core/Command/Maintenance/Mode.php b/core/Command/Maintenance/Mode.php
index c2af33aa4ed..853e843f57b 100644
--- a/core/Command/Maintenance/Mode.php
+++ b/core/Command/Maintenance/Mode.php
@@ -1,27 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Michael Weimann <mail@michael-weimann.eu>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author scolebrook <scolebrook@mac.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Maintenance;
@@ -33,17 +15,17 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Mode extends Command {
- protected IConfig $config;
-
- public function __construct(IConfig $config) {
- $this->config = $config;
+ public function __construct(
+ protected IConfig $config,
+ ) {
parent::__construct();
}
protected function configure() {
$this
->setName('maintenance:mode')
- ->setDescription('set maintenance mode')
+ ->setDescription('Show or toggle maintenance mode status')
+ ->setHelp('Maintenance mode prevents new logins, locks existing sessions, and disables background jobs.')
->addOption(
'on',
null,
diff --git a/core/Command/Maintenance/Repair.php b/core/Command/Maintenance/Repair.php
index 01e62f2cd32..f0c88f6811b 100644
--- a/core/Command/Maintenance/Repair.php
+++ b/core/Command/Maintenance/Repair.php
@@ -1,38 +1,13 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Temtaime <temtaime@gmail.com>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Maintenance;
use Exception;
-use OCP\App\IAppManager;
-use OCP\EventDispatcher\Event;
-use OCP\EventDispatcher\IEventDispatcher;
-use OCP\IConfig;
use OC\Repair\Events\RepairAdvanceEvent;
use OC\Repair\Events\RepairErrorEvent;
use OC\Repair\Events\RepairFinishEvent;
@@ -40,6 +15,10 @@ use OC\Repair\Events\RepairInfoEvent;
use OC\Repair\Events\RepairStartEvent;
use OC\Repair\Events\RepairStepEvent;
use OC\Repair\Events\RepairWarningEvent;
+use OCP\App\IAppManager;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IConfig;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
@@ -47,18 +26,16 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Repair extends Command {
- protected \OC\Repair $repair;
- protected IConfig $config;
- private IEventDispatcher $dispatcher;
private ProgressBar $progress;
private OutputInterface $output;
- private IAppManager $appManager;
+ protected bool $errored = false;
- public function __construct(\OC\Repair $repair, IConfig $config, IEventDispatcher $dispatcher, IAppManager $appManager) {
- $this->repair = $repair;
- $this->config = $config;
- $this->dispatcher = $dispatcher;
- $this->appManager = $appManager;
+ public function __construct(
+ protected \OC\Repair $repair,
+ protected IConfig $config,
+ private IEventDispatcher $dispatcher,
+ private IAppManager $appManager,
+ ) {
parent::__construct();
}
@@ -84,7 +61,7 @@ class Repair extends Command {
$this->repair->addStep($step);
}
- $apps = $this->appManager->getInstalledApps();
+ $apps = $this->appManager->getEnabledApps();
foreach ($apps as $app) {
if (!$this->appManager->isEnabledForUser($app)) {
continue;
@@ -93,7 +70,7 @@ class Repair extends Command {
if (!is_array($info)) {
continue;
}
- \OC_App::loadApp($app);
+ $this->appManager->loadApp($app);
$steps = $info['repair-steps']['post-migration'];
foreach ($steps as $step) {
try {
@@ -104,6 +81,8 @@ class Repair extends Command {
}
}
+
+
$maintenanceMode = $this->config->getSystemValueBool('maintenance');
$this->config->setSystemValue('maintenance', true);
@@ -120,7 +99,7 @@ class Repair extends Command {
$this->repair->run();
$this->config->setSystemValue('maintenance', $maintenanceMode);
- return 0;
+ return $this->errored ? 1 : 0;
}
public function handleRepairFeedBack(Event $event): void {
@@ -139,6 +118,7 @@ class Repair extends Command {
$this->output->writeln('<comment> - WARNING: ' . $event->getMessage() . '</comment>');
} elseif ($event instanceof RepairErrorEvent) {
$this->output->writeln('<error> - ERROR: ' . $event->getMessage() . '</error>');
+ $this->errored = true;
}
}
}
diff --git a/core/Command/Maintenance/RepairShareOwnership.php b/core/Command/Maintenance/RepairShareOwnership.php
index c4d8da7bf74..16675545afe 100644
--- a/core/Command/Maintenance/RepairShareOwnership.php
+++ b/core/Command/Maintenance/RepairShareOwnership.php
@@ -3,34 +3,18 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de>
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Maintenance;
-use Symfony\Component\Console\Command\Command;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserManager;
+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;
@@ -38,15 +22,10 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class RepairShareOwnership extends Command {
- private IDBConnection $dbConnection;
- private IUserManager $userManager;
-
public function __construct(
- IDBConnection $dbConnection,
- IUserManager $userManager
+ private IDBConnection $dbConnection,
+ private IUserManager $userManager,
) {
- $this->dbConnection = $dbConnection;
- $this->userManager = $userManager;
parent::__construct();
}
@@ -55,7 +34,7 @@ class RepairShareOwnership extends Command {
->setName('maintenance:repair-share-owner')
->setDescription('repair invalid share-owner entries in the database')
->addOption('no-confirm', 'y', InputOption::VALUE_NONE, "Don't ask for confirmation before repairing the shares")
- ->addArgument('user', InputArgument::OPTIONAL, "User to fix incoming shares for, if omitted all users will be fixed");
+ ->addArgument('user', InputArgument::OPTIONAL, 'User to fix incoming shares for, if omitted all users will be fixed');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
@@ -73,15 +52,16 @@ class RepairShareOwnership extends Command {
}
if ($shares) {
- $output->writeln("");
- $output->writeln("Found " . count($shares) . " shares with invalid share owner");
+ $output->writeln('');
+ $output->writeln('Found ' . count($shares) . ' shares with invalid share owner');
foreach ($shares as $share) {
/** @var array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string} $share */
$output->writeln(" - share {$share['shareId']} from \"{$share['initiator']}\" to \"{$share['receiver']}\" at \"{$share['fileTarget']}\", owned by \"{$share['owner']}\", that should be owned by \"{$share['mountOwner']}\"");
}
- $output->writeln("");
+ $output->writeln('');
if (!$noConfirm) {
+ /** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Repair these shares? [y/N]', false);
@@ -89,10 +69,10 @@ class RepairShareOwnership extends Command {
return 0;
}
}
- $output->writeln("Repairing " . count($shares) . " shares");
+ $output->writeln('Repairing ' . count($shares) . ' shares');
$this->repairShares($shares);
} else {
- $output->writeln("Found no shares with invalid share owner");
+ $output->writeln('Found no shares with invalid share owner');
}
return 0;
@@ -107,7 +87,7 @@ class RepairShareOwnership extends Command {
$brokenShares = $qb
->select('s.id', 'm.user_id', 's.uid_owner', 's.uid_initiator', 's.share_with', 's.file_target')
->from('share', 's')
- ->join('s', 'filecache', 'f', $qb->expr()->eq('s.item_source', $qb->expr()->castColumn('f.fileid', IQueryBuilder::PARAM_STR)))
+ ->join('s', 'filecache', 'f', $qb->expr()->eq($qb->expr()->castColumn('s.item_source', IQueryBuilder::PARAM_INT), 'f.fileid'))
->join('s', 'mounts', 'm', $qb->expr()->eq('f.storage', 'm.storage_id'))
->where($qb->expr()->neq('m.user_id', 's.uid_owner'))
->andWhere($qb->expr()->eq($qb->func()->concat($qb->expr()->literal('/'), 'm.user_id', $qb->expr()->literal('/')), 'm.mount_point'))
@@ -118,7 +98,7 @@ class RepairShareOwnership extends Command {
foreach ($brokenShares as $share) {
$found[] = [
- 'shareId' => (int) $share['id'],
+ 'shareId' => (int)$share['id'],
'fileTarget' => $share['file_target'],
'initiator' => $share['uid_initiator'],
'receiver' => $share['share_with'],
@@ -152,7 +132,7 @@ class RepairShareOwnership extends Command {
foreach ($brokenShares as $share) {
$found[] = [
- 'shareId' => (int) $share['id'],
+ 'shareId' => (int)$share['id'],
'fileTarget' => $share['file_target'],
'initiator' => $share['uid_initiator'],
'receiver' => $share['share_with'],
diff --git a/core/Command/Maintenance/UpdateHtaccess.php b/core/Command/Maintenance/UpdateHtaccess.php
index 67c6db22b21..eeff3bf8c62 100644
--- a/core/Command/Maintenance/UpdateHtaccess.php
+++ b/core/Command/Maintenance/UpdateHtaccess.php
@@ -1,28 +1,13 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Maintenance;
+use OC\Setup;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -35,11 +20,11 @@ class UpdateHtaccess extends Command {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- if (\OC\Setup::updateHtaccess()) {
+ if (Setup::updateHtaccess()) {
$output->writeln('.htaccess has been updated');
return 0;
} else {
- $output->writeln('<error>Error updating .htaccess file, not enough permissions or "overwrite.cli.url" set to an invalid URL?</error>');
+ $output->writeln('<error>Error updating .htaccess file, not enough permissions, not enough free space or "overwrite.cli.url" set to an invalid URL?</error>');
return 1;
}
}
diff --git a/core/Command/Maintenance/UpdateTheme.php b/core/Command/Maintenance/UpdateTheme.php
index e469b218b3f..3fbcb546cca 100644
--- a/core/Command/Maintenance/UpdateTheme.php
+++ b/core/Command/Maintenance/UpdateTheme.php
@@ -1,27 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Maintenance;
@@ -33,15 +14,11 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class UpdateTheme extends UpdateJS {
- protected IMimeTypeDetector $mimetypeDetector;
- protected ICacheFactory $cacheFactory;
-
public function __construct(
IMimeTypeDetector $mimetypeDetector,
- ICacheFactory $cacheFactory
+ protected ICacheFactory $cacheFactory,
) {
parent::__construct($mimetypeDetector);
- $this->cacheFactory = $cacheFactory;
}
protected function configure() {
diff --git a/core/Command/Memcache/DistributedClear.php b/core/Command/Memcache/DistributedClear.php
new file mode 100644
index 00000000000..424f21f1e81
--- /dev/null
+++ b/core/Command/Memcache/DistributedClear.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Memcache;
+
+use OC\Core\Command\Base;
+use OCP\ICacheFactory;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class DistributedClear extends Base {
+ public function __construct(
+ protected ICacheFactory $cacheFactory,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('memcache:distributed:clear')
+ ->setDescription('Clear values from the distributed memcache')
+ ->addOption('prefix', null, InputOption::VALUE_REQUIRED, 'Only remove keys matching the prefix');
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $cache = $this->cacheFactory->createDistributed();
+ $prefix = $input->getOption('prefix');
+ if ($cache->clear($prefix)) {
+ if ($prefix) {
+ $output->writeln('<info>Distributed cache matching prefix ' . $prefix . ' cleared</info>');
+ } else {
+ $output->writeln('<info>Distributed cache cleared</info>');
+ }
+ return 0;
+ } else {
+ $output->writeln('<error>Failed to clear cache</error>');
+ return 1;
+ }
+ }
+}
diff --git a/core/Command/Memcache/DistributedDelete.php b/core/Command/Memcache/DistributedDelete.php
new file mode 100644
index 00000000000..ae0855acb03
--- /dev/null
+++ b/core/Command/Memcache/DistributedDelete.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Memcache;
+
+use OC\Core\Command\Base;
+use OCP\ICacheFactory;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class DistributedDelete extends Base {
+ public function __construct(
+ protected ICacheFactory $cacheFactory,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('memcache:distributed:delete')
+ ->setDescription('Delete a value in the distributed memcache')
+ ->addArgument('key', InputArgument::REQUIRED, 'The key to delete');
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $cache = $this->cacheFactory->createDistributed();
+ $key = $input->getArgument('key');
+ if ($cache->remove($key)) {
+ $output->writeln('<info>Distributed cache key <info>' . $key . '</info> deleted</info>');
+ return 0;
+ } else {
+ $output->writeln('<error>Failed to delete cache key ' . $key . '</error>');
+ return 1;
+ }
+ }
+}
diff --git a/core/Command/Memcache/DistributedGet.php b/core/Command/Memcache/DistributedGet.php
new file mode 100644
index 00000000000..bf1b00d312d
--- /dev/null
+++ b/core/Command/Memcache/DistributedGet.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Memcache;
+
+use OC\Core\Command\Base;
+use OCP\ICacheFactory;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class DistributedGet extends Base {
+ public function __construct(
+ protected ICacheFactory $cacheFactory,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('memcache:distributed:get')
+ ->setDescription('Get a value from the distributed memcache')
+ ->addArgument('key', InputArgument::REQUIRED, 'The key to retrieve');
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $cache = $this->cacheFactory->createDistributed();
+ $key = $input->getArgument('key');
+
+ $value = $cache->get($key);
+ $this->writeMixedInOutputFormat($input, $output, $value);
+ return 0;
+ }
+}
diff --git a/core/Command/Memcache/DistributedSet.php b/core/Command/Memcache/DistributedSet.php
new file mode 100644
index 00000000000..0f31c22f730
--- /dev/null
+++ b/core/Command/Memcache/DistributedSet.php
@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Memcache;
+
+use OC\Core\Command\Base;
+use OC\Core\Command\Config\System\CastHelper;
+use OCP\ICacheFactory;
+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 DistributedSet extends Base {
+ public function __construct(
+ protected ICacheFactory $cacheFactory,
+ private CastHelper $castHelper,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('memcache:distributed:set')
+ ->setDescription('Set a value in the distributed memcache')
+ ->addArgument('key', InputArgument::REQUIRED, 'The key to set')
+ ->addArgument('value', InputArgument::REQUIRED, 'The value to set')
+ ->addOption(
+ 'type',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Value type [string, integer, float, boolean, json, null]',
+ 'string'
+ );
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $cache = $this->cacheFactory->createDistributed();
+ $key = $input->getArgument('key');
+ $value = $input->getArgument('value');
+ $type = $input->getOption('type');
+ ['value' => $value, 'readable-value' => $readable] = $this->castHelper->castValue($value, $type);
+ if ($cache->set($key, $value)) {
+ $output->writeln('Distributed cache key <info>' . $key . '</info> set to <info>' . $readable . '</info>');
+ return 0;
+ } else {
+ $output->writeln('<error>Failed to set cache key ' . $key . '</error>');
+ return 1;
+ }
+ }
+}
diff --git a/core/Command/Memcache/RedisCommand.php b/core/Command/Memcache/RedisCommand.php
new file mode 100644
index 00000000000..429dd28f3b3
--- /dev/null
+++ b/core/Command/Memcache/RedisCommand.php
@@ -0,0 +1,56 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Robin Appelman <robin@icewind.nl>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Memcache;
+
+use OC\Core\Command\Base;
+use OC\RedisFactory;
+use OCP\ICertificateManager;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class RedisCommand extends Base {
+ public function __construct(
+ protected ICertificateManager $certificateManager,
+ protected RedisFactory $redisFactory,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('memcache:redis:command')
+ ->setDescription('Send raw redis command to the configured redis server')
+ ->addArgument('redis-command', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'The command to run');
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $command = $input->getArgument('redis-command');
+ if (!$this->redisFactory->isAvailable()) {
+ $output->writeln('<error>No redis server configured</error>');
+ return 1;
+ }
+ try {
+ $redis = $this->redisFactory->getInstance();
+ } catch (\Exception $e) {
+ $output->writeln('Failed to connect to redis: ' . $e->getMessage());
+ return 1;
+ }
+
+ $redis->setOption(\Redis::OPT_REPLY_LITERAL, true);
+ $result = $redis->rawCommand(...$command);
+ if ($result === false) {
+ $output->writeln('<error>Redis command failed</error>');
+ return 1;
+ }
+ $output->writeln($result);
+ return 0;
+ }
+}
diff --git a/core/Command/Preview/Cleanup.php b/core/Command/Preview/Cleanup.php
new file mode 100644
index 00000000000..dad981a5243
--- /dev/null
+++ b/core/Command/Preview/Cleanup.php
@@ -0,0 +1,88 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Preview;
+
+use OC\Core\Command\Base;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Cleanup extends Base {
+
+ public function __construct(
+ private IRootFolder $rootFolder,
+ private LoggerInterface $logger,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('preview:cleanup')
+ ->setDescription('Removes existing preview files');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ try {
+ $appDataFolder = $this->rootFolder->get($this->rootFolder->getAppDataDirectoryName());
+
+ if (!$appDataFolder instanceof Folder) {
+ $this->logger->error("Previews can't be removed: appdata is not a folder");
+ $output->writeln("Previews can't be removed: appdata is not a folder");
+ return 1;
+ }
+
+ /** @var Folder $previewFolder */
+ $previewFolder = $appDataFolder->get('preview');
+
+ } catch (NotFoundException $e) {
+ $this->logger->error("Previews can't be removed: appdata folder can't be found", ['exception' => $e]);
+ $output->writeln("Previews can't be removed: preview folder isn't deletable");
+ return 1;
+ }
+
+ if (!$previewFolder->isDeletable()) {
+ $this->logger->error("Previews can't be removed: preview folder isn't deletable");
+ $output->writeln("Previews can't be removed: preview folder isn't deletable");
+ return 1;
+ }
+
+ try {
+ $previewFolder->delete();
+ $this->logger->debug('Preview folder deleted');
+ $output->writeln('Preview folder deleted', OutputInterface::VERBOSITY_VERBOSE);
+ } catch (NotFoundException $e) {
+ $output->writeln("Previews weren't deleted: preview folder was not found while deleting it");
+ $this->logger->error("Previews weren't deleted: preview folder was not found while deleting it", ['exception' => $e]);
+ return 1;
+ } catch (NotPermittedException $e) {
+ $output->writeln("Previews weren't deleted: you don't have the permission to delete preview folder");
+ $this->logger->error("Previews weren't deleted: you don't have the permission to delete preview folder", ['exception' => $e]);
+ return 1;
+ }
+
+ try {
+ $appDataFolder->newFolder('preview');
+ $this->logger->debug('Preview folder recreated');
+ $output->writeln('Preview folder recreated', OutputInterface::VERBOSITY_VERBOSE);
+ } catch (NotPermittedException $e) {
+ $output->writeln("Preview folder was deleted, but you don't have the permission to create preview folder");
+ $this->logger->error("Preview folder was deleted, but you don't have the permission to create preview folder", ['exception' => $e]);
+ return 1;
+ }
+
+ $output->writeln('Previews removed');
+ return 0;
+ }
+}
diff --git a/core/Command/Preview/Generate.php b/core/Command/Preview/Generate.php
new file mode 100644
index 00000000000..222c42f613b
--- /dev/null
+++ b/core/Command/Preview/Generate.php
@@ -0,0 +1,118 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Preview;
+
+use OCP\Files\Config\IUserMountCache;
+use OCP\Files\File;
+use OCP\Files\IRootFolder;
+use OCP\Files\Node;
+use OCP\Files\NotFoundException;
+use OCP\IPreview;
+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 Generate extends Command {
+ public function __construct(
+ private IRootFolder $rootFolder,
+ private IUserMountCache $userMountCache,
+ private IPreview $previewManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure() {
+ $this
+ ->setName('preview:generate')
+ ->setDescription('generate a preview for a file')
+ ->addArgument('file', InputArgument::REQUIRED, 'path or fileid of the file to generate the preview for')
+ ->addOption('size', 's', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'size to generate the preview for in pixels, defaults to 64x64', ['64x64'])
+ ->addOption('crop', 'c', InputOption::VALUE_NONE, 'crop the previews instead of maintaining aspect ratio')
+ ->addOption('mode', 'm', InputOption::VALUE_REQUIRED, "mode for generating uncropped previews, 'cover' or 'fill'", IPreview::MODE_FILL);
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $fileInput = $input->getArgument('file');
+ $sizes = $input->getOption('size');
+ $sizes = array_map(function (string $size) use ($output) {
+ if (str_contains($size, 'x')) {
+ $sizeParts = explode('x', $size, 2);
+ } else {
+ $sizeParts = [$size, $size];
+ }
+ if (!is_numeric($sizeParts[0]) || !is_numeric($sizeParts[1] ?? null)) {
+ $output->writeln("<error>Invalid size $size</error>");
+ return null;
+ }
+
+ return array_map('intval', $sizeParts);
+ }, $sizes);
+ if (in_array(null, $sizes)) {
+ return 1;
+ }
+
+ $mode = $input->getOption('mode');
+ if ($mode !== IPreview::MODE_FILL && $mode !== IPreview::MODE_COVER) {
+ $output->writeln("<error>Invalid mode $mode</error>");
+ return 1;
+ }
+ $crop = $input->getOption('crop');
+ $file = $this->getFile($fileInput);
+ if (!$file) {
+ $output->writeln("<error>File $fileInput not found</error>");
+ return 1;
+ }
+ if (!$file instanceof File) {
+ $output->writeln("<error>Can't generate previews for folders</error>");
+ return 1;
+ }
+
+ if (!$this->previewManager->isAvailable($file)) {
+ $output->writeln('<error>No preview generator available for file of type' . $file->getMimetype() . '</error>');
+ return 1;
+ }
+
+ $specifications = array_map(function (array $sizes) use ($crop, $mode) {
+ return [
+ 'width' => $sizes[0],
+ 'height' => $sizes[1],
+ 'crop' => $crop,
+ 'mode' => $mode,
+ ];
+ }, $sizes);
+
+ $this->previewManager->generatePreviews($file, $specifications);
+ if (count($specifications) > 1) {
+ $output->writeln('generated <info>' . count($specifications) . '</info> previews');
+ } else {
+ $output->writeln('preview generated');
+ }
+ return 0;
+ }
+
+ private function getFile(string $fileInput): ?Node {
+ if (is_numeric($fileInput)) {
+ $mounts = $this->userMountCache->getMountsForFileId((int)$fileInput);
+ if (!$mounts) {
+ return null;
+ }
+ $mount = $mounts[0];
+ $userFolder = $this->rootFolder->getUserFolder($mount->getUser()->getUID());
+ return $userFolder->getFirstNodeById((int)$fileInput);
+ } else {
+ try {
+ return $this->rootFolder->get($fileInput);
+ } catch (NotFoundException $e) {
+ return null;
+ }
+ }
+ }
+}
diff --git a/core/Command/Preview/Repair.php b/core/Command/Preview/Repair.php
index 4d08024483b..a92a4cf8ed0 100644
--- a/core/Command/Preview/Repair.php
+++ b/core/Command/Preview/Repair.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, Morris Jobke <hey@morrisjobke.de>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Morris Jobke <hey@morrisjobke.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Preview;
@@ -37,6 +19,7 @@ use OCP\Lock\LockedException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -45,20 +28,17 @@ use Symfony\Component\Console\Question\ConfirmationQuestion;
use function pcntl_signal;
class Repair extends Command {
- protected IConfig $config;
- private IRootFolder $rootFolder;
- private LoggerInterface $logger;
private bool $stopSignalReceived = false;
private int $memoryLimit;
private int $memoryTreshold;
- private ILockingProvider $lockingProvider;
-
- public function __construct(IConfig $config, IRootFolder $rootFolder, LoggerInterface $logger, IniGetWrapper $phpIni, ILockingProvider $lockingProvider) {
- $this->config = $config;
- $this->rootFolder = $rootFolder;
- $this->logger = $logger;
- $this->lockingProvider = $lockingProvider;
+ public function __construct(
+ protected IConfig $config,
+ private IRootFolder $rootFolder,
+ private LoggerInterface $logger,
+ IniGetWrapper $phpIni,
+ private ILockingProvider $lockingProvider,
+ ) {
$this->memoryLimit = (int)$phpIni->getBytes('memory_limit');
$this->memoryTreshold = $this->memoryLimit - 25 * 1024 * 1024;
@@ -80,11 +60,11 @@ class Repair extends Command {
$thresholdInMiB = round($this->memoryTreshold / 1024 / 1024, 1);
$output->writeln("Memory limit is $limitInMiB MiB");
$output->writeln("Memory threshold is $thresholdInMiB MiB");
- $output->writeln("");
+ $output->writeln('');
$memoryCheckEnabled = true;
} else {
- $output->writeln("No memory limit in place - disabled memory check. Set a PHP memory limit to automatically stop the execution of this migration script once memory consumption is close to this limit.");
- $output->writeln("");
+ $output->writeln('No memory limit in place - disabled memory check. Set a PHP memory limit to automatically stop the execution of this migration script once memory consumption is close to this limit.');
+ $output->writeln('');
$memoryCheckEnabled = false;
}
@@ -93,20 +73,20 @@ class Repair extends Command {
if ($dryMode) {
- $output->writeln("INFO: The migration is run in dry mode and will not modify anything.");
- $output->writeln("");
+ $output->writeln('INFO: The migration is run in dry mode and will not modify anything.');
+ $output->writeln('');
} elseif ($deleteMode) {
- $output->writeln("WARN: The migration will _DELETE_ old previews.");
- $output->writeln("");
+ $output->writeln('WARN: The migration will _DELETE_ old previews.');
+ $output->writeln('');
}
$instanceId = $this->config->getSystemValueString('instanceid');
- $output->writeln("This will migrate all previews from the old preview location to the new one.");
+ $output->writeln('This will migrate all previews from the old preview location to the new one.');
$output->writeln('');
$output->writeln('Fetching previews that need to be migrated …');
- /** @var \OCP\Files\Folder $currentPreviewFolder */
+ /** @var Folder $currentPreviewFolder */
$currentPreviewFolder = $this->rootFolder->get("appdata_$instanceId/preview");
$directoryListing = $currentPreviewFolder->getDirectoryListing();
@@ -143,17 +123,18 @@ class Repair extends Command {
}
if ($total === 0) {
- $output->writeln("All previews are already migrated.");
+ $output->writeln('All previews are already migrated.');
return 0;
}
$output->writeln("A total of $total preview files need to be migrated.");
- $output->writeln("");
- $output->writeln("The migration will always migrate all previews of a single file in a batch. After each batch the process can be canceled by pressing CTRL-C. This will finish the current batch and then stop the migration. This migration can then just be started and it will continue.");
+ $output->writeln('');
+ $output->writeln('The migration will always migrate all previews of a single file in a batch. After each batch the process can be canceled by pressing CTRL-C. This will finish the current batch and then stop the migration. This migration can then just be started and it will continue.');
if ($input->getOption('batch')) {
$output->writeln('Batch mode active: migration is started right away.');
} else {
+ /** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('<info>Should the migration be started? (y/[n]) </info>', false);
@@ -165,12 +146,12 @@ class Repair extends Command {
// register the SIGINT listener late in here to be able to exit in the early process of this command
pcntl_signal(SIGINT, [$this, 'sigIntHandler']);
- $output->writeln("");
- $output->writeln("");
+ $output->writeln('');
+ $output->writeln('');
$section1 = $output->section();
$section2 = $output->section();
$progressBar = new ProgressBar($section2, $total);
- $progressBar->setFormat("%current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% Used Memory: %memory:6s%");
+ $progressBar->setFormat('%current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% Used Memory: %memory:6s%');
$time = (new \DateTime())->format('H:i:s');
$progressBar->setMessage("$time Starting …");
$progressBar->maxSecondsBetweenRedraws(0.2);
@@ -212,10 +193,10 @@ class Repair extends Command {
$memoryUsage = memory_get_usage();
if ($memoryCheckEnabled && $memoryUsage > $this->memoryTreshold) {
- $section1->writeln("");
- $section1->writeln("");
- $section1->writeln("");
- $section1->writeln(" Stopped process 25 MB before reaching the memory limit to avoid a hard crash.");
+ $section1->writeln('');
+ $section1->writeln('');
+ $section1->writeln('');
+ $section1->writeln(' Stopped process 25 MB before reaching the memory limit to avoid a hard crash.');
$time = (new \DateTime())->format('H:i:s');
$section1->writeln("$time Reached memory limit and stopped to avoid hard crash.");
return 1;
@@ -226,7 +207,7 @@ class Repair extends Command {
$section1->writeln(" Locking \"$lockName\" …", OutputInterface::VERBOSITY_VERBOSE);
$this->lockingProvider->acquireLock($lockName, ILockingProvider::LOCK_EXCLUSIVE);
} catch (LockedException $e) {
- $section1->writeln(" Skipping because it is locked - another process seems to work on this …");
+ $section1->writeln(' Skipping because it is locked - another process seems to work on this …');
continue;
}
@@ -294,14 +275,14 @@ class Repair extends Command {
}
$this->lockingProvider->releaseLock($lockName, ILockingProvider::LOCK_EXCLUSIVE);
- $section1->writeln(" Unlocked", OutputInterface::VERBOSITY_VERBOSE);
+ $section1->writeln(' Unlocked', OutputInterface::VERBOSITY_VERBOSE);
$section1->writeln(" Finished migrating previews of file with fileId $name …");
$progressBar->advance();
}
$progressBar->finish();
- $output->writeln("");
+ $output->writeln('');
return 0;
}
diff --git a/core/Command/Preview/ResetRenderedTexts.php b/core/Command/Preview/ResetRenderedTexts.php
index df623651f83..4cae315e48b 100644
--- a/core/Command/Preview/ResetRenderedTexts.php
+++ b/core/Command/Preview/ResetRenderedTexts.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2021, Daniel Calviño Sánchez <danxuliu@gmail.com>
- *
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\Preview;
@@ -39,24 +22,14 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ResetRenderedTexts extends Command {
- protected IDBConnection $connection;
- protected IUserManager $userManager;
- protected IAvatarManager $avatarManager;
- private Root $previewFolder;
- private IMimeTypeLoader $mimeTypeLoader;
-
- public function __construct(IDBConnection $connection,
- IUserManager $userManager,
- IAvatarManager $avatarManager,
- Root $previewFolder,
- IMimeTypeLoader $mimeTypeLoader) {
+ public function __construct(
+ protected IDBConnection $connection,
+ protected IUserManager $userManager,
+ protected IAvatarManager $avatarManager,
+ private Root $previewFolder,
+ private IMimeTypeLoader $mimeTypeLoader,
+ ) {
parent::__construct();
-
- $this->connection = $connection;
- $this->userManager = $userManager;
- $this->avatarManager = $avatarManager;
- $this->previewFolder = $previewFolder;
- $this->mimeTypeLoader = $mimeTypeLoader;
}
protected function configure() {
@@ -147,7 +120,7 @@ class ResetRenderedTexts extends Command {
$qb->select('path', 'mimetype')
->from('filecache')
->where($qb->expr()->eq('fileid', $qb->createNamedParameter($this->previewFolder->getId())));
- $cursor = $qb->execute();
+ $cursor = $qb->executeQuery();
$data = $cursor->fetch();
$cursor->closeCursor();
@@ -180,7 +153,7 @@ class ResetRenderedTexts extends Command {
)
);
- $cursor = $qb->execute();
+ $cursor = $qb->executeQuery();
while ($row = $cursor->fetch()) {
yield $row;
diff --git a/core/Command/Router/ListRoutes.php b/core/Command/Router/ListRoutes.php
new file mode 100644
index 00000000000..8932b549a65
--- /dev/null
+++ b/core/Command/Router/ListRoutes.php
@@ -0,0 +1,129 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Router;
+
+use OC\Core\Command\Base;
+use OC\Route\Router;
+use OCP\App\AppPathNotFoundException;
+use OCP\App\IAppManager;
+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 ListRoutes extends Base {
+
+ public function __construct(
+ protected IAppManager $appManager,
+ protected Router $router,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('router:list')
+ ->setDescription('Find the target of a route or all routes of an app')
+ ->addArgument(
+ 'app',
+ InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
+ 'Only list routes of these apps',
+ )
+ ->addOption(
+ 'ocs',
+ null,
+ InputOption::VALUE_NONE,
+ 'Only list OCS routes',
+ )
+ ->addOption(
+ 'index',
+ null,
+ InputOption::VALUE_NONE,
+ 'Only list index.php routes',
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $apps = $input->getArgument('app');
+ if (empty($apps)) {
+ $this->router->loadRoutes();
+ } else {
+ foreach ($apps as $app) {
+ if ($app === 'core') {
+ $this->router->loadRoutes($app, false);
+ continue;
+ }
+
+ try {
+ $this->appManager->getAppPath($app);
+ } catch (AppPathNotFoundException $e) {
+ $output->writeln('<comment>App ' . $app . ' not found</comment>');
+ return self::FAILURE;
+ }
+
+ if (!$this->appManager->isEnabledForAnyone($app)) {
+ $output->writeln('<comment>App ' . $app . ' is not enabled</comment>');
+ return self::FAILURE;
+ }
+
+ $this->router->loadRoutes($app, true);
+ }
+ }
+
+ $ocsOnly = $input->getOption('ocs');
+ $indexOnly = $input->getOption('index');
+
+ $rows = [];
+ $collection = $this->router->getRouteCollection();
+ foreach ($collection->all() as $routeName => $route) {
+ if (str_starts_with($routeName, 'ocs.')) {
+ if ($indexOnly) {
+ continue;
+ }
+ $routeName = substr($routeName, 4);
+ } elseif ($ocsOnly) {
+ continue;
+ }
+
+ $path = $route->getPath();
+ if (str_starts_with($path, '/ocsapp/')) {
+ $path = '/ocs/v2.php/' . substr($path, strlen('/ocsapp/'));
+ }
+ $row = [
+ 'route' => $routeName,
+ 'request' => implode(', ', $route->getMethods()),
+ 'path' => $path,
+ ];
+
+ if ($output->isVerbose()) {
+ $row['requirements'] = json_encode($route->getRequirements());
+ }
+
+ $rows[] = $row;
+ }
+
+ usort($rows, static function (array $a, array $b): int {
+ $aRoute = $a['route'];
+ if (str_starts_with($aRoute, 'ocs.')) {
+ $aRoute = substr($aRoute, 4);
+ }
+ $bRoute = $b['route'];
+ if (str_starts_with($bRoute, 'ocs.')) {
+ $bRoute = substr($bRoute, 4);
+ }
+ return $aRoute <=> $bRoute;
+ });
+
+ $this->writeTableInOutputFormat($input, $output, $rows);
+ return self::SUCCESS;
+ }
+}
diff --git a/core/Command/Router/MatchRoute.php b/core/Command/Router/MatchRoute.php
new file mode 100644
index 00000000000..3b90463c7b2
--- /dev/null
+++ b/core/Command/Router/MatchRoute.php
@@ -0,0 +1,100 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Router;
+
+use OC\Core\Command\Base;
+use OC\Route\Router;
+use OCP\App\IAppManager;
+use OCP\Server;
+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\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\RequestContext;
+
+class MatchRoute extends Base {
+
+ public function __construct(
+ private Router $router,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('router:match')
+ ->setDescription('Match a URL to the target route')
+ ->addArgument(
+ 'path',
+ InputArgument::REQUIRED,
+ 'Path of the request',
+ )
+ ->addOption(
+ 'method',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'HTTP method',
+ 'GET',
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $context = new RequestContext(method: strtoupper($input->getOption('method')));
+ $this->router->setContext($context);
+
+ $path = $input->getArgument('path');
+ if (str_starts_with($path, '/index.php/')) {
+ $path = substr($path, 10);
+ }
+ if (str_starts_with($path, '/ocs/v1.php/') || str_starts_with($path, '/ocs/v2.php/')) {
+ $path = '/ocsapp' . substr($path, strlen('/ocs/v2.php'));
+ }
+
+ try {
+ $route = $this->router->findMatchingRoute($path);
+ } catch (MethodNotAllowedException) {
+ $output->writeln('<error>Method not allowed on this path</error>');
+ return self::FAILURE;
+ } catch (ResourceNotFoundException) {
+ $output->writeln('<error>Path not matched</error>');
+ if (preg_match('/\/apps\/([^\/]+)\//', $path, $matches)) {
+ $appManager = Server::get(IAppManager::class);
+ if (!$appManager->isEnabledForAnyone($matches[1])) {
+ $output->writeln('');
+ $output->writeln('<comment>App ' . $matches[1] . ' is not enabled</comment>');
+ }
+ }
+ return self::FAILURE;
+ }
+
+ $row = [
+ 'route' => $route['_route'],
+ 'appid' => $route['caller'][0] ?? null,
+ 'controller' => $route['caller'][1] ?? null,
+ 'method' => $route['caller'][2] ?? null,
+ ];
+
+ if ($output->isVerbose()) {
+ $route = $this->router->getRouteCollection()->get($row['route']);
+ $row['path'] = $route->getPath();
+ if (str_starts_with($row['path'], '/ocsapp/')) {
+ $row['path'] = '/ocs/v2.php/' . substr($row['path'], strlen('/ocsapp/'));
+ }
+ $row['requirements'] = json_encode($route->getRequirements());
+ }
+
+ $this->writeTableInOutputFormat($input, $output, [$row]);
+ return self::SUCCESS;
+ }
+}
diff --git a/core/Command/Security/BruteforceAttempts.php b/core/Command/Security/BruteforceAttempts.php
new file mode 100644
index 00000000000..d5fa0a284fd
--- /dev/null
+++ b/core/Command/Security/BruteforceAttempts.php
@@ -0,0 +1,65 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\Security;
+
+use OC\Core\Command\Base;
+use OCP\Security\Bruteforce\IThrottler;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class BruteforceAttempts extends Base {
+ public function __construct(
+ protected IThrottler $throttler,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('security:bruteforce:attempts')
+ ->setDescription('Show bruteforce attempts status for a given IP address')
+ ->addArgument(
+ 'ipaddress',
+ InputArgument::REQUIRED,
+ 'IP address for which the attempts status is to be shown',
+ )
+ ->addArgument(
+ 'action',
+ InputArgument::OPTIONAL,
+ 'Only count attempts for the given action',
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $ip = $input->getArgument('ipaddress');
+
+ if (!filter_var($ip, FILTER_VALIDATE_IP)) {
+ $output->writeln('<error>"' . $ip . '" is not a valid IP address</error>');
+ return 1;
+ }
+
+ $data = [
+ 'bypass-listed' => $this->throttler->isBypassListed($ip),
+ 'attempts' => $this->throttler->getAttempts(
+ $ip,
+ (string)$input->getArgument('action'),
+ ),
+ 'delay' => $this->throttler->getDelay(
+ $ip,
+ (string)$input->getArgument('action'),
+ ),
+ ];
+
+ $this->writeArrayInOutputFormat($input, $output, $data);
+
+ return 0;
+ }
+}
diff --git a/core/Command/Security/BruteforceResetAttempts.php b/core/Command/Security/BruteforceResetAttempts.php
new file mode 100644
index 00000000000..6987c0ef682
--- /dev/null
+++ b/core/Command/Security/BruteforceResetAttempts.php
@@ -0,0 +1,45 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\Security;
+
+use OC\Core\Command\Base;
+use OCP\Security\Bruteforce\IThrottler;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class BruteforceResetAttempts extends Base {
+ public function __construct(
+ protected IThrottler $throttler,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('security:bruteforce:reset')
+ ->setDescription('resets bruteforce attempts for given IP address')
+ ->addArgument(
+ 'ipaddress',
+ InputArgument::REQUIRED,
+ 'IP address for which the attempts are to be reset'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $ip = $input->getArgument('ipaddress');
+
+ if (!filter_var($ip, FILTER_VALIDATE_IP)) {
+ $output->writeln('<error>"' . $ip . '" is not a valid IP address</error>');
+ return 1;
+ }
+
+ $this->throttler->resetDelayForIP($ip);
+ return 0;
+ }
+}
diff --git a/core/Command/Security/ExportCertificates.php b/core/Command/Security/ExportCertificates.php
new file mode 100644
index 00000000000..dcf34d4bce4
--- /dev/null
+++ b/core/Command/Security/ExportCertificates.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+declare(strict_types=1);
+
+namespace OC\Core\Command\Security;
+
+use OC\Core\Command\Base;
+use OCP\ICertificateManager;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ExportCertificates extends Base {
+ public function __construct(
+ protected ICertificateManager $certificateManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('security:certificates:export')
+ ->setDescription('export the certificate bundle');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $bundlePath = $this->certificateManager->getAbsoluteBundlePath();
+ $bundle = file_get_contents($bundlePath);
+ $output->writeln($bundle);
+ return 0;
+ }
+}
diff --git a/core/Command/Security/ImportCertificate.php b/core/Command/Security/ImportCertificate.php
index 9db7889e307..b23612baeb1 100644
--- a/core/Command/Security/ImportCertificate.php
+++ b/core/Command/Security/ImportCertificate.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Security;
@@ -30,10 +14,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ImportCertificate extends Base {
- protected ICertificateManager $certificateManager;
-
- public function __construct(ICertificateManager $certificateManager) {
- $this->certificateManager = $certificateManager;
+ public function __construct(
+ protected ICertificateManager $certificateManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/Security/ListCertificates.php b/core/Command/Security/ListCertificates.php
index 15dd1812077..cf1874a09d3 100644
--- a/core/Command/Security/ListCertificates.php
+++ b/core/Command/Security/ListCertificates.php
@@ -1,24 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Security;
@@ -26,18 +11,20 @@ use OC\Core\Command\Base;
use OCP\ICertificate;
use OCP\ICertificateManager;
use OCP\IL10N;
+use OCP\L10N\IFactory as IL10NFactory;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ListCertificates extends Base {
- protected ICertificateManager $certificateManager;
protected IL10N $l;
- public function __construct(ICertificateManager $certificateManager, IL10N $l) {
- $this->certificateManager = $certificateManager;
- $this->l = $l;
+ public function __construct(
+ protected ICertificateManager $certificateManager,
+ IL10NFactory $l10nFactory,
+ ) {
parent::__construct();
+ $this->l = $l10nFactory->get('core');
}
protected function configure() {
diff --git a/core/Command/Security/RemoveCertificate.php b/core/Command/Security/RemoveCertificate.php
index 2f9c6ff978a..48062724d52 100644
--- a/core/Command/Security/RemoveCertificate.php
+++ b/core/Command/Security/RemoveCertificate.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Carla Schroder <carla@owncloud.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\Security;
@@ -30,10 +14,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class RemoveCertificate extends Base {
- protected ICertificateManager $certificateManager;
-
- public function __construct(ICertificateManager $certificateManager) {
- $this->certificateManager = $certificateManager;
+ public function __construct(
+ protected ICertificateManager $certificateManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/Security/ResetBruteforceAttempts.php b/core/Command/Security/ResetBruteforceAttempts.php
deleted file mode 100644
index 8def0873bdf..00000000000
--- a/core/Command/Security/ResetBruteforceAttempts.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-/**
- * @copyright Copyright (c) 2020, Johannes Riedel (johannes@johannes-riedel.de)
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Johannes Riedel <joeried@users.noreply.github.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-namespace OC\Core\Command\Security;
-
-use OC\Core\Command\Base;
-use OC\Security\Bruteforce\Throttler;
-use Symfony\Component\Console\Input\InputArgument;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
-
-class ResetBruteforceAttempts extends Base {
- protected Throttler $throttler;
-
- public function __construct(Throttler $throttler) {
- $this->throttler = $throttler;
- parent::__construct();
- }
-
- protected function configure() {
- $this
- ->setName('security:bruteforce:reset')
- ->setDescription('resets bruteforce attemps for given IP address')
- ->addArgument(
- 'ipaddress',
- InputArgument::REQUIRED,
- 'IP address for which the attempts are to be reset'
- );
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int {
- $ip = $input->getArgument('ipaddress');
-
- if (!filter_var($ip, FILTER_VALIDATE_IP)) {
- $output->writeln('<error>"' . $ip . '" is not a valid IP address</error>');
- return 1;
- }
-
- $this->throttler->resetDelayForIP($ip);
- return 0;
- }
-}
diff --git a/core/Command/SetupChecks.php b/core/Command/SetupChecks.php
new file mode 100644
index 00000000000..6ef67726839
--- /dev/null
+++ b/core/Command/SetupChecks.php
@@ -0,0 +1,84 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace OC\Core\Command;
+
+use OCP\RichObjectStrings\IRichTextFormatter;
+use OCP\SetupCheck\ISetupCheckManager;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class SetupChecks extends Base {
+ public function __construct(
+ private ISetupCheckManager $setupCheckManager,
+ private IRichTextFormatter $richTextFormatter,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+
+ $this
+ ->setName('setupchecks')
+ ->setDescription('Run setup checks and output the results')
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $results = $this->setupCheckManager->runAll();
+ switch ($input->getOption('output')) {
+ case self::OUTPUT_FORMAT_JSON:
+ case self::OUTPUT_FORMAT_JSON_PRETTY:
+ $this->writeArrayInOutputFormat($input, $output, $results);
+ break;
+ default:
+ foreach ($results as $category => $checks) {
+ $output->writeln("\t{$category}:");
+ foreach ($checks as $check) {
+ $styleTag = match ($check->getSeverity()) {
+ 'success' => 'info',
+ 'error' => 'error',
+ 'warning' => 'comment',
+ default => null,
+ };
+ $emoji = match ($check->getSeverity()) {
+ 'success' => '✓',
+ 'error' => '✗',
+ 'warning' => '⚠',
+ default => 'ℹ',
+ };
+ $verbosity = ($check->getSeverity() === 'error' ? OutputInterface::VERBOSITY_QUIET : OutputInterface::VERBOSITY_NORMAL);
+ $description = $check->getDescription();
+ $descriptionParameters = $check->getDescriptionParameters();
+ if ($description !== null && $descriptionParameters !== null) {
+ $description = $this->richTextFormatter->richToParsed($description, $descriptionParameters);
+ }
+ $output->writeln(
+ "\t\t"
+ . ($styleTag !== null ? "<{$styleTag}>" : '')
+ . "{$emoji} "
+ . ($check->getName() ?? $check::class)
+ . ($description !== null ? ': ' . $description : '')
+ . ($styleTag !== null ? "</{$styleTag}>" : ''),
+ $verbosity
+ );
+ }
+ }
+ }
+ foreach ($results as $category => $checks) {
+ foreach ($checks as $check) {
+ if ($check->getSeverity() !== 'success') {
+ return self::FAILURE;
+ }
+ }
+ }
+ return self::SUCCESS;
+ }
+}
diff --git a/core/Command/Status.php b/core/Command/Status.php
index c59dac557a8..a00d4a94658 100644
--- a/core/Command/Status.php
+++ b/core/Command/Status.php
@@ -1,46 +1,27 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command;
-use OC_Util;
use OCP\Defaults;
use OCP\IConfig;
+use OCP\ServerVersion;
use OCP\Util;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Status extends Base {
- private IConfig $config;
- private Defaults $themingDefaults;
-
- public function __construct(IConfig $config, Defaults $themingDefaults) {
+ public function __construct(
+ private IConfig $config,
+ private Defaults $themingDefaults,
+ private ServerVersion $serverVersion,
+ ) {
parent::__construct('status');
-
- $this->config = $config;
- $this->themingDefaults = $themingDefaults;
}
protected function configure() {
@@ -61,8 +42,8 @@ class Status extends Base {
$needUpgrade = Util::needUpgrade();
$values = [
'installed' => $this->config->getSystemValueBool('installed', false),
- 'version' => implode('.', Util::getVersion()),
- 'versionstring' => OC_Util::getVersionString(),
+ 'version' => implode('.', $this->serverVersion->getVersion()),
+ 'versionstring' => $this->serverVersion->getVersionString(),
'edition' => '',
'maintenance' => $maintenanceMode,
'needsDbUpgrade' => $needUpgrade,
diff --git a/core/Command/SystemTag/Add.php b/core/Command/SystemTag/Add.php
index f4fb80eb70a..df8b507b07d 100644
--- a/core/Command/SystemTag/Add.php
+++ b/core/Command/SystemTag/Add.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2021, hosting.de, Johannes Leuker <developers@hosting.de>
- *
- * @author Johannes Leuker <j.leuker@hosting.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\SystemTag;
@@ -31,10 +15,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Add extends Base {
- protected ISystemTagManager $systemTagManager;
-
- public function __construct(ISystemTagManager $systemTagManager) {
- $this->systemTagManager = $systemTagManager;
+ public function __construct(
+ protected ISystemTagManager $systemTagManager,
+ ) {
parent::__construct();
}
@@ -91,7 +74,7 @@ class Add extends Base {
]);
return 0;
} catch (TagAlreadyExistsException $e) {
- $output->writeln('<error>'.$e->getMessage().'</error>');
+ $output->writeln('<error>' . $e->getMessage() . '</error>');
return 2;
}
}
diff --git a/core/Command/SystemTag/Delete.php b/core/Command/SystemTag/Delete.php
index 4c1145ae1b4..f657f4473ab 100644
--- a/core/Command/SystemTag/Delete.php
+++ b/core/Command/SystemTag/Delete.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2021, hosting.de, Johannes Leuker <developers@hosting.de>
- *
- * @author Johannes Leuker <j.leuker@hosting.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\SystemTag;
@@ -30,10 +14,9 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Delete extends Base {
- protected ISystemTagManager $systemTagManager;
-
- public function __construct(ISystemTagManager $systemTagManager) {
- $this->systemTagManager = $systemTagManager;
+ public function __construct(
+ protected ISystemTagManager $systemTagManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/SystemTag/Edit.php b/core/Command/SystemTag/Edit.php
index 7ed933c3b35..09c662e58e9 100644
--- a/core/Command/SystemTag/Edit.php
+++ b/core/Command/SystemTag/Edit.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2021, hosting.de, Johannes Leuker <developers@hosting.de>
- *
- * @author Johannes Leuker <j.leuker@hosting.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\SystemTag;
@@ -31,10 +15,9 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Edit extends Base {
- protected ISystemTagManager $systemTagManager;
-
- public function __construct(ISystemTagManager $systemTagManager) {
- $this->systemTagManager = $systemTagManager;
+ public function __construct(
+ protected ISystemTagManager $systemTagManager,
+ ) {
parent::__construct();
}
@@ -58,6 +41,12 @@ class Edit extends Base {
null,
InputOption::VALUE_OPTIONAL,
'sets the access control level (public, restricted, invisible)',
+ )
+ ->addOption(
+ 'color',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'set the tag color',
);
}
@@ -98,15 +87,30 @@ class Edit extends Base {
}
}
+ $color = $tag->getColor();
+ if ($input->hasOption('color')) {
+ $color = $input->getOption('color');
+ if (substr($color, 0, 1) === '#') {
+ $color = substr($color, 1);
+ }
+
+ if ($input->getOption('color') === '') {
+ $color = null;
+ } elseif (strlen($color) !== 6 || !ctype_xdigit($color)) {
+ $output->writeln('<error>Color must be a 6-digit hexadecimal value</error>');
+ return 2;
+ }
+ }
+
try {
- $this->systemTagManager->updateTag($input->getArgument('id'), $name, $userVisible, $userAssignable);
- $output->writeln('<info>Tag updated ("' . $name . '", '. $userVisible . ', ' . $userAssignable . ')</info>');
+ $this->systemTagManager->updateTag($input->getArgument('id'), $name, $userVisible, $userAssignable, $color);
+ $output->writeln('<info>Tag updated ("' . $name . '", ' . json_encode($userVisible) . ', ' . json_encode($userAssignable) . ', "' . ($color ? "#$color" : '') . '")</info>');
return 0;
} catch (TagNotFoundException $e) {
$output->writeln('<error>Tag not found</error>');
return 1;
} catch (TagAlreadyExistsException $e) {
- $output->writeln('<error>'.$e->getMessage().'</error>');
+ $output->writeln('<error>' . $e->getMessage() . '</error>');
return 2;
}
}
diff --git a/core/Command/SystemTag/ListCommand.php b/core/Command/SystemTag/ListCommand.php
index 7993eb87891..2c6435d6faf 100644
--- a/core/Command/SystemTag/ListCommand.php
+++ b/core/Command/SystemTag/ListCommand.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2021, hosting.de, Johannes Leuker <developers@hosting.de>
- *
- * @author Johannes Leuker <j.leuker@hosting.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\SystemTag;
@@ -30,10 +14,9 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ListCommand extends Base {
- protected ISystemTagManager $systemTagManager;
-
- public function __construct(ISystemTagManager $systemTagManager) {
- $this->systemTagManager = $systemTagManager;
+ public function __construct(
+ protected ISystemTagManager $systemTagManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/TaskProcessing/EnabledCommand.php b/core/Command/TaskProcessing/EnabledCommand.php
new file mode 100644
index 00000000000..0d4b831812c
--- /dev/null
+++ b/core/Command/TaskProcessing/EnabledCommand.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\TaskProcessing;
+
+use OC\Core\Command\Base;
+use OCP\IAppConfig;
+use OCP\TaskProcessing\IManager;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class EnabledCommand extends Base {
+ public function __construct(
+ protected IManager $taskProcessingManager,
+ private IAppConfig $appConfig,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure() {
+ $this
+ ->setName('taskprocessing:task-type:set-enabled')
+ ->setDescription('Enable or disable a task type')
+ ->addArgument(
+ 'task-type-id',
+ InputArgument::REQUIRED,
+ 'ID of the task type to configure'
+ )
+ ->addArgument(
+ 'enabled',
+ InputArgument::REQUIRED,
+ 'status of the task type availability. Set 1 to enable and 0 to disable.'
+ );
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $enabled = (bool)$input->getArgument('enabled');
+ $taskType = $input->getArgument('task-type-id');
+ $json = $this->appConfig->getValueString('core', 'ai.taskprocessing_type_preferences', lazy: true);
+ try {
+ if ($json === '') {
+ $taskTypeSettings = [];
+ } else {
+ $taskTypeSettings = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
+ }
+
+ $taskTypeSettings[$taskType] = $enabled;
+
+ $this->appConfig->setValueString('core', 'ai.taskprocessing_type_preferences', json_encode($taskTypeSettings), lazy: true);
+ $this->writeArrayInOutputFormat($input, $output, $taskTypeSettings);
+ return 0;
+ } catch (\JsonException $e) {
+ throw new \JsonException('Error in TaskType DB entry');
+ }
+
+ }
+}
diff --git a/core/Command/TaskProcessing/GetCommand.php b/core/Command/TaskProcessing/GetCommand.php
new file mode 100644
index 00000000000..5c4fd17f2f8
--- /dev/null
+++ b/core/Command/TaskProcessing/GetCommand.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\TaskProcessing;
+
+use OC\Core\Command\Base;
+use OCP\TaskProcessing\IManager;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class GetCommand extends Base {
+ public function __construct(
+ protected IManager $taskProcessingManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure() {
+ $this
+ ->setName('taskprocessing:task:get')
+ ->setDescription('Display all information for a specific task')
+ ->addArgument(
+ 'task-id',
+ InputArgument::REQUIRED,
+ 'ID of the task to display'
+ );
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $taskId = (int)$input->getArgument('task-id');
+ $task = $this->taskProcessingManager->getTask($taskId);
+ $jsonTask = $task->jsonSerialize();
+ $jsonTask['error_message'] = $task->getErrorMessage();
+ $this->writeArrayInOutputFormat($input, $output, $jsonTask);
+ return 0;
+ }
+}
diff --git a/core/Command/TaskProcessing/ListCommand.php b/core/Command/TaskProcessing/ListCommand.php
new file mode 100644
index 00000000000..81eb258d35d
--- /dev/null
+++ b/core/Command/TaskProcessing/ListCommand.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\TaskProcessing;
+
+use OC\Core\Command\Base;
+use OCP\TaskProcessing\IManager;
+use OCP\TaskProcessing\Task;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ListCommand extends Base {
+ public function __construct(
+ protected IManager $taskProcessingManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure() {
+ $this
+ ->setName('taskprocessing:task:list')
+ ->setDescription('list tasks')
+ ->addOption(
+ 'userIdFilter',
+ 'u',
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks for one user ID'
+ )
+ ->addOption(
+ 'type',
+ 't',
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks for one task type'
+ )
+ ->addOption(
+ 'appId',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks for one app ID'
+ )
+ ->addOption(
+ 'customId',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks for one custom ID'
+ )
+ ->addOption(
+ 'status',
+ 's',
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks that have a specific status (STATUS_UNKNOWN=0, STATUS_SCHEDULED=1, STATUS_RUNNING=2, STATUS_SUCCESSFUL=3, STATUS_FAILED=4, STATUS_CANCELLED=5)'
+ )
+ ->addOption(
+ 'scheduledAfter',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks that were scheduled after a specific date (Unix timestamp)'
+ )
+ ->addOption(
+ 'endedBefore',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks that ended before a specific date (Unix timestamp)'
+ );
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $userIdFilter = $input->getOption('userIdFilter');
+ if ($userIdFilter === null) {
+ $userIdFilter = '';
+ } elseif ($userIdFilter === '') {
+ $userIdFilter = null;
+ }
+ $type = $input->getOption('type');
+ $appId = $input->getOption('appId');
+ $customId = $input->getOption('customId');
+ $status = $input->getOption('status');
+ $scheduledAfter = $input->getOption('scheduledAfter');
+ $endedBefore = $input->getOption('endedBefore');
+
+ $tasks = $this->taskProcessingManager->getTasks($userIdFilter, $type, $appId, $customId, $status, $scheduledAfter, $endedBefore);
+ $arrayTasks = array_map(static function (Task $task) {
+ $jsonTask = $task->jsonSerialize();
+ $jsonTask['error_message'] = $task->getErrorMessage();
+ return $jsonTask;
+ }, $tasks);
+
+ $this->writeArrayInOutputFormat($input, $output, $arrayTasks);
+ return 0;
+ }
+}
diff --git a/core/Command/TaskProcessing/Statistics.php b/core/Command/TaskProcessing/Statistics.php
new file mode 100644
index 00000000000..86478b34db1
--- /dev/null
+++ b/core/Command/TaskProcessing/Statistics.php
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\TaskProcessing;
+
+use OC\Core\Command\Base;
+use OCP\TaskProcessing\IManager;
+use OCP\TaskProcessing\Task;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Statistics extends Base {
+ public function __construct(
+ protected IManager $taskProcessingManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure() {
+ $this
+ ->setName('taskprocessing:task:stats')
+ ->setDescription('get statistics for tasks')
+ ->addOption(
+ 'userIdFilter',
+ 'u',
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks for one user ID'
+ )
+ ->addOption(
+ 'type',
+ 't',
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks for one task type'
+ )
+ ->addOption(
+ 'appId',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks for one app ID'
+ )
+ ->addOption(
+ 'customId',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks for one custom ID'
+ )
+ ->addOption(
+ 'status',
+ 's',
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks that have a specific status (STATUS_UNKNOWN=0, STATUS_SCHEDULED=1, STATUS_RUNNING=2, STATUS_SUCCESSFUL=3, STATUS_FAILED=4, STATUS_CANCELLED=5)'
+ )
+ ->addOption(
+ 'scheduledAfter',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks that were scheduled after a specific date (Unix timestamp)'
+ )
+ ->addOption(
+ 'endedBefore',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'only get the tasks that ended before a specific date (Unix timestamp)'
+ );
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $userIdFilter = $input->getOption('userIdFilter');
+ if ($userIdFilter === null) {
+ $userIdFilter = '';
+ } elseif ($userIdFilter === '') {
+ $userIdFilter = null;
+ }
+ $type = $input->getOption('type');
+ $appId = $input->getOption('appId');
+ $customId = $input->getOption('customId');
+ $status = $input->getOption('status');
+ $scheduledAfter = $input->getOption('scheduledAfter');
+ $endedBefore = $input->getOption('endedBefore');
+
+ $tasks = $this->taskProcessingManager->getTasks($userIdFilter, $type, $appId, $customId, $status, $scheduledAfter, $endedBefore);
+
+ $stats = ['Number of tasks' => count($tasks)];
+
+ $maxRunningTime = 0;
+ $totalRunningTime = 0;
+ $runningTimeCount = 0;
+
+ $maxQueuingTime = 0;
+ $totalQueuingTime = 0;
+ $queuingTimeCount = 0;
+
+ $maxUserWaitingTime = 0;
+ $totalUserWaitingTime = 0;
+ $userWaitingTimeCount = 0;
+
+ $maxInputSize = 0;
+ $maxOutputSize = 0;
+ $inputCount = 0;
+ $inputSum = 0;
+ $outputCount = 0;
+ $outputSum = 0;
+
+ foreach ($tasks as $task) {
+ // running time
+ if ($task->getStartedAt() !== null && $task->getEndedAt() !== null) {
+ $taskRunningTime = $task->getEndedAt() - $task->getStartedAt();
+ $totalRunningTime += $taskRunningTime;
+ $runningTimeCount++;
+ if ($taskRunningTime >= $maxRunningTime) {
+ $maxRunningTime = $taskRunningTime;
+ }
+ }
+ // queuing time
+ if ($task->getScheduledAt() !== null && $task->getStartedAt() !== null) {
+ $taskQueuingTime = $task->getStartedAt() - $task->getScheduledAt();
+ $totalQueuingTime += $taskQueuingTime;
+ $queuingTimeCount++;
+ if ($taskQueuingTime >= $maxQueuingTime) {
+ $maxQueuingTime = $taskQueuingTime;
+ }
+ }
+ // user waiting time
+ if ($task->getScheduledAt() !== null && $task->getEndedAt() !== null) {
+ $taskUserWaitingTime = $task->getEndedAt() - $task->getScheduledAt();
+ $totalUserWaitingTime += $taskUserWaitingTime;
+ $userWaitingTimeCount++;
+ if ($taskUserWaitingTime >= $maxUserWaitingTime) {
+ $maxUserWaitingTime = $taskUserWaitingTime;
+ }
+ }
+ // input/output sizes
+ if ($task->getStatus() === Task::STATUS_SUCCESSFUL) {
+ $outputString = json_encode($task->getOutput());
+ if ($outputString !== false) {
+ $outputCount++;
+ $outputLength = strlen($outputString);
+ $outputSum += $outputLength;
+ if ($outputLength > $maxOutputSize) {
+ $maxOutputSize = $outputLength;
+ }
+ }
+ }
+ $inputString = json_encode($task->getInput());
+ if ($inputString !== false) {
+ $inputCount++;
+ $inputLength = strlen($inputString);
+ $inputSum += $inputLength;
+ if ($inputLength > $maxInputSize) {
+ $maxInputSize = $inputLength;
+ }
+ }
+ }
+
+ if ($runningTimeCount > 0) {
+ $stats['Max running time'] = $maxRunningTime;
+ $averageRunningTime = $totalRunningTime / $runningTimeCount;
+ $stats['Average running time'] = (int)$averageRunningTime;
+ $stats['Running time count'] = $runningTimeCount;
+ }
+ if ($queuingTimeCount > 0) {
+ $stats['Max queuing time'] = $maxQueuingTime;
+ $averageQueuingTime = $totalQueuingTime / $queuingTimeCount;
+ $stats['Average queuing time'] = (int)$averageQueuingTime;
+ $stats['Queuing time count'] = $queuingTimeCount;
+ }
+ if ($userWaitingTimeCount > 0) {
+ $stats['Max user waiting time'] = $maxUserWaitingTime;
+ $averageUserWaitingTime = $totalUserWaitingTime / $userWaitingTimeCount;
+ $stats['Average user waiting time'] = (int)$averageUserWaitingTime;
+ $stats['User waiting time count'] = $userWaitingTimeCount;
+ }
+ if ($outputCount > 0) {
+ $stats['Max output size (bytes)'] = $maxOutputSize;
+ $averageOutputSize = $outputSum / $outputCount;
+ $stats['Average output size (bytes)'] = (int)$averageOutputSize;
+ $stats['Number of tasks with output'] = $outputCount;
+ }
+ if ($inputCount > 0) {
+ $stats['Max input size (bytes)'] = $maxInputSize;
+ $averageInputSize = $inputSum / $inputCount;
+ $stats['Average input size (bytes)'] = (int)$averageInputSize;
+ $stats['Number of tasks with input'] = $inputCount;
+ }
+
+ $this->writeArrayInOutputFormat($input, $output, $stats);
+ return 0;
+ }
+}
diff --git a/core/Command/TwoFactorAuth/Base.php b/core/Command/TwoFactorAuth/Base.php
index 27bd381d951..034ea36afca 100644
--- a/core/Command/TwoFactorAuth/Base.php
+++ b/core/Command/TwoFactorAuth/Base.php
@@ -1,27 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\TwoFactorAuth;
@@ -30,7 +11,12 @@ use OCP\IUserManager;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
class Base extends \OC\Core\Command\Base {
- protected IUserManager $userManager;
+ public function __construct(
+ ?string $name,
+ protected IUserManager $userManager,
+ ) {
+ parent::__construct($name);
+ }
/**
* Return possible values for the named option
diff --git a/core/Command/TwoFactorAuth/Cleanup.php b/core/Command/TwoFactorAuth/Cleanup.php
index 7d3fc3c33f7..f8f116af3fd 100644
--- a/core/Command/TwoFactorAuth/Cleanup.php
+++ b/core/Command/TwoFactorAuth/Cleanup.php
@@ -3,41 +3,26 @@
declare(strict_types=1);
/**
- * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\TwoFactorAuth;
use OCP\Authentication\TwoFactorAuth\IRegistry;
+use OCP\IUserManager;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Cleanup extends Base {
- private IRegistry $registry;
-
- public function __construct(IRegistry $registry) {
- parent::__construct();
-
- $this->registry = $registry;
+ public function __construct(
+ private IRegistry $registry,
+ IUserManager $userManager,
+ ) {
+ parent::__construct(
+ null,
+ $userManager,
+ );
}
protected function configure() {
diff --git a/core/Command/TwoFactorAuth/Disable.php b/core/Command/TwoFactorAuth/Disable.php
index 54e4b138a0a..c60c1245735 100644
--- a/core/Command/TwoFactorAuth/Disable.php
+++ b/core/Command/TwoFactorAuth/Disable.php
@@ -1,24 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\TwoFactorAuth;
@@ -29,12 +14,14 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Disable extends Base {
- private ProviderManager $manager;
-
- public function __construct(ProviderManager $manager, IUserManager $userManager) {
- parent::__construct('twofactorauth:disable');
- $this->manager = $manager;
- $this->userManager = $userManager;
+ public function __construct(
+ private ProviderManager $manager,
+ IUserManager $userManager,
+ ) {
+ parent::__construct(
+ 'twofactorauth:disable',
+ $userManager,
+ );
}
protected function configure() {
@@ -51,14 +38,14 @@ class Disable extends Base {
$providerId = $input->getArgument('provider_id');
$user = $this->userManager->get($uid);
if (is_null($user)) {
- $output->writeln("<error>Invalid UID</error>");
+ $output->writeln('<error>Invalid UID</error>');
return 1;
}
if ($this->manager->tryDisableProviderFor($providerId, $user)) {
$output->writeln("Two-factor provider <options=bold>$providerId</> disabled for user <options=bold>$uid</>.");
return 0;
} else {
- $output->writeln("<error>The provider does not support this operation.</error>");
+ $output->writeln('<error>The provider does not support this operation.</error>');
return 2;
}
}
diff --git a/core/Command/TwoFactorAuth/Enable.php b/core/Command/TwoFactorAuth/Enable.php
index 67c1778399d..215cb31397e 100644
--- a/core/Command/TwoFactorAuth/Enable.php
+++ b/core/Command/TwoFactorAuth/Enable.php
@@ -1,24 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\TwoFactorAuth;
@@ -29,12 +14,14 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Enable extends Base {
- private ProviderManager $manager;
-
- public function __construct(ProviderManager $manager, IUserManager $userManager) {
- parent::__construct('twofactorauth:enable');
- $this->manager = $manager;
- $this->userManager = $userManager;
+ public function __construct(
+ private ProviderManager $manager,
+ IUserManager $userManager,
+ ) {
+ parent::__construct(
+ 'twofactorauth:enable',
+ $userManager,
+ );
}
protected function configure() {
@@ -51,14 +38,14 @@ class Enable extends Base {
$providerId = $input->getArgument('provider_id');
$user = $this->userManager->get($uid);
if (is_null($user)) {
- $output->writeln("<error>Invalid UID</error>");
+ $output->writeln('<error>Invalid UID</error>');
return 1;
}
if ($this->manager->tryEnableProviderFor($providerId, $user)) {
$output->writeln("Two-factor provider <options=bold>$providerId</> enabled for user <options=bold>$uid</>.");
return 0;
} else {
- $output->writeln("<error>The provider does not support this operation.</error>");
+ $output->writeln('<error>The provider does not support this operation.</error>');
return 2;
}
}
diff --git a/core/Command/TwoFactorAuth/Enforce.php b/core/Command/TwoFactorAuth/Enforce.php
index d8fa41e2e95..3315f045bc8 100644
--- a/core/Command/TwoFactorAuth/Enforce.php
+++ b/core/Command/TwoFactorAuth/Enforce.php
@@ -3,44 +3,24 @@
declare(strict_types=1);
/**
- * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\TwoFactorAuth;
-use function implode;
use OC\Authentication\TwoFactorAuth\EnforcementState;
use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
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 function implode;
class Enforce extends Command {
- private MandatoryTwoFactor $mandatoryTwoFactor;
-
- public function __construct(MandatoryTwoFactor $mandatoryTwoFactor) {
+ public function __construct(
+ private MandatoryTwoFactor $mandatoryTwoFactor,
+ ) {
parent::__construct();
-
- $this->mandatoryTwoFactor = $mandatoryTwoFactor;
}
protected function configure() {
diff --git a/core/Command/TwoFactorAuth/State.php b/core/Command/TwoFactorAuth/State.php
index 4694c76b408..ab2e8f2aecf 100644
--- a/core/Command/TwoFactorAuth/State.php
+++ b/core/Command/TwoFactorAuth/State.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\TwoFactorAuth;
@@ -33,13 +15,14 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class State extends Base {
- private IRegistry $registry;
-
- public function __construct(IRegistry $registry, IUserManager $userManager) {
- parent::__construct('twofactorauth:state');
-
- $this->registry = $registry;
- $this->userManager = $userManager;
+ public function __construct(
+ private IRegistry $registry,
+ IUserManager $userManager,
+ ) {
+ parent::__construct(
+ 'twofactorauth:state',
+ $userManager,
+ );
}
protected function configure() {
@@ -54,7 +37,7 @@ class State extends Base {
$uid = $input->getArgument('uid');
$user = $this->userManager->get($uid);
if (is_null($user)) {
- $output->writeln("<error>Invalid UID</error>");
+ $output->writeln('<error>Invalid UID</error>');
return 1;
}
@@ -68,9 +51,9 @@ class State extends Base {
$output->writeln("Two-factor authentication is not enabled for user $uid");
}
- $output->writeln("");
- $this->printProviders("Enabled providers", $enabled, $output);
- $this->printProviders("Disabled providers", $disabled, $output);
+ $output->writeln('');
+ $this->printProviders('Enabled providers', $enabled, $output);
+ $this->printProviders('Disabled providers', $disabled, $output);
return 0;
}
@@ -91,15 +74,15 @@ class State extends Base {
}
private function printProviders(string $title, array $providers,
- OutputInterface $output) {
+ OutputInterface $output) {
if (empty($providers)) {
// Ignore and don't print anything
return;
}
- $output->writeln($title . ":");
+ $output->writeln($title . ':');
foreach ($providers as $provider) {
- $output->writeln("- " . $provider);
+ $output->writeln('- ' . $provider);
}
}
}
diff --git a/core/Command/Upgrade.php b/core/Command/Upgrade.php
index e929dc22bc8..c3d6aacc714 100644
--- a/core/Command/Upgrade.php
+++ b/core/Command/Upgrade.php
@@ -1,45 +1,14 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Andreas Fischer <bantu@owncloud.com>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Nils Wittenbrink <nilswittenbrink@web.de>
- * @author Owen Winkler <a_github@midnightcircus.com>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Sander Ruitenbeek <sander@grids.be>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Thomas Pulzer <t.pulzer@kniel.de>
- * @author Valdnet <47037905+Valdnet@users.noreply.github.com>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command;
-use OCP\EventDispatcher\Event;
-use OCP\EventDispatcher\IEventDispatcher;
-use OCP\IConfig;
-use OCP\Util;
use OC\Console\TimestampFormatter;
use OC\DB\MigratorExecuteSqlEvent;
-use OC\Installer;
use OC\Repair\Events\RepairAdvanceEvent;
use OC\Repair\Events\RepairErrorEvent;
use OC\Repair\Events\RepairFinishEvent;
@@ -48,7 +17,12 @@ use OC\Repair\Events\RepairStartEvent;
use OC\Repair\Events\RepairStepEvent;
use OC\Repair\Events\RepairWarningEvent;
use OC\Updater;
-use Psr\Log\LoggerInterface;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IConfig;
+use OCP\IURLGenerator;
+use OCP\Server;
+use OCP\Util;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
@@ -62,15 +36,11 @@ class Upgrade extends Command {
public const ERROR_INVALID_ARGUMENTS = 4;
public const ERROR_FAILURE = 5;
- private IConfig $config;
- private LoggerInterface $logger;
- private Installer $installer;
-
- public function __construct(IConfig $config, LoggerInterface $logger, Installer $installer) {
+ public function __construct(
+ private IConfig $config,
+ private IURLGenerator $urlGenerator,
+ ) {
parent::__construct();
- $this->config = $config;
- $this->logger = $logger;
- $this->installer = $installer;
}
protected function configure() {
@@ -87,27 +57,23 @@ class Upgrade extends Command {
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
if (Util::needUpgrade()) {
- if (OutputInterface::VERBOSITY_NORMAL < $output->getVerbosity()) {
+ if ($output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
// Prepend each line with a little timestamp
$timestampFormatter = new TimestampFormatter($this->config, $output->getFormatter());
$output->setFormatter($timestampFormatter);
}
$self = $this;
- $updater = new Updater(
- $this->config,
- \OC::$server->getIntegrityCodeChecker(),
- $this->logger,
- $this->installer
- );
+ $updater = Server::get(Updater::class);
+ $incompatibleOverwrites = $this->config->getSystemValue('app_install_overwrite', []);
/** @var IEventDispatcher $dispatcher */
- $dispatcher = \OC::$server->get(IEventDispatcher::class);
+ $dispatcher = Server::get(IEventDispatcher::class);
$progress = new ProgressBar($output);
$progress->setFormat(" %message%\n %current%/%max% [%bar%] %percent:3s%%");
$listener = function (MigratorExecuteSqlEvent $event) use ($progress, $output): void {
$message = $event->getSql();
- if (OutputInterface::VERBOSITY_NORMAL < $output->getVerbosity()) {
+ if ($output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
$output->writeln(' Executing SQL ' . $message);
} else {
if (strlen($message) > 60) {
@@ -143,11 +109,11 @@ class Upgrade extends Command {
$progress->finish();
$output->writeln('');
} elseif ($event instanceof RepairStepEvent) {
- if (OutputInterface::VERBOSITY_NORMAL < $output->getVerbosity()) {
+ if ($output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
$output->writeln('<info>Repair step: ' . $event->getStepName() . '</info>');
}
} elseif ($event instanceof RepairInfoEvent) {
- if (OutputInterface::VERBOSITY_NORMAL < $output->getVerbosity()) {
+ if ($output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
$output->writeln('<info>Repair info: ' . $event->getMessage() . '</info>');
}
} elseif ($event instanceof RepairWarningEvent) {
@@ -167,59 +133,61 @@ class Upgrade extends Command {
$dispatcher->addListener(RepairErrorEvent::class, $repairListener);
- $updater->listen('\OC\Updater', 'maintenanceEnabled', function () use ($output) {
+ $updater->listen('\OC\Updater', 'maintenanceEnabled', function () use ($output): void {
$output->writeln('<info>Turned on maintenance mode</info>');
});
- $updater->listen('\OC\Updater', 'maintenanceDisabled', function () use ($output) {
+ $updater->listen('\OC\Updater', 'maintenanceDisabled', function () use ($output): void {
$output->writeln('<info>Turned off maintenance mode</info>');
});
- $updater->listen('\OC\Updater', 'maintenanceActive', function () use ($output) {
+ $updater->listen('\OC\Updater', 'maintenanceActive', function () use ($output): void {
$output->writeln('<info>Maintenance mode is kept active</info>');
});
$updater->listen('\OC\Updater', 'updateEnd',
- function ($success) use ($output, $self) {
+ function ($success) use ($output, $self): void {
if ($success) {
- $message = "<info>Update successful</info>";
+ $message = '<info>Update successful</info>';
} else {
- $message = "<error>Update failed</error>";
+ $message = '<error>Update failed</error>';
}
$output->writeln($message);
});
- $updater->listen('\OC\Updater', 'dbUpgradeBefore', function () use ($output) {
+ $updater->listen('\OC\Updater', 'dbUpgradeBefore', function () use ($output): void {
$output->writeln('<info>Updating database schema</info>');
});
- $updater->listen('\OC\Updater', 'dbUpgrade', function () use ($output) {
+ $updater->listen('\OC\Updater', 'dbUpgrade', function () use ($output): void {
$output->writeln('<info>Updated database</info>');
});
- $updater->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use ($output) {
- $output->writeln('<comment>Disabled incompatible app: ' . $app . '</comment>');
+ $updater->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use ($output, &$incompatibleOverwrites): void {
+ if (!in_array($app, $incompatibleOverwrites)) {
+ $output->writeln('<comment>Disabled incompatible app: ' . $app . '</comment>');
+ }
});
- $updater->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use ($output) {
+ $updater->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use ($output): void {
$output->writeln('<info>Update app ' . $app . ' from App Store</info>');
});
- $updater->listen('\OC\Updater', 'appSimulateUpdate', function ($app) use ($output) {
+ $updater->listen('\OC\Updater', 'appSimulateUpdate', function ($app) use ($output): void {
$output->writeln("<info>Checking whether the database schema for <$app> can be updated (this can take a long time depending on the database size)</info>");
});
- $updater->listen('\OC\Updater', 'appUpgradeStarted', function ($app, $version) use ($output) {
+ $updater->listen('\OC\Updater', 'appUpgradeStarted', function ($app, $version) use ($output): void {
$output->writeln("<info>Updating <$app> ...</info>");
});
- $updater->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($output) {
+ $updater->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($output): void {
$output->writeln("<info>Updated <$app> to $version</info>");
});
- $updater->listen('\OC\Updater', 'failure', function ($message) use ($output, $self) {
+ $updater->listen('\OC\Updater', 'failure', function ($message) use ($output, $self): void {
$output->writeln("<error>$message</error>");
});
- $updater->listen('\OC\Updater', 'setDebugLogLevel', function ($logLevel, $logLevelName) use ($output) {
- $output->writeln("<info>Setting log level to debug</info>");
+ $updater->listen('\OC\Updater', 'setDebugLogLevel', function ($logLevel, $logLevelName) use ($output): void {
+ $output->writeln('<info>Setting log level to debug</info>');
});
- $updater->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use ($output) {
- $output->writeln("<info>Resetting log level</info>");
+ $updater->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use ($output): void {
+ $output->writeln('<info>Resetting log level</info>');
});
- $updater->listen('\OC\Updater', 'startCheckCodeIntegrity', function () use ($output) {
- $output->writeln("<info>Starting code integrity check...</info>");
+ $updater->listen('\OC\Updater', 'startCheckCodeIntegrity', function () use ($output): void {
+ $output->writeln('<info>Starting code integrity check...</info>');
});
- $updater->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use ($output) {
- $output->writeln("<info>Finished code integrity check</info>");
+ $updater->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use ($output): void {
+ $output->writeln('<info>Finished code integrity check</info>');
});
$success = $updater->upgrade();
@@ -240,7 +208,11 @@ class Upgrade extends Command {
. 'config.php and call this script again.</comment>', true);
return self::ERROR_MAINTENANCE_MODE;
} else {
- $output->writeln('<info>Nextcloud is already latest version</info>');
+ $output->writeln('<info>No upgrade required.</info>');
+ $output->writeln('');
+ $output->writeln('Note: This command triggers the upgrade actions associated with a new version. The new version\'s updated source files must be deployed in advance.');
+ $doc = $this->urlGenerator->linkToDocs('admin-update');
+ $output->writeln('See the upgrade documentation: ' . $doc . ' for more information.');
return self::ERROR_UP_TO_DATE;
}
}
@@ -255,9 +227,9 @@ class Upgrade extends Command {
$trustedDomains = $this->config->getSystemValue('trusted_domains', []);
if (empty($trustedDomains)) {
$output->write(
- '<warning>The setting "trusted_domains" could not be ' .
- 'set automatically by the upgrade script, ' .
- 'please set it manually</warning>'
+ '<warning>The setting "trusted_domains" could not be '
+ . 'set automatically by the upgrade script, '
+ . 'please set it manually</warning>'
);
}
}
diff --git a/core/Command/User/Add.php b/core/Command/User/Add.php
index 24d11fbee6e..4de4e247991 100644
--- a/core/Command/User/Add.php
+++ b/core/Command/User/Add.php
@@ -1,35 +1,23 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Laurens Post <lkpost@scept.re>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\User;
use OC\Files\Filesystem;
+use OCA\Settings\Mailer\NewUserMailHelper;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IAppConfig;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserManager;
+use OCP\Mail\IMailer;
+use OCP\Security\Events\GenerateSecurePasswordEvent;
+use OCP\Security\ISecureRandom;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputArgument;
@@ -39,57 +27,80 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
class Add extends Command {
- protected IUserManager $userManager;
- protected IGroupManager $groupManager;
-
- public function __construct(IUserManager $userManager, IGroupManager $groupManager) {
+ public function __construct(
+ protected IUserManager $userManager,
+ protected IGroupManager $groupManager,
+ protected IMailer $mailer,
+ private IAppConfig $appConfig,
+ private NewUserMailHelper $mailHelper,
+ private IEventDispatcher $eventDispatcher,
+ private ISecureRandom $secureRandom,
+ ) {
parent::__construct();
- $this->userManager = $userManager;
- $this->groupManager = $groupManager;
}
- protected function configure() {
+ protected function configure(): void {
$this
->setName('user:add')
- ->setDescription('adds a user')
+ ->setDescription('adds an account')
->addArgument(
'uid',
InputArgument::REQUIRED,
- 'User ID used to login (must only contain a-z, A-Z, 0-9, -, _ and @)'
+ 'Account ID used to login (must only contain a-z, A-Z, 0-9, -, _ and @)'
)
->addOption(
'password-from-env',
null,
InputOption::VALUE_NONE,
- 'read password from environment variable OC_PASS'
+ 'read password from environment variable NC_PASS/OC_PASS'
+ )
+ ->addOption(
+ 'generate-password',
+ null,
+ InputOption::VALUE_NONE,
+ 'Generate a secure password. A welcome email with a reset link will be sent to the user via an email if --email option and newUser.sendEmail config are set'
)
->addOption(
'display-name',
null,
InputOption::VALUE_OPTIONAL,
- 'User name used in the web UI (can contain any characters)'
+ 'Login used in the web UI (can contain any characters)'
)
->addOption(
'group',
'g',
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
- 'groups the user should be added to (The group will be created if it does not exist)'
+ 'groups the account should be added to (The group will be created if it does not exist)'
+ )
+ ->addOption(
+ 'email',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'When set, users may register using the default email verification workflow'
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$uid = $input->getArgument('uid');
if ($this->userManager->userExists($uid)) {
- $output->writeln('<error>The user "' . $uid . '" already exists.</error>');
+ $output->writeln('<error>The account "' . $uid . '" already exists.</error>');
return 1;
}
+ $password = '';
+
+ // Setup password.
if ($input->getOption('password-from-env')) {
- $password = getenv('OC_PASS');
+ $password = getenv('NC_PASS') ?: getenv('OC_PASS');
+
if (!$password) {
- $output->writeln('<error>--password-from-env given, but OC_PASS is empty!</error>');
+ $output->writeln('<error>--password-from-env given, but NC_PASS/OC_PASS is empty!</error>');
return 1;
}
+ } elseif ($input->getOption('generate-password')) {
+ $passwordEvent = new GenerateSecurePasswordEvent();
+ $this->eventDispatcher->dispatchTyped($passwordEvent);
+ $password = $passwordEvent->getPassword() ?? $this->secureRandom->generate(20);
} elseif ($input->isInteractive()) {
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
@@ -103,29 +114,28 @@ class Add extends Command {
$confirm = $helper->ask($input, $output, $question);
if ($password !== $confirm) {
- $output->writeln("<error>Passwords did not match!</error>");
+ $output->writeln('<error>Passwords did not match!</error>');
return 1;
}
} else {
- $output->writeln("<error>Interactive input or --password-from-env is needed for entering a password!</error>");
+ $output->writeln('<error>Interactive input or --password-from-env or --generate-password is needed for setting a password!</error>');
return 1;
}
try {
$user = $this->userManager->createUser(
$input->getArgument('uid'),
- $password
+ $password,
);
} catch (\Exception $e) {
$output->writeln('<error>' . $e->getMessage() . '</error>');
return 1;
}
-
if ($user instanceof IUser) {
- $output->writeln('<info>The user "' . $user->getUID() . '" was created successfully</info>');
+ $output->writeln('<info>The account "' . $user->getUID() . '" was created successfully</info>');
} else {
- $output->writeln('<error>An error occurred while creating the user</error>');
+ $output->writeln('<error>An error occurred while creating the account</error>');
return 1;
}
@@ -153,9 +163,33 @@ class Add extends Command {
}
if ($group instanceof IGroup) {
$group->addUser($user);
- $output->writeln('User "' . $user->getUID() . '" added to group "' . $group->getGID() . '"');
+ $output->writeln('Account "' . $user->getUID() . '" added to group "' . $group->getGID() . '"');
}
}
+
+ $email = $input->getOption('email');
+ if (!empty($email)) {
+ if (!$this->mailer->validateMailAddress($email)) {
+ $output->writeln(\sprintf(
+ '<error>The given email address "%s" is invalid. Email not set for the user.</error>',
+ $email,
+ ));
+
+ return 1;
+ }
+
+ $user->setSystemEMailAddress($email);
+
+ if ($this->appConfig->getValueString('core', 'newUser.sendEmail', 'yes') === 'yes') {
+ try {
+ $this->mailHelper->sendMail($user, $this->mailHelper->generateTemplate($user, true));
+ $output->writeln('Welcome email sent to ' . $email);
+ } catch (\Exception $e) {
+ $output->writeln('Unable to send the welcome email to ' . $email);
+ }
+ }
+ }
+
return 0;
}
}
diff --git a/core/Command/User/AddAppPassword.php b/core/Command/User/AuthTokens/Add.php
index ec39cdc974e..89b20535c63 100644
--- a/core/Command/User/AddAppPassword.php
+++ b/core/Command/User/AuthTokens/Add.php
@@ -3,28 +3,10 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, NextCloud, Inc.
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Sean Molenaar <sean@seanmolenaar.eu>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
-namespace OC\Core\Command\User;
+namespace OC\Core\Command\User\AuthTokens;
use OC\Authentication\Events\AppPasswordCreatedEvent;
use OC\Authentication\Token\IProvider;
@@ -40,31 +22,25 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
-class AddAppPassword extends Command {
- protected IUserManager $userManager;
- protected IProvider $tokenProvider;
- private ISecureRandom $random;
- private IEventDispatcher $eventDispatcher;
-
- public function __construct(IUserManager $userManager,
- IProvider $tokenProvider,
- ISecureRandom $random,
- IEventDispatcher $eventDispatcher) {
- $this->tokenProvider = $tokenProvider;
- $this->userManager = $userManager;
- $this->random = $random;
- $this->eventDispatcher = $eventDispatcher;
+class Add extends Command {
+ public function __construct(
+ protected IUserManager $userManager,
+ protected IProvider $tokenProvider,
+ private ISecureRandom $random,
+ private IEventDispatcher $eventDispatcher,
+ ) {
parent::__construct();
}
protected function configure() {
$this
- ->setName('user:add-app-password')
- ->setDescription('Add app password for the named user')
+ ->setName('user:auth-tokens:add')
+ ->setAliases(['user:add-app-password'])
+ ->setDescription('Add app password for the named account')
->addArgument(
'user',
InputArgument::REQUIRED,
- 'Username to add app password for'
+ 'Login to add app password for'
)
->addOption(
'password-from-env',
@@ -81,21 +57,21 @@ class AddAppPassword extends Command {
$user = $this->userManager->get($username);
if (is_null($user)) {
- $output->writeln('<error>User does not exist</error>');
+ $output->writeln('<error>Account does not exist</error>');
return 1;
}
if ($input->getOption('password-from-env')) {
- $password = getenv('NC_PASS') ?? getenv('OC_PASS');
+ $password = getenv('NC_PASS') ?: getenv('OC_PASS');
if (!$password) {
- $output->writeln('<error>--password-from-env given, but NC_PASS is empty!</error>');
+ $output->writeln('<error>--password-from-env given, but NC_PASS/OC_PASS is empty!</error>');
return 1;
}
} elseif ($input->isInteractive()) {
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
- $question = new Question('Enter the user password: ');
+ $question = new Question('Enter the account password: ');
$question->setHidden(true);
/** @var null|string $password */
$password = $helper->ask($input, $output, $question);
@@ -105,7 +81,7 @@ class AddAppPassword extends Command {
$output->writeln('<info>No password provided. The generated app password will therefore have limited capabilities. Any operation that requires the login password will fail.</info>');
}
- $token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
+ $token = $this->random->generate(72, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
$generatedToken = $this->tokenProvider->generateToken(
$token,
$user->getUID(),
diff --git a/core/Command/User/AuthTokens/Delete.php b/core/Command/User/AuthTokens/Delete.php
new file mode 100644
index 00000000000..2047d2eae2a
--- /dev/null
+++ b/core/Command/User/AuthTokens/Delete.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\User\AuthTokens;
+
+use DateTimeImmutable;
+use OC\Authentication\Token\IProvider;
+use OC\Core\Command\Base;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Exception\RuntimeException;
+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 Delete extends Base {
+ public function __construct(
+ protected IProvider $tokenProvider,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('user:auth-tokens:delete')
+ ->setDescription('Deletes an authentication token')
+ ->addArgument(
+ 'uid',
+ InputArgument::REQUIRED,
+ 'ID of the user to delete tokens for'
+ )
+ ->addArgument(
+ 'id',
+ InputArgument::OPTIONAL,
+ 'ID of the auth token to delete'
+ )
+ ->addOption(
+ 'last-used-before',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Delete tokens last used before a given date.'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $uid = $input->getArgument('uid');
+ $id = (int)$input->getArgument('id');
+ $before = $input->getOption('last-used-before');
+
+ if ($before) {
+ if ($id) {
+ throw new RuntimeException('Option --last-used-before cannot be used with [<id>]');
+ }
+
+ return $this->deleteLastUsedBefore($uid, $before);
+ }
+
+ if (!$id) {
+ throw new RuntimeException('Not enough arguments. Specify the token <id> or use the --last-used-before option.');
+ }
+ return $this->deleteById($uid, $id);
+ }
+
+ protected function deleteById(string $uid, int $id): int {
+ $this->tokenProvider->invalidateTokenById($uid, $id);
+
+ return Command::SUCCESS;
+ }
+
+ protected function deleteLastUsedBefore(string $uid, string $before): int {
+ $date = $this->parseDateOption($before);
+ if (!$date) {
+ throw new RuntimeException('Invalid date format. Acceptable formats are: ISO8601 (w/o fractions), "YYYY-MM-DD" and Unix time in seconds.');
+ }
+
+ $this->tokenProvider->invalidateLastUsedBefore($uid, $date->getTimestamp());
+
+ return Command::SUCCESS;
+ }
+
+ /**
+ * @return \DateTimeImmutable|false
+ */
+ protected function parseDateOption(string $input) {
+ $date = false;
+
+ // Handle Unix timestamp
+ if (filter_var($input, FILTER_VALIDATE_INT)) {
+ return new DateTimeImmutable('@' . $input);
+ }
+
+ // ISO8601
+ $date = DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, $input);
+ if ($date) {
+ return $date;
+ }
+
+ // YYYY-MM-DD
+ return DateTimeImmutable::createFromFormat('!Y-m-d', $input);
+ }
+}
diff --git a/core/Command/User/AuthTokens/ListCommand.php b/core/Command/User/AuthTokens/ListCommand.php
new file mode 100644
index 00000000000..b36aa717505
--- /dev/null
+++ b/core/Command/User/AuthTokens/ListCommand.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\User\AuthTokens;
+
+use OC\Authentication\Token\IProvider;
+use OC\Authentication\Token\IToken;
+use OC\Core\Command\Base;
+use OCP\IUserManager;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ListCommand extends Base {
+ public function __construct(
+ protected IUserManager $userManager,
+ protected IProvider $tokenProvider,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+
+ $this
+ ->setName('user:auth-tokens:list')
+ ->setDescription('List authentication tokens of an user')
+ ->addArgument(
+ 'user',
+ InputArgument::REQUIRED,
+ 'User to list auth tokens for'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $user = $this->userManager->get($input->getArgument('user'));
+
+ if (is_null($user)) {
+ $output->writeln('<error>user not found</error>');
+ return 1;
+ }
+
+ $tokens = $this->tokenProvider->getTokenByUser($user->getUID());
+
+ $tokens = array_map(function (IToken $token) use ($input): mixed {
+ $sensitive = [
+ 'password',
+ 'password_hash',
+ 'token',
+ 'public_key',
+ 'private_key',
+ ];
+ $data = array_diff_key($token->jsonSerialize(), array_flip($sensitive));
+
+ if ($input->getOption('output') === self::OUTPUT_FORMAT_PLAIN) {
+ $data = $this->formatTokenForPlainOutput($data);
+ }
+
+ return $data;
+ }, $tokens);
+
+ $this->writeTableInOutputFormat($input, $output, $tokens);
+
+ return 0;
+ }
+
+ public function formatTokenForPlainOutput(array $token): array {
+ $token['scope'] = implode(', ', array_keys(array_filter($token['scope'] ?? [])));
+
+ $token['lastActivity'] = date(DATE_ATOM, $token['lastActivity']);
+
+ $token['type'] = match ($token['type']) {
+ IToken::TEMPORARY_TOKEN => 'temporary',
+ IToken::PERMANENT_TOKEN => 'permanent',
+ IToken::WIPE_TOKEN => 'wipe',
+ default => $token['type'],
+ };
+
+ return $token;
+ }
+}
diff --git a/core/Command/User/ClearGeneratedAvatarCacheCommand.php b/core/Command/User/ClearGeneratedAvatarCacheCommand.php
new file mode 100644
index 00000000000..515b3a913b7
--- /dev/null
+++ b/core/Command/User/ClearGeneratedAvatarCacheCommand.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\User;
+
+use OC\Avatar\AvatarManager;
+use OC\Core\Command\Base;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ClearGeneratedAvatarCacheCommand extends Base {
+ public function __construct(
+ protected AvatarManager $avatarManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setDescription('clear avatar cache')
+ ->setName('user:clear-avatar-cache');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $output->writeln('Clearing avatar cache has started');
+ $this->avatarManager->clearCachedAvatars();
+ $output->writeln('Cleared avatar cache successfully');
+ return 0;
+ }
+}
diff --git a/core/Command/User/Delete.php b/core/Command/User/Delete.php
index 9624f04fa18..c5d0578f5f8 100644
--- a/core/Command/User/Delete.php
+++ b/core/Command/User/Delete.php
@@ -1,26 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Jens-Christian Fischer <jens-christian.fischer@switch.ch>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\User;
@@ -33,14 +16,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Delete extends Base {
- /** @var IUserManager */
- protected $userManager;
-
- /**
- * @param IUserManager $userManager
- */
- public function __construct(IUserManager $userManager) {
- $this->userManager = $userManager;
+ public function __construct(
+ protected IUserManager $userManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/User/Disable.php b/core/Command/User/Disable.php
index bc819f39e1d..4713950bf30 100644
--- a/core/Command/User/Disable.php
+++ b/core/Command/User/Disable.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\User;
@@ -32,10 +16,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Disable extends Base {
- protected IUserManager $userManager;
-
- public function __construct(IUserManager $userManager) {
- $this->userManager = $userManager;
+ public function __construct(
+ protected IUserManager $userManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/User/Enable.php b/core/Command/User/Enable.php
index f4e16eec4af..23f56e5dd4f 100644
--- a/core/Command/User/Enable.php
+++ b/core/Command/User/Enable.php
@@ -1,25 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\User;
@@ -32,10 +16,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Enable extends Base {
- protected IUserManager $userManager;
-
- public function __construct(IUserManager $userManager) {
- $this->userManager = $userManager;
+ public function __construct(
+ protected IUserManager $userManager,
+ ) {
parent::__construct();
}
diff --git a/core/Command/User/Info.php b/core/Command/User/Info.php
index 1e89a8d0911..e7fc9286e74 100644
--- a/core/Command/User/Info.php
+++ b/core/Command/User/Info.php
@@ -1,30 +1,13 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl>
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\User;
use OC\Core\Command\Base;
+use OCP\Files\NotFoundException;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserManager;
@@ -35,12 +18,10 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Info extends Base {
- protected IUserManager $userManager;
- protected IGroupManager $groupManager;
-
- public function __construct(IUserManager $userManager, IGroupManager $groupManager) {
- $this->userManager = $userManager;
- $this->groupManager = $groupManager;
+ public function __construct(
+ protected IUserManager $userManager,
+ protected IGroupManager $groupManager,
+ ) {
parent::__construct();
}
@@ -77,7 +58,8 @@ class Info extends Base {
'groups' => $groups,
'quota' => $user->getQuota(),
'storage' => $this->getStorageInfo($user),
- 'last_seen' => date(\DateTimeInterface::ATOM, $user->getLastLogin()), // ISO-8601
+ 'first_seen' => $this->formatLoginDate($user->getFirstLogin()),
+ 'last_seen' => $this->formatLoginDate($user->getLastLogin()),
'user_directory' => $user->getHome(),
'backend' => $user->getBackendClassName()
];
@@ -85,6 +67,16 @@ class Info extends Base {
return 0;
}
+ private function formatLoginDate(int $timestamp): string {
+ if ($timestamp < 0) {
+ return 'unknown';
+ } elseif ($timestamp === 0) {
+ return 'never';
+ } else {
+ return date(\DateTimeInterface::ATOM, $timestamp); // ISO-8601
+ }
+ }
+
/**
* @param IUser $user
* @return array
@@ -94,7 +86,7 @@ class Info extends Base {
\OC_Util::setupFS($user->getUID());
try {
$storage = \OC_Helper::getStorageInfo('/');
- } catch (\OCP\Files\NotFoundException $e) {
+ } catch (NotFoundException $e) {
return [];
}
return [
diff --git a/core/Command/User/Keys/Verify.php b/core/Command/User/Keys/Verify.php
new file mode 100644
index 00000000000..024e9346072
--- /dev/null
+++ b/core/Command/User/Keys/Verify.php
@@ -0,0 +1,83 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\User\Keys;
+
+use OC\Security\IdentityProof\Manager;
+use OCP\IUser;
+use OCP\IUserManager;
+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 Verify extends Command {
+ public function __construct(
+ protected IUserManager $userManager,
+ protected Manager $keyManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('user:keys:verify')
+ ->setDescription('Verify if the stored public key matches the stored private key')
+ ->addArgument(
+ 'user-id',
+ InputArgument::REQUIRED,
+ 'User ID of the user to verify'
+ )
+ ;
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $userId = $input->getArgument('user-id');
+
+ $user = $this->userManager->get($userId);
+ if (!$user instanceof IUser) {
+ $output->writeln('Unknown user');
+ return static::FAILURE;
+ }
+
+ $key = $this->keyManager->getKey($user);
+ $publicKey = $key->getPublic();
+ $privateKey = $key->getPrivate();
+
+ $output->writeln('User public key size: ' . strlen($publicKey));
+ $output->writeln('User private key size: ' . strlen($privateKey));
+
+ // Derive the public key from the private key again to validate the stored public key
+ $opensslPrivateKey = openssl_pkey_get_private($privateKey);
+ $publicKeyDerived = openssl_pkey_get_details($opensslPrivateKey);
+ $publicKeyDerived = $publicKeyDerived['key'];
+ $output->writeln('User derived public key size: ' . strlen($publicKeyDerived));
+
+ $output->writeln('');
+
+ $output->writeln('Stored public key:');
+ $output->writeln($publicKey);
+ $output->writeln('Derived public key:');
+ $output->writeln($publicKeyDerived);
+
+ if ($publicKey != $publicKeyDerived) {
+ $output->writeln('<error>Stored public key does not match stored private key</error>');
+ return static::FAILURE;
+ }
+
+ $output->writeln('<info>Stored public key matches stored private key</info>');
+
+ return static::SUCCESS;
+ }
+}
diff --git a/core/Command/User/LastSeen.php b/core/Command/User/LastSeen.php
index 5ea6c64d249..984def72cd6 100644
--- a/core/Command/User/LastSeen.php
+++ b/core/Command/User/LastSeen.php
@@ -1,27 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Pierre Ozoux <pierre@ozoux.net>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\User;
@@ -31,44 +13,71 @@ use OCP\IUserManager;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
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 LastSeen extends Base {
- protected IUserManager $userManager;
-
- public function __construct(IUserManager $userManager) {
- $this->userManager = $userManager;
+ public function __construct(
+ protected IUserManager $userManager,
+ ) {
parent::__construct();
}
- protected function configure() {
+ protected function configure(): void {
$this
->setName('user:lastseen')
->setDescription('shows when the user was logged in last time')
->addArgument(
'uid',
- InputArgument::REQUIRED,
+ InputArgument::OPTIONAL,
'the username'
- );
+ )
+ ->addOption(
+ 'all',
+ null,
+ InputOption::VALUE_NONE,
+ 'shows a list of when all users were last logged in'
+ )
+ ;
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $user = $this->userManager->get($input->getArgument('uid'));
- if (is_null($user)) {
- $output->writeln('<error>User does not exist</error>');
- return 1;
+ $singleUserId = $input->getArgument('uid');
+
+ if ($singleUserId) {
+ $user = $this->userManager->get($singleUserId);
+ if (is_null($user)) {
+ $output->writeln('<error>User does not exist</error>');
+ return 1;
+ }
+
+ $lastLogin = $user->getLastLogin();
+ if ($lastLogin === 0) {
+ $output->writeln($user->getUID() . ' has never logged in.');
+ } else {
+ $date = new \DateTime();
+ $date->setTimestamp($lastLogin);
+ $output->writeln($user->getUID() . "'s last login: " . $date->format('Y-m-d H:i:s T'));
+ }
+
+ return 0;
}
- $lastLogin = $user->getLastLogin();
- if ($lastLogin === 0) {
- $output->writeln('User ' . $user->getUID() .
- ' has never logged in, yet.');
- } else {
- $date = new \DateTime();
- $date->setTimestamp($lastLogin);
- $output->writeln($user->getUID() .
- '`s last login: ' . $date->format('d.m.Y H:i'));
+ if (!$input->getOption('all')) {
+ $output->writeln('<error>Please specify a username, or "--all" to list all</error>');
+ return 1;
}
+
+ $this->userManager->callForAllUsers(static function (IUser $user) use ($output): void {
+ $lastLogin = $user->getLastLogin();
+ if ($lastLogin === 0) {
+ $output->writeln($user->getUID() . ' has never logged in.');
+ } else {
+ $date = new \DateTime();
+ $date->setTimestamp($lastLogin);
+ $output->writeln($user->getUID() . "'s last login: " . $date->format('Y-m-d H:i:s T'));
+ }
+ });
return 0;
}
diff --git a/core/Command/User/ListCommand.php b/core/Command/User/ListCommand.php
index c254a8a11cf..66b831c793b 100644
--- a/core/Command/User/ListCommand.php
+++ b/core/Command/User/ListCommand.php
@@ -1,26 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl>
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Robin Appelman <robin@icewind.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Command\User;
@@ -33,13 +15,10 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ListCommand extends Base {
- protected IUserManager $userManager;
- protected IGroupManager $groupManager;
-
- public function __construct(IUserManager $userManager,
- IGroupManager $groupManager) {
- $this->userManager = $userManager;
- $this->groupManager = $groupManager;
+ public function __construct(
+ protected IUserManager $userManager,
+ protected IGroupManager $groupManager,
+ ) {
parent::__construct();
}
@@ -48,6 +27,11 @@ class ListCommand extends Base {
->setName('user:list')
->setDescription('list configured users')
->addOption(
+ 'disabled',
+ 'd',
+ InputOption::VALUE_NONE,
+ 'List disabled users only'
+ )->addOption(
'limit',
'l',
InputOption::VALUE_OPTIONAL,
@@ -74,7 +58,11 @@ class ListCommand extends Base {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $users = $this->userManager->search('', (int) $input->getOption('limit'), (int) $input->getOption('offset'));
+ if ($input->getOption('disabled')) {
+ $users = $this->userManager->getDisabledUsers((int)$input->getOption('limit'), (int)$input->getOption('offset'));
+ } else {
+ $users = $this->userManager->searchDisplayName('', (int)$input->getOption('limit'), (int)$input->getOption('offset'));
+ }
$this->writeArrayInOutputFormat($input, $output, $this->formatUsers($users, (bool)$input->getOption('info')));
return 0;
@@ -82,18 +70,13 @@ class ListCommand extends Base {
/**
* @param IUser[] $users
- * @param bool [$detailed=false]
- * @return array
+ * @return \Generator<string,string|array>
*/
- private function formatUsers(array $users, bool $detailed = false) {
- $keys = array_map(function (IUser $user) {
- return $user->getUID();
- }, $users);
-
- $values = array_map(function (IUser $user) use ($detailed) {
+ private function formatUsers(array $users, bool $detailed = false): \Generator {
+ foreach ($users as $user) {
if ($detailed) {
$groups = $this->groupManager->getUserGroupIds($user);
- return [
+ $value = [
'user_id' => $user->getUID(),
'display_name' => $user->getDisplayName(),
'email' => (string)$user->getSystemEMailAddress(),
@@ -101,13 +84,25 @@ class ListCommand extends Base {
'enabled' => $user->isEnabled(),
'groups' => $groups,
'quota' => $user->getQuota(),
- 'last_seen' => date(\DateTimeInterface::ATOM, $user->getLastLogin()), // ISO-8601
+ 'first_seen' => $this->formatLoginDate($user->getFirstLogin()),
+ 'last_seen' => $this->formatLoginDate($user->getLastLogin()),
'user_directory' => $user->getHome(),
'backend' => $user->getBackendClassName()
];
+ } else {
+ $value = $user->getDisplayName();
}
- return $user->getDisplayName();
- }, $users);
- return array_combine($keys, $values);
+ yield $user->getUID() => $value;
+ }
+ }
+
+ private function formatLoginDate(int $timestamp): string {
+ if ($timestamp < 0) {
+ return 'unknown';
+ } elseif ($timestamp === 0) {
+ return 'never';
+ } else {
+ return date(\DateTimeInterface::ATOM, $timestamp); // ISO-8601
+ }
}
}
diff --git a/core/Command/User/Profile.php b/core/Command/User/Profile.php
new file mode 100644
index 00000000000..fd5fbed08cd
--- /dev/null
+++ b/core/Command/User/Profile.php
@@ -0,0 +1,234 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\User;
+
+use OC\Core\Command\Base;
+use OCP\Accounts\IAccount;
+use OCP\Accounts\IAccountManager;
+use OCP\Accounts\PropertyDoesNotExistException;
+use OCP\IUser;
+use OCP\IUserManager;
+use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
+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 Profile extends Base {
+ public function __construct(
+ protected IUserManager $userManager,
+ protected IAccountManager $accountManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure() {
+ parent::configure();
+ $this
+ ->setName('user:profile')
+ ->setDescription('Read and modify user profile properties')
+ ->addArgument(
+ 'uid',
+ InputArgument::REQUIRED,
+ 'Account ID used to login'
+ )
+ ->addArgument(
+ 'key',
+ InputArgument::OPTIONAL,
+ 'Profile property to set, get or delete',
+ ''
+ )
+
+ // Get
+ ->addOption(
+ 'default-value',
+ null,
+ InputOption::VALUE_REQUIRED,
+ '(Only applicable on get) If no default value is set and the property does not exist, the command will exit with 1'
+ )
+
+ // Set
+ ->addArgument(
+ 'value',
+ InputArgument::OPTIONAL,
+ 'The new value of the property',
+ null
+ )
+ ->addOption(
+ 'update-only',
+ null,
+ InputOption::VALUE_NONE,
+ 'Only updates the value, if it is not set before, it is not being added'
+ )
+
+ // Delete
+ ->addOption(
+ 'delete',
+ null,
+ InputOption::VALUE_NONE,
+ 'Specify this option to delete the property value'
+ )
+ ->addOption(
+ 'error-if-not-exists',
+ null,
+ InputOption::VALUE_NONE,
+ 'Checks whether the property exists before deleting it'
+ )
+ ;
+ }
+
+ protected function checkInput(InputInterface $input): IUser {
+ $uid = $input->getArgument('uid');
+ $user = $this->userManager->get($uid);
+ if (!$user) {
+ throw new \InvalidArgumentException('The user "' . $uid . '" does not exist.');
+ }
+ // normalize uid
+ $input->setArgument('uid', $user->getUID());
+
+ $key = $input->getArgument('key');
+ if ($key === '') {
+ if ($input->hasParameterOption('--default-value')) {
+ throw new \InvalidArgumentException('The "default-value" option can only be used when specifying a key.');
+ }
+ if ($input->getArgument('value') !== null) {
+ throw new \InvalidArgumentException('The value argument can only be used when specifying a key.');
+ }
+ if ($input->getOption('delete')) {
+ throw new \InvalidArgumentException('The "delete" option can only be used when specifying a key.');
+ }
+ }
+
+ if ($input->getArgument('value') !== null && $input->hasParameterOption('--default-value')) {
+ throw new \InvalidArgumentException('The value argument can not be used together with "default-value".');
+ }
+ if ($input->getOption('update-only') && $input->getArgument('value') === null) {
+ throw new \InvalidArgumentException('The "update-only" option can only be used together with "value".');
+ }
+
+ if ($input->getOption('delete') && $input->hasParameterOption('--default-value')) {
+ throw new \InvalidArgumentException('The "delete" option can not be used together with "default-value".');
+ }
+ if ($input->getOption('delete') && $input->getArgument('value') !== null) {
+ throw new \InvalidArgumentException('The "delete" option can not be used together with "value".');
+ }
+ if ($input->getOption('error-if-not-exists') && !$input->getOption('delete')) {
+ throw new \InvalidArgumentException('The "error-if-not-exists" option can only be used together with "delete".');
+ }
+
+ return $user;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ try {
+ $user = $this->checkInput($input);
+ } catch (\InvalidArgumentException $e) {
+ $output->writeln('<error>' . $e->getMessage() . '</error>');
+ return self::FAILURE;
+ }
+
+ $uid = $input->getArgument('uid');
+ $key = $input->getArgument('key');
+ $userAccount = $this->accountManager->getAccount($user);
+
+ if ($key === '') {
+ $settings = $this->getAllProfileProperties($userAccount);
+ $this->writeArrayInOutputFormat($input, $output, $settings);
+ return self::SUCCESS;
+ }
+
+ $value = $this->getStoredValue($userAccount, $key);
+ $inputValue = $input->getArgument('value');
+ if ($inputValue !== null) {
+ if ($input->hasParameterOption('--update-only') && $value === null) {
+ $output->writeln('<error>The property does not exist for user "' . $uid . '".</error>');
+ return self::FAILURE;
+ }
+
+ return $this->editProfileProperty($output, $userAccount, $key, $inputValue);
+ } elseif ($input->hasParameterOption('--delete')) {
+ if ($input->hasParameterOption('--error-if-not-exists') && $value === null) {
+ $output->writeln('<error>The property does not exist for user "' . $uid . '".</error>');
+ return self::FAILURE;
+ }
+
+ return $this->deleteProfileProperty($output, $userAccount, $key);
+ } elseif ($value !== null) {
+ $output->writeln($value);
+ } elseif ($input->hasParameterOption('--default-value')) {
+ $output->writeln($input->getOption('default-value'));
+ } else {
+ $output->writeln('<error>The property does not exist for user "' . $uid . '".</error>');
+ return self::FAILURE;
+ }
+
+ return self::SUCCESS;
+ }
+
+ private function deleteProfileProperty(OutputInterface $output, IAccount $userAccount, string $key): int {
+ return $this->editProfileProperty($output, $userAccount, $key, '');
+ }
+
+ private function editProfileProperty(OutputInterface $output, IAccount $userAccount, string $key, string $value): int {
+ try {
+ $userAccount->getProperty($key)->setValue($value);
+ } catch (PropertyDoesNotExistException $exception) {
+ $output->writeln('<error>' . $exception->getMessage() . '</error>');
+ return self::FAILURE;
+ }
+
+ $this->accountManager->updateAccount($userAccount);
+ return self::SUCCESS;
+ }
+
+ private function getStoredValue(IAccount $userAccount, string $key): ?string {
+ try {
+ $property = $userAccount->getProperty($key);
+ } catch (PropertyDoesNotExistException) {
+ return null;
+ }
+ return $property->getValue() === '' ? null : $property->getValue();
+ }
+
+ private function getAllProfileProperties(IAccount $userAccount): array {
+ $properties = [];
+
+ foreach ($userAccount->getAllProperties() as $property) {
+ if ($property->getValue() !== '') {
+ $properties[$property->getName()] = $property->getValue();
+ }
+ }
+
+ return $properties;
+ }
+
+ /**
+ * @param string $argumentName
+ * @param CompletionContext $context
+ * @return string[]
+ */
+ public function completeArgumentValues($argumentName, CompletionContext $context): array {
+ if ($argumentName === 'uid') {
+ return array_map(static fn (IUser $user) => $user->getUID(), $this->userManager->search($context->getCurrentWord()));
+ }
+ if ($argumentName === 'key') {
+ $userId = $context->getWordAtIndex($context->getWordIndex() - 1);
+ $user = $this->userManager->get($userId);
+ if (!($user instanceof IUser)) {
+ return [];
+ }
+
+ $account = $this->accountManager->getAccount($user);
+
+ $properties = $this->getAllProfileProperties($account);
+ return array_keys($properties);
+ }
+ return [];
+ }
+}
diff --git a/core/Command/User/Report.php b/core/Command/User/Report.php
index e080a617258..c0f054adb00 100644
--- a/core/Command/User/Report.php
+++ b/core/Command/User/Report.php
@@ -3,29 +3,9 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\User;
@@ -41,13 +21,10 @@ use Symfony\Component\Console\Output\OutputInterface;
class Report extends Command {
public const DEFAULT_COUNT_DIRS_MAX_USERS = 500;
- protected IUserManager $userManager;
- private IConfig $config;
-
- public function __construct(IUserManager $userManager,
- IConfig $config) {
- $this->userManager = $userManager;
- $this->config = $config;
+ public function __construct(
+ protected IUserManager $userManager,
+ private IConfig $config,
+ ) {
parent::__construct();
}
@@ -66,7 +43,7 @@ class Report extends Command {
protected function execute(InputInterface $input, OutputInterface $output): int {
$table = new Table($output);
- $table->setHeaders(['User Report', '']);
+ $table->setHeaders(['Account Report', '']);
$userCountArray = $this->countUsers();
$total = 0;
if (!empty($userCountArray)) {
diff --git a/core/Command/User/ResetPassword.php b/core/Command/User/ResetPassword.php
index 294cea38b71..0e8b1325770 100644
--- a/core/Command/User/ResetPassword.php
+++ b/core/Command/User/ResetPassword.php
@@ -1,29 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Andreas Fischer <bantu@owncloud.com>
- * @author Christopher Schäpers <kondou@ts.unde.re>
- * @author Clark Tomlinson <fallen013@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Laurens Post <lkpost@scept.re>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Sujith H <sharidasan@owncloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\User;
@@ -41,13 +21,11 @@ use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
class ResetPassword extends Base {
- protected IUserManager $userManager;
- private IAppManager $appManager;
-
- public function __construct(IUserManager $userManager, IAppManager $appManager) {
+ public function __construct(
+ protected IUserManager $userManager,
+ private IAppManager $appManager,
+ ) {
parent::__construct();
- $this->userManager = $userManager;
- $this->appManager = $appManager;
}
protected function configure() {
@@ -57,13 +35,13 @@ class ResetPassword extends Base {
->addArgument(
'user',
InputArgument::REQUIRED,
- 'Username to reset password'
+ 'Login to reset password'
)
->addOption(
'password-from-env',
null,
InputOption::VALUE_NONE,
- 'read password from environment variable OC_PASS'
+ 'read password from environment variable NC_PASS/OC_PASS'
)
;
}
@@ -78,9 +56,9 @@ class ResetPassword extends Base {
}
if ($input->getOption('password-from-env')) {
- $password = getenv('OC_PASS');
+ $password = getenv('NC_PASS') ?: getenv('OC_PASS');
if (!$password) {
- $output->writeln('<error>--password-from-env given, but OC_PASS is empty!</error>');
+ $output->writeln('<error>--password-from-env given, but NC_PASS/OC_PASS is empty!</error>');
return 1;
}
} elseif ($input->isInteractive()) {
@@ -103,7 +81,7 @@ class ResetPassword extends Base {
$password = $helper->ask($input, $output, $question);
if ($password === null) {
- $output->writeln("<error>Password cannot be empty!</error>");
+ $output->writeln('<error>Password cannot be empty!</error>');
return 1;
}
@@ -112,11 +90,11 @@ class ResetPassword extends Base {
$confirm = $helper->ask($input, $output, $question);
if ($password !== $confirm) {
- $output->writeln("<error>Passwords did not match!</error>");
+ $output->writeln('<error>Passwords did not match!</error>');
return 1;
}
} else {
- $output->writeln("<error>Interactive input or --password-from-env is needed for entering a new password!</error>");
+ $output->writeln('<error>Interactive input or --password-from-env is needed for entering a new password!</error>');
return 1;
}
@@ -129,9 +107,9 @@ class ResetPassword extends Base {
}
if ($success) {
- $output->writeln("<info>Successfully reset password for " . $username . "</info>");
+ $output->writeln('<info>Successfully reset password for ' . $username . '</info>');
} else {
- $output->writeln("<error>Error while resetting password!</error>");
+ $output->writeln('<error>Error while resetting password!</error>');
return 1;
}
return 0;
diff --git a/core/Command/User/Setting.php b/core/Command/User/Setting.php
index fac5c3c976c..7fc5aab1dc7 100644
--- a/core/Command/User/Setting.php
+++ b/core/Command/User/Setting.php
@@ -1,27 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Johannes Leuker <j.leuker@hosting.de>
- * @author Kim Brose <kim.brose@rwth-aachen.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Command\User;
@@ -36,13 +18,11 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Setting extends Base {
- protected IUserManager $userManager;
- protected IConfig $config;
-
- public function __construct(IUserManager $userManager, IConfig $config) {
+ public function __construct(
+ protected IUserManager $userManager,
+ protected IConfig $config,
+ ) {
parent::__construct();
- $this->userManager = $userManager;
- $this->config = $config;
}
protected function configure() {
@@ -53,7 +33,7 @@ class Setting extends Base {
->addArgument(
'uid',
InputArgument::REQUIRED,
- 'User ID used to login'
+ 'Account ID used to login'
)
->addArgument(
'app',
@@ -175,7 +155,8 @@ class Setting extends Base {
$user = $this->userManager->get($uid);
if ($user instanceof IUser) {
if ($key === 'email') {
- $user->setEMailAddress($input->getArgument('value'));
+ $email = $input->getArgument('value');
+ $user->setSystemEMailAddress(mb_strtolower(trim($email)));
} elseif ($key === 'display_name') {
if (!$user->setDisplayName($input->getArgument('value'))) {
if ($user->getDisplayName() === $input->getArgument('value')) {
@@ -239,7 +220,7 @@ class Setting extends Base {
}
}
- protected function getUserSettings($uid, $app) {
+ protected function getUserSettings(string $uid, string $app): array {
$settings = $this->config->getAllUserValues($uid);
if ($app !== '') {
if (isset($settings[$app])) {
@@ -250,7 +231,10 @@ class Setting extends Base {
}
$user = $this->userManager->get($uid);
- $settings['settings']['display_name'] = $user->getDisplayName();
+ if ($user !== null) {
+ // Only add the display name if the user exists
+ $settings['settings']['display_name'] = $user->getDisplayName();
+ }
return $settings;
}
diff --git a/core/Command/User/SyncAccountDataCommand.php b/core/Command/User/SyncAccountDataCommand.php
new file mode 100644
index 00000000000..c353df6fe9f
--- /dev/null
+++ b/core/Command/User/SyncAccountDataCommand.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\User;
+
+use OC\Core\Command\Base;
+use OCP\Accounts\IAccountManager;
+use OCP\Accounts\PropertyDoesNotExistException;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\User\Backend\IGetDisplayNameBackend;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class SyncAccountDataCommand extends Base {
+ public function __construct(
+ protected IUserManager $userManager,
+ protected IAccountManager $accountManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure() {
+ $this
+ ->setName('user:sync-account-data')
+ ->setDescription('sync user backend data to accounts table for configured users')
+ ->addOption(
+ 'limit',
+ 'l',
+ InputOption::VALUE_OPTIONAL,
+ 'Number of users to retrieve',
+ '500'
+ )->addOption(
+ 'offset',
+ 'o',
+ InputOption::VALUE_OPTIONAL,
+ 'Offset for retrieving users',
+ '0'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $users = $this->userManager->searchDisplayName('', (int)$input->getOption('limit'), (int)$input->getOption('offset'));
+
+ foreach ($users as $user) {
+ $this->updateUserAccount($user, $output);
+ }
+ return 0;
+ }
+
+ private function updateUserAccount(IUser $user, OutputInterface $output): void {
+ $changed = false;
+ $account = $this->accountManager->getAccount($user);
+ if ($user->getBackend() instanceof IGetDisplayNameBackend) {
+ try {
+ $displayNameProperty = $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
+ } catch (PropertyDoesNotExistException) {
+ $displayNameProperty = null;
+ }
+ if (!$displayNameProperty || $displayNameProperty->getValue() !== $user->getDisplayName()) {
+ $output->writeln($user->getUID() . ' - updating changed display name');
+ $account->setProperty(
+ IAccountManager::PROPERTY_DISPLAYNAME,
+ $user->getDisplayName(),
+ $displayNameProperty ? $displayNameProperty->getScope() : IAccountManager::SCOPE_PRIVATE,
+ $displayNameProperty ? $displayNameProperty->getVerified() : IAccountManager::NOT_VERIFIED,
+ $displayNameProperty ? $displayNameProperty->getVerificationData() : ''
+ );
+ $changed = true;
+ }
+ }
+
+ if ($changed) {
+ $this->accountManager->updateAccount($account);
+ $output->writeln($user->getUID() . ' - account data updated');
+ } else {
+ $output->writeln($user->getUID() . ' - nothing to update');
+ }
+ }
+}
diff --git a/core/Command/User/Welcome.php b/core/Command/User/Welcome.php
new file mode 100644
index 00000000000..65637759689
--- /dev/null
+++ b/core/Command/User/Welcome.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 FedericoHeichou <federicoheichou@gmail.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\User;
+
+use OC\Core\Command\Base;
+use OCA\Settings\Mailer\NewUserMailHelper;
+use OCP\IUserManager;
+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 Welcome extends Base {
+ /**
+ * @param IUserManager $userManager
+ * @param NewUserMailHelper $newUserMailHelper
+ */
+ public function __construct(
+ protected IUserManager $userManager,
+ private NewUserMailHelper $newUserMailHelper,
+ ) {
+ parent::__construct();
+ }
+
+ /**
+ * @return void
+ */
+ protected function configure() {
+ $this
+ ->setName('user:welcome')
+ ->setDescription('Sends the welcome email')
+ ->addArgument(
+ 'user',
+ InputArgument::REQUIRED,
+ 'The user to send the email to'
+ )
+ ->addOption(
+ 'reset-password',
+ 'r',
+ InputOption::VALUE_NONE,
+ 'Add the reset password link to the email'
+ )
+ ;
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $userId = $input->getArgument('user');
+ // check if user exists
+ $user = $this->userManager->get($userId);
+ if ($user === null) {
+ $output->writeln('<error>User does not exist</error>');
+ return 1;
+ }
+ $email = $user->getEMailAddress();
+ if ($email === '' || $email === null) {
+ $output->writeln('<error>User does not have an email address</error>');
+ return 1;
+ }
+ try {
+ $emailTemplate = $this->newUserMailHelper->generateTemplate($user, $input->getOption('reset-password'));
+ $this->newUserMailHelper->sendMail($user, $emailTemplate);
+ } catch (\Exception $e) {
+ $output->writeln('<error>Failed to send email: ' . $e->getMessage() . '</error>');
+ return 1;
+ }
+ return 0;
+ }
+}