diff --git a/appinfo/info.xml b/appinfo/info.xml
index 86fe64b6..ad8b87fe 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -46,6 +46,7 @@ This app allows users to easily migrate from one instance to another using an ex
+ OCA\UserMigration\Command\Manage
OCA\UserMigration\Command\Export
OCA\UserMigration\Command\Import
diff --git a/lib/Command/Export.php b/lib/Command/Export.php
index 018670a7..900f0352 100644
--- a/lib/Command/Export.php
+++ b/lib/Command/Export.php
@@ -10,8 +10,11 @@
namespace OCA\UserMigration\Command;
use OC\Core\Command\Base;
+use OCA\UserMigration\AppInfo\Application;
use OCA\UserMigration\ExportDestination;
use OCA\UserMigration\Service\UserMigrationService;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IConfig;
use OCP\IUser;
use OCP\IUserManager;
use OCP\UserMigration\IMigrator;
@@ -26,6 +29,8 @@ class Export extends Base {
public function __construct(
private IUserManager $userManager,
private UserMigrationService $migrationService,
+ private IConfig $config,
+ private ITimeFactory $timeFactory,
) {
parent::__construct();
}
@@ -173,6 +178,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if (rename($path, $finalPath) === false) {
throw new \Exception('Failed to rename ' . basename($path) . ' to ' . basename($finalPath));
}
+ $this->config->setUserValue($user->getUID(), Application::APP_ID, 'lastExport', (string)$this->timeFactory->getTime());
$io->writeln("Export saved in $finalPath");
} catch (\Exception $e) {
if ($io->isDebug()) {
diff --git a/lib/Command/Import.php b/lib/Command/Import.php
index e4d9d353..28f474c1 100644
--- a/lib/Command/Import.php
+++ b/lib/Command/Import.php
@@ -9,8 +9,10 @@
namespace OCA\UserMigration\Command;
+use OCA\UserMigration\AppInfo\Application;
use OCA\UserMigration\ImportSource;
use OCA\UserMigration\Service\UserMigrationService;
+use OCP\IConfig;
use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@@ -23,6 +25,7 @@ class Import extends Command {
public function __construct(
private IUserManager $userManager,
private UserMigrationService $migrationService,
+ private IConfig $config,
) {
parent::__construct();
}
@@ -73,6 +76,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$io->writeln("Importing from {$path}…");
$importSource = new ImportSource($path);
$this->migrationService->import($importSource, $user, $io);
+ /* Reset exported state of user after import */
+ $this->config->deleteUserValue($user->getUID(), Application::APP_ID, 'lastExport');
$io->writeln("Successfully imported from {$path}");
} catch (\Exception $e) {
if ($io->isDebug()) {
diff --git a/lib/Command/Manage.php b/lib/Command/Manage.php
new file mode 100644
index 00000000..c37a1002
--- /dev/null
+++ b/lib/Command/Manage.php
@@ -0,0 +1,137 @@
+setName('user_migration:manage')
+ ->setDescription('List users exported by the admin, delete them by batch')
+ ->addOption(
+ 'limit',
+ 'l',
+ InputOption::VALUE_REQUIRED,
+ 'Limit the number of listed users',
+ 100,
+ )
+ ->addOption(
+ 'since',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Filter by minimum export date',
+ )
+ ->addOption(
+ 'delete',
+ null,
+ InputOption::VALUE_NONE,
+ 'Delete the exported users',
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ if ((string)$input->getOption('since') !== '') {
+ $since = new \DateTime($input->getOption('since'));
+ $output->writeln('Since ' . $since->format(\DateTimeInterface::ATOM) . '');
+ } else {
+ $since = null;
+ }
+ $values = iterator_to_array($this->queryUsers((int)$input->getOption('limit'), $since));
+ $this->writeTableInOutputFormat($input, $output, $values);
+ if ($input->getOption('delete')) {
+ /** @var QuestionHelper $helper */
+ $helper = $this->getHelper('question');
+ $question = new ConfirmationQuestion('Please confirm to delete the above listed users [y/n]', !$input->isInteractive());
+
+ if (!$helper->ask($input, $output, $question)) {
+ $output->writeln('Deletion canceled');
+ return self::SUCCESS;
+ }
+ $errors = $this->deleteUsers(array_column($values, 'userid'), $output);
+ if ($errors > 0) {
+ return self::FAILURE;
+ }
+ }
+ return self::SUCCESS;
+ }
+
+ private function queryUsers(int $limit, ?\DateTime $since): \Generator {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->select('userid', 'configvalue')
+ ->from('preferences')
+ ->where($qb->expr()->eq('appid', $qb->createNamedParameter(Application::APP_ID)))
+ ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('lastExport')));
+
+ if ($since !== null) {
+ $qb->andWhere($qb->expr()->gte('configvalue', $qb->createNamedParameter($since->getTimestamp(), IQueryBuilder::PARAM_INT)));
+ }
+
+ $qb->orderBy('configvalue')
+ ->setMaxResults($limit);
+
+ $result = $qb->executeQuery();
+
+ while ($row = $result->fetch()) {
+ yield [
+ 'userid' => $row['userid'],
+ 'Last export' => date(\DateTimeInterface::ATOM, (int)$row['configvalue'])
+ ];
+ }
+
+ $result->closeCursor();
+ }
+
+ /**
+ * @param iterable $uids
+ */
+ private function deleteUsers(iterable $uids, OutputInterface $output): int {
+ $errors = 0;
+ foreach ($uids as $uid) {
+ $user = $this->userManager->get($uid);
+ if (is_null($user)) {
+ $output->writeln('User ' . $uid . ' does not exist');
+ $errors++;
+ continue;
+ }
+
+ if ($user->delete()) {
+ $output->writeln('User "' . $uid . '" was deleted');
+ } else {
+ $output->writeln('User "' . $uid . '" could not be deleted. Please check the logs.');
+ $errors++;
+ }
+ }
+ return $errors;
+ }
+}
diff --git a/tests/stubs/stub.phpstub b/tests/stubs/stub.phpstub
index 6a203ed5..c0eb2768 100644
--- a/tests/stubs/stub.phpstub
+++ b/tests/stubs/stub.phpstub
@@ -125,8 +125,9 @@ namespace OC\Cache {
namespace OC\Core\Command {
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+ use Symfony\Component\Console\Command\Command;
- class Base {
+ class Base extends Command {
public const OUTPUT_FORMAT_PLAIN = 'plain';
public const OUTPUT_FORMAT_JSON = 'json';
public const OUTPUT_FORMAT_JSON_PRETTY = 'json_pretty';
@@ -139,5 +140,6 @@ namespace OC\Core\Command {
public function setName(string $name) {}
public function getHelper(string $name) {}
protected function writeArrayInOutputFormat(InputInterface $input, OutputInterface $output, array $items, string $prefix = ' - ') {}
+ protected function writeTableInOutputFormat(InputInterface $input, OutputInterface $output, array $items): void {}
}
}