Your IP : 216.73.217.34


Current Path : /home/checkyd/www/7b5d0/
Upload File :
Current File : /home/checkyd/www/7b5d0/inc.tar

date_gestion.php000064400000002465152525113000007723 0ustar00<?php
/**
 * Plugin Spip-Bonux
 * Le plugin qui lave plus SPIP que SPIP
 * (c) 2008 Mathieu Marcillaud, Cedric Morin, Tetue
 * Licence GPL
 */

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

/**
 * Recuperer les champs date_xx et heure_xx, verifier leur coherence et les reformater
 *
 * @param string $suffixe
 * @param bool $horaire
 * @param array $erreurs
 * @return int
 */
function verifier_corriger_date_saisie($suffixe, $horaire, &$erreurs) {
	include_spip('inc/filtres');
	$date = _request("date_$suffixe").($horaire?' '.trim(_request("heure_$suffixe")).':00':'');
	$date = recup_date($date);
	if (!$date) {
		return '';
	}
	$ret = null;
	if (!$ret=mktime(0, 0, 0, $date[1], $date[2], $date[0])) {
		$erreurs["date_$suffixe"] = _T('spip_bonux:erreur_date');
	} elseif (!$ret=mktime($date[3], $date[4], $date[5], $date[1], $date[2], $date[0])) {
		$erreurs["date_$suffixe"] = _T('spip_bonux:erreur_heure');
	}
	if ($ret) {
		if (trim(_request("date_$suffixe") !== ($d=date('d/m/Y', $ret)))) {
			$erreurs["date_$suffixe"] = _T('spip_bonux:erreur_date_corrigee');
			set_request("date_$suffixe", $d);
		}
		if ($horaire and trim(_request("heure_$suffixe") !== ($h = date('H:i', $ret)))) {
			$erreurs["heure_$suffixe"] = _T('spip_bonux:erreur_heure_corrigee');
			set_request("heure_$suffixe", $h);
		}
	}
	return $ret;
}
importer_csv.php000064400000010661152525113000007767 0ustar00<?php
/**
 * Plugin Spip-Bonux
 * Le plugin qui lave plus SPIP que SPIP
 * (c) 2008 Mathieu Marcillaud, Cedric Morin, Tetue
 * Licence GPL
 *
 * Fonctions de lecture d'un fichier CSV pour transformation en array()
 *
 */

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

include_spip('inc/charsets');

/**
 * Based on an example by ramdac at ramdac dot org
 * Returns a multi-dimensional array from a CSV file optionally using the
 * first row as a header to create the underlying data as associative arrays.
 *
 * @param string $file Filepath including filename
 * @param bool $head Use first row as header.
 * @param string $delim Specify a delimiter other than a comma.
 * @param int $len Line length to be passed to fgetcsv
 * @return array or false on failure to retrieve any rows.
 */

/**
 * Importer le charset d'une ligne
 *
 * Importe un texte de CSV dans un charset et le passe dans le charset du site (utf8 probablement)
 * Les CSV peuvent sous ms@@@ avoir le charset 'iso-8859-1', mais pasfois aussi 'windows-1252' :/
 *
 * @param mixed $texte
 * @param bool|string $definir_charset_source
 *     false : ne fait rien
 *     string : utilisera pour les prochains imports le charset indiqué
 *     true : remet le charset d'import par défaut de la fonction
 * @return array
 */
function importer_csv_importcharset($texte, $definir_charset_source = false) {
	// le plus frequent, en particulier avec les trucs de ms@@@
	static $charset_source = 'iso-8859-1';
	if ($definir_charset_source) {
		if ($definir_charset_source === true) {
			$charset_source = 'iso-8859-1';
		} else {
			$charset_source = $definir_charset_source;
		}
	}
	// mais open-office sait faire mieux, donc mefiance !
	if (is_utf8($texte)) {
		$charset = 'utf-8';
	} else {
		$charset = $charset_source;
	}
	return importer_charset($texte, $charset);
}

/**
 * enlever les accents des cles presentes dans le head,
 * sinon ca pose des problemes ...
 *
 * @param string $key
 * @return string
 */
function importer_csv_nettoie_key($key) {
	return translitteration($key);
}

/**
 * Lit un fichier csv et retourne un tableau
 * si $head est true, la premiere ligne est utilisee en header
 * pour generer un tableau associatif
 *
 * @param string $file
 * @param array $options
 *   bool $head
 *   string $delim
 *   string $enclos
 *   int $len
 *   string $charset_source : Permet de définir un charset source du CSV, si différent de utf-8 ou iso-8859-1
 * @return false|array
 */
function inc_importer_csv_dist($file, $options = []) {

	// support ancienne syntaxe
	// inc_importer_csv_dist($file, $head = false, $delim = ',', $enclos = '"', $len = 10000, $charset_source = '')
	if (!is_array($options)) {
		$args = func_get_args();
		$options = [];
		foreach ([1 => 'head', 2 => 'delim', 3 => 'enclos', 4 => 'len', 5 => 'charset_source'] as $k => $option) {
			if (!empty($args[$k])) {
				$options[$option] = $args[$k];
			}
		}
	}

	$default_options = [
		'head' => false,
		'delim' => ',',
		'enclos' => '"',
		'len' => 10000,
		'charset_source' => '',
	];
	$options = array_merge($default_options, $options);


	$return = false;
	if (@file_exists($file)
		and $handle = fopen($file, 'r')) {
		if ($options['charset_source']) {
			importer_csv_importcharset('', $options['charset_source']);
		}
		if ($options['head']) {
			$header = fgetcsv($handle, $options['len'], $options['delim'], $options['enclos']);
			if ($header) {
				$header = array_map('importer_csv_importcharset', $header);
				$header = array_map('importer_csv_nettoie_key', $header);
				$header_type = array();
				foreach ($header as $heading) {
					if (!isset($header_type[$heading])) {
						$header_type[$heading] = "scalar";
					} else {
						$header_type[$heading] = "array";
					}
				}
			}
		}

		while (($data = fgetcsv($handle, $options['len'], $options['delim'], $options['enclos'])) !== false) {
			$data = array_map('importer_csv_importcharset', $data);
			if ($options['head'] and isset($header)) {
				$row = array();
				foreach ($header as $key => $heading) {
					if ($header_type[$heading] == "array") {
						if (!isset($row[$heading])) {
							$row[$heading] = array();
						}
						if (isset($data[$key]) and strlen($data[$key])) {
							$row[$heading][] = $data[$key];
						}
					} else {
						$row[$heading] = (isset($data[$key])) ? $data[$key] : '';
					}
				}
				$return[] = $row;
			} else {
				$return[] = $data;
			}
		}
		if ($options['charset_source']) {
			importer_csv_importcharset('', true);
		}
	}

	return $return;
}
exporter_csv.php000064400000015101152525113000007770 0ustar00<?php
/**
 * Plugin Spip-Bonux
 * Le plugin qui lave plus SPIP que SPIP
 * (c) 2008 Mathieu Marcillaud, Cedric Morin, Tetue
 * Licence GPL
 *
 * Fonctions d'export d'une requete sql ou d'un tableau
 * au format CSV
 * Merge du plugin csv_import et spip-surcharges
 *
 */

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

include_spip('inc/charsets');
include_spip('inc/filtres');
include_spip('inc/texte');

/**
 * Exporter un champ pour un export CSV : pas de retour a la ligne,
 * et echapper les guillements par des doubles guillemets
 *
 * NB : on supprime les retours lignes qui sont pourtant supportes par le standard CSV et LibreOffice, mais pas par Excel
 *
 * @param string $champ
 * @return string
 */
function exporter_csv_champ($champ) {
	$champ = str_replace("\r\n", "\n", $champ);
	$champ = str_replace("\r", "\n", $champ);
	$champ = preg_replace(",\n\s+,ms", "\n", $champ);
	$champ = preg_replace(',\s[\s]+,ms', ' ', $champ);
	$champ = str_replace('"', '""', $champ);

	return '"' . $champ . '"';
}

/**
 * Exporter un champ pour un export CSV sans retour a la ligne,
 * (qui sont supportees par le standard CSV et LibreOffice, mais pas par Excel)
 *
 * et echapper les guillements par des doubles guillemets
 *
 * @param string $champ
 * @return string
 */
function exporter_csv_champ_no_lf($champ) {
	$champ = preg_replace(',[\s]+,ms', ' ', $champ);
	$champ = str_replace('"', '""', $champ);

	return '"' . $champ . '"';
}

/**
 * Exporter une ligne complete au format CSV, avec delimiteur fourni
 *
 * @uses exporter_csv_champ()
 * @uses exporter_csv_champ_no_lf()
 *
 * @param int $nb
 * @param array $ligne
 * @param string $delim
 * @param string|null $importer_charset
 *     Si défini exporte dans le charset indiqué
 * @param callable $callback
 * @return string
 */
function exporter_csv_ligne_numerotee($nb, $ligne, $delim = ',', $importer_charset = null, $callback=null, $fonction_exporter_champ=null) {
	if ($callback) {
		$ligne = call_user_func($callback, $nb, $ligne, $importer_charset);
	}
	if (!$fonction_exporter_champ or !function_exists($fonction_exporter_champ)) {
		$fonction_exporter_champ = 'exporter_csv_champ';
	}
	$output = join($delim, array_map($fonction_exporter_champ, $ligne)) . "\r\n";
	if ($importer_charset) {
		$output = str_replace('’', '\'', $output);
		$output = unicode2charset(html2unicode(charset2unicode($output)), $importer_charset);
	}
	return $output;
}

/**
 * @deprecated
 *
 * @param $ligne
 * @param string $delim
 * @param null $importer_charset
 * @return string
 */
function exporter_csv_ligne($ligne, $delim = ',', $importer_charset = null){
	return exporter_csv_ligne_numerotee(null, $ligne, $delim, $importer_charset);
}

/**
 * Exporte une ressource sous forme de fichier CSV
 *
 * La ressource peut etre un tableau ou une resource SQL issue d'une requete
 * L'extension est choisie en fonction du delimiteur :
 * - si on utilise ',' c'est un vrai csv avec extension csv
 * - si on utilise ';' ou tabulation c'est pour E*cel, et on exporte en iso-truc, avec une extension .xls
 *
 * @uses exporter_csv_ligne()
 *
 * @param string $titre
 *   titre utilise pour nommer le fichier
 * @param array|resource $resource
 * @param array $options
 *   string $delim : delimiteur
 *   array $entetes : tableau d'en-tetes pour nommer les colonnes (genere la premiere ligne)
 *   bool $envoyer : pour envoyer le fichier exporte (permet le telechargement)
 *   string $charset : charset de l'export si different de celui du site
 *   callable callback : fonction callback a appeler sur chaque ligne pour mettre en forme/completer les donnees
 * @return string
 */
function inc_exporter_csv_dist($titre, $resource, $options = []) {

	// support ancienne syntaxe
	// inc_exporter_csv_dist($titre, $resource, $delim = ', ', $entetes = null, $envoyer = true)
	if (!is_array($options)) {
		$args = func_get_args();
		$options = [];
		foreach (array(2 => 'delim', 3 => 'entetes', 4 => 'envoyer') as $k => $option) {
			if (!empty($args[$k])) {
				$options[$option] = $args[$k];
			}
		}
	}

	$default_options = array(
		'delim' => ', ',
		'entetes' => null,
		'envoyer' => true,
		'charset' => null,
		'callback' => null,
	);
	$options = array_merge($default_options, $options);

	$filename = preg_replace(',[^-_\w]+,', '_', translitteration(textebrut(typo($titre))));

	if ($options['delim'] == 'TAB') {
		$options['delim'] = "\t";
	}
	if (!in_array($options['delim'], array(',', ';', "\t"))) {
		$options['delim'] = ',';
	}

	$charset = $GLOBALS['meta']['charset'];
	$importer_charset = null;
	$fonction_exporter_champ = null;
	if ($options['delim'] == ',') {
		$extension = 'csv';
	} else {
		$extension = 'xls';
		# Excel n'accepte pas l'utf-8 ni les entites html... on transcode tout ce qu'on peut
		$charset = 'iso-8859-1';
		# Excel n'accepte pas les retours ligne dans les CSV
		$fonction_exporter_champ = 'exporter_csv_champ_no_lf';
	}
	// mais si une option charset est explicite, elle a la priorite
	if (!empty($options['charset'])) {
		$charset = $options['charset'];
	}

	$importer_charset = (($charset === $GLOBALS['meta']['charset']) ? null : $charset);

	$filename = "$filename.$extension";

	$output = '';
	$nb = 0;
	if (!empty($options['entetes']) and is_array($options['entetes'])) {
		$output = exporter_csv_ligne_numerotee($nb, $options['entetes'], $options['delim'], $importer_charset, $options['callback'], $fonction_exporter_champ);
	}
	// les donnees commencent toujours a la ligne 1, qu'il y ait ou non des entetes
	$nb++;

	if ($options['envoyer']) {
		$disposition = ($options['envoyer'] === 'attachment' ? 'attachment' : 'inline');
		header("Content-Type: text/comma-separated-values; charset=$charset");
		header("Content-Disposition: $disposition; filename=$filename");

		// Vider tous les tampons
		$level = @ob_get_level();
		while ($level--) {
			@ob_end_flush();
		}
	}

	// si envoyer=='attachment' on passe par un fichier temporaire
	// sinon on ecrit directement sur stdout
	if ($options['envoyer'] and $options['envoyer'] !== 'attachment') {
		$fichier = "php://output";
	}
	else {
		$fichier = sous_repertoire(_DIR_CACHE, 'export') . $filename;
	}

	$fp = fopen($fichier, 'w');
	$length = fwrite($fp, $output);

	while ($row = is_array($resource) ? array_shift($resource) : sql_fetch($resource)) {
		$output = exporter_csv_ligne_numerotee($nb, $row, $options['delim'], $importer_charset, $options['callback'], $fonction_exporter_champ);
		$length += fwrite($fp, $output);
		$nb++;
	}
	fclose($fp);

	if ($options['envoyer']) {
		if ($options['envoyer'] === 'attachment') {
			header("Content-Length: $length");
			readfile($fichier);
		}
		// si on a envoye inline, c'est deja tout bon
		exit;
	}

	return $fichier;
}
exporter_xls.php000064400000007545152525113000010020 0ustar00<?php
/**
 * Plugin Spip-Bonux
 * Le plugin qui lave plus SPIP que SPIP
 * (c) 2008 Mathieu Marcillaud, Cedric Morin, Tetue
 * Licence GPL
 *
 * Fonctions d'export d'une requete sql ou d'un tableau
 * au format CSV
 * Merge du plugin csv_import et spip-surcharges
 *
 */

use Export\ExportDataExcel;

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

include_spip('inc/charsets');
include_spip('inc/filtres');
include_spip('inc/texte');


