diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 787b180f..7c0c7a52 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -17,6 +17,7 @@ namespace OCA\SoftwareCatalog\AppInfo; +use OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -28,6 +29,7 @@ use OCA\OpenRegister\Event\ObjectLockedEvent; use OCA\OpenRegister\Event\ObjectUnlockedEvent; use OCA\OpenRegister\Event\ObjectRevertedEvent; +use OCP\IConfig; use OCP\IDBConnection; use OCP\IUserManager; use OCP\IGroupManager; @@ -95,7 +97,8 @@ public function register(IRegistrationContext $context): void $c, $c->get(IAppManager::class), $c->get(\Psr\Log\LoggerInterface::class), - $c->get(SymfonyEmailService::class) + $c->get(SymfonyEmailService::class), + $c->get(IConfig::class) ); }); @@ -182,7 +185,8 @@ public function register(IRegistrationContext $context): void $container->get(IAppConfig::class), $container->get('Psr\Log\LoggerInterface'), $container->get(SettingsService::class), - $container->get(IDBConnection::class) + $container->get(IDBConnection::class), + $container->get(ContactPersonHandler::class), ); }); diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index 383288f7..30e01ca9 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -21,7 +21,9 @@ use OCA\OpenRegister\Service\ObjectService; use OCA\SoftwareCatalog\Service\OrganisatieService; use OCA\SoftwareCatalog\Service\ContactpersoonService; +use OCA\SoftwareCatalog\Service\SoftwareCatalogue\ContactPersonHandler; use OCA\SoftwareCatalog\Service\SymfonyEmailService; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IAppConfig; use OCP\IDBConnection; use Psr\Log\LoggerInterface; @@ -100,6 +102,7 @@ public function __construct( LoggerInterface $logger, SettingsService $settingsService, private IDBConnection $db, + private readonly ContactPersonHandler $contactpersonHandler, ) { $this->organisatieService = $organisatieService; $this->contactpersoonService = $contactpersoonService; @@ -133,7 +136,7 @@ public function performOrganizationsSync(): array $qb = $this->db->getQueryBuilder(); $qb->select('o.uuid', $qb->createFunction('json_unquote(json_extract(o.object, \'$.status\')) as status'), 'o2.uuid as oreg_uuid', 'o2.active as active') - ->from('openregister_objects', 'o') + ->from(from: 'openregister_objects', alias: 'o') ->leftJoin(fromAlias:'o', join: 'openregister_organisations', alias: 'o2', condition: 'o.uuid = o2.uuid') ->where($qb->expr()->eq('o.schema', $qb->createNamedParameter($organizationSchema))) ->andWhere($qb->expr()->eq('o.register', $qb->createNamedParameter($register))) @@ -161,6 +164,96 @@ public function performOrganizationsSync(): array return $stats; } + public function performContactSync() :array + { + // Check configuration + $voorzieningenConfig = $this->settingsService->getVoorzieningenConfig(); + $register = $voorzieningenConfig['register'] ?? ''; +// $organizationSchema = $voorzieningenConfig['organisatie_schema'] ?? ''; + $contactSchema = $voorzieningenConfig['contactpersoon_schema'] ?? ''; + + $stats = [ + 'organizationsProcessed' => 0, + 'entitiesCreated' => 0, + 'entitiesUpdated' => 0, + 'contactPersonsProcessed' => 0, + 'usersCreated' => 0, + 'usersUpdated' => 0, + 'errors' => [], + 'startTime' => date('Y-m-d H:i:s'), + 'endTime' => null, + 'duration' => null + ]; + + $qb = $this->db->getQueryBuilder(); + + $qb->select( + 'o.uuid', + 'a.uid', + $qb->createFunction('json_unquote(json_extract(o.object, \'$.e-mailadres\')) as email'), + $qb->createFunction('json_unquote(json_extract(o.object, \'$.username\')) as username') + ) + ->from('openregister_objects', 'o') + ->leftJoin( + fromAlias: 'o', + join: 'accounts_data', + alias: 'a', + condition: 'json_unquote(json_extract(o.object, \'$.e-mailadres\')) = a.value') + ->where($qb->expr()->eq('o.register', $qb->createNamedParameter($register))) + ->andWhere($qb->expr()->eq('o.schema', $qb->createNamedParameter($contactSchema))) + ->andWhere($qb->expr()->isNull($qb->createFunction('json_unquote(json_extract(o.object, \'$.username\'))'))); + +// var_dump($qb->getSQL()); + $contacts = $qb->execute()->fetchAll(); + + foreach ($contacts as $contact) { + $objectService = \OC::$server->get('OCA\OpenRegister\Service\ObjectService'); + $contactEntity = $objectService->find($contact['uuid']); + $contactEntityObject = $contactEntity->getObject(); + + $contactEntityObject['username'] = $contact['uid']; + + if ($contact['uid'] === null) { + $user = $this->contactpersonHandler->createUserAccount($contactEntity); + $contactEntityObject['username'] = $user->getUID(); + } + + $contactEntity->setObject($contactEntityObject); + $objectService->saveObject(object: $contactEntity, register: $register, schema: $contactSchema); + + $stats['contactPersonsProcessed']++; + } + + return $stats; + } + + + public function performUserSync(): array + { + $voorzieningenConfig = $this->settingsService->getVoorzieningenConfig(); + $register = $voorzieningenConfig['register'] ?? ''; +// $organizationSchema = $voorzieningenConfig['organisatie_schema'] ?? ''; + $contactSchema = $voorzieningenConfig['contactpersoon_schema'] ?? ''; + + + $qb = $this->db->getQueryBuilder(); + $qb->select('o.uuid', $qb->createFunction('json_unquote(json_extract(`o`.`object`, \'$.username\')) as username'), $qb->createFunction('json_unquote(json_extract(o.object, \'$.organisatie\')) as organisation'), 'oo.users') + ->from(from:'openregister_objects', alias: 'o') + ->leftJoin(fromAlias: 'o', join: 'openregister_organisations', alias: 'oo', condition: 'oo.uuid = json_unquote(json_extract(o.object, \'$.organisatie\'))') + ->where($qb->expr()->eq('o.register', $qb->createNamedParameter($register))) + ->andWhere($qb->expr()->orX('o.schema', $qb->createNamedParameter($contactSchema))) + ->andWhere($qb->expr()->eq($qb->createFunction('json_contains(oo.users, json_extract(`o`.`object`, \'$.username\'))'), $qb->createNamedParameter(0))); + + $sql = $qb->getSQL(); +// var_dump($sql); + $users = $qb->execute()->fetchAll(); +// var_dump('hello'); + foreach($users as $user) { + $this->organisatieService->addUsersToOrganization($user['organisation'], [$user['username']]); + } + + return []; + } /** @@ -881,6 +974,9 @@ public function performScheduledSync(int $minutesBack = 10): array // Perform the core synchronization with time-based filtering // $syncResults = $this->performFullSync($minutesBack); $syncResults = $this->performOrganizationsSync(); + + $syncResults = array_merge($this->performContactSync(), $syncResults); + $this->performUserSync(); // Record the sync time $this->recordSyncTime(); @@ -948,6 +1044,10 @@ public function performManualSync(int $minutesBack = 0): array try { $syncResults = $this->performOrganizationsSync(); + + $syncResults = array_merge($this->performContactSync(), $syncResults); + + $this->performUserSync(); // die; // Perform the core synchronization with time-based filtering diff --git a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php index 2a468bbd..26af0a5b 100644 --- a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php +++ b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php @@ -18,6 +18,7 @@ namespace OCA\SoftwareCatalog\Service\SoftwareCatalogue; +use OCP\IConfig; use OCP\IUserManager; use OCP\IUser; use OCP\Security\ISecureRandom; @@ -62,6 +63,7 @@ public function __construct( private readonly IAppManager $_appManager, private readonly LoggerInterface $_logger, private readonly SymfonyEmailService $_emailService, + private readonly IConfig $config, ) { } @@ -86,7 +88,7 @@ private function _getObjectService(): ?\OCA\OpenRegister\Service\ObjectService * Generates a username from contact data with fallback strategies * * @param array $contactData The contact data array - * + * * @return string Generated username */ public function generateUsernameFromContactData(array $contactData): string @@ -97,7 +99,7 @@ public function generateUsernameFromContactData(array $contactData): string $tussenvoegsel = $contactData['tussenvoegsel'] ?? ''; $achternaam = $contactData['achternaam'] ?? ''; $email = $contactData['email'] ?? $contactData['e-mailadres'] ?? ''; - + // Strategy 1: full email address (PRIORITY) @@ -134,7 +136,7 @@ public function generateUsernameFromContactData(array $contactData): string $this->_logger->error('All username generation strategies failed', ['contactData' => $contactData]); return ''; } - + /** * Validates if a username meets Nextcloud requirements */ @@ -143,25 +145,25 @@ private function isValidUsername(string $username): bool if (empty($username)) { return false; } - + // Basic validation rules (adjust based on your Nextcloud configuration) if (strlen($username) < 3 || strlen($username) > 64) { return false; } - + // Must start with alphanumeric if (!preg_match('/^[a-z0-9]/', $username)) { return false; } - + // Only allow alphanumeric, dots, underscores, dashes, and @ symbol (for email addresses) if (!preg_match('/^[a-z0-9._@-]+$/', $username)) { return false; } - + return true; } - + /** * Ensures username is unique by adding counter if needed */ @@ -169,19 +171,19 @@ private function ensureUniqueUsername(string $username): string { $originalUsername = $username; $counter = 1; - + while ($this->_userManager->userExists($username)) { $username = $originalUsername . $counter; $counter++; - + // Safety check to prevent infinite loop if ($counter > 9999) { $username = $originalUsername . uniqid(); break; } } - + return $username; } @@ -189,7 +191,7 @@ private function ensureUniqueUsername(string $username): string * Creates a user account for a contact person * * @param object $contactpersoonObject The contact person object - * + * * @return \OCP\IUser|null The created user or null if failed */ public function createUserAccount(object $contactpersoonObject, bool $isFirstContact = false): ?\OCP\IUser @@ -197,7 +199,7 @@ public function createUserAccount(object $contactpersoonObject, bool $isFirstCon try { $objectData = $contactpersoonObject->getObject(); $email = $objectData['email'] ?? $objectData['e-mailadres'] ?? ''; - + if (empty($email)) { $this->_logger->warning( 'Cannot create user account: no email address provided', @@ -205,14 +207,14 @@ public function createUserAccount(object $contactpersoonObject, bool $isFirstCon ); return null; } - + // Generate username first to check both email and username existence $username = $objectData['username'] ?? ''; if (empty($username)) { $username = $this->generateUsernameFromContactData($objectData); } - + // Check if user already exists by email if ($this->_userManager->userExists($email)) { $this->_logger->info( @@ -226,13 +228,13 @@ public function createUserAccount(object $contactpersoonObject, bool $isFirstCon if (!empty($organizationUuid)) { $this->storeUserOrganizationUuid($existingUser, $organizationUuid); } - + // Update groups for existing user $this->assignUserGroups($existingUser, $objectData, $isFirstContact); return $existingUser; } } - + // Check if user already exists by username $existingUserByUsername = $this->_userManager->get($username); if ($existingUserByUsername) { @@ -240,45 +242,45 @@ public function createUserAccount(object $contactpersoonObject, bool $isFirstCon 'User already exists with username', ['username' => $username, 'contactpersoonId' => $contactpersoonObject->getId()] ); - + // Store organization UUID for existing user $organizationUuid = $objectData['organisation'] ?? $objectData['organisatie'] ?? ''; if (!empty($organizationUuid)) { $this->storeUserOrganizationUuid($existingUserByUsername, $organizationUuid); } - + // Update groups for existing user $this->assignUserGroups($existingUserByUsername, $objectData, $isFirstContact); return $existingUserByUsername; } - + // Username already generated above for existence checks - + // Create user account $user = $this->_userManager->createUser($username, $username); - + if ($user) { - + // Set user details $user->setEMailAddress($email); $user->setDisplayName($this->getDisplayNameFromContactData($objectData)); - + // Store organization UUID in user config for OpenConnector access $organizationUuid = $objectData['organisation'] ?? $objectData['organisatie'] ?? ''; if (!empty($organizationUuid)) { $this->storeUserOrganizationUuid($user, $organizationUuid); } - + // Set user groups based on roles and organization $this->assignUserGroups($user, $objectData, $isFirstContact); - + // Update contactpersoon with username $objectData['username'] = $username; $contactpersoonObject->setObject($objectData); - + // Send user creation email $this->sendUserCreationEmail($user, $objectData); - + $this->_logger->info( 'Created user account for contact person', [ @@ -287,7 +289,7 @@ public function createUserAccount(object $contactpersoonObject, bool $isFirstCon 'email' => $email ] ); - + return $user; } else { $this->_logger->error( @@ -299,9 +301,9 @@ public function createUserAccount(object $contactpersoonObject, bool $isFirstCon ] ); } - + return null; - + } catch (\Exception $e) { $this->_logger->error( 'Failed to create user account: ' . $e->getMessage(), @@ -322,7 +324,7 @@ public function createUserAccount(object $contactpersoonObject, bool $isFirstCon * * @param \OCP\IUser $user The user to assign groups to * @param array $objectData The contact person data - * + * * @return void */ private function assignUserGroups(\OCP\IUser $user, array $objectData, bool $isFirstContact = false): void @@ -330,23 +332,21 @@ private function assignUserGroups(\OCP\IUser $user, array $objectData, bool $isF try { $roles = $objectData['roles'] ?? []; $organizationId = $objectData['organisation'] ?? $objectData['organisatie'] ?? ''; - - // Ensure roles is an array if (!is_array($roles)) { $roles = [$roles]; } - + // Get the settings service to access group configurations $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); - + // Add user to ALL generic user groups (as requested) $genericGroups = $settingsService->getGenericUserGroups(); foreach ($genericGroups as $groupName) { $this->addUserToGroup($user, $groupName, 'generic-user-group'); } - + // Add user to organization admin groups if this is the first contact if ($isFirstContact) { $organizationAdminGroups = $settingsService->getOrganizationAdminGroups(); @@ -354,14 +354,14 @@ private function assignUserGroups(\OCP\IUser $user, array $objectData, bool $isF $this->addUserToGroup($user, $groupName, 'organization-admin'); } } - + // Add user to organization group if available if (!empty($organizationId)) { $organizationGroup = $this->getOrganizationGroup((string)$organizationId); - + if ($organizationGroup && !$organizationGroup->inGroup($user)) { $organizationGroup->addUser($user); - + // If this is the first contact, make them a subadmin of the organization group if ($isFirstContact) { try { @@ -397,7 +397,7 @@ private function assignUserGroups(\OCP\IUser $user, array $objectData, bool $isF ); } } - + // Check if organization is of type "Gemeente" and add to "ambtenaar" group $organizationType = $this->getOrganizationType((string)$organizationId); if (strtolower($organizationType) === 'gemeente') { @@ -412,7 +412,7 @@ private function assignUserGroups(\OCP\IUser $user, array $objectData, bool $isF ); } } - + $this->_logger->info( 'Successfully assigned user groups', [ @@ -422,7 +422,7 @@ private function assignUserGroups(\OCP\IUser $user, array $objectData, bool $isF 'organizationAdminGroups' => $isFirstContact ? ($organizationAdminGroups ?? []) : [] ] ); - + } catch (\Exception $e) { $this->_logger->error( 'Failed to assign user groups: ' . $e->getMessage(), @@ -458,7 +458,7 @@ private function getAllowedRoleGroups(): array * @param \OCP\IUser $user The user to add * @param string $groupName The group name * @param string $type The type of group assignment (for logging) - * + * * @return void */ private function addUserToGroup(\OCP\IUser $user, string $groupName, string $type): void @@ -474,7 +474,7 @@ private function addUserToGroup(\OCP\IUser $user, string $groupName, string $typ ); } } - + if ($group && !$group->inGroup($user)) { $group->addUser($user); $this->_logger->info( @@ -505,14 +505,14 @@ private function addUserToGroup(\OCP\IUser $user, string $groupName, string $typ * @param \OCP\IUser $user The user to update * @param array $newRoles The new roles * @param array $oldRoles The old roles (optional) - * + * * @return void */ public function updateUserGroupsFromRoles(\OCP\IUser $user, array $newRoles, array $oldRoles = []): void { try { $allowedGroups = $this->getAllowedRoleGroups(); - + // Remove user from groups for roles they no longer have if (!empty($oldRoles)) { $removedRoles = array_diff($oldRoles, $newRoles); @@ -534,7 +534,7 @@ public function updateUserGroupsFromRoles(\OCP\IUser $user, array $newRoles, arr } } } - + // Add user to groups for new roles foreach ($newRoles as $role) { if (in_array($role, array_keys($allowedGroups))) { @@ -542,11 +542,11 @@ public function updateUserGroupsFromRoles(\OCP\IUser $user, array $newRoles, arr $this->addUserToGroup($user, $groupName, 'role-update'); } } - + // Ensure organization type-based groups are preserved // (e.g., "ambtenaar" for Gemeente organizations) $this->ensureOrganizationTypeGroups($user); - + } catch (\Exception $e) { $this->_logger->error( 'Failed to update user groups from roles: ' . $e->getMessage(), @@ -562,7 +562,7 @@ public function updateUserGroupsFromRoles(\OCP\IUser $user, array $newRoles, arr * Ensures organization type-based groups are assigned to the user * * @param \OCP\IUser $user The user to check and update - * + * * @return void */ private function ensureOrganizationTypeGroups(\OCP\IUser $user): void @@ -571,21 +571,21 @@ private function ensureOrganizationTypeGroups(\OCP\IUser $user): void // Find the user's organization by looking for their contactpersoon $objectService = $this->_getObjectService(); $contactpersoon = $this->findContactpersoonByUsername($user->getUID()); - + if ($contactpersoon) { $contactData = $contactpersoon->getObject(); $organizationId = $contactData['organisation'] ?? ''; - + if (!empty($organizationId)) { $organizationType = $this->getOrganizationType($organizationId); - + // If organization is Gemeente, ensure user is in ambtenaar group if (strtolower($organizationType) === 'gemeente') { $this->addUserToGroup($user, 'ambtenaar', 'gemeente-organization-preserve'); } } } - + } catch (\Exception $e) { $this->_logger->error( 'Failed to ensure organization type groups: ' . $e->getMessage(), @@ -601,7 +601,7 @@ private function ensureOrganizationTypeGroups(\OCP\IUser $user): void * Finds contactpersoon object by username * * @param string $username The username to search for - * + * * @return object|null The contactpersoon object or null if not found */ private function findContactpersoonByUsername(string $username): ?object @@ -609,28 +609,28 @@ private function findContactpersoonByUsername(string $username): ?object try { $objectService = $this->_getObjectService(); $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); - + // Get configuration values $registerId = $settingsService->getVoorzieningenRegisterId(); $contactpersoonSchemaId = $settingsService->getSchemaIdForObjectType('contactpersoon'); - + if (!$registerId || !$contactpersoonSchemaId) { throw new \Exception('Register or schema ID not configured for contactpersoon'); } - + // Search for contactpersoon with the given username $searchFilters = [ 'username' => $username ]; - + $results = $objectService->findAll($searchFilters, $registerId, $contactpersoonSchemaId); - + if (!empty($results)) { return $results[0]; // Return the first match } - + return null; - + } catch (\Exception $e) { $this->_logger->error( 'Failed to find contactpersoon by username: ' . $e->getMessage(), @@ -647,7 +647,7 @@ private function findContactpersoonByUsername(string $username): ?object * Gets the organization group for a given organization ID * * @param string $organizationId The organization ID - * + * * @return \OCP\IGroup|null The organization group or null if not found */ private function getOrganizationGroup(string $organizationId): ?\OCP\IGroup @@ -658,32 +658,32 @@ private function getOrganizationGroup(string $organizationId): ?\OCP\IGroup if (!$objectService) { return null; } - + // Get register and schema IDs dynamically from configuration $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); $registerId = $settingsService->getVoorzieningenRegisterId(); $organisatieSchemaId = $settingsService->getSchemaIdForObjectType('organisatie'); - + if (!$registerId || !$organisatieSchemaId) { $this->_logger->warning('Register or schema ID not configured for organisatie'); return null; } - + // Use find() method with proper register/schema context $organizationObject = $objectService->find($organizationId, [], false, $registerId, $organisatieSchemaId); - + if ($organizationObject) { $organizationData = $organizationObject->getObject(); $groupId = $organizationData['group'] ?? ''; - + if (!empty($groupId)) { $group = $this->_groupManager->get($groupId); return $group; } } - + return null; - + } catch (\Exception $e) { $this->_logger->error( 'Failed to get organization group: ' . $e->getMessage(), @@ -701,7 +701,7 @@ private function getOrganizationGroup(string $organizationId): ?\OCP\IGroup * * @param object $contactObject The contact object being processed (contactpersoon) * @param array $objectData The contact data - * + * * @return bool True if this is the first contact for the organization */ private function isFirstContactForOrganization(object $contactObject, array $objectData): bool @@ -709,12 +709,12 @@ private function isFirstContactForOrganization(object $contactObject, array $obj try { $organizationId = $objectData['organisation'] ?? $objectData['organisatie'] ?? ''; $currentContactId = $contactObject->getId(); - + if (empty($organizationId)) { $this->_logger->warning('No organization ID found for contact object'); return false; } - + $this->_logger->info( 'Checking if contact is first for organization', [ @@ -722,18 +722,18 @@ private function isFirstContactForOrganization(object $contactObject, array $obj 'organizationId' => $organizationId ] ); - + // Simple approach: Check if any OTHER users exist with this organization UUID $objectService = $this->_getObjectService(); if (!$objectService) { $this->_logger->error('ObjectService not available for first contact check'); return false; } - + // Get settings for schema IDs $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); $registerId = $settingsService->getVoorzieningenRegisterId(); - + // Check contactpersoon schema $contactpersoonSchemaId = $settingsService->getSchemaIdForObjectType('contactpersoon'); if ($contactpersoonSchemaId) { @@ -742,12 +742,12 @@ private function isFirstContactForOrganization(object $contactObject, array $obj $registerId, $contactpersoonSchemaId ); - + // Filter out the current contact being processed $otherContacts = array_filter($existingContacts, function($contact) use ($currentContactId) { return $contact->getId() !== $currentContactId; }); - + $this->_logger->info( 'Found existing contacts for organization', [ @@ -758,15 +758,15 @@ private function isFirstContactForOrganization(object $contactObject, array $obj 'isFirstContact' => empty($otherContacts) ] ); - + // If there are any OTHER existing contacts, this is not the first if (!empty($otherContacts)) { return false; } } - + return true; - + } catch (\Exception $e) { $this->_logger->error( 'Failed to determine if first contact: ' . $e->getMessage(), @@ -788,7 +788,7 @@ private function isFirstContactForOrganization(object $contactObject, array $obj * * @param IUser $user The user object * @param string|int $organizationUuid The organization UUID (can be string or int) - * + * * @return void */ private function storeUserOrganizationUuid(IUser $user, string|int $organizationUuid): void @@ -797,14 +797,14 @@ private function storeUserOrganizationUuid(IUser $user, string|int $organization if (!empty($organizationUuid)) { // Convert to string to ensure consistent storage $organizationUuidStr = (string)$organizationUuid; - - $this->_config->setUserValue( + + $this->config->setUserValue( $user->getUID(), 'core', 'organisation', $organizationUuidStr ); - + $this->_logger->info( 'Stored organization UUID in user config', [ @@ -831,7 +831,7 @@ private function storeUserOrganizationUuid(IUser $user, string|int $organization * Gets a display name from contact data * * @param array $contactData The contact data - * + * * @return string The display name */ private function getDisplayNameFromContactData(array $contactData): string @@ -841,7 +841,7 @@ private function getDisplayNameFromContactData(array $contactData): string $contactData['tussenvoegsel'] ?? '', $contactData['achternaam'] ?? '' ]); - + return implode(' ', $parts) ?: ($contactData['email'] ?? $contactData['e-mailadres'] ?? 'Unknown User'); } @@ -849,7 +849,7 @@ private function getDisplayNameFromContactData(array $contactData): string * Handles new contact creation * * @param object $contactObject The contact object - * + * * @return void */ public function handleNewContact(object $contactObject): void @@ -877,7 +877,7 @@ public function handleNewContact(object $contactObject): void * Handles contact update * * @param object $contactObject The contact object - * + * * @return void */ public function handleContactUpdate(object $contactObject): void @@ -905,7 +905,7 @@ public function handleContactUpdate(object $contactObject): void * Handles contact deletion * * @param object $contactObject The contact object - * + * * @return void */ public function handleContactDeletion(object $contactObject): void @@ -924,10 +924,10 @@ public function handleContactDeletion(object $contactObject): void if ($user) { // Option 1: Delete the user account // $user->delete(); - + // Option 2: Just disable the user $user->setEnabled(false); - + $this->_logger->info( 'User account disabled due to contact deletion', [ @@ -935,7 +935,7 @@ public function handleContactDeletion(object $contactObject): void 'contactId' => $contactObject->getId() ] ); - + // Send account suspension notification email $this->sendAccountSuspensionEmail($user, $objectData); } @@ -958,7 +958,7 @@ public function handleContactDeletion(object $contactObject): void * @param object $contactpersoonObject The contactpersoon object * @param string $username The username * @param string $organizationUuid The organization UUID - * + * * @return void */ public function assignBeheerderRole(object $contactpersoonObject, string $username, string $organizationUuid): void @@ -966,19 +966,19 @@ public function assignBeheerderRole(object $contactpersoonObject, string $userna try { $objectData = $contactpersoonObject->getObject(); $currentRoles = $objectData['roles'] ?? []; - + if (!is_array($currentRoles)) { $currentRoles = []; } - + // Add beheerder role if not already present if (!in_array('beheerder', array_map('strtolower', $currentRoles))) { $currentRoles[] = 'beheerder'; - + // Update the contactpersoon object (but don't save to prevent event loops) $objectData['roles'] = $currentRoles; $contactpersoonObject->setObject($objectData); - + // Note: NOT saving the object here to prevent infinite event loops // The original API call/operation will handle persistence $this->_logger->info('Beheerder role added to contactpersoon object, but not saved to prevent event loops', [ @@ -987,20 +987,20 @@ public function assignBeheerderRole(object $contactpersoonObject, string $userna 'updatedRoles' => $currentRoles, 'objectId' => $contactpersoonObject->getId() ]); - + // Add user to beheerder group $beheerderGroup = $this->_groupManager->get('beheerder'); if (!$beheerderGroup) { $beheerderGroup = $this->_groupManager->createGroup('beheerder'); } - + if ($beheerderGroup) { $user = $this->_userManager->get($username); if ($user && !$beheerderGroup->inGroup($user)) { $beheerderGroup->addUser($user); } } - + $this->_logger->info( 'Assigned beheerder role to first user in organization', [ @@ -1010,7 +1010,7 @@ public function assignBeheerderRole(object $contactpersoonObject, string $userna ] ); } - + } catch (\Exception $e) { $this->_logger->error( 'Failed to assign beheerder role: ' . $e->getMessage(), @@ -1028,7 +1028,7 @@ public function assignBeheerderRole(object $contactpersoonObject, string $userna * * @param string $username The username * @param string $managerUsername The manager's username - * + * * @return void */ public function setUserManager(string $username, string $managerUsername): void @@ -1036,7 +1036,7 @@ public function setUserManager(string $username, string $managerUsername): void try { $user = $this->_userManager->get($username); $manager = $this->_userManager->get($managerUsername); - + if (!$user || !$manager) { $this->_logger->warning( 'Cannot set manager - user or manager not found', @@ -1049,7 +1049,7 @@ public function setUserManager(string $username, string $managerUsername): void ); return; } - + // In Nextcloud, we can set this as a user preference or custom attribute // Since there's no built-in manager field, we'll use preferences \OC::$server->getConfig()->setUserValue( @@ -1058,7 +1058,7 @@ public function setUserManager(string $username, string $managerUsername): void 'manager', $managerUsername ); - + $this->_logger->info( 'Set user manager', [ @@ -1066,7 +1066,7 @@ public function setUserManager(string $username, string $managerUsername): void 'manager' => $managerUsername ] ); - + } catch (\Exception $e) { $this->_logger->error( 'Failed to set user manager: ' . $e->getMessage(), @@ -1083,7 +1083,7 @@ public function setUserManager(string $username, string $managerUsername): void * Gets a user's manager * * @param string $username The username - * + * * @return string|null The manager's username or null if not set */ public function getUserManager(string $username): ?string @@ -1095,9 +1095,9 @@ public function getUserManager(string $username): ?string 'manager', '' ); - + return !empty($manager) ? $manager : null; - + } catch (\Exception $e) { $this->_logger->error( 'Failed to get user manager: ' . $e->getMessage(), @@ -1114,7 +1114,7 @@ public function getUserManager(string $username): ?string * Gets the organization type for a given organization ID * * @param string $organizationId The organization ID - * + * * @return string The organization type or empty string if not found */ private function getOrganizationType(string $organizationId): string @@ -1122,27 +1122,27 @@ private function getOrganizationType(string $organizationId): string try { // Get the organization object to find its type $objectService = $this->_getObjectService(); - + // Get register and schema IDs dynamically from configuration $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); $registerId = $settingsService->getVoorzieningenRegisterId(); $organisatieSchemaId = $settingsService->getSchemaIdForObjectType('organisatie'); - + if (!$registerId || !$organisatieSchemaId) { $this->_logger->warning('Register or schema ID not configured for organisatie'); return ''; } - + // Try to find by UUID first, then by database ID if needed $organizationObject = $objectService->find($organizationId, [], false, $registerId, $organisatieSchemaId); - + if ($organizationObject) { $organizationData = $organizationObject->getObject(); return $organizationData['type'] ?? ''; } - + return ''; - + } catch (\Exception $e) { $this->_logger->error( 'Failed to get organization type: ' . $e->getMessage(), @@ -1160,7 +1160,7 @@ private function getOrganizationType(string $organizationId): string * * @param \OCP\IUser $user The created user * @param array $objectData The contact person data - * + * * @return void */ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): void @@ -1170,7 +1170,7 @@ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): voi 'username' => $user->getUID(), 'email' => $user->getEMailAddress() ]); - + // Prepare user data for email $userData = [ 'username' => $user->getUID(), @@ -1180,7 +1180,7 @@ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): voi 'achternaam' => $objectData['achternaam'] ?? '', 'roles' => $objectData['roles'] ?? [] ]; - + // Get organization data if available $organizationData = []; $organizationId = $objectData['organisation'] ?? $objectData['organisatie'] ?? ''; @@ -1191,12 +1191,12 @@ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): voi $settingsService = $this->_container->get('OCA\SoftwareCatalog\Service\SettingsService'); $registerId = $settingsService->getVoorzieningenRegisterId(); $organisatieSchemaId = $settingsService->getSchemaIdForObjectType('organisatie'); - + if (!$registerId || !$organisatieSchemaId) { $this->_logger->warning('Register or schema ID not configured for organisatie'); return; } - + $organizationObject = $objectService->find($organizationId, [], false, $registerId, $organisatieSchemaId); if ($organizationObject) { $organizationData = $organizationObject->getObject(); @@ -1212,10 +1212,10 @@ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): voi ]); } } - + // Send user creation email $success = $this->_emailService->sendUserCreationEmail($userData, $organizationData); - + if ($success) { $this->_logger->info('User creation email sent successfully', [ 'username' => $user->getUID(), @@ -1227,7 +1227,7 @@ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): voi 'email' => $user->getEMailAddress() ]); } - + } catch (\Exception $e) { $this->_logger->error('Exception sending user creation email: ' . $e->getMessage(), [ 'username' => $user->getUID(), @@ -1245,7 +1245,7 @@ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): voi * * @param object $contactpersoonObject The contactpersoon object to process * @param bool $isUpdate Whether this is an update operation (defaults to false) - * + * * @return bool True if processing was successful * @throws \Exception If processing fails */ @@ -1259,99 +1259,99 @@ public function processContactpersoon(object $contactpersoonObject, bool $isUpda // Get object data $objectData = $contactpersoonObject->getObject(); - + // Check if username exists and is filled $username = $objectData['username'] ?? ''; - + if (empty($username)) { $this->_logger->info('Username not found or empty, creating inactive user account'); - + // Generate username from name fields $username = $this->generateUsernameFromContactData($objectData); - + // For updates, try to find existing user first to avoid expensive isFirstContactForOrganization check if ($isUpdate) { $existingUser = $this->_userManager->get($username); - + if ($existingUser) { $this->_logger->info('Found existing user during update, skipping expensive first contact check', [ 'username' => $username, 'objectId' => $contactpersoonObject->getId() ]); - + // Update the contactpersoon object with the username (but don't save to prevent event loops) $objectData['username'] = $username; $contactpersoonObject->setObject($objectData); - + $this->_logger->info('Username added to contactpersoon object during update, but not saved to prevent event loops', [ 'username' => $username, 'objectId' => $contactpersoonObject->getId() ]); - + // Ensure contactpersoon is added to organization $this->ensureContactpersoonInOrganization($contactpersoonObject); - + return true; } } - + // Determine if this is the first contact for the organization (expensive operation) $isFirstContact = $this->isFirstContactForOrganization($contactpersoonObject, $objectData); - + // Create the user account $user = $this->createUserAccount($contactpersoonObject, $isFirstContact); - + if ($user === null) { throw new \Exception('Failed to create user account'); } - + // Set user to inactive initially $this->setUserInactive($user->getUID()); - + // Update the contactpersoon object with the username (but don't save to prevent event loops) $objectData['username'] = $username; $contactpersoonObject->setObject($objectData); - + // Note: NOT saving the object here to prevent infinite event loops // The original API call/operation will handle persistence $this->_logger->info('Username added to contactpersoon object, but not saved to prevent event loops', [ 'username' => $username, 'objectId' => $contactpersoonObject->getId() ]); - + // Ensure contactpersoon is added to organization $this->ensureContactpersoonInOrganization($contactpersoonObject); - + // Also add user to organization entity (OpenRegister entity, not object) $this->addUserToOrganizationEntity($contactpersoonObject, $username); - + $this->_logger->info( - 'Successfully created inactive user and updated contactpersoon', + 'Successfully created inactive user and updated contactpersoon', [ 'username' => $username, 'objectId' => $contactpersoonObject->getId() ] ); - + return true; } - + $this->_logger->info( - 'Username already exists, contactpersoon processed', + 'Username already exists, contactpersoon processed', [ 'username' => $username, 'objectId' => $contactpersoonObject->getId() ] ); - + // Ensure contactpersoon is added to organization (even for existing users) $this->ensureContactpersoonInOrganization($contactpersoonObject); - + return true; - + } catch (\Exception $e) { $this->_logger->error( - 'Failed to process contactpersoon object: ' . $e->getMessage(), + 'Failed to process contactpersoon object: ' . $e->getMessage(), [ 'exception' => $e, 'objectId' => $contactpersoonObject->getId() ?? 'unknown' @@ -1365,24 +1365,24 @@ public function processContactpersoon(object $contactpersoonObject, bool $isUpda * Sets a user account to inactive * * @param string $username The username to set as inactive - * + * * @return bool True if successful */ public function setUserInactive(string $username): bool { try { $user = $this->_userManager->get($username); - + if ($user) { $user->setEnabled(false); - + $this->_logger->info( 'Set user account to inactive', [ 'username' => $username ] ); - + return true; } else { $this->_logger->warning( @@ -1391,10 +1391,10 @@ public function setUserInactive(string $username): bool 'username' => $username ] ); - + return false; } - + } catch (\Exception $e) { $this->_logger->error( 'Failed to set user inactive: ' . $e->getMessage(), @@ -1403,7 +1403,7 @@ public function setUserInactive(string $username): bool 'exception' => $e ] ); - + return false; } } @@ -1412,24 +1412,24 @@ public function setUserInactive(string $username): bool * Sets a user account to active * * @param string $username The username to set as active - * + * * @return bool True if successful */ public function setUserActive(string $username): bool { try { $user = $this->_userManager->get($username); - + if ($user) { $user->setEnabled(true); - + $this->_logger->info( 'Set user account to active', [ 'username' => $username ] ); - + return true; } else { $this->_logger->warning( @@ -1438,10 +1438,10 @@ public function setUserActive(string $username): bool 'username' => $username ] ); - + return false; } - + } catch (\Exception $e) { $this->_logger->error( 'Failed to set user active: ' . $e->getMessage(), @@ -1450,7 +1450,7 @@ public function setUserActive(string $username): bool 'exception' => $e ] ); - + return false; } } @@ -1460,7 +1460,7 @@ public function setUserActive(string $username): bool * * @param object $contactpersoonObject The updated contactpersoon object * @param object $oldContactpersoonObject The previous contactpersoon object - * + * * @return void */ public function handleContactpersoonUpdate(object $contactpersoonObject, object $oldContactpersoonObject): void @@ -1472,14 +1472,14 @@ public function handleContactpersoonUpdate(object $contactpersoonObject, object // Process the updated contactpersoon $this->processContactpersoon($contactpersoonObject); - + // Check for role changes and update groups accordingly $newData = $contactpersoonObject->getObject(); $oldData = $oldContactpersoonObject->getObject(); - + $newRoles = $newData['roles'] ?? []; $oldRoles = $oldData['roles'] ?? []; - + // Ensure both are arrays if (!is_array($newRoles)) { $newRoles = [$newRoles]; @@ -1487,7 +1487,7 @@ public function handleContactpersoonUpdate(object $contactpersoonObject, object if (!is_array($oldRoles)) { $oldRoles = [$oldRoles]; } - + // Check if roles have changed if ($newRoles !== $oldRoles) { $username = $newData['username'] ?? ''; @@ -1503,7 +1503,7 @@ public function handleContactpersoonUpdate(object $contactpersoonObject, object 'newRoles' => $newRoles ] ); - + // Update user groups based on role changes $this->updateUserGroupsFromRoles($user, $newRoles, $oldRoles); } @@ -1526,7 +1526,7 @@ public function handleContactpersoonUpdate(object $contactpersoonObject, object * * @param \OCP\IUser $user The suspended user * @param array $objectData The contact person data - * + * * @return void */ private function sendAccountSuspensionEmail(\OCP\IUser $user, array $objectData): void @@ -1536,17 +1536,17 @@ private function sendAccountSuspensionEmail(\OCP\IUser $user, array $objectData) 'username' => $user->getUID(), 'email' => $user->getEMailAddress() ]); - - // For now, we'll use a simple log message as the PhpEmailService + + // For now, we'll use a simple log message as the PhpEmailService // doesn't have a specific suspension email method yet // This can be extended later if needed - + $this->_logger->info('Account suspension email would be sent here', [ 'username' => $user->getUID(), 'email' => $user->getEMailAddress(), 'displayName' => $user->getDisplayName() ]); - + } catch (\Exception $e) { $this->_logger->error('Exception sending account suspension email: ' . $e->getMessage(), [ 'username' => $user->getUID(), @@ -1560,7 +1560,7 @@ private function sendAccountSuspensionEmail(\OCP\IUser $user, array $objectData) * Checks if a contactpersoon username is in the organization's users list * * @param object $contactpersoonObject The contactpersoon object - * + * * @return bool True if the user should be added to the organization */ public function shouldAddContactpersoonToOrganization(object $contactpersoonObject): bool @@ -1591,10 +1591,10 @@ public function shouldAddContactpersoonToOrganization(object $contactpersoonObje try { $organizationObject = $objectService->find($organizationUuid, [], false, $registerId, $organisatieSchemaId); $organizationData = $organizationObject->getObject(); - + // Check if the username is already in the organization's users $organizationUsers = $organizationData['users'] ?? []; - + if (is_array($organizationUsers) && !in_array($username, $organizationUsers)) { $this->_logger->info('ContactPersonHandler: Contactpersoon should be added to organization', [ 'username' => $username, @@ -1631,7 +1631,7 @@ public function shouldAddContactpersoonToOrganization(object $contactpersoonObje * Adds a contactpersoon username to the organization's users list * * @param object $contactpersoonObject The contactpersoon object - * + * * @return bool True if the user was successfully added */ public function addContactpersoonToOrganization(object $contactpersoonObject): bool @@ -1668,17 +1668,17 @@ public function addContactpersoonToOrganization(object $contactpersoonObject): b try { $organizationObject = $objectService->find($organizationUuid, [], false, $registerId, $organisatieSchemaId); $organizationData = $organizationObject->getObject(); - + // Add the username to the organization's users list $organizationUsers = $organizationData['users'] ?? []; if (!is_array($organizationUsers)) { $organizationUsers = []; } - + if (!in_array($username, $organizationUsers)) { $organizationUsers[] = $username; $organizationData['users'] = $organizationUsers; - + // Update the organization object $updatedOrganization = $objectService->saveObject( $organizationData, @@ -1730,7 +1730,7 @@ public function addContactpersoonToOrganization(object $contactpersoonObject): b * Ensures contactpersoon is added to organization after user creation/update * * @param object $contactpersoonObject The contactpersoon object - * + * * @return void */ public function ensureContactpersoonInOrganization(object $contactpersoonObject): void @@ -1744,7 +1744,7 @@ public function ensureContactpersoonInOrganization(object $contactpersoonObject) if ($this->shouldAddContactpersoonToOrganization($contactpersoonObject)) { // Add user to organization $result = $this->addContactpersoonToOrganization($contactpersoonObject); - + if ($result) { $this->_logger->info('ContactPersonHandler: Successfully ensured contactpersoon in organization', [ 'objectId' => $contactpersoonObject->getId() @@ -1773,13 +1773,13 @@ public function ensureContactpersoonInOrganization(object $contactpersoonObject) ); } } - + /** * Adds a user to the organization entity (OpenRegister entity, not object) * * @param object $contactpersoonObject The contactpersoon object * @param string $username The username to add - * + * * @return void */ private function addUserToOrganizationEntity(object $contactpersoonObject, string $username): void @@ -1787,7 +1787,7 @@ private function addUserToOrganizationEntity(object $contactpersoonObject, strin try { $objectData = $contactpersoonObject->getObject(); $organizationUuid = $objectData['organisation'] ?? $objectData['organisatie'] ?? ''; - + if (empty($organizationUuid)) { $this->_logger->warning('ContactPersonHandler: No organization reference found for contact person', [ 'objectId' => $contactpersoonObject->getId(), @@ -1795,24 +1795,24 @@ private function addUserToOrganizationEntity(object $contactpersoonObject, strin ]); return; } - + $this->_logger->info('ContactPersonHandler: Adding user to organization entity', [ 'objectId' => $contactpersoonObject->getId(), 'username' => $username, 'organizationUuid' => $organizationUuid ]); - + try { $organisationMapper = $this->_container->get('OCA\\OpenRegister\\Db\\OrganisationMapper'); $organisation = $organisationMapper->findByUuid($organizationUuid); - + if ($organisation) { $currentUsers = $organisation->getUsers() ?? []; if (!in_array($username, $currentUsers)) { $currentUsers[] = $username; $organisation->setUsers($currentUsers); $organisationMapper->save($organisation); - + $this->_logger->info('ContactPersonHandler: Successfully added user to organization entity', [ 'objectId' => $contactpersoonObject->getId(), 'username' => $username, @@ -1850,4 +1850,4 @@ private function addUserToOrganizationEntity(object $contactpersoonObject, strin } } -} \ No newline at end of file +}