From fbaa6cfa375b9602441d5c57baada54de16be262 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Thu, 14 Aug 2025 16:34:26 +0200 Subject: [PATCH] Fix emailing --- lib/AppInfo/Application.php | 4 +- lib/Service/OrganisatieService.php | 86 ++- lib/Service/OrganizationSyncService.php | 7 +- lib/Service/SettingsService.php | 721 +++++++++--------- .../ContactPersonHandler.php | 110 +-- lib/Service/SymfonyEmailService.php | 214 ++++-- 6 files changed, 628 insertions(+), 514 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 7c0c7a52..9d0213b7 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -139,7 +139,9 @@ public function register(IRegistrationContext $context): void $container->get('Psr\Log\LoggerInterface'), $container, $container->get('OCP\App\IAppManager'), - $container->get(IAppConfig::class) + $container->get(IAppConfig::class), + $container->get(IUserManager::class), + $container->get(SymfonyEmailService::class), ); }); diff --git a/lib/Service/OrganisatieService.php b/lib/Service/OrganisatieService.php index a5bbfffa..c283f133 100644 --- a/lib/Service/OrganisatieService.php +++ b/lib/Service/OrganisatieService.php @@ -19,6 +19,7 @@ namespace OCA\SoftwareCatalog\Service; use OCA\SoftwareCatalog\Service\SoftwareCatalogue\OrganizationHandler; +use OCP\IUserManager; use Psr\Log\LoggerInterface; use Psr\Container\ContainerInterface; use OCP\App\IAppManager; @@ -26,10 +27,10 @@ /** * Service for handling organization-specific operations - * + * * This service provides functionality for organization entity creation, * status management, and integration with OpenRegister. - * + * * @category Service * @package OCA\SoftwareCatalog\Service * @author Conduction b.v. @@ -53,7 +54,9 @@ public function __construct( private readonly LoggerInterface $logger, private readonly ContainerInterface $container, private readonly IAppManager $appManager, - private readonly IAppConfig $config + private readonly IAppConfig $config, + private readonly IUserManager $userManager, + private readonly SymfonyEmailService $emailService, ) { } @@ -61,7 +64,7 @@ public function __construct( * Creates an organization entity in OpenRegister * * @param array $objectData The organization object data - * + * * @return object|null The created organisation entity or null on failure */ public function createOrganisationInOpenRegister(array $objectData): ?object @@ -72,34 +75,34 @@ public function createOrganisationInOpenRegister(array $objectData): ?object $this->logger->error('OrganisatieService: No organization UUID provided for creation'); return null; } - + $this->logger->info('OrganisatieService: Creating organization entity in OpenRegister', [ 'organizationUuid' => $organizationUuid, 'naam' => $objectData['naam'] ?? 'Unknown' ]); - + // Map the data for OpenRegister $mappedData = $this->mapOrganizationDataForOpenRegister($objectData); - + // Get organisation service $organisationService = $this->getOrganisationService(); if (!$organisationService) { $this->logger->error('OrganisatieService: OrganisationService not available'); return null; } - + // Create the organization entity $organisationEntity = $this->createOrganisationEntityInternal($organisationService, $mappedData, $organizationUuid); - + if ($organisationEntity) { $this->logger->info('OrganisatieService: Successfully created organization entity', [ 'organizationUuid' => $organizationUuid, 'entityId' => $organisationEntity->getId() ]); } - + return $organisationEntity; - + } catch (\Exception $e) { $this->logger->error('OrganisatieService: Error creating organization entity', [ 'error' => $e->getMessage(), @@ -115,7 +118,7 @@ public function createOrganisationInOpenRegister(array $objectData): ?object * * @param string $organizationUuid The organization UUID * @param array $objectData The organization object data - * + * * @return bool True if update was successful */ public function updateOrganizationStatus(string $organizationUuid, array $objectData): bool @@ -129,21 +132,21 @@ public function updateOrganizationStatus(string $organizationUuid, array $object // Get the organization entity $organisationMapper = $this->container->get('OCA\OpenRegister\Db\OrganisationMapper'); $organisationEntity = $organisationMapper->findByUuid($organizationUuid); - + // Map status from SoftwareCatalog to OpenRegister $active = $this->mapStatus($objectData['beoordeling'] ?? 'actief'); - + // Update the entity $organisationEntity->setActive($active); $organisationMapper->save($organisationEntity); - + $this->logger->info('OrganisatieService: Successfully updated organization status', [ 'organizationUuid' => $organizationUuid, 'active' => $active ]); - + return true; - + } catch (\Exception $e) { $this->logger->error('OrganisatieService: Failed to update organization status', [ 'organizationUuid' => $organizationUuid, @@ -176,7 +179,7 @@ private function getOrganisationService(): ?\OCA\OpenRegister\Service\Organisati * Maps organization data for OpenRegister format * * @param array $objectData The organization object data - * + * * @return array The mapped data for OpenRegister */ private function mapOrganizationDataForOpenRegister(array $objectData): array @@ -195,13 +198,13 @@ private function mapOrganizationDataForOpenRegister(array $objectData): array * Maps status from Software Catalog to OpenRegister format * * @param string $status The status from Software Catalog - * + * * @return bool The mapped active status for OpenRegister */ private function mapStatus(string $status): bool { $normalizedStatus = strtolower(trim($status)); - + return match ($normalizedStatus) { 'actief', 'active' => true, 'inactief', 'inactive', 'deactief' => false, @@ -215,7 +218,7 @@ private function mapStatus(string $status): bool * @param \OCA\OpenRegister\Service\OrganisationService $organisationService The organisation service * @param array $mappedData The mapped data * @param string $organizationUuid The organization UUID - * + * * @return \OCA\OpenRegister\Db\Organisation The created organisation entity */ private function createOrganisationEntityInternal( @@ -223,7 +226,7 @@ private function createOrganisationEntityInternal( array $mappedData, string $organizationUuid ): \OCA\OpenRegister\Db\Organisation { - + $this->logger->info('OrganisatieService: Creating organisation entity', [ 'uuid' => $organizationUuid, 'name' => $mappedData['naam'], @@ -235,21 +238,21 @@ private function createOrganisationEntityInternal( // Let me check what parameters are actually expected and use a simpler approach $organisationEntity = $organisationService->createOrganisation( $mappedData['naam'], // name (string) - $mappedData['type'] ?? '', // description (string) + $mappedData['type'] ?? '', // description (string) false, // addCurrentUser (bool) - don't auto-add current user $organizationUuid // uuid (string) - might be 4th parameter ); - + // Set additional properties after creation if ($organisationEntity) { $organisationEntity->setActive($mappedData['active']); $organisationEntity->setUsers([]); // Will be populated by contact person processing - + // Save the updated entity $organisationMapper = $this->container->get('OCA\OpenRegister\Db\OrganisationMapper'); $organisationMapper->save($organisationEntity); } - + $this->logger->info('OrganisatieService: Organisation entity created successfully', [ 'uuid' => $organizationUuid, 'entityId' => $organisationEntity->getId(), @@ -264,7 +267,7 @@ private function createOrganisationEntityInternal( * * @param string $organizationUuid The organization UUID * @param array $usernames Array of usernames to add - * + * * @return bool True if successful */ public function addUsersToOrganization(string $organizationUuid, array $usernames): bool @@ -278,28 +281,41 @@ public function addUsersToOrganization(string $organizationUuid, array $username // Get the organization entity $organisationMapper = $this->container->get('OCA\OpenRegister\Db\OrganisationMapper'); $organisationEntity = $organisationMapper->findByUuid($organizationUuid); - + // Get current users and merge with new ones $currentUsers = $organisationEntity->getUsers() ?? []; $allUsers = array_unique(array_merge($currentUsers, $usernames)); - + + foreach($usernames as $username) { + $user = $this->userManager->get($username); + + $userData = [ + 'username' => $user->getUID(), + 'email' => $user->getEMailAddress(), + 'name' => $user->getDisplayName(), + ]; + + $this->emailService->sendUserUpdateEmail($userData, $organisationEntity->jsonSerialize()); + } + // Update the entity $organisationEntity->setUsers($allUsers); $organisationMapper->save($organisationEntity); - + $this->logger->info('OrganisatieService: Successfully added users to organization', [ 'organizationUuid' => $organizationUuid, 'totalUsers' => count($allUsers), 'addedUsers' => array_diff($allUsers, $currentUsers) ]); - + return true; - + } catch (\Exception $e) { $this->logger->error('OrganisatieService: Failed to add users to organization', [ 'organizationUuid' => $organizationUuid, 'error' => $e->getMessage() ]); + var_dump($e->getMessage(), $e->getTraceAsString()); return false; } } @@ -314,7 +330,7 @@ public function getAdminGroupUsernames(): array try { $groupManager = \OC::$server->get('OCP\IGroupManager'); $adminGroup = $groupManager->get('admin'); - + if ($adminGroup) { $adminUsers = $adminGroup->getUsers(); $adminUsernames = []; @@ -323,7 +339,7 @@ public function getAdminGroupUsernames(): array } return $adminUsernames; } - + return []; } catch (\Exception $e) { $this->logger->error('OrganisatieService: Failed to get admin users', [ @@ -332,4 +348,4 @@ public function getAdminGroupUsernames(): array return []; } } -} \ No newline at end of file +} diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index 30e01ca9..62fca143 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -159,6 +159,7 @@ public function performOrganizationsSync(): array $org = $this->ensureOrganisationEntity($object,$stats); + } return $stats; @@ -247,6 +248,8 @@ public function performUserSync(): array $sql = $qb->getSQL(); // var_dump($sql); $users = $qb->execute()->fetchAll(); + +// var_dump(count($users)); // var_dump('hello'); foreach($users as $user) { $this->organisatieService->addUsersToOrganization($user['organisation'], [$user['username']]); @@ -496,12 +499,14 @@ private function ensureOrganisationEntity(object $organisatieObject, array &$sta 'newActive' => $shouldBeActive ]); + $isActive = $organisationEntity->getActive(); + $organisationEntity->setActive($shouldBeActive); $organisationMapper->save($organisationEntity); $stats['entitiesUpdated']++; // Send activation email if organization became active - if ($shouldBeActive && !$organisationEntity->getActive()) { + if ($shouldBeActive && !$isActive) { $emailSent = $this->sendOrganizationActivationEmail($objectData); if ($emailSent) { $this->logger->info('OrganizationSyncService: Organization activation email sent successfully', [ diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php index c2eb8bf3..5e4832d6 100644 --- a/lib/Service/SettingsService.php +++ b/lib/Service/SettingsService.php @@ -156,7 +156,7 @@ public function getSettings(): array { // Initialize the data array $data = []; - + // Define the register-specific configuration $data['registerTypes'] = [ 'amef' => [ @@ -165,18 +165,18 @@ public function getSettings(): array 'objectTypes' => ['organization', 'element', 'relationship', 'view', 'model', 'property'] // Complete AMEF object types ], 'voorzieningen' => [ - 'name' => 'Voorzieningen', + 'name' => 'Voorzieningen', 'description' => 'Voorzieningen register for software catalog services', 'objectTypes' => ['organisatie', 'contactpersoon'] // Voorzieningen uses organisatie and contactpersoon schemas ] ]; - + // Deprecated: For backward compatibility only - use registerTypes instead $data['objectTypes'] = [ 'organization', 'contact', ]; - + $data['openRegisters'] = false; $data['availableRegisters'] = []; @@ -186,7 +186,7 @@ public function getSettings(): array if ($openRegisters !== null) { $data['openRegisters'] = true; $rawRegisters = $openRegisters->getRegisters(); - + // Filter schemas to remove properties field for cleaner response $data['availableRegisters'] = array_map(function($register) { if (isset($register['schemas']) && is_array($register['schemas'])) { @@ -220,14 +220,14 @@ public function getSettings(): array $defaults["{$registerType}_{$objectType}_register"] = ''; } } - + // Also maintain backward compatibility for the old structure foreach ($data['objectTypes'] as $type) { $defaults["{$type}_source"] = 'openregister'; $defaults["{$type}_schema"] = ''; $defaults["{$type}_register"] = ''; } - + // Note: Old individual config keys are no longer used // They are maintained only for backward compatibility during migration @@ -300,7 +300,7 @@ public function autoConfigure(bool $force = false): array /** * Auto-configures settings specifically after importing the softwarecatalogus_register.json - * + * * This method looks for the voorzieningen register and automatically configures * the organisatie and contactpersoon schemas, and creates required user groups. * @@ -327,19 +327,19 @@ public function autoConfigureAfterImport(): array } $configuration = []; - + // Step 1: Create required user groups $this->logger->info('Creating required user groups'); $this->createRequiredUserGroups(); $this->logger->info('User groups created successfully'); - + // Look for the voorzieningen register $voorzieningenRegister = null; foreach ($registers as $register) { $registerTitle = strtolower($register['title'] ?? ''); $registerSlug = strtolower($register['slug'] ?? ''); - - if (stripos($registerTitle, 'voorzieningen') !== false || + + if (stripos($registerTitle, 'voorzieningen') !== false || stripos($registerSlug, 'voorzieningen') !== false || $registerTitle === 'voorzieningen' || $registerSlug === 'voorzieningen') { @@ -364,50 +364,50 @@ public function autoConfigureAfterImport(): array foreach ($voorzieningenRegister['schemas'] as $schema) { $schemaTitle = strtolower($schema['title'] ?? ''); $schemaSlug = strtolower($schema['slug'] ?? ''); - + // Look for organisatie schema - if (stripos($schemaTitle, 'organisatie') !== false || + if (stripos($schemaTitle, 'organisatie') !== false || stripos($schemaSlug, 'organisatie') !== false || $schemaTitle === 'organisatie' || $schemaSlug === 'organisatie') { - + // Set voorzieningen_organisatie configuration $configuration['voorzieningen_organisatie_source'] = 'openregister'; $configuration['voorzieningen_organisatie_register'] = (string) $voorzieningenRegister['id']; $configuration['voorzieningen_organisatie_schema'] = (string) $schema['id']; - + // Set sync-compatible configuration (OrganizationSyncService expects this key) $configuration['voorzieningen_register'] = (string) $voorzieningenRegister['id']; - + // Also set backward compatibility organization configuration $configuration['organization_source'] = 'openregister'; $configuration['organization_register'] = (string) $voorzieningenRegister['id']; $configuration['organization_schema'] = (string) $schema['id']; - + $this->logger->info('Configured organisatie schema', [ 'schema_id' => $schema['id'], 'schema_title' => $schema['title'] ]); } // Look for contactpersoon schema - else if (stripos($schemaTitle, 'contactpersoon') !== false || + else if (stripos($schemaTitle, 'contactpersoon') !== false || stripos($schemaSlug, 'contactpersoon') !== false || $schemaTitle === 'contactpersoon' || $schemaSlug === 'contactpersoon') { - + // Set voorzieningen_contactpersoon configuration $configuration['voorzieningen_contactpersoon_source'] = 'openregister'; $configuration['voorzieningen_contactpersoon_register'] = (string) $voorzieningenRegister['id']; $configuration['voorzieningen_contactpersoon_schema'] = (string) $schema['id']; - + // Set sync-compatible configuration (OrganizationSyncService expects this key) $configuration['voorzieningen_register'] = (string) $voorzieningenRegister['id']; - + // Also set backward compatibility contact configuration $configuration['contact_source'] = 'openregister'; $configuration['contact_register'] = (string) $voorzieningenRegister['id']; $configuration['contact_schema'] = (string) $schema['id']; - + $this->logger->info('Configured contactpersoon schema', [ 'schema_id' => $schema['id'], 'schema_title' => $schema['title'] @@ -430,7 +430,7 @@ public function autoConfigureAfterImport(): array $this->logger->info('Auto-configuration marked as completed'); return $configuration; - + } catch (\Exception $e) { throw new \RuntimeException('Failed to auto-configure after import: ' . $e->getMessage()); } @@ -446,28 +446,28 @@ public function autoConfigureAfterImport(): array public function getSchemaIdForObjectType(string $objectType): ?int { $startTime = microtime(true); - + $this->logger->debug("SettingsService: Starting schema ID lookup", [ 'objectType' => $objectType, 'timestamp' => date('Y-m-d H:i:s') ]); - + // First try register-specific configuration // Check for AMEF register specific schemas if ($objectType === 'organization') { $this->logger->debug("SettingsService: Checking AMEF organization schema", [ 'objectType' => $objectType ]); - + $schemaId = $this->config->getValueString($this->_appName, 'amef_organization_schema', ''); - + $this->logger->debug("SettingsService: AMEF organization schema result", [ 'objectType' => $objectType, 'configKey' => 'amef_organization_schema', 'rawValue' => $schemaId, 'isEmpty' => empty($schemaId) ]); - + if (!empty($schemaId)) { $result = (int) $schemaId; $this->logger->info("SettingsService: Found AMEF organization schema", [ @@ -477,21 +477,21 @@ public function getSchemaIdForObjectType(string $objectType): ?int ]); return $result; } - + // Also check voorzieningen register for organization/organisatie $this->logger->debug("SettingsService: Checking voorzieningen organisatie schema for organization", [ 'objectType' => $objectType ]); - + $schemaId = $this->config->getValueString($this->_appName, 'voorzieningen_organisatie_schema', ''); - + $this->logger->debug("SettingsService: Voorzieningen organisatie schema result", [ 'objectType' => $objectType, 'configKey' => 'voorzieningen_organisatie_schema', 'rawValue' => $schemaId, 'isEmpty' => empty($schemaId) ]); - + if (!empty($schemaId)) { $result = (int) $schemaId; $this->logger->info("SettingsService: Found voorzieningen organisatie schema for organization", [ @@ -502,21 +502,21 @@ public function getSchemaIdForObjectType(string $objectType): ?int return $result; } } - + if ($objectType === 'organisatie') { $this->logger->debug("SettingsService: Checking voorzieningen organisatie schema", [ 'objectType' => $objectType ]); - + $schemaId = $this->config->getValueString($this->_appName, 'voorzieningen_organisatie_schema', ''); - + $this->logger->debug("SettingsService: Voorzieningen organisatie schema result", [ 'objectType' => $objectType, 'configKey' => 'voorzieningen_organisatie_schema', 'rawValue' => $schemaId, 'isEmpty' => empty($schemaId) ]); - + if (!empty($schemaId)) { $result = (int) $schemaId; $this->logger->info("SettingsService: Found voorzieningen organisatie schema", [ @@ -527,21 +527,21 @@ public function getSchemaIdForObjectType(string $objectType): ?int return $result; } } - + if ($objectType === 'contactpersoon') { $this->logger->debug("SettingsService: Checking voorzieningen contactpersoon schema", [ 'objectType' => $objectType ]); - + $schemaId = $this->config->getValueString($this->_appName, 'voorzieningen_contactpersoon_schema', ''); - + $this->logger->debug("SettingsService: Voorzieningen contactpersoon schema result", [ 'objectType' => $objectType, 'configKey' => 'voorzieningen_contactpersoon_schema', 'rawValue' => $schemaId, 'isEmpty' => empty($schemaId) ]); - + if (!empty($schemaId)) { $result = (int) $schemaId; $this->logger->info("SettingsService: Found voorzieningen contactpersoon schema", [ @@ -552,22 +552,22 @@ public function getSchemaIdForObjectType(string $objectType): ?int return $result; } } - + // Fall back to generic configuration for backward compatibility $this->logger->debug("SettingsService: Checking generic configuration", [ 'objectType' => $objectType, 'configKey' => "{$objectType}_schema" ]); - + $schemaId = $this->config->getValueString($this->_appName, "{$objectType}_schema", ''); - + $this->logger->debug("SettingsService: Generic configuration result", [ 'objectType' => $objectType, 'configKey' => "{$objectType}_schema", 'rawValue' => $schemaId, 'isEmpty' => empty($schemaId) ]); - + if ($schemaId) { $result = (int) $schemaId; $this->logger->info("SettingsService: Found generic schema configuration", [ @@ -577,7 +577,7 @@ public function getSchemaIdForObjectType(string $objectType): ?int ]); return $result; } - + $this->logger->warning("SettingsService: No schema ID found for object type", [ 'objectType' => $objectType, 'checkedConfigurations' => [ @@ -588,7 +588,7 @@ public function getSchemaIdForObjectType(string $objectType): ?int ], 'lookupTime' => round((microtime(true) - $startTime) * 1000, 2) . 'ms' ]); - + return null; } @@ -613,24 +613,24 @@ public function getRegisterIdForObjectType(string $objectType): ?int public function getVoorzieningenRegisterId(): ?int { $startTime = microtime(true); - + $this->logger->debug("SettingsService: Starting voorzieningen register ID lookup", [ 'timestamp' => date('Y-m-d H:i:s') ]); - + // Try voorzieningen-specific configuration first $this->logger->debug("SettingsService: Checking voorzieningen organisatie register", [ 'configKey' => 'voorzieningen_organisatie_register' ]); - + $registerId = $this->config->getValueString($this->_appName, 'voorzieningen_organisatie_register', ''); - + $this->logger->debug("SettingsService: Voorzieningen organisatie register result", [ 'configKey' => 'voorzieningen_organisatie_register', 'rawValue' => $registerId, 'isEmpty' => empty($registerId) ]); - + if (!empty($registerId)) { $result = (int) $registerId; $this->logger->info("SettingsService: Found voorzieningen organisatie register", [ @@ -639,20 +639,20 @@ public function getVoorzieningenRegisterId(): ?int ]); return $result; } - + // Also try contactpersoon as fallback $this->logger->debug("SettingsService: Checking voorzieningen contactpersoon register", [ 'configKey' => 'voorzieningen_contactpersoon_register' ]); - + $registerId = $this->config->getValueString($this->_appName, 'voorzieningen_contactpersoon_register', ''); - + $this->logger->debug("SettingsService: Voorzieningen contactpersoon register result", [ 'configKey' => 'voorzieningen_contactpersoon_register', 'rawValue' => $registerId, 'isEmpty' => empty($registerId) ]); - + if (!empty($registerId)) { $result = (int) $registerId; $this->logger->info("SettingsService: Found voorzieningen contactpersoon register", [ @@ -661,14 +661,14 @@ public function getVoorzieningenRegisterId(): ?int ]); return $result; } - + // Fall back to organization register for backward compatibility $this->logger->debug("SettingsService: Checking organization register for backward compatibility", [ 'configKey' => 'organization_register' ]); - + $result = $this->getRegisterIdForObjectType('organization'); - + if ($result !== null) { $this->logger->info("SettingsService: Found organization register for backward compatibility", [ 'registerId' => $result, @@ -676,7 +676,7 @@ public function getVoorzieningenRegisterId(): ?int ]); return $result; } - + $this->logger->warning("SettingsService: No register ID found for voorzieningen", [ 'checkedConfigurations' => [ 'voorzieningen_organisatie_register' => true, @@ -685,7 +685,7 @@ public function getVoorzieningenRegisterId(): ?int ], 'lookupTime' => round((microtime(true) - $startTime) * 1000, 2) . 'ms' ]); - + return null; } @@ -697,14 +697,14 @@ public function getVoorzieningenRegisterId(): ?int public function isFullyConfigured(): bool { $objectTypes = ['organization', 'contact']; - + foreach ($objectTypes as $type) { $schemaId = $this->getSchemaIdForObjectType($type); if (!$schemaId) { return false; } } - + return true; } @@ -717,18 +717,18 @@ public function getConfigurationStatus(): array { $objectTypes = ['organization', 'contact']; $status = []; - + foreach ($objectTypes as $type) { $schemaId = $this->getSchemaIdForObjectType($type); $registerId = $this->getRegisterIdForObjectType($type); - + $status[$type] = [ 'configured' => !empty($schemaId) && !empty($registerId), 'schemaId' => $schemaId, 'registerId' => $registerId, ]; } - + return $status; } @@ -761,7 +761,7 @@ public function initialize(?string $minOpenRegisterVersion = self::MIN_OPENREGIS try { // Check if OpenRegister is installed and enabled $checkStart = microtime(true); - + if (!$this->isOpenRegisterInstalled($minOpenRegisterVersion)) { $error = 'OpenRegister is not installed or does not meet minimum version requirements'; $results['errors'][] = $error; @@ -778,7 +778,7 @@ public function initialize(?string $minOpenRegisterVersion = self::MIN_OPENREGIS $results['openRegister'] = true; $results['timing']['openregister_check'] = round((microtime(true) - $checkStart) * 1000, 2) . 'ms'; - + $this->logger->info('SettingsService: OpenRegister is available'); // Load settings from file if needed (do this first) @@ -809,7 +809,7 @@ public function initialize(?string $minOpenRegisterVersion = self::MIN_OPENREGIS $autoConfigStart = microtime(true); if (!$this->isFullyConfigured()) { $this->logger->info('SettingsService: App not fully configured, attempting auto-configuration'); - + try { // First try the post-import auto-configuration (more specific) $configuration = $this->autoConfigureAfterImport(); @@ -846,7 +846,7 @@ public function initialize(?string $minOpenRegisterVersion = self::MIN_OPENREGIS // Final configuration status check $results['fullyConfigured'] = $this->isFullyConfigured(); - + if (!$results['fullyConfigured']) { $warning = 'App is not fully configured after initialization. Manual configuration may be required.'; $results['warnings'][] = $warning; @@ -856,11 +856,11 @@ public function initialize(?string $minOpenRegisterVersion = self::MIN_OPENREGIS } $results['timing']['total'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms'; - + $this->logger->info('SettingsService: Initialization completed', [ 'results' => [ 'openRegister' => $results['openRegister'], - 'autoConfigured' => $results['autoConfigured'], + 'autoConfigured' => $results['autoConfigured'], 'fullyConfigured' => $results['fullyConfigured'], 'settingsLoaded' => $results['settingsLoaded'], 'errors' => count($results['errors']), @@ -893,24 +893,24 @@ public function initialize(?string $minOpenRegisterVersion = self::MIN_OPENREGIS public function loadSettings(bool $force = false): array { $results = []; - + try { // Load settings from merged softwarecatalogus_register.json $softwareCatalogPath = __DIR__ . '/../Settings/softwarecatalogus_register.json'; if (file_exists($softwareCatalogPath)) { $softwareCatalogContent = file_get_contents($softwareCatalogPath); $softwareCatalogSettings = json_decode($softwareCatalogContent, true); - + if (json_last_error() === JSON_ERROR_NONE) { $results['softwarecatalog'] = $softwareCatalogSettings; - + // Import via configuration service if available with version checking try { $configurationService = $this->getConfigurationService(); - + // Get the current app version dynamically $currentAppVersion = $this->appManager->getAppVersion(\OCA\SoftwareCatalog\AppInfo\Application::APP_ID); - + $importResult = $configurationService->importFromJson( data: $softwareCatalogSettings, owner: null, @@ -918,7 +918,7 @@ public function loadSettings(bool $force = false): array version: $currentAppVersion, force: $force ); - + $results['softwarecatalog_imported'] = true; $results['import_result'] = $importResult; } catch (\Exception $e) { @@ -935,7 +935,7 @@ public function loadSettings(bool $force = false): array } return $results; - + } catch (\Exception $e) { throw new \RuntimeException('Failed to load settings: ' . $e->getMessage()); } @@ -949,7 +949,7 @@ public function loadSettings(bool $force = false): array public function getGenericUserGroups(): array { $groupsJson = $this->config->getValueString($this->_appName, 'generic_user_groups', ''); - + if (empty($groupsJson)) { // Return default groups if no configuration exists return [ @@ -968,14 +968,14 @@ public function getGenericUserGroups(): array * Sets the list of generic user groups in configuration * * @param array $groups Array of generic user groups - * + * * @return void */ public function setGenericUserGroups(array $groups): void { $groupsJson = json_encode($groups, JSON_THROW_ON_ERROR); $this->config->setValueString($this->_appName, 'generic_user_groups', $groupsJson); - + $this->logger->info( 'Updated generic user groups configuration', [ @@ -992,7 +992,7 @@ public function setGenericUserGroups(array $groups): void public function getOrganizationAdminGroups(): array { $groupsJson = $this->config->getValueString($this->_appName, 'organization_admin_groups', ''); - + if (empty($groupsJson)) { // Return default groups if no configuration exists return [ @@ -1008,14 +1008,14 @@ public function getOrganizationAdminGroups(): array * Sets the list of organization admin groups in configuration * * @param array $groups Array of organization admin groups - * + * * @return void */ public function setOrganizationAdminGroups(array $groups): void { $groupsJson = json_encode($groups, JSON_THROW_ON_ERROR); $this->config->setValueString($this->_appName, 'organization_admin_groups', $groupsJson); - + $this->logger->info( 'Updated organization admin groups configuration', [ @@ -1032,7 +1032,7 @@ public function setOrganizationAdminGroups(array $groups): void public function getSuperUserGroups(): array { $groupsJson = $this->config->getValueString($this->_appName, 'super_user_groups', ''); - + if (empty($groupsJson)) { // Return default groups if no configuration exists return [ @@ -1049,14 +1049,14 @@ public function getSuperUserGroups(): array * Sets the list of super user groups in configuration * * @param array $groups Array of super user groups - * + * * @return void */ public function setSuperUserGroups(array $groups): void { $groupsJson = json_encode($groups, JSON_THROW_ON_ERROR); $this->config->setValueString($this->_appName, 'super_user_groups', $groupsJson); - + $this->logger->info( 'Updated super user groups configuration', [ @@ -1069,7 +1069,7 @@ public function setSuperUserGroups(array $groups): void * Validates a list of group names * * @param array $groups Array of group names to validate - * + * * @return array Array with validation results */ public function validateGroups(array $groups): array @@ -1079,24 +1079,24 @@ public function validateGroups(array $groups): array 'invalid' => [], 'errors' => [] ]; - + foreach ($groups as $groupName) { if (empty($groupName) || !is_string($groupName)) { $results['invalid'][] = $groupName; $results['errors'][] = 'Group name cannot be empty'; continue; } - + // Check for invalid characters if (preg_match('/[^a-zA-Z0-9._-]/', $groupName)) { $results['invalid'][] = $groupName; $results['errors'][] = "Group name '{$groupName}' contains invalid characters"; continue; } - + $results['valid'][] = $groupName; } - + return $results; } @@ -1111,7 +1111,7 @@ public function createAndConfigureUserGroups(): array { try { $this->logger->info('SettingsService: Starting user group creation and configuration'); - + $result = [ 'success' => true, 'message' => 'User groups configured successfully', @@ -1119,10 +1119,10 @@ public function createAndConfigureUserGroups(): array 'existing' => [], 'total' => 0 ]; - + // Get the group manager $groupManager = \OC::$server->getGroupManager(); - + // Define the required groups (matching role-based system) $requiredGroups = [ // Role-based user groups (exact match with ContactPersoon roles) @@ -1132,28 +1132,28 @@ public function createAndConfigureUserGroups(): array 'functioneel-beheerder' => 'Manages functional aspects of the system', 'vng-raadpleger' => 'Views VNG-related information', 'organisatie-beheerder' => 'Manages organization data and settings', - + // Plural form for organization contacts 'organisaties-beheerder' => 'Organization administrators (plural)', - + // Special groups 'ambtenaar' => 'Civil servants from Gemeente organizations', 'software-catalog-users' => 'General software catalog users', - + // Super user groups 'software-catalog-admins' => 'Software catalog system administrators' ]; - + foreach ($requiredGroups as $groupId => $description) { $this->logger->debug("SettingsService: Processing group: {$groupId}"); - + // Check if group already exists if ($groupManager->groupExists($groupId)) { $result['existing'][] = $groupId; $this->logger->debug("SettingsService: Group {$groupId} already exists"); continue; } - + // Create the group $group = $groupManager->createGroup($groupId); if ($group !== false) { @@ -1164,13 +1164,13 @@ public function createAndConfigureUserGroups(): array $result['success'] = false; } } - + $result['total'] = count($requiredGroups); - + // Update the configuration with the correct role-based groups $this->setGenericUserGroups([ 'aanbod-beheerder', - 'gebruik-beheerder', + 'gebruik-beheerder', 'gebruik-raadpleger', 'functioneel-beheerder', 'vng-raadpleger', @@ -1178,35 +1178,35 @@ public function createAndConfigureUserGroups(): array 'ambtenaar', 'software-catalog-users' ]); - + $this->setOrganizationAdminGroups([ 'organisaties-beheerder', 'organisatie-beheerder' ]); - + $this->setSuperUserGroups([ 'admin', // Keep existing admin group 'software-catalog-admins' ]); - + $createdCount = count($result['created']); $existingCount = count($result['existing']); - + if ($createdCount > 0) { $result['message'] = "Created {$createdCount} new groups, {$existingCount} already existed"; } else { $result['message'] = "All {$existingCount} required groups already exist"; } - + $this->logger->info('SettingsService: User group creation and configuration completed', [ 'created_groups' => $result['created'], 'existing_groups' => $result['existing'], 'total_required' => $result['total'], 'success' => $result['success'] ]); - + return $result; - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to create and configure user groups', [ 'exception' => $e->getMessage() @@ -1227,7 +1227,7 @@ public function createAndConfigureUserGroups(): array * * This method creates the default user groups needed for proper operation: * - Generic user groups for general access - * - Organization admin groups for managing organizations + * - Organization admin groups for managing organizations * - Super user groups for system administration * * @return void @@ -1238,10 +1238,10 @@ private function createRequiredUserGroups(): void { try { $this->logger->info('Starting creation of required user groups'); - + // Get the group manager $groupManager = \OC::$server->getGroupManager(); - + // Define the required groups (matching role-based system) $requiredGroups = [ // Role-based user groups (exact match with ContactPersoon roles) @@ -1251,31 +1251,31 @@ private function createRequiredUserGroups(): void 'functioneel-beheerder' => 'Manages functional aspects of the system', 'vng-raadpleger' => 'Views VNG-related information', 'organisatie-beheerder' => 'Manages organization data and settings', - + // Plural form for organization contacts 'organisaties-beheerder' => 'Organization administrators (plural)', - + // Special groups 'ambtenaar' => 'Civil servants from Gemeente organizations', 'software-catalog-users' => 'General software catalog users', - + // Super user groups 'software-catalog-admins' => 'Software catalog system administrators' ]; - + $createdGroups = []; $existingGroups = []; - + foreach ($requiredGroups as $groupId => $description) { $this->logger->debug("Processing group: {$groupId}"); - + // Check if group already exists if ($groupManager->groupExists($groupId)) { $existingGroups[] = $groupId; $this->logger->debug("Group {$groupId} already exists, skipping"); continue; } - + // Create the group $group = $groupManager->createGroup($groupId); if ($group !== false) { @@ -1285,11 +1285,11 @@ private function createRequiredUserGroups(): void $this->logger->warning("Failed to create user group: {$groupId}"); } } - + // Update the configuration with the correct role-based groups $this->setGenericUserGroups([ 'aanbod-beheerder', - 'gebruik-beheerder', + 'gebruik-beheerder', 'gebruik-raadpleger', 'functioneel-beheerder', 'vng-raadpleger', @@ -1297,23 +1297,23 @@ private function createRequiredUserGroups(): void 'ambtenaar', 'software-catalog-users' ]); - + $this->setOrganizationAdminGroups([ 'organisaties-beheerder', 'organisatie-beheerder' ]); - + $this->setSuperUserGroups([ 'admin', // Keep existing admin group 'software-catalog-admins' ]); - + $this->logger->info('User group creation completed', [ 'created_groups' => $createdGroups, 'existing_groups' => $existingGroups, 'total_required' => count($requiredGroups) ]); - + } catch (\Exception $e) { $this->logger->error('Failed to create required user groups: ' . $e->getMessage(), [ 'exception' => $e @@ -1330,13 +1330,13 @@ private function createRequiredUserGroups(): void public function getAllGroups(): array { $groups = []; - + // Get group manager if possible if ($this->appManager->isInstalled('user_management')) { try { $groupManager = \OC::$server->getGroupManager(); $allGroups = $groupManager->search(''); - + foreach ($allGroups as $group) { $groups[] = [ 'id' => $group->getGID(), @@ -1349,7 +1349,7 @@ public function getAllGroups(): array $this->logger->error('Failed to get all groups: ' . $e->getMessage()); } } - + return $groups; } @@ -1361,7 +1361,7 @@ public function getAllGroups(): array public function getEmailSettings(): array { $this->logger->debug('SoftwareCatalog: Loading email settings from configuration'); - + $settings = [ 'enabled' => $this->config->getValueString($this->_appName, 'email_enabled', 'false') === 'true', 'senderEmail' => $this->config->getValueString($this->_appName, 'sender_email', 'noreply@softwarecatalogus.nl'), @@ -1371,36 +1371,37 @@ public function getEmailSettings(): array 'organizationActivationEnabled' => $this->config->getValueString($this->_appName, 'email_org_activation_enabled', 'true') === 'true', 'userCreationEnabled' => $this->config->getValueString($this->_appName, 'email_user_creation_enabled', 'true') === 'true', 'userPasswordEnabled' => $this->config->getValueString($this->_appName, 'email_user_password_enabled', 'true') === 'true', - + 'userOrganisationEnabled' => $this->config->getValueString($this->_appName, 'email_user_organisation_enabled', 'true') === 'true', + // Symfony Mailer transport configuration 'transportType' => $this->config->getValueString($this->_appName, 'email_transport_type', 'smtp'), - + // SMTP configuration 'smtpHost' => $this->config->getValueString($this->_appName, 'email_smtp_host', 'localhost'), 'smtpPort' => (int) $this->config->getValueString($this->_appName, 'email_smtp_port', '587'), 'smtpEncryption' => $this->config->getValueString($this->_appName, 'email_smtp_encryption', 'tls'), 'smtpUsername' => $this->config->getValueString($this->_appName, 'email_smtp_username', ''), 'smtpPassword' => $this->config->getValueString($this->_appName, 'email_smtp_password', ''), - + // SendGrid configuration 'sendgridApiKey' => $this->config->getValueString($this->_appName, 'email_sendgrid_api_key', ''), - + // Mailgun configuration 'mailgunApiKey' => $this->config->getValueString($this->_appName, 'email_mailgun_api_key', ''), 'mailgunDomain' => $this->config->getValueString($this->_appName, 'email_mailgun_domain', ''), - + // Postmark configuration 'postmarkApiKey' => $this->config->getValueString($this->_appName, 'email_postmark_api_key', ''), - + // Amazon SES configuration 'sesAccessKey' => $this->config->getValueString($this->_appName, 'email_ses_access_key', ''), 'sesSecretKey' => $this->config->getValueString($this->_appName, 'email_ses_secret_key', ''), 'sesRegion' => $this->config->getValueString($this->_appName, 'email_ses_region', 'us-east-1'), - + // Mailjet configuration 'mailjetApiKey' => $this->config->getValueString($this->_appName, 'email_mailjet_api_key', ''), 'mailjetSecretKey' => $this->config->getValueString($this->_appName, 'email_mailjet_secret_key', ''), - + // Templates 'templates' => [ 'organization_registration' => $this->getEmailTemplate('organization_registration'), @@ -1409,7 +1410,7 @@ public function getEmailSettings(): array 'user_password' => $this->getEmailTemplate('user_password'), ] ]; - + $this->logger->info('SoftwareCatalog: Email settings loaded from configuration', [ 'enabled' => $settings['enabled'], 'transport_type' => $settings['transportType'], @@ -1420,7 +1421,7 @@ public function getEmailSettings(): array 'mailjet_secret_key_length' => strlen($settings['mailjetSecretKey']), 'test_receiver_override' => $settings['testReceiverOverride'] ]); - + return $settings; } @@ -1442,48 +1443,48 @@ public function updateEmailSettings(array $emailSettings): array 'organizationActivationEnabled' => 'email_org_activation_enabled', 'userCreationEnabled' => 'email_user_creation_enabled', 'userPasswordEnabled' => 'email_user_password_enabled', - + 'userOrganisationEnabled' => 'email_user_organisation_enabled', + // Symfony Mailer transport configuration 'transportType' => 'email_transport_type', - + // SMTP configuration 'smtpHost' => 'email_smtp_host', 'smtpPort' => 'email_smtp_port', 'smtpEncryption' => 'email_smtp_encryption', 'smtpUsername' => 'email_smtp_username', 'smtpPassword' => 'email_smtp_password', - + // SendGrid configuration 'sendgridApiKey' => 'email_sendgrid_api_key', - + // Mailgun configuration 'mailgunApiKey' => 'email_mailgun_api_key', 'mailgunDomain' => 'email_mailgun_domain', - + // Postmark configuration 'postmarkApiKey' => 'email_postmark_api_key', - + // Amazon SES configuration 'sesAccessKey' => 'email_ses_access_key', 'sesSecretKey' => 'email_ses_secret_key', 'sesRegion' => 'email_ses_region', - + // Mailjet configuration 'mailjetApiKey' => 'email_mailjet_api_key', 'mailjetSecretKey' => 'email_mailjet_secret_key', ]; - $updatedSettings = []; - + foreach ($allowedSettings as $settingKey => $configKey) { if (array_key_exists($settingKey, $emailSettings)) { $value = $emailSettings[$settingKey]; - + // Convert boolean values to strings if (is_bool($value)) { $value = $value ? 'true' : 'false'; } - + $this->config->setValueString($this->_appName, $configKey, (string) $value); $updatedSettings[$settingKey] = $this->config->getValueString($this->_appName, $configKey); } @@ -1510,7 +1511,7 @@ public function getEmailTemplate(string $templateName): string { $configKey = "email_template_{$templateName}"; $defaultTemplate = $this->getDefaultEmailTemplate($templateName); - + return $this->config->getValueString($this->_appName, $configKey, $defaultTemplate); } @@ -1527,14 +1528,14 @@ public function updateEmailTemplate(string $templateName, string $templateConten try { $configKey = "email_template_{$templateName}"; $this->config->setValueString($this->_appName, $configKey, $templateContent); - + $this->logger->info( 'Email template updated successfully', [ 'templateName' => $templateName ] ); - + return true; } catch (\Exception $e) { $this->logger->error( @@ -1666,13 +1667,13 @@ public function getEmailTemplateVariables(string $templateName): array public function getDebugInfo(): array { $debugInfo = []; - + try { // Get current configuration values $debugInfo['configuration'] = []; $configKeys = [ 'amef_organization_source', - 'amef_organization_register', + 'amef_organization_register', 'amef_organization_schema', 'voorzieningen_organisatie_source', 'voorzieningen_organisatie_register', @@ -1688,19 +1689,19 @@ public function getDebugInfo(): array 'contact_register', 'contact_schema' ]; - + foreach ($configKeys as $key) { $value = $this->config->getValueString($this->_appName, $key, ''); $debugInfo['configuration'][$key] = empty($value) ? '' : $value; } - + // Get group configurations $debugInfo['userGroups'] = [ 'generic' => $this->getGenericUserGroups(), 'organizationAdmin' => $this->getOrganizationAdminGroups(), 'superUser' => $this->getSuperUserGroups() ]; - + // Get email settings (without sensitive data) $emailSettings = $this->getEmailSettings(); unset($emailSettings['smtpPassword']); @@ -1710,14 +1711,14 @@ public function getDebugInfo(): array unset($emailSettings['sesSecretKey']); unset($emailSettings['mailjetSecretKey']); $debugInfo['emailSettings'] = $emailSettings; - + // Get OpenRegister status $debugInfo['openRegister'] = [ 'installed' => $this->isOpenRegisterInstalled(), 'enabled' => $this->isOpenRegisterEnabled(), 'availableRegisters' => [] ]; - + if ($debugInfo['openRegister']['installed'] && $debugInfo['openRegister']['enabled']) { try { $objectService = $this->getObjectService(); @@ -1726,11 +1727,11 @@ public function getDebugInfo(): array $debugInfo['openRegister']['error'] = $e->getMessage(); } } - + } catch (\Exception $e) { $debugInfo['error'] = $e->getMessage(); } - + return $debugInfo; } @@ -1739,7 +1740,7 @@ public function getDebugInfo(): array * * @param string $email The email address to send to * @param array $emailSettings The email settings to use - * + * * @return array Result of the test email */ public function sendTestEmail(string $email, array $emailSettings = []): array @@ -1752,17 +1753,17 @@ public function sendTestEmail(string $email, array $emailSettings = []): array 'message' => 'Email address is required' ]; } - + $this->logger->info('SoftwareCatalog: Starting sendTestEmail process', [ 'recipient' => $email, 'has_email_settings' => !empty($emailSettings) ]); - + try { // Ensure vendor autoloader is loaded include_once __DIR__ . '/../../vendor/autoload.php'; $this->logger->debug('SoftwareCatalog: Vendor autoloader loaded'); - + // Use provided settings or fall back to stored settings if (empty($emailSettings)) { $emailSettings = $this->getEmailSettings(); @@ -1770,7 +1771,7 @@ public function sendTestEmail(string $email, array $emailSettings = []): array } else { $this->logger->info('SoftwareCatalog: Using provided email settings'); } - + // Log the email configuration (without sensitive data) $this->logger->info('SoftwareCatalog: Email configuration', [ 'enabled' => $emailSettings['enabled'] ?? false, @@ -1780,7 +1781,7 @@ public function sendTestEmail(string $email, array $emailSettings = []): array 'has_mailjet_api_key' => !empty($emailSettings['mailjetApiKey']), 'has_mailjet_secret_key' => !empty($emailSettings['mailjetSecretKey']), ]); - + // Check if email is enabled if (!($emailSettings['enabled'] ?? false)) { $this->logger->warning('SoftwareCatalog: Email notifications are disabled'); @@ -1789,7 +1790,7 @@ public function sendTestEmail(string $email, array $emailSettings = []): array 'message' => 'Email notifications are disabled' ]; } - + // Use test receiver override if configured $recipient = $emailSettings['testReceiverOverride'] ?? $email; $this->logger->info('SoftwareCatalog: Final recipient determined', [ @@ -1797,27 +1798,27 @@ public function sendTestEmail(string $email, array $emailSettings = []): array 'final_recipient' => $recipient, 'using_override' => !empty($emailSettings['testReceiverOverride']) ]); - + // Create transport based on configuration $this->logger->info('SoftwareCatalog: Creating email transport'); $transport = $this->createEmailTransport($emailSettings); $this->logger->info('SoftwareCatalog: Email transport created successfully'); - + $mailer = new Mailer($transport); $this->logger->info('SoftwareCatalog: Mailer instance created'); - + // Create test email $senderEmail = $emailSettings['senderEmail'] ?? 'noreply@softwarecatalogus.nl'; $senderName = $emailSettings['senderName'] ?? 'Software Catalogus'; $transportType = $emailSettings['transportType'] ?? 'smtp'; - + $this->logger->info('SoftwareCatalog: Creating email message', [ 'sender_email' => $senderEmail, 'sender_name' => $senderName, 'transport_type' => $transportType, 'recipient' => $recipient ]); - + $email = (new Email()) ->from(new Address($senderEmail, $senderName)) ->to($recipient) @@ -1830,23 +1831,23 @@ public function sendTestEmail(string $email, array $emailSettings = []): array