/**
 * Preparer une ligne avant export XLS : charset si besoin + callback
 *
 * @param int $nb
 * @param array $ligne
 * @param string|null $importer_charset
 *     Si défini exporte dans le charset indiqué
 * @param callable $callback
 * @return string
 */
function exporter_xls_preparer_ligne_numerotee($nb, $ligne, $importer_charset = null, $callback=null) {
	if ($callback) {
		$ligne = call_user_func($callback, $nb, $ligne, $importer_charset);
	}
	foreach ($ligne as $k => $v) {
		$ligne[$k] = str_replace('’', '\'', $ligne[$k]);
		$ligne[$k] = rtrim($ligne[$k]);
		if ($importer_charset and !is_numeric($v)) {
			$ligne[$k] = unicode2charset(html2unicode(charset2unicode($ligne[$k])), $importer_charset);
		}
	}
	return $ligne;
}

/**
 * Exporte une ressource sous forme de fichier CSV
 *
 * La ressource peut etre un tableau ou une resource SQL issue d'une requete
 * L'extension est choisie en fonction du delimiteur :
 * - si on utilise ',' c'est un vrai csv avec extension csv
 * - si on utilise ';' ou tabulation c'est pour E*cel, et on exporte en iso-truc, avec une extension .xls
 *
 * @uses exporter_xls_ligne()
 *
 * @param string $titre
 *   titre utilise pour nommer le fichier
 * @param array|resource $resource
 * @param array $options
 *   array $entetes : tableau d'en-tetes pour nommer les colonnes (genere la premiere ligne)
 *   bool $envoyer : pour envoyer le fichier exporte (permet le telechargement)
 *   string $charset : charset de l'export si different de celui du site
 *   callable callback : fonction callback a appeler sur chaque ligne pour mettre en forme/completer les donnees
 * @return string
 */
function inc_exporter_xls_dist($titre, $resource, $options = []) {

	include_spip('lib/php-export-data/src/ExportData');
	include_spip('lib/php-export-data/src/ExportDataExcel');

	$default_options = [
		'entetes' => null,
		'envoyer' => true,
		'charset' => null,
		'callback' => null,
	];
	$options = array_merge($default_options, $options);

	$filename = preg_replace(',[^-_\w]+,', '_', translitteration(textebrut(typo($titre))));


	$extension = 'xls';

	$charset = $GLOBALS['meta']['charset'];
	// mais si une option charset est explicite, elle a la priorite
	if (!empty($options['charset'])) {
		$charset = $options['charset'];
	}

	$importer_charset = (($charset === $GLOBALS['meta']['charset']) ? null : $charset);
	$filename = "$filename.$extension";
	$fichier = sous_repertoire(_DIR_CACHE, 'export') . $filename;

	if ($options['envoyer']) {
		// Vider tous les tampons
		$level = @ob_get_level();
		while ($level--) {
			@ob_end_flush();
		}
	}

	$exporter = new ExportDataExcel($options['envoyer'] ? 'browser' : 'file', $fichier);
	$exporter->encoding = $charset;

	$exporter->initialize(); // start export

	$nb = 0;
	if (!empty($options['entetes']) and is_array($options['entetes'])) {
		$ligne = exporter_xls_preparer_ligne_numerotee($nb, $options['entetes'], $importer_charset, $options['callback']);
		$exporter->addRow($ligne);
	}

	// les donnees commencent toujours a la ligne 1, qu'il y ait ou non des entetes
	$nb++;

	while ($row = is_array($resource) ? array_shift($resource) : sql_fetch($resource)) {
		$ligne = exporter_xls_preparer_ligne_numerotee($nb, $row, $importer_charset, $options['callback']);
		$exporter->addRow($ligne);
		$nb++;
	}

	$exporter->finalize(); // writes the footer, flushes remaining data to browser.

	if ($options['envoyer']) {
		// si on a envoye inline, c'est deja tout bon
		exit;
	}

	return $fichier;
}
getdocument.php000064400000003220152525410300007565 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Gère un cas d'upload trop gros
 *
 * Fichier obsolète, à supprimer.
 * Mais fonction utilisée encore dans medias_detecter_fond_par_defaut()
 *
 * @package SPIP\Medias\Upload
 **/

#
# Fichier obsolete, a supprimer
#

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

// inclure les fonctions bases du core
include_once _DIR_RESTREINT . 'inc/documents.php';

include_spip('inc/minipres');


/**
 * Traite l'erreur d'un upload trop gros
 *
 * L'erreur est appelée depuis public.php et medias_detecter_fond_par_defaut
 * et affiche un minipres avec la taille limite de documents possibles
 *
 * @see minipres()
 **/
function erreur_upload_trop_gros() {
	include_spip('inc/filtres');

	$msg = '<p>'
		. taille_en_octets($_SERVER['CONTENT_LENGTH'])
		. '<br />'
		. _T(
			'medias:upload_limit',
			array('max' => ini_get('upload_max_filesize'))
		)
		. '</p>';

	echo minipres(_T('pass_erreur'), "<div class='upload_answer upload_error'>" . $msg . '</div>');
	exit;
}
choisir_mode_document.php000064400000004135152525410300011617 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Gestion des modes de documents
 *
 * @package SPIP\Medias\Modes
 */
if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

/**
 * Choisir le mode du document : image/document
 *
 * @param array $infos
 *     Informations sur le document (en base + prises sur le fichier)
 * @param bool $type_inclus_image
 *     Type d'inclusion demandée : true si image
 * @param string $objet
 *     Objet sur lequel est placé le document
 * @return string
 *     Mode du document
 */
function inc_choisir_mode_document($infos, $type_inclus_image, $objet) {

	// si ce n'est pas une image, c'est forcement un document
	if (!$infos['type_image'] or !$type_inclus_image) {
		return 'document';
	}

	// si on a pas le droit d'ajouter de document a l'objet, c'est donc un mode image
	if ($objet and isset($GLOBALS['meta']["documents_$objet"]) and ($GLOBALS['meta']["documents_$objet"] == 'non')) {
		return 'image';
	}


	// _INTERFACE_DOCUMENTS
	// en fonction de la taille de l'image
	// par defaut l'affectation en fonction de la largeur de l'image
	// est desactivee car pas comprehensible par le novice
	// il suffit de faire dans mes_options
	// define('_LARGEUR_MODE_IMAGE', 450);
	// pour beneficier de cette detection auto
	@define('_LARGEUR_MODE_IMAGE', 0);

	if (!_LARGEUR_MODE_IMAGE) {
		return 'image';
	}

	if ($infos['largeur'] > 0
		and $infos['largeur'] < _LARGEUR_MODE_IMAGE
	) {
		return 'image';
	} else {
		return 'document';
	}
}
documenter_objet.php000064400000003661152525410300010610 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Calcul du portfolio / liste de documents de l'espace privé
 *
 * @package SPIP\Medias\Fonctions
 **/

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

/**
 * Retourne le code HTML du portfolio, liste des documents et formulaire d'ajout de documents
 *
 * @note
 *     Marque les documents vus dans les textes de l'objet avant le calcul !
 *
 * @uses inc_marquer_doublons_doc_dist()
 *
 * @param int $id Identifiant de l'objet
 * @param string $type Type d'objet
 * @return string code HTML
 **/
function inc_documenter_objet_dist($id, $type) {
	$serveur = '';

	// avant de documenter un objet, on verifie que ses documents vus sont bien lies !
	$spip_table_objet = table_objet_sql($type);
	$table_objet = table_objet($type);
	$id_table_objet = id_table_objet($type, $serveur);
	$champs = sql_fetsel('*', $spip_table_objet, addslashes($id_table_objet) . '=' . intval($id));

	$marquer_doublons_doc = charger_fonction('marquer_doublons_doc', 'inc');
	$marquer_doublons_doc($champs, $id, $type, $id_table_objet, $table_objet, $spip_table_objet, '', $serveur);

	$contexte = array('objet' => $type, 'id_objet' => $id, 'espace_prive' => test_espace_prive());

	return recuperer_fond('prive/objets/contenu/portfolio_document', array_merge($_GET, $contexte));
}
determiner_statut_document.php000064400000005305152525410300012715 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Gestion des modes de documents
 *
 * @package SPIP\Medias\Modes
 */
if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

/**
 * Determiner le statut automatique d'un document
 * @param int $id_document
 * @param string $statut_ancien
 * @param string $date_publication_ancienne
 * @return array|false
 */
function inc_determiner_statut_document($id_document, $statut_ancien, $date_publication_ancienne) {

	$statut = 'prepa';

	$trouver_table = charger_fonction('trouver_table', 'base');
	$res = sql_select(
		'id_objet,objet',
		'spip_documents_liens',
		"objet!='document' AND id_document=" . intval($id_document)
	);

	// On aura 19 jours 3h14 et 7 secondes pour corriger en 2038 (limitation de la représentation POSIX du temps sur les 32 bits)
	$date_publication = strtotime('2038-01-01 00:00:00');
	include_spip('base/objets');
	while ($row = sql_fetch($res)) {
		if (
			// cas particulier des rubriques qui sont publiees des qu'elles contiennent un document !
			$row['objet'] == 'rubrique'
			// ou si objet publie selon sa declaration
			or objet_test_si_publie($row['objet'], $row['id_objet'])
		) {
			$statut = 'publie';
			$date_publication = 0;
			continue;
		} // si pas publie, et article, il faut checker la date de post-publi eventuelle
		elseif ($row['objet'] == 'article'
			and $row2 = sql_fetsel(
				'date',
				'spip_articles',
				'id_article=' . intval($row['id_objet']) . " AND statut='publie'"
			)
		) {
			$statut = 'publie';
			$date_publication = min($date_publication, strtotime($row2['date']));
		}
	}

	$date_publication = date('Y-m-d H:i:s', $date_publication);
	if ($statut == 'publie' and $statut_ancien == 'publie' and $date_publication == $date_publication_ancienne) {
		return false;
	}
	if ($statut != 'publie' and $statut_ancien != 'publie' and $statut_ancien != '0') {
		return false;
	}

	$champs = [];
	if ($statut !== $statut_ancien) {
		$champs['statut'] = $statut;
	}
	if ($date_publication !== $date_publication_ancienne) {
		$champs['date_publication'] = $date_publication;
	}

	return $champs;
}
renseigner_document.php000064400000012211152525410300011306 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Fonctions pour compléter les informations connues d'un document
 *
 * @package SPIP\Medias\Renseigner
 **/

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

/**
 * Récuperer les infos distantes d'une URL,
 * et renseigner pour une insertion en base
 *
 * Utilise une variable static car appellée plusieurs fois au cours du même hit
 * (vérification puis traitement)
 *
 * Un plugin peut avec le pipeline renseigner_document_distant renseigner
 * les informations fichier et mode avant l'analyse et traitement par ce plugin,
 * qui dans ce cas ne les fera pas. Exemple : OEmbed
 *
 * @pipeline_appel renseigner_document_distant
 *
 * @param string $source
 *     URL du document
 * @return array|string
 *     Informations sur le fichier distant, sinon message d'erreur.
 *     Sans erreur, retourne un tableau :
 *
 *     - source : URL du fichier
 *     - distant : Est-ce un fichier distant ?
 *     - mode : Mode d'inclusion
 *     - fichier : Chemin local du fichier s'il a été recopié
 */
function renseigner_source_distante($source) {
	static $infos = array();
	if (isset($infos[$source])) {
		return $infos[$source];
	}

	include_spip('inc/distant');
	// on passe la source dans le pipeline, le premier plugin
	// qui est capable de renseigner complete
	// fichier et mode + tous les autres champs a son gout
	// ex : oembed
	$a = pipeline('renseigner_document_distant', array('source' => $source));

	// si la source est encore la, en revenir a la
	// methode traditionnelle : chargement de l'url puis analyse
	if (!isset($a['fichier']) or !isset($a['mode'])) {
		if (!$a = recuperer_infos_distantes($a['source'])) {
			return _T('medias:erreur_chemin_distant', array('nom' => $source));
		}
		# NB: dans les bonnes conditions (fichier autorise et pas trop gros)
		# $a['fichier'] est une copie locale du fichier
		unset($a['body']);
		$a['distant'] = 'oui';
		$a['mode'] = 'document';
		$a['fichier'] = set_spip_doc($source);
	}

	// stocker pour la seconde demande
	return $infos[$source] = $a;
}

/**
 * Renseigner les informations de taille et dimension d'un document
 *
 * Récupère les informations de taille (largeur / hauteur / type_image / taille) d'un document
 * Utilise pour cela les fonctions du répertoire metadatas/*
 *
 * Ces fonctions de récupérations peuvent retourner d'autres champs si ces champs sont définis
 * comme editable dans la déclaration de la table spip_documents
 *
 * @todo
 *     Renommer cette fonction sans "_image"
 *
 * @param string $fichier
 *     Le fichier à examiner
 * @param string $ext
 *     L'extension du fichier à examiner
 * @param bool $distant
 *     Indique que le fichier peut etre distant, on essaiera alors d'en recuperer un bout pour en lire les meta infos
 * @return array|string $infos
 *
 *     - Si c'est une chaîne, c'est une erreur
 *     - Si c'est un tableau, l'ensemble des informations récupérées du fichier
 */
function renseigner_taille_dimension_image($fichier, $ext, $distant = false) {

	$infos = array(
		'largeur' => 0,
		'hauteur' => 0,
		'type_image' => '',
		'taille' => 0
	);

	// Quelques infos sur le fichier
	if (
		!$fichier
		or !@file_exists($fichier)
		or !$infos['taille'] = @intval(filesize($fichier))
	) {
		if ($distant) {
			// on ne saura pas la taille
			unset($infos['taille']);

			// recuperer un debut de fichier 512ko semblent suffire
			$tmp = _DIR_TMP . md5($fichier);
			$res = recuperer_url($fichier, array('file' => $tmp, 'taille_max' => 512 * 1024));
			if (!$res) {
				spip_log("Echec copie du fichier $fichier", 'medias');

				return _T('medias:erreur_copie_fichier', array('nom' => $fichier));
			}
			$fichier = $tmp;
		} else {
			spip_log("Echec copie du fichier $fichier", 'medias');

			return _T('medias:erreur_copie_fichier', array('nom' => $fichier));
		}
	}

	// chercher une fonction de description
	$meta = array();
	if ($metadata = charger_fonction($ext, 'metadata', true)) {
		$meta = $metadata($fichier);
	} else {
		$media = sql_getfetsel('media_defaut', 'spip_types_documents', 'extension=' . sql_quote($ext));
		if ($metadata = charger_fonction($media, 'metadata', true)) {
			$meta = $metadata($fichier);
		}
	}

	$meta = pipeline(
		'renseigner_document',
		array('args' => array('extension' => $ext, 'fichier' => $fichier), 'data' => $meta)
	);

	include_spip('inc/filtres'); # pour objet_info()
	$editables = objet_info('document', 'champs_editables');
	foreach ($meta as $m => $v) {
		if (isset($infos[$m]) or in_array($m, $editables)) {
			$infos[$m] = $v;
		}
	}

	return $infos;
}
verifier_document_mode_vignette.php000064400000002323152525410300013674 0ustar00<?php
/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

