logoDirectory = $logoDirectory; } public function handleLogo(Organizations $organization, $logoFile): void { // 1. Define the destination directory (adjust path as needed, e.g., 'public/uploads/profile_pictures') $destinationDir = 'uploads/organization_logos'; // 2. Create the directory if it doesn't exist if (!file_exists($destinationDir)) { // 0755 is the standard permission (Owner: read/write/exec, Others: read/exec) if (!mkdir($destinationDir, 0755, true) && !is_dir($destinationDir)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $destinationDir)); } } $extension = $logoFile->guessExtension(); // Sanitize the filename to remove special characters/spaces to prevent filesystem errors $safeName = preg_replace('/[^a-zA-Z0-9]/', '', $organization->getName()); $customFilename = $safeName . '_' . date('dmyHis') . '.' . $extension; try { // 4. Move the file to the destination directory // The move() method is standard in Symfony/Laravel UploadedFile objects $logoFile->move($destinationDir, $customFilename); // 5. Update the user entity with the relative path // Ensure you store the path relative to your public folder usually $organization->setLogoUrl('/'.$destinationDir . '/' . $customFilename); } catch (\Exception $e) { // 6. Log the critical error as requested $this->loggerService->logError('File upload failed',[ 'target_organization_id' => $organization->getId(), 'message' => $e->getMessage(), 'file_name' => $customFilename, ]); // Optional: Re-throw the exception if you want the controller/user to know the upload failed throw new FileException('File upload failed.'); } } public function deleteLogo(Organizations $organization): void { // 1. Get the current picture path from the user entity $currentPicturePath = $organization->getLogoUrl(); // If the user doesn't have a picture, simply return (nothing to delete) if (!$currentPicturePath) { return; } try { // 2. Check if the file exists on the server before trying to delete // Note: Ensure $currentPicturePath is relative to the script execution or absolute. if (file_exists($currentPicturePath)) { // 3. Delete the file if (!unlink($currentPicturePath)) { throw new \Exception(sprintf('Could not unlink "%s"', $currentPicturePath)); } } else { // Optional: Log a warning if the DB had a path but the file was missing $this->loggerService->logError('File not found on disk during deletion request', [ 'target_organization_id' => $organization->getId(), 'missing_path' => $currentPicturePath ]); } // 4. Update the user entity to remove the reference $organization->setLogoUrl(""); } catch (\Exception $e) { // 5. Log the critical error // We log it, but strictly speaking, we might still want to nullify the DB // if the file is corrupted/un-deletable to prevent broken images on the frontend. $this->loggerService->logError('File deletion failed', [ 'target_organization_id' => $organization->getId(), 'message' => $e->getMessage(), 'file_path' => $currentPicturePath, ]); // Re-throw if you want to stop execution/show error to user throw new \RuntimeException('Unable to remove profile picture.'); } } /** * Merge all apps with org apps and add a "hasAccess" flag. * * @param array $appsAll * @param array $apps * @return array */ public function appsAccess(array $appsAll, array $apps): array { // Build a quick lookup of app IDs the org has access to $orgAppIds = array_map(static fn(Apps $app) => $app->getId(), $apps); $result = []; foreach ($appsAll as $app) { $result[] = [ 'entity' => $app, // Keep the full entity for Twig 'hasAccess' => in_array($app->getId(), $orgAppIds, true), ]; } return $result; } public function notifyOrganizationAdmins(array $data, string $type): void { $roleAdmin = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN']); $adminUOs = $this->uoRepository->findBy(['organization' => $data['organization'], 'isActive' => true]); foreach ($adminUOs as $adminUO) { $uoa = $this->entityManager->getRepository(UserOrganizatonApp::class) ->findOneBy([ 'userOrganization' => $adminUO, 'role' => $roleAdmin, 'isActive' => true ]); switch ($type) { case 'USER_ACCEPTED': if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) { $newUser = $data['user']; $this->notificationService->notifyUserAcceptedInvite( $adminUO->getUsers(), $newUser, $data['organization'] ); $this->loggerService->logAdminNotified([ 'admin_user_id' =>$adminUO->getUsers()->getId(), 'target_user_id' => $newUser->getId(), 'organization_id' => $data['organization']->getId(),'case' =>$type]); } break; case 'USER_INVITED': if ($uoa) { $invitedUser = $data['user']; $this->notificationService->notifyUserInvited( $adminUO->getUsers(), $invitedUser, $data['organization'] ); $this->loggerService->logAdminNotified([ 'admin_user_id' =>$adminUO->getUsers()->getId(), 'target_user_id' => $invitedUser->getId(), 'organization_id' => $data['organization']->getId(),'case' =>$type]); } break; case 'USER_DEACTIVATED': if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) { $removedUser = $data['user']; $this->notificationService->notifyUserDeactivated( $adminUO->getUsers(), $removedUser, $data['organization'] ); $this->loggerService->logAdminNotified([ 'admin_user_id' =>$adminUO->getUsers()->getId(), 'target_user_id' => $removedUser->getId(), 'organization_id' => $data['organization']->getId(),'case' =>$type]); } break; case 'USER_DELETED': if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) { $removedUser = $data['user']; $this->notificationService->notifyUserDeleted( $adminUO->getUsers(), $removedUser, $data['organization'] ); $this->loggerService->logAdminNotified([ 'admin_user_id' =>$adminUO->getUsers()->getId(), 'target_user_id' => $removedUser->getId(), 'organization_id' => $data['organization']->getId(),'case' =>$type]); } break; case 'USER_ACTIVATED': if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) { $activatedUser = $data['user']; $this->notificationService->notifyUserActivated( $adminUO->getUsers(), $activatedUser, $data['organization'] ); $this->loggerService->logAdminNotified([ 'admin_user_id' =>$adminUO->getUsers()->getId(), 'target_user_id' => $activatedUser->getId(), 'organization_id' => $data['organization']->getId(),'case' =>$type]); } break; } } } /* Function that check if the project prefix was provided and if it is unique, if not it will generate a random one and check again until it is unique */ public function generateUniqueProjectPrefix(): string{ $prefix = $this->generateRandomPrefix(); while ($this->entityManager->getRepository(Organizations::class)->findOneBy(['projectPrefix' => $prefix])) { $prefix = $this->generateRandomPrefix(); } return $prefix; } private function generateRandomPrefix(): string { return substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 4); } }