src/Service/VacancyService.php line 686

Open in your IDE?
  1. <?php
  2. namespace App\Service;
  3. use App\Client\GoogleAnalyticsClient;
  4. use App\Client\MultiSiteClient;
  5. use App\Component\Attributes\AttributeManager;
  6. use App\Component\Attributes\AttributeManagerFactory;
  7. use App\Component\Configuration\Util\Config;
  8. use App\Decorator\VacancyDecorator;
  9. use App\Entity\Asset;
  10. use App\Entity\Company;
  11. use App\Entity\ObjectMultiMedia;
  12. use App\Entity\OgImage;
  13. use App\Entity\Option;
  14. use App\Entity\OptionValue;
  15. use App\Entity\OptionValueAttribute;
  16. use App\Entity\Repository\VacancyRepository;
  17. use App\Entity\SeoSnippet;
  18. use App\Entity\Site;
  19. use App\Entity\Tag;
  20. use App\Entity\User;
  21. use App\Entity\Vacancy;
  22. use App\Entity\VacancyAttribute;
  23. use App\Entity\VacancyDescriptionForm;
  24. use App\Entity\VacancyUspValue;
  25. use App\EventListener\FeatureFlagListener;
  26. use App\FilterSet\FilterSetInterface;
  27. use App\FilterSet\VacancyFilterSet;
  28. use App\Form\Setting\VacancySettingType;
  29. use App\Manager\FilterManager;
  30. use App\Manager\MultiMediaManager;
  31. use App\Manager\OptionManager;
  32. use App\Manager\VacancyDomainManager;
  33. use App\Model\Vacancy\QueryContext;
  34. use App\Model\Vacancy\SearchResultSet;
  35. use App\Model\Vacancy\VacancyDomainCollection;
  36. use App\Model\Vacancy\VacancyWrapper;
  37. use App\Reader\Vacancy\AbstractVacancyReader;
  38. use App\Reader\Vacancy\DoctrineReader;
  39. use App\Reader\Vacancy\GoogleCloudTalentSolutionReader;
  40. use App\Reader\Vacancy\MultilingualDoctrineReader;
  41. use App\Reader\Vacancy\MultiSiteReader;
  42. use App\Reader\Vacancy\VacancyReaderCollection;
  43. use App\Templating\Decorator;
  44. use App\Translation\TranslationUtil;
  45. use App\Util\ArrayUtil;
  46. use App\Util\AssetUtil;
  47. use App\Util\Seo;
  48. use App\Util\VacancyImage;
  49. use DateTime;
  50. use Doctrine\ORM\EntityManagerInterface;
  51. use Doctrine\ORM\NonUniqueResultException;
  52. use Exception;
  53. use Flagception\Manager\FeatureManagerInterface;
  54. use Gedmo\Translatable\TranslatableListener;
  55. use GuzzleHttp\Client;
  56. use JMS\Serializer\Serializer;
  57. use JMS\Serializer\SerializerInterface;
  58. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  59. use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
  60. use Symfony\Component\Form\FormFactoryInterface;
  61. use Symfony\Component\Form\FormInterface;
  62. use Symfony\Component\HttpFoundation\Request;
  63. use Symfony\Component\HttpFoundation\RequestStack;
  64. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  65. use Symfony\Component\Routing\RouterInterface;
  66. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  67. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  68. use Twig\Environment as Twig;
  69. use Twig\Error\LoaderError;
  70. use Twig\Error\RuntimeError;
  71. use Twig\Error\SyntaxError;
  72. class VacancyService
  73. {
  74.     protected $imageSizes = [
  75.         'w360xh420',
  76.         'w240',
  77.         'w240_parent',
  78.         'original_parent',
  79.         'w480',
  80.         'w580',
  81.         'original',
  82.         'square_100x100_crop',
  83.         'w480xh240',
  84.         'w992xh550',
  85.         'scale_100x100',
  86.         'w330xh150',
  87.         'w767xh575',
  88.         'w480_thumbnail',
  89.         'w768xh432_crop',
  90.     ];
  91.     protected AttributeManager $optionValueAttributeManager;
  92.     /**
  93.      * @var FormFactoryInterface
  94.      */
  95.     private $formBuilder;
  96.     /**
  97.      * @var EntityManagerInterface
  98.      */
  99.     private $entityManager;
  100.     /**
  101.      * @var array
  102.      */
  103.     private $possibleSortOptions;
  104.     /**
  105.      * @var ParameterBagInterface
  106.      */
  107.     private $parameterBag;
  108.     /**
  109.      * @var Twig
  110.      */
  111.     private $twig;
  112.     /**
  113.      * @var Decorator
  114.      */
  115.     private $decorator;
  116.     /**
  117.      * @var UrlGeneratorInterface
  118.      */
  119.     private $urlGenerator;
  120.     /**
  121.      * @var TranslatableListener|null
  122.      */
  123.     private $translatableListener;
  124.     /**
  125.      * @var VacancyDecorator
  126.      */
  127.     private $vacancyDecorator;
  128.     /**
  129.      * @var RouterInterface
  130.      */
  131.     private $router;
  132.     /**
  133.      * @var Serializer
  134.      */
  135.     private $serializer;
  136.     /**
  137.      * @var SiteService
  138.      */
  139.     private $siteService;
  140.     /**
  141.      * @var Client
  142.      */
  143.     private $client;
  144.     /**
  145.      * @var FeatureManagerInterface
  146.      */
  147.     private $featureManager;
  148.     /**
  149.      * @var Request|null
  150.      */
  151.     private $request;
  152.     /**
  153.      * @var CreditService
  154.      */
  155.     private $creditService;
  156.     /**
  157.      * @var Seo
  158.      */
  159.     private $seo;
  160.     /**
  161.      * @var VacancyImage
  162.      */
  163.     private $vacancyImage;
  164.     /**
  165.      * @var AssetUtil
  166.      */
  167.     private $assetUtil;
  168.     /**
  169.      * @var VacancyReaderCollection
  170.      */
  171.     private $readerCollection;
  172.     /**
  173.      * @var Site[]|null
  174.      */
  175.     private $sites;
  176.     /**
  177.      * @var AuthorizationCheckerInterface
  178.      */
  179.     private $authorizationChecker;
  180.     /**
  181.      * @var TokenStorageInterface
  182.      */
  183.     private $tokenStorage;
  184.     /**
  185.      * @var TranslationUtil
  186.      */
  187.     private $translationUtil;
  188.     private OptionManager $optionManager;
  189.     private FilterManager $filterManager;
  190.     private VacancyDomainManager $vacancyDomainManager;
  191.     private VacancyFilterSet $filterSet;
  192.     /**
  193.      * @var EntityManagerInterface
  194.      */
  195.     private $parentEntityManager;
  196.     protected MultiMediaManager $multiMediaManager;
  197.     protected GoogleAnalyticsClient $analyticsClient;
  198.     protected GoogleAnalyticsService $googleAnalyticsService;
  199.     private Config $config;
  200.     private AttributeManager $attributeManager;
  201.     /**
  202.      * Service constructor.
  203.      */
  204.     public function __construct(
  205.         FormFactoryInterface $formFactory,
  206.         EntityManagerInterface $entityManager,
  207.         ParameterBagInterface $parameterBag,
  208.         Twig $twig,
  209.         Decorator $decorator,
  210.         UrlGeneratorInterface $urlGenerator,
  211.         VacancyDecorator $vacancyDecorator,
  212.         RouterInterface $router,
  213.         SerializerInterface $serializer,
  214.         SiteService $siteService,
  215.         MultiSiteClient $multiSiteClient,
  216.         FeatureManagerInterface $featureManager,
  217.         RequestStack $requestStack,
  218.         CreditService $creditService,
  219.         Seo $seo,
  220.         VacancyImage $vacancyImage,
  221.         AssetUtil $assetUtil,
  222.         VacancyReaderCollection $readerCollection,
  223.         AuthorizationCheckerInterface $authorizationChecker,
  224.         TokenStorageInterface $tokenStorage,
  225.         TranslationUtil $translationUtil,
  226.         OptionManager $optionManager,
  227.         FilterManager $filterManager,
  228.         VacancyDomainManager $vacancyDomainManager,
  229.         ?TranslatableListener $translatableListener,
  230.         VacancyFilterSet $filterSet,
  231.         EntityManagerInterface $parentEntityManager,
  232.         MultiMediaManager $multiMediaManager,
  233.         GoogleAnalyticsClient $analyticsClient,
  234.         GoogleAnalyticsService $googleAnalyticsService,
  235.         Config $config,
  236.         AttributeManagerFactory $attributeManagerFactory
  237.     ) {
  238.         $this->formBuilder $formFactory;
  239.         $this->entityManager $entityManager;
  240.         $this->possibleSortOptions $parameterBag->get('vacancy.sorting_options');
  241.         $this->parameterBag $parameterBag;
  242.         $this->twig $twig;
  243.         $this->decorator $decorator;
  244.         $this->urlGenerator $urlGenerator;
  245.         $this->vacancyDecorator $vacancyDecorator;
  246.         $this->router $router;
  247.         $this->serializer $serializer;
  248.         $this->siteService $siteService;
  249.         $this->client $multiSiteClient;
  250.         $this->featureManager $featureManager;
  251.         $this->request $requestStack->getCurrentRequest();
  252.         $this->creditService $creditService;
  253.         $this->seo $seo;
  254.         $this->vacancyImage $vacancyImage;
  255.         $this->assetUtil $assetUtil;
  256.         $this->readerCollection $readerCollection;
  257.         $this->authorizationChecker $authorizationChecker;
  258.         $this->tokenStorage $tokenStorage;
  259.         $this->translatableListener $translatableListener;
  260.         $this->translationUtil $translationUtil;
  261.         $this->optionManager $optionManager;
  262.         $this->filterManager $filterManager;
  263.         $this->vacancyDomainManager $vacancyDomainManager;
  264.         $this->filterSet $filterSet;
  265.         $this->parentEntityManager $parentEntityManager;
  266.         $this->multiMediaManager $multiMediaManager;
  267.         $this->analyticsClient $analyticsClient;
  268.         $this->googleAnalyticsService $googleAnalyticsService;
  269.         $this->config $config;
  270.         $this->attributeManager $attributeManagerFactory->create(VacancyAttribute::class);
  271.         $this->optionValueAttributeManager $attributeManagerFactory->create(OptionValueAttribute::class);
  272.     }
  273.     public function getSortingForm(): FormInterface
  274.     {
  275.         $siteSortOptions $this->possibleSortOptions;
  276.         return $this->formBuilder->createBuilder()
  277.             ->add('sortBy'ChoiceType::class, [
  278.                 'choices' => array_flip($siteSortOptions),
  279.                 'label' => 'Sorteren op',
  280.             ])
  281.             ->getForm();
  282.     }
  283.     public function processOptionValueIndex(int $optionValueIdint $newIndex)
  284.     {
  285.         $optionValue $this->entityManager->getRepository(OptionValue::class)->find($optionValueId);
  286.         $optionValue->setPosition($newIndex);
  287.         $this->entityManager->persist($optionValue);
  288.         $this->entityManager->flush();
  289.     }
  290.     public function processVacancyUspValueIndex(int $vacancyUspValueIdint $newIndex)
  291.     {
  292.         $vacancyUspValue $this->entityManager->getRepository(VacancyUspValue::class)->find($vacancyUspValueId);
  293.         $vacancyUspValue->setPosition($newIndex);
  294.         $this->entityManager->persist($vacancyUspValue);
  295.         $this->entityManager->flush();
  296.     }
  297.     public function getFacets(
  298.         array $options,
  299.         array $filters,
  300.         FilterSetInterface $filterSet,
  301.         bool $showInternalVacancies true,
  302.         ?Site $site null,
  303.         ?VacancyDomainCollection $domainCollection null
  304.     ): array {
  305.         $locale null;
  306.         $entityManager $this->getEntityManager();
  307.         if ($this->translatableListener instanceof TranslatableListener) {
  308.             $locale $this->translatableListener->getListenerLocale();
  309.             $facetCounts $entityManager
  310.                 ->getRepository(Vacancy::class)->findTranslatedByOptionsWithFilterSetFacets(
  311.                     $options,
  312.                     $filterSet,
  313.                     $showInternalVacancies,
  314.                     $locale,
  315.                     $site,
  316.                     $this->parameterBag->get('site_translation_fallback'),
  317.                     $domainCollection
  318.                 );
  319.         } else {
  320.             $facetCounts $entityManager
  321.                 ->getRepository(Vacancy::class)->findByOptionsWithFilterSetFacets(
  322.                     $options,
  323.                     $filterSet,
  324.                     $showInternalVacancies,
  325.                     $locale,
  326.                     $site,
  327.                     $domainCollection
  328.                 );
  329.         }
  330.         $data $this->transformFacetCount([], $facetCounts$filters);
  331.         foreach ($data as $optionValueKey => $optionValue) {
  332.             foreach ($optionValue['values'] as $optionValueValue) {
  333.                 if ($optionValueValue['selected']) {
  334.                     $data $this->getFacetsForOption(
  335.                         $optionValue['title'],
  336.                         $data,
  337.                         $options,
  338.                         $filters,
  339.                         $filterSet,
  340.                         $showInternalVacancies,
  341.                         $locale,
  342.                         $site,
  343.                         $domainCollection
  344.                     );
  345.                     break;
  346.                 }
  347.             }
  348.             if (Option::OPTION_DISPLAY_TYPE_DROPDOWN === $optionValue['display_type']) {
  349.                 $data[$optionValueKey]['dropdown_title'] = $optionValue['title'];
  350.                 if ($optionValueValue['selected']) {
  351.                     $data[$optionValueKey]['dropdown_title'] = $optionValueValue['value'];
  352.                 }
  353.             }
  354.             $this->sortByPosition($data[$optionValueKey]['values']);
  355.         }
  356.         $data array_values($data);
  357.         $this->sortByPosition($data);
  358.         return $data;
  359.     }
  360.     /**
  361.      * @param $data
  362.      */
  363.     public function sortByPosition(&$data)
  364.     {
  365.         usort($data, function ($optionA$optionB) {
  366.             if ($optionA['position'] === $optionB['position']) {
  367.                 return 0;
  368.             }
  369.             return $optionA['position'] < $optionB['position'] ? -1;
  370.         });
  371.     }
  372.     public function getFacetsForOption(
  373.         string $optionName,
  374.         array $data,
  375.         array $options,
  376.         array $filters,
  377.         FilterSetInterface $filterSet,
  378.         bool $showInternalVacancies,
  379.         ?string $locale null,
  380.         ?Site $site null,
  381.         ?VacancyDomainCollection $vacancyDomainCollection null
  382.     ): array {
  383.         $data[$optionName]['values'] = [];
  384.         $entityManager $this->getEntityManager();
  385.         foreach ($options['filters'] as $index => $option) {
  386.             if ('filterquery' === $option['name'] && isset($options['filters'][$index]['value'][$optionName])) {
  387.                 unset($options['filters'][$index]['value'][$optionName]);
  388.             }
  389.             if (!$options['filters'][$index]['value']) {
  390.                 unset($options['filters'][$index]);
  391.             }
  392.         }
  393.         if ($this->translatableListener instanceof TranslatableListener) {
  394.             $facetCounts $entityManager->getRepository(Vacancy::class)
  395.                 ->findTranslatedByOptionsWithFilterSetFacets(
  396.                     $options,
  397.                     $filterSet,
  398.                     $showInternalVacancies,
  399.                     $locale,
  400.                     $site,
  401.                     $this->parameterBag->get('site_translation_fallback'),
  402.                     $vacancyDomainCollection
  403.                 );
  404.         } else {
  405.             $facetCounts $entityManager->getRepository(Vacancy::class)
  406.                 ->findByOptionsWithFilterSetFacets(
  407.                     $options,
  408.                     $filterSet,
  409.                     $showInternalVacancies,
  410.                     $locale,
  411.                     $site,
  412.                     $vacancyDomainCollection
  413.                 );
  414.         }
  415.         return $this->transformFacetCount($data$facetCounts$filters$optionName);
  416.     }
  417.     public function transformFacetCount(
  418.         array $data,
  419.         array $facetCounts,
  420.         array $filters,
  421.         ?string $excludedOptionName null
  422.     ): array {
  423.         if ($this->config->get('site_vacancy_overview_facet_show_zero_results')) {
  424.             $locale $this->translatableListener?->getListenerLocale();
  425.             $facetCountsIds array_column($facetCounts'id');
  426.             $optionValues $this->entityManager->getRepository(OptionValue::class)->findFacetOptionValues($locale);
  427.             foreach ($optionValues as $optionValue) {
  428.                 if (\in_array($optionValue['id'], $facetCountsIdstrue)) {
  429.                     continue;
  430.                 }
  431.                 $optionValue['count'] = 0;
  432.                 $optionValue['optionValueVacancyImageFileName'] = false;
  433.                 $facetCounts[] = $optionValue;
  434.             }
  435.         }
  436.         foreach ($facetCounts as $facetCount) {
  437.             if ($excludedOptionName && $facetCount['title'] !== $excludedOptionName) {
  438.                 continue;
  439.             }
  440.             if (!isset($data[$facetCount['title']])) {
  441.                 $data[$facetCount['title']] = [
  442.                     'id' => $facetCount['option_id'],
  443.                     'title' => $facetCount['title'],
  444.                     'internal_name' => $facetCount['internalName'],
  445.                     'contextual_title' => $facetCount['contextualTitle'] ?? null,
  446.                     'slug' => $facetCount['slug'],
  447.                     'expanded_by_default' => $facetCount['expandedByDefault'],
  448.                     'font_awesome_icon' => $facetCount['fontAwesomeIcon'] ?? null,
  449.                     'position' => $facetCount['optionPosition'],
  450.                     'display_type' => $facetCount['display_type'] ?? null,
  451.                     'expanded_strategy' => $facetCount['expanded_strategy'] ?? null,
  452.                     'strategy_on_overview' => $facetCount['strategyOnOverview'] ?? null,
  453.                 ];
  454.             }
  455.             $isSelected false;
  456.             if (\array_key_exists($facetCount['title'], $filters) && \is_array($filters[$facetCount['title']])) {
  457.                 $isSelected \in_array($facetCount['value'], $filters[$facetCount['title']], true);
  458.             }
  459.             if (\array_key_exists($facetCount['title'], $filters) && \is_scalar($filters[$facetCount['title']])) {
  460.                 $isSelected $facetCount['value'] === $filters[$facetCount['title']];
  461.             }
  462.             $data[$facetCount['title']]['values'][] = [
  463.                 'id' => $facetCount['id'],
  464.                 // We use name or value for the same value in the application, so add both
  465.                 'name' => $facetCount['value'],
  466.                 'value' => $facetCount['value'],
  467.                 'title' => $facetCount['optionValueTitle'] ?? null,
  468.                 // We use both count and vacancyCount throughout the system, so add both
  469.                 'count' => (int) $facetCount['count'],
  470.                 'vacancyCount' => (int) $facetCount['count'],
  471.                 'slug' => $facetCount['optionValueSlug'],
  472.                 'content' => '',
  473.                 'position' => $facetCount['optionValuePosition'],
  474.                 'selected' => $isSelected,
  475.                 'detail_page' => $facetCount['detail_page'],
  476.                 'attributes' => $this->optionValueAttributeManager->getNormalizedValuesByClassAndId(OptionValue::class, $facetCount['id']),
  477.             ];
  478.         }
  479.         foreach ($data as &$option) {
  480.             $option['values_count'] = \count($option['values']);
  481.         }
  482.         return $data;
  483.     }
  484.     /**
  485.      * @throws Exception When datetime cannot be initialised
  486.      */
  487.     public function copy(Vacancy $vacancy): Vacancy
  488.     {
  489.         // clone current vacancy
  490.         $newVacancy = clone $vacancy;
  491.         $newVacancy->setEndDate(new DateTime(
  492.             sprintf('+%s days'$this->config->get('site_vacancy_lifespan_days'))
  493.         ));
  494.         return $newVacancy;
  495.     }
  496.     public function renderLatestVacancies(int $limit 50, ?string $locale null, ?string $template null): string
  497.     {
  498.         $entityManager $this->getEntityManager();
  499.         $vacancyRepository $entityManager->getRepository(Vacancy::class);
  500.         $vacancies $vacancyRepository->findLatestVacancies(
  501.             $limit,
  502.             $this->parameterBag->get('site_vacancy_overview_prefilter'),
  503.             $locale
  504.         );
  505.         $this->vacancyDecorator->decorate($vacanciesfalse);
  506.         $response '';
  507.         try {
  508.             $response $this->twig->render(
  509.                 $template '@default/pages/'.$template '@default/pages/vacancy_latest.html.twig',
  510.                 [
  511.                     'vacancies' => $vacancies,
  512.                 ]
  513.             );
  514.         } catch (LoaderError|RuntimeError|SyntaxError $exception) {
  515.         }
  516.         return $response;
  517.     }
  518.     /**
  519.      * @param array $preFilters
  520.      */
  521.     public function getOptionsFromRequest(Request $request, ?array $preFilters = []): array
  522.     {
  523.         return $this->filterManager->getOptionsFromRequest($request$preFilters);
  524.     }
  525.     public function fetchFilters(Request $request, array $preFilters): array
  526.     {
  527.         return $this->optionManager->fetchFilters($request$preFilters);
  528.     }
  529.     public function fetchFiltersFromUrl(Request $request): array
  530.     {
  531.         return $this->optionManager->fetchFiltersFromUrl($request);
  532.     }
  533.     public function getVacancyFromRequest(Request $request): ?VacancyWrapper
  534.     {
  535.         if ($this->translatableListener instanceof TranslatableListener) {
  536.             $this->translatableListener->setTranslatableLocale($request->getLocale());
  537.         }
  538.         if (!$request->get('id')) {
  539.             return null;
  540.         }
  541.         $vacancyRepository $this->getEntityManager()->getRepository(Vacancy::class);
  542.         if ($vacancy $vacancyRepository->find($request->get('id'), nullnulltrue)) {
  543.             $this->translationUtil->translateEntity($vacancy$request->getLocale());
  544.             $wrapper = new VacancyWrapper();
  545.             $wrapper->vacancy $vacancy;
  546.             return $wrapper;
  547.         }
  548.         if (
  549.             $this->featureManager->isActive(FeatureFlagListener::FEATURE_CHILD_SITE) &&
  550.             $this->parameterBag->has('site_master_host_url')
  551.         ) {
  552.             $vacancyJsonString $this->getVacancyDetailResponse($request);
  553.             if (!$vacancyJsonString) {
  554.                 return null;
  555.             }
  556.             $vacancy = !empty($vacancyJsonString) ?
  557.                 $this->serializer->deserialize($vacancyJsonStringVacancy::class, 'json') :
  558.                 null;
  559.             $wrapper = new VacancyWrapper();
  560.             $wrapper->vacancy $vacancy;
  561.             $vacancyJson json_decode($vacancyJsonStringtrue);
  562.             if (!empty($vacancyJson['company']) && !empty($vacancyJson['company']['logo'])) {
  563.                 $wrapper->vacancyImages->companyLogo->filtersWithUrls $vacancyJson['company']['logo'];
  564.             }
  565.             if (!empty($vacancyJson['overview_image'])) {
  566.                 $wrapper->vacancyImages->overviewImage->filtersWithUrls $vacancyJson['overview_image'];
  567.             }
  568.             if (!empty($vacancyJson['detail_image'])) {
  569.                 $wrapper->vacancyImages->detailImage->filtersWithUrls $vacancyJson['detail_image'];
  570.             }
  571.             if (!empty($vacancyJson['company']) && !empty($vacancyJson['company']['overview_image'])) {
  572.                 $wrapper->vacancyImages->companyOverViewImage->filtersWithUrls $vacancyJson['company']['overview_image'];
  573.             }
  574.             if (!empty($vacancyJson['company']) && !empty($vacancyJson['company']['hero'])) {
  575.                 $wrapper->vacancyImages->companyHeroImage->filtersWithUrls $vacancyJson['company']['hero'];
  576.             }
  577.             if (!empty($vacancyJson['hero'])) {
  578.                 $wrapper->vacancyImages->heroImage->filtersWithUrls $vacancyJson['hero'];
  579.             }
  580.             return $wrapper;
  581.         }
  582.         return null;
  583.     }
  584.     private function getVacancyDetailResponse(Request $request): ?string
  585.     {
  586.         try {
  587.             $response $this->client->get($this->router->generate(
  588.                 'app_rest_vacancy_getvacancy',
  589.                 ['id' => $request->get('id'), 'host' => $request->getHost()]
  590.             ));
  591.             $vacancyJsonString $response->getBody()->getContents();
  592.         } catch (Exception $exception) {
  593.             $vacancyJsonString null;
  594.         }
  595.         return $vacancyJsonString;
  596.     }
  597.     /**
  598.      * @param Vacancy|null $vacancy
  599.      */
  600.     public function getRelatedVacancies(Request $requestVacancy $vacancy, ?int $limit null): array
  601.     {
  602.         return $this->getVacancyReader()->getRelatedVacancies($request$vacancy$limit);
  603.     }
  604.     public function getVacancyCanonicalUrl(Vacancy $vacancy): string
  605.     {
  606.         if (!$vacancy->getSite() && !empty($this->parameterBag->get('site_master_host_url'))) {
  607.             $result $this->client->get($this->router->generate('rest_vacancy_site', ['id' => $vacancy->getId()]));
  608.             $siteJson $result->getBody()->getContents();
  609.             $site null;
  610.             if (!empty($siteJson) && json_decode($siteJson)) {
  611.                 $site $this->serializer->deserialize($siteJsonSite::class, 'json');
  612.             }
  613.             $vacancy->setSite($site);
  614.         }
  615.         return $this->siteService->generateRoute(
  616.             'vacancy_detail',
  617.             $vacancy->getSite(),
  618.             ['id' => $vacancy->getId(), 'slug' => $vacancy->getSlug()]
  619.         );
  620.     }
  621.     /**
  622.      * @return Vacancy[]
  623.      */
  624.     public function getTopVacancies(?int $limit null, ?string $sort null): array
  625.     {
  626.         return $this->getVacancyReader()->getFeaturedVacancies($limit$sort);
  627.     }
  628.     public function setCompanyAddress(Vacancy $vacancy): Vacancy
  629.     {
  630.         if (($company $vacancy->getCompany()) instanceof Company) {
  631.             $vacancy->setAddress($company->getAddress());
  632.             $vacancy->setZipcode($company->getZipcode());
  633.             $vacancy->setCity($company->getCity());
  634.         }
  635.         return $vacancy;
  636.     }
  637.     /**
  638.      * @throws Exception
  639.      */
  640.     public function renewSortDate(Vacancy $vacancy): Vacancy
  641.     {
  642.         $vacancy->setSortDate(new DateTime());
  643.         $this->entityManager->persist($vacancy);
  644.         $this->entityManager->flush();
  645.         return $vacancy;
  646.     }
  647.     public function addRedirectClick(Vacancy $vacancy)
  648.     {
  649.         $vacancy $vacancy->addRedirect();
  650.         $this->entityManager->persist($vacancy);
  651.         $this->entityManager->flush();
  652.     }
  653.     public function getFreePosting(User $user, ?Company $company null): bool
  654.     {
  655.         /* @var User $user */
  656.         if (!$company instanceof Company) {
  657.             $company $user->getCompany();
  658.         }
  659.         $freePostingStatuses $this->creditService->getFreePostingStatuses();
  660.         return $company->getCompanyStatus() && \in_array($company->getCompanyStatus(), $freePostingStatusestrue);
  661.     }
  662.     /**
  663.      * @return array|bool
  664.      */
  665.     public function getRelatedOverviewMeta(Vacancy $vacancy): ?array
  666.     {
  667.         $mainOption false;
  668.         $mainOptionValue false;
  669.         // use primary discipline instead of option
  670.         if ($this->config->get('site_vacancy_use_primary_discipline_instead_of_option_for_related_overview')) {
  671.             if (null === $vacancy->getPrimaryDiscipline()) {
  672.                 return null;
  673.             }
  674.             $relatedOverviewPath $this->siteService->generateRoute(
  675.                 'vacancies_with_filter',
  676.                 $vacancy->getPrimaryDiscipline()->getSite(),
  677.                 [
  678.                     'filters' => $vacancy->getPrimaryDiscipline()->getOption()->getSlug().'/'.$vacancy->getPrimaryDiscipline()->getSlug(),
  679.                 ]
  680.             );
  681.             $relatedVacanciesCount $this->parentEntityManager->getRepository(Vacancy::class)
  682.                 ->countByOptionValue($vacancy->getPrimaryDiscipline()->getOption(), $vacancy->getPrimaryDiscipline(), $this->siteService->getSite());
  683.         } else {
  684.             $siteRelationOverviewOption $this->config->get('site_vacancy_related_overview_option');
  685.             if ($siteRelationOverviewOption) {
  686.                 foreach ($vacancy->getOptionValues()->toArray() as $optionValue) {
  687.                     if ($optionValue->getOption()->getId() === $siteRelationOverviewOption->getId()) {
  688.                         $mainOption $optionValue->getOption();
  689.                         $mainOptionValue $optionValue;
  690.                         break;
  691.                     }
  692.                 }
  693.             }
  694.             if (!$mainOption || !$mainOptionValue) {
  695.                 return null;
  696.             }
  697.             $relatedOverviewPath $this->router->generate('vacancies_with_filter', [
  698.                 'filters' => $mainOption->getSlug().'/'.$mainOptionValue->getSlug(),
  699.             ]);
  700.             $relatedVacanciesCount $this->entityManager->getRepository(Vacancy::class)
  701.                 ->countByOptionValue($mainOption$mainOptionValue$this->siteService->getSite());
  702.         }
  703.         return [
  704.             'path' => $relatedOverviewPath,
  705.             'count' => $relatedVacanciesCount,
  706.         ];
  707.     }
  708.     public function getSeoSnippetsFromFacets(array $facets, ?Site $site null): array
  709.     {
  710.         $searchOptionValues = [];
  711.         foreach ($facets as $facet) {
  712.             foreach ($facet['values'] as $value) {
  713.                 if (!$value['selected']) {
  714.                     continue;
  715.                 }
  716.                 $searchOptionValues[] = $value['id'];
  717.             }
  718.         }
  719.         if (!$searchOptionValues) {
  720.             return [];
  721.         }
  722.         return $this->createSeoSnippetArray($searchOptionValues$site);
  723.     }
  724.     public function getSeoSnippetsFromFilters(array $filters, ?Site $site): array
  725.     {
  726.         $searchOptionValues = [];
  727.         foreach ($filters as $filter) {
  728.             foreach ($filter as $subFilter) {
  729.                 $searchOptionValues[] = $subFilter['id'];
  730.             }
  731.         }
  732.         if (!$searchOptionValues) {
  733.             return [];
  734.         }
  735.         return $this->createSeoSnippetArray($searchOptionValues$site);
  736.     }
  737.     private function createSeoSnippetArray(array $searchOptionValues, ?Site $site): array
  738.     {
  739.         $arrayVacancyOverviewTitle $this->config->get('site_vacancy_overview_title');
  740.         $overviewImage $this->config->get('theme_vacancy_overview_image');
  741.         $locale $this->request->getLocale() ?: $this->request->getDefaultLocale();
  742.         $allSeoSnippets $this->getEntityManager()->getRepository(SeoSnippet::class)
  743.             ->findByOptionValues($searchOptionValues$sitefalse);
  744.         $seoSnippets array_filter(
  745.             $allSeoSnippets,
  746.             static function (SeoSnippet $seoSnippet) use ($searchOptionValues) {
  747.                 if (\count($searchOptionValues) !== $seoSnippet->getOptionValues()->count()) {
  748.                     return false;
  749.                 }
  750.                 foreach ($seoSnippet->getOptionValues() as $option) {
  751.                     if (!\in_array($option->getId(), $searchOptionValuestrue)) {
  752.                         return false;
  753.                     }
  754.                 }
  755.                 return true;
  756.             }
  757.         );
  758.         $array = [
  759.             'id' => '',
  760.             'name' => '',
  761.             'snippet' => '',
  762.             'optionValues' => '',
  763.             'site' => '',
  764.             'headerImage' => $overviewImage,
  765.             'title' => $arrayVacancyOverviewTitle[$locale] ?? '',
  766.             'ogImage' => '',
  767.             'metaTitle' => '',
  768.             'metaDescription' => '',
  769.         ];
  770.         $seoSnippet = [] !== $seoSnippets reset($seoSnippets) : $array;
  771.         if (\is_array($seoSnippet)) {
  772.             return $seoSnippet;
  773.         }
  774.         $fallBackTitle $arrayVacancyOverviewTitle[$locale] ?? '';
  775.         $array = [
  776.             'id' => $seoSnippet->getId(),
  777.             'name' => $seoSnippet->getName(),
  778.             'snippet' => $seoSnippet->getSnippet(),
  779.             'optionValues' => $seoSnippet->getOptionValues(),
  780.             'site' => $seoSnippet->getSite(),
  781.             'headerImage' => $seoSnippet->getHeaderImage() ?? $overviewImage,
  782.             'title' => $seoSnippet->getTitle() ? $seoSnippet->getTitle() : $fallBackTitle,
  783.             'ogImage' => $seoSnippet->getOgImage(),
  784.             'metaTitle' => $seoSnippet->getMetaTitle(),
  785.             'metaDescription' => $seoSnippet->getMetaDescription(),
  786.         ];
  787.         return $array;
  788.     }
  789.     /**
  790.      * @return Asset|string|null
  791.      */
  792.     public function getOgImage(Vacancy $vacancy)
  793.     {
  794.         if ($vacancy->getOgImage() instanceof OgImage) {
  795.             return $this->seo->renderOgImage($vacancy->getOgImage(), $vacancy);
  796.         }
  797.         $ogImageOptionValue $this->getOgImageOptionValue($vacancy);
  798.         switch ($this->config->get('site_vacancy_detail_og_image_strategy')) {
  799.             case VacancySettingType::VACANCY_DETAIL_OG_IMAGE_STRATEGY_COMPANY_LOGO:
  800.                 if ($vacancy->getCompany() && $vacancy->getCompany()->getLogo()) {
  801.                     return $vacancy->getCompany()
  802.                         ->getLogo();
  803.                 }
  804.                 break;
  805.             case VacancySettingType::VACANCY_DETAIL_OG_IMAGE_STRATEGY_VACANCY_IMAGE:
  806.                 if ($ogImageOptionValue) {
  807.                     return $ogImageOptionValue->getVacancyImage();
  808.                 }
  809.                 break;
  810.             case VacancySettingType::VACANCY_DETAIL_OG_IMAGE_STRATEGY_VACANCY_HERO_IMAGE:
  811.                 if ($ogImageOptionValue) {
  812.                     return $ogImageOptionValue->getVacancyHeroImage();
  813.                 }
  814.                 break;
  815.             default:
  816.                 if ($this->vacancyImage->getHeroImage($vacancy)) {
  817.                     return $this->vacancyImage->getHeroImage($vacancy);
  818.                 }
  819.                 break;
  820.         }
  821.         if ($vacancy->getOgImage() instanceof OgImage) {
  822.             return $this->seo->renderOgImage($vacancy->getOgImage(), $vacancy);
  823.         }
  824.         return null;
  825.     }
  826.     protected function getOgImageOptionValue(Vacancy $vacancy): ?OptionValue
  827.     {
  828.         $ogImageOption $this->config->get('site_vacancy_detail_og_image_option');
  829.         if (!$ogImageOption) {
  830.             return null;
  831.         }
  832.         // Determine the first optionValue with $ogImageOption as parent
  833.         foreach ($vacancy->getOptionValues() as $optionValue) {
  834.             if ($optionValue->getOption()->getId() === $ogImageOption->getId()) {
  835.                 return $optionValue;
  836.             }
  837.         }
  838.         return null;
  839.     }
  840.     public function transformVacancy(Vacancy $vacancy, ?Site $site nullbool $shouldFilterOptions true): array
  841.     {
  842.         $optionValueRepository $this->entityManager->getRepository(OptionValue::class);
  843.         $company $vacancy->getCompany();
  844.         $recruiter $vacancy->getRecruiter();
  845.         $optionValues = [];
  846.         /** @var OptionValue $optionValue */
  847.         foreach ($vacancy->getOptionValues() as $optionValue) {
  848.             $option $optionValue->getOption();
  849.             if ($option->getSites()->count() && $shouldFilterOptions) {
  850.                 if (!$option->getSites()->contains($site)) {
  851.                     continue;
  852.                 }
  853.             }
  854.             $optionValues[] = [
  855.                 'id' => $optionValue->getId(),
  856.                 'value' => $optionValue->getValue(),
  857.                 'title' => $optionValue->getTitle(),
  858.                 'sub_title' => $optionValue->getSubTitle(),
  859.                 'slug' => $optionValue->getSlug(),
  860.                 'vacancies' => [],
  861.                 'external_reference' => $optionValue->getExternalReference(),
  862.                 'vacancy_count' => $optionValueRepository->getVacancyCountByOptionValue($optionValue),
  863.                 'option' => [
  864.                     'id' => $option->getId(),
  865.                     'title' => $option->getTitle(),
  866.                     'slug' => $option->getSlug(),
  867.                     'font_awesome_icon' => $option->getFontAwesomeIcon(),
  868.                     'expanded_by_default' => $option->isExpandedByDefault(),
  869.                     'strategy_on_overview' => $option->getStrategyOnOverview(),
  870.                     'visible_in_detail' => $option->isVisibleInDetail(),
  871.                     'values' => [],
  872.                 ],
  873.                 'font_awesome_icon' => $optionValue->getFontAwesomeIcon(),
  874.                 'content' => $optionValue->getContent(),
  875.             ];
  876.         }
  877.         $primaryDiscipline null;
  878.         if ($vacancy->getPrimaryDiscipline()) {
  879.             $primaryDiscipline = [
  880.                 'id' => $vacancy->getPrimaryDiscipline()->getId(),
  881.                 'value' => $vacancy->getPrimaryDiscipline()->getValue(),
  882.                 'slug' => $vacancy->getPrimaryDiscipline()->getSlug(),
  883.             ];
  884.         }
  885.         $secundaryDiscipline null;
  886.         if ($vacancy->getSecundaryDiscipline()) {
  887.             $secundaryDiscipline = [
  888.                 'id' => $vacancy->getSecundaryDiscipline()->getId(),
  889.                 'value' => $vacancy->getSecundaryDiscipline()->getValue(),
  890.                 'slug' => $vacancy->getSecundaryDiscipline()->getSlug(),
  891.             ];
  892.         }
  893.         $site null;
  894.         if ($vacancy->getSite()) {
  895.             $site $this->transformSite($vacancy->getSite());
  896.         }
  897.         $sitesToFindOn = [];
  898.         if ($vacancy->getSitesToFindOn()) {
  899.             foreach ($vacancy->getSitesToFindOn() as $siteToFindOn) {
  900.                 $sitesToFindOn[] = $this->transformSite($siteToFindOn);
  901.             }
  902.         }
  903.         $objectMultiMedia = [];
  904.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_MULTI_MEDIA)) {
  905.             $objectMultiMedia $this->multiMediaManager->getFeaturedObjectMultiMediaByClass(Vacancy::class);
  906.         }
  907.         $featuredMultiMedia $this->getFeaturedMultiMedia($objectMultiMedia$vacancy->getId());
  908.         $detailUrl null;
  909.         if ($vacancy->getSlug()) {
  910.             $detailUrl $this->siteService->generateRoute(
  911.                 'vacancy_detail',
  912.                 $vacancy->getSite(),
  913.                 ['id' => $vacancy->getId(), 'slug' => $vacancy->getSlug()]
  914.             );
  915.         }
  916.         /** @var Tag[] $labels */
  917.         $labels $vacancy->getLabels()->toArray();
  918.         $data = [
  919.             'id' => $vacancy->getId(),
  920.             'external_id' => $vacancy->getExternalId(),
  921.             'external_reference' => $vacancy->getExternalReference(),
  922.             'created' => $vacancy->getCreated()?->format(\DATE_RFC3339),
  923.             'updated' => $vacancy->getUpdated()?->format(\DATE_RFC3339),
  924.             'contact_person' => $vacancy->getContactPerson(),
  925.             'contact_person_phone' => $vacancy->getContactPersonPhone(),
  926.             'slug' => $vacancy->getSlug(),
  927.             'title' => $vacancy->getTitle(),
  928.             'company_name' => $vacancy->getCompanyName(),
  929.             'primary_discipline' => $primaryDiscipline,
  930.             'secundary_discipline' => $secundaryDiscipline,
  931.             'site' => $site,
  932.             'sites_to_find_on' => $sitesToFindOn,
  933.             'city' => $vacancy->getCity(),
  934.             'zipcode' => $vacancy->getZipcode(),
  935.             'intro' => $vacancy->getIntro(),
  936.             'option_values' => $optionValues,
  937.             'main_icon' => [
  938.                 'font_awesome_icon' => $vacancy->getMainIcon() ? $vacancy->getMainIcon()->getFontAwesomeIcon() : '',
  939.             ],
  940.             'vacancy_usp_values' => [],
  941.             'usps' => [],
  942.             'featured' => $vacancy->isFeatured(),
  943.             'internal_vacancy' => $vacancy->isInternalVacancy(),
  944.             'start_date' => $vacancy->getStartDate()?->format(\DATE_RFC3339),
  945.             'job_start_date' => $vacancy->getJobStartDate()?->format(\DATE_RFC3339),
  946.             'new' => $vacancy->isNew(),
  947.             'knockout_questions' => $vacancy->getKnockoutQuestions(),
  948.             'address' => $vacancy->getAddress(),
  949.             'latitude' => $vacancy->getLatitude(),
  950.             'longitude' => $vacancy->getLongitude(),
  951.             'overview_image' => $vacancy->getOverviewImage() && $vacancy->getOverviewImage()->getFileName() ?
  952.                 $this->assetUtil->getAssetUrlsForFilters($vacancy->getOverviewImage(), $this->imageSizes) :
  953.                 null,
  954.             'hero' => $this->vacancyImage->getHeroImage($vacancy) && $this->vacancyImage->getHeroImage($vacancy)->getFileName() ?
  955.                 $this->assetUtil->getAssetUrlsForFilters($this->vacancyImage->getHeroImage($vacancy), $this->imageSizes) :
  956.                 null,
  957.             'detail_url' => $detailUrl,
  958.             'custom_salary' => $vacancy->getCustomSalary(),
  959.             'featured_multi_media' => $featuredMultiMedia,
  960.             'attribute_values' => $this->attributeManager->getNormalizedValues($vacancy),
  961.             'salary' => $vacancy->getSalary(),
  962.             'min_salary' => $vacancy->getMinSalary(),
  963.             'max_salary' => $vacancy->getMaxSalary(),
  964.             'salary_unit' => $vacancy->getSalaryUnit(),
  965.             'labels' => array_map(function (Tag $tag) {
  966.                 return [
  967.                     'value' => $tag->getValue(),
  968.                     'appearance' => $tag->getAppearance(),
  969.                 ];
  970.             }, $labels),
  971.         ];
  972.         if (!empty($vacancy->getExternalUrl())) {
  973.             $data['external_url'] = $vacancy->getExternalUrl();
  974.         }
  975.         if ($company) {
  976.             $data['company'] = [
  977.                 'id' => $company->getId(),
  978.                 'slug' => $company->getSlug(),
  979.                 'name' => $vacancy->getCompanyName() ?? $company->getName(),
  980.                 'zipcode' => $company->getZipcode(),
  981.                 'city' => $company->getCity(),
  982.                 'website' => $company->getWebsite(),
  983.                 'email' => $company->getEmail(),
  984.                 'option_values' => [],
  985.                 'address' => $company->getAddress(),
  986.                 'latitude' => $company->getLatitude(),
  987.                 'longitude' => $company->getLongitude(),
  988.                 'logo' => ($company->getLogo() && $company->getLogo()->getFileName()) ?
  989.                     $this->assetUtil->getAssetUrlsForFilters($company->getLogo(), $this->imageSizes) : null,
  990.                 'video' => $company->getVideo(),
  991.                 'detail_page_published' => $company->isDetailPagePublished(),
  992.             ];
  993.         }
  994.         if ($recruiter) {
  995.             $data['recruiter'] = [
  996.                 'id' => $recruiter->getId(),
  997.                 'first_name' => $recruiter->getFirstName(),
  998.                 'last_name_prefix' => $recruiter->getLastNamePrefix(),
  999.                 'last_name' => $recruiter->getLastName(),
  1000.                 'email' => $recruiter->getEmail(),
  1001.                 'phone' => $recruiter->getPhone(),
  1002.                 'linkedin_url' => $recruiter->getLinkedinUrl(),
  1003.                 'intro' => $recruiter->getIntro(),
  1004.                 'description' => $recruiter->getDescription(),
  1005.                 'picture_name' => $recruiter->getPictureName(),
  1006.                 'updated_at' => $recruiter->getUpdatedAt(),
  1007.                 'vacancies' => [],
  1008.                 'external_reference' => $recruiter->getExternalReference(),
  1009.                 'companies' => [],
  1010.             ];
  1011.         }
  1012.         return $data;
  1013.     }
  1014.     /**
  1015.      * @throws NonUniqueResultException
  1016.      */
  1017.     public function transformVacancies(array $vacancies, ?Site $site null): array
  1018.     {
  1019.         $vacancyOutput = [];
  1020.         /** @var Vacancy $vacancy */
  1021.         foreach ($vacancies as $vacancy) {
  1022.             $vacancyOutput[] = $this->transformVacancy($vacancy$site);
  1023.         }
  1024.         return $vacancyOutput;
  1025.     }
  1026.     public function getVacanciesResponseByRequest(Request $requestbool $isMap false): SearchResultSet
  1027.     {
  1028.         if ($isMap) {
  1029.             return $this->getVacancyReader()->getMapSearchResultSetByRequest($request);
  1030.         }
  1031.         return $this->getVacancyReader()->getSearchResultSetByRequest($request);
  1032.     }
  1033.     public function getVacancyReader(): AbstractVacancyReader
  1034.     {
  1035.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_GOOGLE_CLOUD_TALENT_SOLUTION)) {
  1036.             return $this->readerCollection->get(GoogleCloudTalentSolutionReader::class);
  1037.         }
  1038.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_CHILD_SITE)) {
  1039.             return $this->readerCollection->get(MultiSiteReader::class);
  1040.         }
  1041.         if ($this->translatableListener instanceof TranslatableListener) {
  1042.             return $this->readerCollection->get(MultilingualDoctrineReader::class);
  1043.         }
  1044.         return $this->readerCollection->get(DoctrineReader::class);
  1045.     }
  1046.     /**
  1047.      * @return int
  1048.      */
  1049.     public function getVacancyCount(Request $request): ?int
  1050.     {
  1051.         return $this->getVacancyReader()->getVacancyCount($request);
  1052.     }
  1053.     public function getRecentVacancies(Request $request): array
  1054.     {
  1055.         $vacancies = [];
  1056.         $companyLogos = [];
  1057.         if (!$ids $request->get('vacancyIds')) {
  1058.             return [];
  1059.         }
  1060.         return $this->getVacancyReader()->getVacanciesByIds($ids);
  1061.     }
  1062.     /**
  1063.      * @return Vacancy[]
  1064.      */
  1065.     public function getVacanciesByContext(QueryContext $queryContext): array
  1066.     {
  1067.         $locale null;
  1068.         // If no fallback, exclude the vacancies with no translations by getting vacancies from only current locale
  1069.         if (false === $this->translatableListener?->getTranslationFallback()) {
  1070.             $locale $this->translatableListener?->getListenerLocale();
  1071.         }
  1072.         /** @var VacancyRepository $repository */
  1073.         $repository $this->getEntityManager()->getRepository(Vacancy::class);
  1074.         return $repository->findVacanciesWithContext($queryContext$locale);
  1075.     }
  1076.     public function getVacancyCountByContext(QueryContext $queryContext): int
  1077.     {
  1078.         $locale null;
  1079.         if (false === $this->translatableListener?->getTranslationFallback()) {
  1080.             $locale $this->translatableListener?->getListenerLocale();
  1081.         }
  1082.         /** @var VacancyRepository $repository */
  1083.         $repository $this->getEntityManager()->getRepository(Vacancy::class);
  1084.         return $repository->findVacancyCountByContext($queryContext$locale);
  1085.     }
  1086.     private function getCompanyLogos(array $responseData): array
  1087.     {
  1088.         $companyLogos = [];
  1089.         foreach ($responseData as $vacancyData) {
  1090.             if (!empty($vacancyData->company)
  1091.                 && !empty($vacancyData->company->logo)
  1092.                 && empty($companyLogos[$vacancyData->company->id])) {
  1093.                 $companyLogos[$vacancyData->company->id] = (array) $vacancyData->company->logo;
  1094.             }
  1095.         }
  1096.         return $companyLogos;
  1097.     }
  1098.     public function isCacheAble(): bool
  1099.     {
  1100.         return $this->getVacancyReader()->isCacheAble();
  1101.     }
  1102.     public function addSitesToVacancy(Vacancy $vacancystring $siteMappingValue): Vacancy
  1103.     {
  1104.         $vacancy->getSitesToFindOn()->clear();
  1105.         if (!$this->sites) {
  1106.             $this->sites $this->entityManager->getRepository(Site::class)->findAll();
  1107.         }
  1108.         foreach ($this->sites as $site) {
  1109.             if (\in_array($siteMappingValue$site->getMappingValues() ?? [], true)) {
  1110.                 $vacancy->addSiteToFindOn($site);
  1111.             }
  1112.         }
  1113.         return $vacancy;
  1114.     }
  1115.     public function fetchVacanciesWithApplicantsBelowThreshold(int $threshold, ?int $limit null): array
  1116.     {
  1117.         $vacancies $this->entityManager
  1118.             ->getRepository(Vacancy::class)
  1119.             ->findVacanciesWithApplicantCountLessThan($threshold$limit)
  1120.         ;
  1121.         return array_filter($vacancies, function (array $vacancy) {
  1122.             if ($this->authorizationChecker->isGranted('ROLE_ADMIN_MANAGER')) {
  1123.                 return true;
  1124.             }
  1125.             if ($this->authorizationChecker->isGranted('ROLE_ADMIN_JOB_OWNER') && $vacancy['vacancy']->getCompany()) {
  1126.                 return $vacancy['vacancy']->getCompany()->getConsultant() ===
  1127.                     $this->tokenStorage->getToken()->getUser();
  1128.             }
  1129.             return false;
  1130.         });
  1131.     }
  1132.     /**
  1133.      * @throws Exception
  1134.      *
  1135.      * @return array
  1136.      */
  1137.     public function fetchExpiringVacancies(int $days, ?int $limit null)
  1138.     {
  1139.         $vacancies $this->entityManager->getRepository(Vacancy::class)
  1140.             ->findExpiringInDays($days$limit);
  1141.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_COMPANY_DASHBOARD)) {
  1142.             $vacancies array_filter($vacancies, function (Vacancy $vacancy) {
  1143.                 if ($this->authorizationChecker->isGranted('ROLE_ADMIN_MANAGER')) {
  1144.                     return true;
  1145.                 }
  1146.                 if ($this->authorizationChecker->isGranted('ROLE_ADMIN_JOB_OWNER')) {
  1147.                     return $vacancy->getCompany()->getConsultant() === $this->tokenStorage->getToken()->getUser();
  1148.                 }
  1149.                 return false;
  1150.             });
  1151.         }
  1152.         return $vacancies;
  1153.     }
  1154.     public function getActiveVacanciesByOptionValue(OptionValue $optionValue): array
  1155.     {
  1156.         return $this->entityManager->getRepository(Vacancy::class)->findPublishedVacancies(null$optionValue->getId(), 10);
  1157.     }
  1158.     public function getDescriptionFields(bool $mandatoryOnly false): array
  1159.     {
  1160.         if (!$descriptionForm $this->entityManager->getRepository(VacancyDescriptionForm::class)->findOneBy(['type' => 'regular'])) {
  1161.             return [];
  1162.         }
  1163.         $descriptionFields = [];
  1164.         $mandatoryFields = [];
  1165.         $formFields json_decode($descriptionForm->getFields(), true);
  1166.         foreach ($formFields as $field) {
  1167.             $descriptionFields[] = $field['name'];
  1168.             if (!empty($field['required']) && true === $field['required']) {
  1169.                 $mandatoryFields[] = $field['name'];
  1170.             }
  1171.         }
  1172.         if ($mandatoryOnly) {
  1173.             return $mandatoryFields;
  1174.         }
  1175.         return $descriptionFields;
  1176.     }
  1177.     private function transformSite(Site $site): array
  1178.     {
  1179.         return [
  1180.             'id' => $site->getId(),
  1181.             'name' => $site->getName(),
  1182.         ];
  1183.     }
  1184.     public function getPublishedVacancies(
  1185.         ?int $limit null,
  1186.         ?int $companyId null,
  1187.         ?int $filterId null,
  1188.         ?Site $site null,
  1189.         array $context = []
  1190.     ): array {
  1191.         return $this->getEntityManager()->getRepository(Vacancy::class)
  1192.             ->findPublishedVacancies(
  1193.                 $companyId,
  1194.                 $filterId,
  1195.                 $limit,
  1196.                 null,
  1197.                 $site
  1198.             )
  1199.         ;
  1200.     }
  1201.     public function getSitemapVacancies(): array
  1202.     {
  1203.         return $this->entityManager->getRepository(Vacancy::class)
  1204.             ->findSitemapVacancies()
  1205.         ;
  1206.     }
  1207.     private function getEntityManager(): EntityManagerInterface
  1208.     {
  1209.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_CHILD_SITE)) {
  1210.             return $this->parentEntityManager;
  1211.         }
  1212.         return $this->entityManager;
  1213.     }
  1214.     public function getFeaturedMultiMedia(array $objectMultiMediaint $vacancyId): array
  1215.     {
  1216.         if (!$this->featureManager->isActive(FeatureFlagListener::FEATURE_MULTI_MEDIA)) {
  1217.             return [];
  1218.         }
  1219.         $serializedFeaturedMultiMedia = [];
  1220.         $relatedOjectMultiMedia array_filter($objectMultiMedia, static function (ObjectMultiMedia $relatedOjectMultiMedia) use ($vacancyId) {
  1221.             return $relatedOjectMultiMedia->getForeignKey() === $vacancyId;
  1222.         });
  1223.         $featuredObjectMultiMedia array_shift($relatedOjectMultiMedia);
  1224.         if ($featuredObjectMultiMedia) {
  1225.             $featuredMultiMedia $featuredObjectMultiMedia->getMultiMedia();
  1226.             $serializedFeaturedMultiMedia = [
  1227.                 'id' => $featuredMultiMedia->getId(),
  1228.                 'adapter' => $featuredMultiMedia->getAdapter(),
  1229.                 'title' => $featuredMultiMedia->getTitle(),
  1230.                 'embed_url' => $featuredMultiMedia->getEmbedUrl(),
  1231.                 'external_thumbnail' => $featuredMultiMedia->getExternalThumbnail() ?? '',
  1232.                 'custom_thumbnail' => $featuredMultiMedia->getCustomThumbnail() ? $this->assetUtil->getAssetUrlsForFilters($featuredMultiMedia->getCustomThumbnail(), $this->imageSizes) : null,
  1233.             ];
  1234.         }
  1235.         return $serializedFeaturedMultiMedia;
  1236.     }
  1237.     public function getViewsPerVacancyData(string $startDate '30daysAgo'string $endDate '1daysAgo'string $company 'All'): array
  1238.     {
  1239.         $options = ['startdate' => $startDate'enddate' => $endDate];
  1240.         $viewData $this->analyticsClient->getViewsPerVacancyData($options);
  1241.         $viewData $this->googleAnalyticsService->getVacanciesInformation($viewData$company);
  1242.         $companies $this->entityManager->getRepository(Company::class)->getCompanyNames();
  1243.         sort($companies);
  1244.         array_unshift($companies'All');
  1245.         return ['viewData' => $viewData'companies' => $companies'option' => $options'currentCompanySelected' => $company];
  1246.     }
  1247.     /**
  1248.      * @throws LoaderError
  1249.      * @throws RuntimeError
  1250.      * @throws SyntaxError
  1251.      */
  1252.     public function renderECommerceScript(Vacancy $vacancy): string
  1253.     {
  1254.         $category null;
  1255.         $categoryOption $this->config->get('site_google_category_option');
  1256.         if ($categoryOption) {
  1257.             $category $vacancy->getOptionValues()->filter(
  1258.                 function (OptionValue $optionValue) use ($categoryOption) {
  1259.                     return $optionValue->getOption()->getId() === $categoryOption->getId();
  1260.                 }
  1261.             )->first()
  1262.             ;
  1263.         }
  1264.         $output $this->twig->render('scripts/e_commerce_vacancy_detail_script.html.twig', [
  1265.             'category' => $category ?? null,
  1266.             'vacancy' => $vacancy,
  1267.         ]);
  1268.         if ($this->featureManager->isActive(FeatureFlagListener::FEATURE_HIRESERVE)) {
  1269.             $output .= $this->twig->render('scripts/ubeeo_ecommerce_vacancy_detail_script.html.twig', [
  1270.                 'ubeeo_website_id' => $this->parameterBag->get('site_hire_serve_web_site_id'),
  1271.                 'vacancy' => $vacancy,
  1272.             ]);
  1273.         }
  1274.         return $output;
  1275.     }
  1276.     /**
  1277.      * @throws LoaderError
  1278.      * @throws RuntimeError
  1279.      * @throws SyntaxError
  1280.      */
  1281.     public function renderHeadEndScriptForVacancyDetail(): string
  1282.     {
  1283.         if (
  1284.             $this->parameterBag->get('site_ingoedebanen_enable_tracking') &&
  1285.             $this->featureManager->isActive(FeatureFlagListener::FEATURE_INGOEDEBANEN_ADVANCED_STATISTICS)
  1286.         ) {
  1287.             return $this->twig->render('scripts/ingoedebanen_start_script.html.twig');
  1288.         }
  1289.         return '';
  1290.     }
  1291.     /**
  1292.      * @throws LoaderError
  1293.      * @throws RuntimeError
  1294.      * @throws SyntaxError
  1295.      */
  1296.     public function renderBodyEndScriptForVacancyDetail(Vacancy $vacancy): string
  1297.     {
  1298.         if (
  1299.             $this->parameterBag->get('site_ingoedebanen_enable_tracking') &&
  1300.             $this->featureManager->isActive(FeatureFlagListener::FEATURE_INGOEDEBANEN_ADVANCED_STATISTICS)
  1301.         ) {
  1302.             return $this->twig->render('scripts/ingoedebanen_vacancy_end_script.html.twig', [
  1303.                 'vacancy' => $vacancy,
  1304.             ]);
  1305.         }
  1306.         return '';
  1307.     }
  1308.     public function getCompanyVacancies(int $companyId): array
  1309.     {
  1310.         return $this->getEntityManager()->getRepository(Vacancy::class)
  1311.             ->findCompanyVacancies($companyId);
  1312.     }
  1313.     /**
  1314.      * @return array<int|string, array<int|string, mixed>>
  1315.      */
  1316.     public function getVacancyUspValues(array $vacancy, array $vacancyUspCollection, ?Option $vacancyUspStrategyOption): array
  1317.     {
  1318.         $vacancyUspCollection ArrayUtil::setKeysToSubArrayValue('id'$vacancyUspCollection);
  1319.         $vacancyUspValues ArrayUtil::getSubArrayOrEmptyArray('vacancyUspValues'$vacancy);
  1320.         $optionValues ArrayUtil::getSubArrayOrEmptyArray('optionValues'$vacancy);
  1321.         if ($vacancyUspStrategyOption instanceof Option && \count($optionValues) > && === \count($vacancyUspValues)) {
  1322.             foreach ($optionValues as $optionValue) {
  1323.                 if (($optionValue['option']['id'] ?? false) === $vacancyUspStrategyOption->getId()
  1324.                     && \is_array($optionValue['vacancyUspValues'] ?? null)
  1325.                 ) {
  1326.                     $vacancyUspValues $optionValue['vacancyUspValues'];
  1327.                     break;
  1328.                 }
  1329.             }
  1330.         }
  1331.         $output = [];
  1332.         if (=== \count($vacancyUspValues)) {
  1333.             return $output;
  1334.         }
  1335.         foreach ($vacancyUspValues as $vacancyUspValue) {
  1336.             if (!\is_array($vacancyUspValue)) {
  1337.                 continue;
  1338.             }
  1339.             $vacancyUspValueId $vacancyUspValue['id'] ?? null;
  1340.             if (!\is_string($vacancyUspValueId) && !\is_int($vacancyUspValueId)) {
  1341.                 continue;
  1342.             }
  1343.             $foundVacancyUspValue $vacancyUspCollection[$vacancyUspValueId] ?? null;
  1344.             if (!\is_array($foundVacancyUspValue)) {
  1345.                 continue;
  1346.             }
  1347.             $vacancyUsp $foundVacancyUspValue['vacancyUsp'] ?? null;
  1348.             $vacancyUspId $foundVacancyUspValue['vacancyUsp']['id'] ?? null;
  1349.             if (\is_array($vacancyUsp)
  1350.                 && (\is_int($vacancyUspId) || \is_string($vacancyUspId))
  1351.             ) {
  1352.                 if (!\array_key_exists($vacancyUspId$output)) {
  1353.                     $title $vacancyUsp['title'] ?? null;
  1354.                     $position $vacancyUsp['position'] ?? null;
  1355.                     $output[$vacancyUspId] = [
  1356.                         'title' => \is_string($title) ? $title 'Unavailable',
  1357.                         'position' => \is_string($position) ? $position 1000,
  1358.                         'values' => [],
  1359.                     ];
  1360.                 }
  1361.                 if (\array_key_exists('value'$foundVacancyUspValue)) {
  1362.                     $position $foundVacancyUspValue['position'] ?? 1000;
  1363.                     if (!\is_array($output[$vacancyUspId]['values'] ?? null)) {
  1364.                         $output[$vacancyUspId]['values'] = [];
  1365.                     }
  1366.                     while (\array_key_exists($position$output[$vacancyUspId]['values'])) {
  1367.                         ++$position;
  1368.                     }
  1369.                     $output[$vacancyUspId]['values'][$position] = $foundVacancyUspValue['value'];
  1370.                 }
  1371.             }
  1372.         }
  1373.         usort($output, fn (array $a, array $b) => $a['position'] <=> $b['position']);
  1374.         return ArrayUtil::ksortSubArraysByKeys('values'$output);
  1375.     }
  1376. }