aboutsummaryrefslogtreecommitdiffstats
path: root/core/Command
diff options
context:
space:
mode:
Diffstat (limited to 'core/Command')
-rw-r--r--core/Command/App/Disable.php1
-rw-r--r--core/Command/App/ListApps.php1
-rw-r--r--core/Command/App/Update.php3
-rw-r--r--core/Command/Background/Job.php8
-rw-r--r--core/Command/Background/JobBase.php9
-rw-r--r--core/Command/Broadcast/Test.php13
-rw-r--r--core/Command/Config/App/Base.php2
-rw-r--r--core/Command/Config/App/SetConfig.php25
-rw-r--r--core/Command/Config/ListConfigs.php7
-rw-r--r--core/Command/Config/System/Base.php1
-rw-r--r--core/Command/Config/System/CastHelper.php76
-rw-r--r--core/Command/Config/System/SetConfig.php77
-rw-r--r--core/Command/Db/ConvertFilecacheBigInt.php1
-rw-r--r--core/Command/Db/ConvertMysqlToMB4.php1
-rw-r--r--core/Command/Db/ConvertType.php6
-rw-r--r--core/Command/Db/Migrations/ExecuteCommand.php1
-rw-r--r--core/Command/Db/Migrations/GenerateCommand.php9
-rw-r--r--core/Command/Encryption/ChangeKeyStorageRoot.php8
-rw-r--r--core/Command/Encryption/EncryptAll.php1
-rw-r--r--core/Command/Encryption/ListModules.php2
-rw-r--r--core/Command/Encryption/MigrateKeyStorage.php16
-rw-r--r--core/Command/Group/AddUser.php1
-rw-r--r--core/Command/Group/ListCommand.php1
-rw-r--r--core/Command/Group/RemoveUser.php1
-rw-r--r--core/Command/Info/File.php3
-rw-r--r--core/Command/Info/FileUtils.php108
-rw-r--r--core/Command/Info/Storage.php49
-rw-r--r--core/Command/Info/Storages.php43
-rw-r--r--core/Command/Integrity/CheckApp.php59
-rw-r--r--core/Command/Log/File.php5
-rw-r--r--core/Command/Maintenance/Install.php14
-rw-r--r--core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php14
-rw-r--r--core/Command/Maintenance/Mimetype/UpdateJS.php3
-rw-r--r--core/Command/Maintenance/UpdateHtaccess.php3
-rw-r--r--core/Command/Maintenance/UpdateTheme.php1
-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/Preview/Repair.php2
-rw-r--r--core/Command/Router/ListRoutes.php129
-rw-r--r--core/Command/Router/MatchRoute.php100
-rw-r--r--core/Command/SetupChecks.php12
-rw-r--r--core/Command/SystemTag/Add.php1
-rw-r--r--core/Command/SystemTag/Delete.php1
-rw-r--r--core/Command/SystemTag/Edit.php1
-rw-r--r--core/Command/SystemTag/ListCommand.php1
-rw-r--r--core/Command/TaskProcessing/EnabledCommand.php7
-rw-r--r--core/Command/TaskProcessing/GetCommand.php1
-rw-r--r--core/Command/TaskProcessing/ListCommand.php1
-rw-r--r--core/Command/TaskProcessing/Statistics.php1
-rw-r--r--core/Command/TwoFactorAuth/Base.php1
-rw-r--r--core/Command/Upgrade.php43
-rw-r--r--core/Command/User/Add.php6
-rw-r--r--core/Command/User/AuthTokens/Add.php4
-rw-r--r--core/Command/User/AuthTokens/Delete.php1
-rw-r--r--core/Command/User/AuthTokens/ListCommand.php1
-rw-r--r--core/Command/User/Info.php4
-rw-r--r--core/Command/User/LastSeen.php2
-rw-r--r--core/Command/User/ListCommand.php1
-rw-r--r--core/Command/User/Profile.php234
-rw-r--r--core/Command/User/ResetPassword.php6
-rw-r--r--core/Command/User/SyncAccountDataCommand.php10
-rw-r--r--core/Command/User/Welcome.php14
64 files changed, 1126 insertions, 218 deletions
diff --git a/core/Command/App/Disable.php b/core/Command/App/Disable.php
index a0a20ef21dd..121ad3f010c 100644
--- a/core/Command/App/Disable.php
+++ b/core/Command/App/Disable.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
diff --git a/core/Command/App/ListApps.php b/core/Command/App/ListApps.php
index bb59e441119..dc947bea55f 100644
--- a/core/Command/App/ListApps.php
+++ b/core/Command/App/ListApps.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
diff --git a/core/Command/App/Update.php b/core/Command/App/Update.php
index b2d02e222de..71c7f84e5b0 100644
--- a/core/Command/App/Update.php
+++ b/core/Command/App/Update.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
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;
@@ -64,7 +65,7 @@ class Update extends Command {
$apps = [$singleAppId];
try {
$this->manager->getAppPath($singleAppId);
- } catch (\OCP\App\AppPathNotFoundException $e) {
+ } catch (AppPathNotFoundException $e) {
$output->writeln($singleAppId . ' not installed');
return 1;
}
diff --git a/core/Command/Background/Job.php b/core/Command/Background/Job.php
index 7fa005cf231..9a862f5a13a 100644
--- a/core/Command/Background/Job.php
+++ b/core/Command/Background/Job.php
@@ -10,6 +10,8 @@ namespace OC\Core\Command\Background;
use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\IJobList;
+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;
@@ -75,7 +77,7 @@ class Job extends Command {
$output->writeln('<info>Job executed!</info>');
$output->writeln('');
- if ($job instanceof \OCP\BackgroundJob\TimedJob) {
+ if ($job instanceof TimedJob) {
$this->printJobInfo($jobId, $job, $output);
}
} else {
@@ -99,10 +101,10 @@ class Job extends Command {
$output->writeln('Job class: ' . get_class($job));
$output->writeln('Arguments: ' . json_encode($job->getArgument()));
- $isTimedJob = $job instanceof \OCP\BackgroundJob\TimedJob;
+ $isTimedJob = $job instanceof TimedJob;
if ($isTimedJob) {
$output->writeln('Type: timed');
- } elseif ($job instanceof \OCP\BackgroundJob\QueuedJob) {
+ } elseif ($job instanceof QueuedJob) {
$output->writeln('Type: queued');
} else {
$output->writeln('Type: job');
diff --git a/core/Command/Background/JobBase.php b/core/Command/Background/JobBase.php
index d92bb77d4b6..81d16f874eb 100644
--- a/core/Command/Background/JobBase.php
+++ b/core/Command/Background/JobBase.php
@@ -10,12 +10,15 @@ declare(strict_types=1);
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 \OC\Core\Command\Base {
+abstract class JobBase extends Base {
public function __construct(
protected IJobList $jobList,
@@ -41,10 +44,10 @@ abstract class JobBase extends \OC\Core\Command\Base {
$output->writeln('Job class: ' . get_class($job));
$output->writeln('Arguments: ' . json_encode($job->getArgument()));
- $isTimedJob = $job instanceof \OCP\BackgroundJob\TimedJob;
+ $isTimedJob = $job instanceof TimedJob;
if ($isTimedJob) {
$output->writeln('Type: timed');
- } elseif ($job instanceof \OCP\BackgroundJob\QueuedJob) {
+ } elseif ($job instanceof QueuedJob) {
$output->writeln('Type: queued');
} else {
$output->writeln('Type: job');
diff --git a/core/Command/Broadcast/Test.php b/core/Command/Broadcast/Test.php
index fb80ce26ff0..eb8b49bc3ee 100644
--- a/core/Command/Broadcast/Test.php
+++ b/core/Command/Broadcast/Test.php
@@ -44,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/Config/App/Base.php b/core/Command/Config/App/Base.php
index 07341c4faf9..e90a8e78f5b 100644
--- a/core/Command/Config/App/Base.php
+++ b/core/Command/Config/App/Base.php
@@ -7,12 +7,14 @@ declare(strict_types=1);
*/
namespace OC\Core\Command\Config\App;
+use OC\Config\ConfigManager;
use OCP\IAppConfig;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
abstract class Base extends \OC\Core\Command\Base {
public function __construct(
protected IAppConfig $appConfig,
+ protected readonly ConfigManager $configManager,
) {
parent::__construct();
}
diff --git a/core/Command/Config/App/SetConfig.php b/core/Command/Config/App/SetConfig.php
index 345067cfd45..1f4ab81bf05 100644
--- a/core/Command/Config/App/SetConfig.php
+++ b/core/Command/Config/App/SetConfig.php
@@ -9,7 +9,6 @@ declare(strict_types=1);
namespace OC\Core\Command\Config\App;
use OC\AppConfig;
-use OCP\Exceptions\AppConfigIncorrectTypeException;
use OCP\Exceptions\AppConfigUnknownKeyException;
use OCP\IAppConfig;
use Symfony\Component\Console\Helper\QuestionHelper;
@@ -161,7 +160,6 @@ class SetConfig extends Base {
}
$value = (string)$input->getOption('value');
-
switch ($type) {
case IAppConfig::VALUE_MIXED:
$updated = $this->appConfig->setValueMixed($appName, $configName, $value, $lazy, $sensitive);
@@ -172,34 +170,19 @@ class SetConfig extends Base {
break;
case IAppConfig::VALUE_INT:
- if ($value !== ((string)((int)$value))) {
- throw new AppConfigIncorrectTypeException('Value is not an integer');
- }
- $updated = $this->appConfig->setValueInt($appName, $configName, (int)$value, $lazy, $sensitive);
+ $updated = $this->appConfig->setValueInt($appName, $configName, $this->configManager->convertToInt($value), $lazy, $sensitive);
break;
case IAppConfig::VALUE_FLOAT:
- if ($value !== ((string)((float)$value))) {
- throw new AppConfigIncorrectTypeException('Value is not a float');
- }
- $updated = $this->appConfig->setValueFloat($appName, $configName, (float)$value, $lazy, $sensitive);
+ $updated = $this->appConfig->setValueFloat($appName, $configName, $this->configManager->convertToFloat($value), $lazy, $sensitive);
break;
case IAppConfig::VALUE_BOOL:
- if (in_array(strtolower($value), ['true', '1', 'on', 'yes'])) {
- $valueBool = true;
- } elseif (in_array(strtolower($value), ['false', '0', 'off', 'no'])) {
- $valueBool = false;
- } else {
- throw new AppConfigIncorrectTypeException('Value is not a boolean, please use \'true\' or \'false\'');
- }
- $updated = $this->appConfig->setValueBool($appName, $configName, $valueBool, $lazy);
+ $updated = $this->appConfig->setValueBool($appName, $configName, $this->configManager->convertToBool($value), $lazy);
break;
case IAppConfig::VALUE_ARRAY:
- $valueArray = json_decode($value, true, flags: JSON_THROW_ON_ERROR);
- $valueArray = (is_array($valueArray)) ? $valueArray : throw new AppConfigIncorrectTypeException('Value is not an array');
- $updated = $this->appConfig->setValueArray($appName, $configName, $valueArray, $lazy, $sensitive);
+ $updated = $this->appConfig->setValueArray($appName, $configName, $this->configManager->convertToArray($value), $lazy, $sensitive);
break;
}
}
diff --git a/core/Command/Config/ListConfigs.php b/core/Command/Config/ListConfigs.php
index 094348dd9ba..b81bfbf4d18 100644
--- a/core/Command/Config/ListConfigs.php
+++ b/core/Command/Config/ListConfigs.php
@@ -7,6 +7,7 @@
*/
namespace OC\Core\Command\Config;
+use OC\Config\ConfigManager;
use OC\Core\Command\Base;
use OC\SystemConfig;
use OCP\IAppConfig;
@@ -22,6 +23,7 @@ class ListConfigs extends Base {
public function __construct(
protected SystemConfig $systemConfig,
protected IAppConfig $appConfig,
+ protected ConfigManager $configManager,
) {
parent::__construct();
}
@@ -44,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')
;
}
@@ -51,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;
diff --git a/core/Command/Config/System/Base.php b/core/Command/Config/System/Base.php
index ce39cd4c95b..088d902b4fd 100644
--- a/core/Command/Config/System/Base.php
+++ b/core/Command/Config/System/Base.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
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/SetConfig.php b/core/Command/Config/System/SetConfig.php
index 62ab7f7120f..1b1bdc66a6e 100644
--- a/core/Command/Config/System/SetConfig.php
+++ b/core/Command/Config/System/SetConfig.php
@@ -17,6 +17,7 @@ use Symfony\Component\Console\Output\OutputInterface;
class SetConfig extends Base {
public function __construct(
SystemConfig $systemConfig,
+ private CastHelper $castHelper,
) {
parent::__construct($systemConfig);
}
@@ -57,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) {
@@ -81,80 +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' => (float)$value,
- 'readable-value' => 'double ' . (float)$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,
- ];
-
- case 'json':
- $value = json_decode($value, true);
- return [
- 'value' => $value,
- 'readable-value' => 'json ' . json_encode($value),
- ];
-
- default:
- throw new \InvalidArgumentException('Invalid type');
- }
- }
-
- /**
* @param array $configNames
* @param mixed $existingValues
* @param mixed $value
diff --git a/core/Command/Db/ConvertFilecacheBigInt.php b/core/Command/Db/ConvertFilecacheBigInt.php
index f5028aacaef..0d96d139701 100644
--- a/core/Command/Db/ConvertFilecacheBigInt.php
+++ b/core/Command/Db/ConvertFilecacheBigInt.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/Db/ConvertMysqlToMB4.php b/core/Command/Db/ConvertMysqlToMB4.php
index 8a2abecc804..926e56c4300 100644
--- a/core/Command/Db/ConvertMysqlToMB4.php
+++ b/core/Command/Db/ConvertMysqlToMB4.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 ownCloud GmbH
* SPDX-License-Identifier: AGPL-3.0-only
diff --git a/core/Command/Db/ConvertType.php b/core/Command/Db/ConvertType.php
index b5d1b9b9330..bca41407f68 100644
--- a/core/Command/Db/ConvertType.php
+++ b/core/Command/Db/ConvertType.php
@@ -13,9 +13,11 @@ use Doctrine\DBAL\Schema\Table;
use OC\DB\Connection;
use OC\DB\ConnectionFactory;
use OC\DB\MigrationService;
+use OC\DB\PgSqlTools;
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;
@@ -159,7 +161,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')) {
@@ -401,7 +403,7 @@ class ConvertType extends Command implements CompletionAwareInterface {
$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
diff --git a/core/Command/Db/Migrations/ExecuteCommand.php b/core/Command/Db/Migrations/ExecuteCommand.php
index cb6edd7c78c..a89072c1ad1 100644
--- a/core/Command/Db/Migrations/ExecuteCommand.php
+++ b/core/Command/Db/Migrations/ExecuteCommand.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2017 ownCloud GmbH
diff --git a/core/Command/Db/Migrations/GenerateCommand.php b/core/Command/Db/Migrations/GenerateCommand.php
index ed29412f00b..a75280fa8b1 100644
--- a/core/Command/Db/Migrations/GenerateCommand.php
+++ b/core/Command/Db/Migrations/GenerateCommand.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2017 ownCloud GmbH
@@ -22,8 +23,8 @@ 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);
@@ -38,6 +39,7 @@ use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
+use Override;
/**
* FIXME Auto-generated migration step: Please modify to your needs!
@@ -49,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 {
}
@@ -58,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}}
}
@@ -67,6 +71,7 @@ class {{classname}} extends SimpleMigrationStep {
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
*/
+ #[Override]
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
}
}
diff --git a/core/Command/Encryption/ChangeKeyStorageRoot.php b/core/Command/Encryption/ChangeKeyStorageRoot.php
index 76cde1b8e77..3049fd2ca08 100644
--- a/core/Command/Encryption/ChangeKeyStorageRoot.php
+++ b/core/Command/Encryption/ChangeKeyStorageRoot.php
@@ -123,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');
}
@@ -183,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/EncryptAll.php b/core/Command/Encryption/EncryptAll.php
index 684591f4586..f2c991471b6 100644
--- a/core/Command/Encryption/EncryptAll.php
+++ b/core/Command/Encryption/EncryptAll.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/core/Command/Encryption/ListModules.php b/core/Command/Encryption/ListModules.php
index b1f35b05bc9..bf02c29f432 100644
--- a/core/Command/Encryption/ListModules.php
+++ b/core/Command/Encryption/ListModules.php
@@ -57,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 c2090d22d1c..937b17cde5f 100644
--- a/core/Command/Encryption/MigrateKeyStorage.php
+++ b/core/Command/Encryption/MigrateKeyStorage.php
@@ -80,10 +80,10 @@ 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);
@@ -127,10 +127,10 @@ 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);
diff --git a/core/Command/Group/AddUser.php b/core/Command/Group/AddUser.php
index 1f144b13893..999113390af 100644
--- a/core/Command/Group/AddUser.php
+++ b/core/Command/Group/AddUser.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/Group/ListCommand.php b/core/Command/Group/ListCommand.php
index a3622585d18..01522a23f7f 100644
--- a/core/Command/Group/ListCommand.php
+++ b/core/Command/Group/ListCommand.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/Group/RemoveUser.php b/core/Command/Group/RemoveUser.php
index 7c58f9ac4c4..952fc6e7712 100644
--- a/core/Command/Group/RemoveUser.php
+++ b/core/Command/Group/RemoveUser.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/Info/File.php b/core/Command/Info/File.php
index c21c2e666fb..287bd0e29cb 100644
--- a/core/Command/Info/File.php
+++ b/core/Command/Info/File.php
@@ -9,6 +9,7 @@ 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;
@@ -176,7 +177,7 @@ class File extends Command {
if ($input->getOption('storage-tree')) {
$storageTmp = $storage;
$storageClass = get_class($storageTmp) . ' (cache:' . get_class($storageTmp->getCache()) . ')';
- while ($storageTmp instanceof \OC\Files\Storage\Wrapper\Wrapper) {
+ while ($storageTmp instanceof Wrapper) {
$storageTmp = $storageTmp->getWrapperStorage();
$storageClass .= "\n\t" . '> ' . get_class($storageTmp) . ' (cache:' . get_class($storageTmp->getCache()) . ')';
}
diff --git a/core/Command/Info/FileUtils.php b/core/Command/Info/FileUtils.php
index 5de5f5fcaa6..bc07535a289 100644
--- a/core/Command/Info/FileUtils.php
+++ b/core/Command/Info/FileUtils.php
@@ -8,11 +8,13 @@ declare(strict_types=1);
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;
@@ -21,22 +23,28 @@ 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 \OCP\Files\NotPermittedException
- * @throws \OC\User\NoUserException
+ * @throws NotPermittedException
+ * @throws NoUserException
*/
public function getFilesByUser(FileInfo $file): array {
$id = $file->getId();
@@ -218,4 +226,100 @@ class FileUtils {
}
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/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 e1889a35cfe..0145a3f8070 100644
--- a/core/Command/Integrity/CheckApp.php
+++ b/core/Command/Integrity/CheckApp.php
@@ -40,31 +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');
- if ($path === '') {
- $path = $this->appLocator->getAppPath($appid);
+ if ($input->getOption('all') && $input->getArgument('appid')) {
+ $output->writeln('<error>Option "--all" cannot be combined with an appid</error>');
+ return 1;
}
- 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);
- $this->writeArrayInOutputFormat($input, $output, $result);
- if (count($result) > 0) {
- $output->writeln('<error>' . count($result) . ' errors found</error>', OutputInterface::VERBOSITY_VERBOSE);
- 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;
+ }
+
+ 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);
- } else {
- $output->writeln('<comment>App signature not found, skipping app integrity check</comment>');
+ return 0;
}
- return 0;
+
+ return 1;
}
}
diff --git a/core/Command/Log/File.php b/core/Command/Log/File.php
index 8b4a38db611..ba5dad956e9 100644
--- a/core/Command/Log/File.php
+++ b/core/Command/Log/File.php
@@ -8,6 +8,7 @@
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;
@@ -61,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;
}
@@ -87,7 +88,7 @@ class File extends Command implements Completion\CompletionAwareInterface {
$rotateSize = $this->config->getSystemValue('log_rotate_size', 100 * 1024 * 1024);
if ($rotateSize) {
- $rotateString = \OCP\Util::humanFileSize($rotateSize);
+ $rotateString = Util::humanFileSize($rotateSize);
} else {
$rotateString = 'disabled';
}
diff --git a/core/Command/Maintenance/Install.php b/core/Command/Maintenance/Install.php
index d23a8d30070..6170c5a2638 100644
--- a/core/Command/Maintenance/Install.php
+++ b/core/Command/Maintenance/Install.php
@@ -15,6 +15,7 @@ use OC\Console\TimestampFormatter;
use OC\Migration\ConsoleOutput;
use OC\Setup;
use OC\SystemConfig;
+use OCP\Server;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
@@ -43,6 +44,7 @@ class Install extends Command {
->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('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')
@@ -51,15 +53,15 @@ class Install extends Command {
protected function execute(InputInterface $input, OutputInterface $output): int {
// validate the environment
- $setupHelper = \OCP\Server::get(\OC\Setup::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;
}
}
@@ -119,6 +121,7 @@ class Install extends Command {
if ($input->hasParameterOption('--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');
@@ -141,7 +144,7 @@ class Install extends Command {
}
}
- 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 . '>?');
@@ -150,7 +153,7 @@ class Install extends Command {
$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 . '>.');
}
@@ -160,6 +163,7 @@ class Install extends Command {
'dbpass' => $dbPass,
'dbname' => $dbName,
'dbhost' => $dbHost,
+ 'admindisable' => $disableAdminUser,
'adminlogin' => $adminLogin,
'adminpass' => $adminPassword,
'adminemail' => $adminEmail,
diff --git a/core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php b/core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php
index 5b598d20672..f8f19a61993 100644
--- a/core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php
+++ b/core/Command/Maintenance/Mimetype/GenerateMimetypeFileBuilder.php
@@ -15,7 +15,7 @@ class GenerateMimetypeFileBuilder {
* @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
@@ -71,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
@@ -83,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/UpdateJS.php b/core/Command/Maintenance/Mimetype/UpdateJS.php
index 35633f16355..2132ff54c6d 100644
--- a/core/Command/Maintenance/Mimetype/UpdateJS.php
+++ b/core/Command/Maintenance/Mimetype/UpdateJS.php
@@ -32,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/UpdateHtaccess.php b/core/Command/Maintenance/UpdateHtaccess.php
index 4939e368e2d..eeff3bf8c62 100644
--- a/core/Command/Maintenance/UpdateHtaccess.php
+++ b/core/Command/Maintenance/UpdateHtaccess.php
@@ -7,6 +7,7 @@
*/
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;
@@ -19,7 +20,7 @@ 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 {
diff --git a/core/Command/Maintenance/UpdateTheme.php b/core/Command/Maintenance/UpdateTheme.php
index f819b9c8e58..3fbcb546cca 100644
--- a/core/Command/Maintenance/UpdateTheme.php
+++ b/core/Command/Maintenance/UpdateTheme.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
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/Preview/Repair.php b/core/Command/Preview/Repair.php
index 3ccd8231300..a92a4cf8ed0 100644
--- a/core/Command/Preview/Repair.php
+++ b/core/Command/Preview/Repair.php
@@ -86,7 +86,7 @@ class Repair extends Command {
$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();
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/SetupChecks.php b/core/Command/SetupChecks.php
index 60517e224b3..6ef67726839 100644
--- a/core/Command/SetupChecks.php
+++ b/core/Command/SetupChecks.php
@@ -61,12 +61,12 @@ class SetupChecks extends Base {
$description = $this->richTextFormatter->richToParsed($description, $descriptionParameters);
}
$output->writeln(
- "\t\t" .
- ($styleTag !== null ? "<{$styleTag}>" : '') .
- "{$emoji} " .
- ($check->getName() ?? $check::class) .
- ($description !== null ? ': ' . $description : '') .
- ($styleTag !== null ? "</{$styleTag}>" : ''),
+ "\t\t"
+ . ($styleTag !== null ? "<{$styleTag}>" : '')
+ . "{$emoji} "
+ . ($check->getName() ?? $check::class)
+ . ($description !== null ? ': ' . $description : '')
+ . ($styleTag !== null ? "</{$styleTag}>" : ''),
$verbosity
);
}
diff --git a/core/Command/SystemTag/Add.php b/core/Command/SystemTag/Add.php
index 92ed42c37bc..df8b507b07d 100644
--- a/core/Command/SystemTag/Add.php
+++ b/core/Command/SystemTag/Add.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/SystemTag/Delete.php b/core/Command/SystemTag/Delete.php
index 73b3dc8187a..f657f4473ab 100644
--- a/core/Command/SystemTag/Delete.php
+++ b/core/Command/SystemTag/Delete.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/SystemTag/Edit.php b/core/Command/SystemTag/Edit.php
index 614f2798ce4..09c662e58e9 100644
--- a/core/Command/SystemTag/Edit.php
+++ b/core/Command/SystemTag/Edit.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/SystemTag/ListCommand.php b/core/Command/SystemTag/ListCommand.php
index 836869f157d..2c6435d6faf 100644
--- a/core/Command/SystemTag/ListCommand.php
+++ b/core/Command/SystemTag/ListCommand.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/TaskProcessing/EnabledCommand.php b/core/Command/TaskProcessing/EnabledCommand.php
index b382de12a81..7195d19a7a4 100644
--- a/core/Command/TaskProcessing/EnabledCommand.php
+++ b/core/Command/TaskProcessing/EnabledCommand.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -47,15 +48,15 @@ class EnabledCommand extends Base {
} else {
$taskTypeSettings = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
}
-
+
$taskTypeSettings[$taskType] = $enabled;
-
+
$this->config->setAppValue('core', 'ai.taskprocessing_type_preferences', json_encode($taskTypeSettings));
$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
index a61ddbe1621..5c4fd17f2f8 100644
--- a/core/Command/TaskProcessing/GetCommand.php
+++ b/core/Command/TaskProcessing/GetCommand.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/TaskProcessing/ListCommand.php b/core/Command/TaskProcessing/ListCommand.php
index f4ea76729d9..81eb258d35d 100644
--- a/core/Command/TaskProcessing/ListCommand.php
+++ b/core/Command/TaskProcessing/ListCommand.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/TaskProcessing/Statistics.php b/core/Command/TaskProcessing/Statistics.php
index a3dc9ee0254..86478b34db1 100644
--- a/core/Command/TaskProcessing/Statistics.php
+++ b/core/Command/TaskProcessing/Statistics.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/TwoFactorAuth/Base.php b/core/Command/TwoFactorAuth/Base.php
index 70e33bfd23c..034ea36afca 100644
--- a/core/Command/TwoFactorAuth/Base.php
+++ b/core/Command/TwoFactorAuth/Base.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/Upgrade.php b/core/Command/Upgrade.php
index 6220c9a70d4..c3d6aacc714 100644
--- a/core/Command/Upgrade.php
+++ b/core/Command/Upgrade.php
@@ -21,6 +21,7 @@ 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;
@@ -63,11 +64,11 @@ class Upgrade extends Command {
}
$self = $this;
- $updater = \OCP\Server::get(Updater::class);
+ $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 {
@@ -132,17 +133,17 @@ 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>';
} else {
@@ -150,42 +151,42 @@ class Upgrade extends Command {
}
$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, &$incompatibleOverwrites) {
+ $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) {
+ $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) {
+ $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) {
+ $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) {
+ $updater->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use ($output): void {
$output->writeln('<info>Finished code integrity check</info>');
});
@@ -226,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 033d2bdc9a2..4de4e247991 100644
--- a/core/Command/User/Add.php
+++ b/core/Command/User/Add.php
@@ -52,7 +52,7 @@ class Add extends Command {
'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',
@@ -91,10 +91,10 @@ class Add extends Command {
// 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')) {
diff --git a/core/Command/User/AuthTokens/Add.php b/core/Command/User/AuthTokens/Add.php
index ad4bf732bd0..89b20535c63 100644
--- a/core/Command/User/AuthTokens/Add.php
+++ b/core/Command/User/AuthTokens/Add.php
@@ -62,9 +62,9 @@ class Add extends Command {
}
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()) {
diff --git a/core/Command/User/AuthTokens/Delete.php b/core/Command/User/AuthTokens/Delete.php
index f2c75a8ad99..2047d2eae2a 100644
--- a/core/Command/User/AuthTokens/Delete.php
+++ b/core/Command/User/AuthTokens/Delete.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/User/AuthTokens/ListCommand.php b/core/Command/User/AuthTokens/ListCommand.php
index 1ebd4a0f0b4..b36aa717505 100644
--- a/core/Command/User/AuthTokens/ListCommand.php
+++ b/core/Command/User/AuthTokens/ListCommand.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/core/Command/User/Info.php b/core/Command/User/Info.php
index 6487b533fa2..e7fc9286e74 100644
--- a/core/Command/User/Info.php
+++ b/core/Command/User/Info.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -6,6 +7,7 @@
namespace OC\Core\Command\User;
use OC\Core\Command\Base;
+use OCP\Files\NotFoundException;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserManager;
@@ -84,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/LastSeen.php b/core/Command/User/LastSeen.php
index dbb611a4fff..984def72cd6 100644
--- a/core/Command/User/LastSeen.php
+++ b/core/Command/User/LastSeen.php
@@ -68,7 +68,7 @@ class LastSeen extends Base {
return 1;
}
- $this->userManager->callForAllUsers(static function (IUser $user) use ($output) {
+ $this->userManager->callForAllUsers(static function (IUser $user) use ($output): void {
$lastLogin = $user->getLastLogin();
if ($lastLogin === 0) {
$output->writeln($user->getUID() . ' has never logged in.');
diff --git a/core/Command/User/ListCommand.php b/core/Command/User/ListCommand.php
index e7fb3de71f0..66b831c793b 100644
--- a/core/Command/User/ListCommand.php
+++ b/core/Command/User/ListCommand.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
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/ResetPassword.php b/core/Command/User/ResetPassword.php
index 2f18c3d473e..0e8b1325770 100644
--- a/core/Command/User/ResetPassword.php
+++ b/core/Command/User/ResetPassword.php
@@ -41,7 +41,7 @@ class ResetPassword extends Base {
'password-from-env',
null,
InputOption::VALUE_NONE,
- 'read password from environment variable OC_PASS'
+ 'read password from environment variable NC_PASS/OC_PASS'
)
;
}
@@ -56,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()) {
diff --git a/core/Command/User/SyncAccountDataCommand.php b/core/Command/User/SyncAccountDataCommand.php
index 640b66581e1..c353df6fe9f 100644
--- a/core/Command/User/SyncAccountDataCommand.php
+++ b/core/Command/User/SyncAccountDataCommand.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -16,15 +17,10 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class SyncAccountDataCommand extends Base {
- protected IUserManager $userManager;
- protected IAccountManager $accountManager;
-
public function __construct(
- IUserManager $userManager,
- IAccountManager $accountManager,
+ protected IUserManager $userManager,
+ protected IAccountManager $accountManager,
) {
- $this->userManager = $userManager;
- $this->accountManager = $accountManager;
parent::__construct();
}
diff --git a/core/Command/User/Welcome.php b/core/Command/User/Welcome.php
index c383811f982..65637759689 100644
--- a/core/Command/User/Welcome.php
+++ b/core/Command/User/Welcome.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2023 FedericoHeichou <federicoheichou@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -15,24 +16,15 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Welcome extends Base {
- /** @var IUserManager */
- protected $userManager;
-
- /** @var NewUserMailHelper */
- private $newUserMailHelper;
-
/**
* @param IUserManager $userManager
* @param NewUserMailHelper $newUserMailHelper
*/
public function __construct(
- IUserManager $userManager,
- NewUserMailHelper $newUserMailHelper,
+ protected IUserManager $userManager,
+ private NewUserMailHelper $newUserMailHelper,
) {
parent::__construct();
-
- $this->userManager = $userManager;
- $this->newUserMailHelper = $newUserMailHelper;
}
/**