src/Renderer/VacancyRenderer.php line 148

Open in your IDE?
  1. <?php
  2. namespace App\Renderer;
  3. use App\Component\Attributes\AttributeManager;
  4. use App\Component\Attributes\AttributeManagerFactory;
  5. use App\Component\Configuration\Util\Config;
  6. use App\Component\FeedbackCompany\Manager\FeedbackCompanyManager;
  7. use App\Decorator\VacancyDecorator;
  8. use App\Decorator\VacancyOptionDecorator;
  9. use App\Entity\ApplicationProcessItemCollection;
  10. use App\Entity\Asset;
  11. use App\Entity\CompanyAttribute;
  12. use App\Entity\Gallery;
  13. use App\Entity\Option;
  14. use App\Entity\Recruiter;
  15. use App\Entity\RecruiterAttribute;
  16. use App\Entity\Route as RouteEntity;
  17. use App\Entity\Testimonial;
  18. use App\Entity\Vacancy;
  19. use App\Entity\VacancyAttribute;
  20. use App\Entity\VacancyDescriptionForm;
  21. use App\EventListener\FeatureFlagListener;
  22. use App\Form\Setting\VacancySettingType;
  23. use App\Manager\EventManager;
  24. use App\Manager\GoogleForJobsManager;
  25. use App\Manager\TagManager;
  26. use App\Model\Tealium\Base;
  27. use App\Model\Vacancy\VacancyWrapper;
  28. use App\Service\UtmService;
  29. use App\Service\VacancyService;
  30. use App\Templating\Decorator;
  31. use App\Util\AssetUtil;
  32. use App\Util\VacancyFallback;
  33. use App\Util\VacancyImage;
  34. use App\Util\VacancyUspValueUtil;
  35. use App\Util\VacancyUtil;
  36. use DateTime;
  37. use Doctrine\ORM\EntityManagerInterface;
  38. use Flagception\Manager\FeatureManagerInterface;
  39. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  40. use Symfony\Component\HttpFoundation\RedirectResponse;
  41. use Symfony\Component\HttpFoundation\Request;
  42. use Symfony\Component\HttpFoundation\RequestStack;
  43. use Symfony\Component\HttpFoundation\Response;
  44. use Symfony\Component\Routing\RouterInterface;
  45. use Twig\Environment;
  46. use Twig\Error\LoaderError;
  47. use Twig\Error\RuntimeError;
  48. use Twig\Error\SyntaxError;
  49. class VacancyRenderer
  50. {
  51.     public const RELATED_VACANCIES_LIMIT 4;
  52.     protected ParameterBagInterface $parameterBag;
  53.     protected VacancyDecorator $vacancyDecorator;
  54.     protected VacancyOptionDecorator $vacancyOptionDecorator;
  55.     protected FeatureManagerInterface $featureManager;
  56.     protected VacancyService $vacancyService;
  57.     protected TagManager $tagManager;
  58.     protected VacancyImage $vacancyImage;
  59.     protected Environment $twig;
  60.     protected Decorator $templateDecorator;
  61.     protected Page $pageRenderer;
  62.     protected RouterInterface $router;
  63.     protected EntityManagerInterface $entityManager;
  64.     protected RequestStack $requestStack;
  65.     protected EventManager $eventManager;
  66.     protected AttributeManager $vacancyAttributeManager;
  67.     protected AttributeManager $recruiterAttributeManager;
  68.     private Config $config;
  69.     private GoogleForJobsManager $googleForJobsManager;
  70.     private AssetUtil $assetUtil;
  71.     protected AttributeManager $companyAttributeManager;
  72.     private FeedbackCompanyManager $feedbackCompanyManager;
  73.     /**
  74.      * VacancyRenderer constructor.
  75.      */
  76.     public function __construct(
  77.         ParameterBagInterface $parameterBag,
  78.         VacancyDecorator $vacancyDecorator,
  79.         VacancyOptionDecorator $vacancyOptionDecorator,
  80.         FeatureManagerInterface $featureManager,
  81.         VacancyService $vacancyService,
  82.         TagManager $tagManager,
  83.         VacancyImage $vacancyImage,
  84.         Environment $twig,
  85.         Decorator $templateDecorator,
  86.         Page $pageRenderer,
  87.         RouterInterface $router,
  88.         EntityManagerInterface $entityManager,
  89.         RequestStack $requestStack,
  90.         GoogleForJobsManager $googleForJobsManager,
  91.         Config $config,
  92.         AssetUtil $assetUtil,
  93.         FeedbackCompanyManager $feedbackCompanyManager,
  94.         EventManager $eventManager,
  95.         AttributeManagerFactory $attributeManagerFactory,
  96.         private readonly VacancyFallback $vacancyFallback,
  97.     ) {
  98.         $this->parameterBag $parameterBag;
  99.         $this->vacancyDecorator $vacancyDecorator;
  100.         $this->vacancyOptionDecorator $vacancyOptionDecorator;
  101.         $this->featureManager $featureManager;
  102.         $this->vacancyService $vacancyService;
  103.         $this->tagManager $tagManager;
  104.         $this->vacancyImage $vacancyImage;
  105.         $this->twig $twig;
  106.         $this->templateDecorator $templateDecorator;
  107.         $this->pageRenderer $pageRenderer;
  108.         $this->router $router;
  109.         $this->entityManager $entityManager;
  110.         $this->requestStack $requestStack;
  111.         $this->googleForJobsManager $googleForJobsManager;
  112.         $this->config $config;
  113.         $this->assetUtil $assetUtil;
  114.         $this->feedbackCompanyManager $feedbackCompanyManager;
  115.         $this->eventManager $eventManager;
  116.         $this->vacancyAttributeManager $attributeManagerFactory->create(VacancyAttribute::class);
  117.         $this->companyAttributeManager $attributeManagerFactory->create(CompanyAttribute::class);
  118.         $this->recruiterAttributeManager $attributeManagerFactory->create(RecruiterAttribute::class);
  119.     }
  120.     /**
  121.      * @throws LoaderError
  122.      * @throws RuntimeError
  123.      * @throws SyntaxError
  124.      */
  125.     public function renderDetail(
  126.         Request $request,
  127.         VacancyWrapper $vacancyWrapper,
  128.         string $template '@default/pages/vacancy_detail.html.twig'
  129.     ): Response {
  130.         $vacancy $vacancyWrapper->vacancy;
  131.         // TODO: before enabling, do not make multiple requests to same resource
  132.         $overviewImage $vacancyWrapper->vacancyImages->overviewImage;
  133.         $companyLogo $vacancyWrapper->vacancyImages->companyLogo;
  134.         if ((!$vacancy || $vacancy->isDeleted()) && $redirect $this->redirectVacancyByStrategy($vacancy)) {
  135.             return $redirect;
  136.         }
  137.         $relatedVacancies = [];
  138.         $relatedOverview null;
  139.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_VACANCY_RELATED_VACANCIES)) {
  140.             $relatedVacancies $this->vacancyService->getRelatedVacancies($request$vacancyself::RELATED_VACANCIES_LIMIT);
  141.             $relatedOverview $this->vacancyService->getRelatedOverviewMeta($vacancy);
  142.         }
  143.         $vacancyArray = [$vacancy];
  144.         $this->vacancyDecorator->decorate($relatedVacancies);
  145.         $this->vacancyDecorator->decorate($vacancyArraytrue);
  146.         $this->vacancyOptionDecorator->decorate($vacancyArray);
  147.         $this->vacancyOptionDecorator->decorate($relatedVacancies);
  148.         /** @var Vacancy $vacancy */
  149.         $vacancy reset($vacancyArray);
  150.         // detach entity because of mutation
  151.         $this->entityManager->detach($vacancy);
  152.         if (!$vacancy->getRecruiter()) {
  153.             $recruiterStrategy $this->config->get('site_vacancy_detail_recruiter_strategy');
  154.             switch ($recruiterStrategy) {
  155.                 case VacancySettingType::VACANCY_DETAIL_RECRUITER_STRATEGY_OPTION:
  156.                     $recruiterOption $this->config->get('site_vacancy_detail_recruiter_option');
  157.                     if ($recruiterOption) {
  158.                         $recruiterOptionOptionValues VacancyUtil::getOptionValuesByOptionId($vacancy$recruiterOption->getId());
  159.                         $recruiterOptionOptionValue $recruiterOptionOptionValues->first();
  160.                         if ($recruiterOptionOptionValue) {
  161.                             $vacancy->setRecruiter($recruiterOptionOptionValue->getRecruiter());
  162.                         }
  163.                     }
  164.                     break;
  165.                 case VacancySettingType::VACANCY_DETAIL_RECRUITER_STRATEGY_FALLBACK:
  166.                     $recruiterFallback $this->config->get('site_vacancy_detail_recruiter_fallback');
  167.                     if ($recruiterFallback) {
  168.                         $recruiter $this->entityManager->getRepository(Recruiter::class)
  169.                             ->find($recruiterFallback->getId());
  170.                         $vacancy->setRecruiter($recruiter);
  171.                     }
  172.                     break;
  173.             }
  174.         }
  175.         $recruiter $vacancy->getRecruiter();
  176.         if ($recruiter instanceof Recruiter) {
  177.             $recruiter->setAttributes($this->recruiterAttributeManager->getNormalizedValues($recruiter));
  178.             $vacancy->setRecruiter($recruiter);
  179.         }
  180.         $this->calculateGallery($vacancy);
  181.         $ogImage $this->vacancyService->getOgImage($vacancy);
  182.         $optionsForTemplate = [];
  183.         foreach ($vacancy->getOptionValues() as $optionValue) {
  184.             if (empty($optionsForTemplate[$optionValue->getOption()->getInternalName()])) {
  185.                 $optionsForTemplate[$optionValue->getOption()->getInternalName()] = [];
  186.             }
  187.             $optionsForTemplate[$optionValue->getOption()->getInternalName()][] = $optionValue;
  188.         }
  189.         $relatedTestimonials = [];
  190.         if ($vacancy->getTags()) {
  191.             $relatedTestimonials $this->tagManager->getEntitiesByRelatedTags($vacancyTestimonial::class);
  192.         }
  193.         if ($vacancy->getCompany() && $vacancy->getCompany()->getDescription() && $description json_decode($vacancy->getCompany()->getDescription(), true)) {
  194.             $vacancy->getCompany()->setDescription(implode(''$description));
  195.         }
  196.         if (VacancySettingType::VACANCY_DETAIL_ADDRESS_SIDEBLOCK_SOURCE_COMPANY === $this->config->get('site_vacancy_detail_address_sideblock_source')) {
  197.             $vacancy $this->vacancyService->setCompanyAddress($vacancy);
  198.         }
  199.         $utm UtmService::getUtmArrayFromRequest($this->requestStack->getMainRequest());
  200.         $applyUrl $this->router->generate('vacancy_apply'array_merge(['id' => $vacancy->getId()], $utm));
  201.         $applicationProcess null;
  202.         if (
  203.             $this->featureManager->isActive(FeatureFlagListener::FEATURE_APPLICATION_PROCESS) &&
  204.             !$applicationProcess $vacancy->getApplicationProcess()
  205.         ) {
  206.             $applicationProcess $this->entityManager
  207.                 ->getRepository(ApplicationProcessItemCollection::class)
  208.                 ->findOneBy(['default' => true]);
  209.         }
  210.         $vacancy->options $optionsForTemplate;
  211.         $route $this->entityManager->getRepository(RouteEntity::class)
  212.             ->findOneBy(['name' => $request->get('_route')])
  213.         ;
  214.         $options = [];
  215.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_TEALIUM)) {
  216.             $options['tealium_element'] = Base::createForVacancy($vacancy$this->config);
  217.         }
  218.         $overviewImages array_map(function (Asset $asset) {
  219.             return $this->assetUtil->getAssetUrlsForFilters($asset);
  220.         }, $this->vacancyImage->getOverviewImages($vacancy));
  221.         $heroImages array_map(function (Asset $asset) {
  222.             return $this->assetUtil->getAssetUrlsForFilters($asset);
  223.         }, $this->vacancyImage->getHeroImages($vacancy));
  224.         if ($vacancy->getCompany()) {
  225.             $vacancy->getCompany()->setAttributes(
  226.                 $this->companyAttributeManager->getNormalizedValues($vacancy->getCompany())
  227.             );
  228.         }
  229.         $vacancy->setAttributes($this->vacancyAttributeManager->getNormalizedValues($vacancy));
  230.         $reviews = [];
  231.         if (
  232.             $this->featureManager->isActive(FeatureFlagListener::FEATURE_FEEDBACK_COMPANY) &&
  233.             $vacancy->getCompany()
  234.         ) {
  235.             $reviews $this->feedbackCompanyManager->getReviewsForCompany($vacancy->getCompany());
  236.         }
  237.         /** @var Option|null $vacancyUspStrategyOption */
  238.         $vacancyUspStrategyOption $this->config->get('site_vacancy_usp_strategy_option');
  239.         $vacancy->setCalendar($this->vacancyFallback->getCalendar($vacancy));
  240.         return $this->pageRenderer
  241.             ->withEntity($vacancy)
  242.             ->renderPage(
  243.                 '{vacancy}',
  244.                 $template,
  245.                 [
  246.                     'vacancy' => $vacancy,
  247.                     'apiOverviewImage' => $overviewImage,
  248.                     'apiCompanyLogo' => $companyLogo,
  249.                     'apiDetailImage' => $vacancyWrapper->vacancyImages->detailImage,
  250.                     'apiCompanyHero' => $vacancyWrapper->vacancyImages->companyHeroImage,
  251.                     'jobPostingStructuredData' => $this->googleForJobsManager->generateStructureData($vacancy),
  252.                     'detailImage' => $this->vacancyImage->getDetailImage($vacancy),
  253.                     'overviewImage' => $this->vacancyImage->getOverviewImage($vacancy),
  254.                     'overviewImages' => $overviewImages,
  255.                     'heroImage' => $this->vacancyImage->getHeroImage($vacancy),
  256.                     'heroImages' => $heroImages,
  257.                     'relatedVacancies' => $relatedVacancies,
  258.                     'relatedOverview' => $relatedOverview,
  259.                     'vacancyUsps' => $vacancy->getUsps()->getValues(),
  260.                     'vacancyUspValues' => VacancyUspValueUtil::getForVacancyDetail($vacancy$vacancyUspStrategyOption),
  261.                     'relatedTestimonials' => $relatedTestimonials,
  262.                     'applyUrl' => $applyUrl,
  263.                     'applicationProcess' => $applicationProcess,
  264.                     'e_commerce_script' => $this->vacancyService->renderECommerceScript($vacancy),
  265.                     'head_end_script' => $this->vacancyService->renderHeadEndScriptForVacancyDetail(),
  266.                     'body_end_script' => $this->vacancyService->renderBodyEndScriptForVacancyDetail($vacancy),
  267.                     'company_reviews' => $reviews,
  268.                     'description' => $this->getVacancyDescription($vacancy),
  269.                     'upcoming_event' => $this->featureManager->isActive(FeatureFlagListener::FEATURE_EVENTS) ?
  270.                         $this->eventManager->getUpcomingEvent() : null,
  271.                 ],
  272.                 [
  273.                     $this->vacancyService->getVacancyCanonicalUrl($vacancy),
  274.                 ],
  275.                 [
  276.                     'metaTitle' => $vacancy->getMetaTitle(),
  277.                     'metaDescription' => empty($vacancy->getMetaDescription()) ? $vacancy->getIntro() : $vacancy->getMetaDescription(),
  278.                     'ogImage' => $ogImage,
  279.                 ],
  280.                 $vacancy,
  281.                 $route,
  282.                 $vacancyWrapper->vacancyImages->heroImage->hasUrlForFilter('original') ?
  283.                     $vacancyWrapper->vacancyImages->heroImage->getUrlForFilter('original') :
  284.                     $this->vacancyImage->getHeroImage($vacancy),
  285.                 $options
  286.             );
  287.     }
  288.     private function redirectVacancyByStrategy(?Vacancy $vacancy): ?Response
  289.     {
  290.         $deletedStrategy $this->config->get('site_vacancy_deleted_vacancy_strategy');
  291.         $expirationPeriod $this->config->get('site_vacancy_deleted_vacancy_expiration_period');
  292.         $defaultRedirect '404_page';
  293.         $preferedRedirect $this->config->get('site_vacancy_deleted_vacancy_redirect') ?: $defaultRedirect;
  294.         if (VacancySettingType::DELETED_STRATEGY_THROW_404 === $deletedStrategy) {
  295.             return new RedirectResponse(
  296.                 $this->router->generate($defaultRedirect)
  297.             );
  298.         }
  299.         if (
  300.             null !== $vacancy &&
  301.             null !== $expirationPeriod &&
  302.             $expirationPeriod < (new DateTime())->diff($vacancy->getDeleted())->days
  303.         ) {
  304.             return new RedirectResponse(
  305.                 $this->router->generate($preferedRedirect)
  306.             );
  307.         }
  308.         return null;
  309.     }
  310.     private function getVacancyDescription(Vacancy $vacancy): array
  311.     {
  312.         $form $this->entityManager->getRepository(VacancyDescriptionForm::class)->findOneBy([]);
  313.         if (!($form instanceof VacancyDescriptionForm)) {
  314.             return [];
  315.         }
  316.         $fields json_decode($form->getFields(), true);
  317.         $namedFields array_column($fields'name''label');
  318.         $vacancyDescription json_decode($vacancy->getDescription(), true);
  319.         $description = [];
  320.         foreach ($namedFields as $key => $value) {
  321.             if (empty($body $vacancyDescription[$value] ?? '')) {
  322.                 continue;
  323.             }
  324.             $description[$value] = [
  325.                 'title' => $key,
  326.                 'body' => $body,
  327.             ];
  328.         }
  329.         return $description;
  330.     }
  331.     public function calculateGallery(Vacancy $vacancy): void
  332.     {
  333.         if (!$vacancy->getGallery()) {
  334.             $galleryStrategy $this->config->get('site_vacancy_detail_gallery_strategy');
  335.             switch ($galleryStrategy) {
  336.                 case VacancySettingType::VACANCY_DETAIL_GALLERY_STRATEGY_NONE:
  337.                 default:
  338.                     break;
  339.                 case VacancySettingType::VACANCY_DETAIL_GALLERY_STRATEGY_COMPANY:
  340.                     $company $vacancy->getCompany();
  341.                     if ($company) {
  342.                         $vacancy->setGallery($company->getGallery());
  343.                     }
  344.                     break;
  345.                 case VacancySettingType::VACANCY_DETAIL_GALLERY_STRATEGY_OPTION:
  346.                     $galleryOption $this->config->get('site_vacancy_detail_gallery_option');
  347.                     if ($galleryOption) {
  348.                         $galleryOptionOptionValues VacancyUtil::getOptionValuesByOptionId($vacancy$galleryOption->getId());
  349.                         $galleryOptionOptionValue $galleryOptionOptionValues->first();
  350.                         if ($galleryOptionOptionValue) {
  351.                             $vacancy->setGallery($galleryOptionOptionValue->getGallery());
  352.                         }
  353.                     }
  354.                     break;
  355.                 case VacancySettingType::VACANCY_DETAIL_GALLERY_STRATEGY_FALLBACK:
  356.                     $galleryFallback $this->config->get('site_vacancy_detail_gallery_fallback');
  357.                     if ($galleryFallback) {
  358.                         $gallery $this->entityManager->getRepository(Gallery::class)->find($galleryFallback->getId());
  359.                         $vacancy->setGallery($gallery);
  360.                     }
  361.                     break;
  362.             }
  363.         }
  364.     }
  365. }