diff --git a/assets/controllers/organization_controller.js b/assets/controllers/organization_controller.js index 6271eb8..a999613 100644 --- a/assets/controllers/organization_controller.js +++ b/assets/controllers/organization_controller.js @@ -72,7 +72,7 @@ export default class extends Controller { formatterParams: { height: "50px", width: "50px", - urlPrefix: "", + urlPrefix: "/", urlSuffix: "", }, width: 100, diff --git a/src/Controller/OrganizationController.php b/src/Controller/OrganizationController.php index 9691197..08642a1 100644 --- a/src/Controller/OrganizationController.php +++ b/src/Controller/OrganizationController.php @@ -232,6 +232,7 @@ class OrganizationController extends AbstractController try { $organization->setIsActive(false); $organization->setIsDeleted(true); + $this->organizationsService->deleteLogo($organization); // Deactivate all associated UsersOrganizations $this->userOrganizationService->deactivateAllUserOrganizationLinks($actingUser, null, $organization); @@ -344,12 +345,12 @@ class OrganizationController extends AbstractController // Map to array $data = array_map(function (Organizations $org) { - $picture = $this->awsService->getPublicUrl($_ENV['S3_PORTAL_BUCKET']) . $org->getLogoUrl(); + return [ 'id' => $org->getId(), 'name' => $org->getName(), 'email' => $org->getEmail(), - 'logoUrl' => $picture ?: null, + 'logoUrl' => $org->getLogoUrl() ?: null, 'active' => $org->isActive(), 'showUrl' => $this->generateUrl('organization_show', ['id' => $org->getId()]), ]; diff --git a/src/Service/OrganizationsService.php b/src/Service/OrganizationsService.php index 25e2400..c236ba3 100644 --- a/src/Service/OrganizationsService.php +++ b/src/Service/OrganizationsService.php @@ -30,25 +30,86 @@ class OrganizationsService 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(); - $customFilename = $organization->getName() . '_' . date('dmyHis') . "." . $extension; + // 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 { - $this->awsService->PutDocObj($_ENV['S3_PORTAL_BUCKET'], $logoFile, $customFilename, $extension, 'logo/'); - $this->loggerService->logAWSAction('Upload organization logo', [ - 'organization_id' => $organization->getId(), - 'filename' => $customFilename, - 'bucket' => $_ENV['S3_PORTAL_BUCKET'], + // 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, ]); - $organization->setLogoUrl('logo/' . $customFilename); - } catch (FileException $e) { - $this->loggerService->logError('Failed to upload organization logo to S3', [ - 'organization_id' => $organization->getId(), - 'error' => $e->getMessage(), - 'bucket' => $_ENV['S3_PORTAL_BUCKET'], + + // 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, ]); - throw new FileException('Failed to upload logo to S3: ' . $e->getMessage()); + + // Re-throw if you want to stop execution/show error to user + throw new \RuntimeException('Unable to remove profile picture.'); } } diff --git a/templates/organization/show.html.twig b/templates/organization/show.html.twig index 95b045c..a24e1b2 100644 --- a/templates/organization/show.html.twig +++ b/templates/organization/show.html.twig @@ -12,7 +12,7 @@