<?php
/**
* User: remmel
* Date: 1/4/16
* Time: 12:45 AM
*/
namespace App\Service;
use App\Controller\Frontend\SeoOd;
use App\Controller\Frontend\Thumbnail;
use App\Entity\Block;
use App\Entity\model\Conveyance;
use App\Entity\model\RouteSeo;
use App\Entity\model\StatPriceDuration;
use App\Entity\PictureStop;
use App\Entity\Price;
use App\Entity\Stop;
use App\Entity\StopLang;
use App\Extension\LocaleListener;
use App\Repository\BlockRepository;
use App\Repository\PictureStopRepository;
use App\Repository\PriceRepository;
use App\Repository\StationRepository;
use App\Repository\StopLangRepository;
use App\Repository\StopRepository;
use App\Service\Client\ClientRepository;
use App\Utils\StringUtils;
use App\Utils\Utils;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Added as service in services.yml (also can be set as service in Route(service="app.base_controller") in next Symfony version)
*/
class OdSeoService
{
const TRANS_IDS = [
'train' => ['trip.1', 'trip.2', 'trip.3', 'trip.4', 'trip.5', 'trip.6', 'trip.reverse', 'trip.list', 'trip.list.address', 'trip.list.companies', 'trip.list.schedule', 'address.dep.one', 'address.dep.multi', 'address.arr.one', 'address.arr.multi', 'schedule.1', 'schedule.2', 'a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2', 'e.1', 'e.2', 'f.1', 'f.2', 'g.1', 'g.2', 'h.1','h.2'],
'bus' => ['trip.1', 'trip.2', 'trip.3', 'trip.4', 'trip.5', 'trip.6', 'trip.reverse', 'trip.list', 'trip.list.address', 'trip.list.companies', 'trip.list.schedule', 'address.dep.multi', 'address.dep.one', 'address.arr.multi', 'address.arr.one', 'schedule.1', 'schedule.2', 'a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2']
];
private UrlRewrite $urlRewriteService;
private LabelBuilder $labelBuilder;
private TranslatorInterface $translator;
private CurrencyService $currencyService;
private ClientRepository $clientRepo;
protected EntityManagerInterface $em;
protected string $projectDir;
protected StationRepository $stationRepository;
// https://docs.google.com/spreadsheets/d/148-fwmN3X-mHVoAs2crSos-B2Vf6UcwPxkuK0bCqZa8/edit#gid=1807861344
protected array $seoroutes;
public function __construct(string $seoRoutesFilename, CurrencyService $currencyService, UrlRewrite $urlRewriteService, LabelBuilder $labelBuilder, TranslatorInterface $translator, ClientRepository $clientRepo, EntityManagerInterface $em, string $projectDir, StationRepository $stationRepository) {
$this->currencyService = $currencyService;
$this->urlRewriteService = $urlRewriteService;
$this->labelBuilder = $labelBuilder;
$this->translator = $translator;
$this->clientRepo = $clientRepo;
$this->em = $em;
$this->projectDir = $projectDir;
$this->stationRepository = $stationRepository;
$apcu = new ApcuAdapter(); //FIXME used inject one, when cache usage properly configured
$this->seoroutes = $apcu->get('seoroutes', function (ItemInterface $item) use ($seoRoutesFilename) {
return Utils::file_get_contents_csv_header($seoRoutesFilename);
});
}
private function getPriceRepo(): PriceRepository {
return $this->em->getRepository(Price::class);
}
private function getStopLangRepo(): StopLangRepository {
return $this->em->getRepository(StopLang::class);
}
private function getBlockRepo(): BlockRepository {
return $this->em->getRepository(Block::class);
}
private function getStopRepo(): StopRepository {
return $this->em->getRepository(Stop::class);
}
private function getPrictureStopRepo(): PictureStopRepository {
return $this->em->getRepository(PictureStop::class);
}
private function isFrenchRoute(Stop $depStop, Stop $arrStop): bool {
return ($depStop->getCountry() === 'FR' && $arrStop->getCountry() === 'FR');
}
private function getThumbnailOD(Stop $depStop, Stop $arrStop, string $conveyance): ?Thumbnail {
$relativePath = '/bundles/static/uploads/Trajets/';
$absolutePath = $this->projectDir.'/public'.$relativePath; //getcwd doesn't work in test
$img = $this->labelBuilder->translatesThumbnail($depStop->getName(), $arrStop->getName(), $conveyance);
if(file_exists($absolutePath.$img->filename)) {
$img->src = $relativePath.$img->filename;
return $img;
} else {
return null;
}
}
/**
* Content displayed on the indexed page
* 3 cases :
* - not indexed (has date)
* - indexed but no stats
* - has stats
* TODO makes calculation for other currency
* TODO extract that in a seoService
*/
public function getSeoOd(Stop $depStop, Stop $arrStop, string $transport, bool $noIndex, string $_locale): SeoOd {
$seoOd = new SeoOd();
//TODO call that method only if index?
if(!$noIndex && $transport !== Conveyance::ALL) {
$seoOd->currency = $currency = $this->labelBuilder->getTransCurrency();
//1) stat & cheapest
$seoOd->stat = $this->getStat($depStop, $arrStop, $transport, $currency);
if($seoOd->stat) {
$seoOd->cheapest = round($seoOd->stat->getPriceMin() / 100);
}
//2) prices
if($transport === Conveyance::TRAIN || $transport === Conveyance::BUS) {
$seoOd->prices = $this->getPriceRepo()->findLastPriceByStops($depStop->getId(), $arrStop->getId(), $transport);
}
foreach ($seoOd->prices as $p) {
if (empty($p->getCompanyName())) {
$companyName = $this->clientRepo->find($p->getCompanyId())->getName();
$p->setCompanyName($companyName);
$this->currencyService
->updateCurrency($p, $currency);
}
}
//3) img
$seoOd->img = $this->getThumbnailOD($depStop, $arrStop, $transport);
//block
$seoOd->block = $this->getRouteBlock($depStop, $arrStop, $_locale, $transport);
//return link in automated text
if($this->isFrenchRoute($arrStop, $depStop)) {
$seoOd->returnLink = $this->urlRewriteService->createRouteLink($arrStop, $depStop, false, $transport);
}
// stations addresses
$seoOd->depAddresses = $this->stationRepository->find($transport, $depStop->getId());
$seoOd->arrAddresses = $this->stationRepository->find($transport, $arrStop->getId());
//no automated text for flight and carpooling
if ($transport === Conveyance::TRAIN || $transport === Conveyance::BUS) {
$seoOd->transIds = $this->labelBuilder->generatesTransIds($depStop->getId() + $arrStop->getId() * 5, self::TRANS_IDS[$transport], 3);
}
//FIXME quickfix to avoid calling costing findCarrier on everypage (only index and cached page)
$connection = $transport === Conveyance::FLIGHT ? 0 : false;
$carriersName = $this->getPriceRepo()->findCarriers($depStop->getId(), $arrStop->getId(), $connection, $transport);
$seoOd->companiesName = $this->labelBuilder->createCompanyName($carriersName, $transport);
// $seoOd->alternateConvenyanceRoutes = $this->getAlternateRoutesFromCsv($depStop, $arrStop, $transport, $_locale);
}
//add companies automated text
//could directly do that in view, but we need to know before if we have carries to display
if (count($seoOd->transIds)) { //only if automated text
$ids = self::TRANS_IDS[$transport];
$lastKey = end($ids);
$lastLetter = explode('.', $lastKey)[0];
foreach (range('a', $lastLetter) as $c) {
$companyName = $this->translator->trans("$transport.odpage.spinning.$c.name");
if (in_array($companyName, $seoOd->companiesName)) { //could ignore cases
$seoOd->companiesLetter[] = $c;
}
}
}
$country = $this->labelBuilder->getTransCountry();
$seoOd->distanceMi = round($depStop->distance($arrStop, 'N'));
$seoOd->distanceKm = round($depStop->distance($arrStop));
if(in_array($country, ['GB', 'US']))
$seoOd->distanceNice = $seoOd->distanceMi.' mi';
else
$seoOd->distanceNice = $seoOd->distanceKm.' km';
$seoOd->transParams = $this->createsTransODParams($depStop, $arrStop, $seoOd, $_locale);
return $seoOd;
}
public function createsTransODParams(Stop $depStop, Stop $arrStop, SeoOd $seoOd, string $locale): array {
$params = [
'%A%' => $depStop->getName(),
'%B%' => $arrStop->getName(),
'%KM%' => round($seoOd->distanceKm),
'%MI%' => round($seoOd->distanceMi),
'%DISTANCE%' => $seoOd->distanceNice,
'%COMPANIES%' => $this->labelBuilder->joinTexts($seoOd->companiesName),
];
if($seoOd->stat) {
$params = array_merge($params, [
'%PRICE%' => CurrencyService::nicePrice($seoOd->stat->getPriceMin(), $seoOd->currency, $locale),
'%PRICE_MIN%' => CurrencyService::nicePrice($seoOd->stat->getPriceMin(), $seoOd->currency, $locale),
'%PRICE_AVG%' => CurrencyService::nicePrice($seoOd->stat->getPriceAvg(), $seoOd->currency, $locale),
'%PRICE_MAX%' => CurrencyService::nicePrice($seoOd->stat->getPriceMax(), $seoOd->currency, $locale),
'%EUR%' => round($seoOd->stat->getPriceMin() / 100),
'%DUR_AVG%' => $this->labelBuilder->niceDuration($seoOd->stat->getDurationAvg()),
'%DUR_MIN%' => $this->labelBuilder->niceduration($seoOd->stat->getDurationMin()),
'%DUR_MAX%' => $this->labelBuilder->niceduration($seoOd->stat->getDurationMax()),
]);
}
return $params;
}
protected function getStat(Stop $depStop, Stop $arrStop, string $transport, string $currency): ?StatPriceDuration {
$stat = $this->getStatDaysOld($depStop, $arrStop, $transport, $currency, false);
if(!$stat)
$stat = $this->getStatDaysOld($depStop, $arrStop, $transport, $currency, true);
return $stat;
}
protected function getStatDaysOld(Stop $depStop, Stop $arrStop, string $transport, string $currency, bool $lookfurther): ?StatPriceDuration {
$createdDaysOld = $lookfurther ? 30 : 7;
// get direct trip only when flight AND not lookfurther. I guess this can be simplifed, that not really what we want now
$connection = $transport == Conveyance::FLIGHT && !$lookfurther ? 0 : false;
$currenciesUsed = $this->getPriceRepo()
->findRouteCurrency($depStop->getId(), $arrStop->getId(), $transport, $createdDaysOld);
$currenciesWithRates = [
$currency => 1
];
foreach ($currenciesUsed as $c)
$currenciesWithRates[$c] = $this->currencyService->getRate($c, $currency);
return $this->getPriceRepo()
->findRouteStat($depStop->getId(), $arrStop->getId(), $transport, $currenciesWithRates, $createdDaysOld, $connection);
}
protected function getRouteBlock(Stop $dep, Stop $arr, string $_locale, string $conveyance): Block {
$path = '';
$ids = $dep->getId().'-'.$arr->getId();
switch ($conveyance) {
case Conveyance::BUS:
$path = "/$_locale/bus-o-d-$ids"; break;
case Conveyance::TRAIN:
$path = "/$_locale/train/o-d-$ids"; break;
case Conveyance::FLIGHT:
$path = "/$_locale/avion/o-d-$ids"; break;
case Conveyance::CARPOOLING:
$path = "/$_locale/covoiturage/o-d-$ids"; break;
}
$b = $this->getBlockRepo()->findOnebyPathNotNull($path);
if($b->getContent())
$b->setContent(str_replace('%MAP%', self::createMap($dep, $arr), $b->getContent()));
return $b;
}
/** @return RouteSeo[] */
public function getPopularRoutesFromCsv(Stop $dep, string $transportation, string $_locale) :array{
$routes = [];
foreach ($this->seoroutes as $r) {
if($r->locale === $_locale && $r->type === $transportation && (int)$r->dep_stop_id === $dep->getId()) {
$arr = $this->getStopRepo()->findNotRemovedTranslated($r->arr_stop_id, LocaleListener::getLang($_locale));
$routes[] = RouteSeo::createFromStops($dep, $arr);
}
}
return $routes;
}
// /**
// * @return Link[]
// */
// public function getAlternateRoutesFromCsv(Stop $dep, Stop $arr, string $transportation, string $_locale) :array{
// $routes = [];
// foreach ($this->seoroutes as $r) {
// if($r->locale === $_locale && $r->type !== $transportation && (int)$r->dep_stop_id === $dep->getId() && (int)$r->arr_stop_id === $arr->getId()) {
// $routes[] = RouteSeo::createFromStops($dep, $arr);
// }
// }
// return $this->urlRewriteService->createRoutesLink($routes);
// }
public function getInfoStop(string $stopId, string $transport, string $_locale): array {
$block = $this->getBlockRepo()->findOnebyPathNotNull("/$_locale/$transport-o-d/destination/$stopId");
$pictures = $this->getPrictureStopRepo()
->findByStop($stopId);
return ['block' => $block, 'imgs' => $pictures];
}
/**
* Create OD map
*/
public static function createMap(Stop $depStop, Stop $arrStop) :string{
$depGeo = $depStop->getLonLatComma();
$arrGeo = $arrStop->getLonLatComma();
return 'https://static-maps.yandex.ru/1.x/?lang=en_US&l=map&pt='.
"$depGeo,pm2lbm1" .
'~'.
"$arrGeo,pm2lbm2" .
'&size=300,300';
}
}