source: spip-zone/_plugins_/indexer/trunk/iterateur/sphinx.php @ 82609

Last change on this file since 82609 was 82609, checked in by marcimat@…, 5 years ago

Indiquer que l'on enlève le html dans les snippets.

File size: 14.8 KB
Line 
1<?php
2
3if (!defined('_ECRIRE_INC_VERSION')) return;
4
5/**
6 * Gestion de l'itérateur SPHINX
7 *
8 * @package SPIP\Indexer\Iterateur\Sphinx
9**/
10
11include_spip('iterateur/data');
12
13/**
14 * Créer une boucle sur un itérateur SPHINX
15 *
16 * Annonce au compilateur les "champs" disponibles,
17 *
18 * @param Boucle $b
19 *     Description de la boucle
20 * @return Boucle
21 *     Description de la boucle complétée des champs
22 */
23function iterateur_SPHINX_dist($b) {
24        $b->iterateur = 'SPHINX'; # designe la classe d'iterateur
25        $b->show = array(
26                'field' => array(
27                        'docs' => 'ARRAY',
28                        'meta' => 'ARRAY',
29                        'facets' => 'ARRAY',
30                        'query' => 'STRING',
31                        #'*' => 'ALL' // Champ joker *
32                )
33        );
34        return $b;
35}
36
37
38/**
39 * Iterateur SPHINX pour itérer sur des données
40 *
41 * La boucle SPHINX n'a toujours qu'un seul élément.
42 */
43class IterateurSPHINX implements Iterator {
44
45        /**
46         * Type de l'iterateur
47         * @var string
48         */
49        protected $type = 'SPHINX';
50
51        /**
52         * Commandes transmises à l'iterateur
53         * @var array
54         */
55        protected $command = array();
56
57        /**
58         * Infos de debug transmises à l'iterateur
59         * @var array
60         */
61        protected $info = array();
62
63        /**
64         * Instance de SphinxQL
65         * @var \Sphinx\SphinxQL\SphinxQL
66         */
67        protected $sphinxQL = null;
68
69        /**
70         * Instance de SphinxQL\QueryApi
71         * @var \Sphinx\SphinxQL\QueryAPi
72         */
73        protected $queryApi = null;
74
75        /**
76         * Résultat de la requête à Sphinx
77         * @var array
78         */
79        protected $result = array();
80
81        /**
82         * Cle courante
83         * @var null
84         */
85        protected $cle = null;
86
87        /**
88         * Valeur courante
89         * @var null
90         */
91        protected $valeur = null;
92
93        /**
94         * Constructeur
95         *
96         * @param  $command
97         * @param array $info
98         */
99        public function __construct($command, $info=array()) {
100
101                $this->command = $command + array(
102                        'index'     => array(),
103                        'selection' => array(),
104                        'recherche' => array(),
105                        'snippet'   => array(),
106                        'facet'     => array(),
107
108                        'select_filter' => array(),
109                );
110
111#var_dump($this->command);
112
113                $this->info = $info;
114
115                include_spip('inc/indexer');
116
117                $this->sphinxQL  = new \Sphinx\SphinxQL\SphinxQL(SPHINX_SERVER_HOST, SPHINX_SERVER_PORT);
118                $this->queryApi  = new \Sphinx\SphinxQL\QueryApi();
119
120                $this->setIndex($this->command['index']);
121                $this->setSelection($this->command['selection']);
122                $this->setRecherche($this->command['recherche']);
123                $this->setOrderBy($this->command['orderby']);
124                $this->setFacet($this->command['facet']);
125
126                $this->setSelectFilter($this->command['select_filter']);
127
128                $this->setSnippet($this->command);
129
130                $this->runQuery();
131        }
132
133
134        public function runQuery() {
135                $query  = $this->queryApi->get();
136                $result = $this->sphinxQL->allfetsel($query);
137                if (!$result) {
138                        return false;
139                }
140                $this->result = $result;
141                return true;
142        }
143
144
145        public function quote($m) {
146                return $this->queryApi->quote($m);
147        }
148
149
150        /**
151         * Définir la liste des index interrogés (FROM de la requête)
152         *
153         * Par défaut on utilise l'index déclaré dans la conf
154         *
155         * @param array $index Liste des index
156         * @return bool True si au moins un index est ajouté, false sinon
157        **/
158        public function setIndex($index) {
159                if (!is_array($index)) $index = array($index);
160                $index = array_filter($index);
161                if (!$index) {
162                        $index[] = SPHINX_DEFAULT_INDEX;
163                }
164                foreach ($index as $i) {
165                        $this->queryApi->from($i);
166                }
167                return true;
168        }
169
170
171
172        /**
173         * Définir la liste des champs récupérés (SELECT de la requête)
174         *
175         * Par défaut, en absence de précisions, on prend tous les champs
176         *
177         * @param array $select Liste des index
178         * @return bool True si au moins un index est ajouté, false sinon
179        **/
180        public function setSelection($select) {
181                if (!is_array($select)) $select = array($select);
182                $select = array_filter($select);
183                // si aucune selection demandée, on prend tout !
184                if (!$select) {
185                        $select[] = '*';
186                }
187                foreach ($select as $s) {
188                        $this->queryApi->select($s);
189                }
190                return true;
191        }
192
193
194
195        /**
196         * Définir la recherche fulltext
197         *
198         * @param array $index Liste des index
199         * @return bool True si au moins un index est ajouté, false sinon
200        **/
201        public function setRecherche($recherche) {
202                if (!is_array($recherche)) $recherche = array($recherche);
203                $recherche = array_filter($recherche);
204                if (!$recherche) {
205                        return false;
206                }
207                $match = implode(' ',$recherche);
208                $this->queryApi
209                        ->select('WEIGHT() AS score')
210                        ->where('MATCH(' . $this->quote( $recherche ) . ')');
211                return true;
212        }
213
214
215        public function setOrderby($orderby) {
216                if (!is_array($orderby)) $orderby = array($orderby);
217                $orderby = array_filter($orderby);
218                if (!$orderby) {
219                        return false;
220                }
221                foreach ($orderby as $order) {
222                        // juste ASC ou DESC sans le champ… passer le chemin…
223                        if (in_array(trim($order), array('ASC', 'DESC'))) {
224                                continue;
225                        }
226                        if (!preg_match('/(ASC|DESC)$/i', $order)) {
227                                $order .= ' ASC';
228                        }
229                        $this->queryApi->orderby($order);
230                }
231                return true;
232        }
233
234        /**
235         * Définir le snippet
236         */
237        public function setSnippet($command) {
238                $snippet = array_filter($command['snippet']);
239                // si aucune selection demandée, on prend tout !
240                if (!$snippet) {
241                        return $this->setSnippetAuto($command);
242                } else {
243                        $ok = true;
244                        foreach ($snippet as $s) {
245                                if (!is_array($s)) continue;
246                                if (!$s['phrase']) {
247                                        $s['phrase'] = $this->getSnippetAutoPhrase($command);
248                                }
249                                $ok &= $this->setOneSnippet($s);
250                        }
251                }
252                return $ok;
253        }
254
255        /**
256         * Définir 1 snippet depuis sa description
257         *
258         * @param array $desc
259         * @return bool
260        **/
261        public function setOneSnippet($desc) {
262
263                $desc += array(
264                        'champ'  => 'content',
265                        'phrase' => '',
266                        'limit'  => 200,
267                        'as'     => 'snippet'
268                );
269                if (!$desc['phrase']) {
270                        return false;
271                }
272
273                $this->queryApi->addSnippetWords( $desc['phrase'] );
274                $desc['phrase'] = $this->queryApi->getSnippetWords();
275
276                if (!$desc['phrase'] OR !$desc['champ']) {
277                        return false;
278                }
279                $this->queryApi->select("SNIPPET($desc[champ], " . $this->quote($desc['phrase']) . ", 'limit=$desc[limit],html_strip_mode=strip') AS $desc[as]");
280                return true;
281        }
282
283        /**
284         * Définir automatiquement un snippet dans le champ 'snippet'
285         * à partir de la recherche et des filtres
286         */
287        public function setSnippetAuto($command) {
288                $phrase = $this->getSnippetAutoPhrase($command);
289                if (!$phrase) return false;
290                return $this->setOneSnippet(array('phrase' => $phrase));
291        }
292
293        /**
294         * Extrait de la commande de boucle les phrases pertinentes cherchées
295         *
296         * - Cherche la phrase de recherche
297         *
298         * @param array $command Commande de la boucle Sphinx
299         * @return string phrases séparées par espace.
300        **/
301        public function getSnippetAutoPhrase($command) {
302                $phrase = '';
303
304                // mots de la recherche
305                $recherche = $command['recherche'];
306                if (!is_array($recherche)) $recherche = array($recherche);
307                $recherche = array_filter($recherche);
308                $phrase .= implode(' ', $recherche);
309
310                return $phrase;
311        }
312
313
314        /**
315         * Définit les commandes FACET
316         *
317         * @param array $facets Tableau des facettes demandées
318         * @return bool
319        **/
320        public function setFacet($facets) {
321                $facets = array_filter($facets);
322                if (!$facets) {
323                        return false;
324                }
325                $ok = true;
326                foreach ($facets as $facet) {
327                        if (!isset($facet['alias']) OR !isset($facet['query'])) {
328                                $ok = false;
329                                continue;
330                        }
331                        $alias = trim($facet['alias']);
332                        $query = trim($facet['query']);
333                        if (!$alias OR !$query) {
334                                $ok =  false;
335                                continue;
336                        }
337                        $this->facet[] = array('alias' => $alias, 'query' => $query);
338                        $this->queryApi->facet($query);
339                }
340                return $ok;
341        }
342
343
344
345        /**
346         * Définit des filtres
347         *
348         * @param array $facets Tableau des filtres demandées
349         * @return bool
350        **/
351        public function setSelectFilter($filters) {
352                // compter le nombre de filtres ajoutés à la requête.
353                static $nb = 0;
354
355                $facets = array_filter($filters);
356                if (!$filters) {
357                        return false;
358                }
359                foreach ($filters as $filter) {
360                        // ignorer toutes les données vides
361                        if (!is_array($filter) OR !isset($filter['valeur']) OR !$valeur = $filter['valeur']) {
362                                continue;
363                        }
364                        if (is_string($valeur)) {
365                                $valeur = trim($valeur);
366                                $valeurs = array($valeur);
367                        } else {
368                                $valeurs = $valeur;
369                                $valeur = 'Array !';
370                        }
371                        $valeurs = array_unique(array_filter($valeurs));
372                        if (!$valeurs) {
373                                continue;
374                        }
375
376                        $filter += array(
377                                'select_oui'  => '',
378                                'select_null' => '',
379                        );
380
381                        // préparer les données
382                        $valeur = $this->quote($valeur);
383                        $valeurs = array_map(array($this, 'quote'), $valeurs);
384                        $valeurs = implode(', ', $valeurs);
385
386                        if (($valeur == '-') and $filter['select_null']) {
387                                $f = $filter['select_null'];
388                        } elseif ($filter['select_oui']) {
389                                $f = $filter['select_oui'];
390                        }
391
392                        // remplacer d'abord le pluriel !
393                        $f = str_replace(array('@valeurs', '@valeur'), array($valeurs, $valeur), $f);
394                        $this->queryApi->select("($f) AS f$nb");
395                        $this->queryApi->where("f$nb = 1");
396                        $nb++;
397                }
398        }
399
400        /**
401         * Revenir au depart
402         * @return void
403         */
404        public function rewind() {
405                reset($this->result);
406                list($this->cle, $this->valeur) = each($this->result);
407        }
408
409        /**
410         * L'iterateur est-il encore valide ?
411         * @return bool
412         */
413        public function valid(){
414                return !is_null($this->cle);
415        }
416
417        /**
418         * Retourner la valeur
419         * @return null
420         */
421        public function current() {
422                return $this->valeur;
423        }
424
425        /**
426         * Retourner la cle
427         * @return null
428         */
429        public function key() {
430                return $this->cle;
431        }
432
433        /**
434         * Passer a la valeur suivante
435         * @return void
436         */
437        public function next(){
438                if ($this->valid())
439                        list($this->cle, $this->valeur) = each($this->result);
440        }
441
442        /**
443         * Compter le nombre total de resultats
444         * @return int
445         */
446        public function count() {
447                if (is_null($this->total))
448                        $this->total = count($this->result);
449          return $this->total;
450        }
451
452}
453
454
455/**
456 * Transmettre la source (l'index sphinx) désirée
457 * @param string $idb
458 * @param object $boucles
459 * @param object $crit
460 */
461function critere_SPHINX_index_dist($idb, &$boucles, $crit) {
462        $boucle = &$boucles[$idb];
463        // critere unique
464        $boucle->hash .= "\n\t" . '$command[\'index\'] = array();';
465
466        foreach ($crit->param as $param){
467                $boucle->hash .= "\n\t" . '$command[\'index\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).';';
468        }
469}
470
471/**
472 * Transmettre la recherche (le match fulltext) désirée
473 * @param string $idb
474 * @param object $boucles
475 * @param object $crit
476 */
477function critere_SPHINX_recherche_dist($idb, &$boucles, $crit) {
478        $boucle = &$boucles[$idb];
479        // critere unique
480        $boucle->hash .= "\n\t" . '$command[\'recherche\'] = array();';
481
482        foreach ($crit->param as $param){
483                $boucle->hash .= "\n\t" . '$command[\'recherche\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).';';
484        }
485}
486
487
488/**
489 * Indiquer les sélections de la requête
490 *
491 * @param string $idb
492 * @param object $boucles
493 * @param object $crit
494 */
495function critere_SPHINX_select_dist($idb, &$boucles, $crit) {
496        $boucle = &$boucles[$idb];
497        // critere multiple
498        $boucle->hash .= "\n\tif (!isset(\$select_init)) { \$command['selection'] = array(); \$select_init = true; }\n";
499
500        foreach ($crit->param as $param){
501                $boucle->hash .= "\t\$command['selection'][] = "
502                                . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ";\n";
503        }
504}
505
506
507/**
508 * Indiquer les snippets de la requête
509 *
510 * @param string $idb
511 * @param object $boucles
512 * @param object $crit
513 */
514function critere_SPHINX_snippet_dist($idb, &$boucles, $crit) {
515        $boucle = &$boucles[$idb];
516        // critere multiple
517        $boucle->hash .= "\n\tif (!isset(\$snippet_init)) { \$command['snippet'] = array(); \$snippet_init = true; }\n";
518
519        $boucle->hash .= "\t\$command['snippet'][] = [\n"
520                . (isset($crit->param[0]) ? "\t\t'champ'  => ". calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
521                . (isset($crit->param[1]) ? "\t\t'phrase' => ". calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
522                . (isset($crit->param[2]) ? "\t\t'limit'  => ". calculer_liste($crit->param[2], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
523                . (isset($crit->param[3]) ? "\t\t'as'     => ". calculer_liste($crit->param[3], array(), $boucles, $boucles[$idb]->id_parent) . "\n"  : '')
524                . "\t];\n";
525}
526
527
528
529/**
530 * Indiquer les facets de la requête
531 *
532 * @param string $idb
533 * @param object $boucles
534 * @param object $crit
535 */
536function critere_SPHINX_facet_dist($idb, &$boucles, $crit) {
537        $boucle = &$boucles[$idb];
538        // critere multiple
539        $boucle->hash .= "\n\tif (!isset(\$facet_init)) { \$command['facet'] = array(); \$facet_init = true; }\n";
540
541        $boucle->hash .= "\t\$command['facet'][] = array(\n"
542                . (isset($crit->param[0]) ? "\t\t'alias'  => ". calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
543                . (isset($crit->param[1]) ? "\t\t'query' => ". calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
544                . "\t);\n";
545}
546
547/**
548 * Indiquer les filtres de la requête
549 *
550 * @param string $idb
551 * @param object $boucles
552 * @param object $crit
553 */
554function critere_SPHINX_select_filter_dist($idb, &$boucles, $crit) {
555        $boucle = &$boucles[$idb];
556        // critere multiple
557        $boucle->hash .= "\n\tif (!isset(\$sfilter_init)) { \$command['select_filter'] = array(); \$sfilter_init = true; }\n";
558
559        $boucle->hash .= "\t\$command['select_filter'][] = [\n"
560                . (isset($crit->param[0]) ? "\t\t'valeur'      => ". calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
561                . (isset($crit->param[1]) ? "\t\t'select_oui'  => ". calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
562                . (isset($crit->param[2]) ? "\t\t'select_null' => ". calculer_liste($crit->param[2], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
563                . "\t];\n";
564}
565
566
567
568/**
569 * Tris `{par x}`
570 *
571 * @param string $idb
572 * @param object $boucles
573 * @param object $crit
574 */
575function critere_SPHINX_par_dist($idb, &$boucles, $crit) {
576        return critere_SPHINX_parinverse($idb, $boucles, $crit);
577}
578
579/**
580 * Tris `{inverse}`
581 *
582 * @param string $idb
583 * @param object $boucles
584 * @param object $crit
585 */
586function critere_SPHINX_inverse_dist($idb, &$boucles, $crit) {
587        $boucle = &$boucles[$idb];
588        if ($crit->not) {
589                critere_SPHINX_parinverse($idb, $boucles, $crit);
590        } else {
591                // sinon idem parent.
592                critere_inverse_dist($idb, $boucles, $crit);
593        }
594}
595
596/**
597 * Gestion des critères `{par}` et `{inverse}`
598 *
599 * @note
600 *     Sphinx doit toujours avoir le sens de tri (ASC ou DESC).
601 *
602 *     Version simplifié du critère natif de SPIP, avec une permission
603 *     pour les champs de type json `properties.truc`
604 *
605 * @param string $idb
606 * @param object $boucles
607 * @param object $crit
608**/
609function critere_SPHINX_parinverse($idb, $boucles, $crit, $sens = '') {
610        $boucle = &$boucles[$idb];
611        if ($crit->not) {
612                $sens = $sens ? "" : " . ' DESC'";
613        }
614
615        foreach ($crit->param as $tri){
616                $order = "";
617
618                // tris specifies dynamiquement
619                if ($tri[0]->type!='texte'){
620                        // calculer le order dynamique qui verifie les champs
621                        $order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens);
622                } else {
623                        $par = array_shift($tri);
624                        $par = $par->texte;
625                        $order = "'$par'";
626                }
627
628
629                $t = $order.$sens;
630                $boucle->order[] = $t;
631        }
632}
633
634
635
Note: See TracBrowser for help on using the repository browser.