function ajouter_version($id_objet, $objet, $champs, $titre_version = "", $id_auteur) { $paras = $paras_old = $paras_champ = $fragments = array(); // Attention a une edition anonyme (type wiki): id_auteur n'est pas // definie, on enregistre alors le numero IP $str_auteur = intval($id_auteur) ? intval($id_auteur) : $GLOBALS['ip']; // si pas de titre dans cette version, la marquer 'non' permanente, // et elle pourra etre fusionnee avec une revision ulterieure dans un delai < _INTERVALLE_REVISIONS // permet de fusionner plusieurs editions consecutives champs par champs avec les crayons $permanent = empty($titre_version) ? 'non' : ''; // Detruire les tentatives d'archivages non abouties en 1 heure sql_delete('spip_versions', "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version <= 0 AND date < DATE_SUB(" . sql_quote(date('Y-m-d H:i:s')) . ", INTERVAL " . _INTERVALLE_REVISIONS . " SECOND)"); // Signaler qu'on opere en mettant un numero de version negatif // distinctif (pour eviter la violation d'unicite) // et un titre contenant en fait le moment de l'insertion list($ms, $sec) = explode(' ', microtime()); $date = $sec . substr($ms, 1, 4); // SQL ne ramene que 4 chiffres significatifs apres la virgule pour 0.0+titre_version $datediff = ($sec - mktime(0, 0, 0, 9, 1, 2007)) * 1000000 + substr($ms, 2, strlen($ms) - 4); $valeurs = array('id_objet' => $id_objet, 'objet' => $objet, 'id_version' => 0 - $datediff, 'date' => date('Y-m-d H:i:s'), 'id_auteur' => $str_auteur, 'titre_version' => $date); sql_insertq('spip_versions', $valeurs); // Eviter les validations entremelees en s'endormant s'il existe // une version <0 plus recente mais pas plus vieille que 10s // Une <0 encore plus vieille est une operation avortee, // on passe outre (vaut mieux archiver mal que pas du tout). // Pour tester: // 0. mettre le delai a 30 // 1. decommenter le premier sleep(15) // 2. enregistrer une modif // 3. recommenter le premier sleep(15), decommenter le second. // 4. enregistrer une autre modif dans les 15 secondes # sleep(15); $delai = $sec - 10; while (sql_countsel('spip_versions', "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, '', 'text') . " AND 0.0+titre_version > {$delai}")) { spip_log("version {$objet} {$id_objet} :insertion en cours avant {$date} ({$delai})"); sleep(1); $delai++; } # sleep(15); spip_log("sortie $sec $delai"); // Determiner le numero du prochain fragment $next = sql_fetsel("id_fragment", "spip_versions_fragments", "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet), "", "id_fragment DESC", "1"); $onlylock = ''; // Examiner la derniere version $row = sql_fetsel("id_version, champs, id_auteur, date, permanent", "spip_versions", "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version > 0", '', "id_version DESC", "1"); // le champ id_auteur est un varchar dans cette table if ($row) { $id_version = $row['id_version']; $paras_old = recuperer_fragments($id_objet, $objet, $id_version); $champs_old = $row['champs']; if ($row['id_auteur'] != $str_auteur or $row['permanent'] != 'non' or strtotime($row['date']) < time() - _INTERVALLE_REVISIONS) { spip_log(strtotime($row['date']), 'revisions'); spip_log(time(), 'revisions'); spip_log(_INTERVALLE_REVISIONS, 'revisions'); $id_version++; } else { $champs = reconstuire_version(unserialize($champs_old), $paras_old, $champs); $onlylock = 're'; } } else { $id_version = 1; } spip_log($str_auteur, 'revisions'); spip_log($row, 'revisions'); spip_log($id_version, 'revisions'); $next = !$next ? 1 : $next['id_fragment'] + 1; // Generer les nouveaux fragments $codes = array(); foreach ($champs as $nom => $texte) { $codes[$nom] = array(); $paras = separer_paras($texte, $paras); $paras_champ[$nom] = count($paras); } // Apparier les fragments de maniere optimale $n = count($paras); if ($n) { // Tables d'appariement dans les deux sens list(, $trans) = apparier_paras($paras_old, $paras); reset($champs); $nom = ''; // eviter une notice PHP au tout debut de la boucle // on ajoute ''=>0 en debut de tableau. $paras_champ = array($nom => 0) + $paras_champ; for ($i = 0; $i < $n; $i++) { while ($i >= $paras_champ[$nom]) { list($nom, ) = each($champs); } // Lier au fragment existant si possible, sinon creer un nouveau fragment $id_fragment = isset($trans[$i]) ? $trans[$i] : $next++; $codes[$nom][] = $id_fragment; $fragments[$id_fragment] = $paras[$i]; } } foreach ($champs as $nom => $t) { $codes[$nom] = join(' ', $codes[$nom]); # avec la ligne qui suit, un champ qu'on vide ne s'enregistre pas # if (!strlen($codes[$nom])) unset($codes[$nom]); } // Enregistrer les modifications ajouter_fragments($id_objet, $objet, $id_version, $fragments); // Si l'insertion ne servait que de verrou, // la detruire apres mise a jour de l'ancienne entree, // sinon la mise a jour efface en fait le verrou. if (!$onlylock) { sql_updateq('spip_versions', array('id_version' => $id_version, 'date' => date('Y-m-d H:i:s'), 'champs' => serialize($codes), 'permanent' => $permanent, 'titre_version' => $titre_version), "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version < 0 AND titre_version='{$date}'"); } else { sql_updateq('spip_versions', array('date' => date('Y-m-d H:i:s'), 'champs' => serialize($codes), 'permanent' => $permanent, 'titre_version' => $titre_version), "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version={$id_version}"); sql_delete("spip_versions", "id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version < 0 AND titre_version ='{$date}'"); } spip_log($onlylock . "memorise la version {$id_version} de l'objet {$objet} {$id_objet} {$titre_version}"); return $id_version; }
public function comparer($new, $old) { $paras = $this->diff->segmenter($new); $paras_old = $this->diff->segmenter($old); if ($this->diff->fuzzy()) { list($trans_rev, $trans) = apparier_paras($paras_old, $paras); $lcs = lcs_opt($trans); $lcs_rev = array_flip($lcs); } else { list($trans_rev, $trans) = lcs($paras_old, $paras); $lcs = $trans; $lcs_rev = $trans_rev; } reset($paras_old); reset($paras); reset($lcs); unset($i_old); $fin_old = false; foreach ($paras as $i => $p) { if (!isset($trans[$i])) { // Paragraphe ajoute $this->diff->ajouter($p); continue; } $j = $trans[$i]; if (!isset($lcs[$i])) { // Paragraphe deplace $this->diff->deplacer($p, $paras_old[$j]); continue; } if (!$fin_old) { // Paragraphes supprimes jusqu'au paragraphe courant if (!isset($i_old)) { list($i_old, $p_old) = each($paras_old); if (!$p_old) { $fin_old = true; } } while (!$fin_old && $i_old < $j) { if (!isset($trans_rev[$i_old])) { $this->diff->supprimer($p_old); } unset($i_old); list($i_old, $p_old) = each($paras_old); if (!$p_old) { $fin_old = true; } } } // Paragraphe n'ayant pas change de place $this->diff->comparer($p, $paras_old[$j]); } // Paragraphes supprimes a la fin du texte if (!$fin_old) { if (!isset($i_old)) { list($i_old, $p_old) = each($paras_old); if (!strlen($p_old)) { $fin_old = true; } } while (!$fin_old) { if (!isset($trans_rev[$i_old])) { $this->diff->supprimer($p_old); } list($i_old, $p_old) = each($paras_old); if (!$p_old) { $fin_old = true; } } } if (isset($i_old)) { if (!isset($trans_rev[$i_old])) { $this->diff->supprimer($p_old); } } return $this->diff->resultat(); }