function inc_verifier_document_mode_vignette_dist($infos) {

	// Si on veut uploader une vignette, il faut qu'elle ait ete bien lue
	if ($infos['inclus'] != 'image') {
		return _T('medias:erreur_format_fichier_image', array('nom' => $infos['fichier']));
	} #SVG

	if (isset($infos['largeur']) and isset($infos['hauteur'])) {
		if (!($infos['largeur'] or $infos['hauteur'])) {
			return _T('medias:erreur_upload_vignette', array('nom' => $infos['fichier']));
		}
	}

	return true;
}
documents.php000064400000016746152525410300007271 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

// inclure les fonctions bases du core
include_once _DIR_RESTREINT . 'inc/documents.php';

include_spip('inc/actions'); // *action_auteur et determine_upload

// Constante indiquant le charset probable des documents non utf-8 joints

if (!defined('CHARSET_JOINT')) {
	define('CHARSET_JOINT', 'iso-8859-1');
}

// Filtre pour #FICHIER permettant d'incruster le contenu d'un document
// Si 2e arg fourni, conversion dans le charset du site si possible

// https://code.spip.net/@contenu_document
function contenu_document($arg, $charset = '') {
	include_spip('inc/distant');
	if (is_numeric($arg)) {
		$r = sql_fetsel('fichier,distant', 'spip_documents', 'id_document=' . intval($arg));
		if (!$r) {
			return '';
		}
		$f = $r['fichier'];
		$f = ($r['distant'] == 'oui') ? _DIR_RACINE . copie_locale($f) : get_spip_doc($f);
	} else {
		if (!@file_exists($f = $arg)) {
			if (!$f = copie_locale($f)) {
				return '';
			}
			$f = _DIR_RACINE . $f;
		}
	}

	$r = spip_file_get_contents($f);

	if ($charset) {
		include_spip('inc/charsets');
		if ($charset !== 'auto') {
			$r = importer_charset($r, $charset);
		} elseif ($GLOBALS['meta']['charset'] == 'utf-8' and !is_utf8($r)) {
			$r = importer_charset($r, CHARSET_JOINT);
		}
	}

	return $r;
}

// https://code.spip.net/@generer_url_document_dist
function generer_url_document_dist($id_document, $args = '', $ancre = '') {

	include_spip('inc/autoriser');
	if (!autoriser('voir', 'document', $id_document)) {
		return '';
	}

	$r = sql_fetsel('fichier,distant', 'spip_documents', 'id_document=' . intval($id_document));

	if (!$r) {
		return '';
	}

	$f = $r['fichier'];

	if ($r['distant'] == 'oui') {
		return $f;
	}

	// Si droit de voir tous les docs, pas seulement celui-ci
	// il est inutilement couteux de rajouter une protection
	$r = (autoriser('voir', 'document'));
	if (($r and $r !== 'htaccess')) {
		return get_spip_doc($f);
	}

	include_spip('inc/securiser_action');

	// cette action doit etre publique !
	return generer_url_action(
		'acceder_document',
		$args . ($args ? '&' : '')
			. 'arg=' . $id_document
			. ($ancre ? "&ancre=$ancre" : '')
			. '&cle=' . calculer_cle_action($id_document . ',' . $f)
			. '&file=' . rawurlencode($f),
		true,
		true
	);
}

//
// Affiche le document avec sa vignette par defaut
//
// Attention : en mode 'doc', si c'est un fichier graphique on prefere
// afficher une vue reduite, quand c'est possible (presque toujours, donc)
// En mode 'image', l'image conserve sa taille
//
// A noter : dans le portfolio prive on pousse le vice jusqu'a reduire la taille
// de la vignette -> c'est a ca que sert la variable $portfolio
function vignette_automatique($img, $doc, $lien, $x = 0, $y = 0, $align = '', $class = null, $connect = null) {
	include_spip('inc/distant');
	include_spip('inc/texte');
	include_spip('inc/filtres_images_mini');
	if (is_null($class)) {
		$class = 'spip_logo spip_logos';
	}
	$e = $doc['extension'];
	if (!$img) {
		if ($img = image_du_document($doc, $connect)) {
			if (!$x and !$y) {
				// eviter une double reduction
				$img = image_reduire($img);
			}
		} else {
			$f = charger_fonction('vignette', 'inc');
			$img = $f($e, false);
			$size = @getimagesize($img);
			$img = "<img src='$img' " . $size[3] . ' />';
		}
	} else {
		$size = @getimagesize($img);
		$img = "<img src='$img' " . $size[3] . ' />';
	}
	// on appelle image_reduire independamment de la presence ou non
	// des librairies graphiques
	// la fonction sait se debrouiller et faire de son mieux dans tous les cas
	if ($x or $y) {
		$img = image_reduire($img, $x, $y);
	}
	$img = inserer_attribut($img, 'alt', '');
	$img = inserer_attribut($img, 'class', $class);
	if ($align) {
		$img = inserer_attribut($img, 'align', $align);
	}

	if (!$lien) {
		return $img;
	}

	$titre = supprimer_tags(typo($doc['titre']));
	$titre = ' - ' . taille_en_octets($doc['taille'])
		. ($titre ? " - $titre" : '');

	$type = sql_fetsel('titre, mime_type', 'spip_types_documents', 'extension = ' . sql_quote($e));

	$mime = $type['mime_type'];
	$titre = attribut_html(couper($type['titre'] . $titre, 80));

	return "<a href='$lien' type='$mime' title='$titre'>$img</a>";
}

/**
 * Trouve une image caractéristique d'un document.
 *
 * Si celui-ci est une image et que les outils graphiques sont dispos,
 * retourner le document (en exploitant sa copie locale s'il est distant).
 *
 * Si on a un connecteur externe, on utilise l’URL externe.
 *
 * Autrement retourner la vignette fournie par SPIP pour ce type MIME
 *
 * @param array $document
 * @param null|string $connect
 * @return string Chemin de l’image
 */
function image_du_document($document, $connect = null) {
	if ($e = $document['extension']
		and isset($GLOBALS['meta']['formats_graphiques'])
		and (strpos($GLOBALS['meta']['formats_graphiques'], $e) !== false)
		and (!test_espace_prive() or $GLOBALS['meta']['creer_preview'] == 'oui')
		and $document['fichier']
	) {
		include_spip('inc/quete');
		if ($document['distant'] == 'oui') {
			$image = _DIR_RACINE . copie_locale($document['fichier']);
		} elseif ($image = document_spip_externe($document['fichier'], $connect)) {
			return $image;
		} else {
			$image = get_spip_doc($document['fichier']);
		}
		if (@file_exists($image)) {
			return $image;
		}
	}

	return '';
}

/**
 * Afficher un document dans la colonne de gauche
 *
 * @deprecated
 *     Utiliser l'inclusion prévue ou une véritable
 *     déclaration d'objet éditorial (la colonne document
 *     est alors affichée automatiquement sur la page d'édition de l'objet)
 *
 * @param int $id
 *     Identifiant de l'objet, ou id_auteur négatif pour un nouvel objet
 * @param string $type
 *     Type d'objet
 * @param null $script
 *     ??
 * @return string
 *     Code HTML permettant de gérer des documents
 */
function afficher_documents_colonne($id, $type = 'article', $script = null) {
	return recuperer_fond('prive/objets/editer/colonne_document', array('objet' => $type, 'id_objet' => $id));
}


/**
 * Affiche le code d'un raccourcis de document, tel que <doc123|left>
 *
 * Affiche un code de raccourcis de document, et l'insère
 * dans le textarea principal de l'objet (champ 'texte') sur un double-clic
 *
 * @param string $doc
 *    Type de raccourcis : doc,img,emb...
 * @param int $id
 *    Identifiant du document
 * @param string $align
 *    Alignement du document : left,center,right
 *
 * @return string
 *    Texte du raccourcis
 **/
function affiche_raccourci_doc($doc, $id, $align) {
	static $num = 0;
	$pipe = $onclick = '';

	if ($align) {
		$pipe = "|$align";
		$onclick = "\nondblclick=\"barre_inserer('\\x3C$doc$id$pipe&gt;', $('textarea[name=texte]')[0]);\"\ntitle=\"" .
			str_replace(
				'&amp;',
				'&',
				entites_html(_T('medias:double_clic_inserer_doc'))
			) . '"';
	} else {
		$align = 'center';
	}

	return "\n<div style='text-align: $align'$onclick>&lt;$doc$id$pipe&gt;</div>\n";
}
verifier_document_mode_image.php000064400000002315152525410300013132 0ustar00<?php
/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

function inc_verifier_document_mode_image_dist($infos) {

	// Si on veut uploader une image, il faut qu'elle ait ete bien lue
	if ($infos['inclus'] != 'image') {
		return _T('medias:erreur_format_fichier_image', array('nom' => $infos['fichier']));
	} #SVG

	if (isset($infos['largeur']) and isset($infos['hauteur'])) {
		if (!($infos['largeur'] or $infos['hauteur'])) {
			return _T('medias:erreur_upload_vignette', array('nom' => $infos['fichier']));
		}
	}

	return true;
}
joindre_document.php000064400000026016152525410300010607 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}


/**
 * Recuperer le nom du fichier selon le mode d'upload choisi
 * et mettre cela au format $_FILES
 *
 * Renvoie une liste de fichier ou un message en cas d'erreur
 *
 * @return string/array
 */
function joindre_trouver_fichier_envoye() {
	static $files = array();
	// on est appele deux fois dans un hit, resservir ce qu'on a trouve a la verif
	// lorsqu'on est appelle au traitement

	if (count($files)) {
		return $files;
	}

	if (_request('joindre_upload')) {
		$post = isset($_FILES) ? $_FILES : $GLOBALS['HTTP_POST_FILES'];
		$files = array();
		if (is_array($post)) {
			include_spip('action/ajouter_documents');
			foreach ($post as $file) {
				if (is_array($file['name'])) {
					while (count($file['name'])) {
						$test = array(
							'error' => array_shift($file['error']),
							'name' => array_shift($file['name']),
							'tmp_name' => array_shift($file['tmp_name']),
							'type' => array_shift($file['type']),
						);
						if (!($test['error'] == 4)) {
							if (is_string($err = joindre_upload_error($test['error']))) {
								return $err;
							} // un erreur upload
							if (!is_array(verifier_upload_autorise($test['name']))) {
								return _T('medias:erreur_upload_type_interdit', array('nom' => $test['name']));
							}
							$files[] = $test;
						}
					}
				} else {
					//UPLOAD_ERR_NO_FILE
					if (!($file['error'] == 4)) {
						if (is_string($err = joindre_upload_error($file['error']))) {
							return $err;
						} // un erreur upload
						if (!is_array(verifier_upload_autorise($file['name']))) {
							return _T('medias:erreur_upload_type_interdit', array('nom' => $file['name']));
						}
						$files[] = $file;
					}
				}
			}
			if (!count($files)) {
				return _T('medias:erreur_indiquez_un_fichier');
			}
		}

		return $files;
	} elseif (_request('joindre_distant')) {
		$path = _request('url');
		if (!strlen($path) or $path == 'http://') {
			return _T('medias:erreur_indiquez_un_fichier');
		}
		include_spip('inc/distant');
		if (!valider_url_distante($path)) {
			return _T('medias:erreur_upload_type_interdit', array('nom' => $path));
		}
		include_spip('action/ajouter_documents');
		$infos = renseigner_source_distante($path);
		if (!is_array($infos)) {
			return $infos;
		} // message d'erreur
		else {
			return array(
				array(
					'name' => basename($path),
					'tmp_name' => $path,
					'distant' => true,
				)
			);
		}
	} elseif (_request('joindre_ftp')) {
		$path = _request('cheminftp');
		if (!$path || strstr($path, '..')) {
			return _T('medias:erreur_indiquez_un_fichier');
		}

		include_spip('inc/documents');
		include_spip('inc/actions');
		$upload = determine_upload();
		if ($path != '/' and $path != './') {
			$upload .= $path;
		}

		if (!is_dir($upload)) {
			// seul un fichier est demande
			return array(
				array(
					'name' => basename($upload),
					'tmp_name' => $upload
				)
			);
		} else {
			// on upload tout un repertoire
			$files = array();
			foreach (preg_files($upload) as $fichier) {
				$files[] = array(
					'name' => basename($fichier),
					'tmp_name' => $fichier
				);
			}

			return $files;
		}
	} elseif (_request('joindre_zip') and $token_zip = _request('chemin_zip')) {
		$zip_to_clean = (isset($GLOBALS['visiteur_session']['zip_to_clean']) ?
			unserialize($GLOBALS['visiteur_session']['zip_to_clean']) : array());
		if (!$zip_to_clean
			or !isset($zip_to_clean[$token_zip])
			or !$path = $zip_to_clean[$token_zip]) {
			return _T('avis_operation_impossible');
		}

		include_spip('inc/documents'); //pour creer_repertoire_documents
		define('_TMP_ZIP', $path);
		define('_TMP_DIR', creer_repertoire_documents(md5($path . $GLOBALS['visiteur_session']['id_auteur'])));
		if (_TMP_DIR == _DIR_IMG) {
			return _T('avis_operation_impossible');
		}

		$files = array();
		if (_request('options_upload_zip') == 'deballe') {
			$files = joindre_deballer_lister_zip($path, _TMP_DIR);
		}

		// si le zip doit aussi etre conserve, l'ajouter
		if (_request('options_upload_zip') == 'upload' or _request('options_deballe_zip_conserver')) {
			$files[] = array(
				'name' => basename($path),
				'tmp_name' => $path,
			);
		}
		return $files;
	}

	return array();
}


// Erreurs d'upload
// renvoie false si pas d'erreur
// et true si erreur = pas de fichier
// pour les autres erreurs renvoie le message d'erreur
function joindre_upload_error($error) {

	if (!$error) {
		return false;
	}
	spip_log("Erreur upload $error -- cf. http://php.net/manual/fr/features.file-upload.errors.php");
	switch ($error) {
		case 4: /* UPLOAD_ERR_NO_FILE */
			return true;

		# on peut affiner les differents messages d'erreur
		case 1: /* UPLOAD_ERR_INI_SIZE */
			$msg = _T(
				'medias:upload_limit',
				array('max' => ini_get('upload_max_filesize'))
			);
			break;
		case 2: /* UPLOAD_ERR_FORM_SIZE */
			$msg = _T(
				'medias:upload_limit',
				array('max' => ini_get('upload_max_filesize'))
			);
			break;
		case 3: /* UPLOAD_ERR_PARTIAL  */
			$msg = _T(
				'medias:upload_limit',
				array('max' => ini_get('upload_max_filesize'))
			);
			break;
		case 6: /* UPLOAD_ERR_NO_TMP_DIR  */
			$msg = _T('medias:erreur_dossier_tmp_manquant');
			break;
		case 7: /* UPLOAD_ERR_CANT_WRITE */
			$msg = _T('medias:erreur_ecriture_fichier');
			break;
		default: /* autre */
			if (!$msg) {
				$msg = _T('pass_erreur') . ' ' . $error
					. '<br />' . propre('[->http://php.net/manual/fr/features.file-upload.errors.php]');
			}
			break;
	}
	spip_log("erreur upload $error");
	return $msg;
}

