source: spip-zone/_plugins_/couteau_suisse/outils/glossaire_fonctions.php @ 75250

Last change on this file since 75250 was 75250, checked in by patfr@…, 6 years ago

Glossaire Interne : suite des vérifications...
+ notice PHP

File size: 15.2 KB
Line 
1<?php
2
3// Outils GLOSSAIRE - 26 mai 2007
4// Serieuse refonte et integration au Couteau Suisse : Patrice Vanneufville
5// Doc : http://contrib.spip.net/?article2206
6
7include_spip('inc/charsets');
8
9// liste des accents (sans casse)
10define('_GLOSSAIRE_ACCENTS', '#(19[2-9]|2[023][0-9]|21[0-46-9]|24[0-689]|25[0-4]|33[89]|35[23]|376)|a(?:acute|circ|elig|grave|ring|tilde|uml)|ccedil|e(?:acute|circ|grave|th|uml)|i(?:acute|circ|grave|uml)|ntilde|o(?:acute|circ|elig|grave|slash|tilde|uml)|s(?:caron|zlig)|thorn|u(?:acute|circ|grave|uml)|y(?:acute|uml)');
11
12// on calcule ici la constante _GLOSSAIRE_QUERY, surchargeable dans config/mes_options.php
13function glossaire_groupes() {
14        $groupes = trim($GLOBALS['glossaire_groupes']);
15        if(!strlen($groupes)) return _q('Glossaire');
16                else {
17                        $groupes = explode(':', $groupes);
18                        foreach($groupes as $i=>$g) $groupes[$i] = _q(trim($g));
19                        return join(" OR type=", $groupes);
20                }
21}
22
23// Separateur des titres de mots stockes en base
24if(!defined('_GLOSSAIRE_TITRE_BASE_SEP')) define('_GLOSSAIRE_TITRE_BASE_SEP', '/');
25// Separateur utilise pour fabriquer le titre de la fenetre de glossaire (fichiers fonds/glossaire_xx.html).
26if(!defined('_GLOSSAIRE_TITRE_SEP')) define('_GLOSSAIRE_TITRE_SEP', '<br />');
27// Balises a echapper avant le traitement du glossaire
28if(!defined('_GLOSSAIRE_ECHAPPER')) define('_GLOSSAIRE_ECHAPPER', 'html|code|cadre|frame|script|cite|acronym|abbr|a');
29// chaine pour interroger la base (SPIP <= 1.92)
30if(!defined('_SPIP19300'))
31        @define('_GLOSSAIRE_QUERY', 'SELECT id_mot, titre, texte, descriptif FROM spip_mots WHERE type=' . glossaire_groupes() . ' ORDER BY id_mot ASC');
32               
33// surcharge possible de cette fonction glossaire_generer_url_dist par : glossaire_generer_url($id_mot, $titre_mot)
34// si elle existe, elle sera utilisee pour generer l'url cliquable des mots trouves
35//   exemple pour annuler le clic : function glossaire_generer_url($id_mot, $titre_mot) { return 'javascript:;'; }
36function glossaire_generer_url_dist($id_mot, $titre_mot) {
37        if(defined('_SPIP19300')) 
38                return generer_url_entite($id_mot, 'mot');
39        // avant SPIP 2.0 :
40        charger_generer_url(); return generer_url_mot($id_mot);
41}
42
43// surcharge possible de cette fonction glossaire_generer_mot_dist par : glossaire_generer_mot($id_mot, $mot)
44// si elle existe, elle sera utilisee pour remplacer le mot detecte dans la phrase
45/* exemple pour utiliser un fond personnalise, mettre une couleur de groupe ou inserer un logo par exemple :
46        function glossaire_generer_mot($id_mot, $mot) {
47                return recuperer_fond('/fonds/mon_glossaire', array('id_mot'=>$id_mot, 'mot'=>$mot));
48        }*/
49function glossaire_generer_mot_dist($id_mot, $mot) {
50        return $mot;
51}
52
53/* surcharge possible de cette fonction glossaire_attributs_lien_dist par : glossaire_attributs_lien($lien, $titre, $gloss_id)
54 si elle existe, elle sera utilisee pour les attributs (sauf class et name) du <a> place autour du mot detecte.
55 $lien : lien du mot - $titre : titre brut du mot - $les_titres : array() des differents titres possibles du mot
56 Exemple :
57        function glossaire_attributs_lien($id_mot, $lien, $titre, $les_titres) {
58                return "href='$lien' title=\"" . attribut_html($les_titres[0]). '"';
59        } */
60function glossaire_attributs_lien_dist($id_mot, $lien, $titre, $les_titres) {
61        return "href='$lien'";
62}
63
64
65// traitement pour #TITRE/mots : retrait des expressions regulieres
66function cs_glossaire_titres($titre) {
67        if(strpos($titre, ',')===false) return $titre;
68        list(,,$mots) = glossaire_parse($titre);
69        return $mots;
70}
71
72// Cette fonction retire du texte les boites de definition et les liens du glossaire
73function cs_retire_glossaire($texte) {
74        $texte = preg_replace(',<span class="gl_(jst?|d[td])".*?</span>,s', '', $texte);
75        if(!defined('_GLOSSAIRE_JS')) $texte = preg_replace(',<span class="gl_dl">.*?</span>,s', '', $texte);
76        return preg_replace(',<a [^>]+class=\'cs_glossaire\'><span class=\'gl_mot\'>(.*?)</span></a>,s', '$1', $texte);
77}
78$GLOBALS['cs_introduire'][] = 'cs_retire_glossaire';
79
80// remplace les accents unicode par l'equivalent charset/unicode/html
81function glossaire_accents($regexpr) {
82        if (strpos($regexpr, '&')===false) return $regexpr;
83        return preg_replace_callback(",&#([0-9]+);,", 'glossaire_accents_callback', str_replace('& ','&amp; ',$regexpr));
84}
85
86// $matches est un caractere unicode sous forme &#XXX;
87// ici on cherche toutes les formes de ce caractere, minuscule ou majuscule : unicode, charset et html
88function glossaire_accents_callback($matches) {
89        $u = unicode2charset($matches[0]);      // charset
90        $u2 = init_mb_string()?mb_strtoupper($u):strtoupper($u);        // charset majuscule
91        $u3 = htmlentities($u2, ENT_QUOTES, $GLOBALS['meta']['charset']);       // html majuscule
92        $u4 = html2unicode($u3); // unicode majuscule
93        $a = array_unique(array($u, $u2, htmlentities($u, ENT_QUOTES, $GLOBALS['meta']['charset']), $u3, $matches[0], $u4));
94//      $a = array_unique(array($u, htmlentities($u, ENT_QUOTES, $GLOBALS['meta']['charset']), $matches[0]));
95        return '(?:'.join('|', $a).')';
96}
97function glossaire_echappe_balises_callback($matches) {
98 global $gloss_ech, $gloss_ech_id;
99 $gloss_ech[] = $matches[0];
100 return '@@E'.$gloss_ech_id++.'@@';
101}
102function glossaire_echappe_mot_callback($matches) {
103 global $gloss_mots, $gloss_mots_id, $gloss_id;
104 $gloss_mots[] = $matches[0];
105 return '@@M'.$gloss_mots_id++.'#'.$gloss_id.'@@';
106}
107
108function glossaire_safe($texte) {
109        // on retire les notes avant propre()
110        return safehtml(cs_propre(preg_replace(', *\[\[(.*?)\]\],msS', '', nl2br(trim($texte)))));
111}
112
113// renvoie le tableau des mots du glossaire
114function glossaire_query_tab() {
115        // interrogation personnalisee de la base
116        if(defined('_GLOSSAIRE_QUERY')) {
117                $res = array();
118                $fetch = function_exists('sql_fetch')?'sql_fetch':'spip_fetch_array';
119                $query = spip_query(_GLOSSAIRE_QUERY);
120                while($r = $fetch($query)) $res[] = $r;
121                return $res;
122        }
123        return sql_allfetsel('id_mot,titre,texte,descriptif', 'spip_mots', 'type='.glossaire_groupes(), '', 'id_mot ASC');
124}
125
126// parse toutes les formes du titre d'un mot-cle du glossaire
127// prendre en compte les formes du mot : architrave/architraves
128function glossaire_parse($titre) {
129        $mots = $regs = $titres = array();
130        foreach(explode(_GLOSSAIRE_TITRE_BASE_SEP, str_replace('</','@@tag@@',$titre)) as $m) {
131                // interpretation des expressions regulieres grace aux virgules : ,un +mot,i
132                $m = trim(str_replace('@@tag@@','</',$m));
133                if(strncmp($m,',',1)===0) $regs[] = $m;
134                else {
135                        $mots[] = charset2unicode($m);
136                        $titres[] = $m;
137                }
138        }
139        if(count($titres))
140                $titres = join(_GLOSSAIRE_TITRE_SEP, $titres);
141        elseif(count($regs)) {
142                preg_match('/^,(.*),\w*$/', $regs[0], $rr);
143                if (strpos($titres = $rr[1], '\\')!==false) {
144                        $titres = preg_replace('@\\\\([\.\\\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|:,-])@', '$1', $titres);
145                        $titres = preg_replace(',\\\\[bswd],i', '', $titres);
146                }
147        } else
148                $titres = '??';
149        if(count($mots)) {
150                $mots = array_unique($mots);
151                array_walk($mots, 'cs_preg_quote');
152                // expliciter l'apostrophe et les accents
153                $mots = str_replace("'", "(?:'|&#8217;)", glossaire_accents(join('|', $mots)));
154        } else $mots = '';
155        $ok_regexp = count($regs)?preg_replace($regs, 't', 'test', 1)!==null:true;
156        return array($mots,$ok_regexp?$regs:array(), $titres, $ok_regexp);
157}
158
159function glossaire_gogogo($texte, $mots, $limit, &$unicode) {
160        // prudence 2 : on protege TOUTES les balises HTML comprenant le mot
161        if (strpos($texte, '<')!==false)
162                $texte = preg_replace_callback(",<[^>]*(?:$mots)[^>]*>,Ui", 'glossaire_echappe_balises_callback', $texte);
163        // prudence 3 : en iso-8859-1, (\W) comprend les accents, mais pas en utf-8... Donc on passe en unicode
164        if(($GLOBALS['meta']['charset'] != 'iso-8859-1') && !$unicode) 
165                { $texte = charset2unicode($texte); $unicode = true; }
166        // prudence 4 : on neutralise le mot si on trouve un accent (HTML ou unicode) juste avant ou apres
167        if (strpos($texte, '&')!==false) {
168                $texte = preg_replace_callback(',&(?:'._GLOSSAIRE_ACCENTS.");(?:$mots),i", 'glossaire_echappe_balises_callback', $texte);
169                $texte = preg_replace_callback(",(?:$mots)&(?:"._GLOSSAIRE_ACCENTS.');,i', 'glossaire_echappe_balises_callback', $texte);
170        }
171        // a chaque mot reconnu, on pose une balise temporaire cryptee
172        return trim(preg_replace_callback(",(?<=\W)(?:$mots)(?=\W),i", "glossaire_echappe_mot_callback", " $texte ", $limit));
173}
174
175// cette fonction n'est pas appelee dans les balises html : html|code|cadre|frame|script|acronym|cite|abbr|a
176// si $liste=true alors la fonction renvoie la liste des mots trouves
177// chaque element du tableau renvoye est array('mot trouve', id_mot, 'lien mot', 'titre mot');
178function cs_rempl_glossaire($texte, $liste=false) {
179        global $gloss_tab1, $gloss_tab2, $gloss_id, $gloss_mots, $gloss_mots_id, $gloss_ech, $gloss_ech_id;
180        // si [!glossaire] est trouve on sort
181        if(strpos($texte, _CS_SANS_GLOSSAIRE)!==false)
182                return $liste?array():str_replace(_CS_SANS_GLOSSAIRE, '', $texte);
183        // mise en static de la table des mots pour eviter d'interrroger la base a chaque fois
184        // attention aux besoins de memoire...
185        static $limit, $glossaire_generer_url, $glossaire_generer_mot, $glossaire_echappements, $glossaire_array = NULL;
186        if(!isset($glossaire_array)) {
187                $glossaire_array = glossaire_query_tab();
188                $glossaire_generer_url = function_exists('glossaire_generer_url')?'glossaire_generer_url':'glossaire_generer_url_dist';
189                $limit = defined('_GLOSSAIRE_LIMITE')?_GLOSSAIRE_LIMITE:-1;
190                $glossaire_generer_mot = function_exists('glossaire_generer_mot')
191                        ?'glossaire_generer_mot($m[2], $GLOBALS[\'gloss_mots\'][$m[1]])':'$GLOBALS[\'gloss_mots\'][$m[1]]';
192                $glossaire_generer_mot = 'return "<a ".$GLOBALS["gloss_tab1"][$m[2]]."_".$GLOBALS["gl_i"]++."\' class=\'cs_glossaire\'><span class=\'gl_mot\'>".'.$glossaire_generer_mot.'."</span>".$GLOBALS["gloss_tab2"][$m[2]]."</a>";';
193                $glossaire_generer_mot = create_function('$m', $glossaire_generer_mot);
194                $glossaire_echappements = create_function('$m','return $GLOBALS[\'gloss_ech\'][$m[1]];');
195        }
196        $unicode = false;
197        // initialisation des globales d'echappement
198        $gloss_ech = $gloss_mots = array();
199        $gloss_ech_id = $gloss_mots_id = 0;
200        // prudence 1 : protection des liens SPIP
201        if(strpos($texte, '[') !== false) 
202                $texte = preg_replace_callback(',\[[^][]*->>?[^]]*\],msS', 'glossaire_echappe_balises_callback', $texte);
203        // parcours de tous les mots, sauf celui qui peut faire partie du contexte (par ex : /spip.php?mot5)
204        $mot_contexte = (isset($GLOBALS['contexte']['id_mot']) && $GLOBALS['contexte']['id_mot'])
205                ?$GLOBALS['contexte']['id_mot']:_request('id_mot');
206        foreach ($glossaire_array as $mot) if (($gloss_id = $mot['id_mot']) <> $mot_contexte) {
207                // parser le mot-cle du glossaire
208                // contexte de langue a prendre en compte ici
209                list($les_mots, $les_regexp, $les_titres, $ok_regexp) = glossaire_parse($titre = extraire_multi($mot['titre']));
210                $mot_present = false;
211                if(!$ok_regexp) 
212                        spip_log(couteauprive_T('glossaire:nom').' - Erreur REGEXP : '.var_export($les_regexp, 1));
213                elseif(count($les_regexp)) {
214                        // a chaque expression reconnue, on pose une balise temporaire cryptee
215                        // ce remplacement est puissant, attention aux balises HTML ; par exemple, eviter : ,div,i
216                        $texte = preg_replace_callback($les_regexp, 'glossaire_echappe_mot_callback', $texte, $limit);
217                        // TODO 1 : sous PHP 5.0, un parametre &$count permet de savoir si un remplacement a eu lieu
218                        // et s'il faut construire la fenetre de glossaire.
219                        // TODO 2 : decrementer le parametre $limit pour $les_mots, si &$count est renseigne.
220                        // en attendant, construisons qd meme la fenetre...
221                        $mot_present = true;
222                }
223                if($les_mots) {
224                        if(preg_match(",\W(?:$les_mots)\W,i", " $texte ")) {
225                                $texte = glossaire_gogogo($texte, $les_mots, $limit, $unicode);
226                                $mot_present = true;
227                        }
228                }
229                // si un mot est trouve, on construit la fenetre de glossaire
230                if($mot_present) {
231                        $lien = $glossaire_generer_url($gloss_id, $titre);
232                        // $definition =strlen($mot['descriptif'])?$mot['descriptif']:$mot['texte'];
233                        if($liste)
234                                // on ne renvoie que la liste des mots trouves
235                                $gloss_tab1[$gloss_id] = array($gloss_id, $lien, $les_titres);
236                        else {
237                                // l'attribut 'name' en fin de chaine est complete plus tard pour eviter les doublons :
238                                $gloss_tab1[$gloss_id] = (function_exists('glossaire_attributs_lien')
239                                        ?glossaire_attributs_lien($gloss_id, $lien, $titre, explode(_GLOSSAIRE_TITRE_SEP, $les_titres))
240                                        :"href='$lien'") . " name='mot$gloss_id";
241                                $gloss_tab2[$gloss_id] = defined('_CS_PRINT')?'':recuperer_fond(
242                                        defined('_GLOSSAIRE_JS')?'fonds/glossaire_js':'fonds/glossaire_css', 
243                                        array('id_mot' => $gloss_id, 'titre' => $les_titres, 
244                                                'texte' => glossaire_safe($mot['texte']), 
245                                                'descriptif' => glossaire_safe($mot['descriptif'])));
246                        }
247                }
248        }
249        $GLOBALS['gl_i'] = 0;
250        if($liste) $texte = (preg_match_all(',@@M(\d+)#(\d+)@@,', $texte, $reg, PREG_SET_ORDER) 
251                        && array_walk($reg,
252                create_function('&$v,$k', '$v=array_merge(array($GLOBALS[\'gloss_mots\'][$v[1]]),$GLOBALS[\'gloss_tab1\'][$v[2]]);'))
253                )?$reg:array();
254        else {
255                // remplacement des echappements
256                $texte = preg_replace_callback(',@@E(\d+)@@,', $glossaire_echappements, $texte);
257                // remplacement final des balises posees ci-dessus
258                $texte = preg_replace_callback(',@@M(\d+)#(\d+)@@,', $glossaire_generer_mot, $texte);
259        }
260        // nettoyage
261        unset($gloss_tab1, $gloss_tab2, $gloss_id, $gloss_mots, $gloss_mots_id, $gloss_ech, $gloss_ech_id);
262        // ordre correct des balises en cas d'acronyme ou d'abreviation
263        if(strpos($texte, '</span></a></a')!==false)
264                $texte = preg_replace(',(<a(bbr|cronym) [^>]+>)(<a [^>]+class=\'cs_glossaire\'><span class=\'gl_mot\'>)(.*?)</span>(<span class="gl_.*?</span>)</a></a\\2>,smS', '$3$1$4</a$2></span>$5</a>', $texte);
265        return $texte;
266}
267
268// filtre appliquant l'insertion du glossaire
269function cs_glossaire($texte) {
270        return cs_echappe_balises(_GLOSSAIRE_ECHAPPER, 'cs_rempl_glossaire', $texte);
271}
272
273// filtre renvoyant la liste des mots trouves dans le texte
274function cs_mots_glossaire($texte, $type='', $sep='') {
275        if(strpos($texte, "<span class='gl_mot'>")!==false && preg_match_all(",'gl_mot'>(.*?)</span>,", $texte, $reg))
276                // glossaire deja present, on simplifie donc le texte
277                $texte = join('  ', $reg[1]);
278        $mots = cs_echappe_balises(_GLOSSAIRE_ECHAPPER, 'cs_rempl_glossaire', $texte, true);
279        if(!count($mots)) return strlen($sep)?'':$mots;
280        $lien = '$v="<a href=\"$v[2]\"';
281        $titre = strpos($type,'_unique')===false?'str_replace("<br />"," / ", $v[3])':'reset(explode(_GLOSSAIRE_TITRE_SEP, $v[3]))';
282        switch($type) {
283                case '':return $mots;
284                case 'id_mot':
285                        array_walk($mots, create_function('&$v', '$v=$v[1];'));
286                        break;
287                case 'mot':
288                        array_walk($mots, create_function('&$v', '$v=$v[0];'));
289                        break;
290                case 'titre': case 'titre_unique':
291                        array_walk($mots, create_function('&$v', "\$v=$titre;"));
292                        break;
293                case 'lien_mot':
294                        array_walk($mots, create_function('&$v', $lien.'>$v[0]</a>";'));
295                        break;
296                case 'lien_titre': case 'lien_titre_unique':
297                        array_walk($mots, create_function('&$v', $lien.'>".'.$titre.'."</a>";'));
298                        break;
299                case 'nuage': case 'nuage_unique':
300                        $stats = array(); $min = 999999; $max = 0;
301                        foreach($mots as $m) $stats[$m[1]]++;
302                        $m = min($stats); $d = max($stats) - $m;
303                        array_walk($stats, create_function('&$v',  $d?"\$v=round((\$v-$m)*9/$d)+1;":'$v=1;')); // valeurs de 1 a 10
304                        array_walk($mots, create_function('&$v,$k,&$s', $lien.' class=\"nuage".$s[$v[1]]."\">".'.$titre.'."</a>";'), $stats);
305                        break;
306                default:return "#GLOSSAIRE/$type?";
307        }
308        $mots = array_unique($mots);
309        return strlen($sep)?join($sep, $mots):$mots;
310}
311
312?>
Note: See TracBrowser for help on using the repository browser.