Datum: ' . date('Y-m-d H:i:s') . '

Met vriendelijke groet,
Het Software Catalogus Team

'); - + $this->logger->info('SoftwareCatalog: Email message created, attempting to send'); - + // Send the email $mailer->send($email); - + $this->logger->info('SoftwareCatalog: Email sent successfully via Symfony Mailer', [ 'recipient' => $recipient, 'transport' => $transportType, 'sender' => $senderEmail ]); - + return [ 'success' => true, 'message' => "Test email sent successfully to {$recipient} via {$transportType}" ]; - + } catch (\Exception $e) { $this->logger->error('SoftwareCatalog: Failed to send test email', [ 'recipient' => $email, @@ -1866,7 +1867,7 @@ public function sendTestEmail(string $email, array $emailSettings = []): array * Test email connection without sending an actual email * * @param array $emailSettings The email settings to test - * + * * @return array Result of the connection test */ public function testEmailConnection(array $emailSettings = []): array @@ -1874,12 +1875,12 @@ public function testEmailConnection(array $emailSettings = []): array $this->logger->info('SoftwareCatalog: Starting email connection test', [ 'has_email_settings' => !empty($emailSettings) ]); - + try { // Ensure vendor autoloader is loaded include_once __DIR__ . '/../../vendor/autoload.php'; $this->logger->debug('SoftwareCatalog: Vendor autoloader loaded'); - + // Use provided settings or fall back to stored settings if (empty($emailSettings)) { $emailSettings = $this->getEmailSettings(); @@ -1887,7 +1888,7 @@ public function testEmailConnection(array $emailSettings = []): array } else { $this->logger->info('SoftwareCatalog: Using provided email settings'); } - + // Log the email configuration (without sensitive data) $this->logger->info('SoftwareCatalog: Email configuration for connection test', [ 'enabled' => $emailSettings['enabled'] ?? false, @@ -1896,7 +1897,7 @@ public function testEmailConnection(array $emailSettings = []): array 'sender_name' => $emailSettings['senderName'] ?? 'not set', 'has_credentials' => $this->hasValidCredentials($emailSettings) ]); - + // Check if email is enabled if (!($emailSettings['enabled'] ?? false)) { $this->logger->warning('SoftwareCatalog: Email notifications are disabled'); @@ -1905,41 +1906,41 @@ public function testEmailConnection(array $emailSettings = []): array 'message' => 'Email notifications are disabled' ]; } - + // Validate basic settings $transportType = $emailSettings['transportType'] ?? 'smtp'; $senderEmail = $emailSettings['senderEmail'] ?? ''; - + if (empty($senderEmail)) { return [ 'success' => false, 'message' => 'Sender email address is required' ]; } - + // Create transport based on configuration (this tests the connection) $this->logger->info('SoftwareCatalog: Creating email transport for connection test'); $transport = $this->createEmailTransport($emailSettings); $this->logger->info('SoftwareCatalog: Email transport created successfully'); - + // Test the connection by creating a mailer instance $mailer = new Mailer($transport); $this->logger->info('SoftwareCatalog: Mailer instance created for connection test'); - + // For some transports, we can test the connection more directly $connectionDetails = $this->getConnectionDetails($emailSettings); - + $this->logger->info('SoftwareCatalog: Email connection test completed successfully', [ 'transport' => $transportType, 'sender' => $senderEmail ]); - + return [ 'success' => true, 'message' => "Email connection test successful for {$transportType}", 'details' => $connectionDetails ]; - + } catch (\Exception $e) { $this->logger->error('SoftwareCatalog: Email connection test failed', [ 'exception_class' => get_class($e), @@ -1963,7 +1964,7 @@ public function testEmailConnection(array $emailSettings = []): array private function hasValidCredentials(array $emailSettings): bool { $transportType = $emailSettings['transportType'] ?? 'smtp'; - + switch ($transportType) { case 'smtp': return !empty($emailSettings['smtpHost']) && !empty($emailSettings['smtpPort']); @@ -1991,7 +1992,7 @@ private function hasValidCredentials(array $emailSettings): bool private function getConnectionDetails(array $emailSettings): array { $transportType = $emailSettings['transportType'] ?? 'smtp'; - + switch ($transportType) { case 'smtp': return [ @@ -2034,7 +2035,7 @@ private function getConnectionDetails(array $emailSettings): array return ['type' => $transportType]; } } - + /** * Creates an email transport based on configuration * @@ -2045,11 +2046,11 @@ private function getConnectionDetails(array $emailSettings): array private function createEmailTransport(array $emailSettings): \Symfony\Component\Mailer\Transport\TransportInterface { $transportType = $emailSettings['transportType'] ?? 'smtp'; - + $this->logger->info('SoftwareCatalog: Creating transport', [ 'transport_type' => $transportType ]); - + switch ($transportType) { case 'mailjet': $this->logger->info('SoftwareCatalog: Creating Mailjet transport'); @@ -2064,7 +2065,7 @@ private function createEmailTransport(array $emailSettings): \Symfony\Component\ throw new \InvalidArgumentException("Unsupported transport type: {$transportType}"); } } - + /** * Creates a Mailjet transport * @@ -2075,14 +2076,14 @@ private function createMailjetTransport(array $settings): \Symfony\Component\Mai { $apiKey = $settings['mailjetApiKey'] ?? ''; $secretKey = $settings['mailjetSecretKey'] ?? ''; - + $this->logger->info('SoftwareCatalog: Mailjet transport configuration', [ 'has_api_key' => !empty($apiKey), 'api_key_length' => strlen($apiKey), 'has_secret_key' => !empty($secretKey), 'secret_key_length' => strlen($secretKey) ]); - + if (empty($apiKey) || empty($secretKey)) { $this->logger->error('SoftwareCatalog: Mailjet API key and secret key are required', [ 'api_key_empty' => empty($apiKey), @@ -2096,11 +2097,11 @@ private function createMailjetTransport(array $settings): \Symfony\Component\Mai urlencode($apiKey), urlencode($secretKey) ); - + $this->logger->info('SoftwareCatalog: Creating Mailjet transport with DSN', [ 'dsn_pattern' => 'mailjet+api://***:***@default' ]); - + try { $transport = Transport::fromDsn($dsn); $this->logger->info('SoftwareCatalog: Mailjet transport created successfully', [ @@ -2115,7 +2116,7 @@ private function createMailjetTransport(array $settings): \Symfony\Component\Mai throw $e; } } - + /** * Creates an SMTP transport * @@ -2129,7 +2130,7 @@ private function createSmtpTransport(array $settings): \Symfony\Component\Mailer $encryption = $settings['smtpEncryption'] ?? 'tls'; $username = $settings['smtpUsername'] ?? ''; $password = $settings['smtpPassword'] ?? ''; - + $this->logger->info('SoftwareCatalog: SMTP transport configuration', [ 'host' => $host, 'port' => $port, @@ -2137,7 +2138,7 @@ private function createSmtpTransport(array $settings): \Symfony\Component\Mailer 'has_username' => !empty($username), 'has_password' => !empty($password) ]); - + $dsn = sprintf( 'smtp://%s:%s@%s:%d', urlencode($username), @@ -2145,15 +2146,15 @@ private function createSmtpTransport(array $settings): \Symfony\Component\Mailer $host, $port ); - + if ($encryption && $encryption !== 'none') { $dsn .= '?encryption=' . $encryption; } - + $this->logger->info('SoftwareCatalog: Creating SMTP transport with DSN', [ 'dsn_pattern' => sprintf('smtp://***:***@%s:%d%s', $host, $port, $encryption && $encryption !== 'none' ? '?encryption=' . $encryption : '') ]); - + try { $transport = Transport::fromDsn($dsn); $this->logger->info('SoftwareCatalog: SMTP transport created successfully', [ @@ -2182,40 +2183,40 @@ private function shouldLoadSettings(): bool try { // Get the current app version $currentAppVersion = $this->appManager->getAppVersion(\OCA\SoftwareCatalog\AppInfo\Application::APP_ID); - + $this->logger->info('SettingsService: Checking if settings should be loaded', [ 'current_app_version' => $currentAppVersion ]); - + // Get the configuration service to check stored version $configurationService = $this->getConfigurationService(); $storedVersion = $configurationService->getConfiguredAppVersion(\OCA\SoftwareCatalog\AppInfo\Application::APP_ID); - + $this->logger->info('SettingsService: Version comparison details', [ 'current_app_version' => $currentAppVersion, 'stored_config_version' => $storedVersion, 'stored_version_is_null' => $storedVersion === null ]); - + // If no stored version exists, we need to load settings if ($storedVersion === null) { $this->logger->info('SettingsService: No stored version found, settings should be loaded'); return true; } - + // Compare versions using semantic versioning // Load settings if current version is newer than stored version $shouldLoad = version_compare($currentAppVersion, $storedVersion, '>'); - + $this->logger->info('SettingsService: Version comparison result', [ 'current_version' => $currentAppVersion, 'stored_version' => $storedVersion, 'should_load' => $shouldLoad, 'version_compare_result' => version_compare($currentAppVersion, $storedVersion) ]); - + return $shouldLoad; - + } catch (\Exception $e) { // If we can't determine versions, err on the side of loading settings $this->logger->warning('Failed to check if settings should be loaded: ' . $e->getMessage(), [ @@ -2239,15 +2240,15 @@ public function getVersionInfo(): array try { // Get the current app version $currentAppVersion = $this->appManager->getAppVersion(\OCA\SoftwareCatalog\AppInfo\Application::APP_ID); - + $this->logger->debug('SettingsService: Getting version information', [ 'current_app_version' => $currentAppVersion ]); - + // Get the configuration service to check stored version $configurationService = $this->getConfigurationService(); $storedConfigVersion = null; - + try { $storedConfigVersion = $configurationService->getConfiguredAppVersion(\OCA\SoftwareCatalog\AppInfo\Application::APP_ID); } catch (\Exception $e) { @@ -2256,18 +2257,18 @@ public function getVersionInfo(): array ]); // Continue with null stored version } - + // Determine if versions match - $versionsMatch = $storedConfigVersion !== null && + $versionsMatch = $storedConfigVersion !== null && version_compare($currentAppVersion, $storedConfigVersion, '='); - - $needsUpdate = $storedConfigVersion === null || + + $needsUpdate = $storedConfigVersion === null || version_compare($currentAppVersion, $storedConfigVersion, '>'); - + // Check OpenRegister status $openRegisterInstalled = $this->isOpenRegisterInstalled(); $openRegisterEnabled = $openRegisterInstalled && $this->isOpenRegisterEnabled(); - + $versionInfo = [ 'appName' => 'SoftwareCatalog', 'appVersion' => $currentAppVersion, @@ -2280,9 +2281,9 @@ public function getVersionInfo(): array 'openRegisterInstalled' => $openRegisterInstalled, 'openRegisterEnabled' => $openRegisterEnabled ]; - + $this->logger->info('SettingsService: Version information compiled', $versionInfo); - + return $versionInfo; } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to get version information', [ @@ -2304,13 +2305,13 @@ public function forceUpdate(): array { try { $this->logger->info('SettingsService: Starting force update'); - + // Reset auto-configuration flag $this->config->setValueString($this->_appName, 'auto_config_completed', 'false'); - + // Perform forced import $importResult = $this->manualImport(true); - + if (!$importResult['success']) { return [ 'success' => false, @@ -2318,19 +2319,19 @@ public function forceUpdate(): array 'importResult' => $importResult ]; } - + // Verify configuration after force update $finalVersionInfo = $this->getVersionInfo(); $finalConfigStatus = $this->getConfigurationStatus(); - + $success = $finalVersionInfo['versionsMatch'] || !$finalVersionInfo['needsUpdate']; - + $this->logger->info('SettingsService: Force update completed', [ 'success' => $success, 'final_version_info' => $finalVersionInfo, 'final_config_status' => $finalConfigStatus ]); - + return [ 'success' => $success, 'message' => $success ? 'Force update completed successfully' : 'Force update completed but configuration may need attention', @@ -2338,7 +2339,7 @@ public function forceUpdate(): array 'finalVersionInfo' => $finalVersionInfo, 'finalConfigStatus' => $finalConfigStatus ]; - + } catch (\Exception $e) { $this->logger->error('SettingsService: Force update failed', [ 'exception_message' => $e->getMessage(), @@ -2368,12 +2369,12 @@ public function resetAutoConfiguration(bool $resetConfiguration = false): array $this->logger->info('Resetting auto-configuration', [ 'reset_configuration' => $resetConfiguration ]); - + // Reset the auto-configuration completion flag $this->config->setValueString($this->_appName, 'auto_config_completed', 'false'); - + $resetItems = ['auto_config_completed_flag']; - + if ($resetConfiguration) { // Reset schema and register configurations $configKeysToReset = [ @@ -2390,24 +2391,24 @@ public function resetAutoConfiguration(bool $resetConfiguration = false): array 'contact_register', 'contact_schema' ]; - + foreach ($configKeysToReset as $key) { $this->config->setValueString($this->_appName, $key, ''); } - + $resetItems[] = 'schema_register_configurations'; } - + $this->logger->info('Auto-configuration reset completed', [ 'reset_items' => $resetItems ]); - + return [ 'success' => true, 'message' => 'Auto-configuration reset successfully', 'reset_items' => $resetItems ]; - + } catch (\Exception $e) { $this->logger->error('Failed to reset auto-configuration: ' . $e->getMessage()); return [ @@ -2434,12 +2435,12 @@ public function manualImport(bool $forceImport = false): array $this->logger->info('SettingsService: Starting manual import', [ 'force_import' => $forceImport ]); - + // Get version info first $versionInfo = $this->getVersionInfo(); - + $this->logger->info('SettingsService: Pre-import version info', $versionInfo); - + // Check if import is needed (unless forced) if (!$forceImport && $versionInfo['versionsMatch'] && $versionInfo['isFullyConfigured']) { $this->logger->info('SettingsService: Import not needed - versions match and fully configured'); @@ -2449,7 +2450,7 @@ public function manualImport(bool $forceImport = false): array 'versionInfo' => $versionInfo ]; } - + // If force import is requested or auto-config not completed, reset auto-configuration flag if ($forceImport || !$versionInfo['autoConfigCompleted']) { $this->config->setValueString($this->_appName, 'auto_config_completed', 'false'); @@ -2457,14 +2458,14 @@ public function manualImport(bool $forceImport = false): array 'reason' => $forceImport ? 'force_import' : 'auto_config_not_completed' ]); } - + // Perform the import $this->logger->info('SettingsService: Starting settings import'); $importResult = $this->loadSettings($forceImport); $this->logger->info('SettingsService: Settings import completed', [ 'import_result' => $importResult ]); - + // Auto-configure after successful import $autoConfigResult = null; try { @@ -2486,15 +2487,15 @@ public function manualImport(bool $forceImport = false): array ]); // Don't fail the entire import if auto-configuration fails } - + // Wait a moment for any async operations to complete usleep(100000); // 0.1 seconds - + // Get updated version info - this should now reflect the changes $this->logger->info('SettingsService: Getting updated version info after import'); $updatedVersionInfo = $this->getVersionInfo(); $this->logger->info('SettingsService: Post-import version info', $updatedVersionInfo); - + $message = 'Configuration imported successfully'; if (!empty($autoConfigResult)) { $message .= ' and auto-configured'; @@ -2502,7 +2503,7 @@ public function manualImport(bool $forceImport = false): array if ($forceImport) { $message .= ' (forced import)'; } - + return [ 'success' => true, 'message' => $message, @@ -2511,7 +2512,7 @@ public function manualImport(bool $forceImport = false): array 'versionInfo' => $updatedVersionInfo, 'configurationStatus' => $this->getConfigurationStatus() ]; - + } catch (\Exception $e) { $this->logger->error('SettingsService: Manual import failed', [ 'exception_message' => $e->getMessage(), @@ -2531,7 +2532,7 @@ public function manualImport(bool $forceImport = false): array * * This method orchestrates the complete auto-configuration process: * 1. Configuration file loading - * 2. Voorzieningen register configuration + * 2. Voorzieningen register configuration * 3. AMEF register configuration * 4. User groups configuration * @@ -2543,7 +2544,7 @@ public function performConsolidatedAutoConfiguration(bool $force = false): array $this->logger->info('SettingsService: Starting consolidated auto-configuration', [ 'force' => $force ]); - + $results = [ 'success' => true, 'message' => 'Auto-configuration completed successfully', @@ -2552,42 +2553,42 @@ public function performConsolidatedAutoConfiguration(bool $force = false): array 'timestamp' => time(), 'force' => $force ]; - + // Step 1: Load configuration files $this->logger->info('SettingsService: Step 1 - Loading configuration'); $configResult = $this->loadConfiguration($force); $results['steps']['configurationLoad'] = $configResult; $this->addStepResult($results, $configResult, 'Configuration loading'); - + // Step 2: Configure Voorzieningen (Dutch register system) $this->logger->info('SettingsService: Step 2 - Configuring Voorzieningen'); $voorzieningenResult = $this->configureVoorzieningen(); $results['steps']['voorzieningenConfiguration'] = $voorzieningenResult; $this->addStepResult($results, $voorzieningenResult, 'Voorzieningen configuration'); - + // Step 3: Configure AMEF (ArchiMate/English register system) $this->logger->info('SettingsService: Step 3 - Configuring AMEF'); $amefResult = $this->configureAmef(); $results['steps']['amefConfiguration'] = $amefResult; $this->addStepResult($results, $amefResult, 'AMEF configuration'); - + // Step 4: Configure User Groups $this->logger->info('SettingsService: Step 4 - Configuring User Groups'); $groupsResult = $this->configureGroups(); $results['steps']['groupsConfiguration'] = $groupsResult; $this->addStepResult($results, $groupsResult, 'User groups configuration'); - + // Determine overall success $results['success'] = empty($results['errors']); if (!$results['success']) { $results['message'] = 'Auto-configuration completed with some issues'; } - + $this->logger->info('SettingsService: Consolidated auto-configuration completed', [ 'success' => $results['success'], 'errors_count' => count($results['errors']) ]); - + return $results; } @@ -2601,7 +2602,7 @@ private function loadConfiguration(bool $force): array { try { $importResult = $this->manualImport($force); - + return [ 'success' => $importResult['success'], 'message' => $importResult['message'] ?? 'Configuration loaded', @@ -2815,7 +2816,7 @@ private function configureGroups(): array try { // Call the method to create required user groups $result = $this->createAndConfigureUserGroups(); - + return [ 'success' => $result['success'], 'message' => $result['message'], @@ -2867,11 +2868,11 @@ public function getConsolidatedConfiguration(): array 'user_creation' => $this->getEmailTemplate('user_creation'), 'user_password' => $this->getEmailTemplate('user_password'), ]; - + // Get Voorzieningen and AMEF configs (without object counts for performance) $voorzieningenConfig = $this->getVoorzieningenConfig(); $amefConfig = $this->getAmefConfig(); - + return [ 'voorzieningen' => $voorzieningenConfig, 'amef' => $amefConfig, @@ -2987,24 +2988,24 @@ public function getAmefConfig(): array try { // Get ArchiMateService from container to avoid circular dependency $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); - + // Use reflection to access the private getAmefConfig method $reflection = new \ReflectionClass($archiMateService); $method = $reflection->getMethod('getAmefConfig'); $method->setAccessible(true); - + return $method->invoke($archiMateService); - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to get AMEF config from ArchiMateService', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); - + // Fallback to direct config access if ArchiMateService is not available $config = $this->config->getValueString($this->_appName, 'amef_config', '{}'); $decoded = json_decode($config, true); - + if (!is_array($decoded)) { // Fallback to individual config values for backward compatibility $decoded = [ @@ -3017,7 +3018,7 @@ public function getAmefConfig(): array 'properties_schema' => $this->config->getValueString($this->_appName, 'amef_properties_schema', '') ]; } - + return $decoded; } } @@ -3043,7 +3044,7 @@ public function getEmailConfig(): array { $config = $this->config->getValueString($this->_appName, 'email_config', '{}'); $decoded = json_decode($config, true); - + if (!is_array($decoded)) { // Fallback to individual config values for backward compatibility $decoded = [ @@ -3060,7 +3061,7 @@ public function getEmailConfig(): array 'mailjet_secret_key' => $this->config->getValueString($this->_appName, 'email_mailjet_secret_key', '') ]; } - + return $decoded; } @@ -3089,25 +3090,25 @@ public function getArchiMateStatus(): array try { // Get ArchiMateService from container to avoid circular dependency $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); - + return $archiMateService->getArchiMateStatus(); - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to get ArchiMate status from ArchiMateService', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); - + // Fallback to direct config access if ArchiMateService is not available $importStatus = $this->config->getValueString($this->_appName, 'archimate_import_status', '{}'); $exportStatus = $this->config->getValueString($this->_appName, 'archimate_export_status', '{}'); - + $importDecoded = json_decode($importStatus, true); $exportDecoded = json_decode($exportStatus, true); - + // Get AMEF object counts $amefObjectCounts = $this->getAmefObjectCounts(); - + return [ 'import' => is_array($importDecoded) ? $importDecoded : [], 'export' => is_array($exportDecoded) ? $exportDecoded : [], @@ -3150,10 +3151,10 @@ private function getVoorzieningenObjectCounts(): array 'totalSectorObjects' => 0 ]; } - + $voorzieningenConfig = $this->getVoorzieningenConfig(); $registerId = $voorzieningenConfig['register'] ?? null; - + // Define all schema mappings $schemaMappings = [ 'organisatie_schema' => 'totalOrganisatieObjects', @@ -3175,18 +3176,18 @@ private function getVoorzieningenObjectCounts(): array 'module_versie_schema' => 'totalModuleVersieObjects', 'sector_schema' => 'totalSectorObjects' ]; - + $counts = []; - + // Initialize all counts to 0 foreach ($schemaMappings as $key => $countKey) { $counts[$countKey] = 0; } - + // Count objects for each configured schema foreach ($schemaMappings as $configKey => $countKey) { $schemaId = $voorzieningenConfig[$configKey] ?? null; - + if ($registerId && $schemaId) { try { $query = [ @@ -3202,17 +3203,17 @@ private function getVoorzieningenObjectCounts(): array } } } - + $this->logger->debug('SettingsService: Retrieved Voorzieningen object counts', $counts); - + return $counts; - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to get Voorzieningen object counts', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); - + return [ 'totalOrganisatieObjects' => 0, 'totalContactpersoonObjects' => 0, @@ -3249,7 +3250,7 @@ private function getAmefObjectCounts(): array try { // Get ArchiMateService from container to avoid circular dependency $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); - + // Get object counts using ArchiMateService methods $elementObjects = $archiMateService->getElementObjects(); $organizationObjects = $archiMateService->getOrganizationObjects(); @@ -3308,15 +3309,15 @@ public function setArchiMateImportStatus(array $status): void try { // Get ArchiMateService from container to avoid circular dependency $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); - + $archiMateService->setArchiMateImportStatus($status); - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to set ArchiMate import status via ArchiMateService', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); - + // Fallback to direct config access if ArchiMateService is not available $jsonStatus = json_encode($status, JSON_PRETTY_PRINT); $this->config->setValueString($this->_appName, 'archimate_import_status', $jsonStatus); @@ -3337,15 +3338,15 @@ public function setArchiMateExportStatus(array $status): void try { // Get ArchiMateService from container to avoid circular dependency $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); - + $archiMateService->setArchiMateExportStatus($status); - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to set ArchiMate export status via ArchiMateService', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); - + // Fallback to direct config access if ArchiMateService is not available $jsonStatus = json_encode($status, JSON_PRETTY_PRINT); $this->config->setValueString($this->_appName, 'archimate_export_status', $jsonStatus); @@ -3365,18 +3366,18 @@ public function clearArchiMateImportStatus(): array try { // Get ArchiMateService from container to avoid circular dependency $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); - + return $archiMateService->clearArchiMateImportStatus(); - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to clear ArchiMate import status via ArchiMateService', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); - + // Fallback to direct config access if ArchiMateService is not available $this->config->deleteKey($this->_appName, 'archimate_import_status'); - + return [ 'cleared' => true, 'process_killed' => false, @@ -3401,18 +3402,18 @@ public function killArchiMateImport(): array try { // Get ArchiMateService from container to avoid circular dependency $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); - + return $archiMateService->clearArchiMateImportStatus(true); // killProcess = true - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to kill ArchiMate import process via ArchiMateService', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); - + // Fallback to just clearing config if ArchiMateService is not available $this->config->deleteKey($this->_appName, 'archimate_import_status'); - + return [ 'cleared' => true, 'process_killed' => false, @@ -3436,18 +3437,18 @@ public function cancelArchiMateImport(): array try { // Get ArchiMateService from container to avoid circular dependency $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); - + return $archiMateService->cancelArchiMateImport(); - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to cancel ArchiMate import via ArchiMateService', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); - + // Fallback to just clearing config if ArchiMateService is not available $this->config->deleteKey($this->_appName, 'archimate_import_status'); - + return [ 'cancelled' => true, 'was_running' => false, @@ -3473,15 +3474,15 @@ public function clearArchiMateExportStatus(): void try { // Get ArchiMateService from container to avoid circular dependency $archiMateService = $this->container->get(\OCA\SoftwareCatalog\Service\ArchiMateService::class); - + $archiMateService->clearArchiMateExportStatus(); - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to clear ArchiMate export status via ArchiMateService', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); - + // Fallback to direct config access if ArchiMateService is not available $this->config->deleteKey($this->_appName, 'archimate_export_status'); } @@ -3614,7 +3615,7 @@ public function cleanupOldConfiguration(): array 'voorzieningen_contactpersoon_register', 'voorzieningen_gebruiker_register', // Deprecated - no longer used 'voorzieningen_contactgegevens_register', // Deprecated - no longer used - + // AMEF keys 'amef_register_id', 'amef_organizations_schema', @@ -3627,7 +3628,7 @@ public function cleanupOldConfiguration(): array 'amef_elementss_schema', 'amef_organizationss_schema', 'amef_relationshipss_schema', - + // Email keys 'email_enabled', 'email_transport_type', @@ -3705,7 +3706,7 @@ public function getAllSettings(): array ]; return $result; - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to get all settings', [ 'exception' => $e->getMessage() @@ -3730,12 +3731,12 @@ public function getObjectCountsStatistics(): array 'amef' => [], 'timestamp' => time() ]; - + // Get Voorzieningen statistics try { $voorzieningenConfig = $this->getVoorzieningenConfig(); $voorzieningenCounts = $this->getVoorzieningenObjectCounts(); - + $statistics['voorzieningen'] = [ 'config' => $voorzieningenConfig, 'object_counts' => $voorzieningenCounts, @@ -3750,12 +3751,12 @@ public function getObjectCountsStatistics(): array 'error' => $e->getMessage() ]; } - + // Get AMEF statistics try { $amefConfig = $this->getAmefConfig(); $amefCounts = $this->getAmefObjectCounts(); - + $statistics['amef'] = [ 'config' => $amefConfig, 'object_counts' => $amefCounts, @@ -3777,9 +3778,9 @@ public function getObjectCountsStatistics(): array 'error' => $e->getMessage() ]; } - + return $statistics; - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to get object counts statistics', [ 'exception' => $e->getMessage() @@ -3818,9 +3819,9 @@ public function getObjectCountsStatistics(): array */ public function getAllEmailTemplates(): array { - $templateTypes = ['organization_registration', 'organization_activation', 'user_creation', 'user_password']; + $templateTypes = ['organization_registration', 'organization_activation', 'user_creation', 'user_password', 'user_organisation']; $templates = []; - + foreach ($templateTypes as $templateName) { try { $templates[$templateName] = $this->getEmailTemplate($templateName); @@ -3829,7 +3830,7 @@ public function getAllEmailTemplates(): array $templates[$templateName] = null; } } - + return $templates; } @@ -3843,7 +3844,7 @@ public function updateGenericUserGroups(array $groups): array { try { $validation = $this->validateGroups($groups); - + if (!empty($validation['invalid'])) { return [ 'success' => false, @@ -3851,15 +3852,15 @@ public function updateGenericUserGroups(array $groups): array 'validation' => $validation ]; } - + $this->setGenericUserGroups($validation['valid']); - + return [ 'success' => true, 'message' => 'Generic user groups updated successfully', 'groups' => $validation['valid'] ]; - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to update generic user groups', [ 'exception' => $e->getMessage() @@ -3881,7 +3882,7 @@ public function updateOrganizationAdminGroups(array $groups): array { try { $validation = $this->validateGroups($groups); - + if (!empty($validation['invalid'])) { return [ 'success' => false, @@ -3889,15 +3890,15 @@ public function updateOrganizationAdminGroups(array $groups): array 'validation' => $validation ]; } - + $this->setOrganizationAdminGroups($validation['valid']); - + return [ 'success' => true, 'message' => 'Organization admin groups updated successfully', 'groups' => $validation['valid'] ]; - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to update organization admin groups', [ 'exception' => $e->getMessage() @@ -3919,7 +3920,7 @@ public function updateSuperUserGroups(array $groups): array { try { $validation = $this->validateGroups($groups); - + if (!empty($validation['invalid'])) { return [ 'success' => false, @@ -3927,15 +3928,15 @@ public function updateSuperUserGroups(array $groups): array 'validation' => $validation ]; } - + $this->setSuperUserGroups($validation['valid']); - + return [ 'success' => true, 'message' => 'Super user groups updated successfully', 'groups' => $validation['valid'] ]; - + } catch (\Exception $e) { $this->logger->error('SettingsService: Failed to update super user groups', [ 'exception' => $e->getMessage() @@ -3961,7 +3962,7 @@ public function getArchiMateConfig(): array try { $config = $this->getAmefConfig(); $status = $this->getArchiMateStatus(); - + return [ 'success' => true, 'config' => $config, @@ -3990,7 +3991,7 @@ public function updateArchiMateConfig(array $config): array { try { $this->setAmefConfig($config); - + return [ 'success' => true, 'message' => 'ArchiMate configuration updated successfully', @@ -4018,7 +4019,7 @@ public function getEmailConfigFocused(): array try { $emailSettings = $this->getEmailSettings(); $emailTemplates = $this->getAllEmailTemplates(); - + return [ 'success' => true, 'emailSettings' => $emailSettings, @@ -4046,13 +4047,13 @@ public function getEmailConfigFocused(): array public function updateEmailConfig(array $config): array { try { - if (isset($config['emailSettings'])) { - $result = $this->updateEmailSettings($config['emailSettings']); + if (isset($config)) { + $result = $this->updateEmailSettings($config); if (!$result['success']) { return $result; } } - + return [ 'success' => true, 'message' => 'Email configuration updated successfully', @@ -4079,7 +4080,7 @@ public function getAmefConfigFocused(): array { try { $config = $this->getAmefConfig(); - + return [ 'success' => true, 'config' => $config, @@ -4181,7 +4182,7 @@ public function updateAmefConfig(array $config): array } $this->setAmefConfig($merged); - + return [ 'success' => true, 'message' => 'AMEF configuration updated successfully', @@ -4208,7 +4209,7 @@ public function getVoorzieningenConfigFocused(): array { try { $config = $this->getVoorzieningenConfig(); - + return [ 'success' => true, 'config' => $config, @@ -4236,7 +4237,7 @@ public function updateVoorzieningenConfig(array $config): array { try { $this->setVoorzieningenConfig($config); - + return [ 'success' => true, 'message' => 'Voorzieningen configuration updated successfully', @@ -4267,7 +4268,7 @@ public function getObjectsCounts(): array 'amef' => $this->getAmefObjectCounts(), 'timestamp' => time() ]; - + return [ 'success' => true, 'counts' => $counts @@ -4292,7 +4293,7 @@ public function getObjectsStatistics(): array { try { $statistics = $this->getObjectCountsStatistics(); - + return [ 'success' => true, 'statistics' => $statistics @@ -4322,7 +4323,7 @@ public function getUserGroupsConfig(): array 'superUser' => $this->getSuperUserGroups(), 'allGroups' => $this->getAllGroups() ]; - + return [ 'success' => true, 'config' => $config, @@ -4350,24 +4351,24 @@ public function updateUserGroupsConfig(array $config): array { try { $results = []; - + if (isset($config['generic'])) { $results['generic'] = $this->updateGenericUserGroups($config['generic']); } - + if (isset($config['organizationAdmin'])) { $results['organizationAdmin'] = $this->updateOrganizationAdminGroups($config['organizationAdmin']); } - + if (isset($config['superUser'])) { $results['superUser'] = $this->updateSuperUserGroups($config['superUser']); } - + // Check if any updates failed $failed = array_filter($results, function($result) { return !$result['success']; }); - + if (!empty($failed)) { return [ 'success' => false, @@ -4375,7 +4376,7 @@ public function updateUserGroupsConfig(array $config): array 'results' => $results ]; } - + return [ 'success' => true, 'message' => 'User groups configuration updated successfully', @@ -4393,4 +4394,4 @@ public function updateUserGroupsConfig(array $config): array } } -} \ No newline at end of file +} diff --git a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php index 26af0a5b..e6213c44 100644 --- a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php +++ b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php @@ -45,26 +45,27 @@ class ContactPersonHandler /** * ContactPersonHandler constructor * - * @param IUserManager $_userManager User manager interface - * @param ISecureRandom $_secureRandom Secure random generator - * @param IGroupManager $_groupManager Group manager interface - * @param IAppConfig $_config Config interface - * @param ContainerInterface $_container Container interface - * @param IAppManager $_appManager App manager interface - * @param LoggerInterface $_logger Logger interface - * @param SymfonyEmailService $_emailService Email service + * @param IUserManager $_userManager User manager interface + * @param ISecureRandom $_secureRandom Secure random generator + * @param IGroupManager $_groupManager Group manager interface + * @param IAppConfig $_config Config interface + * @param ContainerInterface $_container Container interface + * @param IAppManager $_appManager App manager interface + * @param LoggerInterface $_logger Logger interface + * @param SymfonyEmailService $_emailService Email service */ public function __construct( - private readonly IUserManager $_userManager, - private readonly ISecureRandom $_secureRandom, - private readonly IGroupManager $_groupManager, - private readonly IAppConfig $_config, - private readonly ContainerInterface $_container, - private readonly IAppManager $_appManager, - private readonly LoggerInterface $_logger, + private readonly IUserManager $_userManager, + private readonly ISecureRandom $_secureRandom, + private readonly IGroupManager $_groupManager, + private readonly IAppConfig $_config, + private readonly ContainerInterface $_container, + private readonly IAppManager $_appManager, + private readonly LoggerInterface $_logger, private readonly SymfonyEmailService $_emailService, - private readonly IConfig $config, - ) { + private readonly IConfig $config, + ) + { } /** @@ -83,7 +84,6 @@ private function _getObjectService(): ?\OCA\OpenRegister\Service\ObjectService } - /** * Generates a username from contact data with fallback strategies * @@ -101,7 +101,6 @@ public function generateUsernameFromContactData(array $contactData): string $email = $contactData['email'] ?? $contactData['e-mailadres'] ?? ''; - // Strategy 1: full email address (PRIORITY) if (!empty($email) && strpos($email, '@') !== false) { $username = strtolower($email); @@ -322,8 +321,8 @@ public function createUserAccount(object $contactpersoonObject, bool $isFirstCon /** * Assigns user groups based on roles and organization * - * @param \OCP\IUser $user The user to assign groups to - * @param array $objectData The contact person data + * @param \OCP\IUser $user The user to assign groups to + * @param array $objectData The contact person data * * @return void */ @@ -455,9 +454,9 @@ private function getAllowedRoleGroups(): array /** * Adds a user to a group, creating the group if it doesn't exist * - * @param \OCP\IUser $user The user to add - * @param string $groupName The group name - * @param string $type The type of group assignment (for logging) + * @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 */ @@ -502,9 +501,9 @@ private function addUserToGroup(\OCP\IUser $user, string $groupName, string $typ /** * Updates user groups when roles change (handles role removal) * - * @param \OCP\IUser $user The user to update - * @param array $newRoles The new roles - * @param array $oldRoles The old roles (optional) + * @param \OCP\IUser $user The user to update + * @param array $newRoles The new roles + * @param array $oldRoles The old roles (optional) * * @return void */ @@ -700,7 +699,7 @@ private function getOrganizationGroup(string $organizationId): ?\OCP\IGroup * Determines if this contact object is the first contact for the organization * * @param object $contactObject The contact object being processed (contactpersoon) - * @param array $objectData The contact data + * @param array $objectData The contact data * * @return bool True if this is the first contact for the organization */ @@ -744,7 +743,7 @@ private function isFirstContactForOrganization(object $contactObject, array $obj ); // Filter out the current contact being processed - $otherContacts = array_filter($existingContacts, function($contact) use ($currentContactId) { + $otherContacts = array_filter($existingContacts, function ($contact) use ($currentContactId) { return $contact->getId() !== $currentContactId; }); @@ -786,7 +785,7 @@ private function isFirstContactForOrganization(object $contactObject, array $obj * This method stores the organization UUID in the user's 'core' namespace * configuration, making it accessible to other apps like OpenConnector. * - * @param IUser $user The user object + * @param IUser $user The user object * @param string|int $organizationUuid The organization UUID (can be string or int) * * @return void @@ -956,8 +955,8 @@ public function handleContactDeletion(object $contactObject): void * Assigns beheerder role to a user * * @param object $contactpersoonObject The contactpersoon object - * @param string $username The username - * @param string $organizationUuid The organization UUID + * @param string $username The username + * @param string $organizationUuid The organization UUID * * @return void */ @@ -1026,7 +1025,7 @@ public function assignBeheerderRole(object $contactpersoonObject, string $userna /** * Sets a user's manager in Nextcloud * - * @param string $username The username + * @param string $username The username * @param string $managerUsername The manager's username * * @return void @@ -1158,13 +1157,14 @@ private function getOrganizationType(string $organizationId): string /** * Sends user creation email * - * @param \OCP\IUser $user The created user - * @param array $objectData The contact person data + * @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 { + try { $this->_logger->info('Sending user creation email', [ 'username' => $user->getUID(), @@ -1188,16 +1188,16 @@ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): voi try { $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'); + $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; - } + if (!$registerId || !$organisatieSchemaId) { + $this->_logger->warning('Register or schema ID not configured for organisatie'); + return; + } - $organizationObject = $objectService->find($organizationId, [], false, $registerId, $organisatieSchemaId); + $organizationObject = $objectService->find($organizationId, [], false, $registerId, $organisatieSchemaId); if ($organizationObject) { $organizationData = $organizationObject->getObject(); $this->_logger->info('Retrieved organization data for email', [ @@ -1228,14 +1228,14 @@ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): voi ]); } - } catch (\Exception $e) { - $this->_logger->error('Exception sending user creation email: ' . $e->getMessage(), [ - 'username' => $user->getUID(), - 'email' => $user->getEMailAddress(), - 'exception' => $e - ]); - } - } + } catch (\Exception $e) { + $this->_logger->error('Exception sending user creation email: ' . $e->getMessage(), [ + 'username' => $user->getUID(), + 'email' => $user->getEMailAddress(), + 'exception' => $e + ]); + } + } /** * Processes a contactpersoon object to create an inactive user @@ -1244,7 +1244,7 @@ private function sendUserCreationEmail(\OCP\IUser $user, array $objectData): voi * this method will create an inactive user account and set the username property. * * @param object $contactpersoonObject The contactpersoon object to process - * @param bool $isUpdate Whether this is an update operation (defaults to false) + * @param bool $isUpdate Whether this is an update operation (defaults to false) * * @return bool True if processing was successful * @throws \Exception If processing fails @@ -1458,7 +1458,7 @@ public function setUserActive(string $username): bool /** * Handles contactpersoon updates, particularly role changes * - * @param object $contactpersoonObject The updated contactpersoon object + * @param object $contactpersoonObject The updated contactpersoon object * @param object $oldContactpersoonObject The previous contactpersoon object * * @return void @@ -1524,8 +1524,8 @@ public function handleContactpersoonUpdate(object $contactpersoonObject, object /** * Sends account suspension notification email * - * @param \OCP\IUser $user The suspended user - * @param array $objectData The contact person data + * @param \OCP\IUser $user The suspended user + * @param array $objectData The contact person data * * @return void */ diff --git a/lib/Service/SymfonyEmailService.php b/lib/Service/SymfonyEmailService.php index 83e94095..20012d78 100644 --- a/lib/Service/SymfonyEmailService.php +++ b/lib/Service/SymfonyEmailService.php @@ -16,11 +16,11 @@ /** * Symfony Mailer-based email service for sending notification emails - * + * * This service handles sending various types of notification emails * using Symfony Mailer with configurable transports including SMTP, * SendGrid, Mailgun, and other providers. - * + * * @category Service * @package OCA\SoftwareCatalog\Service * @author Conduction b.v. @@ -154,28 +154,25 @@ class SymfonyEmailService * * @var string Contact welcome email template */ - private const CONTACT_WELCOME_TEMPLATE = ' + private const CONTACT_ADDED_TEMPLATE = ' - Welkom bij de Software Catalogus + Toegevoegd aan organisatie

