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 | **/ |
---|
18 | if (!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 */ |
---|
25 | define('_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 | **/ |
---|
34 | function 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 |
---|
51 | function 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 |
---|
84 | function 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 |
---|
93 | function 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 |
---|
106 | function 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 |
---|
160 | function 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 | */ |
---|
290 | function 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 |
---|
354 | function 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 | */ |
---|
439 | function 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 | */ |
---|
463 | function 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 |
---|
486 | function 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 | **/ |
---|
507 | function 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 |
---|
672 | function 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 | '<\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[\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', '<\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 | */ |
---|
748 | function 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 | */ |
---|
774 | function 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 | */ |
---|
810 | function 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 | } |
---|