/**
 * Verifier si le fichier poste est un zip
 * Si on sait le deballer, proposer les options necessaires
 *
 * @param array $files
 * @return string
 */
function joindre_verifier_zip($files) {
	if (function_exists('gzopen')
		and (count($files) == 1)
		and !isset($files[0]['distant'])
		and
		(preg_match('/\.zip$/i', $files[0]['name'])
			or (isset($files[0]['type']) and $files[0]['type'] == 'application/zip'))
	) {
		// on pose le fichier dans le repertoire zip
		// (nota : copier_document n'ecrase pas un fichier avec lui-meme
		// ca autorise a boucler)
		include_spip('inc/getdocument');
		$desc = $files[0];
		$zip = copier_document(
			'zip',
			$desc['name'],
			$desc['tmp_name']
		);

		// Est-ce qu'on sait le lire ?
		include_spip('inc/pclzip');
		if ($zip
			and $archive = new PclZip($zip)
			and $contenu = joindre_decrire_contenu_zip($archive)
			and $tmp = sous_repertoire(_DIR_TMP, 'zip')
			and rename($zip, $tmp = $tmp . basename($zip))
		) {
			$zip_to_clean = (isset($GLOBALS['visiteur_session']['zip_to_clean']) ?
				unserialize($GLOBALS['visiteur_session']['zip_to_clean']) : array());
			$zip_to_clean[md5($tmp)] = $tmp;
			session_set('zip_to_clean', serialize($zip_to_clean));
			$contenu[] = $tmp;

			return $contenu;
		}
	}

	// ce n'est pas un zip sur lequel il faut demander plus de precisions
	return false;
}

/**
 * Verifier et decrire les fichiers de l'archive, en deux listes :
 * - une liste des noms de fichiers ajoutables
 * - une liste des erreurs (fichiers refuses)
 *
 * @param object $zip
 * @return array
 */
function joindre_decrire_contenu_zip($zip) {
	include_spip('action/ajouter_documents');
	// si pas possible de decompacter: installer comme fichier zip joint
	if (!$list = $zip->listContent()) {
		return false;
	}

	// Verifier si le contenu peut etre uploade (verif extension)
	$fichiers = array();
	$erreurs = array();
	foreach ($list as $file) {
		if (accepte_fichier_upload($f = $file['stored_filename'])) {
			$fichiers[$f] = $file;
		} else // pas de message pour les dossiers et fichiers caches
		{
			if (substr($f, -1) !== '/' and substr(basename($f), 0, 1) !== '.') {
				$erreurs[] = _T('medias:erreur_upload_type_interdit', array('nom' => $f));
			}
		}
	}

	// si aucun fichier uploadable : installer comme fichier zip joint
	if (!count($fichiers)) {
		return false;
	}

	ksort($fichiers);

	return array($fichiers, $erreurs);
}


// https://code.spip.net/@joindre_deballes
function joindre_deballer_lister_zip($path, $tmp_dir) {
	include_spip('inc/pclzip');
	$archive = new PclZip($path);
	$archive->extract(
		PCLZIP_OPT_PATH,
		$tmp_dir,
		PCLZIP_CB_PRE_EXTRACT,
		'callback_deballe_fichier'
	);
	if ($contenu = joindre_decrire_contenu_zip($archive)) {
		$files = array();
		$fichiers = reset($contenu);
		foreach ($fichiers as $fichier) {
			$f = basename($fichier['filename']);
			$files[] = array(
				'tmp_name' => $tmp_dir . $f,
				'name' => $f,
				'titrer' => _request('options_deballe_zip_titrer'),
				'mode' => _request('options_deballe_zip_mode_document') ? 'document' : null
			);
		}

		return $files;
	}

	return _T('avis_operation_impossible');
}

if (!function_exists('fixer_extension_document')) {
	/**
	 * Cherche dans la base le type-mime du tableau representant le document
	 * et corrige le nom du fichier ; retourne array(extension, nom corrige)
	 * s'il ne trouve pas, retourne '' et le nom inchange
	 *
	 * @param unknown_type $doc
	 * @return unknown
	 */
// https://code.spip.net/@fixer_extension_document
	function fixer_extension_document($doc) {
		$extension = '';
		$name = $doc['name'];
		if (preg_match(',\.([^.]+)$,', $name, $r)
			and $t = sql_fetsel(
				'extension',
				'spip_types_documents',
				'extension=' . sql_quote(corriger_extension($r[1]))
			)
		) {
			$extension = $t['extension'];
			$name = preg_replace(',\.[^.]*$,', '', $doc['name']) . '.' . $extension;
		} else {
			if ($t = sql_fetsel('extension', 'spip_types_documents', 'mime_type=' . sql_quote($doc['type']))) {
				$extension = $t['extension'];
				$name = preg_replace(',\.[^.]*$,', '', $doc['name']) . '.' . $extension;
			}
		}

		return array($extension, $name);
	}
}

//
// Gestion des fichiers ZIP
//
// https://code.spip.net/@accepte_fichier_upload

function accepte_fichier_upload($f) {
	if (!preg_match(',.*__MACOSX/,', $f)
		and !preg_match(',^\.,', basename($f))
	) {
		include_spip('action/ajouter_documents');
		$ext = corriger_extension((strtolower(substr(strrchr($f, '.'), 1))));

		return sql_countsel(
			'spip_types_documents',
			'extension=' . sql_quote($ext) . " AND upload='oui'"
		);
	}
}

# callback pour le deballage d'un zip telecharge
# http://www.phpconcept.net/pclzip/man/en/?options-pclzip_cb_pre_extractfunction
// https://code.spip.net/@callback_deballe_fichier

function callback_deballe_fichier($p_event, &$p_header) {
	if (accepte_fichier_upload($p_header['filename'])) {
		$p_header['filename'] = _TMP_DIR . basename($p_header['filename']);

		return 1;
	} else {
		return 0;
	}
}
vignette.php000064400000004373152525410300007106 0ustar00<?php
/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Gestion des vignettes de types de fichier
 *
 * @package SPIP\Medias\Vignette
 **/

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

/**
 * Vignette pour une extension de document
 *
 * Recherche les fichiers d'icones au format png pour l'extension demandée.
 * On cherche prive/vignettes/ext.png dans le path.
 *
 * @param string $ext
 *     Extension du fichier. Exemple : png
 * @param bool $size
 *     true pour retourner un tableau avec les tailles de la vignette
 *     false pour retourner uniquement le chemin du fichier
 * @param bool $loop
 *     Autoriser la fonction à s'appeler sur elle-même
 *     (paramètre interne).
 * @return array|bool|string
 *     False si l'image n'est pas trouvée
 *     Chaîne (chemin vers l'image) si on ne demande pas de taille
 *     Tableau (chemin, largeur, hauteur) si on demande avec la taille.
 */
function inc_vignette_dist($ext, $size = true, $loop = true) {

	if (!$ext) {
		$ext = 'txt';
	}

	// Chercher la vignette correspondant a ce type de document
	// dans les vignettes persos, ou dans les vignettes standard
	if (
		# installation dans un dossier /vignettes personnel, par exemple /squelettes/vignettes
	!$v = find_in_path('prive/vignettes/' . $ext . '.png')
	) {
		if ($loop) {
			$f = charger_fonction('vignette', 'inc');
			$v = $f('defaut', false, $loop = false);
		} else {
			$v = false;
		}
	} # pas trouve l'icone de base

	if (!$size) {
		return $v;
	}

	$largeur = $hauteur = 0;
	if ($v and $size = @getimagesize($v)) {
		$largeur = $size[0];
		$hauteur = $size[1];
	}

	return array($v, $largeur, $hauteur);
}
marquer_doublons_doc.php000064400000013117152525410300011463 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Analyse des textes pour trouver et marquer comme vu les documents utilisés dedans
 *
 * @package SPIP\Medias\Fonctions
 **/

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

// la dist ne regarde que chapo et texte, on laisse comme ca,
// mais ca permet d etendre a descriptif ou toto depuis d autres plugins
$GLOBALS['medias_liste_champs'][] = 'texte';
$GLOBALS['medias_liste_champs'][] = 'chapo';

/**
 * Trouver les documents utilisés dans le texte d'un objet et enregistrer cette liaison comme vue.
 *
 * La liste des champs susceptibles de contenir des documents ou images est indiquée
 * par la globale `medias_liste_champs` (un tableau).
 *
 * Le contenu de ces champs (du moins ceux qui existent pour l'objet demandé) est récupéré et analysé.
 * La présence d'un modèle de document dans ces contenus, tel que imgXX, docXX ou embXX
 * indique que le document est utilisé et doit être lié à l'objet, avec le champ `vu=oui`
 *
 * S'il y avait des anciens liens avec vu=oui qui n'ont plus lieu d'être, ils passent à non.
 *
 * @note
 *     La fonction pourrait avoir bien moins d'arguments : seuls $champs, $id, $type ou $objet, $desc, $serveur
 *     sont nécessaires. On calcule $desc s'il est absent, et il contient toutes les infos…
 *
 * @param array $champs
 *     Couples [champ => valeur] connus de l'objet
 * @param int $id
 *     Identifiant de l'objet
 * @param string $type
 *     Type d'objet éditorial (ex: article)
 * @param string $id_table_objet
 *     Nom de la clé primaire sur la table sql de l'objet
 * @param string $table_objet
 *     Nom de l'objet éditorial (ex: articles)
 * @param string $spip_table_objet
 *     Nom de la table sql de l'objet
 * @param array $desc
 *     Description de l'objet, si déjà calculé
 * @param string $serveur
 *     Serveur sql utilisé.
 * @return void|null
 **/
function inc_marquer_doublons_doc_dist(
	$champs,
	$id,
	$type,
	$id_table_objet,
	$table_objet,
	$spip_table_objet,
	$desc = array(),
	$serveur = ''
) {

	// On conserve uniquement les champs qui modifient le calcul des doublons de documents
	// S'il n'y en a aucun, les doublons ne sont pas impactés, donc rien à faire d'autre..
	if (!$champs = array_intersect_key($champs, array_flip($GLOBALS['medias_liste_champs']))) {
		return;
	}

	if (!$desc) {
		$trouver_table = charger_fonction('trouver_table', 'base');
		$desc = $trouver_table($table_objet, $serveur);
	}

	// Il faut récupérer toutes les données qui impactent les liens de documents vus
	// afin de savoir lesquels sont présents dans les textes, et pouvoir actualiser avec
	// les liens actuellement enregistrés.
	$absents = array();

	// Récupérer chaque champ impactant qui existe dans la table de l'objet et qui nous manque
	foreach ($GLOBALS['medias_liste_champs'] as $champ) {
		if (isset($desc['field'][$champ]) and !isset($champs[$champ])) {
			$absents[] = $champ;
		}
	}

	// Retrouver les textes des champs manquants
	if ($absents) {
		$row = sql_fetsel($absents, $spip_table_objet, "$id_table_objet=" . sql_quote($id));
		if ($row) {
			$champs = array_merge($row, $champs);
		}
	}

	include_spip('inc/texte');
	include_spip('base/abstract_sql');
	include_spip('action/editer_liens');
	include_spip('base/objets');

	// récupérer la liste des modèles qui considèrent un document comme vu s'ils sont utilisés dans un texte
	$modeles = lister_tables_objets_sql('spip_documents');
	$modeles = $modeles['modeles'];

	// liste d'id_documents trouvés dans les textes
	$GLOBALS['doublons_documents_inclus'] = array();

	// detecter les doublons dans ces textes
	traiter_modeles(implode(' ', $champs), array('documents' => $modeles), '', '', null, array(
		'objet' => $type,
		'id_objet' => $id,
		$id_table_objet => $id
	));

	$texte_documents_vus = $GLOBALS['doublons_documents_inclus'];

	// on ne modifie les liaisons que si c'est nécessaire
	$bdd_documents_vus = array(
		'oui' => array(),
		'non' => array()
	);

	$liaisons = objet_trouver_liens(array('document' => '*'), array($type => $id));
	foreach ($liaisons as $l) {
		$bdd_documents_vus[$l['vu']][] = $l['id_document'];
	}

	// il y a des nouveaux documents vus dans le texte
	$nouveaux = array_diff($texte_documents_vus, $bdd_documents_vus['oui']);

	// il y a des anciens documents vus dans la bdd
	$anciens = array_diff($bdd_documents_vus['oui'], $texte_documents_vus);

	if ($nouveaux) {
		// on vérifie que les documents indiqués vus existent réellement tout de même (en cas d'erreur de saisie)
		$ids = sql_allfetsel('id_document', 'spip_documents', sql_in('id_document', $nouveaux));
		$ids = array_map('reset', $ids);
		if ($ids) {
			// Creer le lien s'il n'existe pas déjà
			objet_associer(array('document' => $ids), array($type => $id), array('vu' => 'oui'));
			objet_qualifier_liens(array('document' => $ids), array($type => $id), array('vu' => 'oui'));
		}
	}

	if ($anciens) {
		objet_qualifier_liens(array('document' => $anciens), array($type => $id), array('vu' => 'non'));
	}
}
revisions_autoriser.php000064400000005145152526156350011412 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/


/**
 * Autorisations du plugin révisions
 *
 * @package SPIP\Revisions\Autorisations
 **/

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

/**
 * Fonction du pipeline autoriser. N'a rien à faire.
 *
 * @pipeline autoriser
 */
function revisions_autoriser() { }

/**
 * Autorisation de voir les revisions ?
 *
 * Il faut :
 * - des revisions définies pour cet objet
 * - que l'objet existe
 * - que l'on soit autorisé à voir l'objet
 *
 * @param  string $faire Action demandée
 * @param  string $type Type d'objet sur lequel appliquer l'action
 * @param  int $id Identifiant de l'objet
 * @param  array $qui Description de l'auteur demandant l'autorisation
 * @param  array $opt Options de cette autorisation
 * @return bool          true s'il a le droit, false sinon
 */
function autoriser_voirrevisions_dist($faire, $type, $id, $qui, $opt) {
	$table = table_objet_sql($type);
	$id_table_objet = id_table_objet($type);

	include_spip('inc/revisions');
	if (!liste_champs_versionnes($table)) {
		return false;
	}

	if (!$row = sql_fetsel("*", $table, "$id_table_objet=" . intval($id))) {
		return false;
	}

	return
		autoriser('voir', $type, $id, $qui, $opt);
}


