diff --git a/lib/Command/SyncMissingUsersToCommon.php b/lib/Command/SyncMissingUsersToCommon.php
index c4828dba780854654784081ea4bbd5411e0ab928..b506b67c969ea15bad912162345e0e31bf508429 100644
--- a/lib/Command/SyncMissingUsersToCommon.php
+++ b/lib/Command/SyncMissingUsersToCommon.php
@@ -93,6 +93,8 @@ class SyncMissingUsersToCommon extends Command {
'processed' => 0,
'success' => 0,
'errors' => 0,
+ 'skipped' => 0,
+ 'skip_reasons' => [],
'failed_reasons' => []
];
@@ -130,14 +132,35 @@ class SyncMissingUsersToCommon extends Command {
* Process all users from NextCloud
*/
private function processAllUsers(string $ipAddress, bool $isDryRun, OutputInterface $output, array &$stats): void {
- $this->userManager->callForAllUsers(function (IUser $user) use ($ipAddress, $isDryRun, $output, &$stats) {
+ $userCount = 0;
+ $ldapUsers = 0;
+ $nonLdapUsers = 0;
+
+ $output->writeln('Starting user enumeration...');
+
+ $this->userManager->callForAllUsers(function (IUser $user) use ($ipAddress, $isDryRun, $output, &$stats, &$userCount, &$ldapUsers, &$nonLdapUsers) {
+ $userCount++;
+ $backend = $user->getBackend();
+ $backendName = $backend->getBackendName();
+
+ if ($backendName === 'LDAP') {
+ $ldapUsers++;
+ } else {
+ $nonLdapUsers++;
+ }
+
+ // Show initial count after first user
+ if ($userCount === 1) {
+ $output->writeln(sprintf('Found %d+ users in NextCloud (counting during processing)', $userCount));
+ }
+
try {
$this->processSingleUser($user->getUID(), $ipAddress, $isDryRun, $output, $stats);
// Reduce output frequency for better performance
if ($stats['processed'] > 0 && $stats['processed'] % 1000 === 0) {
- $output->writeln(sprintf('Progress: %d processed, %d success, %d errors',
- $stats['processed'], $stats['success'], $stats['errors']));
+ $output->writeln(sprintf('Progress: %d processed, %d success, %d errors (Total: %d, LDAP: %d, Non-LDAP: %d)',
+ $stats['processed'], $stats['success'], $stats['errors'], $userCount, $ldapUsers, $nonLdapUsers));
}
} catch (Exception $e) {
$errorMessage = sprintf('Unexpected error processing user %s: %s', $user->getUID(), $e->getMessage());
@@ -149,6 +172,7 @@ class SyncMissingUsersToCommon extends Command {
// Set total after processing for all users
$stats['total'] = $stats['processed'];
+ $output->writeln(sprintf('Completed processing %d total users (LDAP: %d, Non-LDAP: %d)', $stats['total'], $ldapUsers, $nonLdapUsers));
}
/**
@@ -171,10 +195,33 @@ class SyncMissingUsersToCommon extends Command {
$usernameWithoutDomain = $this->userService->stripLegacyDomainFromUsername($username);
if ($this->userService->isUsernameTaken($usernameWithoutDomain)) {
// This is not an error, just a skip - user already exists
+ $skipReason = sprintf('User already exists in common database: %s', $usernameWithoutDomain);
+ $stats['skipped']++;
+ $stats['skip_reasons'][] = $skipReason;
+ if ($isDryRun) {
+ $output->writeln(sprintf(' Skipping user %s: %s', $username, $skipReason));
+ }
return;
}
- $this->syncUserToCommon($username, $ipAddress, $isDryRun, $output);
+ // Check if user is on LDAP backend
+ $backend = $user->getBackend();
+ $backendName = $backend->getBackendName();
+
+ if ($backendName === 'LDAP') {
+ // Full sync with LDAP metadata
+ $this->syncUserToCommon($username, $ipAddress, $isDryRun, $output, $stats);
+ } else {
+ // Skip non-LDAP users
+ $skipReason = sprintf('Non-LDAP user: %s', $username);
+ $stats['skipped']++;
+ $stats['skip_reasons'][] = $skipReason;
+ if ($isDryRun) {
+ $output->writeln(sprintf(' Skipping user %s: %s', $username, $skipReason));
+ }
+ return;
+ }
+
$stats['success']++;
} catch (Exception $e) {
$errorMessage = sprintf('Error processing user %s: %s', $username, $e->getMessage());
@@ -193,8 +240,27 @@ class SyncMissingUsersToCommon extends Command {
$output->writeln(sprintf('Total users to process: %d', $stats['total']));
$output->writeln(sprintf('Users processed: %d', $stats['processed']));
$output->writeln(sprintf('Successfully synced: %d', $stats['success']));
+ $output->writeln(sprintf('Users skipped: %d', $stats['skipped']));
$output->writeln(sprintf('Errors encountered: %d', $stats['errors']));
+ if (!empty($stats['skip_reasons'])) {
+ $output->writeln('');
+ $output->writeln('=== SKIP REASONS ===');
+
+ // Limit the number of reasons shown for performance
+ $maxReasonsToShow = 50;
+ $reasonsToShow = array_slice($stats['skip_reasons'], 0, $maxReasonsToShow);
+
+ foreach ($reasonsToShow as $reason) {
+ $output->writeln(sprintf('• %s', $reason));
+ }
+
+ if (count($stats['skip_reasons']) > $maxReasonsToShow) {
+ $remaining = count($stats['skip_reasons']) - $maxReasonsToShow;
+ $output->writeln(sprintf('... and %d more skips.', $remaining));
+ }
+ }
+
if (!empty($stats['failed_reasons'])) {
$output->writeln('');
$output->writeln('=== FAILURE REASONS ===');
@@ -225,9 +291,10 @@ class SyncMissingUsersToCommon extends Command {
* @param string $ipAddress The IP address to use
* @param bool $isDryRun Whether this is a dry run
* @param OutputInterface $output The output interface for writing messages
+ * @param array $stats The stats array to update
* @return void
*/
- private function syncUserToCommon(string $username, string $ipAddress, bool $isDryRun, OutputInterface $output): void {
+ private function syncUserToCommon(string $username, string $ipAddress, bool $isDryRun, OutputInterface $output, array &$stats): void {
// Only show processing message in dry run mode for performance
if ($isDryRun) {
$output->writeln(sprintf('Processing user: %s', $username));
@@ -240,8 +307,11 @@ class SyncMissingUsersToCommon extends Command {
// Check if user is on LDAP backend - skip if not
if (!$this->ldapConnectionService->isUserOnLDAPBackend($user)) {
// Skip users that are not on LDAP backend
+ $skipReason = sprintf('User not on LDAP backend: %s', $username);
+ $stats['skipped']++;
+ $stats['skip_reasons'][] = $skipReason;
if ($isDryRun) {
- $output->writeln(sprintf(' Skipping user %s: not on LDAP backend', $username));
+ $output->writeln(sprintf(' Skipping user %s: %s', $username, $skipReason));
}
return; // Skip this user gracefully
}
@@ -288,4 +358,43 @@ class SyncMissingUsersToCommon extends Command {
throw new Exception(sprintf("Failed to sync user %s: %s", $username, $e->getMessage()), 0, $e);
}
}
+
+ /**
+ * Sync a non-LDAP user to the common database (just username)
+ *
+ * @param string $username The username to sync
+ * @param string $ipAddress The IP address to use
+ * @param bool $isDryRun Whether this is a dry run
+ * @param OutputInterface $output The output interface for writing messages
+ * @param array $stats The stats array to update
+ * @return void
+ */
+ private function syncNonLdapUserToCommon(string $username, string $ipAddress, bool $isDryRun, OutputInterface $output, array &$stats): void {
+ // Only show processing message in dry run mode for performance
+ if ($isDryRun) {
+ $output->writeln(sprintf('Processing non-LDAP user: %s', $username));
+ }
+
+ try {
+ // Strip legacy domain from username
+ $usernameWithoutDomain = $this->userService->stripLegacyDomainFromUsername($username);
+
+ if (!$isDryRun) {
+ // Add user to common database with minimal data
+ $this->userService->addUsernameToCommonDataStore(
+ $usernameWithoutDomain,
+ $ipAddress,
+ '', // No recovery email for non-LDAP users
+ date('Y-m-d H:i:s') // Use current time formatted as YYYY-MM-DD HH:MM:SS
+ );
+ // Show success message for actual syncs
+ $output->writeln(sprintf(' ✓ Non-LDAP user synced successfully to common database: %s', $usernameWithoutDomain));
+ } else {
+ $output->writeln(sprintf(' Would sync non-LDAP user to common database: %s', $usernameWithoutDomain));
+ }
+ } catch (Exception $e) {
+ // Re-throw the exception to be caught by the caller
+ throw new Exception(sprintf("Failed to sync non-LDAP user %s: %s", $username, $e->getMessage()), 0, $e);
+ }
+ }
}