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

Last change on this file since 82598 was 82598, checked in by fil@…, 7 years ago

dernier array() pour avoir une boucle SPHINX qui tourne sous SPIP 2.1

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                $desc['phrase'] = $this->queryApi->get_snippet_words($desc['phrase']);
274
275                if (!$desc['phrase'] OR !$desc['champ']) {
276                        return false;
277                }
278                $this->queryApi->select("SNIPPET($desc[champ], " . $this->quote($desc['phrase']) . ", 'limit=$desc[limit]') AS $desc[as]");
279                return true;
280        }
281
282        /**
283         * Définir automatiquement un snippet dans le champ 'snippet'
284         * à partir de la recherche et des filtres
285         */
286        public function setSnippetAuto($command) {
287                $phrase = $this->getSnippetAutoPhrase($command);
288                if (!$phrase) return false;
289                return $this->setOneSnippet(array('phrase' => $phrase));
290        }
291
292        /**
293         * Extrait de la commande de boucle les phrases pertinentes cherchées
294         *
295         * - Cherche la phrase de recherche
296         *
297         * @param array $command Commande de la boucle Sphinx
298         * @return string phrases séparées par espace.
299        **/
300        public function getSnippetAutoPhrase($command) {
301                $phrase = '';
302
303                // mots de la recherche
304                $recherche = $command['recherche'];
305                if (!is_array($recherche)) $recherche = array($recherche);
306                $recherche = array_filter($recherche);
307                $phrase .= implode(' ', $recherche);
308
309                return $phrase;
310        }
311
312
313        /**
314         * Définit les commandes FACET
315         *
316         * @param array $facets Tableau des facettes demandées
317         * @return bool
318        **/
319        public function setFacet($facets) {
320                $facets = array_filter($facets);
321                if (!$facets) {
322                        return false;
323                }
324                $ok = true;
325                foreach ($facets as $facet) {
326                        if (!isset($facet['alias']) OR !isset($facet['query'])) {
327                                $ok = false;
328                                continue;
329                        }
330                        $alias = trim($facet['alias']);
331                        $query = trim($facet['query']);
332                        if (!$alias OR !$query) {
333                                $ok =  false;
334                                continue;
335                        }
336                        $this->facet[] = array('alias' => $alias, 'query' => $query);
337                        $this->queryApi->facet($query);
338                }
339                return $ok;
340        }
341
342
343
344        /**
345         * Définit des filtres
346         *
347         * @param array $facets Tableau des filtres demandées
348         * @return bool
349        **/
350        public function setSelectFilter($filters) {
351                // compter le nombre de filtres ajoutés à la requête.
352                static $nb = 0;
353
354                $facets = array_filter($filters);
355                if (!$filters) {
356                        return false;
357                }
358                foreach ($filters as $filter) {
359                        // ignorer toutes les données vides
360                        if (!is_array($filter) OR !isset($filter['valeur']) OR !$valeur = $filter['valeur']) {
361                                continue;
362                        }
363                        if (is_string($valeur)) {
364                                $valeur = trim($valeur);
365                                $valeurs = array($valeur);
366                        } else {
367                                $valeurs = $valeur;
368                                $valeur = 'Array !';
369                        }
370                        $valeurs = array_unique(array_filter($valeurs));
371                        if (!$valeurs) {
372                                continue;
373                        }
374
375                        $filter += array(
376                                'select_oui'  => '',
377                                'select_null' => '',
378                        );
379
380                        // préparer les données
381                        $valeur = $this->quote($valeur);
382                        $valeurs = array_map(array($this, 'quote'), $valeurs);
383                        $valeurs = implode(', ', $valeurs);
384
385                        if (($valeur == '-') and $filter['select_null']) {
386                                $f = $filter['select_null'];
387                        } elseif ($filter['select_oui']) {
388                                $f = $filter['select_oui'];
389                        }
390
391                        // remplacer d'abord le pluriel !
392                        $f = str_replace(array('@valeurs', '@valeur'), array($valeurs, $valeur), $f);
393                        $this->queryApi->select("($f) AS f$nb");
394                        $this->queryApi->where("f$nb = 1");
395                        $nb++;
396                }
397        }
398
399        /**
400         * Revenir au depart
401         * @return void
402         */
403        public function rewind() {
404                reset($this->result);
405                list($this->cle, $this->valeur) = each($this->result);
406        }
407
408        /**
409         * L'iterateur est-il encore valide ?
410         * @return bool
411         */
412        public function valid(){
413                return !is_null($this->cle);
414        }
415
416        /**
417         * Retourner la valeur
418         * @return null
419         */
420        public function current() {
421                return $this->valeur;
422        }
423
424        /**
425         * Retourner la cle
426         * @return null
427         */
428        public function key() {
429                return $this->cle;
430        }
431
432        /**
433         * Passer a la valeur suivante
434         * @return void
435         */
436        public function next(){
437                if ($this->valid())
438                        list($this->cle, $this->valeur) = each($this->result);
439        }
440
441        /**
442         * Compter le nombre total de resultats
443         * @return int
444         */
445        public function count() {
446                if (is_null($this->total))
447                        $this->total = count($this->result);
448          return $this->total;
449        }
450
451}
452
453
454/**
455 * Transmettre la source (l'index sphinx) désirée
456 * @param string $idb
457 * @param object $boucles
458 * @param object $crit
459 */
460function critere_SPHINX_index_dist($idb, &$boucles, $crit) {
461        $boucle = &$boucles[$idb];
462        // critere unique
463        $boucle->hash .= "\n\t" . '$command[\'index\'] = array();';
464
465        foreach ($crit->param as $param){
466                $boucle->hash .= "\n\t" . '$command[\'index\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).';';
467        }
468}
469
470/**
471 * Transmettre la recherche (le match fulltext) désiréé
472 * @param string $idb
473 * @param object $boucles
474 * @param object $crit
475 */
476function critere_SPHINX_recherche_dist($idb, &$boucles, $crit) {
477        $boucle = &$boucles[$idb];
478        // critere unique
479        $boucle->hash .= "\n\t" . '$command[\'recherche\'] = array();';
480
481        foreach ($crit->param as $param){
482                $boucle->hash .= "\n\t" . '$command[\'recherche\'][] = '.calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent).';';
483        }
484}
485
486
487/**
488 * Indiquer les sélections de la requête
489 *
490 * @param string $idb
491 * @param object $boucles
492 * @param object $crit
493 */
494function critere_SPHINX_select_dist($idb, &$boucles, $crit) {
495        $boucle = &$boucles[$idb];
496        // critere multiple
497        $boucle->hash .= "\n\tif (!isset(\$select_init)) { \$command['selection'] = array(); \$select_init = true; }\n";
498
499        foreach ($crit->param as $param){
500                $boucle->hash .= "\t\$command['selection'][] = "
501                                . calculer_liste($param, array(), $boucles, $boucles[$idb]->id_parent) . ";\n";
502        }
503}
504
505
506/**
507 * Indiquer les snippets de la requête
508 *
509 * @param string $idb
510 * @param object $boucles
511 * @param object $crit
512 */
513function critere_SPHINX_snippet_dist($idb, &$boucles, $crit) {
514        $boucle = &$boucles[$idb];
515        // critere multiple
516        $boucle->hash .= "\n\tif (!isset(\$snippet_init)) { \$command['snippet'] = array(); \$snippet_init = true; }\n";
517
518        $boucle->hash .= "\t\$command['snippet'][] = [\n"
519                . (isset($crit->param[0]) ? "\t\t'champ'  => ". calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
520                . (isset($crit->param[1]) ? "\t\t'phrase' => ". calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
521                . (isset($crit->param[2]) ? "\t\t'limit'  => ". calculer_liste($crit->param[2], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
522                . (isset($crit->param[3]) ? "\t\t'as'     => ". calculer_liste($crit->param[3], array(), $boucles, $boucles[$idb]->id_parent) . "\n"  : '')
523                . "\t];\n";
524}
525
526
527
528/**
529 * Indiquer les facets de la requête
530 *
531 * @param string $idb
532 * @param object $boucles
533 * @param object $crit
534 */
535function critere_SPHINX_facet_dist($idb, &$boucles, $crit) {
536        $boucle = &$boucles[$idb];
537        // critere multiple
538        $boucle->hash .= "\n\tif (!isset(\$facet_init)) { \$command['facet'] = array(); \$facet_init = true; }\n";
539
540        $boucle->hash .= "\t\$command['facet'][] = array(\n"
541                . (isset($crit->param[0]) ? "\t\t'alias'  => ". calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
542                . (isset($crit->param[1]) ? "\t\t'query' => ". calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
543                . "\t);\n";
544}
545
546/**
547 * Indiquer les filtres de la requête
548 *
549 * @param string $idb
550 * @param object $boucles
551 * @param object $crit
552 */
553function critere_SPHINX_select_filter_dist($idb, &$boucles, $crit) {
554        $boucle = &$boucles[$idb];
555        // critere multiple
556        $boucle->hash .= "\n\tif (!isset(\$sfilter_init)) { \$command['select_filter'] = array(); \$sfilter_init = true; }\n";
557
558        $boucle->hash .= "\t\$command['select_filter'][] = [\n"
559                . (isset($crit->param[0]) ? "\t\t'valeur'      => ". calculer_liste($crit->param[0], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
560                . (isset($crit->param[1]) ? "\t\t'select_oui'  => ". calculer_liste($crit->param[1], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
561                . (isset($crit->param[2]) ? "\t\t'select_null' => ". calculer_liste($crit->param[2], array(), $boucles, $boucles[$idb]->id_parent) . ",\n" : '')
562                . "\t];\n";
563}
564
565
566
567/**
568 * Tris `{par x}`
569 *
570 * @param string $idb
571 * @param object $boucles
572 * @param object $crit
573 */
574function critere_SPHINX_par_dist($idb, &$boucles, $crit) {
575        return critere_SPHINX_parinverse($idb, $boucles, $crit);
576}
577
578/**
579 * Tris `{inverse}`
580 *
581 * @param string $idb
582 * @param object $boucles
583 * @param object $crit
584 */
585function critere_SPHINX_inverse_dist($idb, &$boucles, $crit) {
586        $boucle = &$boucles[$idb];
587        if ($crit->not) {
588                critere_SPHINX_parinverse($idb, $boucles, $crit);
589        } else {
590                // sinon idem parent.
591                critere_inverse_dist($idb, $boucles, $crit);
592        }
593}
594
595/**
596 * Gestion des critères `{par}` et `{inverse}`
597 *
598 * @note
599 *     Sphinx doit toujours avoir le sens de tri (ASC ou DESC).
600 *
601 *     Version simplifié du critère natif de SPIP, avec une permission
602 *     pour les champs de type json `properties.truc`
603 *
604 * @param string $idb
605 * @param object $boucles
606 * @param object $crit
607**/
608function critere_SPHINX_parinverse($idb, $boucles, $crit, $sens = '') {
609        $boucle = &$boucles[$idb];
610        if ($crit->not) {
611                $sens = $sens ? "" : " . ' DESC'";
612        }
613
614        foreach ($crit->param as $tri){
615                $order = "";
616
617                // tris specifies dynamiquement
618                if ($tri[0]->type!='texte'){
619                        // calculer le order dynamique qui verifie les champs
620                        $order = calculer_critere_arg_dynamique($idb, $boucles, $tri, $sens);
621                } else {
622                        $par = array_shift($tri);
623                        $par = $par->texte;
624                        $order = "'$par'";
625                }
626
627
628                $t = $order.$sens;
629                $boucle->order[] = $t;
630        }
631}
632
633
634
Note: See TracBrowser for help on using the repository browser.