/**
 * Autorisation de voir le menu révisions
 *
 * Il faut des révisions activées et présentes.
 *
 * @param  string $faire Action demandée
 * @param  string $type Type d'objet sur lequel appliquer l'action
 * @param  int $id Identifiant de l'objet
 * @param  array $qui Description de l'auteur demandant l'autorisation
 * @param  array $opt Options de cette autorisation
 * @return bool          true s'il a le droit, false sinon
 */
function autoriser_revisions_menu_dist($faire, $type = '', $id = 0, $qui = null, $opt = null) {
	// SI pas de revisions sur un objet quelconque.
	// ET pas de version... pas de bouton, c'est inutile...
	include_spip('inc/config');
	if (!lire_config('objets_versions/') and !sql_countsel('spip_versions')) {
		return false;
	}

	return true;
}
suivi_versions.php000064400000014575152526156350010372 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Fonctions de suivi de versions
 *
 * @package SPIP\Revisions\Versions
 **/
if (!defined("_ECRIRE_INC_VERSION")) {
	return;
}

include_spip('inc/revisions');
include_spip('inc/diff');

/**
 * Afficher un diff correspondant à une révision d'un objet
 *
 * @param int $id_objet Identifiant de l'objet
 * @param string $objet Objet
 * @param int $id_version Identifiant de la version
 * @param bool $court
 *     - false : affiche le diff complet
 *     - true  : indique juste la taille en octets des changements
 * @return string
 *     Texte HTML du diff.
 */
function revisions_diff($id_objet, $objet, $id_version, $court = false) {
	$textes = revision_comparee($id_objet, $objet, $id_version, 'diff');
	if (!is_array($textes)) {
		return $textes;
	}
	$rev = '';
	$nb = 0;
	foreach ($textes as $var => $t) {
		if ($n = strlen($t)) {
			if ($court) {
				$nb += $n;
			} else {
				$aff = propre_diff($t);
				if ($GLOBALS['les_notes']) {
					$aff .= '<p>' . $GLOBALS['les_notes'] . '</p>';
					$GLOBALS['les_notes'] = '';
				}
				$rev .= "<blockquote>$aff</blockquote>";
			}
		}
	}

	return $court ? _T('taille_octets', array('taille' => $nb)) : $rev;
}

/**
 * Retrouver le champ d'un objet, pour une version demandée
 *
 * Si le champ n'est pas déjà présent dans la liste des champs ($champs),
 * on remonte les versions à partir du id_version donné, jusqu'à
 * récupérer une version qui contient ce champ. On complète alors la liste
 * des champs avec la version du champ trouvée.
 *
 * @param string $objet Objet
 * @param int $id_objet Identifiant de l'objet
 * @param int $id_version Identifiant de la version
 * @param string $champ Le nom du champ à retrouver
 * @param array $champs Liste des champs déjà connus
 * @return void
 */
function retrouver_champ_version_objet($objet, $id_objet, $id_version, $champ, &$champs) {
	if (isset($champs[$champ])) {
		return;
	}

	// Remonter dans le temps pour trouver le champ en question
	// pour la version demandee
	$id_ref = $id_version - 1;
	$prev = array();
	while (!isset($prev[$champ]) and $id_ref > 0) {
		$prev = recuperer_version($id_objet, $objet, $id_ref--);
	}
	if (isset($prev[$champ])) {
		$champs[$champ] = $prev[$champ];
	} else {
		// le champ n'a jamais ete versionne :
		// il etait initialement vide
		$champs[$champ] = '';
	}
}

/**
 * Liste les champs modifiés par une version de révision donnée
 *
 * Pour un couple objet/id_objet et id_version donné, calcule les champs
 * qui ont été modifiés depuis une version précédente et la version
 * d'id_version, et les retourne.
 *
 * La version précédente est par défaut la version juste
 * avant id_version, mais peut être définie via le paramètre id_diff.
 *
 * Le retour est plus ou moins locace en fonction du paramètre format.
 *
 * @param int $id_objet Identifiant de l'objet
 * @param string $objet Objet
 * @param int $id_version Identifiant de la version
 * @param string $format
 *     Type de retour
 *     - diff => seulement les modifs (page revisions)
 *     - apercu => idem, mais en plus tres cout s'il y en a bcp
 *     - complet => tout, avec surlignage des modifications (page revision)
 * @param null $id_diff
 *     Identifiant de la version de base du diff, par défaut l'id_version
 *     juste précédent
 * @return array
 *     Couples (champ => texte)
 */
function revision_comparee($id_objet, $objet, $id_version, $format = 'diff', $id_diff = null) {
	include_spip('inc/diff');

	// chercher le numero de la version precedente
	if (!$id_diff) {
		$id_diff = sql_getfetsel("id_version", "spip_versions",
			"id_objet=" . intval($id_objet) . " AND id_version < " . intval($id_version) . " AND objet=" . sql_quote($objet),
			"", "id_version DESC", "1");
	}

	if ($id_version && $id_diff) {

		// si l'ordre est inverse, on remet a l'endroit
		if ($id_diff > $id_version) {
			$t = $id_version;
			$id_version = $id_diff;
			$id_diff = $t;
		}

		$old = recuperer_version($id_objet, $objet, $id_diff);
		$new = recuperer_version($id_objet, $objet, $id_version);

		$textes = array();

		// Mode "diff": on ne s'interesse qu'aux champs presents dans $new
		// Mode "complet": on veut afficher tous les champs
		switch ($format) {
			case 'complet':
				$champs = liste_champs_versionnes(table_objet_sql($objet));
				break;
			case 'diff':
			case 'apercu':
			default:
				$champs = array_keys($new);
				break;
		}

		// memoriser les cas les plus courant
		$afficher_diff_champ = charger_fonction('champ', 'afficher_diff');
		$afficher_diff_jointure = charger_fonction('jointure', 'afficher_diff');
		foreach ($champs as $champ) {
			// Remonter dans le temps pour trouver le champ en question
			// pour chaque version
			retrouver_champ_version_objet($objet, $id_objet, $id_version, $champ, $new);
			retrouver_champ_version_objet($objet, $id_objet, $id_diff, $champ, $old);

			if (!strlen($new[$champ]) && !strlen($old[$champ])) {
				continue;
			}

			// si on n'a que le vieux, ou que le nouveau, on ne
			// l'affiche qu'en mode "complet"
			if ($format == 'complet') {
				$textes[$champ] = strlen($new[$champ])
					? $new[$champ] : $old[$champ];
			}

			// si on a les deux, le diff nous interesse, plus ou moins court
			if (isset($new[$champ]) and isset($old[$champ])) {
				if (!$afficher_diff = charger_fonction($objet . "_" . $champ, 'afficher_diff', true)
					and !$afficher_diff = charger_fonction($champ, 'afficher_diff', true)
				) {
					$afficher_diff = (strncmp($champ, 'jointure_', 9) == 0 ? $afficher_diff_jointure : $afficher_diff_champ);
				}

				$textes[$champ] = $afficher_diff($champ, $old[$champ], $new[$champ], $format);
			}
		}
	}

	// que donner par defaut ? (par exemple si id_version=1)
	if (!$textes) {
		$textes = recuperer_version($id_objet, $objet, $id_version);
	}

	return $textes;
}
revisions_pipeline.php000064400000031215152526156350011177 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Pipelines utilisés du plugin révisions
 *
 * @package SPIP\Revisions\Pipelines
 **/
if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}


/**
 * Ajoute dans le bloc d'info d'un objet un bouton permettant d'aller voir
 * l'historique de ses révisions
 *
 * @param array $flux Données du pipeline
 * @return array $flux  Données du pipeline
 */
function revisions_boite_infos($flux) {
	$type = $flux['args']['type'];
	if ($id = intval($flux['args']['id'])
		and $tables = unserialize($GLOBALS['meta']['objets_versions'])
		and in_array(table_objet_sql($type), $tables)
		and autoriser('voirrevisions', $type, $id)
		// regarder le numero de revision le plus eleve, et afficher le bouton
		// si c'est interessant (id_version>1)
		and sql_countsel('spip_versions', 'id_objet=' . intval($id) . ' AND objet = ' . sql_quote($type)) > 1
	) {
		include_spip('inc/presentation');
		$flux['data'] .= icone_horizontale(_T('revisions:info_historique_lien'),
			generer_url_ecrire('revision', "id_objet=$id&objet=$type"), "revision-24.png");
	}

	return $flux;
}

/**
 * Afficher les dernières révisions sur l'accueil et le suivi
 *
 * Liste les révisions en bas de la page d'accueil de ecrire/
 * et sur la page de suivi de l'activité du site
 *
 * @param array $flux Données du pipeline
 * @return array $flux  Données du pipeline
 */
function revisions_affiche_milieu($flux) {
	// la bonne page et des objets révisables cochées !
	if (in_array($flux['args']['exec'], array('accueil', 'suivi_edito'))
		and unserialize($GLOBALS['meta']['objets_versions'])
	) {
		$contexte = array();
		if ($GLOBALS['visiteur_session']['statut'] !== '0minirezo') {
			$contexte['id_auteur'] = $GLOBALS['visiteur_session']['id_auteur'];
		}
		$flux['data'] .= recuperer_fond('prive/objets/liste/versions', $contexte, array('ajax' => true));
	}

	return $flux;
}

/**
 * Définir les metas de configuration liées aux révisions
 *
 * Utilisé par inc/config
 *
 * @param array $metas Liste des métas et leurs valeurs par défaut
 * @return array        Liste des métas et leurs valeurs par défaut
 */
function revisions_configurer_liste_metas($metas) {
	// Dorénavant dans les metas on utilisera un array serialisé de types d'objets
	// qui correspondront aux objets versionnés
	$metas['objets_versions'] = '';

	return $metas;
}


/**
 * Charge les données d'une révision donnée dans le formulaire d'édition d'un objet
 *
 * @param array $flux Données du pipeline
 * @return array $flux  Données du pipeline
 */
function revisions_formulaire_charger($flux) {
	if (strncmp($flux['args']['form'], 'editer_', 7) == 0
		and $id_version = _request('id_version')
		and $objet = substr($flux['args']['form'], 7)
		and $id_table_objet = id_table_objet($objet)
		and isset($flux['data'][$id_table_objet])
		and $id = intval($flux['data'][$id_table_objet])
		and !$flux['args']['je_suis_poste']
	) {
		// ajouter un message convival pour indiquer qu'on a restaure la version
		$flux['data']['message_ok'] = _T('revisions:icone_restaurer_version', array('version' => $id_version));
		$flux['data']['message_ok'] .= "<br />" . _T('revisions:message_valider_recuperer_version');
		// recuperer la version
		include_spip('inc/revisions');
		$champs = recuperer_version_complete($id, $objet, $id_version);
		foreach ($champs as $champ => $valeur) {
			if (!strncmp($champ, 'jointure_', 9) == 0) {
				if ($champ == 'id_rubrique') {
					$flux['data']['id_parent'] = $valeur;
				} else {
					$flux['data'][$champ] = $valeur;
				}
			}
		}
	}

	return $flux;
}

/**
 * Sur une insertion en base, lever un flag pour ne pas creer une premiere révision vide
 *
 * @param array $x Données du pipeline
 * @return array $x  Données du pipeline
 */
function revisions_pre_insertion($x) {
	$table = $x['args']['table'];
	include_spip('inc/revisions');
	if ($champs = liste_champs_versionnes($table)) {
		// on ne connait pas encore l'ID, mais on leve un flag pour ne pas creer de premiere revision pour ce type d'objet
		// sur une simple jointure (auteur)
		$GLOBALS['premiere_revision']["$table:0"] = true;
	}

	return $x;
}


/**
 * Sur une insertion en base, lever un flag pour ne pas creer une premiere révision vide
 * dans pre_edition mais attendre la post_edition pour cela
 *
 * @param array $x Données du pipeline
 * @return array $x  Données du pipeline
 */
function revisions_post_insertion($x) {
	$table = $x['args']['table'];
	include_spip('inc/revisions');
	if ($champs = liste_champs_versionnes($table)) {
		if (isset($GLOBALS['premiere_revision']["$table:0"])) {
			unset($GLOBALS['premiere_revision']["$table:0"]);
		}
		$GLOBALS['premiere_revision']["$table:" . $x['args']['id_objet']] = true;
	}

	return $x;
}

/**
 * Avant toute modification en base
 * vérifier qu'une version initiale existe bien pour cet objet
 * et la creer sinon avec l'etat actuel de l'objet
 *
 * @param array $x Données du pipeline
 * @return array $x  Données du pipeline
 */
function revisions_pre_edition($x) {
	// ne rien faire quand on passe ici en controle md5
	if (!isset($x['args']['action'])
		or $x['args']['action'] !== 'controler'
	) {
		$table = $x['args']['table'];
		include_spip('inc/revisions');
		// si flag leve passer son chemin, post_edition le fera (mais baisser le flag en le gardant en memoire tout de meme)
		if (isset($GLOBALS['premiere_revision']["$table:" . $x['args']['id_objet']])) {
			$GLOBALS['premiere_revision']["$table:" . $x['args']['id_objet']] = 0;
		} // sinon creer une premiere revision qui date et dont on ne connait pas l'auteur
		elseif ($versionnes = liste_champs_versionnes($table)) {
			$objet = isset($x['args']['type']) ? $x['args']['type'] : objet_type($table);
			verifier_premiere_revision($table, $objet, $x['args']['id_objet'], $versionnes, -1);
		}
	}

	return $x;
}

/**
 * Avant modification en base d'un lien,
 * enregistrer une première révision de l'objet si nécessaire
 *
 * @param array $x Données du pipeline
 * @return array $x  Données du pipeline
 */
