src/Controller/BusinessContactsController.php line 48

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