<?php
declare(strict_types=1);
namespace Croisiland\CommonBundle\Helper\Cruise;
use Crea\MediaBundle\Entity\Enum\MediaFamilyCodeEnum;
use Crea\MediaBundle\Entity\MediaTagOwnerInterface;
use Crea\MediaBundle\Helper\MediaTagOwnerHelper;
use Croisiland\CommonBundle\Entity\Company\Atmosphere;
use Croisiland\CommonBundle\Entity\Cruise\Cruise;
use Croisiland\CommonBundle\Entity\Cruise\CruiseInterface;
use Croisiland\CommonBundle\Entity\Itinerary\DestinationMain;
use Croisiland\CommonBundle\Entity\Itinerary\Itinerary;
use Croisiland\CommonBundle\Entity\Itinerary\Stopover;
use Croisiland\CommonBundle\Entity\Offer\Offer;
use Croisiland\CommonBundle\Entity\Itinerary\ItinerarySegment;
use Croisiland\CommonBundle\Entity\Ship\Ship;
use Croisiland\CommonBundle\Enum\SegmentTypeEnum;
use Croisiland\CommonBundle\Helper\DomDocumentHelper;
use Croisiland\CommonBundle\Helper\Itinerary\ItineraryHelper;
use Croisiland\CommonBundle\Repository\Cruise\CruiseRepository;
use Croisiland\CommonBundle\Repository\Itinerary\ItinerarySegmentRepository;
use Croisiland\CommonBundle\RepositoryView\Offer\OfferCruiseViewRepository;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use function PHPUnit\Framework\matches;
class CruiseHelper
{
public function __construct(
private readonly CruiseRepository $cruiseRepository,
private readonly OfferCruiseViewRepository $offerCruiseRepository,
private readonly ItinerarySegmentRepository $itinerarySegmentRepository,
private readonly DomDocumentHelper $domDocumentHelper,
private readonly ItineraryHelper $itineraryHelper,
private readonly MediaTagOwnerHelper $mediaTagOwnerHelper,
)
{
}
public function getUpdatedCruiseName(Cruise $cruise): string
{
$newName = '';
$companyName = null;
if (null !== $cruise->getShip() && null !== $cruise->getShip()->getCompany()) {
$companyName = $cruise->getShip()->getCompany()->getName();
}
$itineraryName = null;
if (null !== $cruise->getItinerary()) {
$itineraryName = $cruise->getItinerary()->getName();
}
if (null !== $companyName && null !== $itineraryName) {
$newName = $companyName.' : '.$itineraryName;
if (strlen($newName) > 255) {
$newName = substr($newName, 0, 252).'...';
}
}
return $newName;
}
/**
* @param array<int, int> $cruises
*
* @return Cruise[]
*/
public function getCruisesFromIds(array $cruises, ?callable $callback = null): array
{
$resultSet = [];
foreach ($cruises as $cruiseId) {
$cruise = $this->cruiseRepository->find($cruiseId);
if ($cruise instanceof Cruise && (null === $callback || $callback($cruise))) {
$resultSet[] = $cruise;
}
}
return $resultSet;
}
/**
* Récupère un propriétaire de balise média (avatar) pour une croisière.
* L'avatar est sélectionné selon cet ordre de priorité :
* 1. Une escale aléatoire (en excluant le premier et dernier segment si plus de 2 segments).
* 2. Le navire de la croisière.
* 3. La destination de l'itinéraire.
*
* @param CruiseInterface $cruise La croisière pour laquelle récupérer l'avatar.
* @return MediaTagOwnerInterface|null Le propriétaire de balise média ou null si aucun candidat valide n'est trouvé.
*/
public function getCruiseAvatarOwner(CruiseInterface $cruise): ?MediaTagOwnerInterface
{
// Liste des candidats potentiels pour l'avatar, dans l'ordre de priorité.
$avatarCandidates = [
$this->getRandomStopover($cruise),
$cruise->getShip(),
$cruise->getItinerary()->getDestination(),
];
// Sélectionner le premier candidat ayant des médias associés.
foreach ($avatarCandidates as $avatarCandidate) {
/** @var MediaTagOwnerInterface $avatarCandidate */
$criteria = $this->getCruiseAvatarCriteria($avatarCandidate);
try {
if ($avatarCandidate !== null && $this->mediaTagOwnerHelper->hasMedia($avatarCandidate, MediaFamilyCodeEnum::FAM_IMAGE, $criteria)) {
return $avatarCandidate;
}
} catch (NoResultException|NonUniqueResultException $e) {
continue;
}
}
return null;
}
/**
* @return array<string|array<string>>
*/
private function getCruiseAvatarCriteria(?MediaTagOwnerInterface $mediaOwner): array
{
return match (true) {
$mediaOwner instanceof Stopover,
$mediaOwner instanceof Ship,
$mediaOwner instanceof DestinationMain => ['main'],
default => [],
};
}
/**
* Commun Bundle
*/
protected function getRandomStopover(CruiseInterface $cruise): ?Stopover
{
$segments = $this->itinerarySegmentRepository->getStopoverSegmentsForItinerary($cruise->getItinerary());
if (empty($segments)) {
return null;
}
// Filtrer les segments pour ignorer le premier et le dernier si plus de 2 segments.
if (count($segments) > 2) {
$segments = array_slice($segments, 1, -1);
}
shuffle($segments);
// Sélectionner un segment aléatoire.
foreach ($segments as $segment) {
if ($this->mediaTagOwnerHelper->hasMedia($segment->getStopover(), MediaFamilyCodeEnum::FAM_IMAGE, $this->getCruiseAvatarCriteria($segment->getStopover()))) {
return $segment->getStopover();
}
}
return null;
}
public function getCruiseDisplayName(Cruise $cruise): string
{
if ($cruise->getItinerary() instanceof Itinerary) {
$itineraryName = $this->itineraryHelper->getName($cruise->getItinerary());
if ($itineraryName !== null && $itineraryName !== '') {
return $itineraryName;
}
}
return $cruise->getShip()->getCompany()->getName() .':'. $cruise->getShip()->getName() . ' - ' . $cruise->getDepartureDate()->format('d/m/Y');
}
public function getCruiseSelectName(Cruise $cruise): string
{
return sprintf(
' [%s] %s • %s (%s)',
$cruise->getId(),
$cruise->getCode(),
$this->getCruiseDisplayName($cruise),
$cruise->getDepartureDate()->format('d/m/Y').' - '.$cruise->getArrivalDate()->format('d/m/Y'),
);
}
/**
* Retourne le nombre de croisières actives ou non pour une Offer donnée
* concernant les croisières dont la date de départ est dans le futur ou égale à aujourd'hui
*/
public function getNbCruisesByOffer(Offer $offer):int
{
$cruiseIds = $this->offerCruiseRepository->findAllCruiseIdsPerOffer($offer);
return count($cruiseIds);
}
/**
* Retourne le nombre de croisières actives pour une Offer donnée
* concernant les croisières dont la date de départ est dans le futur ou égale à aujourd'hui
*/
public function getNbActiveCruisesByOffer(Offer $offer): int
{
$cruises = $this->cruiseRepository->findUpComingCruiseByOffer($offer, true);
return count($cruises);
}
public function determineOfferOrigin(Offer $offer):?string
{
$results = $this->offerCruiseRepository->findResultsPerOffer($offer);
$counts = array_count_values(array_column($results, 'linkType'));
$nbDirect = $counts['direct'] ?? 0;
$nbViaPrice = $counts['via_price'] ?? 0;
if ($nbDirect > 0 && $nbViaPrice > 0) {
return 'mixed';
} elseif ($nbDirect > 0) {
return 'direct';
} elseif ($nbViaPrice > 0) {
return 'via_price';
}
return null;
}
public function getCruiseIncludeForAtmosphereArray(Cruise $cruise, ?Atmosphere $atmosphere = null): array
{
return $this->getCruiseItemsForAtmosphereArray($cruise, $atmosphere, 'included');
}
public function getCruiseExcludeForAtmosphereArray(Cruise $cruise, ?Atmosphere $atmosphere = null): array
{
return $this->getCruiseItemsForAtmosphereArray($cruise, $atmosphere, 'excluded');
}
private function getCruiseItemsForAtmosphereArray(Cruise $cruise, ?Atmosphere $atmosphere, string $propertyName): array
{
$method = 'get' . mb_convert_case($propertyName, MB_CASE_TITLE, 'UTF-8');
$cruiseItems = $this->domDocumentHelper->parseHtmlListToArray($cruise->$method());
if (!empty($cruiseItems)) {
return $cruiseItems;
}
$itineraryItems = $this->domDocumentHelper->parseHtmlListToArray($cruise->getItinerary()->$method());
if (!empty($itineraryItems)) {
return $itineraryItems;
}
$companyItems = $this->domDocumentHelper->parseHtmlListToArray($cruise->getShip()->getCompany()->$method());
if ($atmosphere === null) {
return $companyItems;
}
$atmosphereItems = $this->domDocumentHelper->parseHtmlListToArray($atmosphere->$method());
if (empty($atmosphereItems)) {
return $companyItems;
}
return array_unique(array_merge($companyItems, $atmosphereItems));
}
}