function revisions_pre_edition_lien($x) {
	if (intval($x['args']['id_objet_source']) > 0
		and intval($x['args']['id_objet']) > 0
	) {
		$table = table_objet_sql($x['args']['objet']);
		$id_objet = intval($x['args']['id_objet']);
		include_spip('inc/revisions');
		// si c'est une creation de lien qui arrive aussitot apres l'insertion on ne cree pas de revision
		if (isset($GLOBALS['premiere_revision']["$table:0"])) {
			unset($GLOBALS['premiere_revision']["$table:0"]);
			$GLOBALS['premiere_revision']["$table:" . $id_objet] = true;
		}
		if (isset($GLOBALS['premiere_revision']["$table:" . $id_objet])) {
			$GLOBALS['premiere_revision']["$table:" . $id_objet] = 0;
		} // ex : si le champ jointure_mots est versionnable sur les articles
		elseif ($versionnes = liste_champs_versionnes($table)
			and in_array($j = 'jointure_' . table_objet($x['args']['objet_source']), $versionnes)
		) {
			verifier_premiere_revision($table, $x['args']['objet'], $id_objet, $versionnes, -1);
		}

		$table = table_objet_sql($x['args']['objet_source']);
		$id_objet = $x['args']['id_objet_source'];
		// si c'est une creation de lien qui arrive aussitot apres l'insertion on ne cree pas de revision
		if (isset($GLOBALS['premiere_revision']["$table:0"])) {
			unset($GLOBALS['premiere_revision']["$table:0"]);
			$GLOBALS['premiere_revision']["$table:" . $id_objet] = true;
		}
		if (isset($GLOBALS['premiere_revision']["$table:" . $id_objet])) {
			$GLOBALS['premiere_revision']["$table:" . $id_objet] = 0;
		} // ex : si le champ jointure_articles est versionnable sur les mots
		elseif ($versionnes = liste_champs_versionnes($table)
			and in_array($j = 'jointure_' . table_objet($x['args']['objet']), $versionnes)
		) {
			verifier_premiere_revision($table, $x['args']['objet_source'], $id_objet, $versionnes, -1);
		}
	}

	return $x;
}

/**
 * Après modification en base, versionner l'objet
 *
 * @param array $x Données du pipeline
 * @return array $x  Données du pipeline
 */
function revisions_post_edition($x) {
	include_spip('inc/revisions');
	if (isset($x['args']['table']) and $versionnes = liste_champs_versionnes($x['args']['table'])) {
		// Regarder si au moins une des modifs est versionnable
		$champs = array();
		$table = $x['args']['table'];
		$objet = isset($x['args']['type']) ? $x['args']['type'] : objet_type($table);
		include_spip('inc/session');

		if (isset($GLOBALS['premiere_revision']["$table:" . $x['args']['id_objet']])) {
			unset($GLOBALS['premiere_revision']["$table:" . $x['args']['id_objet']]);
			// verifier la premiere version : sur une version initiale on attend ici pour la creer
			// plutot que de creer une version vide+un diff
			verifier_premiere_revision($table, $objet, $x['args']['id_objet'], $versionnes, session_get('id_auteur'));
		} else {
			// on versionne les differences
			foreach ($versionnes as $key) {
				if (isset($x['data'][$key])) {
					$champs[$key] = $x['data'][$key];
				}
			}

			if (count($champs)) {
				ajouter_version($x['args']['id_objet'], $objet, $champs, '', session_get('id_auteur'));
			}
		}
	}

	return $x;
}


/**
 * Après modification en base d'un lien, versionner l'objet si nécessaire
 *
 * @param array $x Données du pipeline
 * @return array $x  Données du pipeline
 */
function revisions_post_edition_lien($x) {
	/*pipeline('post_edition_lien',
		array(
			'args' => array(
				'table_lien' => $table_lien,
				'objet_source' => $objet_source,
				'id_objet_source' => $l[$primary],
				'objet' => $l['objet'],
				'id_objet' => $id_o,
				'action'=>'delete',
			),
			'data' => $couples
		)
	*/
	if (intval($x['args']['id_objet_source']) > 0
		and intval($x['args']['id_objet']) > 0
	) {

		$table = table_objet_sql($x['args']['objet']);
		$id_objet = $x['args']['id_objet'];
		include_spip('inc/revisions');
		include_spip('inc/session');
		// si c'est une creation de lien qui arrive aussitot apres l'insertion on ne cree pas de revision
		// (au cas ou pre_edition_lien n'aurait pas ete appele ?)
		if (isset($GLOBALS['premiere_revision']["$table:0"])) {
			unset($GLOBALS['premiere_revision']["$table:0"]);
			$GLOBALS['premiere_revision']["$table:" . $id_objet] = true;
		}
		if (isset($GLOBALS['premiere_revision']["$table:" . $id_objet])) {
			$GLOBALS['premiere_revision']["$table:" . $id_objet] = 0;
		} // ex : si le champ jointure_mots est versionnable sur les articles
		elseif ($versionnes = liste_champs_versionnes($table)
			and in_array($j = 'jointure_' . table_objet($x['args']['objet_source']), $versionnes)
		) {
			$champs = array(
				$j => recuperer_valeur_champ_jointure($x['args']['objet'], $id_objet, $x['args']['objet_source'])
			);
			ajouter_version($id_objet, $x['args']['objet'], $champs, '', session_get('id_auteur'));
		}

		$table = table_objet_sql($x['args']['objet_source']);
		$id_objet = $x['args']['id_objet_source'];
		// si c'est une creation de lien qui arrive aussitot apres l'insertion on ne cree pas de revision
		// (au cas ou pre_edition_lien n'aurait pas ete appele ?)
		if (isset($GLOBALS['premiere_revision']["$table:0"])) {
			unset($GLOBALS['premiere_revision']["$table:0"]);
			$GLOBALS['premiere_revision']["$table:" . $id_objet] = true;
		}
		if (isset($GLOBALS['premiere_revision']["$table:" . $id_objet])) {
			$GLOBALS['premiere_revision']["$table:" . $id_objet] = 0;
		} // ex : si le champ jointure_articles est versionnable sur les mots
		elseif ($versionnes = liste_champs_versionnes($table)
			and in_array($j = 'jointure_' . table_objet($x['args']['objet']), $versionnes)
		) {
			$champs = array(
				$j => recuperer_valeur_champ_jointure($x['args']['objet_source'], $id_objet, $x['args']['objet'])
			);
			ajouter_version($id_objet, $x['args']['objet_source'], $champs, '', session_get('id_auteur'));
		}
	}

	return $x;
}

/**
 * Ajoute la tâche d'optimisation des tables dans la liste des tâches périodiques
 *
 * @pipeline taches_generales_cron
 *
 * @param array $taches_generales
 *     Tableau des tâches et leur périodicité en seconde
 * @return array
 *     Tableau des tâches et leur périodicité en seconde
 */
function revisions_taches_generales_cron($taches_generales) {
	$taches_generales['optimiser_revisions'] = 86400;

	return $taches_generales;
}
revisions.php000064400000071776152526156350007332 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Fonctions utilitaires du plugin révisions
 *
 * @package SPIP\Revisions\Fonctions
 **/
if (!defined("_ECRIRE_INC_VERSION")) {
	return;
}

$GLOBALS['agregation_versions'] = 10;

/** Intervalle de temps (en seconde) separant deux révisions par un même auteur */
define('_INTERVALLE_REVISIONS', 600);

/**
 * Découper les paragraphes d'un texte en fragments
 *
 * @param string $texte Texte à fragmenter
 * @param array $paras Tableau de fragments déjà là
 * @return string[]      Tableau de fragments (paragraphes)
 **/
function separer_paras($texte, $paras = array()) {
	if (!$paras) {
		$paras = array();
	}
	while (preg_match("/(\r\n?){2,}|\n{2,}/", $texte, $regs)) {
		$p = strpos($texte, $regs[0]) + strlen($regs[0]);
		$paras[] = substr($texte, 0, $p);
		$texte = substr($texte, $p);
	}
	if ($texte) {
		$paras[] = $texte;
	}

	return $paras;
}

// https://code.spip.net/@replace_fragment
function replace_fragment($id_objet, $objet, $version_min, $version_max, $id_fragment, $fragment) {
	$fragment = serialize($fragment);
	$compress = 0;

	/* On ne compresse plus les fragments car probleme de portabilite de base, corruptions de donnees
	en backup SQLite ou meme en mysqldump */
	// pour le portage en PG il faut l'equivalente au mysql_escape_string
	// et deporter son appel dans les fonctions d'abstraction.
	/*
	if (function_exists('gzcompress')
	AND $GLOBALS['connexions'][0]['type'] == 'mysql') {
		$s = gzcompress($fragment);
		if (strlen($s) < strlen($fragment)) {
			# spip_log("gain gz: ".intval(100 - 100 * strlen($s) / strlen($fragment)),'revisions');
			$compress = 1;
			$fragment = $s;
		}
	}
	*/

	// Attention a echapper $fragment, binaire potentiellement gz
	return array(
		'id_objet' => intval($id_objet),
		'objet' => $objet,
		'id_fragment' => intval($id_fragment),
		'version_min' => intval($version_min),
		'version_max' => intval($version_max),
		'compress' => $compress,
		'fragment' => $fragment
	);
}

// https://code.spip.net/@envoi_replace_fragments
function envoi_replace_fragments($replaces) {
	$desc = $GLOBALS['tables_auxiliaires']['spip_versions_fragments'];
	foreach ($replaces as $r) {
		sql_replace('spip_versions_fragments', $r, $desc);
	}
}


// https://code.spip.net/@envoi_delete_fragments
function envoi_delete_fragments($id_objet, $objet, $deletes) {
	if (count($deletes)) {
		sql_delete("spip_versions_fragments",
			"id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND ((" . join(") OR (",
				$deletes) . "))");
	}
}


//
// Ajouter les fragments de la derniere version (tableau associatif id_fragment => texte)
//
// https://code.spip.net/@ajouter_fragments
function ajouter_fragments($id_objet, $objet, $id_version, $fragments) {
	global $agregation_versions;

	$replaces = array();
	foreach ($fragments as $id_fragment => $texte) {
		$nouveau = true;
		// Recuperer la version la plus recente
		$row = sql_fetsel("compress, fragment, version_min, version_max", "spip_versions_fragments",
			"id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_fragment=$id_fragment AND version_min<=$id_version",
			"", "version_min DESC", "1");

		if ($row) {
			$fragment = $row['fragment'];
			$version_min = $row['version_min'];
			if ($row['compress'] > 0) {
				$fragment = @gzuncompress($fragment);
			}
			$fragment = unserialize($fragment);
			if (is_array($fragment)) {
				unset($fragment[$id_version]);
				// Si le fragment n'est pas trop gros, prolonger celui-ci
				$nouveau = count($fragment) >= $agregation_versions
					&& strlen($row['fragment']) > 1000;
			}
		}
		if ($nouveau) {
			$fragment = array($id_version => $texte);
			$version_min = $id_version;
		} else {
			// Ne pas dupliquer les fragments non modifies
			$modif = true;
			for ($i = $id_version - 1; $i >= $version_min; $i--) {
				if (isset($fragment[$i])) {
					$modif = ($fragment[$i] != $texte);
					break;
				}
			}
			if ($modif) {
				$fragment[$id_version] = $texte;
			}
		}

		// Preparer l'enregistrement du fragment
		$replaces[] = replace_fragment($id_objet, $objet, $version_min, $id_version, $id_fragment, $fragment);
	}

	envoi_replace_fragments($replaces);
}

//
// Supprimer tous les fragments d'un objet lies a un intervalle de versions
// (essaie d'eviter une trop grande fragmentation)
//
// https://code.spip.net/@supprimer_fragments
function supprimer_fragments($id_objet, $objet, $version_debut, $version_fin) {
	global $agregation_versions;

	$replaces = array();
	$deletes = array();

	// D'abord, vider les fragments inutiles
	sql_delete("spip_versions_fragments",
		"id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND version_min>=$version_debut AND version_max<=$version_fin");

	// Fragments chevauchant l'ensemble de l'intervalle, s'ils existent
	$result = sql_select("id_fragment, compress, fragment, version_min, version_max", "spip_versions_fragments",
		"id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND version_min<$version_debut AND version_max>$version_fin");

	while ($row = sql_fetch($result)) {
		$id_fragment = $row['id_fragment'];
		$fragment = $row['fragment'];
		if ($row['compress'] > 0) {
			$fragment = gzuncompress($fragment);
		}
		$fragment = unserialize($fragment);
		for ($i = $version_fin; $i >= $version_debut; $i--) {
			if (isset($fragment[$i])) {
				// Recopier le dernier fragment si implicite
				if (!isset($fragment[$version_fin + 1])) {
					$fragment[$version_fin + 1] = $fragment[$i];
				}
				unset($fragment[$i]);
			}
		}

		$replaces[] = replace_fragment($id_objet, $objet,
			$row['version_min'], $row['version_max'], $id_fragment, $fragment);
	}

	// Fragments chevauchant le debut de l'intervalle, s'ils existent
	$result = sql_select("id_fragment, compress, fragment, version_min, version_max", "spip_versions_fragments",
		"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");

	$deb_fragment = array();
	while ($row = sql_fetch($result)) {
		$id_fragment = $row['id_fragment'];
		$fragment = $row['fragment'];
		$version_min = $row['version_min'];
		$version_max = $row['version_max'];
		if ($row['compress'] > 0) {
			$fragment = gzuncompress($fragment);
		}
		$fragment = unserialize($fragment);
		for ($i = $version_debut; $i <= $version_max; $i++) {
			if (isset($fragment[$i])) {
				unset($fragment[$i]);
			}
		}

		// Stocker temporairement le fragment pour agregation
		$deb_fragment[$id_fragment] = $fragment;
		// Ajuster l'intervalle des versions
		$deb_version_min[$id_fragment] = $version_min;
		$deb_version_max[$id_fragment] = $version_debut - 1;
	}

	// Fragments chevauchant la fin de l'intervalle, s'ils existent
	$result = sql_select("id_fragment, compress, fragment, version_min, version_max", "spip_versions_fragments",
		"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");

	while ($row = sql_fetch($result)) {
		$id_fragment = $row['id_fragment'];
		$fragment = $row['fragment'];
		$version_min = $row['version_min'];
		$version_max = $row['version_max'];
		if ($row['compress'] > 0) {
			$fragment = gzuncompress($fragment);
		}
		$fragment = unserialize($fragment);
		for ($i = $version_fin; $i >= $version_min; $i--) {
			if (isset($fragment[$i])) {
				// Recopier le dernier fragment si implicite
				if (!isset($fragment[$version_fin + 1])) {
					$fragment[$version_fin + 1] = $fragment[$i];
				}
				unset($fragment[$i]);
			}
		}

		// Virer l'ancien enregistrement (la cle primaire va changer)
		$deletes[] = "id_fragment=$id_fragment AND version_min=$version_min";
		// Essayer l'agregation
		$agreger = false;
		if (isset($deb_fragment[$id_fragment])) {
			$agreger = (count($deb_fragment[$id_fragment]) + count($fragment) <= $agregation_versions);
			if ($agreger) {
				$fragment = $deb_fragment[$id_fragment] + $fragment;
				$version_min = $deb_version_min[$id_fragment];
			} else {
				$replaces[] = replace_fragment($id_objet, $objet,
					$deb_version_min[$id_fragment], $deb_version_max[$id_fragment],
					$id_fragment, $deb_fragment[$id_fragment]);
			}
			unset($deb_fragment[$id_fragment]);
		}
		if (!$agreger) {
			// Ajuster l'intervalle des versions
			$version_min = $version_fin + 1;
		}
		$replaces[] = replace_fragment($id_objet, $objet, $version_min, $version_max, $id_fragment, $fragment);
	}

	// Ajouter fragments restants
	if (is_array($deb_fragment) && count($deb_fragment) > 0) {
		foreach ($deb_fragment as $id_fragment => $fragment) {
			$replaces[] = replace_fragment($id_objet, $objet,
				$deb_version_min[$id_fragment], $deb_version_max[$id_fragment],
				$id_fragment, $deb_fragment[$id_fragment]);
		}
	}

	envoi_replace_fragments($replaces);
	envoi_delete_fragments($id_objet, $objet, $deletes);
}


