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

Last change on this file was 113294, checked in by spip.franck@…, 2 months ago

Il parait que le futur c'est maintenant :-D

File size: 29.0 KB
Line 
1<?php
2
3/***************************************************************************\
4 *  SPIP, Systeme de publication pour l'internet                           *
5 *                                                                         *
6 *  Copyright (c) 2001-2019                                                *
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// https://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// https://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// https://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// https://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// https://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// https://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 * Retrouve les champs d'un objet et leurs contenus à une version donnée
432 *
433 * @uses recuperer_version()
434 * @uses liste_champs_versionnes()
435 * @uses retrouver_champ_version_objet()
436 *
437 * @param int $id_objet Identifiant de l'objet
438 * @param string $objet Objet
439 * @param int $id_version Identifiant de la version
440 * @return array           Couples champs => textes
441**/
442function recuperer_version_complete($id_objet, $objet, $id_version) {
443        if (!$id_version or !$id_objet or !$objet) {
444                return array();
445        }
446
447        include_spip('inc/suivi_versions');
448
449        // champs modifiés à la version voulue
450        $textes = recuperer_version($id_objet, $objet, $id_version);
451
452        // tous les champs possibles versionnés pour l'objet
453        $champs = liste_champs_versionnes(table_objet_sql($objet));
454
455        foreach ($champs as $champ) {
456                // Remonter dans le temps pour trouver le champ en question pour chaque version
457                retrouver_champ_version_objet($objet, $id_objet, $id_version, $champ, $textes);
458        }
459
460        return $textes;
461}
462
463/**
464 * Récupérer les champs d'un objet, pour une version demandée
465 *
466 * @param int $id_objet Identifiant de l'objet
467 * @param string $objet Objet
468 * @param int $id_version Identifiant de la version
469 * @return array           Couples champs => textes
470 */
471function recuperer_version($id_objet, $objet, $id_version) {
472
473        $champs = sql_getfetsel("champs", "spip_versions",
474                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version=" . intval($id_version));
475        if (!$champs or !is_array($champs = unserialize($champs))) {
476                return array();
477        } else {
478                return reconstuire_version($champs, 
479                        recuperer_fragments($id_objet, $objet, $id_version));
480        }
481}
482
483/**
484 * Reconstruire une version donnée
485 *
486 * À partir de la liste des champs et de fragments,
487 * retourne le texte de chaque champ.
488 *
489 * @param array $champs Couples (champ => liste d'id_fragment).
490 *                          La liste est de la forme "5 32 7 16 8 2"
491 * @param array $fragments Couples (id_fragment => texte)
492 * @param array $res Couples (champ => texte) déjà connus
493 * @return array            Couples (champ => texte)
494 */
495function reconstuire_version($champs, $fragments, $res = array()) {
496
497        static $msg;
498        if (!$msg) {
499                $msg = _T('forum_titre_erreur');
500        }
501
502        foreach ($champs as $nom => $code) {
503                if (!isset($res[$nom])) {
504                        $t = '';
505                        foreach (array_filter(explode(' ', $code)) as $id) {
506                                $t .= isset($fragments[$id])
507                                        ? $fragments[$id]
508                                        : "[$msg$id]";
509                        }
510                        $res[$nom] = $t;
511                }
512        }
513
514        return $res;
515}
516
517// https://code.spip.net/@supprimer_versions
518function supprimer_versions($id_objet, $objet, $version_min, $version_max) {
519        sql_delete("spip_versions",
520                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version>=$version_min AND id_version<=$version_max");
521
522        supprimer_fragments($id_objet, $objet, $version_min, $version_max);
523}
524
525
526/**
527 * Ajouter une version à un objet éditorial
528 *
529 * @param int $id_objet
530 * @param string $objet
531 * @param array $champs
532 * @param string $titre_version
533 *     Titre donné aux modifications apportées
534 * @param int|null $id_auteur
535 *     Auteur apportant les modifications. En absence (session anonyme), utilisera l'IP pour garder une trace.
536 * @return int
537 *     id_version : identifiant de la version
538 **/
539function ajouter_version($id_objet, $objet, $champs, $titre_version = "", $id_auteur = null) {
540        $paras = $paras_old = $paras_champ = $fragments = array();
541
542        // Attention a une edition anonyme (type wiki): id_auteur n'est pas
543        // definie, on enregistre alors le numero IP
544        $str_auteur = intval($id_auteur) ? intval($id_auteur) : $GLOBALS['ip'];
545
546        // si pas de titre dans cette version, la marquer 'non' permanente,
547        // et elle pourra etre fusionnee avec une revision ulterieure dans un delai < _INTERVALLE_REVISIONS
548        // permet de fusionner plusieurs editions consecutives champs par champs avec les crayons
549        $permanent = empty($titre_version) ? 'non' : '';
550
551        // Detruire les tentatives d'archivages non abouties en 1 heure
552        sql_delete('spip_versions', array(
553                        "id_objet=" . intval($id_objet),
554                        "objet=" . sql_quote($objet),
555                        "id_version <= 0",
556                        "date < DATE_SUB(" . sql_quote(date('Y-m-d H:i:s')) . ", INTERVAL " . _INTERVALLE_REVISIONS . " SECOND)"
557                )
558        );
559
560        // Signaler qu'on opere en mettant un numero de version negatif
561        // distinctif (pour eviter la violation d'unicite)
562        // et un titre contenant en fait le moment de l'insertion
563        list($ms, $sec) = explode(' ', microtime());
564        $date = $sec . substr($ms, 1,
565                        4) - 20; // SQL ne ramene que 4 chiffres significatifs apres la virgule pour 0.0+titre_version
566        $datediff = ($sec - mktime(0, 0, 0, 9, 1, 2007)) * 1000000 + substr($ms, 2, strlen($ms) - 4);
567
568        $valeurs = array(
569                'id_objet' => $id_objet,
570                'objet' => $objet,
571                'id_version' => (0 - $datediff),
572                'date' => date('Y-m-d H:i:s'),
573                'id_auteur' => $str_auteur, //  varchar ici!
574                'titre_version' => $date
575        );
576        sql_insertq('spip_versions', $valeurs);
577
578        // Eviter les validations entremelees en s'endormant s'il existe
579        // une version <0 plus recente mais pas plus vieille que 10s
580        // Une <0 encore plus vieille est une operation avortee,
581        // on passe outre (vaut mieux archiver mal que pas du tout).
582        // Pour tester:
583        // 0. mettre le delai a 30
584        // 1. decommenter le premier sleep(15)
585        // 2. enregistrer une modif
586        // 3. recommenter le premier sleep(15), decommenter le second.
587        // 4. enregistrer une autre modif dans les 15 secondes
588#         sleep(15);
589        $delai = $sec - 10;
590        while (sql_countsel('spip_versions',
591                "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,
592                        '', 'text') . " AND 0.0+titre_version > $delai")) {
593                spip_log("version $objet $id_objet :insertion en cours avant $date ($delai)", 'revisions');
594                sleep(1);
595                $delai++;
596        }
597#   sleep(15);  spip_log("sortie $sec $delai");
598        // Determiner le numero du prochain fragment
599        $next = sql_fetsel("id_fragment", "spip_versions_fragments",
600                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet), "", "id_fragment DESC", "1");
601
602        $onlylock = '';
603
604        // Examiner la derniere version
605        $row = sql_fetsel("id_version, champs, id_auteur, date, permanent", "spip_versions",
606                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version > 0", '', "id_version DESC",
607                "1"); // le champ id_auteur est un varchar dans cette table
608
609        if ($row) {
610                $id_version = $row['id_version'];
611                $paras_old = recuperer_fragments($id_objet, $objet, $id_version);
612                $champs_old = $row['champs'];
613                if ($row['id_auteur'] != $str_auteur
614                        or $row['permanent'] != 'non'
615                        or strtotime($row['date']) < (time() - _INTERVALLE_REVISIONS)
616                ) {
617                        spip_log(strtotime($row['date']), 'revisions');
618                        spip_log(time(), 'revisions');
619                        spip_log(_INTERVALLE_REVISIONS, 'revisions');
620                        $id_version++;
621                }
622                // version precedente recente, on va la mettre a jour
623                // avec les nouveaux arrivants si presents
624                else {
625                        $champs = reconstuire_version(unserialize($champs_old), $paras_old, $champs);
626                        $onlylock = 're';
627                }
628        } else {
629                $id_version = 1;
630        }
631
632        $next = !$next ? 1 : ($next['id_fragment'] + 1);
633
634        // Generer les nouveaux fragments
635        $codes = array();
636        foreach ($champs as $nom => $texte) {
637                $codes[$nom] = array();
638                $paras = separer_paras($texte, $paras);
639                $paras_champ[$nom] = count($paras);
640        }
641
642        // Apparier les fragments de maniere optimale
643        $n = count($paras);
644        if ($n) {
645                // Tables d'appariement dans les deux sens
646                list(, $trans) = apparier_paras($paras_old, $paras);
647                reset($champs);
648                $nom = '';
649
650                // eviter une notice PHP au tout debut de la boucle
651                // on ajoute ''=>0 en debut de tableau.
652                $paras_champ = array($nom => 0) + $paras_champ;
653
654                for ($i = 0; $i < $n; $i++) {
655                        while ($i >= $paras_champ[$nom]) {
656                                $nom = key($champs);
657                                next($champs);
658                        }
659                        // Lier au fragment existant si possible, sinon creer un nouveau fragment
660                        $id_fragment = isset($trans[$i]) ? $trans[$i] : $next++;
661                        $codes[$nom][] = $id_fragment;
662                        $fragments[$id_fragment] = $paras[$i];
663                }
664        }
665        foreach ($champs as $nom => $t) {
666                $codes[$nom] = join(' ', $codes[$nom]);
667                # avec la ligne qui suit, un champ qu'on vide ne s'enregistre pas
668                # if (!strlen($codes[$nom])) unset($codes[$nom]);
669        }
670
671        // Enregistrer les modifications
672        ajouter_fragments($id_objet, $objet, $id_version, $fragments);
673
674        // Si l'insertion ne servait que de verrou,
675        // la detruire apres mise a jour de l'ancienne entree,
676        // sinon la mise a jour efface en fait le verrou.
677
678        if (!$onlylock) {
679                sql_updateq('spip_versions', array(
680                        'id_version' => $id_version,
681                        'date' => date('Y-m-d H:i:s'),
682                        'champs' => serialize($codes),
683                        'permanent' => $permanent,
684                        'titre_version' => $titre_version
685                ),
686                        "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version < 0 AND titre_version='$date'");
687        } else {
688                sql_updateq('spip_versions', array(
689                        'date' => date('Y-m-d H:i:s'),
690                        'champs' => serialize($codes),
691                        'permanent' => $permanent,
692                        'titre_version' => $titre_version
693                ), "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version=$id_version");
694                sql_delete("spip_versions",
695                        "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version < 0 AND titre_version ='$date'");
696        }
697        spip_log($onlylock . "memorise la version $id_version de l'objet $objet $id_objet $titre_version", 'revisions');
698
699        return $id_version;
700}
701
702// les textes "diff" ne peuvent pas passer dans propre directement,
703// car ils contiennent des <span> et <div> parfois mal places
704// https://code.spip.net/@propre_diff
705function propre_diff($texte) {
706
707        $span_diff = array();
708        if (preg_match_all(',<(/)?(span|div) (class|rem)="diff-[^>]*>,', $texte, $regs, PREG_SET_ORDER)) {
709                $regs = array_slice($regs, 0, 500); #limiter la casse s'il y en a trop
710                foreach ($regs as $c => $reg) {
711                        $texte = str_replace($reg[0], '@@@SPIP_DIFF' . $c . '@@@', $texte);
712                }
713        }
714
715        // [ ...<span diff> -> lien ]
716        // < tag <span diff> >
717        $texte = preg_replace(',<([^>]*?@@@SPIP_DIFF[0-9]+@@@),',
718                '&lt;\1', $texte);
719
720        # attention ici astuce seulement deux @@ finals car on doit eviter
721        # deux patterns a suivre, afin de pouvoir prendre [ mais eviter [[
722        $texte = preg_replace(',(^|[^[])[[]([^[\]]*@@@SPIP_DIFF[0-9]+@@),',
723                '\1&#91;\2', $texte);
724
725        // desactiver TeX & toujours-paragrapher
726        $tex = $GLOBALS['traiter_math'];
727        $GLOBALS['traiter_math'] = '';
728        $mem = $GLOBALS['toujours_paragrapher'];
729        $GLOBALS['toujours_paragrapher'] = false;
730
731        $texte = propre($texte);
732
733        // retablir
734        $GLOBALS['traiter_math'] = $tex;
735        $GLOBALS['toujours_paragrapher'] = $mem;
736
737        // un blockquote mal ferme peut gener l'affichage, et title plante safari
738        $texte = preg_replace(',<(/?(blockquote|title)[^>]*)>,i', '&lt;\1>', $texte);
739
740        // Dans les <cadre> c'est un peu plus complique
741        if (preg_match_all(',<textarea (.*)</textarea>,Uims', $texte, $area, PREG_SET_ORDER)) {
742                foreach ($area as $reg) {
743                        $remplace = preg_replace(',@@@SPIP_DIFF[0-9]+@@@,', '**', $reg[0]);
744                        if ($remplace <> $reg[0]) {
745                                $texte = str_replace($reg[0], $remplace, $texte);
746                        }
747                }
748        }
749
750        // replacer les valeurs des <span> et <div> diff-
751        if (is_array($regs)) {
752                foreach ($regs as $c => $reg) {
753                        $bal = (!$reg[1]) ? $reg[0] : "</$reg[2]>";
754                        $texte = str_replace('@@@SPIP_DIFF' . $c . '@@@', $bal, $texte);
755                        $GLOBALS['les_notes'] = str_replace('@@@SPIP_DIFF' . $c . '@@@', $bal, $GLOBALS['les_notes']);
756                }
757        }
758
759
760        // quand le dernier tag est ouvrant le refermer ...
761        $reg = end($regs);
762        if (!$reg[1] and $reg[2]) {
763                $texte .= "</$reg[2]>";
764        }
765
766        // et interdire_scripts !
767        $texte = interdire_scripts($texte);
768
769        return $texte;
770}
771
772
773/**
774 * Liste les champs versionnés d'une table objet.
775 *
776 * @param string $table
777 *     Nom complet de sa table sql. Exemple 'spip_articles'
778 * @return array
779 *     Liste des champs versionnés
780 */
781function liste_champs_versionnes($table) {
782        $liste_objets_versionnees = is_array(unserialize($GLOBALS['meta']['objets_versions'])) ? unserialize($GLOBALS['meta']['objets_versions']) : array();
783
784        if (!in_array($table, $liste_objets_versionnees)) {
785                return array();
786        }
787
788        include_spip('base/objets');
789        if ($infos = lister_tables_objets_sql($table)
790                and isset($infos['champs_versionnes'])
791        ) {
792                return $infos['champs_versionnes'];
793        }
794
795        return array();
796}
797
798/**
799 * Lorsqu'un champ versionée est une jointure, récuperer tous les liens
800 * et les mettre sous forme de liste énumérée
801 *
802 * @param string $objet
803 * @param string $id_objet
804 * @param string $jointure
805 * @return string
806 */
807function recuperer_valeur_champ_jointure($objet, $id_objet, $jointure) {
808        $objet_joint = objet_type($jointure);
809        include_spip('action/editer_liens');
810        $v = array();
811        if (objet_associable($objet_joint)) {
812                $liens = objet_trouver_liens(array($objet_joint => '*'), array($objet => $id_objet));
813                foreach ($liens as $l) {
814                        $v[] = $l[$objet_joint];
815                }
816        } elseif (objet_associable($objet)) {
817                $liens = objet_trouver_liens(array($objet => $id_objet), array($objet_joint => '*'));
818                foreach ($liens as $l) {
819                        $v[] = $l[$objet];
820                }
821        }
822        sort($v);
823
824        return implode(",", $v);
825}
826
827/**
828 * Créer la première révision d'un objet si nécessaire
829 *
830 * À faire notamment si on vient d'activer l'extension et qu'on fait une modif
831 * sur un objet qui était déjà en base, mais non versionné
832 *
833 * La fonction renvoie le numéro de la dernière version de l'objet,
834 * et 0 si pas de version pour cet objet
835 *
836 * @param string $table
837 * @param string $objet
838 * @param int $id_objet
839 * @param array $champs
840 * @param int $id_auteur
841 * @return int
842 */
843function verifier_premiere_revision($table, $objet, $id_objet, $champs = null, $id_auteur = 0) {
844
845        $id_table_objet = id_table_objet($objet);
846        if (!$champs) {
847                $champs = liste_champs_versionnes($table);
848        }
849        if (!$champs) {
850                return false;
851        }
852
853        if (!$id_version = sql_getfetsel('id_version', 'spip_versions',
854                "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet), '', 'id_version DESC', '0,1')
855        ) {
856                // recuperer toutes les valeurs actuelles des champs
857                // pour l'objet
858                $originaux = sql_fetsel("*", $table, "$id_table_objet=" . intval($id_objet));
859                $premiere = false;
860                $champs_originaux = array();
861
862                foreach ($champs as $v) {
863                        if (isset($originaux[$v])) {
864                                $champs_originaux[$v] = $originaux[$v];
865                        } elseif (strncmp($v, 'jointure_', 9) == 0) {
866                                $champs_originaux[$v] = recuperer_valeur_champ_jointure($objet, $id_objet, substr($v, 9));
867                        }
868                        if (isset($champs_originaux[$v]) and isset($originaux[$v]) and strlen($originaux[$v])) {
869                                $premiere = true;
870                        }
871                }
872
873                // Si un champ est non vide,
874                // il faut creer une premiere revision
875                if ($premiere) {
876                        $trouver_table = charger_fonction('trouver_table', 'base');
877                        $desc = $trouver_table($table);
878
879                        // "trouver" une date raisonnable pour la version initiale
880
881                        $date_modif = "";
882                        foreach (array('date_modif', 'maj') as $d) {
883                                if (!$date_modif and isset($originaux[$d]) and $t = strtotime($originaux[$d])) {
884                                        $date_modif = date("Y-m-d H:i:s", $t - 20);
885                                }
886                        }
887                        if (!$date_modif
888                                and isset($desc['date'])
889                                and isset($originaux[$desc['date']])
890                        ) {
891                                $date_modif = $originaux[$desc['date']];
892                        } elseif (!$date_modif) {
893                                $date_modif = date("Y-m-d H:i:s", time() - 20);
894                        }
895
896                        if ($id_version = ajouter_version($id_objet, $objet, $champs_originaux, _T('revisions:version_initiale'),
897                                $id_auteur)
898                        ) {
899                                sql_updateq('spip_versions', array('date' => $date_modif),
900                                        "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version=$id_version");
901                        }
902                }
903        }
904
905        return $id_version;
906}
Note: See TracBrowser for help on using the repository browser.