source: spip-zone/_plugins_/indexer/trunk/inc/indexer.php @ 93516

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

pouvoir dumper la table qu'on veut

File size: 11.9 KB
Line 
1<?php
2
3if (!defined('_ECRIRE_INC_VERSION')) return;
4
5// Constantes pour connexion à Sphinx
6defined('SPHINX_SERVER_HOST')   || define('SPHINX_SERVER_HOST', '127.0.0.1');
7defined('SPHINX_SERVER_PORT')   || define('SPHINX_SERVER_PORT', 9306);
8defined('SPHINX_DEFAULT_INDEX') || define('SPHINX_DEFAULT_INDEX', 'spip');
9
10// Charge les classes possibles de l'indexer
11if (!class_exists('Composer\\Autoload\\ClassLoader')) {
12        require_once _DIR_PLUGIN_INDEXER . 'lib/Composer/Autoload/ClassLoader.php';
13}
14
15$loader = new \Composer\Autoload\ClassLoader();
16
17// register classes with namespaces
18$loader->add('Indexer', _DIR_PLUGIN_INDEXER . 'lib');
19$loader->add('Sphinx',  _DIR_PLUGIN_INDEXER . 'lib');
20$loader->addPsr4('Spip\\Indexer\\Sources\\',  _DIR_PLUGIN_INDEXER . 'Sources');
21
22$loader->register();
23
24/**
25 * Renvoyer un indexeur configuré avec un (et peut-être un jour plusieurs) lieu de stockage des choses à indexer
26 *
27 * Par défaut renvoie l'indexeur avec le stockage Sphinx intégré en interne et les paramètres des define()
28 *
29 * @pipeline_appel indexer_indexer
30 * @return Retourne un objet Indexer, ayant été configuré avec la méthode registerStorage()
31 */
32function indexer_indexer(){
33        static $indexer = null;
34       
35        if (is_null($indexer)){
36                // On crée un indexeur
37                $indexer = new Indexer\Indexer();
38       
39                // On tente de le configurer avec Sphinx et les define()
40                try {
41                        $indexer->registerStorage(
42                                new Indexer\Storage\Sphinx(
43                                        new Sphinx\SphinxQL\SphinxQL(SPHINX_SERVER_HOST, SPHINX_SERVER_PORT),
44                                        SPHINX_DEFAULT_INDEX
45                                )
46                        );
47                } catch( \Exception $e ) {
48                        if (!$message = $e->getMessage()) {
49                                $message = _L('Erreur inconnue');
50                        }
51                        die("<p class='erreur'>$message</p>");
52                }
53               
54                // On le fait passer dans un pipeline
55                $indexer = pipeline('indexer_indexer', $indexer);
56        }
57       
58        return $indexer;
59}
60
61/**
62 * Renvoyer les sources de données disponibles dans le site
63 *
64 * Un pipeline "indexer_sources" est appelée avec la liste par défaut, permettant de retirer ou d'ajouter des sources.
65 *
66 * @pipeline_appel indexer_sources
67 * @return Sources Retourne un objet Sources listant les sources enregistrées avec la méthode register()
68 */
69function indexer_sources(){
70        static $sources = null;
71       
72        if (is_null($sources)){
73                include_spip('base/objets');
74                include_spip('inc/config');
75               
76                // On crée la liste des sources
77                $sources = new Indexer\Sources\Sources();
78               
79                // Toute la hiérarchie des rubriques
80                $sources->register('hierarchie_rubriques', new Spip\Indexer\Sources\HierarchieRubriques());
81               
82                // Toute la hiérarchie des mots
83                if (_DIR_PLUGIN_MOTS) {
84                        $sources->register('hierarchie_mots', new Spip\Indexer\Sources\HierarchieMots());
85                }
86               
87                // On ajoute chaque objet configuré aux sources à indexer
88                // Par défaut on enregistre les articles s'il n'y a rien
89                foreach (lire_config('indexer/sources_objets', array('spip_articles')) as $table) {
90                        if ($table) {
91                                $sources->register(
92                                        table_objet($table),
93                                        new Spip\Indexer\Sources\SpipDocuments(objet_type($table))
94                                );
95                        }
96                }
97               
98                // On passe les sources dans un pipeline
99                $sources = pipeline('indexer_sources', $sources);
100        }
101       
102        return $sources;
103}
104
105/**
106 * Indexer une partie des documents d'une source.
107 *
108 * Cette fonction est utilisable facilement pour programmer un job.
109 *
110 * @param string $alias_de_sources Alias donné à la source enregistrée dans les Sources du site
111 * @param mixed $start Début des documents à indexer
112 * @param mixed $end Fin des documents à indexer
113 * @return void
114 */
115function indexer_job_indexer_source($alias_de_sources, $start, $end){
116        // On va chercher l'indexeur du SPIP
117        $indexer = indexer_indexer();
118        // On va chercher les sources du SPIP
119        $sources = indexer_sources();
120        // On récupère celle demandée en paramètre
121        $source = $sources->getSource($alias_de_sources);
122        // On va chercher les documents à indexer
123        $documents = $source->getDocuments($start, $end);
124        // Et on le remplace (ou ajoute) dans l'indexation
125        $res = $indexer->replaceDocuments($documents);
126
127        // en cas d'erreur, on se reprogramme pour une autre fois
128        if (!$res) {
129                job_queue_add(
130                        'indexer_job_indexer_source',
131                        "Replan indexation $alias_de_sources $start $end",
132                        array($alias_de_sources, $start, $end, uniqid()),
133                        'inc/indexer',
134                        false, // duplication possible car ce job-ci n'est pas encore nettoyé
135                        time() + 15*60, // attendre 15 minutes
136                        -10 // priorite basse
137                );
138        }
139}
140
141
142
143
144function indexer_statistiques_indexes_depuis($date_reference = null) {
145        if (!$date_reference)
146                $date_reference = intval(lire_meta('indexer_derniere_reindexation'));
147
148        $query = "SELECT COUNT(*) AS c,
149        properties.objet AS objet,
150        (date_indexation > $date_reference) AS recent,
151        properties.source AS source
152        FROM " . SPHINX_DEFAULT_INDEX . "
153        GROUP BY recent, properties.objet, properties.source";
154
155        $sphinx = new Sphinx\SphinxQL\SphinxQL(SPHINX_SERVER_HOST, SPHINX_SERVER_PORT);
156
157        $all = $sphinx->allfetsel($query);
158
159        if (!is_array($all)
160        OR !is_array($all['query'])
161        OR !is_array($all['query']['docs'])) {
162                echo "<p class=error>" . _L('Erreur Sphinx')."</p>";
163        } else {
164                $liste = [];
165                foreach ($all['query']['docs'] as $l) {
166                        $liste[$l['source']][table_objet($l['objet'])][$l['recent']] = intval($l['c']);
167                }
168                return $liste;
169        }
170}
171
172
173
174function indexer_tout_reindexer_async($bloc = 100) {
175
176        foreach(indexer_lister_blocs_indexation($bloc) as $alias => $ids) {
177                foreach ($ids as $id) {
178                        $start = $id;
179                        $end = $id + $bloc-1;
180                        job_queue_add(
181                                'indexer_job_indexer_source',
182                                'Indexer '.$alias.' de '.$start.' à '.$end,
183                                array($alias, $start, $end),
184                                'inc/indexer',
185                                true // pas de duplication
186                        );
187                }
188        }
189
190        ecrire_meta('indexer_derniere_reindexation', time());
191}
192
193function indexer_lister_blocs_indexation($bloc = 100) {
194
195        // On récupère toutes les sources compatibles avec l'indexation
196        $sources = indexer_sources();
197
198        $liste = [];
199
200        foreach ($sources as $alias => $source) {
201                // Si une méthode pour définir explicitement existe, on l'utilise
202                if (method_exists($source, 'getObjet')){
203                        $objet_source = $source->getObjet();
204                }
205                // Sinon on cherche avec l'alias donné à la source
206                else {
207                        $objet_source = objet_type(strtolower($alias));
208                }
209
210                // alias = mots, objet_source = mot
211                // alias = hierarchie_rubriques, objet_source = rubrique
212                $liste[$alias] = [];
213
214                $parts = new \ArrayIterator($source->getParts($bloc));
215               
216                while ($parts->valid()) {
217                        $part = $parts->key();
218                        $liste[$alias][] = $part*$bloc;
219                        $parts->next();
220                }
221        }
222
223        return array_filter($liste);
224}
225
226// suggerer des mots (si aspell est installee)
227// code adapté de https://github.com/splitbrain/dokuwiki-plugin-spellcheck/blob/master/spellcheck.php
228function indexer_suggestions($mot) {
229
230        include_spip('lib/aspell');
231        if (!class_exists('Aspell')) {
232                spip_log('pas trouvé aspell.php', 'indexer');
233                return array();
234        }
235
236        try {
237                $spell = new Aspell($GLOBALS['spip_lang'],null,'utf-8');
238                // $spell->setMode(PSPELL_FAST);
239                if(!$spell->runAspell($mot, $out,$err,array('!','+html','@nbsp'))){
240                        spip_log("An error occurred while trying to run the spellchecker: ".$err, 'indexer');
241                        return null;
242                } 
243        } catch(Exception $e) {
244                spip_log($e, 'indexer');
245                return null;
246        }
247
248
249        // go through the result
250        $lines = split("\n",$out);
251        $rcnt  = count($lines)-1;    // aspell result count
252        for($i=$rcnt; $i>=0; $i--){
253                $line = trim($lines[$i]);
254                if($line[0] == '@') continue; // comment
255                if($line[0] == '*') continue; // no mistake in this word
256                if($line[0] == '+') continue; // root of word was found
257                if($line[0] == '?') continue; // word was guessed
258                if(empty($line)){
259                        continue;
260                }
261                // now get the misspelled words
262                if (preg_match('/^& ([^ ]+) (\d+) (\d+): (.*)/',$line,$match)){
263                        // match with suggestions
264                        $word = $match[1];
265                        $off  = $match[3]-1;
266                        $sug  = split(', ',$match[4]);
267                } else if (preg_match('/^# ([^ ]+) (\d+)/',$line,$match)) {
268                        // match without suggestions
269                        $word = $match[1];
270                        $off  = $match[2]-1;
271                        $sug  = null;
272                } else {
273                        // couldn't parse output
274                        spip_log("The spellchecker output couldn't be parsed line $i '$line'", 'indexer');
275                        return null;
276                }
277        }
278
279        // aspell peut nous renvoyer des mots accentués
280        // qui auront la même valeur dans sphinx,
281        // il faut donc unifier
282        // ne pas non plus accepter de mots avec apostrophe
283        $suggests = array();
284        if (is_array($sug)) {
285                foreach($sug as $t) {
286                        if (strpos($t, "'") === false) {
287                                $s = translitteration($t);
288                                $suggests[$s] = $t;
289                        }
290                }
291                $sug = $suggests;
292        }
293       
294        return $sug;
295}
296
297
298// trier les mots par nombre d'occurrences reelles dans la base Sphinx
299// et supprimer ceux qui n'y figurent pas
300// on se base sur la forme exacte (=mot) ; et sans espaces ni tirets !
301function indexer_motiver_mots($mots) {
302        $liste = [];
303        foreach($mots as $i => $m) {
304                $mots[$i] = '='.preg_replace('/\W/', '', $m);
305        }
306        $m = join(' ', $mots);
307        $query = "SELECT id FROM " . SPHINX_DEFAULT_INDEX . " WHERE MATCH('$m') LIMIT 1";
308
309        $sphinx = new Sphinx\SphinxQL\SphinxQL(SPHINX_SERVER_HOST, SPHINX_SERVER_PORT);
310        $all = $sphinx->allfetsel($query);
311
312        if (!is_array($all)
313        OR !is_array($all['query'])
314        OR !is_array($all['query']['meta'])) {
315                // echo "<p class=error>" . _L('Erreur Sphinx')."</p>";
316        } else {
317                if (is_array($all['query']['meta']['keywords'])) {
318                        foreach($all['query']['meta']['keywords'] as $i => $w) {
319                                $translitt = str_replace('=', '', $w['keyword']);
320                                if (intval($w['docs']) > 3)
321                                        $liste[$translitt] = intval($w['docs']);
322                        }
323                        $liste = array_filter($liste);
324                        arsort($liste);
325                }
326                return array_keys($liste);
327        }
328}
329
330// compare une liste de suggestions au contenu indexé dans la base sphinx
331function indexer_suggestions_motivees($mot) {
332        $sug = indexer_suggestions($mot);
333        if (is_array($sug) AND count($sug) > 0) {
334                $sug = indexer_motiver_mots($sug);
335        }
336        return $sug;
337}
338
339/*
340 * faire un DUMP SQL de notre base sphinx
341 * index: nom de l'index (table) a dumper
342 * dest: fichier destination (null: stdout)
343 * bloc : nombre d'enregistrements a rapatrier a chaque tour (maximum 1000)
344 * usage :
345      include_spip('inc/indexer');
346      indexer_dumpsql();
347      // indexer_dumpsql(SPHINX_DEFAULT_INDEX, 'tmp/indexer.sql', 1000);
348*/
349function indexer_dumpsql($index = null, $dest = null, $bloc = 100) {
350        if (is_null($dest)) {
351                $dest = 'php://stdout';
352        }
353
354        $fp = fopen($dest,'w');
355        if (!$fp) {
356                spip_log('Impossible d ouvrir '.$dest, 'indexer');
357                return false;
358        }
359
360        $sphinx = new Sphinx\SphinxQL\SphinxQL(SPHINX_SERVER_HOST, SPHINX_SERVER_PORT);
361
362
363        $version = unserialize(lire_meta('plugin'));
364        $version = $version['INDEXER']['version'];
365
366        if (is_null($index)) {
367                $index = SPHINX_DEFAULT_INDEX;
368        }
369
370        $comm = '# SPIP indexer / SphinxQL Dump
371# version '. $version .'
372# http://contrib.spip.net/Indexer
373#
374# Host: '.SPHINX_SERVER_HOST.':'.SPHINX_SERVER_PORT.'
375# Generation Time: '.date(DATE_ISO8601).'
376# Server version: (unknown)
377# PHP Version: '. phpversion() .'
378#
379# Database : `'. $index .'`
380#
381
382';
383
384
385        $query = "DESC ".$index;
386        $all = $sphinx->allfetsel($query);
387
388        if (isset($all['query']['docs'])) {
389                $comm .= '# --------------------------------------------------------
390
391#
392# Table structure for table `' . $index . '`
393#
394
395CREATE TABLE `' . $index . '` (
396';
397                $fields = [];
398                foreach($all['query']['docs'] as $doc) {
399                        $fields[] = "\t" . '`' . $doc['Field'] .'` ' . $doc['Type'];
400                }
401                $comm .= join(",\n", $fields);
402                $comm .= "
403)
404
405
406#
407# Dumping data for table `" . $index . "`
408#
409
410";
411        }
412
413        if (!fwrite($fp, $comm)) {
414                spip_log('Impossible d ecrire dans '.$dest, 'indexer');
415                return false;
416        }
417
418        do {
419               
420                $where = isset($begin) ? " WHERE id > $begin " : '';
421                $query = "SELECT * FROM " . $index . $where . " ORDER BY id ASC LIMIT 0,$bloc";
422
423                $all = $sphinx->allfetsel($query);
424                $cdocs = count($all['query']['docs']);
425                if ($cdocs > 0) {
426                        foreach($all['query']['docs'] as $doc) {
427                                $sql = 'INSERT INTO ' . $index . ' ('
428                                        . join(', ', array_keys($doc))
429                                        . ') VALUES ('
430                                        . join(', ', array_map('_q', $doc))
431                                        . ');' . "\n";
432                               
433                                if (!fwrite($fp, $sql)) {
434                                        spip_log('Impossible d ecrire dans '.$dest, 'indexer');
435                                        return false;
436                                }
437                        }
438                        $begin = $all['query']['docs'][$cdocs-1]['id'];
439                }
440        } while ($cdocs > 0);
441
442        if ($fp) fclose($fp);
443        return true;
444}
Note: See TracBrowser for help on using the repository browser.