source: spip-zone/_core_/plugins/revisions/inc/revisions.php @ 93622

Last change on this file since 93622 was 93622, checked in by cedric@…, 4 years ago

Indentation et regles de codage selon http://www.spip.net/fr_article3497.html#regles_codage

File size: 28.0 KB
Line 
1<?php
2
3/***************************************************************************\
4 *  SPIP, Systeme de publication pour l'internet                           *
5 *                                                                         *
6 *  Copyright (c) 2001-2015                                                *
7 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
8 *                                                                         *
9 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
10 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
11\***************************************************************************/
12
13/**
14 * Fonctions utilitaires du plugin révisions
15 *
16 * @package SPIP\Revisions\Fonctions
17 **/
18if (!defined("_ECRIRE_INC_VERSION")) {
19        return;
20}
21
22$GLOBALS['agregation_versions'] = 10;
23
24/** Intervalle de temps (en seconde) separant deux révisions par un même auteur */
25define('_INTERVALLE_REVISIONS', 600);
26
27/**
28 * Découper les paragraphes d'un texte en fragments
29 *
30 * @param string $texte Texte à fragmenter
31 * @param array $paras Tableau de fragments déjà là
32 * @return string[]      Tableau de fragments (paragraphes)
33 **/
34function separer_paras($texte, $paras = array()) {
35        if (!$paras) {
36                $paras = array();
37        }
38        while (preg_match("/(\r\n?){2,}|\n{2,}/", $texte, $regs)) {
39                $p = strpos($texte, $regs[0])+strlen($regs[0]);
40                $paras[] = substr($texte, 0, $p);
41                $texte = substr($texte, $p);
42        }
43        if ($texte) {
44                $paras[] = $texte;
45        }
46
47        return $paras;
48}
49
50// http://code.spip.net/@replace_fragment
51function replace_fragment($id_objet, $objet, $version_min, $version_max, $id_fragment, $fragment) {
52        $fragment = serialize($fragment);
53        $compress = 0;
54
55        /* On ne compresse plus les fragments car probleme de portabilite de base, corruptions de donnees
56        en backup SQLite ou meme en mysqldump */
57        // pour le portage en PG il faut l'equivalente au mysql_escape_string
58        // et deporter son appel dans les fonctions d'abstraction.
59        /*
60        if (function_exists('gzcompress')
61        AND $GLOBALS['connexions'][0]['type'] == 'mysql') {
62                $s = gzcompress($fragment);
63                if (strlen($s) < strlen($fragment)) {
64                        # spip_log("gain gz: ".intval(100 - 100 * strlen($s) / strlen($fragment)),'revisions');
65                        $compress = 1;
66                        $fragment = $s;
67                }
68        }
69        */
70
71        // Attention a echapper $fragment, binaire potentiellement gz
72        return array(
73                'id_objet' => intval($id_objet),
74                'objet' => $objet,
75                'id_fragment' => intval($id_fragment),
76                'version_min' => intval($version_min),
77                'version_max' => intval($version_max),
78                'compress' => $compress,
79                'fragment' => $fragment
80        );
81}
82
83// http://code.spip.net/@envoi_replace_fragments
84function envoi_replace_fragments($replaces) {
85        $desc = $GLOBALS['tables_auxiliaires']['spip_versions_fragments'];
86        foreach ($replaces as $r) {
87                sql_replace('spip_versions_fragments', $r, $desc);
88        }
89}
90
91
92// http://code.spip.net/@envoi_delete_fragments
93function envoi_delete_fragments($id_objet, $objet, $deletes) {
94        if (count($deletes)) {
95                sql_delete("spip_versions_fragments",
96                        "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND ((" . join(") OR (",
97                                $deletes) . "))");
98        }
99}
100
101
102//
103// Ajouter les fragments de la derniere version (tableau associatif id_fragment => texte)
104//
105// http://code.spip.net/@ajouter_fragments
106function ajouter_fragments($id_objet, $objet, $id_version, $fragments) {
107        global $agregation_versions;
108
109        $replaces = array();
110        foreach ($fragments as $id_fragment => $texte) {
111                $nouveau = true;
112                // Recuperer la version la plus recente
113                $row = sql_fetsel("compress, fragment, version_min, version_max", "spip_versions_fragments",
114                        "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_fragment=$id_fragment AND version_min<=$id_version",
115                        "", "version_min DESC", "1");
116
117                if ($row) {
118                        $fragment = $row['fragment'];
119                        $version_min = $row['version_min'];
120                        if ($row['compress'] > 0) {
121                                $fragment = @gzuncompress($fragment);
122                        }
123                        $fragment = unserialize($fragment);
124                        if (is_array($fragment)) {
125                                unset($fragment[$id_version]);
126                                // Si le fragment n'est pas trop gros, prolonger celui-ci
127                                $nouveau = count($fragment) >= $agregation_versions
128                                        && strlen($row['fragment']) > 1000;
129                        }
130                }
131                if ($nouveau) {
132                        $fragment = array($id_version => $texte);
133                        $version_min = $id_version;
134                } else {
135                        // Ne pas dupliquer les fragments non modifies
136                        $modif = true;
137                        for ($i = $id_version-1; $i >= $version_min; $i--) {
138                                if (isset($fragment[$i])) {
139                                        $modif = ($fragment[$i] != $texte);
140                                        break;
141                                }
142                        }
143                        if ($modif) {
144                                $fragment[$id_version] = $texte;
145                        }
146                }
147
148                // Preparer l'enregistrement du fragment
149                $replaces[] = replace_fragment($id_objet, $objet, $version_min, $id_version, $id_fragment, $fragment);
150        }
151
152        envoi_replace_fragments($replaces);
153}
154
155//
156// Supprimer tous les fragments d'un objet lies a un intervalle de versions
157// (essaie d'eviter une trop grande fragmentation)
158//
159// http://code.spip.net/@supprimer_fragments
160function supprimer_fragments($id_objet, $objet, $version_debut, $version_fin) {
161        global $agregation_versions;
162
163        $replaces = array();
164        $deletes = array();
165
166        // D'abord, vider les fragments inutiles
167        sql_delete("spip_versions_fragments",
168                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND version_min>=$version_debut AND version_max<=$version_fin");
169
170        // Fragments chevauchant l'ensemble de l'intervalle, s'ils existent
171        $result = sql_select("id_fragment, compress, fragment, version_min, version_max", "spip_versions_fragments",
172                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND version_min<$version_debut AND version_max>$version_fin");
173
174        while ($row = sql_fetch($result)) {
175                $id_fragment = $row['id_fragment'];
176                $fragment = $row['fragment'];
177                if ($row['compress'] > 0) {
178                        $fragment = gzuncompress($fragment);
179                }
180                $fragment = unserialize($fragment);
181                for ($i = $version_fin; $i >= $version_debut; $i--) {
182                        if (isset($fragment[$i])) {
183                                // Recopier le dernier fragment si implicite
184                                if (!isset($fragment[$version_fin+1])) {
185                                        $fragment[$version_fin+1] = $fragment[$i];
186                                }
187                                unset($fragment[$i]);
188                        }
189                }
190
191                $replaces[] = replace_fragment($id_objet, $objet,
192                        $row['version_min'], $row['version_max'], $id_fragment, $fragment);
193        }
194
195        // Fragments chevauchant le debut de l'intervalle, s'ils existent
196        $result = sql_select("id_fragment, compress, fragment, version_min, version_max", "spip_versions_fragments",
197                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND version_min<$version_debut AND version_max>=$version_debut AND version_max<=$version_fin");
198
199        $deb_fragment = array();
200        while ($row = sql_fetch($result)) {
201                $id_fragment = $row['id_fragment'];
202                $fragment = $row['fragment'];
203                $version_min = $row['version_min'];
204                $version_max = $row['version_max'];
205                if ($row['compress'] > 0) {
206                        $fragment = gzuncompress($fragment);
207                }
208                $fragment = unserialize($fragment);
209                for ($i = $version_debut; $i <= $version_max; $i++) {
210                        if (isset($fragment[$i])) {
211                                unset($fragment[$i]);
212                        }
213                }
214
215                // Stocker temporairement le fragment pour agregation
216                $deb_fragment[$id_fragment] = $fragment;
217                // Ajuster l'intervalle des versions
218                $deb_version_min[$id_fragment] = $version_min;
219                $deb_version_max[$id_fragment] = $version_debut-1;
220        }
221
222        // Fragments chevauchant la fin de l'intervalle, s'ils existent
223        $result = sql_select("id_fragment, compress, fragment, version_min, version_max", "spip_versions_fragments",
224                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND version_max>$version_fin AND version_min>=$version_debut AND version_min<=$version_fin");
225
226        while ($row = sql_fetch($result)) {
227                $id_fragment = $row['id_fragment'];
228                $fragment = $row['fragment'];
229                $version_min = $row['version_min'];
230                $version_max = $row['version_max'];
231                if ($row['compress'] > 0) {
232                        $fragment = gzuncompress($fragment);
233                }
234                $fragment = unserialize($fragment);
235                for ($i = $version_fin; $i >= $version_min; $i--) {
236                        if (isset($fragment[$i])) {
237                                // Recopier le dernier fragment si implicite
238                                if (!isset($fragment[$version_fin+1])) {
239                                        $fragment[$version_fin+1] = $fragment[$i];
240                                }
241                                unset($fragment[$i]);
242                        }
243                }
244
245                // Virer l'ancien enregistrement (la cle primaire va changer)
246                $deletes[] = "id_fragment=$id_fragment AND version_min=$version_min";
247                // Essayer l'agregation
248                $agreger = false;
249                if (isset($deb_fragment[$id_fragment])) {
250                        $agreger = (count($deb_fragment[$id_fragment])+count($fragment) <= $agregation_versions);
251                        if ($agreger) {
252                                $fragment = $deb_fragment[$id_fragment]+$fragment;
253                                $version_min = $deb_version_min[$id_fragment];
254                        } else {
255                                $replaces[] = replace_fragment($id_objet, $objet,
256                                        $deb_version_min[$id_fragment], $deb_version_max[$id_fragment],
257                                        $id_fragment, $deb_fragment[$id_fragment]);
258                        }
259                        unset($deb_fragment[$id_fragment]);
260                }
261                if (!$agreger) {
262                        // Ajuster l'intervalle des versions
263                        $version_min = $version_fin+1;
264                }
265                $replaces[] = replace_fragment($id_objet, $objet, $version_min, $version_max, $id_fragment, $fragment);
266        }
267
268        // Ajouter fragments restants
269        if (is_array($deb_fragment) && count($deb_fragment) > 0) {
270                foreach ($deb_fragment as $id_fragment => $fragment) {
271                        $replaces[] = replace_fragment($id_objet, $objet,
272                                $deb_version_min[$id_fragment], $deb_version_max[$id_fragment],
273                                $id_fragment, $deb_fragment[$id_fragment]);
274                }
275        }
276
277        envoi_replace_fragments($replaces);
278        envoi_delete_fragments($id_objet, $objet, $deletes);
279}
280
281
282/**
283 * Récupérer les fragments d'un objet pour une version demandée
284 *
285 * @param int $id_objet Identifiant de l'objet
286 * @param string $objet Objet
287 * @param int $id_version Identifiant de la version
288 * @return array           Couples id_fragment => texte
289 */
290function recuperer_fragments($id_objet, $objet, $id_version) {
291        $fragments = array();
292
293        if ($id_version == 0) {
294                return array();
295        }
296
297        $result = sql_select(
298                "id_fragment, version_min, version_max, compress, fragment",
299                "spip_versions_fragments",
300                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet)
301                . " AND version_min<=$id_version AND version_max>=$id_version");
302
303        while ($row = sql_fetch($result)) {
304                $id_fragment = $row['id_fragment'];
305                $version_min = $row['version_min'];
306                $fragment = $row['fragment'];
307                // si le fragment est compressé, tenter de le décompresser, sinon écrire une erreur
308                if ($row['compress'] > 0) {
309                        $fragment_ = @gzuncompress($fragment);
310                        if (strlen($fragment) && $fragment_ === false) {
311                                $fragment = serialize(array($row['version_max'] => "[" . _T('forum_titre_erreur') . $id_fragment . "]"));
312                        } else {
313                                $fragment = $fragment_;
314                        }
315                }
316                // tenter dedésérialiser le fragment, sinon écrire une erreur
317                $fragment_ = unserialize($fragment);
318                if (strlen($fragment) && $fragment_ === false) {
319                        $fragment = array($row['version_max'] => "[" . _T('forum_titre_erreur') . $id_fragment . "]");
320                } else {
321                        $fragment = $fragment_;
322                }
323                // on retrouve le fragment le plus près de notre version
324                for ($i = $id_version; $i >= $version_min; $i--) {
325                        if (isset($fragment[$i])) {
326
327                                ## hack destine a sauver les archives des sites iso-8859-1
328                                ## convertis en utf-8 (les archives ne sont pas converties
329                                ## mais ce code va les nettoyer ; pour les autres charsets
330                                ## la situation n'est pas meilleure ni pire qu'avant)
331                                if ($GLOBALS['meta']['charset'] == 'utf-8'
332                                        AND include_spip('inc/charsets')
333                                        AND !is_utf8($fragment[$i])
334                                ) {
335                                        $fragment[$i] = importer_charset($fragment[$i], 'iso-8859-1');
336                                }
337
338                                $fragments[$id_fragment] = $fragment[$i];
339                                // quitter la boucle dès le premier touvé.
340                                break;
341                        }
342                }
343        }
344
345        return $fragments;
346}
347
348
349//
350// Apparier des paragraphes deux a deux entre une version originale
351// et une version modifiee
352//
353// http://code.spip.net/@apparier_paras
354function apparier_paras($src, $dest, $flou = true) {
355        $src_dest = array();
356        $dest_src = array();
357
358        $t1 = $t2 = array();
359
360        $md1 = $md2 = array();
361        $gz_min1 = $gz_min2 = array();
362        $gz_trans1 = $gz_trans2 = array();
363        $l1 = $l2 = array();
364
365        // Nettoyage de la ponctuation pour faciliter l'appariement
366        foreach ($src as $key => $val) {
367                $t1[$key] = strval(preg_replace("/[[:punct:][:space:]]+/", " ", $val));
368        }
369        foreach ($dest as $key => $val) {
370                $t2[$key] = strval(preg_replace("/[[:punct:][:space:]]+/", " ", $val));
371        }
372
373        // Premiere passe : chercher les correspondance exactes
374        foreach ($t1 as $key => $val) {
375                $md1[$key] = md5($val);
376        }
377        foreach ($t2 as $key => $val) {
378                $md2[md5($val)][$key] = $key;
379        }
380        foreach ($md1 as $key1 => $h) {
381                if (isset($md2[$h])) {
382                        $key2 = reset($md2[$h]);
383                        if (isset($t1[$key1]) AND isset($t2[$key2]) AND $t1[$key1] == $t2[$key2]) {
384                                $src_dest[$key1] = $key2;
385                                $dest_src[$key2] = $key1;
386                                unset($t1[$key1]);
387                                unset($t2[$key2]);
388                                unset($md2[$h][$key2]);
389                        }
390                }
391        }
392
393        if ($flou) {
394                // Deuxieme passe : recherche de correlation par test de compressibilite
395                foreach ($t1 as $key => $val) {
396                        $l1[$key] = strlen(gzcompress($val));
397                }
398                foreach ($t2 as $key => $val) {
399                        $l2[$key] = strlen(gzcompress($val));
400                }
401                foreach ($t1 as $key1 => $s1) {
402                        foreach ($t2 as $key2 => $s2) {
403                                $r = strlen(gzcompress($s1 . $s2));
404                                $taux = 1.0*$r/($l1[$key1]+$l2[$key2]);
405                                if (!isset($gz_min1[$key1]) || !$gz_min1[$key1] || $gz_min1[$key1] > $taux) {
406                                        $gz_min1[$key1] = $taux;
407                                        $gz_trans1[$key1] = $key2;
408                                }
409                                if (!isset($gz_min2[$key2]) || !$gz_min2[$key2] || $gz_min2[$key2] > $taux) {
410                                        $gz_min2[$key2] = $taux;
411                                        $gz_trans2[$key2] = $key1;
412                                }
413                        }
414                }
415
416                // Depouiller les resultats de la deuxieme passe :
417                // ne retenir que les correlations reciproques
418                foreach ($gz_trans1 as $key1 => $key2) {
419                        if ($gz_trans2[$key2] == $key1 && $gz_min1[$key1] < 0.9) {
420                                $src_dest[$key1] = $key2;
421                                $dest_src[$key2] = $key1;
422                        }
423                }
424        }
425
426        // Retourner les mappings
427        return array($src_dest, $dest_src);
428}
429
430
431/**
432 * Récupérer les champs d'un objet, pour une version demandée
433 *
434 * @param int $id_objet Identifiant de l'objet
435 * @param string $objet Objet
436 * @param int $id_version Identifiant de la version
437 * @return array           Couples champs => textes
438 */
439function recuperer_version($id_objet, $objet, $id_version) {
440
441        $champs = sql_getfetsel("champs", "spip_versions",
442                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version=" . intval($id_version));
443        if (!$champs OR !is_array($champs = unserialize($champs))) {
444                return array();
445        } else {
446                return reconstuire_version($champs,
447                        recuperer_fragments($id_objet, $objet, $id_version));
448        }
449}
450
451/**
452 * Reconstruire une version donnée
453 *
454 * À partir de la liste des champs et de fragments,
455 * retourne le texte de chaque champ.
456 *
457 * @param array $champs Couples (champ => liste d'id_fragment).
458 *                          La liste est de la forme "5 32 7 16 8 2"
459 * @param array $fragments Couples (id_fragment => texte)
460 * @param array $res Couples (champ => texte) déjà connus
461 * @return array            Couples (champ => texte)
462 */
463function reconstuire_version($champs, $fragments, $res = array()) {
464
465        static $msg;
466        if (!$msg) {
467                $msg = _T('forum_titre_erreur');
468        }
469
470        foreach ($champs as $nom => $code) {
471                if (!isset($res[$nom])) {
472                        $t = '';
473                        foreach (array_filter(explode(' ', $code)) as $id) {
474                                $t .= isset($fragments[$id])
475                                        ? $fragments[$id]
476                                        : "[$msg$id]";
477                        }
478                        $res[$nom] = $t;
479                }
480        }
481
482        return $res;
483}
484
485// http://code.spip.net/@supprimer_versions
486function supprimer_versions($id_objet, $objet, $version_min, $version_max) {
487        sql_delete("spip_versions",
488                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version>=$version_min AND id_version<=$version_max");
489
490        supprimer_fragments($id_objet, $objet, $version_min, $version_max);
491}
492
493
494/**
495 * Ajouter une version à un objet éditorial
496 *
497 * @param int $id_objet
498 * @param string $objet
499 * @param array $champs
500 * @param string $titre_version
501 *     Titre donné aux modifications apportées
502 * @param int|null $id_auteur
503 *     Auteur apportant les modifications. En absence (session anonyme), utilisera l'IP pour garder une trace.
504 * @return int
505 *     id_version : identifiant de la version
506 **/
507function ajouter_version($id_objet, $objet, $champs, $titre_version = "", $id_auteur = null) {
508        $paras = $paras_old = $paras_champ = $fragments = array();
509
510        // Attention a une edition anonyme (type wiki): id_auteur n'est pas
511        // definie, on enregistre alors le numero IP
512        $str_auteur = intval($id_auteur) ? intval($id_auteur) : $GLOBALS['ip'];
513
514        // si pas de titre dans cette version, la marquer 'non' permanente,
515        // et elle pourra etre fusionnee avec une revision ulterieure dans un delai < _INTERVALLE_REVISIONS
516        // permet de fusionner plusieurs editions consecutives champs par champs avec les crayons
517        $permanent = empty($titre_version) ? 'non' : '';
518
519        // Detruire les tentatives d'archivages non abouties en 1 heure
520        sql_delete('spip_versions', array(
521                        "id_objet=" . intval($id_objet),
522                        "objet=" . sql_quote($objet),
523                        "id_version <= 0",
524                        "date < DATE_SUB(" . sql_quote(date('Y-m-d H:i:s')) . ", INTERVAL " . _INTERVALLE_REVISIONS . " SECOND)"
525                )
526        );
527
528        // Signaler qu'on opere en mettant un numero de version negatif
529        // distinctif (pour eviter la violation d'unicite)
530        // et un titre contenant en fait le moment de l'insertion
531        list($ms, $sec) = explode(' ', microtime());
532        $date = $sec . substr($ms, 1,
533                        4)-20; // SQL ne ramene que 4 chiffres significatifs apres la virgule pour 0.0+titre_version
534        $datediff = ($sec-mktime(0, 0, 0, 9, 1, 2007))*1000000+substr($ms, 2, strlen($ms)-4);
535
536        $valeurs = array(
537                'id_objet' => $id_objet,
538                'objet' => $objet,
539                'id_version' => (0-$datediff),
540                'date' => date('Y-m-d H:i:s'),
541                'id_auteur' => $str_auteur, //  varchar ici!
542                'titre_version' => $date
543        );
544        sql_insertq('spip_versions', $valeurs);
545
546        // Eviter les validations entremelees en s'endormant s'il existe
547        // une version <0 plus recente mais pas plus vieille que 10s
548        // Une <0 encore plus vieille est une operation avortee,
549        // on passe outre (vaut mieux archiver mal que pas du tout).
550        // Pour tester:
551        // 0. mettre le delai a 30
552        // 1. decommenter le premier sleep(15)
553        // 2. enregistrer une modif
554        // 3. recommenter le premier sleep(15), decommenter le second.
555        // 4. enregistrer une autre modif dans les 15 secondes
556#         sleep(15);
557        $delai = $sec-10;
558        while (sql_countsel('spip_versions',
559                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version < 0 AND 0.0+titre_version < $date AND titre_version<>" . sql_quote($date,
560                        '', 'text') . " AND 0.0+titre_version > $delai")) {
561                spip_log("version $objet $id_objet :insertion en cours avant $date ($delai)", 'revisions');
562                sleep(1);
563                $delai++;
564        }
565#   sleep(15);  spip_log("sortie $sec $delai");
566        // Determiner le numero du prochain fragment
567        $next = sql_fetsel("id_fragment", "spip_versions_fragments",
568                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet), "", "id_fragment DESC", "1");
569
570        $onlylock = '';
571
572        // Examiner la derniere version
573        $row = sql_fetsel("id_version, champs, id_auteur, date, permanent", "spip_versions",
574                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version > 0", '', "id_version DESC",
575                "1"); // le champ id_auteur est un varchar dans cette table
576
577        if ($row) {
578                $id_version = $row['id_version'];
579                $paras_old = recuperer_fragments($id_objet, $objet, $id_version);
580                $champs_old = $row['champs'];
581                if ($row['id_auteur'] != $str_auteur
582                        OR $row['permanent'] != 'non'
583                        OR strtotime($row['date']) < (time()-_INTERVALLE_REVISIONS)
584                ) {
585                        spip_log(strtotime($row['date']), 'revisions');
586                        spip_log(time(), 'revisions');
587                        spip_log(_INTERVALLE_REVISIONS, 'revisions');
588                        $id_version++;
589                }
590                // version precedente recente, on va la mettre a jour
591                // avec les nouveaux arrivants si presents
592                else {
593                        $champs = reconstuire_version(unserialize($champs_old), $paras_old, $champs);
594                        $onlylock = 're';
595                }
596        } else {
597                $id_version = 1;
598        }
599
600        $next = !$next ? 1 : ($next['id_fragment']+1);
601
602        // Generer les nouveaux fragments
603        $codes = array();
604        foreach ($champs as $nom => $texte) {
605                $codes[$nom] = array();
606                $paras = separer_paras($texte, $paras);
607                $paras_champ[$nom] = count($paras);
608        }
609
610        // Apparier les fragments de maniere optimale
611        $n = count($paras);
612        if ($n) {
613                // Tables d'appariement dans les deux sens
614                list(, $trans) = apparier_paras($paras_old, $paras);
615                reset($champs);
616                $nom = '';
617
618                // eviter une notice PHP au tout debut de la boucle
619                // on ajoute ''=>0 en debut de tableau.
620                $paras_champ = array($nom => 0)+$paras_champ;
621
622                for ($i = 0; $i < $n; $i++) {
623                        while ($i >= $paras_champ[$nom]) {
624                                list($nom,) = each($champs);
625                        }
626                        // Lier au fragment existant si possible, sinon creer un nouveau fragment
627                        $id_fragment = isset($trans[$i]) ? $trans[$i] : $next++;
628                        $codes[$nom][] = $id_fragment;
629                        $fragments[$id_fragment] = $paras[$i];
630                }
631        }
632        foreach ($champs as $nom => $t) {
633                $codes[$nom] = join(' ', $codes[$nom]);
634                # avec la ligne qui suit, un champ qu'on vide ne s'enregistre pas
635                # if (!strlen($codes[$nom])) unset($codes[$nom]);
636        }
637
638        // Enregistrer les modifications
639        ajouter_fragments($id_objet, $objet, $id_version, $fragments);
640
641        // Si l'insertion ne servait que de verrou,
642        // la detruire apres mise a jour de l'ancienne entree,
643        // sinon la mise a jour efface en fait le verrou.
644
645        if (!$onlylock) {
646                sql_updateq('spip_versions', array(
647                        'id_version' => $id_version,
648                        'date' => date('Y-m-d H:i:s'),
649                        'champs' => serialize($codes),
650                        'permanent' => $permanent,
651                        'titre_version' => $titre_version
652                ),
653                        "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version < 0 AND titre_version='$date'");
654        } else {
655                sql_updateq('spip_versions', array(
656                        'date' => date('Y-m-d H:i:s'),
657                        'champs' => serialize($codes),
658                        'permanent' => $permanent,
659                        'titre_version' => $titre_version
660                ), "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version=$id_version");
661                sql_delete("spip_versions",
662                        "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version < 0 AND titre_version ='$date'");
663        }
664        spip_log($onlylock . "memorise la version $id_version de l'objet $objet $id_objet $titre_version", 'revisions');
665
666        return $id_version;
667}
668
669// les textes "diff" ne peuvent pas passer dans propre directement,
670// car ils contiennent des <span> et <div> parfois mal places
671// http://code.spip.net/@propre_diff
672function propre_diff($texte) {
673
674        $span_diff = array();
675        if (preg_match_all(',<(/)?(span|div) (class|rem)="diff-[^>]*>,', $texte, $regs, PREG_SET_ORDER)) {
676                $regs = array_slice($regs, 0, 500); #limiter la casse s'il y en a trop
677                foreach ($regs as $c => $reg) {
678                        $texte = str_replace($reg[0], '@@@SPIP_DIFF' . $c . '@@@', $texte);
679                }
680        }
681
682        // [ ...<span diff> -> lien ]
683        // < tag <span diff> >
684        $texte = preg_replace(',<([^>]*?@@@SPIP_DIFF[0-9]+@@@),',
685                '&lt;\1', $texte);
686
687        # attention ici astuce seulement deux @@ finals car on doit eviter
688        # deux patterns a suivre, afin de pouvoir prendre [ mais eviter [[
689        $texte = preg_replace(',(^|[^[])[[]([^[\]]*@@@SPIP_DIFF[0-9]+@@),',
690                '\1&#91;\2', $texte);
691
692        // desactiver TeX & toujours-paragrapher
693        $tex = $GLOBALS['traiter_math'];
694        $GLOBALS['traiter_math'] = '';
695        $mem = $GLOBALS['toujours_paragrapher'];
696        $GLOBALS['toujours_paragrapher'] = false;
697
698        $texte = propre($texte);
699
700        // retablir
701        $GLOBALS['traiter_math'] = $tex;
702        $GLOBALS['toujours_paragrapher'] = $mem;
703
704        // un blockquote mal ferme peut gener l'affichage, et title plante safari
705        $texte = preg_replace(',<(/?(blockquote|title)[^>]*)>,i', '&lt;\1>', $texte);
706
707        // Dans les <cadre> c'est un peu plus complique
708        if (preg_match_all(',<textarea (.*)</textarea>,Uims', $texte, $area, PREG_SET_ORDER)) {
709                foreach ($area as $reg) {
710                        $remplace = preg_replace(',@@@SPIP_DIFF[0-9]+@@@,', '**', $reg[0]);
711                        if ($remplace <> $reg[0]) {
712                                $texte = str_replace($reg[0], $remplace, $texte);
713                        }
714                }
715        }
716
717        // replacer les valeurs des <span> et <div> diff-
718        if (is_array($regs)) {
719                foreach ($regs as $c => $reg) {
720                        $bal = (!$reg[1]) ? $reg[0] : "</$reg[2]>";
721                        $texte = str_replace('@@@SPIP_DIFF' . $c . '@@@', $bal, $texte);
722                        $GLOBALS['les_notes'] = str_replace('@@@SPIP_DIFF' . $c . '@@@', $bal, $GLOBALS['les_notes']);
723                }
724        }
725
726
727        // quand le dernier tag est ouvrant le refermer ...
728        $reg = end($regs);
729        if (!$reg[1] AND $reg[2]) {
730                $texte .= "</$reg[2]>";
731        }
732
733        // et interdire_scripts !
734        $texte = interdire_scripts($texte);
735
736        return $texte;
737}
738
739
740/**
741 * Liste les champs versionnés d'une table objet.
742 *
743 * @param string $table
744 *     Nom complet de sa table sql. Exemple 'spip_articles'
745 * @return array
746 *     Liste des champs versionnés
747 */
748function liste_champs_versionnes($table) {
749        $liste_objets_versionnees = is_array(unserialize($GLOBALS['meta']['objets_versions'])) ? unserialize($GLOBALS['meta']['objets_versions']) : array();
750
751        if (!in_array($table, $liste_objets_versionnees)) {
752                return array();
753        }
754
755        include_spip('base/objets');
756        if ($infos = lister_tables_objets_sql($table)
757                AND isset($infos['champs_versionnes'])
758        ) {
759                return $infos['champs_versionnes'];
760        }
761
762        return array();
763}
764
765/**
766 * Lorsqu'un champ versionée est une jointure, récuperer tous les liens
767 * et les mettre sous forme de liste énumérée
768 *
769 * @param string $objet
770 * @param string $id_objet
771 * @param string $jointure
772 * @return string
773 */
774function recuperer_valeur_champ_jointure($objet, $id_objet, $jointure) {
775        $objet_joint = objet_type($jointure);
776        include_spip('action/editer_liens');
777        $v = array();
778        if (objet_associable($objet_joint)) {
779                $liens = objet_trouver_liens(array($objet_joint => '*'), array($objet => $id_objet));
780                foreach ($liens as $l) {
781                        $v[] = $l[$objet_joint];
782                }
783        } elseif (objet_associable($objet)) {
784                $liens = objet_trouver_liens(array($objet => $id_objet), array($objet_joint => '*'));
785                foreach ($liens as $l) {
786                        $v[] = $l[$objet];
787                }
788        }
789        sort($v);
790
791        return implode(",", $v);
792}
793
794/**
795 * Créer la première révision d'un objet si nécessaire
796 *
797 * À faire notamment si on vient d'activer l'extension et qu'on fait une modif
798 * sur un objet qui était déjà en base, mais non versionné
799 *
800 * La fonction renvoie le numéro de la dernière version de l'objet,
801 * et 0 si pas de version pour cet objet
802 *
803 * @param string $table
804 * @param string $objet
805 * @param int $id_objet
806 * @param array $champs
807 * @param int $id_auteur
808 * @return int
809 */
810function verifier_premiere_revision($table, $objet, $id_objet, $champs = null, $id_auteur = 0) {
811
812        $id_table_objet = id_table_objet($objet);
813        if (!$champs) {
814                $champs = liste_champs_versionnes($table);
815        }
816        if (!$champs) {
817                return false;
818        }
819
820        if (!$id_version = sql_getfetsel('id_version', 'spip_versions',
821                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet), '', 'id_version DESC', '0,1')
822        ) {
823                // recuperer toutes les valeurs actuelles des champs
824                // pour l'objet
825                $originaux = sql_fetsel("*", $table, "$id_table_objet=" . intval($id_objet));
826                $premiere = false;
827                $champs_originaux = array();
828
829                foreach ($champs as $v) {
830                        if (isset($originaux[$v])) {
831                                $champs_originaux[$v] = $originaux[$v];
832                        } elseif (strncmp($v, 'jointure_', 9) == 0) {
833                                $champs_originaux[$v] = recuperer_valeur_champ_jointure($objet, $id_objet, substr($v, 9));
834                        }
835                        if (isset($champs_originaux[$v]) AND isset($originaux[$v]) AND strlen($originaux[$v])) {
836                                $premiere = true;
837                        }
838                }
839
840                // Si un champ est non vide,
841                // il faut creer une premiere revision
842                if ($premiere) {
843                        $trouver_table = charger_fonction('trouver_table', 'base');
844                        $desc = $trouver_table($table);
845
846                        // "trouver" une date raisonnable pour la version initiale
847
848                        $date_modif = "";
849                        foreach (array('date_modif', 'maj') as $d) {
850                                if (!$date_modif AND isset($originaux[$d]) AND $t = strtotime($d)) {
851                                        $date_modif = date("Y-m-d H:i:s", $t-20);
852                                }
853                        }
854                        if (!$date_modif
855                                AND isset($desc['date'])
856                                AND isset($originaux[$desc['date']])
857                        ) {
858                                $date_modif = $originaux[$desc['date']];
859                        } elseif (!$date_modif) {
860                                $date_modif = date("Y-m-d H:i:s", time()-20);
861                        }
862
863                        if ($id_version = ajouter_version($id_objet, $objet, $champs_originaux, _T('revisions:version_initiale'),
864                                $id_auteur)
865                        ) {
866                                sql_updateq('spip_versions', array('date' => $date_modif),
867                                        "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version=$id_version");
868                        }
869                }
870        }
871
872        return $id_version;
873}
874
875?>
Note: See TracBrowser for help on using the repository browser.