/**
 * Récupérer les fragments d'un objet pour une version demandée
 *
 * @param int $id_objet Identifiant de l'objet
 * @param string $objet Objet
 * @param int $id_version Identifiant de la version
 * @return array           Couples id_fragment => texte
 */
function recuperer_fragments($id_objet, $objet, $id_version) {
	$fragments = array();

	if ($id_version == 0) {
		return array();
	}

	$result = sql_select(
		"id_fragment, version_min, version_max, compress, fragment",
		"spip_versions_fragments",
		"id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet)
		. " AND version_min<=$id_version AND version_max>=$id_version");

	while ($row = sql_fetch($result)) {
		$id_fragment = $row['id_fragment'];
		$version_min = $row['version_min'];
		$fragment = $row['fragment'];
		// si le fragment est compressé, tenter de le décompresser, sinon écrire une erreur
		if ($row['compress'] > 0) {
			$fragment_ = @gzuncompress($fragment);
			if (strlen($fragment) && $fragment_ === false) {
				$fragment = serialize(array($row['version_max'] => "[" . _T('forum_titre_erreur') . $id_fragment . "]"));
			} else {
				$fragment = $fragment_;
			}
		}
		// tenter dedésérialiser le fragment, sinon écrire une erreur
		$fragment_ = unserialize($fragment);
		if (strlen($fragment) && $fragment_ === false) {
			$fragment = array($row['version_max'] => "[" . _T('forum_titre_erreur') . $id_fragment . "]");
		} else {
			$fragment = $fragment_;
		}
		// on retrouve le fragment le plus près de notre version
		for ($i = $id_version; $i >= $version_min; $i--) {
			if (isset($fragment[$i])) {

				## hack destine a sauver les archives des sites iso-8859-1
				## convertis en utf-8 (les archives ne sont pas converties
				## mais ce code va les nettoyer ; pour les autres charsets
				## la situation n'est pas meilleure ni pire qu'avant)
				if ($GLOBALS['meta']['charset'] == 'utf-8'
					and include_spip('inc/charsets')
					and !is_utf8($fragment[$i])
				) {
					$fragment[$i] = importer_charset($fragment[$i], 'iso-8859-1');
				}

				$fragments[$id_fragment] = $fragment[$i];
				// quitter la boucle dès le premier touvé.
				break;
			}
		}
	}

	return $fragments;
}


//
// Apparier des paragraphes deux a deux entre une version originale
// et une version modifiee
//
// https://code.spip.net/@apparier_paras
function apparier_paras($src, $dest, $flou = true) {
	$src_dest = array();
	$dest_src = array();

	$t1 = $t2 = array();

	$md1 = $md2 = array();
	$gz_min1 = $gz_min2 = array();
	$gz_trans1 = $gz_trans2 = array();
	$l1 = $l2 = array();

	// Nettoyage de la ponctuation pour faciliter l'appariement
	foreach ($src as $key => $val) {
		$t1[$key] = strval(preg_replace("/[[:punct:][:space:]]+/", " ", $val));
	}
	foreach ($dest as $key => $val) {
		$t2[$key] = strval(preg_replace("/[[:punct:][:space:]]+/", " ", $val));
	}

	// Premiere passe : chercher les correspondance exactes
	foreach ($t1 as $key => $val) {
		$md1[$key] = md5($val);
	}
	foreach ($t2 as $key => $val) {
		$md2[md5($val)][$key] = $key;
	}
	foreach ($md1 as $key1 => $h) {
		if (isset($md2[$h])) {
			$key2 = reset($md2[$h]);
			if (isset($t1[$key1]) and isset($t2[$key2]) and $t1[$key1] == $t2[$key2]) {
				$src_dest[$key1] = $key2;
				$dest_src[$key2] = $key1;
				unset($t1[$key1]);
				unset($t2[$key2]);
				unset($md2[$h][$key2]);
			}
		}
	}

	if ($flou) {
		// Deuxieme passe : recherche de correlation par test de compressibilite
		foreach ($t1 as $key => $val) {
			$l1[$key] = strlen(gzcompress($val));
		}
		foreach ($t2 as $key => $val) {
			$l2[$key] = strlen(gzcompress($val));
		}
		foreach ($t1 as $key1 => $s1) {
			foreach ($t2 as $key2 => $s2) {
				$r = strlen(gzcompress($s1 . $s2));
				$taux = 1.0 * $r / ($l1[$key1] + $l2[$key2]);
				if (!isset($gz_min1[$key1]) || !$gz_min1[$key1] || $gz_min1[$key1] > $taux) {
					$gz_min1[$key1] = $taux;
					$gz_trans1[$key1] = $key2;
				}
				if (!isset($gz_min2[$key2]) || !$gz_min2[$key2] || $gz_min2[$key2] > $taux) {
					$gz_min2[$key2] = $taux;
					$gz_trans2[$key2] = $key1;
				}
			}
		}

		// Depouiller les resultats de la deuxieme passe :
		// ne retenir que les correlations reciproques
		foreach ($gz_trans1 as $key1 => $key2) {
			if ($gz_trans2[$key2] == $key1 && $gz_min1[$key1] < 0.9) {
				$src_dest[$key1] = $key2;
				$dest_src[$key2] = $key1;
			}
		}
	}

	// Retourner les mappings
	return array($src_dest, $dest_src);
}

/**
 * Retrouve les champs d'un objet et leurs contenus à une version donnée
 *
 * @uses recuperer_version()
 * @uses liste_champs_versionnes()
 * @uses retrouver_champ_version_objet()
 * 
 * @param int $id_objet Identifiant de l'objet
 * @param string $objet Objet
 * @param int $id_version Identifiant de la version
 * @return array           Couples champs => textes 
**/
function recuperer_version_complete($id_objet, $objet, $id_version) {
	if (!$id_version or !$id_objet or !$objet) {
		return array();
	}

	include_spip('inc/suivi_versions');

	// champs modifiés à la version voulue
	$textes = recuperer_version($id_objet, $objet, $id_version);

	// tous les champs possibles versionnés pour l'objet
	$champs = liste_champs_versionnes(table_objet_sql($objet));

	foreach ($champs as $champ) {
		// Remonter dans le temps pour trouver le champ en question pour chaque version
		retrouver_champ_version_objet($objet, $id_objet, $id_version, $champ, $textes);
	}

	return $textes;
}

/**
 * Récupérer les champs d'un objet, pour une version demandée
 *
 * @param int $id_objet Identifiant de l'objet
 * @param string $objet Objet
 * @param int $id_version Identifiant de la version
 * @return array           Couples champs => textes
 */
function recuperer_version($id_objet, $objet, $id_version) {

	$champs = sql_getfetsel("champs", "spip_versions",
		"id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version=" . intval($id_version));
	if (!$champs or !is_array($champs = unserialize($champs))) {
		return array();
	} else {
		return reconstuire_version($champs, 
			recuperer_fragments($id_objet, $objet, $id_version));
	}
}

/**
 * Reconstruire une version donnée
 *
 * À partir de la liste des champs et de fragments,
 * retourne le texte de chaque champ.
 *
 * @param array $champs Couples (champ => liste d'id_fragment).
 *                          La liste est de la forme "5 32 7 16 8 2"
 * @param array $fragments Couples (id_fragment => texte)
 * @param array $res Couples (champ => texte) déjà connus
 * @return array            Couples (champ => texte)
 */
function reconstuire_version($champs, $fragments, $res = array()) {

	static $msg;
	if (!$msg) {
		$msg = _T('forum_titre_erreur');
	}

	foreach ($champs as $nom => $code) {
		if (!isset($res[$nom])) {
			$t = '';
			foreach (array_filter(explode(' ', $code)) as $id) {
				$t .= isset($fragments[$id])
					? $fragments[$id]
					: "[$msg$id]";
			}
			$res[$nom] = $t;
		}
	}

	return $res;
}

// https://code.spip.net/@supprimer_versions
function supprimer_versions($id_objet, $objet, $version_min, $version_max) {
	sql_delete("spip_versions",
		"id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version>=$version_min AND id_version<=$version_max");

	supprimer_fragments($id_objet, $objet, $version_min, $version_max);
}


/**
 * Ajouter une version à un objet éditorial
 *
 * @param int $id_objet
 * @param string $objet
 * @param array $champs
 * @param string $titre_version
 *     Titre donné aux modifications apportées
 * @param int|null $id_auteur
 *     Auteur apportant les modifications. En absence (session anonyme), utilisera l'IP pour garder une trace.
 * @return int
 *     id_version : identifiant de la version
 **/
