<?php
namespace App\Controller;
use DateTime;
use App\Entity\User;
use App\Entity\Image;
use Twig\Environment;
use App\Entity\Country;
use App\Entity\Magasin;
use App\Entity\Produit;
use App\Entity\Category;
use App\Data\SearchData;
use App\Form\SearchForm;
use App\Data\ShopContext;
use App\Form\ProductType;
use App\Service\PictureService;
use Doctrine\ORM\EntityRepository;
use App\Entity\MaintenanceSchedule;
use App\Repository\ProduitRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use App\Controller\ProduitVariantController;
use App\Repository\ProduitVariantRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Translation\Translator;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Form\FormError;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\MaintenanceScheduleRepository;
use Symfony\Component\Translation\LocaleSwitcher;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class ProduitController extends AbstractController
{
private CsrfTokenManagerInterface $csrfTokenManager;
private $params;
private $shopContext;
public function __construct(CsrfTokenManagerInterface $csrfTokenManager,
ShopContext $shopContext)
{
$this->csrfTokenManager = $csrfTokenManager;
$this->shopContext = $shopContext;
}
public function validateCsrfToken($token): bool
{
return $this->csrfTokenManager->isTokenValid(new CsrfToken('my-form-promo', $token));
}
/**
* Load shop_id => country_id map via raw SQL.
* DBAL 2.x / 3.x compatible.
*/
private function loadShopCountryMap(EntityManagerInterface $entityManager): array
{
$map = [];
try {
$conn = $entityManager->getConnection();
$rows = [];
if (method_exists($conn, 'fetchAllAssociative')) {
$rows = $conn->fetchAllAssociative('SELECT id, country_id FROM magasin');
} else {
$stmt = $conn->executeQuery('SELECT id, country_id FROM magasin');
if (method_exists($stmt, 'fetchAllAssociative')) {
$rows = $stmt->fetchAllAssociative();
} elseif (method_exists($stmt, 'fetchAll')) {
$rows = $stmt->fetchAll();
}
}
foreach ($rows as $row) {
$id = isset($row['id']) ? (int) $row['id'] : null;
$cid = isset($row['country_id']) && $row['country_id'] !== null ? (int) $row['country_id'] : null;
if ($id !== null) $map[$id] = $cid;
}
} catch (\Throwable $e) {
error_log('[Julico shopCountryMap ERROR] ' . $e->getMessage());
}
return $map;
}
/**
* Convert anything iterable (Paginator, array, IteratorAggregate) to a plain array.
* Critical because findSearch() returns a Doctrine Paginator, not a real array.
*/
private function toArray($iterable): array
{
if (is_array($iterable)) return $iterable;
$out = [];
try {
foreach ($iterable as $item) { $out[] = $item; }
} catch (\Throwable $e) {
error_log('[Julico toArray ERROR] ' . $e->getMessage());
}
return $out;
}
/**
* Shop IDs the given user is linked to (their vendor shops).
*/
private function vendorShopIds($user): array
{
$ids = [];
try {
if ($user && method_exists($user, 'getLinkedMagasins')) {
foreach ($user->getLinkedMagasins() as $m) {
if (is_object($m) && method_exists($m, 'getId')) {
$ids[] = (int) $m->getId();
}
}
}
} catch (\Throwable $e) {}
return $ids;
}
// A shop is "live" on the storefront when it is active AND its subscription has
// not expired (a null paid-until date means grandfathered = live). This mirrors
// the rule in ProduitRepository so listings, the shop subnav and the product
// page all agree on which shops are visible to the public.
private function isShopLive($shop): bool
{
try {
if (!is_object($shop) || !method_exists($shop, 'isActive')) { return false; }
if ($shop->isActive() !== true) { return false; }
if (method_exists($shop, 'getSubscriptionPaidUntil')) {
$until = $shop->getSubscriptionPaidUntil();
if ($until !== null && $until < new \DateTime('today')) { return false; }
}
return true;
} catch (\Throwable $e) {
return false;
}
}
#[Route('/locale/{_locale}', name: 'app_locale')]
public function setLocale(Request $request,SessionInterface $session
,TranslatorInterface $translator,LocaleSwitcher $localeSwitcher ):Response
{
$locale = $request->get('_locale');
$currentLocale = $localeSwitcher->getLocale();
if($locale !=null){
$localeSwitcher->setLocale($locale);
$translator->setLocale($locale);
$request->getSession()->set('_locale',$locale);
}
$sessionlocal= $request->getSession()->get('_locale');
return $this->redirectToRoute("app_home",['_locale'=>$sessionlocal]);
}
#[Route(path: ['/home/{shopName}', '/home'], name: 'app_home',
defaults: ['shopName' => null]
)]
public function index(ManagerRegistry $doctrine, Request $request
,?string $shopName
,Environment $twig,Security $security,SessionInterface $session
,TranslatorInterface $translator,LocaleSwitcher $localeSwitcher
,EntityManagerInterface $entityManager):Response
{
if ($request->getSession()->has('_locale')) {
$locale = $request->getSession()->get('_locale');
if($locale !=null){
$localeSwitcher->setLocale($locale);
$translator->setLocale($locale);
$request->getSession()->set('lang',$locale);
}
}
else {
$request->getSession()->set('_locale','en');
$request->getSession()->set('lang','en');
$locale = 'en';
if($locale !=null){
$localeSwitcher->setLocale($locale);
$translator->setLocale($locale);
}
}
$session->set('srv_msg',null);
$shops = [];
if ($shopName) {
$slugs = explode(',', $shopName);
$shops = $entityManager->getRepository(Magasin::class)->findBy(['nom' => $slugs]);
if (!$shops) {
throw $this->createNotFoundException('Aucune boutique trouvée');
}
$this->shopContext->setShops($shops);
} elseif ($request->query->get('shopName')) {
$shops = $entityManager->getRepository(Magasin::class)->findBy(['nom' => [$request->query->get('shopName')]]);
$this->shopContext->setShops($shops);
} else {
$this->shopContext->clear();
}
$lastActive= $doctrine->getRepository(MaintenanceSchedule::class)
->findLastActiveSchedule();
if ($lastActive) {
return $this->redirectToRoute("maintenance_index",['_locale'=>$locale,'lastActiveId'=>$lastActive->getId()]);
}
$data = new SearchData();
$form = $this->createForm(SearchForm::class, $data);
$form->handleRequest($request);
$data->categories = $request->query->all('categories');
$data->shops = $shops;
$data->promo = $request->query->get('promo');
if (!$form->isSubmitted()) {
$data->q = $request->query->get('q');
$data->min = $request->query->get('min');
$data->max = $request->query->get('max');
}
$offset = max(0, $request->query->getInt('offset', 0));
$findProduitsNouveaute = [];
$user = $security->getUser();
$isAdmin = 0;
if ($user) {
$roles = $user->getRoles();
if (in_array('ROLE_ADMIN', $roles, true)) {
$isAdmin = 1;
}
}
if($form->isSubmitted() && $form->isValid() ){
$produits = $doctrine->getRepository(Produit::class)->findSearch($data,$offset,$isAdmin);
$magasins = $doctrine->getRepository(Magasin::class)->findAll();
}else{
$category = $request->request->get('category', null);
if($category !=null){
$data = new SearchData();
$categories = [$category];
$data->categories = $categories;
}
else{
$findProduitsNouveaute = $doctrine->getRepository(Produit::class)
->findProduitsNouveaute($data,$offset,$isAdmin);
}
$produits = $doctrine->getRepository(Produit::class)->findSearch($data,$offset,$isAdmin);
$magasins = $doctrine->getRepository(Magasin::class)->findAll();
}
// Keep the shop list in step with the product filter: a non-admin must never
// see shops that are inactive or whose subscription has lapsed (findAll above
// returns every shop). Admins keep seeing all shops.
if (!$isAdmin) {
$magasins = array_values(array_filter(
$this->toArray($magasins),
fn($m) => $this->isShopLive($m)
));
}
$allCategories = $entityManager->getRepository(Category::class)
->findBy([], ['nom' => 'ASC']);
$activeFilters = 0;
if (!empty($data->q)) $activeFilters++;
if (!empty($data->categories)) $activeFilters++;
if (!empty($data->promo)) $activeFilters++;
if (!empty($data->min)) $activeFilters++;
if (!empty($data->max)) $activeFilters++;
// ══════════════════════════════════════════════════════════════
// Phase 3 Step 3 — Country lock
// ══════════════════════════════════════════════════════════════
$selectedCountryId = null;
$selectedCountry = null;
$needsCountryPicker = false;
$allCountries = [];
try {
$allCountries = $entityManager->getRepository(Country::class)
->findBy([], ['name' => 'ASC']);
} catch (\Throwable $e) {
error_log('[Julico countries-fetch index] ' . $e->getMessage());
$allCountries = [];
}
try {
$selectedCountryId = $request->getSession()->get('selected_country_id');
if ($selectedCountryId) {
$selectedCountry = $entityManager->getRepository(Country::class)
->find($selectedCountryId);
if (!$selectedCountry) {
$selectedCountryId = null;
$request->getSession()->remove('selected_country_id');
}
}
} catch (\Throwable $e) {
error_log('[Julico selected-country index] ' . $e->getMessage());
$selectedCountryId = null;
$selectedCountry = null;
}
if (!$isAdmin) {
try {
if (!$selectedCountryId && $user instanceof User) {
if (method_exists($user, 'getAddresses')) {
foreach ($user->getAddresses() as $addr) {
$isDef = method_exists($addr, 'isDefaultAddresse') ? $addr->isDefaultAddresse() : false;
$addrCountry = method_exists($addr, 'getCountry') ? $addr->getCountry() : null;
if ($isDef && $addrCountry) {
$selectedCountryId = $addrCountry->getId();
$selectedCountry = $addrCountry;
$request->getSession()->set('selected_country_id', $selectedCountryId);
break;
}
}
}
}
} catch (\Throwable $e) {
error_log('[Julico auto-derive index] ' . $e->getMessage());
}
if (!$selectedCountryId) {
$needsCountryPicker = true;
}
}
if (!$isAdmin && $selectedCountryId) {
$selCountryIdInt = (int) $selectedCountryId;
$shopCountryMap = $this->loadShopCountryMap($entityManager);
$produitsArr = $this->toArray($produits);
$nouveauteArr = $this->toArray($findProduitsNouveaute);
$magasinsArr = $this->toArray($magasins);
$matchByShopId = function($shopId) use ($selCountryIdInt, $shopCountryMap) {
if ($shopId === null) return false;
$cid = $shopCountryMap[(int) $shopId] ?? null;
return $cid !== null && $cid === $selCountryIdInt;
};
$shopIdOfProduct = function($p) {
try {
if (!is_object($p) || !method_exists($p, 'getMagasin')) return null;
$shop = $p->getMagasin();
if (!$shop || !method_exists($shop, 'getId')) return null;
return (int) $shop->getId();
} catch (\Throwable $e) {
return null;
}
};
try {
$produits = array_values(array_filter($produitsArr, function($p) use ($shopIdOfProduct, $matchByShopId) {
return $matchByShopId($shopIdOfProduct($p));
}));
$findProduitsNouveaute = array_values(array_filter($nouveauteArr, function($p) use ($shopIdOfProduct, $matchByShopId) {
return $matchByShopId($shopIdOfProduct($p));
}));
$magasins = array_values(array_filter($magasinsArr, function($m) use ($matchByShopId) {
try {
if (!is_object($m) || !method_exists($m, 'getId')) return false;
return $matchByShopId((int) $m->getId());
} catch (\Throwable $e) {
return false;
}
}));
} catch (\Throwable $e) {
error_log('[Julico FILTER ERROR index] ' . $e->getMessage());
$produits = [];
$findProduitsNouveaute = [];
$magasins = [];
}
}
return new Response($twig->render('produit/index.html.twig',[
'produits' => $produits,
'magasins' => $magasins,
'_locale' => $locale,
'produitsNouveaute' => $findProduitsNouveaute,
'form' => $form->createView(),
'previous' => $offset - ProduitRepository::PAGINATOR_PER_PAGE,
'next' => min(count($produits), $offset + ProduitRepository::PAGINATOR_PER_PAGE),
'allCategories' => $allCategories,
'activeFilters' => $activeFilters,
'currentQ' => $data->q ?? '',
'currentMin' => $data->min ?? '',
'currentMax' => $data->max ?? '',
'currentPromo' => $data->promo ?? '',
'currentCategories' => $data->categories ?? [],
'currentSort' => $request->query->get('sort', ''),
'needsCountryPicker' => $needsCountryPicker,
'selectedCountryId' => $selectedCountryId,
'selectedCountry' => $selectedCountry,
'allCountries' => $allCountries,
]));
}
#[Route('/produits/ajax-filter', name: 'ajax_product_filter', methods: ['POST'])]
public function ajaxFilterProducts(Request $request,
CsrfTokenManagerInterface $csrfTokenManager,
ProduitRepository $produitRepository,ManagerRegistry $doctrine,
Environment $twig,Security $security,SessionInterface $session,
TranslatorInterface $translator,LocaleSwitcher $localeSwitcher): JsonResponse
{
$csrfToken = $request->headers->get('X-CSRF-TOKEN');
if (!$this->validateCsrfToken($csrfToken)) {
return new JsonResponse(['error' => 'Invalid CSRF token'], 400);
}
$data = new SearchData();
$data->q = $request->request->get('q');
$data->categories = $request->request->get('categories');
$data->promo = $request->request->get('promo');
$data->min = $request->request->get('min');
$data->max = $request->request->get('max');
$data->dateproduction = $request->request->get('dateproduction');
$data->dateproduction = $request->request->get('dateexpiration');
$findProduitsPromo= [];
$user = $security->getUser();
$isAdmin = 0;
if ($user) {
$roles = $user->getRoles();
if (in_array('ROLE_ADMIN', $roles, true)) {
$isAdmin = 1;
}
}
$products = $produitRepository->findSearchPromo($data,$isAdmin);
$productData = [];
foreach ($products as $product) {
$productData[] = [
'id' => $product->getId(),
'name' => $product->getProductName(),
];
}
return new JsonResponse($productData);
}
#[Route('/produit/add', name: 'add_produit')]
#[Route('/produit/edit/{id}', name: 'edit_produit')]
public function new_edit(int $id =null ,Request $request,EntityManagerInterface $entityManager,Produit $produit = null,
PictureService $pictureService,ParameterBagInterface $params
,TranslatorInterface $translator,LocaleSwitcher $localeSwitcher ): Response
{
// ── Vendor access: logged in, and either an admin or linked to a shop ──
$user = $this->getUser();
if (!$user) {
return $this->redirectToRoute('app_login');
}
$isAdmin = $this->isGranted('ROLE_ADMIN');
$myShopIds = $this->vendorShopIds($user);
if (!$isAdmin && empty($myShopIds)) {
return $this->redirectToRoute('vendor_dashboard');
}
if ($id!=null){
$produit = $entityManager->getRepository(Produit::class)->find($id);
}
if (!$produit) {
$produit = new Produit();
$produitVariants = $produit->getProduitVariants();
$option = "add";
}
else{
$option = "edit";
if (!$isAdmin) {
$prodShopId = (method_exists($produit, 'getMagasin') && $produit->getMagasin())
? (int) $produit->getMagasin()->getId() : null;
if ($prodShopId === null || !in_array($prodShopId, $myShopIds, true)) {
throw $this->createAccessDeniedException('You can only edit products in your own shop.');
}
}
}
$form = $this->createForm(ProductType::class, $produit);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$produit = $form->getData();
// ── Defense in depth: a vendor can only save to one of their own shops ──
if (!$isAdmin) {
$chosenShop = method_exists($produit, 'getMagasin') ? $produit->getMagasin() : null;
$chosenShopId = ($chosenShop && method_exists($chosenShop, 'getId')) ? (int) $chosenShop->getId() : null;
if ($chosenShopId === null || !in_array($chosenShopId, $myShopIds, true)) {
throw $this->createAccessDeniedException('You can only assign products to your own shop.');
}
}
$images = $form->get('images')->getData();
foreach($images as $image){
$folder = 'products';
$fichier = $pictureService->add($image, $folder, 300, 300);
$img = new Image();
$img->setFileName($fichier);
$pictureService = new PictureService($params);
$img->setImageUrl($params->get('images_directory') . $folder);
$produit->addImage($img);
}
$produit->setUser($this->getUser());
// ── Variants + their own images ──
// Iterate the SUB-FORMS so we can read each variant's unmapped "images"
// file field. Skip completely empty rows; coalesce blank price/stock to 0.
foreach ($form->get('produitVariants') as $variantForm) {
$produitVariant = $variantForm->getData();
if (!$produitVariant) { continue; }
$hasLabel = trim((string) $produitVariant->getOptionLabel()) !== ''
|| trim((string) $produitVariant->getOptionLabel2()) !== ''
|| trim((string) $produitVariant->getOptionLabel3()) !== '';
$rawPrix = $produitVariant->getPrix();
$rawQtt = $produitVariant->getQtt();
$hasPrix = $rawPrix !== null && $rawPrix !== '';
$hasQtt = $rawQtt !== null && $rawQtt !== '';
// Ignore rows the vendor left totally empty
if (!$hasLabel && !$hasPrix && !$hasQtt) {
continue;
}
// Keep NOT NULL columns happy if a box was left blank
if (!$hasPrix) { $produitVariant->setPrix('0'); }
if (!$hasQtt) { $produitVariant->setQtt('0'); }
$produitVariant->setProduit($produit);
$produit->addProduitVariant($produitVariant);
$variantImages = $variantForm->has('images') ? $variantForm->get('images')->getData() : [];
if (!empty($variantImages)) {
foreach ($variantImages as $vfile) {
if (!$vfile) { continue; }
$folder = 'products';
$fichier = $pictureService->add($vfile, $folder, 300, 300);
$vimg = new Image();
$vimg->setFileName($fichier);
$vimg->setImageUrl($params->get('images_directory') . $folder);
$vimg->setProduit($produit); // Image.produit is required
$vimg->setProduitVariant($produitVariant); // link photo to this variant
$produit->addImage($vimg);
}
}
}
// ── Auto-fill product Price / Quantity from the variants when left blank ──
$variants = $produit->getProduitVariants();
$hasVariants = count($variants) > 0;
$priceEmpty = ($produit->getPrix() === null || $produit->getPrix() === '');
$qtyEmpty = ($produit->getQtt() === null);
if ($hasVariants) {
if ($priceEmpty) {
$minPrice = null;
foreach ($variants as $v) {
$vp = $v->getPrix();
if ($vp !== null && $vp !== '' && (float) $vp > 0) {
$vpf = (float) $vp;
if ($minPrice === null || $vpf < $minPrice) { $minPrice = $vpf; }
}
}
$produit->setPrix($minPrice !== null ? (string) $minPrice : '0');
}
if ($qtyEmpty) {
$sumQty = 0;
foreach ($variants as $v) {
$vq = $v->getQtt();
if ($vq !== null && $vq !== '') { $sumQty += (int) $vq; }
}
$produit->setQtt($sumQty);
}
} else {
// Simple product (no variants): Price & Quantity are required.
if ($priceEmpty || $qtyEmpty) {
if ($priceEmpty) {
$form->get('prix')->addError(new FormError('Please enter a Price (or add variants and leave this blank to auto-fill it).'));
}
if ($qtyEmpty) {
$form->get('qtt')->addError(new FormError('Please enter a Quantity (or add variants and leave this blank to auto-fill it).'));
}
return $this->render('produit/add.html.twig', [
'formAddProduit' => $form->createView(),
'option' => $option,
]);
}
}
$date = new DateTime();
$produit->setPublishDate( $date);
try{
$entityManager->persist($produit);
}
catch (\Exception $e) {
$this->addFlash('error', 'An error occurred while saving the entity.');
}
$entityManager->flush();
if ($option === 'edit') {
return $this->redirectToRoute($option . '_produit', [
'id' => $produit->getId(),
]);
} else {
return $this->redirectToRoute($option.'_produit');
}
}
if ($request->query->has('_locale')) {
$locale = $request->getSession()->get('_locale');
if($locale !=null){
$localeSwitcher->setLocale($locale);
$translator->setLocale($locale);
}
}
return $this->render('produit/add.html.twig', [
'formAddProduit' => $form->createView(),
'option' => $option
]);
}
#[Route('/produit/{id}', name: 'show_produit')]
public function show( string $id, EntityManagerInterface $entityManager,
TranslatorInterface $translator, LocaleSwitcher $localeSwitcher,
Request $request, Security $security ): Response
{
if ($id != null) {
$produit = $entityManager->getRepository(Produit::class)->find($id);
}
if (!$produit) {
throw $this->createNotFoundException('Product not found');
}
// ── Block the public product page for a shop that isn't live (inactive OR
// subscription expired). Clients are sent home; an admin or the shop's own
// manager may still preview it. Same rule as the listings (isShopLive). ──
$viewer = $security->getUser();
$viewerIsAdmin = $viewer && in_array('ROLE_ADMIN', $viewer->getRoles(), true);
$pShop = method_exists($produit, 'getMagasin') ? $produit->getMagasin() : null;
$shopLive = $this->isShopLive($pShop);
if (!$shopLive) {
$allowed = $viewerIsAdmin;
if (!$allowed && $viewer instanceof User && method_exists($viewer, 'getLinkedMagasins')) {
foreach ($viewer->getLinkedMagasins() as $om) {
if ($om && $pShop && method_exists($om, 'getId') && (int) $om->getId() === (int) $pShop->getId()) {
$allowed = true;
break;
}
}
}
if (!$allowed) {
return $this->redirectToRoute('app_home');
}
}
$locale = $request->getSession()->get('_locale');
if ($locale != null) {
$localeSwitcher->setLocale($locale);
$translator->setLocale($locale);
}
$groupedVariantssize = [];
foreach ($produit->getProduitVariants() as $variant) {
$paramSize = $variant->getParamSize();
if ($paramSize !== null) {
$size = $paramSize->getSizeAbreviation();
if ($size !== null) {
$groupedVariantssize[$size][] = $variant;
}
}
}
$groupedVariantscolor = [];
foreach ($produit->getProduitVariants() as $variant) {
$paramColor = $variant->getParamColor();
if ($paramColor !== null) {
$color = $paramColor->getColorName();
if ($color !== null) {
$groupedVariantscolor[$color][] = $variant;
}
}
}
$variantsArray = [];
foreach ($produit->getProduitVariants() as $variant) {
if (($variant !== null && $variant->getParamColor() !== null) ) {
if ($variant->getParamColor() !== null) {
$variantsArray[$variant->getParamColor()->getColorName()] = [];
}
}
if ($variant !== null && $variant->getParamSize() !== null) {
$variantsArray[$variant->getParamColor()->getColorName()][] = $variant->getParamSize()->getSizeAbreviation();
}
}
$user = $security->getUser();
$isAdmin = false;
if ($user) {
$roles = $user->getRoles();
$isAdmin = in_array('ROLE_ADMIN', $roles, true);
}
$selectedCountryId = null;
$selectedCountry = null;
$needsCountryPicker = false;
$allCountries = [];
try {
$allCountries = $entityManager->getRepository(Country::class)
->findBy([], ['name' => 'ASC']);
} catch (\Throwable $e) {
error_log('[Julico countries-fetch show] ' . $e->getMessage());
$allCountries = [];
}
try {
$selectedCountryId = $request->getSession()->get('selected_country_id');
if ($selectedCountryId) {
$selectedCountry = $entityManager->getRepository(Country::class)
->find($selectedCountryId);
if (!$selectedCountry) {
$selectedCountryId = null;
$request->getSession()->remove('selected_country_id');
}
}
} catch (\Throwable $e) {
error_log('[Julico selected-country show] ' . $e->getMessage());
$selectedCountryId = null;
$selectedCountry = null;
}
if (!$isAdmin) {
try {
if (!$selectedCountryId && $user instanceof User) {
if (method_exists($user, 'getAddresses')) {
foreach ($user->getAddresses() as $addr) {
$isDef = method_exists($addr, 'isDefaultAddresse') ? $addr->isDefaultAddresse() : false;
$addrCountry = method_exists($addr, 'getCountry') ? $addr->getCountry() : null;
if ($isDef && $addrCountry) {
$selectedCountryId = $addrCountry->getId();
$selectedCountry = $addrCountry;
$request->getSession()->set('selected_country_id', $selectedCountryId);
break;
}
}
}
}
if (!$selectedCountryId) {
$needsCountryPicker = true;
}
} catch (\Throwable $e) {
error_log('[Julico auto-derive show] ' . $e->getMessage());
if (!$selectedCountryId) {
$needsCountryPicker = true;
}
}
if ($selectedCountryId) {
try {
$shop = method_exists($produit, 'getMagasin') ? $produit->getMagasin() : null;
if ($shop && method_exists($shop, 'getId')) {
$shopId = (int) $shop->getId();
$shopCountryMap = $this->loadShopCountryMap($entityManager);
$shopCountryId = $shopCountryMap[$shopId] ?? null;
if ($shopCountryId !== null && $shopCountryId !== ((int) $selectedCountryId)) {
return $this->redirectToRoute('app_home');
}
}
} catch (\Throwable $e) {
error_log('[Julico country-block show] ' . $e->getMessage());
}
}
}
$shop = method_exists($produit, 'getMagasin') ? $produit->getMagasin() : null;
$deliveryInfo = [
'address_set' => false,
'has_region' => false,
'region_name' => null,
'can_deliver' => null,
'fee' => null,
'shop_name' => $shop ? $shop->getNom() : null,
'shop_id' => $shop ? $shop->getId() : null,
];
try {
if ($user instanceof User && $shop) {
$defaultAddress = null;
foreach ($user->getAddresses() as $addr) {
if (method_exists($addr, 'isDefaultAddresse') && $addr->isDefaultAddresse()) {
$defaultAddress = $addr;
break;
}
}
if (!$defaultAddress && $user->getAddresses()->count() > 0) {
$defaultAddress = $user->getAddresses()->first();
}
if ($defaultAddress) {
$deliveryInfo['address_set'] = true;
$region = method_exists($defaultAddress, 'getRegion') ? $defaultAddress->getRegion() : null;
if ($region) {
$deliveryInfo['has_region'] = true;
$deliveryInfo['region_name'] = $region->getName();
if (method_exists($shop, 'getDeliveryFeeForRegion')) {
$fee = $shop->getDeliveryFeeForRegion($region);
if ($fee !== null) {
$deliveryInfo['can_deliver'] = (bool) $fee->canDeliver();
$deliveryInfo['fee'] = $fee->getFeeAsFloat();
}
}
}
}
}
} catch (\Throwable $e) {
error_log('[Julico Phase 3 delivery-info] ' . $e->getMessage());
}
return $this->render('produit/show.html.twig', [
'produit' => $produit,
'groupedVariantscolor' => $groupedVariantscolor,
'groupedVariantssize' => $groupedVariantssize,
'variants' => $variantsArray,
'deliveryInfo' => $deliveryInfo,
'needsCountryPicker' => $needsCountryPicker,
'selectedCountryId' => $selectedCountryId,
'selectedCountry' => $selectedCountry,
'allCountries' => $allCountries,
]);
}
}