<?php
namespace App\Service;
use App\Entity\CarryThroughData;
use App\Entity\ConversationMessage\ConversationMessage;
use App\Entity\DebuggingInfoBag;
use App\Entity\ExternalJoboffersSearch\ExternalJoboffersSearchParameters;
use App\Entity\ExternalPartner\ExternalPartner;
use App\Entity\ExternalPartner\IntegratedExternalPartnerCustomer;
use App\Entity\RecurrentJob;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinition;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition0A;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition0B;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition0C;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition0CClicks;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition1A;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition1AClicks;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition1AS;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition1ASL;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition1ASLClicks;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition1B;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition1BS;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition1BSL;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition1C;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition2A;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition2AS;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition2B;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition2BS;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition3A;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition3ASL;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition3B;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition3BSL;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition3C;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition3D;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition3E;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition4A;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition4B;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition5A;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition5B;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition5BClicks;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition6A;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition6B;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition7A;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition7B;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinition7C;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinitionA0;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinitionA1;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinitionA2;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinitionP0;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchBlockDefinitions\BlockDefinitionV0;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchParameters;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchResult;
use App\Entity\RecurrentJobsSearch\RecurrentJobsSearchResultset;
use App\Entity\RecurrentJobsSearch\VectorBasedRecurrentJobsSearchResult;
use App\Entity\Search\SearchTracing;
use App\Entity\Search\SearchTracingEvent;
use App\Entity\SearchtermEnteredEvent;
use App\Entity\User;
use App\Entity\ZipcodeCircumcircles;
use App\Exception\UnknownZipcodeException;
use App\Repository\ZipcodeCircumcirclesRepository;
use App\Utility\DateTimeUtility;
use App\Value\GeneralApplicationSettingsValue;
use App\Value\PossibleAvailabilitiesValue;
use App\Value\ZipcodeRadiusesValue;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Elastica\Query;
use Elastica\Query\BoolQuery;
use Elastica\Query\Exists;
use Elastica\Query\MatchPhrase;
use Elastica\Query\MatchQuery;
use Elastica\Query\QueryString;
use Elastica\Query\Range;
use Elastica\Search;
use Exception;
use FOS\ElasticaBundle\Finder\TransformedFinder;
use FOS\ElasticaBundle\HybridResult;
use FOS\ElasticaBundle\Persister\ObjectPersister;
use HttpException;
use JanusHercules\Shared\Presentation\Entity\PaginationData;
use JanusHercules\Zipcodes\Domain\Service\ZipcodeManagementDomainServiceInterface;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Translation\TranslatorInterface;
class RecurrentJobsSearchService implements CanTraceSearchInterface
{
public const ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM = 'occupationalFieldSearchterm';
public const ELASTICSEARCH_FIELDNAME_RELEVANTPROFESSIONS = 'relevantProfessions';
public const EXCEPTION_CODE_ENDED_UP_WITH_EMPTY_SEARCHTERM = 100;
public const SEARCH_RESULTS_DIVERSIFICATION_PARAMETER = 6;
public const SEARCH_RESULTS_ORGANIC_NAME = 'organic';
public const SEARCHTERM_ALL_JOBS = 'Alle Jobs';
private TransformedFinder $finder;
private ZipcodeCircumcirclesRepository $zipcodeCircumcirclesRepository;
private ExternalJoboffersSearchService $externalJoboffersSearchService;
private SearchTracingService $tracingService;
private ?SearchTracing $currentTracing;
private OccupationalFieldsAndProfessionsSearchService $occupationalFieldsAndProfessionsSearchService;
private TranslatorInterface $translator;
private EntityManagerInterface $entityManager;
private CircuitbreakerService $circuitbreakerService;
private ObjectPersister $recurrentJobsPersister;
private string $kernelEnvironment;
private string $elasticsearchUrl;
public function __construct(
TransformedFinder $recurrentJobsFinder,
ZipcodeCircumcirclesRepository $zipcodeCircumcirclesRepository,
ExternalJoboffersSearchService $externalJoboffersSearchService,
SearchTracingService $tracingService,
OccupationalFieldsAndProfessionsSearchService $occupationalFieldsAndProfessionsSearchService,
TranslatorInterface $translator,
EntityManagerInterface $em,
CircuitbreakerService $circuitbreakerService,
ObjectPersister $recurrentJobsPersister,
private readonly DebuggingService $debuggingService,
private readonly LoggerInterface $logger,
private readonly ZipcodeManagementDomainServiceInterface $zipcodeMgmtService,
string $kernelEnvironment,
string $elasticsearchUrl
) {
$this->finder = $recurrentJobsFinder;
$this->zipcodeCircumcirclesRepository = $zipcodeCircumcirclesRepository;
$this->externalJoboffersSearchService = $externalJoboffersSearchService;
$this->tracingService = $tracingService;
$this->occupationalFieldsAndProfessionsSearchService = $occupationalFieldsAndProfessionsSearchService;
$this->translator = $translator;
$this->entityManager = $em;
$this->circuitbreakerService = $circuitbreakerService;
$this->recurrentJobsPersister = $recurrentJobsPersister;
$this->kernelEnvironment = $kernelEnvironment;
$this->elasticsearchUrl = $elasticsearchUrl;
}
/**
* @throws UnknownZipcodeException
* @throws Exception
*/
public function getResultset(
RecurrentJobsSearchParameters $searchParameters,
int $maximumTotalNumberOfResults,
int $offset = 0,
bool $includeIsLinkedToExternalPartner = false,
bool $showAnonymousResults = false,
?User $user = null,
array $blocksToInclude = RecurrentJobsSearchResultset::BLOCKS_NAMES_IN_ORDER,
int $minimumRequiredNonExternalResults = 50,
?DateTime $minimumCreatedAt = null,
bool $searchOnlyForIntegratedExternalPartnerResults = false
): RecurrentJobsSearchResultset {
$originalFilterSearchterm = $searchParameters->getFilterSearchterm();
$tracing = $this->tracingService->createTracing($this);
$this->currentTracing = $tracing;
$event = new SearchTracingEvent(RecurrentJobsSearchService::class . '::getResultset called.');
$event->addKeyValue('$searchParameters', $searchParameters);
$event->addKeyValue('$maximumTotalNumberOfResults', $maximumTotalNumberOfResults);
$event->addKeyValue('$offset', $offset);
$event->addKeyValue('$includeIsLinkedToExternalPartner', $includeIsLinkedToExternalPartner);
$event->addKeyValue('$showAnonymousResults', $showAnonymousResults);
$tracing->addEvent($event);
$resultset = new RecurrentJobsSearchResultset($searchParameters, $tracing, $blocksToInclude);
$originalFilterZipcode = $searchParameters->getFilterZipcode();
$originalFilterZipcodeRadius = $searchParameters->getFilterZipcodeRadius();
if ($showAnonymousResults) {
$searchParameters->setFilterSearchterm('Service/Kellner');
$searchParameters->setFilterZipcodeRadius(15);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_P0, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block P0'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinitionP0(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_A0, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block A0'));
}
$specialParameters = clone $searchParameters;
$specialParameters->setFilterSearchterm('hays');
$specialParameters->setFilterZipcodeRadius(15);
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$specialParameters,
new BlockDefinitionA0(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
20,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_A1, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block A1'));
}
$specialParameters = clone $searchParameters;
$specialParameters->setFilterZipcodeRadius(15);
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$specialParameters,
new BlockDefinitionA1(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_A2, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block A2'));
}
if ($resultset->getTotalNumberOfResults() < $minimumRequiredNonExternalResults) {
$searchParameters->setFilterZipcodeRadius(30);
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinitionA2(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_0A, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 0A'));
}
if (mb_strpos($searchParameters->getFilterSearchterm(), ' ') !== false) {
$block0aParameters = clone $searchParameters;
$block0aParameters->setFilterSearchterm(mb_ereg_replace(' ', '', mb_ereg_replace('[^A-Za-zäöüÄÖÜß0-9\_ ]', '', $searchParameters->getFilterSearchterm())));
if ($resultset->getTotalNumberOfResults() < $minimumRequiredNonExternalResults) {
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$block0aParameters,
new BlockDefinition0A(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_0B, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 0B'));
}
if (mb_strpos($searchParameters->getFilterSearchterm(), ' ') !== false) {
$block0bParameters = clone $searchParameters;
$occurences = 0;
foreach (explode(' ', $searchParameters->getFilterSearchterm()) as $searchterm) {
if (in_array(mb_strtolower($searchterm), OccupationalFieldsAndProfessionsSearchService::STOPWORDS)) {
++$occurences;
}
}
if (str_word_count($searchParameters->getFilterSearchterm(), 0, 'äöüßÄÖÜ') - $occurences > 1) {
$block0bParameters->setFilterSearchterm(str_replace(' ', ' ANDKEYWORD ', mb_ereg_replace('[^A-Za-zäöüÄÖÜß0-9\_ ]', '', $searchParameters->getFilterSearchterm($searchParameters->getFilterSearchterm()))));
if ($resultset->getTotalNumberOfResults() < $minimumRequiredNonExternalResults) {
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$block0bParameters,
new BlockDefinition0B(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
}
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_0C, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 0C'));
}
if (mb_strpos($searchParameters->getFilterSearchterm(), ' ') !== false) {
$block0cParameters = clone $searchParameters;
$occurences = 0;
foreach (explode(' ', $searchParameters->getFilterSearchterm()) as $searchterm) {
if (in_array(mb_strtolower($searchterm), OccupationalFieldsAndProfessionsSearchService::STOPWORDS)) {
++$occurences;
}
}
if (str_word_count($searchParameters->getFilterSearchterm(), 0, 'äöüßÄÖÜ') - $occurences > 1) {
$block0cParameters->setFilterSearchterm(str_replace(' ', ' ANDKEYWORD ', mb_ereg_replace('[^A-Za-zäöüÄÖÜß0-9\_ ]', '', $searchParameters->getFilterSearchterm())));
if ($resultset->getTotalNumberOfResults() < $minimumRequiredNonExternalResults) {
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$block0cParameters,
new BlockDefinition0C(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
} else {
$this->currentTracing->addEvent(
new SearchTracingEvent('Skipping block 0C Clicks because we already have enough non-external results.')
);
}
} else {
$this->currentTracing->addEvent(
new SearchTracingEvent('Skipping block 0C Clicks because search term is only one word long.')
);
}
} else {
$this->currentTracing->addEvent(
new SearchTracingEvent('Skipping block 0C Clicks because search term does not contain a space.')
);
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_0C_CLICKS, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 0C Clicks'));
}
if (mb_strpos($searchParameters->getFilterSearchterm(), ' ') !== false) {
$block0cClicksParameters = clone $searchParameters;
$occurences = 0;
foreach (explode(' ', $searchParameters->getFilterSearchterm()) as $searchterm) {
if (in_array(mb_strtolower($searchterm), OccupationalFieldsAndProfessionsSearchService::STOPWORDS)) {
++$occurences;
}
}
if (str_word_count($searchParameters->getFilterSearchterm(), 0, 'äöüßÄÖÜ') - $occurences > 1) {
$block0cClicksParameters->setFilterSearchterm(str_replace(' ', ' ANDKEYWORD ', mb_ereg_replace('[^A-Za-zäöüÄÖÜß0-9\_ ]', '', $searchParameters->getFilterSearchterm())));
if ($resultset->getTotalNumberOfResults() < $minimumRequiredNonExternalResults) {
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$block0cClicksParameters,
new BlockDefinition0CClicks(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
} else {
$this->currentTracing->addEvent(
new SearchTracingEvent('Skipping block 0C Clicks because we already have enough non-external results.')
);
}
} else {
$this->currentTracing->addEvent(
new SearchTracingEvent('Skipping block 0C Clicks because search term is only one word long.')
);
}
} else {
$this->currentTracing->addEvent(
new SearchTracingEvent('Skipping block 0C Clicks because search term does not contain a space.')
);
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_1A, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 1A'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition1A(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_1A_CLICKS, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 1A Clicks'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition1AClicks(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_1A_S, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 1A_S'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition1AS(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_1A_SL, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 1A_SL'));
}
$numbersOfCharactersThatNeedToMatch = [];
$numberOfCharactersInSearchterm = mb_strlen(OccupationalFieldsAndProfessionsSearchService::normalizeSearchterm($searchParameters->getFilterSearchterm()));
for ($i = min(15, $numberOfCharactersInSearchterm - 1); $i > 5; --$i) {
$numbersOfCharactersThatNeedToMatch[] = $i;
}
if (sizeof($numbersOfCharactersThatNeedToMatch) === 0) {
$this->currentTracing->addEvent(new SearchTracingEvent('Skipping block 1A_SL because $numbersOfCharactersThatNeedToMatch is zero.'));
} else {
foreach ($numbersOfCharactersThatNeedToMatch as $numberOfCharactersThatNeedToMatch) {
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition1ASL(),
$numberOfCharactersThatNeedToMatch,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_1A_SL_CLICKS, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 1A_SL Clicks'));
}
$numbersOfCharactersThatNeedToMatch = [];
$numberOfCharactersInSearchterm = mb_strlen(OccupationalFieldsAndProfessionsSearchService::normalizeSearchterm($searchParameters->getFilterSearchterm()));
for ($i = min(15, $numberOfCharactersInSearchterm - 1); $i > 5; --$i) {
$numbersOfCharactersThatNeedToMatch[] = $i;
}
if (sizeof($numbersOfCharactersThatNeedToMatch) === 0) {
$this->currentTracing->addEvent(new SearchTracingEvent('Skipping block 1A_SL Clicks because $numbersOfCharactersThatNeedToMatch is zero.'));
} else {
foreach ($numbersOfCharactersThatNeedToMatch as $numberOfCharactersThatNeedToMatch) {
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition1ASLClicks(),
$numberOfCharactersThatNeedToMatch,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_1B, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 1B'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition1B(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_1B_S, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 1B_S'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition1BS(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_1B_SL, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 1B_SL'));
}
$numbersOfCharactersThatNeedToMatch = [];
$numberOfCharactersInSearchterm = mb_strlen(OccupationalFieldsAndProfessionsSearchService::normalizeSearchterm($searchParameters->getFilterSearchterm()));
for ($i = min(15, $numberOfCharactersInSearchterm - 1); $i > 5; --$i) {
$numbersOfCharactersThatNeedToMatch[] = $i;
}
foreach ($numbersOfCharactersThatNeedToMatch as $numberOfCharactersThatNeedToMatch) {
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition1BSL(),
$numberOfCharactersThatNeedToMatch,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_1C, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 1C'));
$event = new SearchTracingEvent('Added 1C Joboffers from Xing because we had less than ' . $minimumRequiredNonExternalResults . ' of our own results in blocks 1a + 1b.');
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition1C(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_2A, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 2A'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition2A(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_2A_S, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 2A_S'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition2AS(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_2B, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 2B'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition2B(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_2B_S, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 2B_S'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition2BS(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_3A, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 3A'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition3A(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_3A_SL, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 3A_SL'));
}
$numbersOfCharactersThatNeedToMatch = [];
$numberOfCharactersInSearchterm = mb_strlen(OccupationalFieldsAndProfessionsSearchService::normalizeSearchterm($searchParameters->getFilterSearchterm()));
for ($i = min(15, $numberOfCharactersInSearchterm - 1); $i > 5; --$i) {
$numbersOfCharactersThatNeedToMatch[] = $i;
}
if (sizeof($numbersOfCharactersThatNeedToMatch) > 0) {
foreach ($numbersOfCharactersThatNeedToMatch as $numberOfCharactersThatNeedToMatch) {
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition3ASL(),
$numberOfCharactersThatNeedToMatch,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
} else {
$this->currentTracing->addEvent(
new SearchTracingEvent(
'Skipping block 3A_SL Clicks because normalized search term "' . OccupationalFieldsAndProfessionsSearchService::normalizeSearchterm($searchParameters->getFilterSearchterm()) . '" is too short.'
)
);
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_3B, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 3B'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition3B(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_3B_SL, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 3B_SL'));
}
$numbersOfCharactersThatNeedToMatch = [];
$numberOfCharactersInSearchterm = mb_strlen(OccupationalFieldsAndProfessionsSearchService::normalizeSearchterm($searchParameters->getFilterSearchterm()));
for ($i = min(15, $numberOfCharactersInSearchterm - 1); $i > 5; --$i) {
$numbersOfCharactersThatNeedToMatch[] = $i;
}
foreach ($numbersOfCharactersThatNeedToMatch as $numberOfCharactersThatNeedToMatch) {
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition3BSL(),
$numberOfCharactersThatNeedToMatch,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_3C, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 3C'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition3C(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_3D, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 3D'));
}
$originalFilterZipcodeRadius = $searchParameters->getFilterZipcodeRadius();
if ($searchParameters->getFilterZipcodeRadius() < ZipcodeRadiusesValue::MAXIMUM) {
$searchParameters->setFilterZipcodeRadius(
ZipcodeRadiusesValue::ALL[ZipcodeRadiusesValue::getKeyForRadius($searchParameters->getFilterZipcodeRadius()) + 1]
);
}
$externalJoboffersSearchResultset = $this->externalJoboffersSearchService->getResultset(
new ExternalJoboffersSearchParameters($searchParameters),
50,
[],
0,
false,
$resultset->getContainedEntityIds(),
new BlockDefinition3D(),
$this->currentTracing
);
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent("ExternalJoboffersSearchResultset has {$externalJoboffersSearchResultset->getTotalNumberOfResults()} results."));
}
$resultset->addResultsFromExternalJoboffersSearchResultset(
$externalJoboffersSearchResultset,
new BlockDefinition3D()
);
$numberOfResultsInBlock = 0;
if ($externalJoboffersSearchResultset->getTotalNumberOfResults() > 0) {
$numberOfResultsInBlock = sizeof($resultset->getResultsByBlock()[RecurrentJobsSearchResultset::BLOCK_3D]);
}
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent("Resultset now has {$resultset->getTotalNumberOfResults()} total results, and $numberOfResultsInBlock in block 3D."));
}
$searchParameters->setFilterZipcodeRadius($originalFilterZipcodeRadius);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_3E, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 3E'));
}
if ($searchParameters->getFilterZipcodeRadius() < ZipcodeRadiusesValue::MAXIMUM) {
$originalFilterZipcodeRadius = $searchParameters->getFilterZipcodeRadius();
$searchParameters->setFilterZipcodeRadius(
ZipcodeRadiusesValue::ALL[ZipcodeRadiusesValue::getKeyForRadius($searchParameters->getFilterZipcodeRadius()) + 1]
);
$externalJoboffersSearchResultset = $this->externalJoboffersSearchService->getResultset(
new ExternalJoboffersSearchParameters($searchParameters),
50,
[],
0,
false,
$resultset->getContainedEntityIds(),
new BlockDefinition3E(),
$this->currentTracing
);
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent("ExternalJoboffersSearchResultset has {$externalJoboffersSearchResultset->getTotalNumberOfResults()} results."));
}
$resultset->addResultsFromExternalJoboffersSearchResultset(
$externalJoboffersSearchResultset,
new BlockDefinition3E()
);
$numberOfResultsInBlock = 0;
if ($externalJoboffersSearchResultset->getTotalNumberOfResults() > 0) {
$numberOfResultsInBlock = sizeof($resultset->getResultsByBlock()[RecurrentJobsSearchResultset::BLOCK_3E]);
}
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent("Resultset now has {$resultset->getTotalNumberOfResults()} total results, and $numberOfResultsInBlock in block 3E."));
}
$searchParameters->setFilterZipcodeRadius($originalFilterZipcodeRadius);
} else {
$this->currentTracing->addEvent(new SearchTracingEvent("Not using block 3E because zipcode radius {$searchParameters->getFilterZipcodeRadius()} is not smaller than " . ZipcodeRadiusesValue::MAXIMUM . '.'));
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_4A, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 4A'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition4A(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_4B, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 4B'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition4B(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_5A, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 5A'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition5A(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_5B_CLICKS, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 5B Clicks'));
}
if ($resultset->getTotalNumberOfResults() < 250) {
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition5BClicks(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
} else {
$this->currentTracing->addEvent(
new SearchTracingEvent('Skipping block 5B Clicks because we already have 250 results.')
);
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_5B, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 5B'));
}
if ($resultset->getTotalNumberOfResults() < 250) {
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition5B(),
0,
self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
} else {
$this->currentTracing->addEvent(
new SearchTracingEvent('Skipping block 5B because we already have 250 results.')
);
}
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_6A, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 6A'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition6A(),
0,
self::ELASTICSEARCH_FIELDNAME_RELEVANTPROFESSIONS,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_6B, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 6B'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition6B(),
0,
self::ELASTICSEARCH_FIELDNAME_RELEVANTPROFESSIONS,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_7A, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 7A'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition7A(),
0,
self::ELASTICSEARCH_FIELDNAME_RELEVANTPROFESSIONS,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_7B, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 7B'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition7B(),
0,
self::ELASTICSEARCH_FIELDNAME_RELEVANTPROFESSIONS,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_7C, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block 7C'));
}
$numberOfResultsAddedInBlock = $this->addResults(
$resultset,
$searchParameters,
new BlockDefinition7C(),
0,
self::ELASTICSEARCH_FIELDNAME_RELEVANTPROFESSIONS,
$offset,
$maximumTotalNumberOfResults,
$user,
$minimumCreatedAt,
$searchOnlyForIntegratedExternalPartnerResults
);
}
if (in_array(RecurrentJobsSearchResultset::BLOCK_V0, $blocksToInclude)) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Considering block V0'));
}
$queryString = '?occupationalFieldSearchterm=' . urlencode($searchParameters->getFilterSearchterm());
$filterZipcode = $searchParameters->getFilterZipcode();
$filterZipcodeRadius = $searchParameters->getFilterZipcodeRadius();
$filterZipcodeRadiusMethodName = "getDistance$filterZipcodeRadius";
$zipcodeCircumcircles = $this->getZipcodeCircumcirclesForZipcode($filterZipcode);
$distance = $zipcodeCircumcircles->$filterZipcodeRadiusMethodName();
$distance = mb_substr($distance, 1);
$distance = mb_substr($distance, 0, -1);
$zipcodes = explode(', ', $distance);
foreach ($zipcodes as $zipcode) {
$queryString .= '&zipcodes[]=' . urlencode($zipcode);
}
$responseBody = file_get_contents(
'http://127.0.0.1:8001/api/recurrent-jobs-search/results/' . $queryString
);
$responseBodyAsArray = json_decode($responseBody, true);
$vectorBasedSearchResults = [];
foreach ($responseBodyAsArray as $responseBodyAsArrayEntry) {
$recurrentJob = $this->entityManager->find(
RecurrentJob::class,
$responseBodyAsArrayEntry['recurrentJob']['id']
);
if (is_null($recurrentJob) || !$recurrentJob->isActive()) {
continue;
}
if ((float)$responseBodyAsArrayEntry['score'] > 0.15) {
// continue;
}
$vectorBasedSearchResults[] = new VectorBasedRecurrentJobsSearchResult(
$recurrentJob,
$responseBodyAsArrayEntry['score']
);
}
$resultset->addResultsFromVectorBasedSearchResultsArray(
$vectorBasedSearchResults,
new BlockDefinitionV0()
);
}
if ($showAnonymousResults) {
$searchParameters->setFilterSearchterm($originalFilterSearchterm);
$searchParameters->setFilterZipcode($originalFilterZipcode);
$searchParameters->setFilterZipcodeRadius($originalFilterZipcodeRadius);
}
$event = new SearchTracingEvent('Returning resultset');
$event->addKeyValue('$resultset', $resultset);
if (!$searchParameters->getFilterAlreadyContacted()) {
if (!is_null($user) && !is_null($user->getDefaultJobseekerProfile())) {
$this->filterAlreadyContacted($resultset, $user->getDefaultJobseekerProfile()->getId());
}
}
return $resultset;
}
public static function traineeCareerLevelWasExplicitlyChosen(
RecurrentJobsSearchParameters $searchParameters
): bool {
$requiredCareerLevels = $searchParameters->getFilterRequiredCareerLevels();
return in_array(0, $requiredCareerLevels)
&& sizeof($requiredCareerLevels) !== 4;
}
/**
* @return int Number of results added
*
* @throws Exception
*/
private function addResults(
RecurrentJobsSearchResultset $resultset,
RecurrentJobsSearchParameters $searchParameters,
RecurrentJobsSearchBlockDefinition $blockDefinition,
int $numberOfCharactersToUseForGettingListOfPartialSearchtermsInSearchterm,
string $fieldNameToMatchSearchtermTo,
int $offset,
int $maximumTotalNumberOfResults,
?User $user = null,
?DateTime $minimumCreatedAt = null,
bool $searchOnlyForIntegratedExternalPartnerResults = false
): int {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent("Assembling query for block '{$blockDefinition->getName()}'"));
}
if ($blockDefinition->isUseProfessionsWithShorterTitleMatchingSearchterm() === true) {
$professionsWithShorterTitleMatchingSearchterm = $this->occupationalFieldsAndProfessionsSearchService->getProfessionsWithShorterTitleMatchingSearchterm(
$searchParameters->getFilterSearchterm()
);
if (sizeof($professionsWithShorterTitleMatchingSearchterm) === 0) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Could not find professions with shorter title that matches the searchterm.'));
}
return 0;
} else {
if (!is_null($this->currentTracing)) {
$professionsWithShorterTitleMatchingSearchtermTitles = '';
foreach ($professionsWithShorterTitleMatchingSearchterm as $profession) {
$professionsWithShorterTitleMatchingSearchtermTitles .= " '{$profession->getTitle()}', ";
}
$this->currentTracing->addEvent(new SearchTracingEvent("Going to search with the following title(s) of profession(s) with shorter title that match the searchterm: {$professionsWithShorterTitleMatchingSearchtermTitles}"));
}
}
}
$zipcodeRadius = $searchParameters->getFilterZipcodeRadius();
if ($blockDefinition->isExtendZipcodeRadius()) {
$zipcodeRadius = ($zipcodeRadius === ZipcodeRadiusesValue::MAXIMUM)
? $zipcodeRadius
: ZipcodeRadiusesValue::ALL[ZipcodeRadiusesValue::getKeyForRadius($zipcodeRadius) + 1];
}
$zipcodeCircumcirclesForDistance = $this->getZipcodesForDistance(
$this->getZipcodeCircumcirclesForZipcode($searchParameters->getFilterZipcode()),
$zipcodeRadius
);
if (!is_null($minimumCreatedAt)) {
$createdAtTimeblock = [
'gte' => $minimumCreatedAt->format(DateTimeUtility::FORMAT_ELASTICSEARCH)
];
} else {
$createdAtTimeblock = null;
}
try {
$boolQuery = $this->assembleElasticsearchQuery(
$fieldNameToMatchSearchtermTo,
$blockDefinition,
$numberOfCharactersToUseForGettingListOfPartialSearchtermsInSearchterm,
[
'condition' => 'addMust',
'employmentType' => 'addShould',
'careerLevel' => 'addShould'
],
$zipcodeCircumcirclesForDistance,
$searchParameters,
$user,
$createdAtTimeblock,
$searchOnlyForIntegratedExternalPartnerResults
);
} catch (Exception $e) {
if ($e->getCode() === self::EXCEPTION_CODE_ENDED_UP_WITH_EMPTY_SEARCHTERM) {
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent(
'Not starting query to ES because we ran into exception EXCEPTION_CODE_ENDED_UP_WITH_EMPTY_SEARCHTERM.'
));
}
return 0;
}
}
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('Start filtering out contained ids from query.'));
}
foreach ($resultset->getContainedEntityIds() as $idToSkip) {
$idsToSkipQuery = new MatchQuery();
$idsToSkipQuery->setFieldQuery('_id', $idToSkip);
$boolQuery->addMustNot($idsToSkipQuery);
}
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('End filtering out contained ids from query.'));
}
// This is not really a functional part of the query, but allows us to add a comment which helps with debugging
$commentQuery = new MatchQuery();
$params = json_encode($searchParameters->asArray());
$commentQuery->setFieldQuery('_comment', "Query for block {$blockDefinition->getName()}. searchParameters: {$params}");
$boolQuery->addMustNot($commentQuery);
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(
new SearchTracingEvent(
"Start querying ES, cURL command is: curl -XGET '{$this->elasticsearchUrl}recurrent_jobs/_search?search_type=" . Search::OPTION_SEARCH_TYPE_DFS_QUERY_THEN_FETCH . "' -H 'Content-Type: application/json' -d '"
. json_encode(['size' => $maximumTotalNumberOfResults, 'query' => $boolQuery->toArray()])
. "'"
)
);
}
$q = new Query($boolQuery);
if ($blockDefinition->getName() === RecurrentJobsSearchResultset::BLOCK_A1) {
$q->setSort(['lastseenAt' => ['order' => 'desc']])->setMinScore(1);
}
$hybridResults = $this->finder->findHybrid(
$q,
$maximumTotalNumberOfResults,
[Search::OPTION_SEARCH_TYPE => Search::OPTION_SEARCH_TYPE_DFS_QUERY_THEN_FETCH]
);
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent('End querying ES.'));
}
if (!is_null($this->currentTracing)) {
$numberOfHybridResults = sizeof($hybridResults);
$ids = [];
/** @var HybridResult $hybridResult */
foreach ($hybridResults as $hybridResult) {
$ids[] = $hybridResult->getResult()->getId();
}
$this->currentTracing->addEvent(new SearchTracingEvent("Got {$numberOfHybridResults} results: " . implode(', ', $ids)));
}
$numberOfResultsAdded = $resultset->addResultsFromRecurrentJobHybridResults($hybridResults, $blockDefinition);
$resultset->sliceResults($offset, $maximumTotalNumberOfResults);
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(new SearchTracingEvent(
"Added {$numberOfResultsAdded} to resultset after deduplication, now at {$resultset->getTotalNumberOfResults()} results in total."
));
}
return $numberOfResultsAdded;
}
/** @throws Exception */
public function getZipcodeCircumcirclesForZipcode(string $zipcode): ZipcodeCircumcircles
{
/** @var ZipcodeCircumcircles $zipcodeCircumcircles */
$zipcodeCircumcircles = $this->zipcodeCircumcirclesRepository->findOneBy([
'zipcode' => $zipcode,
'countrycode' => 'de'
]);
if (is_null($zipcodeCircumcircles)) {
$this->logger->warning("ZipcodeCircumcircles for zipcode '$zipcode' not found. Initiating creation via clone.");
$this->zipcodeMgmtService->initiateCreationOfMissingZipcodeViaClone($zipcode);
throw new UnknownZipcodeException('Unknown zip code ' . $zipcode);
}
return $zipcodeCircumcircles;
}
public function getZipcodesForDistance(
ZipcodeCircumcircles $zipcodeCircumcircles,
int $distance,
$previousZipcodeCircumcirclesForDistance = null
): string {
$distanceMethodName = 'getDistance' . $distance;
$zipcodeCircumcirclesForDistance = str_replace(',', ' ', $zipcodeCircumcircles->$distanceMethodName());
return $this->optimizeZipcodeDistance($zipcodeCircumcirclesForDistance, $previousZipcodeCircumcirclesForDistance);
}
protected function optimizeZipcodeDistance(
string $zipcodeCircumcirclesForDistance,
?string $previousZipcodeCircumcirclesForDistance = null
): string {
if (!empty($previousZipcodeCircumcirclesForDistance)) {
$zipcodeCircumcirclesForDistance = explode(' ', str_replace(['(', ')', '"'], ['', '', ''], $zipcodeCircumcirclesForDistance));
$previousZipcodeCircumcirclesForDistance = explode(' ', str_replace(['(', ')', '"'], ['', '', ''], $previousZipcodeCircumcirclesForDistance));
$diffZipcodes = array_diff($zipcodeCircumcirclesForDistance, $previousZipcodeCircumcirclesForDistance);
return '(' . implode(' ', $diffZipcodes) . ' -1)';
}
return $zipcodeCircumcirclesForDistance;
}
/** @throws Exception */
private function assembleElasticsearchQuery(
string $fieldNameToMatchSearchtermTo,
RecurrentJobsSearchBlockDefinition $blockDefinition,
int $numberOfCharactersToUseForGettingListOfPartialSearchtermsInSearchterm,
array $employmentTypeAndCareerLevelMatchingRules,
string $zipcodeCircumcirclesForDistance,
RecurrentJobsSearchParameters $searchParams,
?User $user = null,
?array $createdAtTimeblock = null,
bool $onlyIncludeIntegratedExternalPartnerCustomerJobs = false
): BoolQuery {
$searchQuery = new BoolQuery();
$this->filterRequiredEmploymentTypesAndCareerLevels($searchParams, $searchQuery, $employmentTypeAndCareerLevelMatchingRules);
$lastSeenRange = [
'gte' => "now-{$blockDefinition->getLastseenAtAfterDays()}d",
'lt' => "now-{$blockDefinition->getLastseenAtBeforeDays()}d"
];
$this->filterLastSeen($lastSeenRange, $searchQuery);
$this->filterCreatedAt($createdAtTimeblock, $searchQuery);
$this->filterZipcode($zipcodeCircumcirclesForDistance, $searchQuery);
$this->filterRequiredTime($searchParams, $searchQuery);
$this->filterSearchterm(
$fieldNameToMatchSearchtermTo,
$blockDefinition->isUseWildcardsForSearchtermAtBeginningAndEnd(),
$blockDefinition->isUseWildcardsForSearchtermAtBeginning(),
$blockDefinition->isUseWildcardsForSearchtermAtEnd(),
$blockDefinition->isUseProfessionsWithShorterTitleMatchingSearchterm(),
$blockDefinition->isUseListOfPartialSearchtermsWithGivenNumberOfCharactersContainedInSearchterm(),
$blockDefinition->isUseSynonyms(),
$numberOfCharactersToUseForGettingListOfPartialSearchtermsInSearchterm,
$searchParams,
$searchQuery,
$user
);
$this->filterRequiredExperience($searchParams, $searchQuery);
$this->filterLocked($searchQuery);
$this->filterPaused($searchQuery);
if ($blockDefinition->getName() === RecurrentJobsSearchResultset::BLOCK_P0) {
$this->filterSuperiorSalary($searchQuery);
}
$includeOnlyClickBasedBillingRecurrentJobsQuery = new MatchPhrase();
$includeOnlyClickBasedBillingRecurrentJobsQuery->setFieldQuery('hasClickBasedBilling', 'true');
if ($blockDefinition->getIncludeOnlyClickBasedBillingRecurrentJobs()) {
$searchQuery->addMust($includeOnlyClickBasedBillingRecurrentJobsQuery);
} else {
if (!$blockDefinition->getIncludeClickBasedBillingRecurrentJobs()) {
$searchQuery->addMustNot($includeOnlyClickBasedBillingRecurrentJobsQuery);
}
$this->filterCustomer(
$searchQuery,
$blockDefinition->isIncludeXingResults(),
$blockDefinition->isIncludeHaysResults(),
$searchParams
);
}
if ($onlyIncludeIntegratedExternalPartnerCustomerJobs) {
$includeOnlyIntegratedExternalPartnerJobsQuery = new Exists('integratedExternalPartnerCustomerId');
$searchQuery->addMust($includeOnlyIntegratedExternalPartnerJobsQuery);
}
return $searchQuery;
}
/** @throws Exception */
private function filterSearchterm(
string $fieldNameToMatchSearchtermTo,
bool $useWildcardsForSearchtermAtBeginningAndEnd,
bool $useWildcardsForSearchtermAtBeginning,
bool $useWildcardsForSearchtermAtEnd,
bool $useProfessionsWithShorterTitleMatchingSearchterm,
bool $useListOfPartialSearchtermsWithGivenNumberOfCharactersContainedInSearchterm,
bool $useSynonyms,
int $numberOfCharactersToUseForGettingListOfPartialSearchtermsInSearchterm,
RecurrentJobsSearchParameters $searchParams,
BoolQuery $searchQuery,
?User $user = null
): void {
if ($fieldNameToMatchSearchtermTo !== self::ELASTICSEARCH_FIELDNAME_OCCUPATIONAL_FIELD_SEARCHTERM
&& $fieldNameToMatchSearchtermTo !== self::ELASTICSEARCH_FIELDNAME_RELEVANTPROFESSIONS
) {
throw new Exception("Unknown value '{$fieldNameToMatchSearchtermTo}' for fieldNameToMatchSearchtermTo.");
}
if ($useProfessionsWithShorterTitleMatchingSearchterm) {
$professionsWithShorterTitleMatchingSearchterm = $this->occupationalFieldsAndProfessionsSearchService->getProfessionsWithShorterTitleMatchingSearchterm(
$searchParams->getFilterSearchterm()
);
$normalizedSearchterm = '';
foreach ($professionsWithShorterTitleMatchingSearchterm as $professionWithShorterTitleMatchingSearchterm) {
$normalizedSearchterm .= ' ' . $professionWithShorterTitleMatchingSearchterm->getTitleForFulltextsearchVariantC();
}
} elseif ($useListOfPartialSearchtermsWithGivenNumberOfCharactersContainedInSearchterm) {
$listOfPartialSearchtermsWithGivenNumberOfCharactersContainedInSearchterm = $this->occupationalFieldsAndProfessionsSearchService->getListOfPartialSearchtermsWithGivenNumberOfCharactersContainedInSearchtermWords(
$searchParams->getFilterSearchterm(),
$numberOfCharactersToUseForGettingListOfPartialSearchtermsInSearchterm,
$this->currentTracing
);
$normalizedSearchterm = '';
foreach ($listOfPartialSearchtermsWithGivenNumberOfCharactersContainedInSearchterm as $partialSearchterm) {
$normalizedSearchterm .= " {$partialSearchterm}";
}
} else {
$normalizedSearchterm = OccupationalFieldsAndProfessionsSearchService::normalizeSearchterm($searchParams->getFilterSearchterm());
}
$normalizedSearchterm = trim($normalizedSearchterm);
if ($normalizedSearchterm !== '') {
$queryStringQuery = $this->occupationalFieldsAndProfessionsSearchService->addWildcardsAndSynonyms(
$normalizedSearchterm,
$useListOfPartialSearchtermsWithGivenNumberOfCharactersContainedInSearchterm,
$useWildcardsForSearchtermAtBeginningAndEnd,
$useWildcardsForSearchtermAtBeginning,
$useWildcardsForSearchtermAtEnd,
$useSynonyms,
$this->currentTracing
);
if ($queryStringQuery !== '') {
$queryStringQuery = $this->occupationalFieldsAndProfessionsSearchService::normalizeToMatchElasticaSyntax($queryStringQuery);
}
if ($queryStringQuery === '') {
throw new Exception('We ended up with an empty queryStringQuery.', self::EXCEPTION_CODE_ENDED_UP_WITH_EMPTY_SEARCHTERM);
}
if (!is_null($this->currentTracing)) {
$this->currentTracing->addEvent(
new SearchTracingEvent("Final query for field {$fieldNameToMatchSearchtermTo} is '{$queryStringQuery}'.")
);
}
$queryString = new QueryString();
$queryString->setFields([$fieldNameToMatchSearchtermTo]);
$queryString->setParam('rewrite', 'scoring_boolean');
$queryType = 'addMust';
preg_match('/minijobs|teilzeitjobs|alle jobs/', $normalizedSearchterm, $matches);
if ($matches) {
$queryStringQuery = 'NOT hays';
$queryType = 'addMust';
}
$queryString->setQuery($queryStringQuery);
$boolQuery = new BoolQuery();
$boolQuery->$queryType($queryString);
$searchQuery->$queryType($boolQuery);
} else {
throw new Exception('We ended up with an empty searchterm.', self::EXCEPTION_CODE_ENDED_UP_WITH_EMPTY_SEARCHTERM);
}
}
private function filterRequiredExperience(RecurrentJobsSearchParameters $searchParams, BoolQuery $searchQuery): void
{
$experienceQuery = new Range();
$experienceQuery->addField('experience', ['gte' => $searchParams->getFilterRequiredExperience()]);
$searchQuery->addShould($experienceQuery);
}
private function filterRequiredEmploymentTypes(
RecurrentJobsSearchParameters $searchParams,
BoolQuery $searchQuery,
$matchCondition = null
): BoolQuery {
// There is large number of "legacy" recurrent jobs from pre-EmploymentTypes times,
// and their owners never chose an employment type for these jobs.
// For these, we added type 4, undefined/keine Angabe.
// However, we do not provide the means to choose "keine Angabe" in the
// UI when searching - therefore, we need to make sure that this special
// employment type is always part of the search automatically, or else,
// noone would be able to find these "special" recurrent jobs.
$employmentTypes = $searchParams->getFilterRequiredEmploymentTypes();
if (!in_array(RecurrentJob::EMPLOYMENT_TYPE_UNDEFINED, $employmentTypes)) {
$employmentTypes[] = RecurrentJob::EMPLOYMENT_TYPE_UNDEFINED;
}
foreach ($employmentTypes as $employmentType) {
$query = new MatchQuery();
$query->setFieldQuery('employmentTypes', (string)$employmentType);
if (!is_null($matchCondition) && in_array($matchCondition, ['addShould', 'addMust', 'addMustNot'])) {
$searchQuery->$matchCondition($query);
} elseif ($searchParams->getAtLeastOneRequiredEmploymentTypeMustMatch() === true) {
$searchQuery->addMust($query);
} else {
$searchQuery->addShould($query);
}
}
return $searchQuery;
}
private function filterRequiredCareerLevels(
RecurrentJobsSearchParameters $searchParams,
BoolQuery $searchQuery,
$matchCondition = null
): BoolQuery {
foreach ($searchParams->getFilterRequiredCareerLevels() as $careerLevel) {
$query = new MatchQuery();
$query->setFieldQuery('careerLevels', (string)$careerLevel);
if (!is_null($matchCondition) && in_array($matchCondition, ['addShould', 'addMust', 'addMustNot'])) {
$searchQuery->$matchCondition($query);
} elseif ($searchParams->getAtLeastOneRequiredCareerLevelMustMatch() === true) {
$searchQuery->addMust($query);
} else {
$searchQuery->addShould($query);
}
}
return $searchQuery;
}
private function filterRequiredEmploymentTypesAndCareerLevels(
RecurrentJobsSearchParameters $searchParams,
BoolQuery $searchQuery,
array $employmentTypeAndCareerLevelMatchingRules = []
): void {
if (sizeof($employmentTypeAndCareerLevelMatchingRules) > 0) {
$employmentTypeBoolQuery = $this->filterRequiredEmploymentTypes($searchParams, new BoolQuery(), $employmentTypeAndCareerLevelMatchingRules['employmentType']);
$careerLevelBoolQuery = $this->filterRequiredCareerLevels($searchParams, new BoolQuery(), $employmentTypeAndCareerLevelMatchingRules['careerLevel']);
$condition = $employmentTypeAndCareerLevelMatchingRules['condition'];
$employmentTypeWithCareerLevelBoolQuery = new BoolQuery();
$employmentTypeWithCareerLevelBoolQuery->$condition($employmentTypeBoolQuery);
$employmentTypeWithCareerLevelBoolQuery->$condition($careerLevelBoolQuery);
$searchQuery->addMust($employmentTypeWithCareerLevelBoolQuery);
}
}
private function filterReviews(BoolQuery $searchQuery): void
{
$reviewsQuery = new MatchQuery();
$reviewsQuery->setFieldQuery('hasNegativeReviews', 'false');
$reviewsQuery->setFieldBoost('hasNegativeReviews', 2500000000.0);
$searchQuery->addShould($reviewsQuery);
}
private function filterLocked(BoolQuery $searchQuery): void
{
$lockedQuery = new MatchQuery();
$lockedQuery->setFieldQuery('isLocked', 'true');
$searchQuery->addMustNot($lockedQuery);
}
private function filterSuperiorSalary(BoolQuery $searchQuery): void
{
$superiorSalaryQuery = new MatchQuery();
$superiorSalaryQuery->setFieldQuery('superiorSalary', 'true');
$searchQuery->addMust($superiorSalaryQuery);
}
private function filterPaused(BoolQuery $searchQuery): void
{
$searchQuery->addMustNot(new Exists('pausedSince'));
}
private function filterIncludeIsLinkedToExternalPartner(BoolQuery $searchQuery, bool $includeIsLinkedToExternalPartner): void
{
if (!$includeIsLinkedToExternalPartner) {
$lockedQuery = new MatchQuery();
$lockedQuery->setFieldQuery('isLinkedToExternalPartner', 'true');
$searchQuery->addMustNot($lockedQuery);
}
}
private function filterLastSeen(array $lastseenAtTimeblock, BoolQuery $searchQuery): void
{
$lastseenAtQuery = new Range();
$lastseenAtQuery->addField('lastseenAt', $lastseenAtTimeblock);
$searchQuery->addMust($lastseenAtQuery);
}
private function filterCreatedAt(?array $createdAtTimeblock, BoolQuery $searchQuery): void
{
if (!is_null($createdAtTimeblock)) {
$existsQuery = new Exists('createdAt');
$createdAtQuery = new Range();
$createdAtQuery->addField('createdAt', $createdAtTimeblock);
$searchQuery->addMust($existsQuery);
$searchQuery->addMust($createdAtQuery);
}
}
private function filterZipcode(string $zipcodeCircumcirclesForDistance, BoolQuery $searchQuery): void
{
$query = new BoolQuery();
$zipcodesQuery = new MatchQuery();
$zipcodesQuery->setFieldQuery('zipcode', $zipcodeCircumcirclesForDistance);
$query->addShould($zipcodesQuery);
$additionalZipcodesQuery = new MatchQuery();
$additionalZipcodesQuery->setFieldQuery('additionalZipcodes', $zipcodeCircumcirclesForDistance);
$query->addShould($additionalZipcodesQuery);
$searchQuery->addMust($query);
}
private function filterCustomer(
BoolQuery $searchQuery,
bool $includeXingResults,
bool $includeHaysResults,
RecurrentJobsSearchParameters $searchParameters
): void {
/** @var IntegratedExternalPartnerCustomer $xingCustomer */
$xingCustomer = $this
->entityManager
->getRepository(IntegratedExternalPartnerCustomer::class)
->findOneBy(['internalId' => IntegratedExternalPartnerCustomer::INTERNAL_ID_XING]);
if (!is_null($xingCustomer)) {
$xingCustomerQuery = new MatchPhrase();
$xingCustomerQuery->setFieldQuery('integratedExternalPartnerCustomerId', $xingCustomer->getId());
if ($includeXingResults) {
$searchQuery->addMust($xingCustomerQuery);
} else {
$searchQuery->addMustNot($xingCustomerQuery);
}
}
/** @var IntegratedExternalPartnerCustomer $haysCustomer */
$haysCustomer = $this->entityManager->getRepository(IntegratedExternalPartnerCustomer::class)->findOneBy(['internalId' => IntegratedExternalPartnerCustomer::INTERNAL_ID_HAYS]);
if (!is_null($haysCustomer)) {
$haysCustomerQuery = new MatchPhrase();
$haysCustomerQuery->setFieldQuery('integratedExternalPartnerCustomerId', $haysCustomer->getId());
if ($includeHaysResults) {
$searchQuery->addMust($haysCustomerQuery);
} else {
$searchQuery->addMustNot($haysCustomerQuery);
}
}
if (!self::traineeCareerLevelWasExplicitlyChosen($searchParameters)) {
$includeAzubiResults = false;
foreach (OccupationalFieldsAndProfessionsSearchService::AZUBI_SEARCHTERMS as $searchterm) {
if (mb_stripos($searchParameters->getFilterSearchterm(), $searchterm) !== false) {
$includeAzubiResults = true;
break;
}
}
/** @var IntegratedExternalPartnerCustomer $kauflandXmlAusbildungCustomer */
$kauflandXmlAusbildungCustomer = $this->entityManager->getRepository(IntegratedExternalPartnerCustomer::class)->findOneBy(['internalId' => IntegratedExternalPartnerCustomer::INTERNAL_ID_KAUFLAND_XML_AUSBILDUNG]);
if (!is_null($kauflandXmlAusbildungCustomer)) {
$kauflandXmlAusbildungCustomerQuery = new MatchPhrase();
$kauflandXmlAusbildungCustomerQuery->setFieldQuery('integratedExternalPartnerCustomerId', $kauflandXmlAusbildungCustomer->getId());
if (!$includeAzubiResults) {
$searchQuery->addMustNot($kauflandXmlAusbildungCustomerQuery);
}
}
// /** @var IntegratedExternalPartnerCustomer $deutscheBahnAgCustomer */
// $deutscheBahnAgCustomer = $this->entityManager->getRepository(IntegratedExternalPartnerCustomer::class)->findOneBy(['internalId' => IntegratedExternalPartnerCustomer::INTERNAL_ID_DEUTSCHE_BAHN_AG]);
// if (!is_null($deutscheBahnAgCustomer)) {
// $deutscheBahnAgCustomerQuery = new MatchPhrase();
// $deutscheBahnAgCustomerQuery->setFieldQuery('integratedExternalPartnerCustomerId', $deutscheBahnAgCustomer->getId());
// $deutscheBahnAgCustomerQuery->setFieldQuery('careerLevels', (string)RecurrentJob::CAREER_LEVEL_TRAINEE);
//
// if (!$includeAzubiResults) {
// $searchQuery->addMustNot($deutscheBahnAgCustomerQuery);
// }
// }
}
}
private function filterRequiredTime(RecurrentJobsSearchParameters $searchParams, BoolQuery $searchQuery): void
{
foreach (PossibleAvailabilitiesValue::WEEKDAYS as $weekday) {
foreach (PossibleAvailabilitiesValue::TIMES_OF_DAY as $timeOfDay) {
$methodName = 'getFilterIsRequiredOn' . $weekday . $timeOfDay;
if ($searchParams->$methodName() === true) {
$requiredTimesQuery = new MatchQuery();
$requiredTimesQuery->setFieldQuery('requiredTimes.isRequiredOn' . $weekday . $timeOfDay, 'true');
// Massively prefer recurrent jobs that have required times on the searched-for times
$requiredTimesQuery->setFieldBoost('requiredTimes.isRequiredOn' . $weekday . $timeOfDay, 10000.0);
$searchQuery->addShould($requiredTimesQuery);
// This should have the effect: From the pool of recurrent jobs that do not require on all requested times,
// prefer those that are available on as many days as possible on the same time of day
for ($i = 0; $i < 5; ++$i) {
$numberOfAvailabilitiesForTimeOfDayQuery = new Range();
$numberOfAvailabilitiesForTimeOfDayQuery->addField('numberOfAvailabilitiesForTimeOfDay.' . $timeOfDay, ['gt' => (float)$i, 'boost' => $i + 1]);
$searchQuery->addShould($numberOfAvailabilitiesForTimeOfDayQuery);
}
}
}
}
}
/**
* @throws \Doctrine\DBAL\Exception
*/
private function filterAlreadyContacted(
RecurrentJobsSearchResultset $resultset,
string $jobseekerProfileId,
): void {
foreach ($resultset->getResults() as $key => $result) {
$joboffererProfileId = $result->getRecurrentJob()->getJoboffererProfile()->getId();
$sql = "SELECT id
FROM {$this->entityManager->getClassMetadata(ConversationMessage::class)->getTableName()}
WHERE jobseeker_profiles_id='{$jobseekerProfileId}'
AND jobofferer_profiles_id='{$joboffererProfileId}'
AND automated_conversation_messages_mailings_id is null
";
$statement = $this->entityManager->getConnection()->executeQuery($sql);
if ($statement->rowCount() > 0) {
$resultset->removeResult($key);
}
}
}
public function prefillForm(
?User $user = null,
?string $formPrefillOccupationalFieldSearchterm = ''
): RecurrentJobsSearchParameters {
$formPrefillZipcode = (!is_null($user)
&& !is_null($user->getDefaultJobseekerProfile())
&& !is_null($user->getDefaultJobseekerProfile()->getAvailabilityZipcode()))
? $user->getDefaultJobseekerProfile()->getAvailabilityZipcode()
: '';
return new RecurrentJobsSearchParameters(
$formPrefillOccupationalFieldSearchterm,
$formPrefillZipcode,
ZipcodeRadiusesValue::ALL[1],
RecurrentJob::EXPERIENCE_MORE_THAN_ONE_YEAR
);
}
public function getPossibleValuesForForm(): array
{
$filterRequiredExperience = [];
$filterRequiredCareerLevels = [];
$filterRequiredEmploymentTypes = [];
foreach (RecurrentJob::POSSIBLE_EXPERIENCE_VALUES_FOR_SELECTION_WITH_TRANSLATION_MAPPING as $key => $translation) {
$filterRequiredExperience[] = ['key' => $key, 'translation' => $this->translator->trans($translation)];
}
foreach (RecurrentJob::POSSIBLE_CAREER_LEVEL_AVAILABLE_FOR_SELECTION_WITH_TRANSLATION_MAPPING as $translation => $key) {
$filterRequiredCareerLevels[] = ['key' => $key, 'translation' => $this->translator->trans($translation)];
}
foreach (RecurrentJob::POSSIBLE_EMPLOYMENT_TYPE_AVAILABLE_FOR_SELECTION_WITH_TRANSLATION_MAPPING as $translation => $key) {
$filterRequiredEmploymentTypes[] = ['key' => $key, 'translation' => $this->translator->trans($translation)];
}
return [
'filterRequiredExperience' => $filterRequiredExperience,
'filterRequiredCareerLevels' => $filterRequiredCareerLevels,
'filterRequiredEmploymentTypes' => $filterRequiredEmploymentTypes,
'filterZipcodeRadius' => ZipcodeRadiusesValue::ALL
];
}
public function getNumberOfResultsPerPage(bool $isMobileAppUser, ?bool $showAnonymousResults = false): int
{
if ($isMobileAppUser) {
return GeneralApplicationSettingsValue::RECURRENT_JOBS_SEARCH_NONANONYMOUS_MAX_NUMBER_OF_RESULTS_PER_PAGE_MOBILE_APP;
}
return $showAnonymousResults
? GeneralApplicationSettingsValue::RECURRENT_JOBS_SEARCH_ANONYMOUS_MAX_NUMBER_OF_RESULTS_PER_PAGE
: GeneralApplicationSettingsValue::RECURRENT_JOBS_SEARCH_NONANONYMOUS_MAX_NUMBER_OF_RESULTS_PER_PAGE;
}
public function getMaxNumberOfResults(bool $isMobileAppUser, int $numberOfResultsPerPage): int
{
return $isMobileAppUser
? GeneralApplicationSettingsValue::RECURRENT_JOBS_SEARCH_MAX_NUMBER_OF_PAGES_MOBILE_APP * $numberOfResultsPerPage
: GeneralApplicationSettingsValue::RECURRENT_JOBS_SEARCH_MAX_NUMBER_OF_PAGES * $numberOfResultsPerPage;
}
public function getContext(?User $user = null): int
{
return $context = is_null($user) ? SearchtermEnteredEvent::CONTEXT_RECURRENT_JOBS_SEARCH_ANONYMOUS : SearchtermEnteredEvent::CONTEXT_RECURRENT_JOBS_SEARCH_LOGGED_IN;
}
/** @throws Exception */
public function handleConversationMessagePrefilling(
bool $showAnonymousResults,
RecurrentJobsSearchParameters $searchParams,
?User $user = null
): array {
if ($showAnonymousResults) {
$newConversationMessageSubjectPrefill = '';
$newConversationMessageBodyPrefill = '';
$registrationCarryThroughData = new CarryThroughData();
$registrationCarryThroughData->setRecurrentJobsSearchParameters($searchParams);
} else {
$newConversationMessagePrefillOccupationalField = ucfirst($this->translator->trans($searchParams->getFilterSearchterm())) . ' (m/w/d)';
$newConversationMessageBodyPrefillAvailabilities = '';
foreach (PossibleAvailabilitiesValue::WEEKDAYS as $weekday) {
foreach (PossibleAvailabilitiesValue::TIMES_OF_DAY as $timeOfDay) {
$methodName = 'getFilterIsRequiredOn' . $weekday . $timeOfDay;
if ($searchParams->$methodName() === true) {
$newConversationMessageBodyPrefillAvailabilities .= '- ' .
$this->translator->trans('availabilities_weekday.' . $weekday) .
' ' .
$this->translator->trans('availabilities_time_of_day.' . $timeOfDay) .
"\n";
}
}
}
$newConversationMessageSubjectPrefill = $this->translator->trans(
'conversations.subject_prefill_to_jobofferer_from_recurrent_jobs_search',
[
'%searchterm%' => $newConversationMessagePrefillOccupationalField
]
);
$newConversationMessageBodyPrefill = $this->translator->trans(
'conversations.body_prefill_to_jobofferer_from_recurrent_jobs_search',
[
'%receiverFirstname%' => '',
'%searchterm%' => $newConversationMessagePrefillOccupationalField,
'%availabilities%' => $newConversationMessageBodyPrefillAvailabilities,
'%contactEmail%' => $user->getDefaultJobseekerProfile()->getContactEmail(),
'%mobilenumber%' => $user->getDefaultJobseekerProfile()->getMobilenumber(),
'%firstname%' => ProfileService::getFirstnameOrAlternativeForSalutation($user->getDefaultJobseekerProfile()),
'%lastname%' => $user->getDefaultJobseekerProfile()->getLastname(),
]
);
}
return [
'newConversationMessageSubjectPrefill' => $newConversationMessageSubjectPrefill,
'newConversationMessageBodyPrefill' => $newConversationMessageBodyPrefill
];
}
/**
* @throws Exception
*/
public function getSentDates(
bool $showAnonymousResults,
array $joboffererProfiles,
LoggerInterface $logger,
?User $user = null
): array {
// Build an array of all jobofferers we already have contacted with a message in the past,
// in order to show this information on the results page
$conversationMessagesSentDatesForJoboffererProfiles = [];
if (is_null($user) || is_null($user->getDefaultJobseekerProfile())) {
return $conversationMessagesSentDatesForJoboffererProfiles;
}
if (!$showAnonymousResults) {
try {
$conversationMessageRepository = $this->entityManager->getRepository(ConversationMessage::class);
$conversationMessagesSentDatesForJoboffererProfiles = $conversationMessageRepository->getLastSentDatesForReceiverProfiles(
$user->getDefaultJobseekerProfile(),
$joboffererProfiles,
true
);
} catch (Exception $e) {
$logger->warning('Could not get last sent dates for jobofferer profiles of recurrent jobs in search results', ['exception' => $e]);
}
}
return $conversationMessagesSentDatesForJoboffererProfiles;
}
public function prepareResultsetForDisplay(bool $showAnonymousResults, RecurrentJobsSearchResultset $resultsetForCurrentPage): RecurrentJobsSearchResultset
{
if ($showAnonymousResults) {
$resultsArray = $resultsetForCurrentPage->getResults();
$alreadyPortrayedJobofferers = [];
foreach ($resultsArray as $key => $result) {
if ($result->containsRecurrentJob()) {
if (in_array($result->getRecurrentJob()->getJoboffererProfile()->getId(), $alreadyPortrayedJobofferers)) {
unset($resultsArray[$key]);
} else {
$alreadyPortrayedJobofferers[] = $result->getRecurrentJob()->getJoboffererProfile()->getId();
}
}
}
shuffle($resultsArray);
$resultsetForCurrentPage->resetResults();
$resultsetForCurrentPage->setResults($resultsArray);
}
return $resultsetForCurrentPage;
}
/** @throws Exception */
public function getInfosAboutResultset(
bool $showAnonymousResults,
RecurrentJobsSearchResultset $resultsetForCurrentPage,
?User $user = null
): array {
$containsExternalJoboffers = false;
$numberOfExternalJoboffersAndOfRecurrentJobsLinkedToExternalPartners = 0;
$joboffererProfiles = [];
if (!$showAnonymousResults) {
foreach ($resultsetForCurrentPage->getResults() as $key => $result) {
if ($result->containsRecurrentJob()) {
/** @var RecurrentJob $recurrentJob */
$recurrentJob = $result->getRecurrentJob();
if (!is_null($user) && $recurrentJob->getJoboffererProfile()->isBlockedBy($user->getDefaultJobseekerProfile())) {
$resultsetForCurrentPage->removeResult($key);
} else {
if (!is_null($user) && !is_null($recurrentJob->getIntegratedExternalPartnerCustomer()) && $recurrentJob->getIntegratedExternalPartnerCustomer()->isBlockedBy($user->getDefaultJobseekerProfile())) {
$resultsetForCurrentPage->removeResult($key);
} else {
$joboffererProfiles[] = $recurrentJob->getJoboffererProfile();
if (!is_null($recurrentJob->getJoboffererProfile()->getUser()->getExternalPartner())) {
$numberOfExternalJoboffersAndOfRecurrentJobsLinkedToExternalPartners = $numberOfExternalJoboffersAndOfRecurrentJobsLinkedToExternalPartners + 1;
}
}
}
}
if ($result->containsExternalJoboffer()) {
$containsExternalJoboffers = true;
$numberOfExternalJoboffersAndOfRecurrentJobsLinkedToExternalPartners = $numberOfExternalJoboffersAndOfRecurrentJobsLinkedToExternalPartners + 1;
}
}
}
return [
'containsExternalJoboffers' => $containsExternalJoboffers,
'numberOfExternalJoboffersAndOfRecurrentJobsLinkedToExternalPartners' => $numberOfExternalJoboffersAndOfRecurrentJobsLinkedToExternalPartners,
'joboffererProfiles' => $joboffererProfiles
];
}
/**
* This ensures that no external partner dominates the first few pages of a result list, even if this partner dominates the number of recurrent jobs in terms of volume.
*
* @see https://go-gastro.atlassian.net/wiki/spaces/JOB/pages/2317025281/Diversifizierungsfunktion for background
*/
public function diversifyJobs(RecurrentJobsSearchResultset $recurrentJobsSearchResultset): RecurrentJobsSearchResultset
{
$resultsetToReturn = new RecurrentJobsSearchResultset(
$recurrentJobsSearchResultset->getParameters(),
$recurrentJobsSearchResultset->getTracing(),
$recurrentJobsSearchResultset->getBlockNamesToInclude()
);
$resultsToReturn = [];
if (sizeof($recurrentJobsSearchResultset->getResults()) <= 5) {
return $recurrentJobsSearchResultset;
}
$countedResultsset = [];
foreach ($recurrentJobsSearchResultset->getResultsByBlock() as $block => $results) {
/** @var RecurrentJobsSearchResult $result */
foreach ($results as $result) {
if ($result->containsRecurrentJob() && $result->getRecurrentJob()->belongsToExternalPartner()) {
$countedResultsset[$block][$result->getRecurrentJob()->getJoboffererProfile()->getUser()->getExternalPartner()->getId()][] = $result;
} elseif ($result->containsExternalJoboffer()) {
$countedResultsset[$block][$result->getExternalJoboffer()->getExternalPartner()->getId()][] = $result;
} else {
$countedResultsset[$block]['organic'][] = $result;
}
}
}
$counter = 0;
$priorities = $this->getPriorities();
foreach ($recurrentJobsSearchResultset->getResultsByBlock() as $block => $results) {
if ($block === RecurrentJobsSearchResultset::BLOCK_3D
|| $block === RecurrentJobsSearchResultset::BLOCK_3E
) {
foreach ($results as $result) {
$resultsToReturn[] = $result;
}
} else {
foreach ($results as $result) {
foreach ($priorities[$counter % 6] as $priority) {
if (array_key_exists($priority, $countedResultsset[$block]) && sizeof($countedResultsset[$block][$priority]) > 0) {
$resultsToReturn[] = array_shift($countedResultsset[$block][$priority]);
break;
}
}
++$counter;
}
}
}
$resultsetToReturn->setResults($resultsToReturn);
return $resultsetToReturn;
}
public function getRecurrentJobIdsForCriteoListing(RecurrentJobsSearchResultset $resultset): string
{
$criteoListingIds = '[';
foreach ($resultset->getResults() as $result) {
if ($result->containsRecurrentJob()) {
$criteoListingIds .= "'" . $result->getRecurrentJob()->getId() . "',";
}
}
if (strlen($criteoListingIds) > 1) {
$criteoListingIds = substr($criteoListingIds, 0, strlen($criteoListingIds) - 1) . ']';
} else {
$criteoListingIds = '';
}
return $criteoListingIds;
}
public function handleZipcode(string $zipcode): string
{
if (strlen($zipcode) > 5) {
preg_match('/\d{5}/', $zipcode, $matches);
if (!is_null($matches) && !is_null($matches[0])) {
return $matches[0];
}
}
return $zipcode;
}
public function getPriorities(): array
{
$returnArray = [];
$shufflingArray = ExternalPartner::IDS_INTEGRATED;
array_push($shufflingArray, self::SEARCH_RESULTS_ORGANIC_NAME);
$counter = 0;
while ($counter < self::SEARCH_RESULTS_DIVERSIFICATION_PARAMETER) {
$subArray = [];
if ($counter % 3 === 0) {
$element = array_splice($shufflingArray, array_search(self::SEARCH_RESULTS_ORGANIC_NAME, $shufflingArray, true), 1);
array_unshift($shufflingArray, $element[0]);
}
foreach ($shufflingArray as $id) {
$subArray[] = $id;
}
$innerCounter = 0;
while ($innerCounter < sizeof($shufflingArray) / self::SEARCH_RESULTS_DIVERSIFICATION_PARAMETER) {
$element = array_splice($shufflingArray, 0, 1);
array_push($shufflingArray, $element[0]);
++$innerCounter;
}
$returnArray[$counter] = $subArray;
++$counter;
}
return $returnArray;
}
/**
* @throws Exception
*/
public function getFullResultsetForSpecificCustomer(
RecurrentJobsSearchParameters $searchParams,
SearchTracing $tracing,
IntegratedExternalPartnerCustomer $customer,
?string $zipcode = null
): RecurrentJobsSearchResultset {
if (!is_null($zipcode)) {
$zipcodeCircumcirclesForDistance = explode(' ', str_replace(')', '', str_replace('(', '', $this->getZipcodesForDistance(
$this->getZipcodeCircumcirclesForZipcode($zipcode),
20
))));
$fullResultset = new RecurrentJobsSearchResultset($searchParams, $tracing);
$recurrentJobs = $this->entityManager->createQueryBuilder()
->select('rj')
->from(RecurrentJob::class, 'rj')
->leftJoin('rj.joboffererProfile', 'p')
->leftJoin('p.user', 'u')
->innerJoin('u.integratedExternalPartnerCustomers', 'iepc')
->where("iepc.id ='" . $customer->getId() . "'")
->andWhere('rj.status = :status')
->andWhere('p.pausedSince is NULL')
->andWhere('rj.zipcode IN(:zipcodes) OR (rj.zipcode is NULL AND p.zipcode IN(:zipcodes))')
->setParameter('status', RecurrentJob::STATUS_ACTIVE)
->setParameter('zipcodes', $zipcodeCircumcirclesForDistance)
->orderBy('rj.createdAt', 'DESC')
->setMaxResults(100)
->getQuery()->getResult();
} else {
$fullResultset = new RecurrentJobsSearchResultset($searchParams, $tracing);
$recurrentJobs = $this->entityManager->createQueryBuilder()
->select('rj')
->from(RecurrentJob::class, 'rj')
->leftJoin('rj.joboffererProfile', 'p')
->leftJoin('p.user', 'u')
->innerJoin('u.integratedExternalPartnerCustomers', 'iepc')
->where("iepc.id ='" . $customer->getId() . "'")
->andWhere('rj.status = :status')
->andWhere('p.pausedSince is NULL')
->setParameter('status', RecurrentJob::STATUS_ACTIVE)
->orderBy('rj.createdAt', 'DESC')
->setMaxResults(100)
->getQuery()->getResult();
}
$results = [];
foreach ($recurrentJobs as $recurrentJob) {
$results[] = new RecurrentJobsSearchResult($recurrentJob, null, 0, new BlockDefinitionA0());
}
$fullResultset->setResults($results);
$fullResultset->sliceResults(0, 100);
return $fullResultset;
}
public function generateRandomSearchParams(): RecurrentJobsSearchParameters
{
$searchParams = new RecurrentJobsSearchParameters(
'none',
10117,
20,
0,
[
RecurrentJob::CAREER_LEVEL_TRAINEE,
RecurrentJob::CAREER_LEVEL_ASSISTANT,
RecurrentJob::CAREER_LEVEL_EXPERT,
RecurrentJob::CAREER_LEVEL_EXECUTIVE
],
false,
[
RecurrentJob::EMPLOYMENT_TYPE_ONCE,
RecurrentJob::EMPLOYMENT_TYPE_HELP,
RecurrentJob::EMPLOYMENT_TYPE_PART_TIME,
RecurrentJob::EMPLOYMENT_TYPE_FULL_TIME
]
);
return $searchParams;
}
public function getWaitingInfo(?User $user): string
{
if (is_null($user)) {
$number = rand(0, 3);
} else {
$number = rand(4, 5);
}
return $this->translator->trans('recurrent_jobs_search.please_wait_infos.' . $number);
}
public function syncRecurrentJobIdWithElasticSearch(
string $recurrentJobId
): void {
/** @var RecurrentJob|null $recurrentJob */
$recurrentJob = $this->entityManager->find(
RecurrentJob::class,
$recurrentJobId
);
if (is_null($recurrentJob)) {
$this->recurrentJobsPersister->deleteById($recurrentJobId);
return;
}
if ($recurrentJob->isFindable()) {
$this->recurrentJobsPersister->replaceOne($recurrentJob);
} else {
$this->recurrentJobsPersister->deleteOne($recurrentJob);
}
}
public function getSimilarActiveRecurrentJob(RecurrentJob $recurrentJob, Request $request): ?RecurrentJob
{
$searchParams = RecurrentJobsSearchParameters::fromRecurrentJob($recurrentJob);
$tracing = $this->tracingService->createTracing($this);
$fullResultset = new RecurrentJobsSearchResultset($searchParams, $tracing);
try {
$fullResultset = $this->getResultset(
$searchParams,
5,
0,
true
);
} catch (UnknownZipcodeException $e) {
$debuggingInfoBag = new DebuggingInfoBag(
'unknown-zipcode-recurrent-jobs-search',
'Unknown zipcode ' . $searchParams->getFilterZipcode() . ' in recurrent jobs search', '',
Logger::WARNING);
$debuggingInfoBag->setRequest($request);
$this->debuggingService->log($debuggingInfoBag);
} catch (Exception $e) {
$userId = null;
throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'An exception occurred while trying to search for recurrent jobs. The query string is ' . $request->getQueryString() . ', and the userId is ' . $userId, $e);
}
if ($fullResultset->getTotalNumberOfResults() > 0) {
foreach ($fullResultset->getResults() as $result) {
if ($result->containsRecurrentJob()) {
return $result->getRecurrentJob();
}
}
}
return null;
}
public function getPaginationDataForResultsPage(
RecurrentJobsSearchResultset $fullResultset,
RecurrentJobsSearchResultset $resultsetForCurrentPage,
int $page,
?int $maxNumberOfPages = null
): PaginationData {
$numberOfResultsPerPage = $this->getNumberOfResultsPerPage(false, false);
$totalNumberOfPages = ceil($fullResultset->getTotalNumberOfResults() / $numberOfResultsPerPage);
$totalJobsPerPage = sizeof($resultsetForCurrentPage->getResults());
$totalJobs = $fullResultset->getTotalNumberOfResults();
if (!is_null($maxNumberOfPages) && $totalNumberOfPages > $maxNumberOfPages) {
$totalNumberOfPages = $maxNumberOfPages;
$totalJobs = $maxNumberOfPages * $numberOfResultsPerPage;
}
$totalItemsInCurrentPage = ($totalJobsPerPage + (($page - 1) * $numberOfResultsPerPage) < $totalJobs)
&& ($totalJobsPerPage < $numberOfResultsPerPage)
? $numberOfResultsPerPage
: $totalJobsPerPage;
return new PaginationData(
$totalNumberOfPages,
$page + 1 <= $totalNumberOfPages ? $page + 1 : $page,
$page - 1 > 0 ? $page - 1 : 1,
$page,
$totalJobs,
$totalItemsInCurrentPage,
$numberOfResultsPerPage,
);
}
}