source: spip-zone/_plugins_/trad-lang/trunk/inc/salvatore.php @ 124343

Last change on this file since 124343 was 124343, checked in by Cerdic, 2 months ago

masquer les mots de passe dans le mail d'erreur en cas de fail

  • Property svn:eol-style set to native
File size: 18.5 KB
Line 
1<?php
2
3/*
4    This file is part of Salvatore, the translation robot of Trad-lang (SPIP)
5
6    Salvatore is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    Trad-Lang is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with Trad-Lang; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20    Copyright 2003-2013
21        Florent Jugla <florent.jugla@eledo.com>,
22        Philippe Riviere <fil@rezo.net>,
23        Chryjs <chryjs!@!free!.!fr>,
24                kent1 <kent1@arscenic.info>
25*/
26
27
28/**
29 * initialiser salvatore si besoin
30 * peut etre appelle plusieurs fois
31 * @param string|array $log_function
32 * @param bool $display_time
33 * @throws Exception
34 */
35function salvatore_init($log_function = null, $display_time = false){
36        static $initialized;
37
38        // set log function if any
39        if ($log_function){
40                salvatore_log('', $log_function, $display_time);
41        }
42
43        if (is_null($initialized)){
44                @ini_set('memory_limit', '50M');
45                if (!defined('_DEBUG_TRAD_LANG')){
46                        define('_DEBUG_TRAD_LANG', 1); // undef si on ne veut pas de messages
47                }
48
49                if (!defined('_DIR_SALVATORE')){
50                        define('_DIR_SALVATORE', _DIR_RACINE . 'salvatore/');
51                }
52
53                if (!defined('_DIR_SALVATORE_TRADUCTIONS')){
54                        define('_DIR_SALVATORE_TRADUCTIONS', _DIR_SALVATORE . 'traductions/');
55                }
56
57                if (!defined('_DIR_SALVATORE_TMP')){
58                        define('_DIR_SALVATORE_TMP', _DIR_SALVATORE . 'tmp/');
59                }
60
61                if (!defined('_DIR_SALVATORE_MODULES')){
62                        define('_DIR_SALVATORE_MODULES', _DIR_SALVATORE . 'modules/');
63                }
64
65                if (!defined('_DIR_SALVATORE_DEPOTS')){
66                        define('_DIR_SALVATORE_DEPOTS', _DIR_SALVATORE . 'depots/');
67                }
68
69                if (defined('_ID_AUTEUR_SALVATORE') and is_numeric(_ID_AUTEUR_SALVATORE)){
70                        $GLOBALS['visiteur_session'] = array();
71                        $GLOBALS['visiteur_session']['id_auteur'] = _ID_AUTEUR_SALVATORE;
72                        // TODO : charger une session complete ?
73                }
74
75                // par defaut on relit les fichiers si modifies depuis moins de 1J
76                if (!defined('_SALVATORE_LECTEUR_REFRESH_DELAY')){
77                        define('_SALVATORE_LECTEUR_REFRESH_DELAY', 24 * 3600);
78                }
79
80                // pourcentage de traduction a partir duquel on exporte la langue
81                if (!defined('_SALVATORE_SEUIL_EXPORT')) {
82                        define('_SALVATORE_SEUIL_EXPORT', 50);
83                }
84
85                if (!defined('_SALVATORE_AUTHOR_COMMITS')) {
86                        define('_SALVATORE_AUTHOR_COMMITS', 'Salvatore <salvatore@rezo.net>');
87                }
88
89                // TODO : a tester/valider quand on sera en prod si on utilise encore
90                if (!defined('_SALVATORE_SVN_PROPSET')) {
91                        define('_SALVATORE_SVN_PROPSET', false);
92                }
93
94
95                if (!isset($GLOBALS['idx_lang'])){
96                        $GLOBALS['idx_lang'] = 0;
97                }
98
99                // verifications des repertoires
100                foreach ([_DIR_SALVATORE, _DIR_SALVATORE_TRADUCTIONS, _DIR_SALVATORE_MODULES, _DIR_SALVATORE_DEPOTS, _DIR_SALVATORE_TMP] as $dir){
101                        salvatore_check_dir($dir);
102                }
103                $initialized = true;
104        }
105}
106
107
108/**
109 * chargement du fichier traductions.txt
110 * Construit une liste de modules avec pour chacun un tableau associatif
111 *
112 * @param string $fichier_traductions
113 * @return array
114 * @throws Exception
115 */
116function salvatore_charger_fichier_traductions($fichier_traductions = null){
117
118        salvatore_init();
119        if (is_null($fichier_traductions)){
120                $fichier_traductions = _DIR_SALVATORE_TRADUCTIONS . 'traductions.txt';
121        }
122        salvatore_check_file($fichier_traductions);
123
124        $lignes = file($fichier_traductions);
125        $lignes = array_map('trim', $lignes);
126        $lignes = array_filter($lignes);
127
128        $liste_trad = array();
129        foreach ($lignes as $ligne){
130                if ($ligne[0]!=='#'){
131                        $liste = explode(';', trim($ligne));
132                        $methode = $url = $branche = $dir = $module = $lang = '';
133
134                        // deprecated ancien format, forcement en svn
135                        // liste courte de type
136                        // url;module;lang
137                        if (count($liste)<=3){
138                                $methode = 'svn';
139                                $branche = '';
140                                $url = $liste[0];
141                                if (empty($liste[1])){
142                                        $module = preg_replace('#.*/(.*)$#', '$1', $url);
143                                } else {
144                                        $module = $liste[1];
145                                }
146                                if (empty($liste[2])){
147                                        $lang = 'fr';
148                                } else {
149                                        $lang = $liste[2];
150                                }
151                        }
152                        // format complet et explicite de 6 valeurs
153                        // seule les valeurs pour branche et dir peuvent etre vide (branche master par defaut en git)
154                        // svn;url;;;module;lang
155                        // git;url;master;subdir/tolang;module;lang
156                        else {
157                                list($methode, $url, $branche, $dir, $module, $lang) = $liste;
158                        }
159                        $methode = trim($methode);
160                        $url = trim($url);
161                        $url = rtrim($url, '/'); // homogeneiser
162                        $dir = trim($dir);
163                        $dir = trim($dir, '/'); // homogeneiser
164                        $branche = trim($branche);
165                        $module = trim($module);
166                        $lang = trim($lang);
167
168                        if ($methode
169                                and $url
170                                and $module
171                                and $lang){
172                                // que fait la $GLOBALS['modules'] ?
173                                if (empty($GLOBALS['modules']) or in_array($module, $GLOBALS['modules'])){
174
175                                        // unifier les urls git en https, plus simple a gerer car ne necessitent pas une cle ssh sur le user php (www-data)
176                                        if (strpos($url, "git@git.spip.net:") === 0) {
177                                                $url = "https://git.spip.net/" . substr($url, 17);
178                                        }
179
180                                        // definir un dir checkout unique meme si plusieurs modules de meme nom dans differents repos
181                                        $d = explode('/', $url);
182                                        while (count($d) and in_array(end($d), ['', 'lang', 'trunk', 'ecrire'])){
183                                                array_pop($d);
184                                        }
185                                        $source = '';
186                                        if (end($d)){
187                                                $source = basename(end($d), '.git');
188                                                $source = '--' . preg_replace(',[^\w-],', '_', $source);
189                                                if ($branche and $branche !== 'master') {
190                                                        $source .= "@$branche";
191                                                }
192                                        }
193                                        $dir_module = "{$module}{$source}-" . substr(md5("$methode:$url:$branche:$dir"), 0, 5);
194                                        $dir_checkout = preg_replace(",\W+,", "-", "$methode-$url") . ($branche ? "--$branche-" : "-") . substr(md5("$methode:$url:$branche"), 0, 5);
195
196                                        $liste_trad[] = [
197                                                'methode' => $methode,
198                                                'url' => $url,
199                                                'branche' => $branche,
200                                                'dir' => $dir,
201                                                'module' => $module,
202                                                'lang' => $lang,
203                                                'dir_module' => $dir_module,
204                                                'dir_checkout' => $dir_checkout,
205                                        ];
206                                }
207                        } else {
208                                salvatore_log("Fichier $fichier_traductions, IGNORE ligne incomplete : $ligne");
209                        }
210                }
211        }
212        return $liste_trad;
213}
214
215/**
216 * Filtrer la liste complete pour ne garder que un ou plusieurs modules specifiques
217 * @param array $liste_trad
218 * @param string|array $modules
219 * @return array
220 */
221function salvatore_filtrer_liste_traductions($liste_trad, $modules) {
222        if (is_string($modules)) {
223                $modules = explode(',', $modules);
224        }
225        $modules = array_map('trim', $modules);
226        $liste_filtree = array();
227        foreach ($liste_trad as $trad) {
228                if (in_array($trad['module'], $modules)) {
229                        $liste_filtree[] = $trad;
230                }
231        }
232        return $liste_filtree;
233}
234
235/**
236 * Extraire la lang d'un fichier de langue d'un module donne
237 * @param string $module
238 * @param string $fichier_lang
239 * @return array|mixed|string|string[]
240 */
241function salvatore_get_lang_from($module, $fichier_lang) {
242        $lang = str_replace($module, '__', basename($fichier_lang, '.php'));
243        $lang = explode('___', $lang, 2);
244        $lang = end($lang);
245
246        return $lang;
247}
248
249/**
250 * URL du gestionnaire trad-lang exportee dans les xml
251 * @return mixed
252 */
253function salvatore_get_self_url() {
254        $url_gestionnaire = $GLOBALS['meta']['adresse_site'];
255        if (defined('_SALVATORE_TEST_URL_GESTIONNAIRE')) {
256                $url_gestionnaire = _SALVATORE_TEST_URL_GESTIONNAIRE;
257        }
258        return $url_gestionnaire;
259}
260
261/**
262 * Verifier si un module de langue est gere par ce salvatore
263 * @param $dir_module
264 * @param $module
265 * @return string
266 *   l'autre gestionnaire de trad si c'est pas nous
267 *   chaine vide si c'est bien nous qui gerons
268 */
269function salvatore_verifier_gestionnaire_traduction($dir_module, $module) {
270
271        /**
272         * On teste ici si le fichier est géré par un autre salvatore
273         * Si oui on empeche son import en le signifiant
274         */
275        if ($t = salvatore_lire_gestionnaire_traduction($dir_module, $module)){
276                $url = extraire_attribut($t, 'url');
277                $gestionnaire = extraire_attribut($t, 'gestionnaire');
278                $url_gestionnaire = salvatore_get_self_url();
279                if ($gestionnaire !== 'salvatore'
280                  or protocole_implicite($url) !== protocole_implicite($url_gestionnaire)) {
281                        return "$gestionnaire@$url";
282                }
283        }
284
285        return '';
286}
287
288/**
289 * Lire la balise <traduction> du fichier .xml
290 * @param string $dir_module
291 * @param string $module
292 * @return string
293 */
294function salvatore_lire_gestionnaire_traduction($dir_module, $module) {
295        $xml_file = $dir_module . '/' . $module . '.xml';
296        /**
297         * On teste ici si le fichier est géré par un autre salvatore
298         * Si oui on empeche son import en le signifiant
299         */
300        if (file_exists($xml_file)){
301                $xml_content = spip_xml_load($xml_file);
302                if (is_array($xml_content)){
303                        // normalement on a qu'une balise <traduction...> englobante, donc on prend la premiere qu'on trouve
304                        if (spip_xml_match_nodes('/^traduction/', $xml_content, $matches)
305                          and $nodes = array_keys($matches)
306                          and $node = reset($nodes)) {
307                                return "<$node>";
308                        }
309                }
310        }
311        return '';
312}
313
314/**
315 * Retrouver la ligne de spip_tradlang_modules qui correspond a un dir_module/module, meme en cas de chanchement de repo (url/branches)
316 * Attention : ca veut dire que si on branche et qu'on veut traduire 2 branches d'un meme module
317 * il faut supprimer le fichier xml de la nouvelle branche pour qu'elle soit bien ajoutee a trad-lang
318 * et eviter qu'on pense que c'est un renommage
319 *
320 * @param $dir_module
321 * @param $module
322 * @return array|bool
323 */
324function salvatore_retrouver_tradlang_module($dir_module, $module) {
325        $base_dir_module = basename($dir_module);
326        if ($row_module = sql_fetsel('*', 'spip_tradlang_modules', 'dir_module = ' . sql_quote($base_dir_module))) {
327                return $row_module;
328        }
329
330        // peut-etre c'est un module qui a change d'url repo, et donc son dir_module a change ?
331        // sur la balise <traduction> le dir_module est ecrit dans id
332        if ($t = salvatore_lire_gestionnaire_traduction($dir_module, $module)
333          and $old_dir_module = extraire_attribut($t, 'id')
334          and $old_dir_module !== $base_dir_module){
335
336                if ($row_module = sql_fetsel('*', 'spip_tradlang_modules', 'dir_module = ' . sql_quote($old_dir_module))) {
337                        return $row_module;
338                }
339        }
340
341        #salvatore_fail("Module $module non trouve", "le module dans $dir_module n'est pas en base et n'est pas un renommage de repository");
342        return false;
343}
344
345/**
346 * Ajouter les credentials user/pass sur les urls de repo
347 * @param string $methode
348 * @param string $url_repository
349 * @param string $module
350 * @return string
351 */
352function salvatore_set_credentials($methode, $url_repository, $module){
353        global $domaines_exceptions, $domaines_exceptions_credentials,
354               $SVNUSER, $SVNPASSWD,
355               $GITUSER, $GITPASSWD;
356
357        // on ne sait pas mettre des credentials si c'est du ssh
358        if (strpos($url_repository, '://')!==false){
359                $user = $pass = false;
360                $parts = parse_url($url_repository);
361                if (empty($parts['user']) and empty($parts['pass'])){
362                        $host = $parts['host'];
363                        require_once(_DIR_ETC . 'salvatore_passwd.inc');
364
365                        if (!empty($domaines_exceptions)
366                                and is_array($domaines_exceptions)
367                                and in_array($host, $domaines_exceptions)){
368                                // on est dans une exception
369
370                                /**
371                                 * Est-ce que cette exception dispose de credentials (Github?)
372                                 */
373                                if (is_array($domaines_exceptions_credentials)
374                                        and !empty($domaines_exceptions_credentials[$host])){
375                                        $user = $domaines_exceptions_credentials[$host]['user'];
376                                        $pass = $domaines_exceptions_credentials[$host]['pass'];
377                                }
378
379                        } else {
380                                // un truc perso pour un module en particulier ?
381                                if (isset(${$module . '_user'})){
382                                        $user = ${$module . '_user'};
383                                        $pass = ${$module . '_passwd'};
384                                } elseif ($methode==='svn' and isset($SVNUSER)) {
385                                        $user = $SVNUSER;
386                                        $pass = $SVNPASSWD;
387                                } elseif ($methode==='git' and isset($GITUSER)) {
388                                        $user = $GITUSER;
389                                        $pass = $GITPASSWD;
390                                }
391                        }
392
393                        if ($user and $pass){
394                                $url_repository = str_replace("://$host", "://" . urlencode($user) . ":" . urlencode($pass) . "@$host", $url_repository);
395                        }
396                }
397
398        }
399
400        return $url_repository;
401}
402
403/**
404 * Charger une fonction vcs
405 * @param string $methode (git|svn)
406 * @param string $function
407 * @return string
408 * @throws Exception
409 */
410function salvatore_vcs_function($methode, $function) {
411        include_spip('salvatore/vcs/' . $methode);
412        if (function_exists($f = "salvatore_vcs_{$methode}_$function")
413          or function_exists($f = $f . '_dist')) {
414                return $f;
415        }
416        throw new \Exception("Erreur fonction $f inexistante");
417}
418
419/**
420 * Verifier qu'un repertoire existe
421 * @param $dir
422 * @throws Exception
423 */
424function salvatore_check_dir($dir){
425        if (!is_dir($dir)){
426                throw new Exception("Erreur : le répertoire $dir n'existe pas");
427        }
428}
429
430/**
431 * Verifier qu'un fichier existe
432 * @param $file
433 * @throws Exception
434 */
435function salvatore_check_file($file){
436        if (!file_exists($file)){
437                throw new Exception("Erreur : Le fichier $file est introuvable");
438        }
439}
440
441/**
442 * Loger
443 * @param string $msg
444 * @param string|array $display_function
445 * @param bool $display_time
446 */
447function salvatore_log($msg = '', $display_function = null, $display_time = false){
448        static $function = null;
449        static $time_log = null;
450
451        if ($display_function and is_callable($display_function)){
452                $function = $display_function;
453                $time_log = $display_time;
454        }
455
456        if (defined('_DEBUG_TRAD_LANG')
457                and _DEBUG_TRAD_LANG
458                and $msg){
459                if ($time_log) {
460                        $t = date('Y-m-d H:i:s') . ": ";
461                        $msg = $t . str_replace("\n", "\n$t", $msg);
462                }
463                if ($function){
464                        call_user_func($function, rtrim($msg));
465                } else {
466                        // fallback : utiliser echo mais enlever les balises de formatage symphony
467                        $msg = str_replace(["<info>", "</info>", "<error>", "</error>", "<comment>", "</comment>", "<question>", "</question>", "</>"], "", $msg);
468                        echo rtrim($msg) . "\n";
469                }
470        }
471}
472
473/**
474 * Echec sur erreur : on envoie un mail si possible et on echoue en lançant une exception
475 * @param $sujet
476 * @param $corps
477 * @throws Exception
478 */
479function salvatore_fail($sujet, $corps){
480        $corps = rtrim($corps) . "\n\n";
481
482        // masquer les mots de passe dans le message si jamais...
483        $pass_to_hide = [];
484        if (!empty($GLOBALS['SVNPASSWD'])) {
485                $pass_to_hide[] = $GLOBALS['SVNPASSWD'];
486        }
487        if (!empty($domaines_exceptions_credentials)) {
488                foreach ($domaines_exceptions_credentials as $domain => $credential) {
489                        if (!empty($credential['pass'])) {
490                                $pass_to_hide[] = $credential['pass'];
491                        }
492                }
493        }
494        foreach ($pass_to_hide as $pass) {
495                $replace = "xxxxxxxx@";
496                $sujet = str_replace("$pass@", $replace, $sujet);
497                $corps = str_replace("$pass@", $replace, $corps);
498        }
499
500        salvatore_envoyer_mail($sujet, $corps);
501        throw new Exception($corps);
502}
503
504/**
505 * @param string $sujet
506 * @param string $corps
507 */
508function salvatore_envoyer_mail($sujet = 'Erreur', $corps = ''){
509        if (defined('_EMAIL_ERREURS') and _EMAIL_ERREURS
510                and defined('_EMAIL_SALVATORE') and _EMAIL_SALVATORE){
511                $envoyer_mail = charger_fonction('envoyer_mail', 'inc');
512                $destinataire = _EMAIL_ERREURS;
513                $from = _EMAIL_SALVATORE;
514                $envoyer_mail($destinataire, $sujet, $corps, $from);
515                salvatore_log("Un email a été envoyé à l'adresse : " . _EMAIL_ERREURS . "\n");
516        }
517}
518
519
520/**
521 * Verifier que la base de salvatore a bien ete mise a jour
522 * pour ajouter le dir_module qui est la cle unique a la place de module
523 * lancer
524 * spip salvatore:upgrade --traductions=...
525 * avec le bon fichier de traduction pour mettre à jour la base de salvatore avant de pouvoir lancer a nouveau le lecteur ou l'ecriveur
526 */
527function salvatore_verifier_base_upgradee() {
528
529        $schema_declare = filtre_info_plugin_dist('tradlang', 'schema');
530        $schema_base = $GLOBALS['meta']['tradlang_base_version'];
531        if ($schema_base !== $schema_declare) {
532                throw new Exception("Schema de base pas a jour ($schema_base vs $schema_declare). Lancez la commande \nspip salvatore:upgrade --help");
533        }
534
535        $trouver_table = charger_fonction('trouver_table', 'base');
536        $desc = $trouver_table('spip_tradlang_modules');
537
538        // est-ce que le champ a ete cree ?
539        if (!isset($desc['field']['dir_module'])) {
540                throw new Exception("Pas de champ dir_module dans la base spip_tradlang_modules. Lancez la commande \nspip salvatore:upgrade --help");
541        }
542
543        // est-ce que tous les modules en base on bien eu un dir_module affecte (et ni vide ni =module qui est la valeur par defaut lors de l'upgrade de base)
544        $nb = sql_countsel('spip_tradlang_modules', "dir_module='' OR dir_module=module");
545        if ($nb>0) {
546                throw new Exception("Le champ dir_module de spip_tradlang_modules n'est pas renseigne pour tous les modules. Lancez la commande \nspip salvatore:upgrade --help");
547        }
548
549}
550
551/**
552 * Nettoyer la chaine de langue (venant du fichier PHP en lecture ou de la base en ecriture)
553 * @param string $chaine
554 * @param string $lang
555 * @return string
556 */
557function salvatore_nettoyer_chaine_langue($chaine, $lang){
558        static $typographie_functions = array();
559
560        if (!isset($typographie_functions[$lang])){
561                $typo = (in_array($lang, array('eo', 'fr', 'cpf')) || strncmp($lang, 'fr_', 3)==0) ? 'fr' : 'en';
562                $typographie_functions[$lang] = charger_fonction($typo, 'typographie');
563        }
564
565        /**
566         * On enlève les sauts de lignes windows pour des sauts de ligne linux
567         */
568
569        $chaine = str_replace("\r\n", "\n", $chaine);
570
571        /**
572         * protection dans les balises genre <a href="..." ou <img src="..."
573         * cf inc/filtres
574         */
575        if (preg_match_all(_TYPO_BALISE, $chaine, $regs, PREG_SET_ORDER)){
576                foreach ($regs as $reg){
577                        $insert = $reg[0];
578                        // hack: on transforme les caracteres a proteger en les remplacant
579                        // par des caracteres "illegaux". (cf corriger_caracteres())
580                        $insert = strtr($insert, _TYPO_PROTEGER, _TYPO_PROTECTEUR);
581                        $chaine = str_replace($reg[0], $insert, $chaine);
582                }
583        }
584
585        /**
586         * Protéger le contenu des balises <html> <code> <cadre> <frame> <tt> <pre>
587         */
588        define('_PROTEGE_BLOCS_HTML', ',<(html|code|cadre|pre|tt)(\s[^>]*)?>(.*)</\1>,UimsS');
589        if ((strpos($chaine, '<')!==false) and preg_match_all(_PROTEGE_BLOCS_HTML, $chaine, $matches, PREG_SET_ORDER)){
590                foreach ($matches as $reg){
591                        $insert = $reg[0];
592                        // hack: on transforme les caracteres a proteger en les remplacant
593                        // par des caracteres "illegaux". (cf corriger_caracteres())
594                        $insert = strtr($insert, _TYPO_PROTEGER, _TYPO_PROTECTEUR);
595                        $chaine = str_replace($reg[0], $insert, $chaine);
596                }
597        }
598
599        /**
600         * On applique la typographie de la langue
601         */
602        $chaine = $typographie_functions[$lang]($chaine);
603
604        /**
605         * On remet les caractères normaux sur les caractères illégaux
606         */
607        $chaine = strtr($chaine, _TYPO_PROTECTEUR, _TYPO_PROTEGER);
608
609        $chaine = unicode_to_utf_8(html_entity_decode(preg_replace('/&([lg]t;)/S', '&amp;\1', $chaine), ENT_NOQUOTES, 'utf-8'));
610
611        return $chaine;
612}
Note: See TracBrowser for help on using the repository browser.