src/Elasticsearch/Moteur.php line 590

Open in your IDE?
  1. <?php
  2. declare (strict_types 1);
  3. /*
  4.  * Copyright (C) Office National de Publication et de Communication - All Rights Reserved
  5.  *
  6.  * Unauthorized copying of this file, via any medium is strictly prohibited by law
  7.  * This file is proprietary and confidential
  8.  */
  9. namespace App\Elasticsearch;
  10. use Elastica\Type;
  11. use Elastica\Query;
  12. use Elastica\Aggregation;
  13. use Elastica\Query\MultiMatch;
  14. use App\Elasticsearch\Contexte;
  15. use Elastica\Query\MatchPhrase;
  16. use App\Elasticsearch\Parametres;
  17. use Knp\Component\Pager\PaginatorInterface;
  18. use FOS\ElasticaBundle\Finder\TransformedFinder;
  19. use Symfony\Component\HttpFoundation\RequestStack;
  20. use Knp\Bundle\PaginatorBundle\Pagination\SlidingPagination;
  21. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  22. final class Moteur
  23. {
  24.     private const GRAINE_INDEX __CLASS__ '_graine';
  25.     private const MAX_RESULT_WINDOW 1000;
  26.     private const PER_PAGE_DEFAULT 30;
  27.     public const PER_PAGE_DEFAULT_2 SELF::PER_PAGE_DEFAULT// J'ai en créé une autre par sousis de retro-compatibilité
  28.     /** @var TransformedFinder */
  29.     private $finder;
  30.     /** @var Type */
  31.     private $index;
  32.     /** @var Paginator */
  33.     private $paginateur;
  34.     /** @var RequestStack */
  35.     private $requestStack;
  36.     /** @var SessionInterface */
  37.     private $session;    
  38.     /** @var Query\BoolQuery */
  39.     private $filtre;
  40.     public function __construct(
  41.         TransformedFinder $finder,
  42.         Type $index,
  43.         PaginatorInterface $paginateur,
  44.         RequestStack $requestStack,
  45.         SessionInterface $session
  46.     ) {
  47.         $this->finder $finder;
  48.         $this->index $index;
  49.         $this->paginateur $paginateur;
  50.         $this->requestStack $requestStack;
  51.         $this->session $session;
  52.     }
  53.     // ----- Operations --------------------------------------------------------
  54.     public function cherche(Parametres $parametres): SlidingPagination
  55.     {
  56.         // Pour les grandes villes, réduire le rayon de recherche
  57.         @$lieu $parametres->lieuSlug;
  58.         if ($lieu) {
  59.             list($lieu) = explode("-"$lieu);
  60.             // $isGrandeVille = \in_array($lieu, ['paris', 'lyon', 'marseille']);
  61.             switch ($lieu) {
  62.                 case 'lyon':
  63.                     $parametres->villesCoordonneesDistance 5;
  64.                     break;
  65.                 
  66.                 case 'paris':
  67.                 case 'marseille':
  68.                     $parametres->villesCoordonneesDistance 6;
  69.                     break;
  70.             }
  71.         }        
  72.         
  73.         do {
  74.             $query $this->buildFilteredQuery($parametres);
  75.             $query->addSort($this->tri($parametres));
  76.             $resultats $this->finder->createPaginatorAdapter($query);
  77.             // Verifie si on est hors les résultats
  78.             $totalPages = (int) ceil($resultats->getTotalHits() / $parametres->maxParPage);
  79.             if ($parametres->pageActuele $totalPages) {
  80.                 return $this->paginateur->paginate(
  81.                     [],
  82.                     $parametres->pageActuele,
  83.                     $parametres->maxParPage $parametres->maxParPage self::PER_PAGE_DEFAULT
  84.                 );
  85.             }
  86.             /** @var SlidingPagination $pagination */
  87.             $pagination $this->paginateur->paginate(
  88.                 $resultats,
  89.                 $parametres->pageActuele,
  90.                 $parametres->maxParPage $parametres->maxParPage self::PER_PAGE_DEFAULT
  91.             );
  92.         } while (=== $pagination->getTotalItemCount() && $parametres->relax());
  93.         $pagination->setTotalItemCount($totalPages*$parametres->maxParPage);
  94.         return $pagination;
  95.     }
  96.     public function createContext(Parametres $parametres): Contexte
  97.     {
  98.         $contexte = new Contexte($parametres->quoiSlug$parametres->lieuSlug);
  99.         $query $this->buildFilteredQuery($parametres);
  100.         $query->addSort($this->tri($parametres));
  101.         $result $this->index->search($query);
  102.         foreach ($result as $item) {
  103.             $contexte->ids[] = $item->getId();
  104.         }
  105.         return $contexte;
  106.     }
  107.     // Renvoi la liste des Ids uniques des villes du contexte de recherche établissements actuel
  108.     public function getIdsVille(Parametres $parametres): array
  109.     {
  110.         $aggregation = new Aggregation\Terms('ville');
  111.         $aggregation->setField('ville');
  112.         $aggregation->setSize(self::MAX_RESULT_WINDOW);
  113.         $query $this->buildFilteredQuery($parametres);
  114.         $query->addAggregation($aggregation);
  115.         $ids = [];
  116.         $result $this->index->search($query)->getAggregation('ville');
  117.         foreach ($result['buckets'] as $bucket) {
  118.             $ids[] = $bucket['key'];
  119.         }
  120.         return $ids;
  121.     }
  122.     // Renvoi la liste des Ids uniques des filtres/diplomes
  123.     public function getIdsFiltres(Parametres $parametres): array
  124.     {
  125.         $aggregation = new Aggregation\Terms('filtresArray');
  126.         $aggregation->setField('filtresArray');
  127.         $aggregation->setSize(self::MAX_RESULT_WINDOW);
  128.         $query $this->buildFilteredQuery($parametres);
  129.         $query->addAggregation($aggregation);
  130.         $ids = [];
  131.         $result $this->index->search($query)->getAggregation('filtresArray');
  132.         foreach ($result['buckets'] as $bucket) {
  133.             $ids[] = $bucket['key'];
  134.         }
  135.         return $ids;
  136.     }    
  137.     // liste des Ids uniques des LVA/LVB (lvx = lva ou lvb)
  138.     public function getIdsLVx(Parametres $parametresstring $lvx): array
  139.     {
  140.         $aggregation = new Aggregation\Terms($lvx);
  141.         $aggregation->setField($lvx);
  142.         $aggregation->setSize(self::MAX_RESULT_WINDOW);
  143.         $query $this->buildFilteredQuery($parametres);
  144.         $query->addAggregation($aggregation);
  145.         $ids = [];
  146.         $result $this->index->search($query)->getAggregation($lvx);
  147.         foreach ($result['buckets'] as $bucket) {
  148.             $ids[] = $bucket['key'];
  149.         }
  150.         return $ids;
  151.     }    
  152.     // Renvoi la liste des Ids uniques des EenseignementsDeSpecialite
  153.     public function getIdsEenseignementsDeSpecialite(Parametres $parametres): array
  154.     {
  155.         $aggregation = new Aggregation\Terms('eds');
  156.         $aggregation->setField('eds');
  157.         $aggregation->setSize(self::MAX_RESULT_WINDOW);
  158.         $query $this->buildFilteredQuery($parametres);
  159.         $query->addAggregation($aggregation);
  160.         $ids = [];
  161.         $result $this->index->search($query)->getAggregation('eds');
  162.         foreach ($result['buckets'] as $bucket) {
  163.             $ids[] = $bucket['key'];
  164.         }
  165.         return $ids;
  166.     }   
  167.     // Renvoi la liste des Ids uniques des typesInternat
  168.     public function getIdsTypesInternat(Parametres $parametres): array
  169.     {
  170.         $aggregation = new Aggregation\Terms('typesInternatKeyword');
  171.         $aggregation->setField('typesInternatKeyword');
  172.         $aggregation->setSize(self::MAX_RESULT_WINDOW);
  173.         $query $this->buildFilteredQuery($parametres);
  174.         $query->addAggregation($aggregation);
  175.         $ids = [];
  176.         $result $this->index->search($query)->getAggregation('typesInternatKeyword');
  177.         foreach ($result['buckets'] as $bucket) {
  178.             $ids[] = $bucket['key'];
  179.         }
  180.         return $ids;
  181.     }       
  182.     public function count(Parametres $parametres): int
  183.     {
  184.         do {
  185.             $query $this->buildFilteredQuery($parametres);
  186.             $count = (int) $this->index->count($query);
  187.         } while (=== $count && $parametres->relax());
  188.         return $count;
  189.     }
  190.     // ----- Query builder -----------------------------------------------------
  191.     private function buildFilteredQuery(Parametres $parametres): Query
  192.     {
  193.         $this->filtre = new Query\BoolQuery();
  194.         // // Chercher uniquement les établissement
  195.         // $filter1 = new Query\Term();
  196.         // $filter1->setTerm('typeClient', 'établissement');
  197.         // $this->filtre->addMust($filter1);
  198.         // Recherche ...
  199.         if (!empty($parametres->typesInternat) || // par internats
  200.             !empty($parametres->formations) || // OU par formations
  201.             $parametres->filtresShouldMust == 'should' // OU par au moins un des filtres défini
  202.         ) {
  203.             $this->filtre->setMinimumShouldMatch(1);
  204.         }
  205.         if ($parametres->participantsSeulement) {
  206.             $this->participantsSeulement();
  207.         }
  208.         $this->filtreEtablissement(
  209.             (array) $parametres->etablissementsExclues
  210.         );
  211.         // Filtre "texte"
  212.         $this->filtreTexte(
  213.             (string) $parametres->texte,
  214.             (string) $parametres->texteCorrespondance,
  215.             (string) $parametres->texteFlou
  216.         );
  217.         // Filtre "type"
  218.         $this->filtreType(
  219.             (array) $parametres->types,
  220.             (array) $parametres->typesExclues
  221.         );
  222.         // Filtres "localisation"
  223.         $this->filtreVille((array) $parametres->villes, (array) $parametres->villesCoordonnees, (int) $parametres->villesCoordonneesDistance);
  224.         $this->filtreLocalisation('departement', (array) $parametres->departements);
  225.         // $this->filtreLocalisation('region', (array) $parametres->regions);
  226.         $this->filtreGeoPolygones((array) $parametres->geoPolygones);
  227.         // Ne pas filtrer par régions dans certains onglets
  228.         // if ( empty($parametres->typesInternat) )
  229.         if ($parametres->lieuSlug != "default") {
  230.             $this->filtreLocalisation('region', (array) $parametres->regions);
  231.         }
  232.         // Chercher dans tt les régionss
  233.         if (null !== $parametres->nombreEleves) {
  234.             $this->nombreEleves($parametres->nombreEleves);
  235.         }
  236.         if (null !== $parametres->filtresEcole) {
  237.             $this->filtresEcole($parametres->filtresEcole);
  238.         }
  239.         if (null !== $parametres->filtres) {
  240.             //$this->filtres((array) $parametres->filtres);
  241.             $this->filtres2('filtres', (array) $parametres->filtres$parametres->filtresShouldMust);
  242.         }
  243.         if (null !== $parametres->formations) {
  244.             $this->formations((array) $parametres->formations);
  245.         }
  246.         if (null !== $parametres->typesInternat) {
  247.             $this->typesInternat((array) $parametres->typesInternat);
  248.         }
  249.         if (null !== $parametres->ageAdmission && != $parametres->ageAdmission) {
  250.             $this->ageAdmission($parametres->ageAdmission);
  251.         }
  252.         if (null !== $parametres->catholique) {
  253.             $this->catholique($parametres->catholique);
  254.         }
  255.         if (null !== $parametres->sousContrat) {
  256.             $this->sousContrat($parametres->sousContrat);
  257.         }
  258.         if (null !== $parametres->alternance) {
  259.             $this->alternance($parametres->alternance);
  260.         }
  261.         if (null !== $parametres->lva || null !== $parametres->lvb) {
  262.             $this->langues($parametres->lva$parametres->lvb);
  263.         }
  264.         if ( !empty($parametres->eds) ) {
  265.             $this->enseignementDeSpecialite($parametres->eds);
  266.         }
  267.         // Groupe les filtres et ajoute le tri
  268.         $query = new Query();
  269.         $query->setQuery($this->filtre);
  270.         $query->setSize(self::MAX_RESULT_WINDOW);
  271.         return $query;
  272.     }
  273.     /*
  274.     https://www.elastic.co/guide/en/elasticsearch/reference/6.1/query-dsl-terms-set-query.html
  275.     https://github.com/ruflin/Elastica
  276.     recherche sur deux champs
  277.      */
  278.     private function participantsSeulement(): void
  279.     {
  280.         $participantTerm = new Query\Term();
  281.         $participantTerm->setTerm('participant'true);
  282.         $participantGestionnaireTerm = new Query\Term();
  283.         $participantGestionnaireTerm->setTerm('participantGestionnaire'true);
  284.         $clientQuery = new Query\BoolQuery();
  285.         $clientQuery->addShould($participantTerm);
  286.         $clientQuery->addShould($participantGestionnaireTerm);
  287.         $clientQuery->setMinimumShouldMatch(1);
  288.         $this->filtre->addMust($clientQuery);
  289.     }
  290.     private function filtreEtablissement(array $exclues): void
  291.     {
  292.         if ($exclues) {
  293.             $terms = new Query\Terms();
  294.             $terms->setTerms('_id'$exclues);
  295.             $this->filtre->addMustNot($terms);
  296.         }
  297.     }
  298.     private function filtreTexte(string $textestring $correspondancestring $flou): void
  299.     {
  300.         // Le quoi est à "tout" quand on fait une recherche depuis l'annuaire catho/privée
  301.         if (!$texte || $texte == 'tout' || strpos($texte'---') !== false) {
  302.             return;
  303.         }
  304.         $match = new Query\Match();
  305.         $match->setFieldQuery('texte'$texte);
  306.         $match->setFieldMinimumShouldMatch('texte'$correspondance);
  307.         $match->setFieldFuzziness('texte'$flou);
  308.         $this->filtre->addMust($match);
  309.     }
  310.     private function filtreType(array $incluses, array $exclues): void
  311.     {
  312.         if ($incluses) {
  313.             $terms = new Query\Terms();
  314.             $terms->setTerms('type'$incluses);
  315.             $this->filtre->addMust($terms);
  316.         }
  317.         if ($exclues) {
  318.             $terms = new Query\Terms();
  319.             $terms->setTerms('type'$exclues);
  320.             $this->filtre->addMustNot($terms);
  321.         }
  322.     }
  323.     private function filtreVille(array $ids, array $coordonneesint $distance): void
  324.     {
  325.         if (!$ids && !$coordonnees) {
  326.             return;
  327.         }
  328.         $villeQuery = new Query\BoolQuery();
  329.         if ($ids) {
  330.             $terms = new Query\Terms();
  331.             $terms->setTerms('ville'$ids);
  332.             $villeQuery->addShould($terms);
  333.         }
  334.         // Recherche via les coordonnees
  335.         if ($coordonnees && $distance 0) {
  336.             $geoQuery = new Query\BoolQuery();
  337.             $distanceInKm sprintf('%dkm'$distance);
  338.             foreach ($coordonnees as $coordonnee) {
  339.                 $geoDistance = new Query\GeoDistance('coordonnees'$coordonnee$distanceInKm);
  340.                 $geoQuery->addShould($geoDistance);
  341.             }
  342.             $geoQuery->setMinimumShouldMatch(1);
  343.             $villeQuery->addShould($geoQuery);
  344.         }
  345.         $villeQuery->setMinimumShouldMatch(1);
  346.         $this->filtre->addMust($villeQuery);
  347.     }
  348.     private function filtreLocalisation(string $champ, array $ids): void
  349.     {
  350.         if (!$ids) {
  351.             return;
  352.         }
  353.         $terms = new Query\Terms();
  354.         $terms->setTerms($champ$ids);
  355.         $this->filtre->addMust($terms);
  356.     }
  357.     private function filtreGeoPolygones(array $points): void
  358.     {
  359.         if (!$points) {
  360.             return;
  361.         }
  362.         $query = new Query\GeoPolygon('coordonnees'$points);
  363.         $this->filtre->addFilter($query);
  364.     }
  365.     // Recherche par nbr d'élèves
  366.     // https://www.elastic.co/guide/en/elasticsearch/reference/6.1/query-dsl-range-query.html
  367.     private function nombreEleves(int $nombre): void
  368.     {
  369.         $range = new Query\Range('nombreEleves', [
  370.             'gte' => $nombre 10,
  371.             'lte' => $nombre 10,
  372.         ]);
  373.         $this->filtre->addMust($range);
  374.     }
  375.     private function filtresEcole(): void
  376.     {
  377.         $payantTerm = new Query\Term();
  378.         $payantTerm->setTerm('filtreEcoleMixte'true);
  379.         $clientQuery = new Query\BoolQuery();
  380.         $clientQuery->addShould($payantTerm);
  381.         $this->filtre->addMust($clientQuery);
  382.     }
  383.     private function filtres2(string $champ, array $ids$filtresShouldMust): void
  384.     {
  385.         if (!$ids) {
  386.             return;
  387.         }
  388.         foreach ($ids as $unFiltre) {
  389.             $query = new MatchPhrase();
  390.             $query->setFieldQuery('filtres'$unFiltre);
  391.             if ($filtresShouldMust == "should") {
  392.                 $this->filtre->addShould($query);
  393.             }
  394.             // au moins une option de celles cochées
  395.             else {
  396.                 $this->filtre->addMust($query);
  397.             }
  398.             // toutes les options
  399.         }
  400.         // foreach ($ids as $unFiltre) {
  401.         //     $terms = new Query\Match();
  402.         //     $terms->setFieldQuery('filtres', $unFiltre);
  403.         //     $terms->setFieldMinimumShouldMatch('filtres', "75");
  404.         //     $terms->setFieldFuzziness('filtres', "0.75");
  405.         //     $this->filtre->addMust($terms);
  406.         // }
  407.     }
  408.     private function formations(array $formations): void
  409.     {
  410.         if (!$formations) {
  411.             return;
  412.         }
  413.         foreach ($formations as $unFiltre) {
  414.             $query = new MatchPhrase();
  415.             $query->setFieldQuery('formations'$unFiltre);
  416.             $this->filtre->addShould($query); // au moins une option de celles cochées
  417.         }
  418.         // foreach ($formations as $forma) {
  419.         //     $query = new MultiMatch();
  420.         //     $query->setQuery($forma);
  421.         //     $query->setFields(['formations']);
  422.         //     $this->filtre->addShould($query);   // au moins une option de celles cochées
  423.         // }
  424.         //
  425.         // $match = new Query\Match();
  426.         // $match->setFieldQuery('formations', $formations);
  427.         // //$match->setFieldMinimumShouldMatch('formations', $correspondance);
  428.         // //$match->setFieldFuzziness('formations', $flou);
  429.         // //$match->setFieldFuzziness('formations', 'AUTO');
  430.         // $this->filtre->addMust($match);
  431.     }
  432.     // ----- Triage ------------------------------------------------------------
  433.     /**
  434.      * Construit le tri d'une recherche.
  435.      *
  436.      * Le tri dépend des paramètres utilisés dans la recherche. Voici le
  437.      * vocabulaire pour expliquer le contexte: "participant" = "droit.participant".
  438.      *
  439.      * L'ordre attendu est:
  440.      *     1. Les participants dans le filtre type/ville/département/region
  441.      *     2. Les participants hors le filtre type/ville/département/region
  442.      *     3. Les participants avec le forfait GX
  443.      *     4. Les non participants qu'acceptent le numéro telephone payant
  444.      *     5. Les non participants qui n'acceptent pas le numéro telephone payant
  445.      */
  446.     private function tri(Parametres $parametres): array
  447.     {
  448.         return [
  449.             '_script' => [
  450.                 'type' => 'string',
  451.                 'order' => 'desc',
  452.                 'script' => [
  453.                     'lang' => 'painless',
  454.                     'source' => "
  455.                         String participant =
  456.                             'true' == doc['participant'].value ?
  457.                             '1' : '0';
  458.                         String type =
  459.                             false == Collections.disjoint(doc['type'], params.types) ?
  460.                             '1' : '0';
  461.                         String ville =
  462.                             false == Collections.disjoint(doc['ville'], params.villes) ?
  463.                             '1' : '0';
  464.                         return
  465.                             participant + '_' +
  466.                             type + '_' +
  467.                             ville + '_' +
  468.                             (params.graine + doc['graine'].value).hashCode();
  469.                     ",
  470.                     'params' => [
  471.                         'graine' => $this->genereGraine($parametres),
  472.                         'types' => $parametres->types,
  473.                         'villes' => $parametres->villes,
  474.                     ],
  475.                 ],
  476.             ],
  477.         ];
  478.     }
  479.     public function genereGraine(Parametres $parametres): string
  480.     {
  481.         // La source du graine est le groupement d'un valeur aléatoire plus les
  482.         // paramètres de recherche, donc : (1) on part d'une copie de tout les
  483.         // paramètres, (2) suprimme les données volatiles/dynamiques, et (3) on
  484.         // injecte le valeur aléatoire unique par utilisateur & session.
  485.         $source = clone $parametres;
  486.         $source->pageActuele null;
  487.         $source->maxParPage null;
  488.         $source->graine null;
  489.         $source->texte $source->disableQuoi null;
  490.         // if (!$this->requestStack->getSession()->has(self::GRAINE_INDEX)) {
  491.         //     $this->requestStack->getSession()->set(self::GRAINE_INDEX, bin2hex(random_bytes(5)));
  492.         // }
  493.         // $source->graine = $this->requestStack->getSession()->get(self::GRAINE_INDEX);
  494.         if (!$this->session->has(self::GRAINE_INDEX)) {
  495.             $this->session->set(self::GRAINE_INDEXbin2hex(random_bytes(5)));
  496.         }
  497.         $source->graine $this->session->get(self::GRAINE_INDEX);
  498.         return hash('crc32'serialize($source));
  499.     }
  500.     // Recherche par Type Internat
  501.     private function typesInternat(array $ids): void
  502.     {
  503.         if (!$ids) {
  504.             return;
  505.         }
  506.         // foreach ($ids as $forma) {
  507.         //     $query = new MultiMatch();
  508.         //     $query->setMinimumShouldMatch('1');
  509.         //     $query->setQuery($forma);
  510.         //     $query->setFields(['typesInternat']);
  511.         //     $this->filtre->addShould($query);   // au moins une option de celles cochées
  512.         // }
  513.         foreach ($ids as $unFiltre) {
  514.             $query = new MatchPhrase();
  515.             $query->setFieldQuery('typesInternat'$unFiltre);
  516.             $this->filtre->addShould($query); // au moins une option de celles cochées
  517.             // $this->filtre->addMust($query);     // toutes les options
  518.         }
  519.         // $villeQuery = new Query\BoolQuery();
  520.         //
  521.         // if ($ids) {
  522.         //     $terms = new Query\Terms();
  523.         //     $terms->setTerms('typesInternat', $ids);
  524.         //     $villeQuery->addShould($terms);
  525.         // }
  526.         // $villeQuery->setMinimumShouldMatch(1);
  527.         // $this->filtre->addMust($villeQuery);
  528.     }
  529.     // Recherche par age d'admission
  530.     // https://github.com/ruflin/Elastica/blob/master/test/Elastica/Query/RangeTest.php
  531.     private function ageAdmission(int $nombre): void
  532.     {
  533.         if ($nombre 0) {
  534.             $range = new Query\Range('ageAdmission', [
  535.                 'gte' => 3,
  536.             ]);
  537.         } else {
  538.             $range = new Query\Range('ageAdmission', [
  539.                 'lte' => 2.9,
  540.             ]);
  541.         }
  542.         $this->filtre->addMust($range);
  543.     }
  544.     private function catholique($etat): void
  545.     {
  546.         $catholique = new Query\Term();
  547.         $catholique->setTerm('catholique'$etat);
  548.         $clientQuery = new Query\BoolQuery();
  549.         $clientQuery->addMust($catholique);
  550.         $this->filtre->addMust($clientQuery);
  551.     }
  552.     private function sousContrat($etat): void
  553.     {
  554.         $sousContrat = new Query\Term();
  555.         $sousContrat->setTerm('sousContrat'$etat);
  556.         $clientQuery = new Query\BoolQuery();
  557.         $clientQuery->addMust($sousContrat);
  558.         $this->filtre->addMust($clientQuery);
  559.     }
  560.     private function alternance($etat): void
  561.     {
  562.         $alternance = new Query\Term();
  563.         $alternance->setTerm('alternance'$etat);
  564.         $clientQuery = new Query\BoolQuery();
  565.         $clientQuery->addMust($alternance);
  566.         $this->filtre->addMust($clientQuery);
  567.     }
  568.     private function langues($lva$lvb null$lvc null): void
  569.     {
  570.         // Closure (fct anonyme) pour préparer la requete ES
  571.         $filtrePrepareQuery = function ($value) {
  572.             return '"' $value '"';
  573.         };
  574.         // v3 : query_string
  575.         // STATIC TEST OK :
  576.         // $qs = new Query\QueryString("lva:(\"LYC-LANG-LV1-ANG\" OR \"LYC-LANG-LV1-ESP\") AND lvb:(\"LYC-LANG-LV2-CHI\")");
  577.         // $this->filtre->addMust($qs);
  578.         $qs null;
  579.         if ($lva) {
  580.             $arr array_map($filtrePrepareQuery$lva);
  581.             $s implode(" OR "$arr);
  582.             $qsA sprintf("lva:(%s)"$s);
  583.             $qs = new Query\QueryString($qsA);
  584.         }
  585.         if ($lvb) {
  586.             $arr array_map($filtrePrepareQuery$lvb);
  587.             $s implode(" OR "$arr);
  588.             $qsB sprintf("lvb:(%s)"$s);
  589.             $qs = new Query\QueryString($qsB);
  590.         }
  591.         if ($lva && $lvb) {
  592.             $qsAB sprintf("%s AND %s"$qsA$qsB);
  593.             $qs = new Query\QueryString($qsAB);
  594.         }
  595.         if ($qs) {
  596.             $this->filtre->addMust($qs);
  597.             // $this->filtre->setMinimumShouldMatch(0);
  598.         }
  599.         // v2
  600.         // $boolA = new Query\BoolQuery;
  601.         // foreach ($lva as $langue) {
  602.         //     $query = new MatchPhrase();
  603.         //     $query->setFieldQuery('lva', $langue);
  604.         //     $boolA->addShould($query); // au moins une option de celles cochées
  605.         // }
  606.         // $this->filtre->addMust($boolA); // au moins une option de celles cochées
  607.         // if ($lvb) {
  608.         //     $boolB = new Query\BoolQuery;
  609.         //     foreach ($lvb as $langue) {
  610.         //         $query = new MatchPhrase();
  611.         //         $query->setFieldQuery('lvb', $langue);
  612.         //         $boolB->addShould($query); // au moins une option de celles cochées
  613.         //     }
  614.         //     $this->filtre->addMust($boolB); // au moins une option de celles cochées
  615.         // }
  616.         // $this->filtre->setMinimumShouldMatch(0);
  617.         // $bool = new Query\BoolQuery;
  618.         // foreach ($lva as $langue) {
  619.         //     $query = new MatchPhrase();
  620.         //     $query->setFieldQuery('lva', $langue);
  621.         //     $bool->addShould($query); // au moins une option de celles cochées
  622.         // }
  623.         // $this->filtre->addMust($bool); // au moins une option de celles cochées
  624.         // $q0 = new Query\BoolQuery();
  625.         // $q = array();
  626.         // foreach ($lva as $langue) {
  627.         //     $qlang = new Query\MatchPhrase();
  628.         //     $q[] = $qlang->setFieldQuery('lva', $langue);
  629.         // }
  630.         // $q0->addShould($q);
  631.         // $this->filtre->addMust($q0);
  632.     }
  633.     private function enseignementDeSpecialite($eds): void
  634.     {
  635.         // Closure (fct anonyme) pour préparer la requete ES
  636.         $filtrePrepareQuery = function ($value) {
  637.             return '"' $value '"';
  638.         };
  639.         $qs null;
  640.         $arr array_map($filtrePrepareQuery$eds);
  641.         $s implode(" AND "$arr);
  642.         $qsA sprintf("eds:(%s)"$s);
  643.         $qs = new Query\QueryString($qsA);
  644.        
  645.         $this->filtre->addMust($qs);
  646.     }
  647. }