Welkom {{ user.name }}!

Beste {{ user.name }},

-

U bent toegevoegd als contactpersoon in de Software Catalogus.

-

Er is automatisch een gebruikersaccount voor u aangemaakt waarmee u kunt inloggen op het platform.

+

Uw gebruikersnaam is succesvol toegevoegd aan {{ organization.name }}.

U kunt nu:

  • Inloggen op het platform
  • -
  • Software informatie beheren
  • +
  • Software beheren voor uw organisatie
  • +
  • Deelnemen aan de open data gemeenschap
  • Samenwerken met andere organisaties

Login gegevens:

-
    -
  • E-mailadres: {{ user.email }}
  • -
  • Wachtwoord: U ontvangt een apart e-mailadres met instructies voor het instellen van uw wachtwoord
  • -
+

We hebben uw bestaande account gekoppeld aan een nieuwe organisatie. Uw inloggegevens zijn hetzelfde, maar u kunt nu uw organisatie wisselen tussen uw organisaties.

Heeft u vragen? Neem dan contact met ons op via info@conduction.nl

Met vriendelijke groet,
Het Software Catalogus Team

@@ -268,7 +265,7 @@ private function createTransport(): TransportInterface { $emailSettings = $this->settingsService->getEmailSettings(); $transportType = $emailSettings['transportType'] ?? 'smtp'; - + try { switch ($transportType) { case 'smtp': @@ -359,7 +356,7 @@ private function createMailgunTransport(array $settings): TransportInterface { $apiKey = $settings['mailgunApiKey'] ?? ''; $domain = $settings['mailgunDomain'] ?? ''; - + if (empty($apiKey) || empty($domain)) { throw new \InvalidArgumentException('Mailgun API key and domain are required'); } @@ -398,7 +395,7 @@ private function createSesTransport(array $settings): TransportInterface $accessKey = $settings['sesAccessKey'] ?? ''; $secretKey = $settings['sesSecretKey'] ?? ''; $region = $settings['sesRegion'] ?? 'us-east-1'; - + if (empty($accessKey) || empty($secretKey)) { throw new \InvalidArgumentException('Amazon SES access key and secret key are required'); } @@ -421,7 +418,7 @@ private function createMailjetTransport(array $settings): TransportInterface { $apiKey = $settings['mailjetApiKey'] ?? ''; $secretKey = $settings['mailjetSecretKey'] ?? ''; - + if (empty($apiKey) || empty($secretKey)) { throw new \InvalidArgumentException('Mailjet API key and secret key are required'); } @@ -451,11 +448,12 @@ public function sendOrganizationRegistrationEmail(array $organization): bool 'hasTemplates' => $configStatus['hasTemplates'], 'organizationName' => $organization['naam'] ?? 'Unknown' ]); + return false; } - + $emailSettings = $this->settingsService->getEmailSettings(); - + // Check if organization registration emails are enabled if (!$emailSettings['organizationRegistrationEnabled']) { $this->logger->info('OrganizationRegistrationEmail: Organization registration emails disabled', [ @@ -464,8 +462,9 @@ public function sendOrganizationRegistrationEmail(array $organization): bool return false; } + $organizationName = $organization['naam'] ?? $organization['name'] ?? 'Onbekende Organisatie'; - + // Determine recipient email $recipientEmail = $this->getRecipientEmail($organization); if (!$recipientEmail) { @@ -500,14 +499,14 @@ public function sendOrganizationRegistrationEmail(array $organization): bool 'organization_registration', $templateData ); - + if ($success) { $this->logger->info('OrganizationRegistrationEmail: Successfully sent registration email', [ 'organizationName' => $organizationName, 'recipientEmail' => $recipientEmail ]); } - + return $success; } catch (\Exception $e) { $this->logger->error('OrganizationRegistrationEmail: Failed to send registration email', [ @@ -539,9 +538,9 @@ public function sendOrganizationActivationEmail(array $organization): bool ]); return false; } - + $emailSettings = $this->settingsService->getEmailSettings(); - + // Check if organization activation emails are enabled if (!$emailSettings['organizationActivationEnabled']) { $this->logger->info('OrganizationActivationEmail: Organization activation emails disabled', [ @@ -551,7 +550,7 @@ public function sendOrganizationActivationEmail(array $organization): bool } $organizationName = $organization['naam'] ?? $organization['name'] ?? 'Onbekende Organisatie'; - + // Determine recipient email $recipientEmail = $this->getRecipientEmail($organization); if (!$recipientEmail) { @@ -586,14 +585,14 @@ public function sendOrganizationActivationEmail(array $organization): bool 'organization_activation', $templateData ); - + if ($success) { $this->logger->info('OrganizationActivationEmail: Successfully sent activation email', [ 'organizationName' => $organizationName, 'recipientEmail' => $recipientEmail ]); } - + return $success; } catch (\Exception $e) { $this->logger->error('OrganizationActivationEmail: Failed to send activation email', [ @@ -626,9 +625,9 @@ public function sendUserCreationEmail(array $user, array $organization = []): bo ]); return false; } - + $emailSettings = $this->settingsService->getEmailSettings(); - + // Check if user creation emails are enabled if (!$emailSettings['userCreationEnabled']) { $this->logger->info('UserCreationEmail: User creation emails disabled', [ @@ -640,7 +639,7 @@ public function sendUserCreationEmail(array $user, array $organization = []): bo $userEmail = $user['email'] ?? ''; $userName = $user['naam'] ?? $user['name'] ?? ($user['voornaam'] ?? '') . ' ' . ($user['achternaam'] ?? ''); $userName = trim($userName); - + if (empty($userEmail)) { $this->logger->warning('UserCreationEmail: Cannot send without email address', [ 'user' => $user, @@ -676,14 +675,14 @@ public function sendUserCreationEmail(array $user, array $organization = []): bo 'user_creation', $templateData ); - + if ($success) { $this->logger->info('UserCreationEmail: Successfully sent user creation email', [ 'userName' => $userName, 'userEmail' => $userEmail ]); } - + return $success; } catch (\Exception $e) { $this->logger->error('UserCreationEmail: Failed to send user creation email', [ @@ -695,6 +694,96 @@ public function sendUserCreationEmail(array $user, array $organization = []): bo } } + /** + * Sends a user creation email + * + * @param array $user The user data + * @param array $organization The organization data (optional) + * @return bool True if email was sent successfully, false otherwise + * @throws \Exception If email sending fails + */ + public function sendUserUpdateEmail(array $user, array $organization = []): bool + { + // Check if email system is fully configured + $configStatus = $this->isEmailSystemConfigured(); + if (!$configStatus['configured']) { + $this->logger->info('UserCreationEmail: Email system not configured, skipping', [ + 'reason' => $configStatus['reason'], + 'hasCredentials' => $configStatus['hasCredentials'], + 'hasTemplates' => $configStatus['hasTemplates'], + 'userEmail' => $user['email'] ?? 'Unknown' + ]); + return false; + } + + $emailSettings = $this->settingsService->getEmailSettings(); + + // Check if user creation emails are enabled + if (!$emailSettings['userOrganisationEnabled']) { + $this->logger->info('UserOrganisationEmail: User creation emails disabled', [ + 'userEmail' => $user['email'] ?? 'Unknown' + ]); + return false; + } + + $userEmail = $user['email'] ?? ''; + $userName = $user['naam'] ?? $user['name'] ?? ($user['voornaam'] ?? '') . ' ' . ($user['achternaam'] ?? ''); + $userName = trim($userName); + + if (empty($userEmail)) { + $this->logger->warning('UserCreationEmail: Cannot send without email address', [ + 'user' => $user, + 'userName' => $userName + ]); + return false; + } + + $this->logger->info('UserCreationEmail: Sending user creation email', [ + 'userName' => $userName, + 'userEmail' => $userEmail, + 'organizationName' => $organization['naam'] ?? $organization['name'] ?? 'Software Catalogus', + 'transportType' => $configStatus['transportType'] + ]); + + // Prepare template data + $templateData = [ + 'user' => [ + 'name' => $userName ?: 'Gebruiker', + 'email' => $userEmail, + 'functie' => $user['functie'] ?? '', + ], + 'organization' => [ + 'name' => $organization['naam'] ?? $organization['name'] ?? 'Software Catalogus', + ] + ]; + + try { + $success = $this->sendTemplatedEmail( + $userEmail, + $userName ?: 'Gebruiker', + 'Welkom bij de Software Catalogus - Account toegevoegd aan organisatie', + 'user_organisation', + $templateData + ); + + if ($success) { + $this->logger->info('UserCreationEmail: Successfully sent user organisation email', [ + 'userName' => $userName, + 'userEmail' => $userEmail + ]); + } + + return $success; + } catch (\Exception $e) { + $this->logger->error('UserCreationEmail: Failed to send user organisation email', [ + 'userName' => $userName, + 'userEmail' => $userEmail, + 'error' => $e->getMessage() + ]); + return false; + } + } + /** * Sends a user password email * @@ -717,9 +806,9 @@ public function sendUserPasswordEmail(array $user, string $password, array $orga ]); return false; } - + $emailSettings = $this->settingsService->getEmailSettings(); - + // Check if user password emails are enabled if (!$emailSettings['userPasswordEnabled']) { $this->logger->info('UserPasswordEmail: User password emails disabled', [ @@ -731,7 +820,7 @@ public function sendUserPasswordEmail(array $user, string $password, array $orga $userEmail = $user['email'] ?? ''; $userName = $user['naam'] ?? $user['name'] ?? ($user['voornaam'] ?? '') . ' ' . ($user['achternaam'] ?? ''); $userName = trim($userName); - + if (empty($userEmail)) { $this->logger->warning('UserPasswordEmail: Cannot send without email address', [ 'user' => $user, @@ -768,14 +857,14 @@ public function sendUserPasswordEmail(array $user, string $password, array $orga 'user_password', $templateData ); - + if ($success) { $this->logger->info('UserPasswordEmail: Successfully sent user password email', [ 'userName' => $userName, 'userEmail' => $userEmail ]); } - + return $success; } catch (\Exception $e) { $this->logger->error('UserPasswordEmail: Failed to send user password email', [ @@ -845,6 +934,7 @@ private function getDefaultTemplate(string $templateName): string 'organization_activation' => self::ORGANIZATION_ACTIVATION_TEMPLATE, 'user_creation' => self::GEBRUIKER_WELCOME_TEMPLATE, 'user_password' => self::USER_PASSWORD_TEMPLATE, + 'user_organisation' => self::CONTACT_ADDED_TEMPLATE, default => self::ORGANIZATION_WELCOME_TEMPLATE, }; } @@ -859,13 +949,13 @@ private function getDefaultTemplate(string $templateName): string private function processTemplate(string $template, array $templateData): string { $processed = $template; - + // Replace organization variables if (isset($templateData['organization'])) { $org = $templateData['organization']; $processed = str_replace('{{ organization.name }}', $org['name'] ?? '', $processed); } - + // Replace user variables if (isset($templateData['user'])) { $user = $templateData['user']; @@ -873,7 +963,7 @@ private function processTemplate(string $template, array $templateData): string $processed = str_replace('{{ user.email }}', $user['email'] ?? '', $processed); $processed = str_replace('{{ user.password }}', $user['password'] ?? '', $processed); } - + // Replace contact variables (backward compatibility) if (isset($templateData['user'])) { $user = $templateData['user']; @@ -899,13 +989,13 @@ private function getRecipientEmail(array $data): ?string } // Try to get email from various fields - $email = $data['email'] ?? null; - + $email = $data['e-mailadres'] ?? null; + // If no direct email, try to get from contactpersonen if (!$email && isset($data['contactpersonen']) && is_array($data['contactpersonen'])) { foreach ($data['contactpersonen'] as $contact) { - if (is_array($contact) && !empty($contact['email'])) { - $email = $contact['email']; + if (is_array($contact) && !empty($contact['e-mailadres'])) { + $email = $contact['e-mailadres']; break; } } @@ -923,7 +1013,7 @@ private function getTestReceiverOverride(): ?string { $emailSettings = $this->settingsService->getEmailSettings(); $override = $emailSettings['testReceiverOverride'] ?? ''; - + return !empty($override) && $this->validateEmail($override) ? $override : null; } @@ -966,7 +1056,7 @@ private function sendEmail( 'sender' => $senderEmail, 'transport' => $emailSettings['transportType'] ?? 'smtp' ]); - + return true; } catch (\Exception $e) { @@ -1021,11 +1111,11 @@ public function getSenderName(): string public function getEmailSettings(): array { $settings = $this->settingsService->getEmailSettings(); - + // Add transport information $settings['availableTransports'] = self::TRANSPORT_TYPES; $settings['transportType'] = $settings['transportType'] ?? 'smtp'; - + return $settings; } @@ -1100,7 +1190,7 @@ public function sendTestEmail(string $testEmail): bool $subject = 'Software Catalogus - Test Email (Symfony Mailer)'; $emailSettings = $this->getEmailSettings(); $transportType = $emailSettings['transportType'] ?? 'smtp'; - + $htmlBody = ' @@ -1211,7 +1301,7 @@ public function setUserPasswordEnabled(bool $enabled): void public function isEmailSystemConfigured(): array { $emailSettings = $this->settingsService->getEmailSettings(); - + // Check if emails are enabled if (!($emailSettings['enabled'] ?? false)) { return [ @@ -1221,15 +1311,15 @@ public function isEmailSystemConfigured(): array 'hasTemplates' => false ]; } - + // Check transport credentials $hasCredentials = $this->hasValidTransportCredentials($emailSettings); - + // Check templates $hasTemplates = $this->hasValidTemplates($emailSettings); - + $configured = $hasCredentials && $hasTemplates; - + return [ 'configured' => $configured, 'reason' => $configured ? 'Email system fully configured' : $this->getConfigurationIssues($hasCredentials, $hasTemplates), @@ -1238,7 +1328,7 @@ public function isEmailSystemConfigured(): array 'transportType' => $emailSettings['transportType'] ?? 'smtp' ]; } - + /** * Checks if the current transport has valid credentials * @@ -1248,7 +1338,7 @@ public function isEmailSystemConfigured(): array private function hasValidTransportCredentials(array $emailSettings): bool { $transportType = $emailSettings['transportType'] ?? 'smtp'; - + switch ($transportType) { case 'mailjet': return !empty($emailSettings['mailjetApiKey']) && !empty($emailSettings['mailjetSecretKey']); @@ -1270,7 +1360,7 @@ private function hasValidTransportCredentials(array $emailSettings): bool return false; } } - + /** * Checks if required templates are configured * @@ -1281,7 +1371,7 @@ private function hasValidTemplates(array $emailSettings): bool { $templates = $emailSettings['templates'] ?? []; $requiredTemplates = ['organization_registration', 'organization_activation', 'user_creation', 'user_password']; - + foreach ($requiredTemplates as $templateName) { $template = $templates[$templateName] ?? ''; // Template is valid if it's not empty or if we have a default template @@ -1289,10 +1379,10 @@ private function hasValidTemplates(array $emailSettings): bool return false; } } - + return true; } - + /** * Gets configuration issues description * @@ -1303,15 +1393,15 @@ private function hasValidTemplates(array $emailSettings): bool private function getConfigurationIssues(bool $hasCredentials, bool $hasTemplates): string { $issues = []; - + if (!$hasCredentials) { $issues[] = 'missing transport credentials'; } - + if (!$hasTemplates) { $issues[] = 'missing email templates'; } - + return 'Configuration incomplete: ' . implode(', ', $issues); } -} \ No newline at end of file +}