<?php
declare(strict_types=1);
namespace App\Controller\Buzz;
use App\Services\MetaTag;
use App\Services\Buzz\RechercheService;
use App\Services\Buzz\CanonicalUrlService;
use App\Repository\SecteursActiviteRepository;
use App\Repository\Buzz\BuzzActualitesRepository;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class RechercheController extends AbstractController
{
private MetaTag $metaTag;
private RechercheService $rechercheService;
private SecteursActiviteRepository $secteursActiviteRepository;
private BuzzActualitesRepository $buzzActualitesRepository;
private CanonicalUrlService $canonicalUrlService;
/**
*
* @param MetaTag $metaTag
* @param RechercheService $rechercheService
* @param SecteursActiviteRepository $secteursActiviteRepository
* @param BuzzActualitesRepository $buzzActualitesRepository
*/
public function __construct(
MetaTag $metaTag,
RechercheService $rechercheService,
SecteursActiviteRepository $secteursActiviteRepository,
BuzzActualitesRepository $buzzActualitesRepository,
CanonicalUrlService $canonicalUrlService
)
{
$this->metaTag = $metaTag;
$this->rechercheService = $rechercheService;
$this->secteursActiviteRepository = $secteursActiviteRepository;
$this->buzzActualitesRepository = $buzzActualitesRepository;
$this->canonicalUrlService = $canonicalUrlService;
}
/**
* Page de recherche avancée des actualités
*
* @param Request $request
* @param string $codePays
* @return Response
*/
public function avancee(Request $request, string $codePays): Response
{
$cleanCodePays = str_replace('_', '', $codePays);
$codePaysWithUnderScore = '_' . $cleanCodePays . '_';
$searchData = $this->rechercheService->getSearchData();
$defaultTypeAnnonce = $request->query->get('type_annonce');
if ($defaultTypeAnnonce === null || $defaultTypeAnnonce === '') {
$defaultTypeAnnonce = '0';
}
$request->query->set('type_annonce', $defaultTypeAnnonce);
$criteres = $this->rechercheService->extractSearchCriteria($request);
$isGroupedBySociete = $this->rechercheService->isGroupedBySociete($criteres);
if ($isGroupedBySociete) {
$hasExplicitTri = $request->query->has('tri') && $request->query->get('tri') !== '';
if (!$hasExplicitTri) {
$criteres['tri'] = '2';
}
} else {
if (!isset($criteres['tri']) || $criteres['tri'] === '' || $criteres['tri'] === null) {
$criteres['tri'] = '0';
}
}
$validTriValues = ['0', '1', '2', '3', '4', '5'];
if (!in_array($criteres['tri'], $validTriValues)) {
$criteres['tri'] = $this->rechercheService->isGroupedBySociete($criteres) ? '2' : '0';
}
$statusAuth = $this->rechercheService->getUserAuthStatus($this->getUser());
$page = max(1, (int)$request->query->get('page', 1));
$limit = 20;
$offset = ($page - 1) * $limit;
$rechercheValue = trim($request->query->get('recherche', ''));
$hasRecherche = false;
if (!empty($rechercheValue) && $rechercheValue !== '1') {
$hasRecherche = true;
} elseif (!empty($criteres['secteur']) && $criteres['secteur'] != '0') {
$hasRecherche = true;
} elseif (!empty($criteres['secteur_niv2']) && $criteres['secteur_niv2'] != '0') {
$hasRecherche = true;
} elseif (!empty($criteres['secteur_niv3']) && $criteres['secteur_niv3'] != '0') {
$hasRecherche = true;
} elseif (!empty($criteres['localisation']) && $criteres['localisation'] != '0') {
$hasRecherche = true;
} elseif (!empty($criteres['region'])) {
$hasRecherche = true;
} elseif (!empty($criteres['type_operation']) && $criteres['type_operation'] != '1') {
$hasRecherche = true;
} elseif (!empty($criteres['fonds']) && $criteres['fonds'] != '0') {
$hasRecherche = true;
} elseif (!empty($criteres['code_naf'])) {
$hasRecherche = true;
} elseif (!empty($criteres['afficher']) && $criteres['afficher'] != '0') {
$hasRecherche = true;
}
$nbTotal = 0;
$actualites = [];
if ($this->rechercheService->isGroupedBySociete($criteres)) {
$groupedLimit = min(5000, max($limit * 10, 500)); // Limite adaptative
$searchResult = $this->buzzActualitesRepository->searchAdvancedGroupedBySociete($criteres, $codePays, $statusAuth, $groupedLimit, 0);
$criteres['offset'] = $offset;
$criteres['limit'] = $limit;
$groupedData = $this->rechercheService->buildGroupedRowsForRecherche($searchResult['articles'], $criteres);
$actualites = $groupedData;
$nbTotal = $searchResult['total'];
} else {
if ($hasRecherche) {
$searchResult = $this->rechercheService->executeSearchWithDebug(
$criteres,
$codePays,
$statusAuth,
$limit,
$offset,
true
);
$actualites = $searchResult['actualites'];
$nbTotal = $searchResult['nbTotal'];
} else {
$emptyCriteres = [
'recherche_dans' => '1',
'date_filter' => '1',
'date_filter_value' => date('d-m-Y'),
'afficher' => '0',
'type_operation' => '1',
'fonds' => '0',
'secteur' => '0',
'localisation' => '0',
'tri' => $criteres['tri'] ?? '0',
'type_annonce' => $criteres['type_annonce'] ?? '0'
];
$searchResult = $this->rechercheService->executeSearchWithDebug(
$emptyCriteres,
$codePays,
$statusAuth,
$limit,
$offset,
true
);
$actualites = $searchResult['actualites'];
$nbTotal = $searchResult['nbTotal'];
}
}
$nbTotalArticles = $hasRecherche ? $nbTotal : $this->buzzActualitesRepository->countTotalActualites($codePays, $statusAuth);
$nbPages = (int) ceil($nbTotal / $limit);
$queryParams = array_merge($criteres, ['recherche' => $rechercheValue]);
$paginationLinks = $this->canonicalUrlService->getBuzzPaginationLinksWithQuery('buzz_recherche_avancee', [], null, $cleanCodePays, $page, $nbPages, $queryParams);
$titreParts = [];
$descParts = [];
$motCle = trim($criteres['recherche'] ?? $criteres['mot_cle'] ?? '');
if ($motCle !== '') {
$titreParts[] = $motCle;
}
if (!empty($criteres['secteur']) && isset($searchData['secteursActivite'][$criteres['secteur']])) {
$titreParts[] = $searchData['secteursActivite'][$criteres['secteur']];
}
if (!empty($criteres['localisation'])) {
$locLabel = $this->rechercheService->getTypeOperationLabelForTemplate(null);
$locMap = [
'33' => 'France',
'32' => 'Belgique',
'41' => 'Suisse',
'01' => 'Canada'
];
$titreParts[] = $locMap[$criteres['localisation']] ?? 'International';
}
if (!empty($criteres['type_operation']) && $criteres['type_operation'] !== '1') {
$typeLabel = $this->rechercheService->getTypeOperationLabelForTemplate(
['2' => 'fusion_acquisition', '3' => 'introduction_bourse', '4' => 'levee_fonds', '5' => 'liquidation', '6' => 'operation_boursiere', '7' => 'restructuration'][$criteres['type_operation']] ?? null
);
if ($typeLabel) {
$titreParts[] = $typeLabel;
}
}
if (!empty($criteres['code_naf'])) {
$titreParts[] = 'Code NAF: ' . $criteres['code_naf'];
}
if (!empty($criteres['fonds']) && $criteres['fonds'] !== '0') {
$titreParts[] = ($criteres['fonds'] === '1') ? 'avec fonds' : 'sans fonds';
}
$baseTitle = "Recherche avancée Buzz";
$baseDesc = "Résultats de recherche des actualités Fusions-Acquisitions et Capital Investissement";
$dynamicTitle = $baseTitle . (count($titreParts) ? ' – ' . implode(' · ', $titreParts) : '');
$dynamicDesc = $baseDesc . (count($titreParts) ? ' : ' . implode(' · ', $titreParts) : '') . '. Filtrez par secteur, localisation, type d’opération et fonds.';
$canonicalUrl = $this->canonicalUrlService->getBuzzSearchCanonical($cleanCodePays);
$metaTag = $this->metaTag
->setTitle($dynamicTitle)
->setDescription($dynamicDesc)
->setCanonical($canonicalUrl);
$hreflangLinks = $this->canonicalUrlService->getBuzzHreflangLinks('buzz_recherche_avancee', [], null, $cleanCodePays);
$searchResultsMessage = $this->rechercheService->getSearchResultsMessage($actualites, $nbTotal, $request->query->has('recherche'));
$authStatusMessage = $this->rechercheService->getAuthStatusMessage($statusAuth);
$triOptions = $this->rechercheService->getTriOptions();
$baseUrl = $this->generateUrl('buzz_recherche_avancee', ['codePays' => $codePaysWithUnderScore]);
$isExportAllowed = $this->rechercheService->isExportAllowed((int)$nbTotal);
$exportLimit = $this->rechercheService->getExportLimit();
$secteursNiv2 = [];
$secteursNiv3 = [];
if (!empty($criteres['secteur']) && $criteres['secteur'] != '0') {
try {
$secteursNiv2Raw = $this->secteursActiviteRepository
->getNomSecteurActivitieLevel2ByIdSecteurLevel1((int)$criteres['secteur']);
$secteursNiv2 = [0 => 'Tous'];
foreach ($secteursNiv2Raw as $secteur) {
$secteursNiv2[$secteur['idSecteurActivite']] = $secteur['nomSecteurActivite'];
}
if (!empty($criteres['secteur_niv2']) && $criteres['secteur_niv2'] != '0') {
$secteursNiv3Raw = $this->secteursActiviteRepository
->getNomSecteurActivitieLevel3ByIdSecteurLevel2((int)$criteres['secteur_niv2']);
$secteursNiv3 = [0 => 'Tous'];
foreach ($secteursNiv3Raw as $secteur) {
$secteursNiv3[$secteur['idSecteurActivite']] = $secteur['nomSecteurActivite'];
}
}
} catch (\Exception $e) {
$secteursNiv2 = [];
$secteursNiv3 = [];
}
}
return $this->render('buzz/recherche/avancee.html.twig', array_merge([
'metaTag' => $metaTag,
'lang' => 'fr',
'currentRoute' => $request->get('_route'),
'codePays' => $cleanCodePays,
'codePaysWithUnderScore' => $codePaysWithUnderScore,
'authFrom' => null,
'actualites' => $actualites,
'criteres' => $criteres,
'pays_principaux' => $this->rechercheService->getPaysPrincipaux(),
'tous_pays' => $this->rechercheService->getTousPays(),
'regions' => $this->rechercheService->getRegionsFrancaises(),
'nbTotal' => $nbTotal,
'nbTotalArticles' => $nbTotalArticles,
'statusAuth' => $statusAuth,
'page' => $page,
'limit' => $limit,
'nbPages' => $nbPages,
'hasRecherche' => $request->query->has('recherche'),
'searchResultsMessage' => $searchResultsMessage,
'authStatusMessage' => $authStatusMessage,
'triOptions' => $triOptions,
'isGroupedBySociete' => $this->rechercheService->isGroupedBySociete($criteres),
'baseUrl' => $baseUrl,
'isExportAllowed' => $isExportAllowed,
'exportLimit' => $exportLimit,
'secteursNiv2' => $secteursNiv2,
'secteursNiv3' => $secteursNiv3,
'hreflangLinks' => $hreflangLinks,
'paginationLinks' => $paginationLinks,
], $searchData));
}
/**
* Export Excel des résultats de recherche
*
* @param Request $request
* @param string $codePays
* @return Response
*/
public function export(Request $request, string $codePays): Response
{
$criteres = $this->rechercheService->extractSearchCriteria($request);
$cleanCodePays = str_replace('_', '', $codePays);
$statusAuth = $this->rechercheService->getUserAuthStatus($this->getUser());
$exportLimit = $this->rechercheService->getExportLimit();
$searchResult = $this->rechercheService->getDataForExport($criteres, $cleanCodePays, $statusAuth, $exportLimit);
$articles = $searchResult['articles'];
$isGroupedBySociete = isset($criteres['type_annonce']) && $criteres['type_annonce'] === '1';
if ($isGroupedBySociete) {
$filename = 'export_actualites_groupes_societe_' . (new \DateTimeImmutable())->format('Y-m-d_H-i-s') . '.xlsx';
$groupedRows = $this->rechercheService->getGroupedRowsForExportDisplayPipeline($criteres, $cleanCodePays, $statusAuth, 1000);
$selectedFieldsParam = trim((string)$request->query->get('export_fields', ''));
if ($selectedFieldsParam !== '') {
$selectedFields = array_values(array_filter(array_map('trim', explode(',', $selectedFieldsParam))));
if (!empty($selectedFields)) {
$headers = $this->rechercheService->getExportHeaders($selectedFields, true);
$exportData = $this->rechercheService->formatArticlesForExportGrouped($groupedRows, $selectedFields);
} else {
$headers = $this->getHeadersForGroupedMode();
$exportData = $this->prepareDataForGroupedMode($groupedRows);
}
} else {
$headers = $this->getHeadersForGroupedMode();
$exportData = $this->prepareDataForGroupedMode($groupedRows);
}
} else {
$filename = 'export_actualites_' . (new \DateTimeImmutable())->format('Y-m-d_H-i-s') . '.xlsx';
$selectedFieldsParam = trim((string)$request->query->get('export_fields', ''));
if ($selectedFieldsParam !== '') {
$selectedFields = array_values(array_filter(array_map('trim', explode(',', $selectedFieldsParam))));
if (!empty($selectedFields)) {
$headers = $this->rechercheService->getExportHeaders($selectedFields, false);
$basic = ['id_actualite','titre_actualite','raison_sociale'];
$onlyBasicRequested = empty(array_diff($selectedFields, $basic));
if ($onlyBasicRequested) {
$source = $this->rechercheService->getArticlesForExportDisplayPipeline($criteres, $cleanCodePays, $statusAuth, $exportLimit);
} else {
$source = $articles;
}
$exportData = $this->rechercheService->formatArticlesForExport($source, $selectedFields);
} else {
$headers = $this->getHeadersForNormalMode();
$exportData = $this->prepareDataForNormalMode($articles);
}
} else {
$headers = $this->getHeadersForNormalMode();
$exportData = $this->prepareDataForNormalMode($articles);
}
}
return $this->generateExcelResponse($exportData, $headers, $filename);
}
/**
* Génère la réponse Excel avec PhpSpreadsheet
*
* @param array $exportData
* @param array $headers
* @param string $filename
* @return Response
*/
private function generateExcelResponse(array $exportData, array $headers, string $filename): Response
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
foreach ($headers as $colIndex => $header) {
$colLetter = Coordinate::stringFromColumnIndex($colIndex + 1);
$sheet->setCellValue($colLetter . '1', $header);
$sheet->getStyle($colLetter . '1')->getFont()->setBold(true);
}
foreach ($exportData as $rowIndex => $row) {
foreach ($row as $colIndex => $value) {
$colLetter = Coordinate::stringFromColumnIndex($colIndex + 1);
$cellValue = is_array($value) ? implode(', ', $value) : (string)$value;
$sheet->setCellValue($colLetter . ($rowIndex + 2), $cellValue);
}
}
foreach (range(1, count($headers)) as $colIndex) {
$colLetter = Coordinate::stringFromColumnIndex($colIndex);
$sheet->getColumnDimension($colLetter)->setAutoSize(true);
}
$response = new StreamedResponse(function () use ($spreadsheet) {
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
});
$response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
$response->headers->set('Cache-Control', 'max-age=0');
return $response;
}
/**
* En-têtes pour le mode groupé par société
*
* @return array
*/
private function getHeadersForGroupedMode(): array
{
return [
'Acquéreur',
'Nombre d\'opération',
'Dernière opération',
'Date de dernière opération',
'SIREN de société',
'Code NAF',
'Siège',
'Code postal',
'Ville',
'Région',
'Pays',
'Téléphone',
'Site internet',
'Résumé de l\'activité',
'Année des éléments chiffrés',
'Chiffre d\'affaires (k euros)',
'Nombre de personnes',
'Nom de l\'expert',
'Adresse Expert',
'Code postal Expert',
'Contact 2',
'Ville Expert',
'Téléphone Expert',
'Contact 1',
'Lien fiche annuaire expert'
];
}
/**
* En-têtes pour le mode normal
*
* @return array
*/
private function getHeadersForNormalMode(): array
{
return [
'ID d\'actualité',
'Titre de l\'actualité',
'Nom de société',
'SIREN de société',
'Code NAF',
'Code postal',
'Région',
'Téléphone',
'Résumé de l\'activité',
'Chiffre d\'affaires (k euros)',
'Informations concernant l\'expert',
'Date opération',
'Type d\'opération',
'Type société',
'Siège',
'Ville',
'Pays',
'Site internet',
'Année des éléments chiffrés',
'Nombre de personnes'
];
}
/**
* Prépare les données pour le mode groupé
*
* @param array $articles
* @return array
*/
private function prepareDataForGroupedMode(array $articles): array
{
$exportData = [];
foreach ($articles as $article) {
$exportData[] = [
$article['raison_sociale'] ?? '',
$article['nb_operations'] ?? '',
$article['titre_actualite'] ?? '',
$article['date_derniere_operation'] ?? '',
$article['numero_siren'] ?? '',
$article['code_naf'] ?? '',
$article['adresse'] ?? '',
$article['code_postal'] ?? '',
$article['ville'] ?? '',
$article['id_region'] ?? '',
$article['id_pays'] ?? '',
$article['telephone'] ?? '',
$article['site_internet'] ?? '',
$article['resume_activite'] ?? '',
$article['annee_n'] ?? '',
$article['ca_n'] ?? '',
$article['nb_personnes_n'] ?? '',
$article['nom_expert'] ?? '',
$article['adresse_expert'] ?? '',
$article['code_postal_expert'] ?? '',
$article['contact2_expert'] ?? '',
$article['ville_expert'] ?? '',
$article['telephone_expert'] ?? '',
$article['contact1_expert'] ?? '',
$article['lien_expert'] ?? ''
];
}
return $exportData;
}
/**
* Prépare les données pour le mode normal
*
* @param array $articles
* @return array
*/
private function prepareDataForNormalMode(array $articles): array
{
$exportData = [];
foreach ($articles as $article) {
$exportData[] = [
$article['id_actualite'] ?? '',
$article['titre_actualite'] ?? '',
$article['raison_sociale'] ?? '',
$article['numero_siren'] ?? '',
$article['code_naf'] ?? '',
$article['code_postal'] ?? '',
$article['id_region'] ?? '',
$article['telephone'] ?? '',
$article['resume_activite'] ?? '',
$article['ca_n'] ?? '',
$article['nom_expert'] ?? '',
$article['date_operation'] ?? '',
$article['type_operation'] ?? '',
$article['nom_role'] ?? '',
$article['adresse'] ?? '',
$article['ville'] ?? '',
$article['id_pays'] ?? '',
$article['site_internet'] ?? '',
$article['annee_n'] ?? '',
$article['nb_personnes_n'] ?? ''
];
}
return $exportData;
}
/**
* Route AJAX pour récupérer la liste des pays
*
* @param Request $request
* @param string $codePays
* @return JsonResponse
*/
public function getListePays(Request $request, string $codePays): JsonResponse
{
$listePays = $this->rechercheService->getListePaysFormatted();
return new JsonResponse([
'success' => true,
'listePays' => $listePays
]);
}
}