source: spip-zone/_plugins_/trad-lang/trunk/inc/salvatore_ecriveur.php @ 119835

Last change on this file since 119835 was 119835, checked in by Cerdic, 2 years ago

refactoring ecriveur, wip

  • Property svn:eol-style set to native
File size: 17.3 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-2020
21        Florent Jugla <florent.jugla@eledo.com>,
22        Philippe Riviere <fil@rezo.net>,
23        Chryjs <chryjs!@!free!.!fr>,
24        kent1 <kent1@arscenic.info>
25        Cerdic <cedric@yterium.com>
26*/
27
28include_spip('base/abstract_sql');
29include_spip('inc/charsets');
30include_spip('inc/config');
31include_spip('inc/filtres');
32include_spip('inc/texte');
33include_spip('inc/xml');
34include_spip('inc/lang_liste');
35include_spip('inc/session');
36
37
38/**
39 * @param array $liste_sources
40 * @param string $dir_modules
41 * @throws Exception
42 */
43function salvatore_ecrire($liste_sources, $dir_modules = null, $message_commit=''){
44        include_spip('inc/salvatore');
45        salvatore_init();
46
47        // on va lire dans la base, il faut qu'elle soit a jour
48        salvatore_verifier_base_upgradee();
49
50        if (is_null($dir_modules)){
51                $dir_modules = _DIR_SALVATORE_MODULES;
52        }
53        salvatore_check_dir($dir_modules);
54        $url_gestionnaire = salvatore_get_self_url();
55
56        foreach ($liste_sources as $source){
57                salvatore_log("\n<info>--- Module " . $source['module'] . " | " . $source['dir_module'] . " | " . $source['url'] . "</info>");
58
59                $module = $source['module'];
60                $dir_module = $dir_modules . $source['dir_module'];
61
62                if ($autre_gestionnaire = salvatore_verifier_gestionnaire_traduction($dir_module, $module)){
63                        salvatore_fail("[Ecriveur] Erreur sur $module", "Erreur : export impossible, le fichier est traduit autre part : $autre_gestionnaire\n");
64                }
65
66                $id_tradlang_module = sql_getfetsel('id_tradlang_module', 'spip_tradlang_modules', 'dir_module = ' . sql_quote($source['dir_module']));
67                if (!$id_tradlang_module) {
68                        salvatore_fail("[Ecriveur] Erreur sur $module", "Erreur : export impossible, le module n'est pas en base\n");
69                }
70                else {
71                        // url de l'interface de traduction d'un module
72                        $url_trad_module = url_absolue(generer_url_entite($id_tradlang_module, 'tradlang_module'), $url_gestionnaire);
73                        salvatore_exporter_module($id_tradlang_module, $source, $url_gestionnaire, $url_trad_module, $message_commit);
74                }
75        }
76}
77
78/**
79 * Genere les fichiers de traduction d'un module
80 *
81 * @param int $id_tradlang_module
82 * @param array $source
83 * @param string $url_site
84 * @param string $url_trad_module
85 * @param string $dir_modules
86 * @param string $message_commit
87 */
88function salvatore_exporter_module($id_tradlang_module, $source, $url_site, $url_trad_module, $dir_modules, $message_commit = ''){
89
90        $url_repo = $source['url'];
91
92        $row_module = sql_fetsel('*', 'spip_tradlang_modules', 'id_tradlang_module=' . intval($id_tradlang_module));
93        if (!$row_module) {
94                $module = $source['module'];
95                salvatore_log("<error>Le module #$id_tradlang_module $module n'existe pas</error>");
96                return false;
97        }
98        $lang_ref = $row_module['lang_mere'];
99        $dir_module = $dir_modules . $row_module['dir_module'];
100        $module = $row_module['module'];
101
102        if (is_numeric($row_module['limite_trad']) and $row_module['limite_trad']>0){
103                $seuil_export = $row_module['limite_trad'];
104        }
105        else {
106                $seuil_export = lire_config('tradlang/seuil_export_tradlang', _SALVATORE_SEUIL_EXPORT);
107        }
108
109        // charger la langue originale, pour la copier si necessaire
110        // TODO : simplifier ? aucune reference a $trad_reference
111        $trad_reference = [];
112        $count_trad_reference = 0;
113        $rows = sql_allfetsel('id, id_tradlang_module,str,comm,statut', 'spip_tradlangs', 'id_tradlang_module=' . intval($id_tradlang_module) . ' AND lang=' . sql_quote($row_module['lang_mere']) . " AND statut='OK'", 'id');
114        foreach ($rows as $row){
115                $row['statut'] = 'NEW';
116                $trad_reference[$row['id']] = $row;
117                $count_trad_reference++;
118        }
119
120        $liste_lang = $liste_lang_non_exportees = $liste_lang_a_supprimer = array();
121        $minimal = ceil((($count_trad_reference*$seuil_export)/100));
122        salvatore_log("Minimal = $minimal ($seuil_export %)");
123
124        $langues = sql_allfetsel('lang,COUNT(*) as count', 'spip_tradlangs', 'id_tradlang_module=' . intval($id_tradlang_module) . " AND statut != 'NEW' AND statut != 'attic'", 'lang', 'lang');
125        foreach ($langues as $langue){
126                /**
127                 * Le fichier est il suffisamment traduit
128                 */
129                if ($langue['count']>=$minimal){
130                        $liste_lang[] = $langue['lang'];
131                } 
132                else {
133                        /**
134                         * Le fichier n'est pas suffisamment traduit et n'existe pas, on ne fera donc rien
135                         */
136                        if (!file_exists($dir_module . '/' . $module . '_' . $langue['lang'] . '.php')){
137                                $liste_lang_non_exportees[] = $langue['lang'];
138                        } else {
139                                /**
140                                 * Il n'est pas suffisamment traduit, cependant, il existe déjà
141                                 * On ne va donc pas le supprimer à la barbare, mais on le met à jour quand même
142                                 */
143                                $liste_lang[] = $langue['lang'];
144                                $liste_lang_a_supprimer[] = $langue['lang'];
145                                $percent = (($langue['count']/$count_trad_reference)*100);
146                                if ($percent<($seuil_export-15)){
147                                        $message_commit .= "La langue '" . $langue['lang'] . "' devrait être supprimée car trop peu traduite (" . number_format($percent, 2) . " %)\n";
148                                }
149                        }
150                }
151        }
152
153        // traiter chaque langue
154        $infos = $commiteurs = array();
155        foreach ($liste_lang as $lang){
156                salvatore_log("Generation de la langue $lang ");
157                // Proteger les caracteres typographiques a l'interieur des tags html
158                $typo = (in_array($lang, array('eo', 'fr', 'cpf')) || strncmp($lang, 'fr_', 3)==0) ? 'fr' : 'en';
159                $typographie = charger_fonction($typo, 'typographie');
160                $tab = "\t";
161
162                $php_lines = $chaines = $id_tradlangs = array();
163                $initiale = '';
164
165                // On ne prend que les MODIF, les RELIRE et les OK pour ne pas rendre les sites multilingues en français
166                $chaines = sql_allfetsel('id_tradlang,id,str,comm,statut,md5', 'spip_tradlangs', 'id_tradlang_module=' . intval($id_tradlang_module) . ' AND lang=' . sql_quote($lang) . " AND statut!='NEW' AND statut!='attic'", 'id');
167                $id_tradlangs = array_column($chaines, 'id_tradlang');
168                $chaines = array_combine(array_column($chaines, 'id'), $chaines);
169                ksort($chaines);
170
171                $total_chaines = ['OK' => 0, 'MODIF' => 0, 'RELIRE' => 0];
172                foreach ($chaines as $chaine){
173                        $total_chaines[$chaine['statut']]++;
174
175                        $comment = salvatore_clean_comment($chaine['comm']);
176
177                        if ($initiale !== strtoupper($chaine['id'][0])){
178                                $initiale = strtoupper($chaine['id'][0]);
179                                $php_lines[] = "\n$tab// $initiale";
180                        }
181
182                        if (strlen($chaine['statut']) and ($chaine['statut']!=='OK')){
183                                $comment .= ' ' . $chaine['statut'];
184                        }
185                        if ($comment){
186                                $comment = ' # ' . trim($comment); // on rajoute les commentaires ?
187                        }
188
189                        $str = savlatore_nettoyer_chaine_base($chaine['str'], $lang);
190
191                        /**
192                         * Calcul du nouveau md5
193                         */
194                        $newmd5 = md5($str);
195
196                        /**
197                         * Si le md5 ou la chaine à changé, on la met à jour dans la base
198                         */
199                        if (($chaine['md5']!==$newmd5) || ($str!=$chaine['str'])){
200                                $r = sql_updateq('spip_tradlangs', array('md5' => $newmd5, 'str' => $str), 'id_tradlang = ' . intval($chaine['id_tradlang']));
201                        }
202
203                        $php_lines[] = $tab . var_export($chaine['id'], 1) . ' => ' . var_export($str, 1) . ',' . $comment;
204                }
205
206
207                $orig = ($lang==$lang_ref) ? $url_repo : false;
208
209                salvatore_log(" - traduction ($total_chaines['OK']/$count_trad_reference OK | $total_chaines['RELIRE']/$count_trad_reference RELIRE | $total_chaines['MODIF']/$count_trad_reference MODIFS), export\n");
210                // historiquement les fichiers de lang de spip_loader ne peuvent pas etre securises
211                $secure = ($module=='tradloader')
212                        ? ''
213                        : "if (!defined('_ECRIRE_INC_VERSION')) {
214return;
215}\n\n";
216
217                $fd = fopen($dir_module . '/' . $module . '_' . $lang . '.php', 'w');
218
219                # supprimer la virgule du dernier item
220                $php_lines[count($php_lines)-1] = preg_replace('/,([^,]*)$/', '\1', $php_lines[count($php_lines)-1]);
221
222                $contenu = join("\n", $php_lines);
223
224                // L'URL du site de traduction
225                $url_trad_module = parametre_url($url_trad_module, 'lang_cible', $lang);
226                /**
227                 * Ecrire le fichier de langue complet
228                 */
229                fwrite(
230                        $fd,
231                        '<' . '?php
232// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
233'
234
235                        . ($orig
236                                ? '// Fichier source, a modifier dans ' . $orig
237                                : '// extrait automatiquement de ' . $url_trad_module . '
238// ** ne pas modifier le fichier **
239'
240                        )
241                        . "\n" . $secure . '$GLOBALS[$GLOBALS[\'idx_lang\']] = array(
242'
243                        . $contenu
244                        . '
245);
246'
247                );
248                fclose($fd);
249
250                // noter la langue et les traducteurs pour lang/module.xml
251                $infos[$lang] = $people_unique = array();
252                $infos[$lang]['traducteurs'] = array();
253                $infos[$lang]['traduits'] = $total_chaines['OK'];
254                $infos[$lang]['modifs'] = $total_chaines['MODIF'];
255                $infos[$lang]['relire'] = $total_chaines['RELIRE'];
256                if (defined('_ID_AUTEUR_SALVATORE') and intval(_ID_AUTEUR_SALVATORE)>0){
257                        $people_unique[] = _ID_AUTEUR_SALVATORE;
258                }
259                $s = sql_allfetsel('DISTINCT(traducteur)', 'spip_tradlangs', 'id_tradlang_module = ' . intval($row_module['id_tradlang_module']) . ' AND lang = ' . sql_quote($lang));
260                foreach ($s as $t){
261                        $traducteurs_lang = explode(',', $t['traducteur']);
262                        foreach ($traducteurs_lang as $traducteur){
263                                if (!in_array($traducteur, $people_unique)){
264                                        if (is_numeric($traducteur) and $id_auteur = intval($traducteur)){
265                                                $traducteur_supp['nom'] = extraire_multi(sql_getfetsel('nom', 'spip_auteurs', 'id_auteur = ' . $id_auteur));
266                                                $traducteur_supp['lien'] = url_absolue(generer_url_entite($id_auteur, 'auteur'), $url_site);
267                                        } elseif (trim(strlen($traducteur))>0) {
268                                                $traducteur_supp['nom'] = trim($traducteur);
269                                                $traducteur_supp['lien'] = '';
270                                        }
271                                        if (isset($traducteur_supp['nom'])){
272                                                $infos[$lang]['traducteurs'][strtolower($traducteur_supp['nom'])] = $traducteur_supp;
273                                        }
274                                        unset($traducteur_supp);
275                                        $people_unique[] = $traducteur;
276                                }
277                        }
278                }
279                unset($people_unique);
280
281                if (substr(exec('svn status ' . _DIR_SALVATORE_TMP . $module . '/' . $module . '_' . $lang . '.php'), 0, 1)=='?'){
282                        if ($row_module['limite_trad']==0){
283                                passthru('svn add ' . _DIR_SALVATORE_TMP . $module . '/' . $module . "_$lang.php 2> /dev/null") ? salvatore_log("$log\n") : '';
284                        } elseif (!in_array($module, array('ecrire', 'spip', 'public'))) {
285                                if ((intval(($infos[$lang]['traduits']/$count_trad_reference)*100)>$seuil_export)){
286                                        passthru('svn add ' . _DIR_SALVATORE_TMP . $module . '/' . $module . "_$lang.php* 2> /dev/null") ? salvatore_log("$log\n") : '';
287                                }
288                        }
289                }
290                /**
291                 * Le fichier a été modifié ou ajouté (svn status A ou M)
292                 *
293                 * On récupère la date de dernier changement avec svn info
294                 * On cherche toutes les dernières modifications dans la base de donnée
295                 * Si un seul auteur de révisions (Hors salvatore et -1) on l'ajoute comme commiteur
296                 * Si plusieurs auteurs le commiteur sera Salvatore
297                 */
298                if (in_array(substr(exec('svn status ' . _DIR_SALVATORE_TMP . $module . '/' . $module . "_$lang.php"), 0, 1), array('A', 'M'))){
299                        $last_change = exec('env LC_MESSAGES=en_US.UTF-8 svn info ' . _DIR_SALVATORE_TMP . $module . '/' . $module . "_$lang.php | awk '/^Last Changed Date/ { print $4 \" \" $5 }'");
300                        $auteur_versions = sql_allfetsel('id_auteur', 'spip_versions', 'objet="tradlang" AND date > ' . sql_quote($last_change) . ' AND ' . sql_in('id_objet', $id_tradlangs) . ' AND id_auteur != "-1" AND id_auteur !=' . intval(_ID_AUTEUR_SALVATORE), 'id_auteur');
301                        if (count($auteur_versions)==1){
302                                $email = sql_getfetsel('email', 'spip_auteurs', 'id_auteur = ' . intval($auteur_versions[0]['id_auteur']));
303                                if ($email){
304                                        $commiteurs[$lang] = $email;
305                                }
306                                salvatore_log("\nLe commiteur sera pour la langue $lang : " . $commiteurs[$lang] . " \n");
307                        }
308                }
309        }
310
311        // ecrire lang/module.xml
312        $xml = "<traduction module=\"$module\" gestionnaire=\"salvatore\" url=\"$url_site\" source=\"$url_repo\" reference=\"$lang_ref\">\n";
313        foreach ($infos as $lang => $info){
314                if (count($info['traducteurs']>0)){
315                        $xml .= "       <langue code=\"$lang\" url=\"" . parametre_url($url_trad_module, 'lang_cible', $lang) . "\" total=\"$count_trad_reference\" traduits=\"" . $info['traduits'] . '" relire="' . $info['relire'] . '" modifs="' . $info['modifs'] . '" nouveaux="' . ($count_trad_reference-($info['modifs']+$info['traduits']+$info['relire'])) . '" pourcent="' . number_format((($info['traduits']/$count_trad_reference)*100), 2) . "\">\n";
316                        ksort($info['traducteurs']);
317                        foreach ($info['traducteurs'] as $nom => $people){
318                                $xml .= '               <traducteur nom="' . entites_html($people['nom']) . '" lien="' . entites_html($people['lien']) . "\" />\n";
319                        }
320                        $xml .= "       </langue>\n";
321                } else {
322                        $xml .= "       <langue code=\"$lang\" url=\"" . parametre_url($url_trad_module, 'lang_cible', $lang) . "\" />\n";
323                }
324        }
325        unset($traducteurs[$lang_ref]);
326        $xml .= "</traduction>\n";
327
328        ecrire_fichier($dir_module . '/' . $module . '.xml', $xml);
329
330        if (isset($liste_lang_non_exportees) and (count($liste_lang_non_exportees)>0)){
331                $liste_lang_non_exportees_string = implode(', ', $liste_lang_non_exportees);
332                salvatore_log("\nLes langues suivantes ne sont pas exportées car trop peu traduites:\n");
333                salvatore_log("$liste_lang_non_exportees_string\n");
334        }
335        if (isset($liste_lang_a_supprimer) and (count($liste_lang_a_supprimer)>0)){
336                $liste_lang_a_supprimer_string = implode(', ', $liste_lang_a_supprimer);
337                salvatore_log("\nLes langues suivantes devraient être supprimées car trop peu traduites:\n");
338                salvatore_log("$liste_lang_a_supprimer_string\n");
339        }
340        if ($row_module['limite_trad']==0){
341                foreach ($liste_lang as $lang){
342                        passthru('svn add ' . _DIR_SALVATORE_TMP . $module . '/' . $module . "_$lang.php* 2> /dev/null") ? salvatore_log("$log\n") : '';
343                }
344        } elseif (!in_array($module, array('ecrire', 'spip', 'public'))) {
345                salvatore_log('Limite trad = ' . $seuil_export);
346                foreach ($liste_lang as $lang){
347                        if ((intval(($infos[$lang]['traduits']/$count_trad_reference)*100)>$seuil_export)
348                                and (substr(exec('svn status ' . _DIR_SALVATORE_TMP . $module . '/' . $module . "_$lang.php"), 0, 1)=='?')){
349                                passthru('svn add ' . _DIR_SALVATORE_TMP . $module . '/' . $module . "_$lang.php* 2> /dev/null") ? salvatore_log("$log\n") : '';
350                        }
351                }
352        }
353        salvatore_log("\n" . passthru('svn status ' . _DIR_SALVATORE_TMP . $module . '/') . "\n");
354        if (strlen($message_commit)>1 || count($commiteurs)>0){
355                $fd = fopen($dir_module . '/message_commit.inc', 'w');
356                # ecrire le fichier
357                fwrite(
358                        $fd,
359                        '<' . '?php
360$message_commit = "' . $message_commit . '";
361
362$commiteurs = ' . var_export($commiteurs, 1) . ';
363
364?' . '>
365'
366                );
367                fclose($fd);
368        }
369}
370
371/**
372 * Nettoyer le commentaire avant ecriture dans le PHP
373 * @param $comment
374 * @return mixed|string
375 */
376function salvatore_clean_comment($comment) {
377        if (strlen(trim($comment))>1){
378                // On remplace les sauts de lignes des commentaires sinon ça crée des erreurs php
379                $comment = str_replace(array("\r\n", "\n", "\r"), ' ', $comment);
380                // Conversion des commentaires en utf-8
381                $comment = unicode_to_utf_8(html_entity_decode(preg_replace('/&([lg]t;)/S', '&amp;\1', $comment), ENT_NOQUOTES, 'utf-8'));
382                return $comment;
383        }
384        return '';
385}
386
387/**
388 * Nettoyer la chaine traduite qui est en base avant export dans le PHP
389 * @param string $chaine
390 * @param string $lang
391 * @return string
392 */
393function savlatore_nettoyer_chaine_base($chaine, $lang) {
394        static $typographie_functions = array();
395
396        if (!isset($typographie_functions[$lang])){
397                $typo = (in_array($lang, array('eo', 'fr', 'cpf')) || strncmp($lang, 'fr_', 3)==0) ? 'fr' : 'en';
398                $typographie_functions[$lang] = charger_fonction($typo, 'typographie');
399        }
400
401        /**
402         * On enlève les sauts de lignes windows pour des sauts de ligne linux
403         */
404        $chaine = str_replace("\r\n", "\n", $chaine);
405
406        /**
407         * protection dans les balises genre <a href="..." ou <img src="..."
408         * cf inc/filtres
409         */
410        if (preg_match_all(_TYPO_BALISE, $chaine, $regs, PREG_SET_ORDER)){
411                foreach ($regs as $reg){
412                        $insert = $reg[0];
413                        // hack: on transforme les caracteres a proteger en les remplacant
414                        // par des caracteres "illegaux". (cf corriger_caracteres())
415                        $insert = strtr($insert, _TYPO_PROTEGER, _TYPO_PROTECTEUR);
416                        $chaine = str_replace($reg[0], $insert, $chaine);
417                }
418        }
419
420        /**
421         * Protéger le contenu des balises <html> <code> <cadre> <frame> <tt> <pre>
422         */
423        define('_PROTEGE_BLOCS_HTML', ',<(html|code|cadre|pre|tt)(\s[^>]*)?>(.*)</\1>,UimsS');
424        if ((strpos($chaine, '<')!==false) and preg_match_all(_PROTEGE_BLOCS_HTML, $chaine, $matches, PREG_SET_ORDER)){
425                foreach ($matches as $reg){
426                        $insert = $reg[0];
427                        // hack: on transforme les caracteres a proteger en les remplacant
428                        // par des caracteres "illegaux". (cf corriger_caracteres())
429                        $insert = strtr($insert, _TYPO_PROTEGER, _TYPO_PROTECTEUR);
430                        $chaine = str_replace($reg[0], $insert, $chaine);
431                }
432        }
433
434        /**
435         * On applique la typographie de la langue
436         */
437        $chaine = $typographie_functions[$lang]($chaine);
438
439        /**
440         * On remet les caractères normaux sur les caractères illégaux
441         */
442        $chaine = strtr($chaine, _TYPO_PROTECTEUR, _TYPO_PROTEGER);
443
444        $chaine = unicode_to_utf_8(html_entity_decode(preg_replace('/&([lg]t;)/S', '&amp;\1', $chaine), ENT_NOQUOTES, 'utf-8'));
445
446        return $chaine;
447}
Note: See TracBrowser for help on using the repository browser.