src/Controller/BusinessContactsController.php line 33

  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\BusinessContacts;
  4. use App\Form\BusinessContactsType;
  5. use App\Form\ImportType;
  6. use App\Repository\BusinessContactsRepository;
  7. use App\Repository\BusinessTypesRepository;
  8. use App\Services\BusinessContactVCFExport;
  9. use App\Services\ImportBusinessContactsService;
  10. use App\Services\CompanyDetailsService;
  11. use App\Services\CountBusinessContactsService;
  12. use App\Services\PhoneAnalyzer;
  13. use Doctrine\ORM\EntityManagerInterface;
  14. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  15. use PhpOffice\PhpSpreadsheet\Writer\Csv;
  16. use Psr\Log\LoggerInterface;
  17. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  18. use Symfony\Bundle\SecurityBundle\Security;
  19. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  20. use Symfony\Component\HttpFoundation\File\Exception\FileException;
  21. use Symfony\Component\HttpFoundation\Request;
  22. use Symfony\Component\HttpFoundation\Response;
  23. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  24. use Symfony\Component\HttpFoundation\StreamedResponse;
  25. use Symfony\Component\Routing\Annotation\Route;
  26. use Symfony\Component\String\Slugger\SluggerInterface;
  27. /**
  28.  * @Route("/businesscontacts")
  29.  */
  30. class BusinessContactsController extends AbstractController
  31. {
  32.     public function __construct(private readonly LoggerInterface $logger)
  33.     {}
  34.     /**
  35.      * @Route("/index", name="business_contacts_index", methods={"GET"})
  36.      */
  37.     public function index(Request $requestBusinessContactsRepository $businessContactsRepositoryBusinessTypesRepository $businessTypesRepositoryCountBusinessContactsService $countBusinessContactsServicePhoneAnalyzer $phoneAnalyzer): Response
  38.     {
  39.         // All types (for selectors, etc.) — alpha by label
  40.         $business_types_all $businessTypesRepository->findAll();
  41.         usort($business_types_all, function ($a$b) {
  42.             return strcmp($a->getBusinessType(), $b->getBusinessType());
  43.         });
  44.         $businessTypeName $request->query->get('business_type');
  45.         $adminViewParam $request->query->get('admin_view'); // "Admin" or null
  46.         $isAdmin $this->isGranted('ROLE_ADMIN');
  47.         $isAdminView $isAdmin && $adminViewParam === 'Admin';   // 👈 only true when admin *and* button toggled on
  48.         // Decide contacts + types as before
  49.         if ($businessTypeName) {
  50.             $businessType $businessTypesRepository->findOneBy(['businessType' => $businessTypeName]);
  51.             if ($businessType) {
  52.                 // Filtered to a single type
  53.                 $business_contacts $businessContactsRepository->findBy(
  54.                     ['businessType' => $businessType],
  55.                     ['id' => 'ASC']
  56.                 );
  57.                 $business_types = [$businessType]; // headings for only this type
  58.             } else {
  59.                 // Invalid filter: fallback to all
  60.                 $business_contacts $businessContactsRepository->findAll();
  61.                 $business_types $businessTypesRepository->findBy([], ['ranking' => 'ASC']);
  62.             }
  63.         } else {
  64.             // No filter: show everything
  65.             $business_contacts $businessContactsRepository->findAll();
  66.             $business_types $businessTypesRepository->findBy([], ['ranking' => 'ASC']);
  67.         }
  68.         // 🔐 Role-based + AdminView-based filtering
  69.         // If NOT in AdminView (i.e. non-admin OR admin with AdminView OFF) → show only Approved
  70.         if (!$isAdminView) {
  71.             $business_contacts array_filter($business_contacts, function ($contact) {
  72.                 return method_exists($contact'getStatus')
  73.                     && $contact->getStatus() === 'Approved';
  74.             });
  75.             $business_contacts array_values($business_contacts);
  76.         }
  77.         // Analyze phones (as before)
  78.         foreach ($business_contacts as $business_contact) {
  79.             $business_contact->phoneAnalysis = [
  80.                 'mobile' => $business_contact->getMobile() ? $phoneAnalyzer->analyze($business_contact->getMobile()) : null,
  81.                 'landline' => $business_contact->getLandline() ? $phoneAnalyzer->analyze($business_contact->getLandline()) : null,
  82.             ];
  83.         }
  84.         // --- Group + sort within each group: company -> firstName -> lastName ---
  85.         $norm = function (?string $s): string {
  86.             return $s === null '' mb_strtolower(trim($s));
  87.         };
  88.         $cmp = function ($a$b) use ($norm): int {
  89.             $aCompany $norm(method_exists($a'getCompany') ? $a->getCompany() : null);
  90.             $bCompany $norm(method_exists($b'getCompany') ? $b->getCompany() : null);
  91.             $aFirst $norm(method_exists($a'getFirstName') ? $a->getFirstName() : null);
  92.             $bFirst $norm(method_exists($b'getFirstName') ? $b->getFirstName() : null);
  93.             $aLast $norm(method_exists($a'getLastName') ? $a->getLastName() : null);
  94.             $bLast $norm(method_exists($b'getLastName') ? $b->getLastName() : null);
  95.             $emptyOrder = function (string $xstring $y): ?int {
  96.                 $xe = ($x === '');
  97.                 $ye = ($y === '');
  98.                 if ($xe && !$ye) return 1;
  99.                 if (!$xe && $ye) return -1;
  100.                 return null;
  101.             };
  102.             if (($eo $emptyOrder($aCompany$bCompany)) !== null) return $eo;
  103.             if ($aCompany !== $bCompany) return $aCompany <=> $bCompany;
  104.             if (($eo $emptyOrder($aFirst$bFirst)) !== null) return $eo;
  105.             if ($aFirst !== $bFirst) return $aFirst <=> $bFirst;
  106.             if (($eo $emptyOrder($aLast$bLast)) !== null) return $eo;
  107.             if ($aLast !== $bLast) return $aLast <=> $bLast;
  108.             $aId method_exists($a'getId') ? (int)$a->getId() : 0;
  109.             $bId method_exists($b'getId') ? (int)$b->getId() : 0;
  110.             return $aId <=> $bId;
  111.         };
  112.         $contacts_by_type = [];
  113.         foreach ($business_types as $bt) {
  114.             $contacts_by_type[$bt->getId()] = [];
  115.         }
  116.         foreach ($business_contacts as $c) {
  117.             $type $c->getBusinessType();
  118.             if ($type && array_key_exists($type->getId(), $contacts_by_type)) {
  119.                 $contacts_by_type[$type->getId()][] = $c;
  120.             }
  121.         }
  122.         foreach ($contacts_by_type as &$group) {
  123.             usort($group$cmp);
  124.         }
  125.         unset($group);
  126.         return $this->render('business_contacts/index.html.twig', [
  127.             'business_contacts' => $business_contacts,
  128.             'business_types' => $business_types,
  129.             'business_types_all' => $business_types_all,
  130.             'countBusinessContactsService' => $countBusinessContactsService,
  131.             'list_or_map' => 'list',
  132.             'admin_check' => $isAdminView 'Yes' 'No'// 👈 now reflects the toggle, not just the role
  133.             'current_business_type' => $businessTypeName,
  134.             'contacts_by_type' => $contacts_by_type,
  135.         ]);
  136.     }
  137.     /**
  138.      * @Route("/map/{subset}", name="business_contacts_map", methods={"GET"})
  139.      */
  140.     public function map(Request $requeststring $subsetBusinessContactsRepository $businessContactsRepositoryBusinessTypesRepository $businessTypesRepositoryCountBusinessContactsService $countBusinessContacts): Response
  141.     {
  142.         $isAdmin $this->isGranted('ROLE_ADMIN');
  143.         // Build criteria
  144.         $criteria = [];
  145.         if (!$isAdmin) {
  146.             $criteria['status'] = 'Approved';
  147.         }
  148.         if ($subset !== 'All') {
  149.             $business_type $businessTypesRepository->findOneBy(['businessType' => $subset]);
  150.             // If the subset name is invalid, return no results instead of error
  151.             if ($business_type) {
  152.                 $criteria['businessType'] = $business_type;
  153.             } else {
  154.                 $business_contacts = [];
  155.             }
  156.         }
  157.         // Fetch contacts (if not already set due to invalid subset)
  158.         if (!isset($business_contacts)) {
  159.             $business_contacts $businessContactsRepository->findBy($criteria);
  160.         }
  161.         // Compute map stats (only count rows with valid coords)
  162.         $latitude_total 0.0;
  163.         $longitude_total 0.0;
  164.         $count 0;
  165.         $latitude_max = -100.0;
  166.         $latitude_min 100.0;
  167.         $longitude_max = -100.0;
  168.         $longitude_min 100.0;
  169.         foreach ($business_contacts as $business_contact) {
  170.             $lat $business_contact->getLocationLatitude();
  171.             $lng $business_contact->getLocationLongitude();
  172.             // Must have both coords and not be zero
  173.             if ($lat !== null && $lng !== null && (float)$lat !== 0.0 && (float)$lng !== 0.0) {
  174.                 $count++;
  175.                 $latitude_total += (float)$lat;
  176.                 $longitude_total += (float)$lng;
  177.                 if ($lat $latitude_max$latitude_max = (float)$lat;
  178.                 if ($lat $latitude_min$latitude_min = (float)$lat;
  179.                 if ($lng $longitude_max$longitude_max = (float)$lng;
  180.                 if ($lng $longitude_min$longitude_min = (float)$lng;
  181.             }
  182.         }
  183.         if ($count === 0) {
  184.             $latitude_average 'No data';
  185.             $longitude_average 'No data';
  186.             $latitude_range 'TBD';
  187.             $longitude_range 'TBD';
  188.         } else {
  189.             $latitude_average $latitude_total $count;
  190.             $longitude_average $longitude_total $count;
  191.             if ($count === 1) {
  192.                 $latitude_range 'TBD';
  193.                 $longitude_range 'TBD';
  194.             } else {
  195.                 $latitude_range $latitude_max $latitude_min;
  196.                 $longitude_range $longitude_max $longitude_min;
  197.             }
  198.         }
  199.         $business_types $businessTypesRepository->findBy([], ['ranking' => 'ASC']);
  200.         return $this->render('business_contacts/map_of_business_contacts.html.twig', [
  201. //            'google_maps_api_key' => $this->getParameter('google_maps_api_key'),
  202.             'business_contacts' => $business_contacts,
  203.             'business_types' => $business_types,
  204.             'subset' => $subset,
  205.             'latitude_max' => $latitude_max,
  206.             'latitude_min' => $latitude_min,
  207.             'latitude_average' => $latitude_average,
  208.             'latitude_range' => $latitude_range,
  209.             'longitude_max' => $longitude_max,
  210.             'longitude_min' => $longitude_min,
  211.             'longitude_average' => $longitude_average,
  212.             'longitude_range' => $longitude_range,
  213.             'count' => $count,
  214.             'list_or_map' => 'map',
  215.             'admin_check' => $isAdmin 'Yes' 'No',
  216.         ]);
  217.     }
  218.     /**
  219.      * @Route("/new/{business_type}", name="business_contacts_new", methods={"GET", "POST"})
  220.      */
  221.     public function new(Request $requeststring $business_typeBusinessContactsRepository $businessContactsRepositoryBusinessTypesRepository $businessTypesRepositoryCompanyDetailsService $companyDetailsEntityManagerInterface $entityManager): Response
  222.     {
  223.         $business_type $businessTypesRepository->find($business_type);
  224.         $default_country $companyDetails->getCompanyDetails()->getCompanyAddressCountry();
  225.         $businessContact = new BusinessContacts();
  226.         $businessContact->setBusinessType($business_type);
  227.         $businessContact->setAddressCountry($default_country);
  228.         $form $this->createForm(BusinessContactsType::class, $businessContact);
  229.         $form->handleRequest($request);
  230.         if ($form->isSubmitted() && $form->isValid()) {
  231.             $photo $form['photo']->getData();
  232.             if ($photo) {
  233.                 $uniqueId uniqid(); // Generates a unique ID
  234.                 $uniqueId3digits substr($uniqueId103); // Extracts the first 3 digits
  235.                 $files_name = [];
  236.                 $photo_directory $this->getParameter('business_contacts_photos_directory');
  237.                 $fileName pathinfo($photo->getClientOriginalName(), PATHINFO_FILENAME);
  238.                 $file_extension $photo->guessExtension();
  239.                 $newFileName $businessContact->getCompany() . "_" $uniqueId3digits "." $file_extension;
  240.                 $photo->move($photo_directory$newFileName);
  241.                 $businessContact->setPhoto($newFileName);
  242.             }
  243.             $file $form['files']->getData();
  244.             if ($file) {
  245.                 $file_name = [];
  246.                 $file_directory $this->getParameter('business_contacts_attachments_directory');
  247.                 $fileName pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
  248.                 $file_extension $file->guessExtension();
  249.                 $newFileName $fileName "." $file_extension;
  250.                 $file->move($file_directory$newFileName);
  251.                 $businessContact->setFiles($newFileName);
  252.             }
  253.             $businessContactsRepository->add($businessContacttrue);
  254.             $firstName $businessContact->getFirstName();
  255.             $lastName $businessContact->getLastName();
  256.             $entityManager->persist($businessContact);
  257.             $entityManager->flush();
  258.             return $this->redirectToRoute('business_contacts_index', [], Response::HTTP_SEE_OTHER);
  259.         }
  260.         return $this->renderForm('business_contacts/new.html.twig', [
  261.             'business_contact' => $businessContact,
  262.             'form' => $form,
  263.         ]);
  264.     }
  265.     /**
  266.      * @Route("/suggestion", name="business_contacts_suggestion", methods={"GET", "POST"})
  267.      */
  268.     public function suggestion(Request $requestBusinessContactsRepository $businessContactsRepository): Response
  269.     {
  270.         $businessContact = new BusinessContacts();
  271.         $form $this->createForm(BusinessContactsType::class, $businessContact);
  272.         $form->handleRequest($request);
  273.         if ($form->isSubmitted() && $form->isValid()) {
  274.             $businessContactsRepository->add($businessContacttrue);
  275.             $businessContact->setStatus('Pending');
  276.             return $this->redirectToRoute('business_contacts_index', [], Response::HTTP_SEE_OTHER);
  277.         }
  278.         return $this->renderForm('business_contacts/new.html.twig', [
  279.             'business_contact' => $businessContact,
  280.             'form' => $form,
  281.         ]);
  282.     }
  283.     /**
  284.      * @Route("/show/{id}", name="business_contacts_show", methods={"GET"})
  285.      */
  286.     public function show(BusinessContacts $businessContact): Response
  287.     {
  288.         $longitude $businessContact->getLocationLongitude();
  289.         $latitude $businessContact->getLocationLatitude();
  290.         return $this->render('business_contacts/show.html.twig', [
  291.             'business_contact' => $businessContact,
  292.             'longitude' => $longitude,
  293.             'latitude' => $latitude,
  294.         ]);
  295.     }
  296.     /**
  297.      * @Route("/edit/{id}", name="business_contacts_edit", methods={"GET", "POST"})
  298.      */
  299.     public function edit(Request $requestBusinessContacts $businessContactBusinessContactsRepository $businessContactsRepository): Response
  300.     {
  301.         $form $this->createForm(BusinessContactsType::class, $businessContact);
  302.         $form->handleRequest($request);
  303.         if ($form->isSubmitted() && $form->isValid()) {
  304.             $photo $form['photo']->getData();
  305.             if ($photo) {
  306.                 $files_name = [];
  307.                 $photo_directory $this->getParameter('business_contacts_photos_directory');
  308.                 $fileName pathinfo($photo->getClientOriginalName(), PATHINFO_FILENAME);
  309.                 $file_extension $photo->guessExtension();
  310.                 if ($businessContact->getFirstName() == '') {
  311.                     $newFileName $businessContact->getCompany() . "." $file_extension;
  312.                 } else {
  313.                     $newFileName $businessContact->getCompany() . "_" $businessContact->getFirstName() . "_" $businessContact->getLastName() . "." $file_extension;
  314.                 }
  315.                 $photo->move($photo_directory$newFileName);
  316.                 $businessContact->setPhoto($newFileName);
  317.             }
  318.             $file $form['files']->getData();
  319.             if ($file) {
  320.                 $file_name = [];
  321.                 $file_directory $this->getParameter('business_contacts_attachments_directory');
  322.                 $fileName pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
  323.                 $file_extension $file->guessExtension();
  324.                 $newFileName $fileName "." $file_extension;
  325.                 $file->move($file_directory$newFileName);
  326.                 $businessContact->setFiles($newFileName);
  327.             }
  328.             $businessContactsRepository->add($businessContacttrue);
  329.             return $this->redirectToRoute('business_contacts_index', [], Response::HTTP_SEE_OTHER);
  330.         }
  331.         return $this->renderForm('business_contacts/edit.html.twig', [
  332.             'business_contact' => $businessContact,
  333.             'form' => $form,
  334.         ]);
  335.     }
  336.     /**
  337.      * @Route("/toggle_status/{id}", name="business_contacts_toggle_status", methods={"POST","GET"})
  338.      */
  339.     public function toggleStatus(BusinessContacts $businessContactEntityManagerInterface $entityManager): Response
  340.     {
  341.         $this->denyAccessUnlessGranted('ROLE_ADMIN');
  342.         $current $businessContact->getStatus();
  343.         if ($current === 'Approved') {
  344.             $businessContact->setStatus('Not approved');
  345.         } elseif ($current === 'Not approved') {
  346.             $businessContact->setStatus('Approved');
  347.         }
  348.         $entityManager->flush();
  349.         return $this->redirectToRoute('business_contacts_index');
  350.     }
  351.     /**
  352.      * @Route("/delete/{id}", name="business_contacts_delete", methods={"POST"})
  353.      */
  354.     public function delete(Request $requestBusinessContacts $businessContactBusinessContactsRepository $businessContactsRepositoryEntityManagerInterface $entityManager): Response
  355.     {
  356.         $referer $request->headers->get('referer');
  357.         $file_name $businessContact->getFiles();
  358.         if ($file_name) {
  359.             $file $this->getParameter('business_contacts_attachments_directory') . $file_name;
  360.             if (file_exists($file)) {
  361.                 unlink($file);
  362.             }
  363.             $businessContact->setFiles('');
  364.             $entityManager->flush();
  365.         }
  366.         $photo_file_name $businessContact->getPhoto();
  367.         if ($photo_file_name) {
  368.             $photo_file_name $this->getParameter('business_contacts_photos_directory') . $photo_file_name;
  369.             if (file_exists($photo_file_name)) {
  370.                 unlink($photo_file_name);
  371.             }
  372.             $businessContact->setPhoto('');
  373.             $entityManager->flush();
  374.         }
  375.         if ($this->isCsrfTokenValid('delete' $businessContact->getId(), $request->request->get('_token'))) {
  376.             $id $businessContact->getId();
  377.             $businessContactsRepository->remove($businessContacttrue);
  378.             // Check if referer is a show or edit page for this deleted entity, if so redirect to index
  379.             if ($referer) {
  380.                 $refererPath parse_url($refererPHP_URL_PATH);
  381.                 if (strpos($refererPath'/business_contacts/show/' $id) !== false ||
  382.                     strpos($refererPath'/business_contacts/edit/' $id) !== false) {
  383.                     return $this->redirectToRoute('business_contacts_index');
  384.                 }
  385.             }
  386.         }
  387.         return $this->redirect($referer ?: $this->generateUrl('business_contacts_index'));
  388.     }
  389.     /**
  390.      * @Route("/delete_GPS_location/{id}", name="business_contacts_delete_GPS_location", methods={"POST"})
  391.      */
  392.     public function deleteGPSLocation(Request $requestint $idBusinessContactsRepository $businessContactsRepositoryEntityManagerInterface $entityManager): Response
  393.     {
  394.         $referer $request->headers->get('referer');
  395.         $businessContact $businessContactsRepository->find($id);
  396.         if ($this->isCsrfTokenValid('delete_gps' $businessContact->getId(), $request->request->get('_token'))) {
  397.             $businessContact->setLocationLatitude(null);
  398.             $businessContact->setLocationLongitude(null);
  399.             $entityManager->flush();
  400.         }
  401.         return $this->redirect($referer ?: $this->generateUrl('business_contacts_index'));
  402.     }
  403.     /**
  404.      * @Route("/delete_all_GPS_locations", name="business_contacts_delete_all_GPS_locations", methods={"POST"})
  405.      */
  406.     public function deleteAllBusinessContactsGPSLocations(Request $requestBusinessContactsRepository $businessContactsRepositoryEntityManagerInterface $entityManager): Response
  407.     {
  408.         $referer $request->headers->get('referer');
  409.         if ($this->isCsrfTokenValid('delete_all_gps'$request->request->get('_token'))) {
  410.             $business_contacts $businessContactsRepository->findAll();
  411.             foreach ($business_contacts as $business_contact) {
  412.                 $business_contact->setLocationLongitude(null);
  413.                 $business_contact->setLocationLatitude(null);
  414.             }
  415.             $entityManager->flush();
  416.         }
  417.         return $this->redirect($referer ?: $this->generateUrl('business_contacts_index'));
  418.     }
  419.     /**
  420.      * @Route("/delete_all", name="business_contacts_delete_all", methods={"POST"})
  421.      */
  422.     public function deleteAllBusinessContacts(Request $requestBusinessContactsRepository $businessContactsRepositoryEntityManagerInterface $entityManager): Response
  423.     {
  424.         if ($this->isCsrfTokenValid('delete_all_business_contacts'$request->request->get('_token'))) {
  425.             $business_contacts $businessContactsRepository->findAll();
  426.             foreach ($business_contacts as $business_contact) {
  427.                 $entityManager->remove($business_contact);
  428.             }
  429.             $entityManager->flush();
  430.         }
  431.         return $this->redirectToRoute('business_contacts_index', [], Response::HTTP_SEE_OTHER);
  432.     }
  433.     /**
  434.      * @Route("/delete_photo_file/{id}", name="business_contact_delete_photo_file", methods={"POST"})
  435.      */
  436.     public function deleteBusinessContactPhotoFile(int $idRequest $requestBusinessContactsRepository $businessContactsRepositoryEntityManagerInterface $entityManager): Response
  437.     {
  438.         $businessContact $businessContactsRepository->find($id);
  439.         if (!$businessContact) {
  440.             throw $this->createNotFoundException('Business contact not found');
  441.         }
  442.         $referer $request->headers->get('referer') ?? $this->generateUrl('business_contacts_edit', ['id' => $id]);
  443.         if ($this->isCsrfTokenValid('delete_photo' $businessContact->getId(), $request->request->get('_token'))) {
  444.             $photoFileName $businessContact->getPhoto();
  445.             if ($photoFileName) {
  446.                 $fullPath $this->getParameter('business_contacts_photos_directory') . DIRECTORY_SEPARATOR $photoFileName;
  447.                 if (is_file($fullPath)) {
  448.                     @unlink($fullPath);
  449.                 }
  450.                 $businessContact->setPhoto(null);
  451.                 $entityManager->flush();
  452.             }
  453.         }
  454.         return $this->redirect($referer);
  455.     }
  456.     /**
  457.      * @Route("/delete_attachment_file/{id}", name="business_contact_delete_attachment_file", methods={"POST"})
  458.      */
  459.     public function deleteBusinessContactAttachmentFile(int $idRequest $requestBusinessContacts $businessContactEntityManagerInterface $entityManager)
  460.     {
  461.         $referer $request->headers->get('referer');
  462.         if ($this->isCsrfTokenValid('delete_attachment' $businessContact->getId(), $request->request->get('_token'))) {
  463.             $file_name $businessContact->getFiles();
  464.             if ($file_name) {
  465.                 $file $this->getParameter('business_contacts_attachments_directory') . "/" $file_name;
  466.                 if (file_exists($file)) {
  467.                     unlink($file);
  468.                 }
  469.                 $businessContact->setFiles('');
  470.                 $entityManager->flush();
  471.             }
  472.         }
  473.         return $this->redirect($referer);
  474.     }
  475.     /**
  476.      * @Route ("/view/photo/{id}", name="view_business_contact_photo")
  477.      */
  478.     public function viewBusinessContactPhoto(Request $request$idBusinessContactsRepository $businessContactsRepository)
  479.     {
  480.         $business_contact $businessContactsRepository->find($id);
  481.         return $this->render('business_contacts/view_photo.html.twig', ['business_contact' => $business_contact]);
  482.     }
  483.     /**
  484.      * @Route ("/business_contacts_export", name="business_contacts_export" )
  485.      */
  486.     public function businessContactsExport(BusinessContactsRepository $businessContactsRepository)
  487.     {
  488.         $data = [];
  489.         $exported_date = new \DateTime('now');
  490.         $exported_date_formatted $exported_date->format('d-M-Y');
  491.         $fileName 'business_contacts_export_' $exported_date_formatted '.csv';
  492.         $count 0;
  493.         $business_contact_list $businessContactsRepository->findAll();
  494.         foreach ($business_contact_list as $business_contact) {
  495.             $data[] = [
  496.                 "BusinessContacts",
  497.                 $business_contact->getStatus(),
  498.                 $business_contact->getBusinessOrPerson(),
  499.                 $business_contact->getBusinessType()->getBusinessType(),
  500.                 $business_contact->getCompany(),
  501.                 $business_contact->getFirstName(),
  502.                 $business_contact->getLastName(),
  503.                 $business_contact->getWebsite(),
  504.                 $business_contact->getEmail(),
  505.                 $business_contact->getLandline(),
  506.                 $business_contact->getMobile(),
  507.                 $business_contact->getAddressStreet(),
  508.                 $business_contact->getAddressCity(),
  509.                 $business_contact->getAddressCounty(),
  510.                 $business_contact->getAddressPostCode(),
  511.                 $business_contact->getAddressCountry(),
  512.                 $business_contact->getLocationLongitude(),
  513.                 $business_contact->getLocationLatitude(),
  514.                 $business_contact->getNotes()
  515.             ];
  516.         }
  517.         $spreadsheet = new Spreadsheet();
  518.         $sheet $spreadsheet->getActiveSheet();
  519.         $sheet->setTitle('Business Contacts');
  520.         $sheet->getCell('A1')->setValue('Entity');
  521.         $sheet->getCell('B1')->setValue('Status');
  522.         $sheet->getCell('C1')->setValue('Business Or Person');
  523.         $sheet->getCell('D1')->setValue('Business Type');
  524.         $sheet->getCell('E1')->setValue('Company');
  525.         $sheet->getCell('F1')->setValue('First Name');
  526.         $sheet->getCell('G1')->setValue('Last Name');
  527.         $sheet->getCell('H1')->setValue('Web Page');
  528.         $sheet->getCell('I1')->setValue('E-mail');
  529.         $sheet->getCell('J1')->setValue('Business Phone');
  530.         $sheet->getCell('K1')->setValue('Mobile Phone');
  531.         $sheet->getCell('L1')->setValue('Business Street');
  532.         $sheet->getCell('M1')->setValue('Business City');
  533.         $sheet->getCell('N1')->setValue('Business County');
  534.         $sheet->getCell('O1')->setValue('Business Postal Code');
  535.         $sheet->getCell('P1')->setValue('Business Country/Region');
  536.         $sheet->getCell('Q1')->setValue('Location Longitude');
  537.         $sheet->getCell('R1')->setValue('Location Latitude');
  538.         $sheet->getCell('S1')->setValue('Notes');
  539.         $sheet->fromArray($datanull'A2'true);
  540.         $total_rows $sheet->getHighestRow();
  541.         for ($i 2$i <= $total_rows$i++) {
  542.             $cell "L" $i;
  543.             $sheet->getCell($cell)->getHyperlink()->setUrl("https://google.com");
  544.         }
  545.         $writer = new Csv($spreadsheet);
  546.         $response = new StreamedResponse(function () use ($writer) {
  547.             $writer->save('php://output');
  548.         });
  549.         $response->headers->set('Content-Type''application/vnd.ms-excel');
  550.         $response->headers->set('Content-Disposition'sprintf('attachment;filename="%s"'$fileName));
  551.         $response->headers->set('Cache-Control''max-age=0');
  552.         return $response;
  553.     }
  554.     /**
  555.      * Export all BusinessContacts as a ZIP of individual .vcf files.
  556.      *
  557.      * @Route("/business_contacts_export_vcf_zip", name="business_contacts_export_vcf_zip", methods={"GET"})
  558.      */
  559.     public function exportBusinessContactsAsZip(BusinessContactsRepository $businessContactsRepositoryBusinessContactVCFExport $businessContactVCFExport): Response
  560.     {
  561.         $contacts $businessContactsRepository->findAll();
  562.         if (!$contacts) {
  563.             return new Response('No contacts to export.'Response::HTTP_NO_CONTENT);
  564.         }
  565.         // Final file name: business_contacts_YYYYMMDD.zip
  566.         $datePart = (new \DateTimeImmutable('now'))->format('Ymd');
  567.         $baseName "business_contacts_{$datePart}";
  568.         $exportDir rtrim($businessContactVCFExport->getExportDirectory(), "/\\");
  569.         $zipPath $exportDir DIRECTORY_SEPARATOR $baseName '.zip';
  570.         // Ensure export dir is OK
  571.         if (!is_dir($exportDir) || !is_writable($exportDir)) {
  572.             $msg "Export directory is not writable: {$exportDir}";
  573.             $this->logger->error($msg);
  574.             return new Response($msgResponse::HTTP_INTERNAL_SERVER_ERROR);
  575.         }
  576.         // Make name unique if needed
  577.         if (file_exists($zipPath)) {
  578.             $i 1;
  579.             do {
  580.                 $zipPath $exportDir DIRECTORY_SEPARATOR $baseName '-' $i '.zip';
  581.                 $i++;
  582.             } while (file_exists($zipPath));
  583.         }
  584.         $downloadName basename($zipPath); // what the user downloads
  585.         // Create temp dir for individual VCFs
  586.         $tempDir sys_get_temp_dir() . DIRECTORY_SEPARATOR 'bc_vcards_' bin2hex(random_bytes(6));
  587.         if (!@mkdir($tempDir0777true) && !is_dir($tempDir)) {
  588.             $msg "Failed to create temporary directory: {$tempDir}";
  589.             $this->logger->error($msg);
  590.             return new Response($msgResponse::HTTP_INTERNAL_SERVER_ERROR);
  591.         }
  592.         try {
  593.             // Write each contact as its own .vcf using the service's BusinessContacts generator
  594.             foreach ($contacts as $c) {
  595.                 $first trim((string)($c->getFirstName() ?? ''));
  596.                 $last trim((string)($c->getLastName() ?? ''));
  597.                 $comp trim((string)($c->getCompany() ?? ''));
  598.                 $label $comp !== '' $comp trim($first ' ' $last);
  599.                 if ($label === '') {
  600.                     $label 'contact';
  601.                 }
  602.                 $safe preg_replace('/[^a-zA-Z0-9._-]/''_'$label);
  603.                 $vcfPath $tempDir DIRECTORY_SEPARATOR $safe '.vcf';
  604.                 // This writes the file to $vcfPath (and returns its contents)
  605.                 $businessContactVCFExport->generateVCard($c$vcfPath);
  606.             }
  607.             // Zip them up
  608.             $zip = new \ZipArchive();
  609.             if ($zip->open($zipPath\ZipArchive::CREATE \ZipArchive::OVERWRITE) !== true) {
  610.                 throw new \RuntimeException("Cannot create ZIP archive at: {$zipPath}");
  611.             }
  612.             foreach (glob($tempDir DIRECTORY_SEPARATOR '*.vcf') as $file) {
  613.                 $zip->addFile($filebasename($file));
  614.             }
  615.             $zip->close();
  616.         } catch (\Throwable $e) {
  617.             $this->logger->error('Failed to generate ZIP of vCards: ' $e->getMessage(), ['exception' => $e]);
  618.             // Cleanup temp dir on failure
  619.             foreach (glob($tempDir DIRECTORY_SEPARATOR '*') ?: [] as $f) {
  620.                 @unlink($f);
  621.             }
  622.             @rmdir($tempDir);
  623.             return new Response('Failed to generate ZIP: ' $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
  624.         }
  625.         // Cleanup temp dir (ZIP stays until sent)
  626.         foreach (glob($tempDir DIRECTORY_SEPARATOR '*') ?: [] as $f) {
  627.             @unlink($f);
  628.         }
  629.         @rmdir($tempDir);
  630.         /** @var BinaryFileResponse $response */
  631.         $response $this->file($zipPath$downloadNameResponseHeaderBag::DISPOSITION_ATTACHMENT);
  632.         // Delete the ZIP after it's been sent to the client
  633.         $response->deleteFileAfterSend(true);
  634.         return $response;
  635.     }
  636.     /**
  637.      * @Route ("/business_contacts_import", name="business_contacts_import" )
  638.      */
  639.     public function businessContactsImport(Request $requestSluggerInterface $sluggerBusinessContactsRepository $businessContactsRepositoryImportBusinessContactsService $businessContactsImportService): Response
  640.     {
  641.         $form $this->createForm(ImportType::class);
  642.         $form->handleRequest($request);
  643.         if ($form->isSubmitted() && $form->isValid()) {
  644.             $importFile $form->get('File')->getData();
  645.             if ($importFile) {
  646.                 $originalFilename pathinfo($importFile->getClientOriginalName(), PATHINFO_FILENAME);
  647.                 $safeFilename $slugger->slug($originalFilename);
  648.                 $newFilename $safeFilename '.' 'csv';
  649.                 try {
  650.                     $importFile->move(
  651.                         $this->getParameter('business_contacts_import_directory'),
  652.                         $newFilename
  653.                     );
  654.                 } catch (FileException $e) {
  655.                     die('Import failed');
  656.                 }
  657.                 $businessContactsImportService->importBusinessContacts($newFilename);
  658.                 return $this->redirectToRoute('business_contacts_index');
  659.             }
  660.         }
  661.         return $this->render('home/import.html.twig', [
  662.             'form' => $form->createView(),
  663.             'heading' => 'Business Contacts Import',
  664.         ]);
  665.     }
  666.     /**
  667.      * @Route("/update/location", name="update_business_contact_location", methods={"POST"})
  668.      */
  669.     public function updateLocation(BusinessContactsRepository $businessContactsRepositoryEntityManagerInterface $manager): Response
  670.     {
  671.         $id $_POST['id'];
  672.         $latitude $_POST['latitude'];
  673.         $longitude $_POST['longitude'];
  674.         $gps $latitude ',' $longitude;
  675.         $business_contact $businessContactsRepository->find($id);
  676.         $business_contact->setLocationLongitude($longitude)
  677.             ->setLocationLatitude($latitude);
  678.         $manager->flush();
  679.         return new Response(null);
  680.     }
  681.     /**
  682.      * @Route("/gps_location_clear/{id}", name="business_contact_clear_gps_location")
  683.      */
  684.     public function clearGPSLocation(Request $requestBusinessContacts $businessContactsEntityManagerInterface $entityManager)
  685.     {
  686.         $referer $request->headers->get('referer');
  687.         $businessContacts->setLocationLongitude(null);
  688.         $businessContacts->setLocationLatitude(null);
  689.         $entityManager->flush();
  690.         return $this->redirect($referer);
  691.     }
  692.     /**
  693.      * @Route("/show_attachment/{id}", name="business_contact_show_attachment")
  694.      */
  695.     public function showAttachmentBusinessContact(int $idBusinessContactsRepository $businessContactsRepository)
  696.     {
  697.         $business_contact $businessContactsRepository->find($id);
  698.         $filename $business_contact->getFiles();
  699.         $filepath $this->getParameter('business_contacts_attachments_directory') . "/" $filename;
  700.         if (file_exists($filepath)) {
  701.             $response = new BinaryFileResponse($filepath);
  702.             $response->setContentDisposition(
  703.                 ResponseHeaderBag::DISPOSITION_INLINE//use ResponseHeaderBag::DISPOSITION_ATTACHMENT to save as an attachment
  704.                 $filename
  705.             );
  706.             return $response;
  707.         } else {
  708.             return new Response("file does not exist");
  709.         }
  710.     }
  711.     /**
  712.      * Export a single BusinessContact vCard with photo + proper filename formatting
  713.      *
  714.      * @Route("/create_vcard_business_contact/{id}", name="create_vcard_business_contact", methods={"GET"})
  715.      */
  716.     public function createVCFBusinessContact(BusinessContacts $businessContactsBusinessContactVCFExport $exporter): Response
  717.     {
  718.         $type strtolower((string)$businessContacts->getBusinessOrPerson());
  719.         if ($type === 'business') {
  720.             $company trim((string)$businessContacts->getCompany());
  721.             $fileBaseName $company !== '' $company 'Business';
  722.         } else {
  723.             $first trim((string)$businessContacts->getFirstName());
  724.             $last trim((string)$businessContacts->getLastName());
  725.             $company trim((string)$businessContacts->getCompany());
  726.             $namePart trim($first '_' $last) ?: 'Contact';
  727.             $fileBaseName $company !== ''
  728.                 sprintf('%s (%s)'$namePart$company)
  729.                 : $namePart;
  730.         }
  731.         $safe = static fn(string $s): string => preg_replace('/[^A-Za-z0-9._() -]+/''_'$s) ?: 'Contact';
  732.         $downloadName $safe($fileBaseName) . '.vcf';
  733.         $exportDir rtrim($exporter->getExportDirectory(), "/\\");
  734.         if (!is_dir($exportDir) || !is_writable($exportDir)) {
  735.             return new Response("Export directory is not writable: $exportDir"Response::HTTP_INTERNAL_SERVER_ERROR);
  736.         }
  737.         $filePath $exportDir DIRECTORY_SEPARATOR $downloadName;
  738.         // Generate vCard with embedded photo via your service
  739.         try {
  740.             $exporter->generateVCard($businessContacts$filePath);
  741.         } catch (\Throwable $e) {
  742.             return new Response('Failed to generate vCard: ' $e->getMessage(), 500);
  743.         }
  744.         if (!is_file($filePath) || filesize($filePath) === 0) {
  745.             return new Response('vCard was not generated'500);
  746.         }
  747.         return $this->file($filePath$downloadNameResponseHeaderBag::DISPOSITION_ATTACHMENT);
  748.     }
  749. }