function ajouter_version($id_objet, $objet, $champs, $titre_version = "", $id_auteur = null) {
	$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', array(
			"id_objet=" . intval($id_objet),
			"objet=" . sql_quote($objet),
			"id_version <= 0",
			"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());
	// SQL ne ramene que 4 chiffres significatifs apres la virgule pour 0.0+titre_version
	$date = ($sec . substr($ms, 1, 4)) - 20;
	$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, //  varchar ici!
		'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)", 'revisions');
		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++;
		}
		// version precedente recente, on va la mettre a jour
		// avec les nouveaux arrivants si presents
		else {
			$champs = reconstuire_version(unserialize($champs_old), $paras_old, $champs);
			$onlylock = 're';
		}
	} else {
		$id_version = 1;
	}

	$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]) {
				$nom = key($champs);
				next($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", 'revisions');

	return $id_version;
}

// les textes "diff" ne peuvent pas passer dans propre directement,
// car ils contiennent des <span> et <div> parfois mal places
// https://code.spip.net/@propre_diff
function propre_diff($texte) {

	$span_diff = array();
	if (preg_match_all(',<(/)?(span|div) (class|rem)="diff-[^>]*>,', $texte, $regs, PREG_SET_ORDER)) {
		$regs = array_slice($regs, 0, 500); #limiter la casse s'il y en a trop
		foreach ($regs as $c => $reg) {
			$texte = str_replace($reg[0], '@@@SPIP_DIFF' . $c . '@@@', $texte);
		}
	}

	// [ ...<span diff> -> lien ]
	// < tag <span diff> >
	$texte = preg_replace(',<([^>]*?@@@SPIP_DIFF[0-9]+@@@),',
		'&lt;\1', $texte);

	# attention ici astuce seulement deux @@ finals car on doit eviter
	# deux patterns a suivre, afin de pouvoir prendre [ mais eviter [[
	$texte = preg_replace(',(^|[^[])[[]([^[\]]*@@@SPIP_DIFF[0-9]+@@),',
		'\1&#91;\2', $texte);

	// desactiver TeX & toujours-paragrapher
	$tex = $GLOBALS['traiter_math'];
	$GLOBALS['traiter_math'] = '';
	$mem = $GLOBALS['toujours_paragrapher'];
	$GLOBALS['toujours_paragrapher'] = false;

	$texte = propre($texte);

	// retablir
	$GLOBALS['traiter_math'] = $tex;
	$GLOBALS['toujours_paragrapher'] = $mem;

	// un blockquote mal ferme peut gener l'affichage, et title plante safari
	$texte = preg_replace(',<(/?(blockquote|title)[^>]*)>,i', '&lt;\1>', $texte);

	// Dans les <cadre> c'est un peu plus complique
	if (preg_match_all(',<textarea (.*)</textarea>,Uims', $texte, $area, PREG_SET_ORDER)) {
		foreach ($area as $reg) {
			$remplace = preg_replace(',@@@SPIP_DIFF[0-9]+@@@,', '**', $reg[0]);
			if ($remplace <> $reg[0]) {
				$texte = str_replace($reg[0], $remplace, $texte);
			}
		}
	}

	// replacer les valeurs des <span> et <div> diff-
	if (is_array($regs)) {
		foreach ($regs as $c => $reg) {
			$bal = (!$reg[1]) ? $reg[0] : "</$reg[2]>";
			$texte = str_replace('@@@SPIP_DIFF' . $c . '@@@', $bal, $texte);
			$GLOBALS['les_notes'] = str_replace('@@@SPIP_DIFF' . $c . '@@@', $bal, $GLOBALS['les_notes']);
		}
	}


	// quand le dernier tag est ouvrant le refermer ...
	$reg = end($regs);
	if (!$reg[1] and $reg[2]) {
		$texte .= "</$reg[2]>";
	}

	// et interdire_scripts !
	$texte = interdire_scripts($texte);

	return $texte;
}


/**
 * Liste les champs versionnés d'une table objet.
 *
 * @param string $table
 *     Nom complet de sa table sql. Exemple 'spip_articles'
 * @return array
 *     Liste des champs versionnés
 */
function liste_champs_versionnes($table) {
	$liste_objets_versionnees = is_array(unserialize($GLOBALS['meta']['objets_versions'])) ? unserialize($GLOBALS['meta']['objets_versions']) : array();

	if (!in_array($table, $liste_objets_versionnees)) {
		return array();
	}

	include_spip('base/objets');
	if ($infos = lister_tables_objets_sql($table)
		and isset($infos['champs_versionnes'])
	) {
		return $infos['champs_versionnes'];
	}

	return array();
}

/**
 * Lorsqu'un champ versionée est une jointure, récuperer tous les liens
 * et les mettre sous forme de liste énumérée
 *
 * @param string $objet
 * @param string $id_objet
 * @param string $jointure
 * @return string
 */
function recuperer_valeur_champ_jointure($objet, $id_objet, $jointure) {
	$objet_joint = objet_type($jointure);
	include_spip('action/editer_liens');
	$v = array();
	if (objet_associable($objet_joint)) {
		$liens = objet_trouver_liens(array($objet_joint => '*'), array($objet => $id_objet));
		foreach ($liens as $l) {
			$v[] = $l[$objet_joint];
		}
	} elseif (objet_associable($objet)) {
		$liens = objet_trouver_liens(array($objet => $id_objet), array($objet_joint => '*'));
		foreach ($liens as $l) {
			$v[] = $l[$objet];
		}
	}
	sort($v);

	return implode(",", $v);
}

/**
 * Créer la première révision d'un objet si nécessaire
 *
 * À faire notamment si on vient d'activer l'extension et qu'on fait une modif
 * sur un objet qui était déjà en base, mais non versionné
 *
 * La fonction renvoie le numéro de la dernière version de l'objet,
 * et 0 si pas de version pour cet objet
 *
 * @param string $table
 * @param string $objet
 * @param int $id_objet
 * @param array $champs
 * @param int $id_auteur
 * @return int
 */
function verifier_premiere_revision($table, $objet, $id_objet, $champs = null, $id_auteur = 0) {

	$id_table_objet = id_table_objet($objet);
	if (!$champs) {
		$champs = liste_champs_versionnes($table);
	}
	if (!$champs) {
		return false;
	}

	if (!$id_version = sql_getfetsel('id_version', 'spip_versions',
		"id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet), '', 'id_version DESC', '0,1')
	) {
		// recuperer toutes les valeurs actuelles des champs
		// pour l'objet
		$originaux = sql_fetsel("*", $table, "$id_table_objet=" . intval($id_objet));
		$premiere = false;
		$champs_originaux = array();

		foreach ($champs as $v) {
			if (isset($originaux[$v])) {
				$champs_originaux[$v] = $originaux[$v];
			} elseif (strncmp($v, 'jointure_', 9) == 0) {
				$champs_originaux[$v] = recuperer_valeur_champ_jointure($objet, $id_objet, substr($v, 9));
			}
			if (isset($champs_originaux[$v]) and isset($originaux[$v]) and strlen($originaux[$v])) {
				$premiere = true;
			}
		}

		// Si un champ est non vide,
		// il faut creer une premiere revision
		if ($premiere) {
			$trouver_table = charger_fonction('trouver_table', 'base');
			$desc = $trouver_table($table);

			// "trouver" une date raisonnable pour la version initiale

			$date_modif = "";
			foreach (array('date_modif', 'maj') as $d) {
				if (!$date_modif and isset($originaux[$d]) and $t = strtotime($originaux[$d])) {
					$date_modif = date("Y-m-d H:i:s", $t - 20);
				}
			}
			if (!$date_modif
				and isset($desc['date'])
				and isset($originaux[$desc['date']])
			) {
				$date_modif = $originaux[$desc['date']];
			} elseif (!$date_modif) {
				$date_modif = date("Y-m-d H:i:s", time() - 20);
			}

			if ($id_version = ajouter_version($id_objet, $objet, $champs_originaux, _T('revisions:version_initiale'),
				$id_auteur)
			) {
				sql_updateq('spip_versions', array('date' => $date_modif),
					"id_objet=" . intval($id_objet) . " AND objet=" . sql_quote($objet) . " AND id_version=$id_version");
			}
		}
	}

	return $id_version;
}
diff.php000064400000030275152526156350006206 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2020                                                *
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Fonctions utilitaires du plugin révisions
 *
 * @package SPIP\Revisions\Diff
 **/

if (!defined("_ECRIRE_INC_VERSION")) {
	return;
}


// LCS (Longest Common Subsequence) en deux versions
// (ref: http://web.archive.org/web/20071206224029/http://www2.toki.or.id/book/AlgDesignManual/BOOK/BOOK5/NODE208.HTM#SECTION03178000000000000000)

/**
 * Calcule un LCS (Longest Common Subsequence) simplifié
 *
 * Chaque chaîne est une permutation de l'autre et on passe en paramètre
 * un des deux tableaux de correspondances
 *
 * @see lcs()
 * @param array $s
 * @return array
 **/
function lcs_opt($s) {
	$n = count($s);
	if (!$n) {
		return array();
	}
	$paths = array();
	$paths_ymin = array();
	$max_len = 0;

	// Insertion des points
	asort($s);
	$max = 400;
	foreach ($s as $y => $c) {
		if ($max-- < 0) {
			break;
		}  # eviter l'explosion memoire des tres gros diff
		for ($len = $max_len; $len > 0; $len--) {
			if ($paths_ymin[$len] < $y) {
				$paths_ymin[$len + 1] = $y;
				$paths[$len + 1] = $paths[$len];
				$paths[$len + 1][$y] = $c;
				break;
			}
		}
		if ($len == 0) {
			$paths_ymin[1] = $y;
			$paths[1] = array($y => $c);
		}
		if ($len + 1 > $max_len) {
			$max_len = $len + 1;
		}
	}

	return $paths[$max_len];
}

/**
 * Calcule un LCS (Longest Common Subsequence)
 *
 * Les deux chaînes n'ont pas été traitées au préalable par la fonction d'appariement
 *
 * @see lcs_opt()
 * @param array $s
 * @param array $t
 * @return array
 **/
function lcs($s, $t) {
	$n = count($s);
	$p = count($t);
	if (!$n || !$p) {
		return array(0 => array(), 1 => array());
	}
	$paths = array();
	$paths_ymin = array();
	$max_len = 0;
	$s_pos = $t_pos = array();

	// Insertion des points
	foreach ($t as $y => $c) {
		$t_pos[trim($c)][] = $y;
	}

	foreach ($s as $x => $c) {
		$c = trim($c);
		if (!isset($t_pos[$c])) {
			continue;
		}
		krsort($t_pos[$c]);
		foreach ($t_pos[$c] as $y) {
			for ($len = $max_len; $len > 0; $len--) {
				if ($paths_ymin[$len] < $y) {
					$paths_ymin[$len + 1] = $y;
					// On construit le resultat sous forme de chaine d'abord,
					// car les tableaux de PHP sont dispendieux en taille memoire
					$paths[$len + 1] = $paths[$len] . " $x,$y";
					break;
				}
			}
			if ($len + 1 > $max_len) {
				$max_len = $len + 1;
			}
			if ($len == 0) {
				$paths_ymin[1] = $y;
				$paths[1] = "$x,$y";
			}
		}
	}
	if (isset($paths[$max_len]) and $paths[$max_len]) {
		$path = explode(" ", $paths[$max_len]);
		$u = $v = array();
		foreach ($path as $p) {
			list($x, $y) = explode(",", $p);
			$u[$x] = $y;
			$v[$y] = $x;
		}

		return array($u, $v);
	}

	return array(0 => array(), 1 => array());
}

/**
 * Génération de diff a plusieurs étages
 *
 * @package SPIP\Revisions\Diff
 **/
class Diff {
	/**
	 * Objet DiffX d'un texte ou partie de texte
	 *
	 * @var Object Objet Diff* (DiffTexte, DiffPara, DiffPhrase)
	 */
	public $diff;
	public $fuzzy;

	/**
	 * Constructeur
	 *
	 * @param Object $diff Objet Diff* d'un texte ou morceau de texte
	 **/
	public function __construct($diff) {
		$this->diff = $diff;
		$this->fuzzy = true;
	}

// https://code.spip.net/@comparer
	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)) {
					$i_old = key($paras_old);
					$p_old = current($paras_old);
					next($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);
					$i_old = key($paras_old);
					$p_old = current($paras_old);
					next($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)) {
				$i_old = key($paras_old);
				$p_old = current($paras_old);
				next($paras_old);
				if (!strlen($p_old)) {
					$fin_old = true;
				}
			}
			while (!$fin_old) {
				if (!isset($trans_rev[$i_old])) {
					$this->diff->supprimer($p_old);
				}
				$i_old = key($paras_old);
				$p_old = current($paras_old);
				next($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();
	}
}

/**
 * Génération de diff sur un Texte
 *
 * @package SPIP\Revisions\Diff
 **/
class DiffTexte {
	public $r;

	/**
	 * Constructeur
	 **/
	public function __construct() {
		$this->r = "";
	}

// https://code.spip.net/@_diff
	public function _diff($p, $p_old) {
		$diff = new Diff(new DiffPara);

		return $diff->comparer($p, $p_old);
	}

// https://code.spip.net/@fuzzy
	public function fuzzy() {
		return true;
	}

	/**
	 * Découper les paragraphes d'un texte en fragments
	 *
	 * @param string $texte Texte à fragmenter
	 * @return string[]       Tableau de fragments (paragraphes)
	 **/
	public function segmenter($texte) {
		return separer_paras($texte);
	}

	// NB :  rem=\"diff-\" est un signal pour la fonction "afficher_para_modifies"
// https://code.spip.net/@ajouter
	public function ajouter($p) {
		$p = trim($p);
		$this->r .= "\n\n\n<span class=\"diff-para-ajoute\" title=\"" . _T('revisions:diff_para_ajoute') . "\">" . $p . "</span rem=\"diff-\">";
	}

// https://code.spip.net/@supprimer
	public function supprimer($p_old) {
		$p_old = trim($p_old);
		$this->r .= "\n\n\n<span class=\"diff-para-supprime\" title=\"" . _T('revisions:diff_para_supprime') . "\">" . $p_old . "</span rem=\"diff-\">";
	}

// https://code.spip.net/@deplacer
	public function deplacer($p, $p_old) {
		$this->r .= "\n\n\n<span class=\"diff-para-deplace\" title=\"" . _T('revisions:diff_para_deplace') . "\">";
		$this->r .= trim($this->_diff($p, $p_old));
		$this->r .= "</span rem=\"diff-\">";
	}

// https://code.spip.net/@comparer
	public function comparer($p, $p_old) {
		$this->r .= "\n\n\n" . $this->_diff($p, $p_old);
	}

// https://code.spip.net/@resultat
	public function resultat() {
		return $this->r;
	}
}

/**
 * Génération de diff sur un paragraphe
 *
 * @package SPIP\Revisions\Diff
 **/
class DiffPara {
	public $r;

	/** Constructeur */
	public function __construct() {
		$this->r = "";
	}

// https://code.spip.net/@_diff
	public function _diff($p, $p_old) {
		$diff = new Diff(new DiffPhrase);

		return $diff->comparer($p, $p_old);
	}

// https://code.spip.net/@fuzzy
	public function fuzzy() {
		return true;
	}

// https://code.spip.net/@segmenter
	public function segmenter($texte) {
		$paras = array();
		$texte = trim($texte);
		while (preg_match('/[\.!\?\]]+\s*/u', $texte, $regs)) {
			$p = strpos($texte, $regs[0]) + strlen($regs[0]);
			$paras[] = substr($texte, 0, $p);
			$texte = substr($texte, $p);
		}
		if ($texte) {
			$paras[] = $texte;
		}

		return $paras;
	}

// https://code.spip.net/@ajouter
	public function ajouter($p) {
		$this->r .= "<span class=\"diff-ajoute\" title=\"" . _T('revisions:diff_texte_ajoute') . "\">" . $p . "</span rem=\"diff-\">";
	}

// https://code.spip.net/@supprimer
	public function supprimer($p_old) {
		$this->r .= "<span class=\"diff-supprime\" title=\"" . _T('revisions:diff_texte_supprime') . "\">" . $p_old . "</span rem=\"diff-\">";
	}

// https://code.spip.net/@deplacer
	public function deplacer($p, $p_old) {
		$this->r .= "<span class=\"diff-deplace\" title=\"" . _T('revisions:diff_texte_deplace') . "\">" . $this->_diff($p,
				$p_old) . "</span rem=\"diff-\">";
	}

// https://code.spip.net/@comparer
	public function comparer($p, $p_old) {
		$this->r .= $this->_diff($p, $p_old);
	}

// https://code.spip.net/@resultat
	public function resultat() {
		return $this->r;
	}
}

/**
 * Génération de diff sur une phrase
 *
 * @package SPIP\Revisions\Diff
 **/
class DiffPhrase {
	public $r;

	/** Constructeur */
	public function __construct() {
		$this->r = "";
	}

// https://code.spip.net/@fuzzy
	public function fuzzy() {
		return false;
	}

// https://code.spip.net/@segmenter
	public function segmenter($texte) {
		$paras = array();
		if (test_pcre_unicode()) {
			$punct = '([[:punct:]]|' . plage_punct_unicode() . ')';
			$mode = 'u';
		} else {
			// Plages de poncutation pour preg_match bugge (ha ha)
			$punct = '([^\w\s\x80-\xFF]|' . plage_punct_unicode() . ')';
			$mode = '';
		}
		$preg = '/(' . $punct . '+)(\s+|$)|(\s+)(' . $punct . '*)/' . $mode;
		while (preg_match($preg, $texte, $regs)) {
			$p = strpos($texte, $regs[0]);
			$l = strlen($regs[0]);
			$punct = $regs[1] ? $regs[1] : $regs[6];
			$milieu = "";
			if ($punct) {
				// notes
				if ($punct == '[[') {
					$avant = substr($texte, 0, $p) . $regs[5] . $punct;
					$texte = $regs[4] . substr($texte, $p + $l);
				} else {
					if ($punct == ']]') {
						$avant = substr($texte, 0, $p) . $regs[5] . $punct;
						$texte = substr($texte, $p + $l);
					} // Attacher les raccourcis fermants au mot precedent
					else {
						if (preg_match(',^[\]}]+$,', $punct)) {
							$avant = substr($texte, 0, $p) . (isset($regs[5]) ? $regs[5] : '') . $punct;
							$texte = $regs[4] . substr($texte, $p + $l);
						} // Attacher les raccourcis ouvrants au mot suivant
						else {
							if (isset($regs[5]) && $regs[5] && preg_match(',^[\[{]+$,', $punct)) {
								$avant = substr($texte, 0, $p) . $regs[5];
								$texte = $punct . substr($texte, $p + $l);
							} // Les autres signes de ponctuation sont des mots a part entiere
							else {
								$avant = substr($texte, 0, $p);
								$milieu = $regs[0];
								$texte = substr($texte, $p + $l);
							}
						}
					}
				}
			} else {
				$avant = substr($texte, 0, $p + $l);
				$texte = substr($texte, $p + $l);
			}
			if ($avant) {
				$paras[] = $avant;
			}
			if ($milieu) {
				$paras[] = $milieu;
			}
		}
		if ($texte) {
			$paras[] = $texte;
		}

		return $paras;
	}

// https://code.spip.net/@ajouter
	public function ajouter($p) {
		$this->r .= "<span class=\"diff-ajoute\" title=\"" . _T('revisions:diff_texte_ajoute') . "\">" . $p . "</span rem=\"diff-\"> ";
	}

// https://code.spip.net/@supprimer
	public function supprimer($p_old) {
		$this->r .= "<span class=\"diff-supprime\" title=\"" . _T('revisions:diff_texte_supprime') . "\">" . $p_old . "</span rem=\"diff-\"> ";
	}

// https://code.spip.net/@comparer
	public function comparer($p, $p_old) {
		$this->r .= $p;
	}

// https://code.spip.net/@resultat
	public function resultat() {
		return $this->r;
	}
}


// https://code.spip.net/@preparer_diff
function preparer_diff($texte) {
	include_spip('inc/charsets');

	$charset = $GLOBALS['meta']['charset'];
	if ($charset == 'utf-8') {
		return unicode_to_utf_8(html2unicode($texte));
	}

	return unicode_to_utf_8(html2unicode(charset2unicode($texte, $charset, true)));
}

// https://code.spip.net/@afficher_diff
function afficher_diff($texte) {
	$charset = $GLOBALS['meta']['charset'];
	if ($charset == 'utf-8') {
		return $texte;
	}

	return charset2unicode($texte, 'utf-8');
}