Your IP : 216.73.216.20


Current Path : /home/etiennec/www/f1f54/
Upload File :
Current File : /home/etiennec/www/f1f54/inc.tar

filtres_images_compat.php000066600000010273152503000360011616 0ustar00<?php
/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2014                                                *
 *  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.   *
\***************************************************************************/

/**
 * Ce fichier ne sert pas
 * Il est maintenu pour assurer la compatibilite des anciens scripts avec les anciens nommages de fonction
 *  
 */

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

function couleur_dec_to_hex($red, $green, $blue) {include_spip('filtres/images_lib');return _couleur_dec_to_hex($red, $green, $blue);}
function couleur_hex_to_dec($couleur) {include_spip('filtres/images_lib');return _couleur_hex_to_dec($couleur);}
function couleur_rgb2hsv($R,$G,$B) {include_spip('filtres/images_lib');return _couleur_rgb2hsv($R,$G,$B);}
function couleur_hsv2rgb($H,$S,$V) {include_spip('filtres/images_lib');return _couleur_hsv2rgb($H,$S,$V);}
function couleur_rgb2hsl($R,$G,$B) {include_spip('filtres/images_lib');return _couleur_rgb2hsl($R,$G,$B);}
function couleur_hsl2rgb($H,$S,$L) {include_spip('filtres/images_lib');return _couleur_hsl2rgb($H,$S,$L);}
function image_couleur_extraire($img, $x=10, $y=6) {include_spip('filtres/images_lib');return _image_couleur_extraire($img, $x, $y);}
function image_distance_pixel($xo, $yo, $x0, $y0) {include_spip('filtres/images_lib');return _image_distance_pixel($xo, $yo, $x0, $y0);}
function image_decal_couleur($coul, $gamma) {include_spip('filtres/images_lib');return _image_decale_composante($coul, $gamma);}
function image_decal_couleur_127($coul, $val) {include_spip('filtres/images_lib');return _image_decale_composante_127 ($coul, $val);}

function image_creer_vignette($valeurs, $maxWidth, $maxHeight, $process='AUTO', $force=false, $test_cache_only = false) {include_spip('inc/filtres_images_lib'); return _image_creer_vignette($valeurs, $maxWidth, $maxHeight, $process, $force, $test_cache_only);}
function image_ecrire_tag($valeurs,$surcharge){include_spip('inc/filtres_images_lib'); return _image_ecrire_tag($valeurs,$surcharge);}
function image_gd_output($img,$valeurs, $qualite=_IMG_GD_QUALITE){include_spip('inc/filtres_images_lib'); return _image_gd_output($img,$valeurs, $qualite);}
function image_imagepng($img,$fichier) {include_spip('inc/filtres_images_lib'); return _image_imagepng($img,$fichier);}
function image_imagegif($img,$fichier) {include_spip('inc/filtres_images_lib'); return _image_imagegif($img,$fichier);}
function image_imagejpg($img,$fichier,$qualite=_IMG_GD_QUALITE) {include_spip('inc/filtres_images_lib'); return image_imagejpg($img,$fichier,$qualite=_IMG_GD_QUALITE);}
function image_imageico($img, $fichier) {include_spip('inc/filtres_images_lib'); return _image_imageico($img, $fichier);}
function image_ratio ($srcWidth, $srcHeight, $maxWidth, $maxHeight) {include_spip('inc/filtres_images_lib');return _image_ratio ($srcWidth, $srcHeight, $maxWidth, $maxHeight);}
function image_tag_changer_taille($tag,$width,$height,$style=false){include_spip('inc/filtres_images_lib');return _image_tag_changer_taille($tag,$width,$height,$style);}
function image_valeurs_trans($img, $effet, $forcer_format = false, $fonction_creation = NULL) {include_spip('inc/filtres_images_lib'); return _image_valeurs_trans($img, $effet, $forcer_format, $fonction_creation);}

// Pour assurer la compatibilite avec les anciens nom des filtres image_xxx
// commencent par "image_"
// http://code.spip.net/@reduire_image
function reduire_image($texte, $taille = -1, $taille_y = -1) {
	return filtrer('image_graver',
		filtrer('image_reduire',$texte, $taille, $taille_y)
	);
}
// http://code.spip.net/@valeurs_image_trans
function valeurs_image_trans($img, $effet, $forcer_format = false) {
	include_spip('inc/filtres_images_lib_mini');
	return _image_valeurs_trans($img, $effet, $forcer_format = false);
}

?>filtres_images.php000066600000002321152503000360010246 0ustar00<?php
/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2014                                                *
 *  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.   *
\***************************************************************************/


/**
 * Ce fichier ne sert plus
 * Il est maintenu pour assurer la compatibilite des anciens scripts
 *  
 */

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

// prise en charge des renomage de fonction
include_spip('inc/filtres_images_compat');

// prise en charge des fonctions de transformation d'image
include_spip('filtres/images_transforme');

// prise en charge des fonctions d'image typo
include_spip('filtres/images_typo');

// prise en charge des fonctions de transformation de couleur
include_spip('filtres/couleurs');

?>svp_decider.php000066600000105051152503074170007556 0ustar00<?php

/**
 * Gestion du décideur : il calcule en fonction d'une demande d'action
 * sur les plugins tous les enchainements que cela implique sur les
 * dépendances.
 *
 * @plugin SVP pour SPIP
 * @license GPL
 * @package SPIP\SVP\Decideur
 */
 
if (!defined("_ECRIRE_INC_VERSION")) return;

include_spip('plugins/installer'); // pour spip_version_compare()
include_spip('inc/svp_rechercher'); // svp_verifier_compatibilite_spip()
# include_spip('inc/plugin'); // plugin_version_compatible() [inclu dans svp_rechercher]

/**
 * Le décideur calcule les actions qui doivent être faites en fonction
 * de ce qui est demandé et des différentes dépendances des plugins.
 *
 * @package SPIP\SVP\Actionner
**/
class Decideur {

	/**
	 * Plugins actifs en cours avant toute modification
	 * @var array
	 *     Index 'i' : plugins triés par identifiant en base [i][32] = tableau de description
	 *     Index 'p' : plugins triés par prefixe de plugin [p][MOTS] = tableau de description
	 */
	var $start = array(
		'i' => array(),
		'p' => array(),
	);

	/**
	 * Plugins actifs à la fin des modifications effectuées
	 * @var array
	 *     Index 'i' : plugins triés par identifiant en base [i][32] = tableau de description
	 *     Index 'p' : plugins triés par prefixe de plugin [p][MOTS] = tableau de description
	 */
	var $end = array(
		'i' => array(),
		'p' => array(),
	);

	/**
	 * Plugins procure par SPIP
	 * @var array
	 *     Tableau ('PREFIXE' => numéro de version)
	 */
	var $procure = array();

	/**
	 * Toutes les actions à faire demandées
	 * (ce que l'on demande à l'origine)
	 * @var array
	 *     Tableau ('identifiant' => tableau de description)
	 */
	var $ask = array();

	/**
	 * Toutes les actions à faire demandées et consécutives aux dépendances
	 * 
	 * @var array
	 *     Tableau ('identifiant' => tableau de description)
	 */
	var $todo = array();

	/**
	 * Toutes les actions à faire consécutives aux dépendances
	 *
	 * C'est à dire les actions à faire en plus de celles demandées.
	 * 
	 * @var array
	 *     Tableau ('identifiant' => tableau de description)
	 */
	var $changes = array();

	/**
	 * Tous les plugins à arrêter (désactiver ou désinstaller)
	 * 
	 * @var array
	 *     Tableau ('PREFIXE' => tableau de description)
	 */
	var $off = array();

	/**
	 * Tous les plugins invalidés (suite a des dependances introuvables, mauvaise version de SPIP...)
	 * 
	 * @var array
	 *     Tableau ('PREFIXE' => tableau de description)
	 */
	var $invalides = array();

	/**
	 * Liste des erreurs
	 * 
	 * @var array
	 *     Tableau ('identifiant' => liste des erreurs)
	 */
	var $err = array();

	/**
	 * État de santé (absence d'erreur)
	 * 
	 * Le résultat true permettra d'effectuer toutes les actions.
	 * Passe à false dès qu'une erreur est présente !
	 * 
	 * @var bool */
	var $ok = true;

	/**
	 * Loguer les différents éléments
	 * 
	 * Sa valeur sera initialisée par la configuration 'mode_log_verbeux' de SVP
	 * 
	 * @var bool */
	var $log = false;

	/**
	 * Générer une erreur si on demande une mise à jour d'un plugin
	 * alors qu'on ne la connait pas.
	 * @var bool */
	var $erreur_sur_maj_introuvable = true;

	/**
	 * Constructeur
	 * 
	 * Initialise la propriété $log en fonction de la configuration
	 */
	function Decideur () {
		include_spip('inc/config');
		$this->log = (lire_config('svp/mode_log_verbeux') == 'oui');
	}


	/**
	 * Liste des plugins déjà actifs
	 *
	 * @var array
	 *     Index 'i' : plugins triés par identifiant en base [i][32] = tableau de description
	 *     Index 'p' : plugins triés par prefixe de plugin [p][MOTS] = tableau de description
	 */
	function liste_plugins_actifs() {
		return $this->infos_courtes(array('pa.actif='.sql_quote('oui'),  'pa.attente=' . sql_quote('non')));
	}

	/**
	 * Teste si un paquet (via son identifiant) est en attente
	 *
	 * Les plugins en attente ont un statut spécial : à la fois dans la
	 * liste des plugins actifs, mais désactivés. Un plugin passe 'en attente'
	 * lorsqu'il est actif mais perd accidentellement une dépendance,
	 * par exemple si une dépendance est supprimée par FTP.
	 * Dès que sa dépendance revient, le plugin se réactive.
	 *
	 * L'interface de gestion des plugins de SVP, elle, permet pour ces plugins
	 * de les désactiver ou réactiver (retéléchargeant alors la dépendance si possible).
	 *
	 * @param int $id
	 *     Identifiant du plugin
	 * @return bool
	 *     Le plugin est-il en attente ?
	 */
	function est_attente_id($id) {
		static $attente = null;
		if (is_null($attente)) {
			$attente = $this->infos_courtes('pa.attente=' . sql_quote('oui'));
		}
		return isset($attente['i'][$id]) ? $attente['i'][$id] : false;
	}

	/**
	 * Liste des plugins procurés par SPIP
	 *
	 * Calcule la liste des plugins que le core de SPIP déclare procurer.
	 *
	 * @return array
	 *     Tableau ('PREFIXE' => version)
	 */
	function liste_plugins_procure() {
		$procure = array();
		$get_infos = charger_fonction('get_infos','plugins');
		$infos['_DIR_RESTREINT'][''] = $get_infos('./',false,_DIR_RESTREINT);

		foreach($infos['_DIR_RESTREINT']['']['procure'] as $_procure) {
			$prefixe = strtoupper($_procure['nom']);
			$procure[$prefixe] = $_procure['version'];
		}

		return $procure;
	}

	/**
	 * Écrit un log
	 *
	 * Écrit un log si la propriété $log l'autorise.
	 * 
	 * @param mixed $quoi
	 *     La chose à logguer (souvent un texte)
	**/
	function log($quoi) {
		if ($this->log) {
			spip_log($quoi,'decideur');
		}
	}

	/**
	 * Retourne le tableau de description d'un paquet (via son identifiant) 
	 *
	 * @note
	 *     Attention, retourne un tableau complexe.
	 *     La description sera dans : ['i'][$id]
	 * @param int $id
	 *     Identifiant du paquet
	 * @return array
	 *     Index 'i' : plugins triés par identifiant en base [i][32] = tableau de description
	 *     Index 'p' : plugins triés par prefixe de plugin [p][MOTS] = tableau de description
	**/
	function infos_courtes_id($id) {
		// on cache ceux la
		static $plug = array();
		if (!isset($plug[$id])) {
			$plug[$id] = $this->infos_courtes('pa.id_paquet=' . sql_quote($id));
		}
		return $plug[$id];
	}

	/**
	 * Récupérer les infos utiles des paquet
	 *
	 * Crée un tableau de description pour chaque paquet dans une
	 * écriture courte comme index ('i' pour identifiant) tel que :
	 * - i = identifiant
	 * - p = prefixe (en majuscule)
	 * - n = nom du plugin
	 * - v = version
	 * - e = etat
	 * - a = actif
	 * - du = dépendances utilise
	 * - dn = dépendances nécessite
	 * - dl = dépendances librairie
	 * - maj = mise à jour
	 * 
	 * 
	 * On passe un where ($condition) et on crée deux tableaux, l'un des paquets
	 * triés par identifiant, l'autre par prefixe.
	 *
	 * @param array|string $condition
	 *     Condition where
	 * @param bool $multiple
	 *     Si multiple, le tableau par préfixe est un sous-tableau (il peut alors
	 *     y avoir plusieurs paquets pour un même prefixe, classés par états décroissants)
	 * @return array
	 *     Index 'i' : plugins triés par identifiant en base [i][32] = tableau de description
	 *     Index 'p' : plugins triés par prefixe de plugin [p][MOTS] = tableau de description
	 *                 ou, avec $multiple=true : [p][MOTS][] = tableau de description
	 */
	function infos_courtes($condition, $multiple=false) {
		$plugs = array(
			'i'=>array(),
			'p'=>array()
		);

		$from = array('spip_paquets AS pa', 'spip_plugins AS pl');
		$orderby = $multiple ? 'pa.etatnum DESC' : '';
		$where = array('pa.id_plugin = pl.id_plugin');
		if (is_array($condition))
			$where = array_merge($where, $condition);
		else
			$where[] = $condition;

		include_spip('inc/filtres'); // extraire_multi()
		$res = sql_allfetsel(array(
			'pa.id_paquet AS i',
			'pl.nom AS n',
			'pl.prefixe AS p',
			'pa.version AS v',
			'pa.etatnum AS e',
			'pa.compatibilite_spip',
			'pa.dependances',
			'pa.id_depot',
			'pa.maj_version AS maj',
			'pa.actif AS a'), $from, $where, '', $orderby);
		foreach ($res as $r) {
			$r['p'] = strtoupper( $r['p'] ); // on s'assure du prefixe en majuscule.
			
			// savoir si un paquet est en local ou non...
			$r['local'] = ($r['id_depot']) == 0 ? true : false;
			unset($r['id_depot']);
			
			$d = unserialize($r['dependances']);
			// voir pour enregistrer en bdd simplement 'n' et 'u' (pas la peine d'encombrer)...
			$deps = array('necessite'=>array(array()), 'utilise'=>array(array()), 'librairie'=>array(array()));
			if (!$d) $d = $deps;
			
			unset($r['dependances']);

			/*
			 * On extrait les multi sur le nom du plugin
			 */
			$r['n'] = extraire_multi($r['n']);

			$plugs['i'][$r['i']] = $r;

			
			// pour chaque type de dependences... (necessite, utilise, librairie)
			// on cree un tableau unique [$dependence] = array()
			// au lieu de plusieurs tableaux par version de spip
			// en ne mettant dans 0 que ce qui concerne notre spip local
			foreach($deps as $cle => $defaut) {
				if (!isset($d[$cle])) {
					$d[$cle] = $defaut;
				}
				
				// gerer les dependences autres que dans 0 (communs ou local) !!!!
				// il peut exister des cles info[dn]["[version_spip_min;version_spip_max]"] de dependences
				if (!isset($d[$cle][0]) OR count($d[$cle]) > 1) {
					$dep = array();
					$dep[0] = isset($d[$cle][0]) ? $d[$cle][0] : array();
					unset($d[$cle][0]);
					foreach ($d[$cle] as $version => $dependences) {
						if (svp_verifier_compatibilite_spip($version)) {
							$dep = array_merge($dep[0], $dependences);
						}
					}
					$d[$cle] = $dep;
				}
			}
			// passer les prefixes en majuscule
			foreach($d['necessite'][0] as $i=>$n) {
				$d['necessite'][0][$i]['nom'] = strtoupper($n['nom']);
			}
			$plugs['i'][$r['i']]['dn'] = $d['necessite'][0];
			$plugs['i'][$r['i']]['du'] = $d['utilise'][0];
			$plugs['i'][$r['i']]['dl'] = $d['librairie'][0];


			if ($multiple) {
				$plugs['p'][$r['p']][] = &$plugs['i'][$r['i']]; // alias
			} else {
				$plugs['p'][$r['p']] = &$plugs['i'][$r['i']]; // alias
			}
			
		}
		return $plugs;
	}


	/**
	 * Ajoute une erreur sur un paquet
	 *
	 * Passe le flag OK à false : on ne pourra pas faire les actions demandées.
	 * 
	 * @param int $id
	 *     Identifiant de paquet
	 * @param string $texte
	 *     Texte de l'erreur
	 */
	function erreur($id, $texte = '') {
		$this->log("erreur: $id -> $texte");
		if (!isset($this->err[$id]) OR !is_array($this->err[$id])) {
			$this->err[$id] = array();
		}
		$this->err[$id][] = $texte;
		$this->ok = false;
	}

	/**
	 * Teste si une erreur est présente sur un paquet (via son identifiant)
	 *
	 * @param int $id
	 *     Identifiant de paquet
	 * @return bool|array
	 *     false si pas d'erreur, tableau des erreurs sinon.
	 */
	function en_erreur($id) {
		return isset($this->err[$id]) ? $this->err[$id] : false;
	}


	/**
	 * Vérifie qu'un plugin plus récent existe pour un préfixe et une version donnée
	 *
	 * @param string $prefixe
	 *     Préfixe du plugin
	 * @param string $version
	 *     Compatibilité à comparer, exemple '[1.0;]'
	 * @return bool|array
	 *     false si pas de plugin plus récent trouvé
	 *     tableau de description du paquet le plus récent sinon
	 */
	function chercher_plugin_recent($prefixe, $version) {
		$news = $this->infos_courtes(array('pl.prefixe=' . sql_quote($prefixe), 'pa.obsolete=' . sql_quote('non'), 'pa.id_depot > '.sql_quote(0)), true);
		$res = false;
		if ($news and count($news['p'][$prefixe]) > 0) {
			foreach ($news['p'][$prefixe] as $new) {
				if (spip_version_compare($new['v'],$version,'>')) {
					if (!$res or version_compare($new['v'],$res['v'],'>')) {
						$res = $new;
					}
				}
			}
		}
		return $res;
	}

	/**
	 * Vérifie qu'un plugin existe pour un préfixe et une version donnée
	 * 
	 * @param string $prefixe
	 *     Préfixe du plugin
	 * @param string $version
	 *     Compatibilité à comparer, exemple '[1.0;]'
	 * @return bool|array
	 *     false si pas de plugin plus récent trouvé
	 *     tableau de description du paquet le plus récent sinon
	 */
	function chercher_plugin_compatible($prefixe, $version) {
		$plugin = array();

		// on choisit en priorite dans les paquets locaux !
		$locaux = $this->infos_courtes(array(
			'pl.prefixe=' . sql_quote($prefixe),
			'pa.obsolete=' . sql_quote('non'),
			'pa.id_depot='.sql_quote(0)), true);
		if ($locaux and isset($locaux['p'][$prefixe]) and count($locaux['p'][$prefixe]) > 0) {
			$v = '000.000.000';
			foreach ($locaux['p'][$prefixe] as $new) {
				if (plugin_version_compatible($version, $new['v'])
				and svp_verifier_compatibilite_spip($new['compatibilite_spip'])
				and ($new['v'] > $v)){
					$plugin = $new;
					$v = $new['v'];
				}
			}
		}

		if (!$plugin) {
			// sinon dans les paquets distants
			$distants = $this->infos_courtes(array(
				'pl.prefixe=' . sql_quote($prefixe),
				'pa.obsolete=' . sql_quote('non'),
				'pa.id_depot>'.sql_quote(0)), true);
			if ($distants and isset($distants['p'][$prefixe]) and count($distants['p'][$prefixe]) > 0) {
				$v = '000.000.000';
				foreach ($distants['p'][$prefixe] as $new) {
					if (plugin_version_compatible($version, $new['v'])
					and svp_verifier_compatibilite_spip($new['compatibilite_spip'])
					and ($new['v'] > $v)){
						$plugin = $new;
						$v = $new['v'];
					}
				}
			}
		}

		return ($plugin ? $plugin : false);
	}


	/**
	 * Indique qu'un paquet passe à on (on l'active)
	 *
	 * @param array $info
	 *     Description du paquet
	**/
	function add($info) {
		$this->end['i'][$info['i']] = $info;
		$this->end['p'][$info['p']] = &$this->end['i'][$info['i']];
	}

	/**
	 * Indique qu'un paquet passe à off (on le désactive ou désinstalle)
	 *
	 * @param array $info
	 *     Description du paquet
	 * @param bool $recur
	 *     Passer à off les plugins qui en dépendent, de façon récursive ?
	**/
	function off($info, $recur = false) {
		$this->log('- stopper ' . $info['p']);
		$this->remove($info);
		$this->off[$info['p']] = $info;

		// si recursif, on stoppe aussi les plugins dependants
		if ($recur) {
			foreach ($this->end['i'] as $id => $plug) {
				if (is_array($plug['dn']) and $plug['dn']) {
					foreach ($plug['dn'] as $n) {
						if ($info['p'] == $n['nom']) {
							$this->change($plug, 'off');
							$this->off($plug, true);
						}
					}
				}
			}
		}
	}

	/**
	 * Teste qu'un paquet (via son préfixe) sera passé off (désactivé ou désinstallé)
	 *
	 * @param string $prefixe
	 *     Prefixe du paquet
	 * @return bool
	 *     Le paquet sera t'il off ?
	**/
	function sera_off($prefixe) {
		return isset($this->off[$prefixe]) ? $this->off[$prefixe] : false;
	}

	/**
	 * Teste qu'un paquet (via son identifiant) sera passé off (désactivé ou désinstallé)
	 *
	 * @param int $id
	 *     Identifiant du paquet
	 * @return bool
	 *     Le paquet sera t'il off ?
	**/
	function sera_off_id($id) {
		foreach ($this->off as $info) {
			if ($info['i'] == $id) {
				return $info;
			}
		}
		return false;
	}

	/**
	 * Teste qu'un paquet (via son préfixe) sera actif
	 *
	 * @param string $prefixe
	 *     Préfixe du paquet
	 * @return bool
	 *     Le paquet sera t'il actif ?
	**/
	function sera_actif($prefixe) {
		return isset($this->end['p'][$prefixe]) ? $this->end['p'][$prefixe] : false;
	}

	/**
	 * Teste qu'un paquet (via son identifiant) sera actif
	 *
	 * @param int $id
	 *     Identifiant du paquet
	 * @return bool
	 *     Le paquet sera t'il actif ?
	**/
	function sera_actif_id($id) {
		return isset($this->end['i'][$id]) ? $this->end['i'][$id] : false;
	}

	/**
	 * Ajouter une action/paquet à la liste des demandées
	 *
	 * L'ajoute aussi à la liste de toutes les actions !
	 * 
	 * @param array $info
	 *     Description du paquet concerné
	 * @param string $quoi
	 *     Type d'action (on, off, kill, upon...)
	 */
	function ask($info, $quoi) {
		$this->ask[$info['i']] = $info;
		$this->ask[$info['i']]['todo'] = $quoi;
		$this->todo($info, $quoi);
	}

	/**
	 * Ajouter une action/paquet à la liste des changements en plus
	 * par rapport à la demande initiale
	 *
	 * L'ajoute aussi à la liste de toutes les actions !
	 * 
	 * @param array $info
	 *     Description du paquet concerné
	 * @param string $quoi
	 *     Type d'action (on, off, kill, upon...)
	 */
	function change($info, $quoi) {
		$this->changes[$info['i']] = $info;
		$this->changes[$info['i']]['todo'] = $quoi;
		$this->todo($info, $quoi);
	}


	/**
	 * Annule une action (automatique) qui finalement était réellement demandée.
	 * 
	 * Par exemple, une mise à 'off' de paquet entraîne d'autres mises à
	 * 'off' des paquets qui en dépendent. Si une action sur un des paquets
	 * dépendants était aussi demandée, il faut annuler l'action automatique.
	 *
	 * @param array $info
	 *     Description du paquet concerné
	 */
	function annule_change($info) {
		unset($this->changes[$info['i']]);
	}

	/**
	 * Ajouter une action/paquet à la liste de toutes les actions à faire
	 *
	 * @param array $info
	 *     Description du paquet concerné
	 * @param string $quoi
	 *     Type d'action (on, off, kill, upon...)
	 */
	function todo($info, $quoi) {
		$this->todo[$info['i']] = $info;
		$this->todo[$info['i']]['todo'] = $quoi;
	}

	/**
	 * Retire un paquet de la liste des paquets à activer
	 *
	 * @param array $info
	 *     Description du paquet concerné
	 */
	function remove($info) {
		// aucazou ce ne soit pas les memes ids entre la demande et la bdd,
		// on efface aussi avec l'id donne par le prefixe.
		// Lorsqu'on desactive un plugin en "attente", il n'est pas actif !
		// on teste tout de meme donc qu'il est la ce prefixe !
		$i = false;
		if (isset($this->end['p'][$info['p']])) {
			$i = $this->end['p'][$info['p']];
		}
		// on enleve les cles par id indique et par prefixe
		unset($this->end['i'][$info['i']], $this->end['p'][$info['p']]);
		// ainsi que l'id aucazou du prefixe
		if ($i) {
			unset($this->end['i'][$i['i']]);
		}

	}


	/**
	 * Invalide un plugin (il est introuvable, ne correspond pas à notre version de SPIP...)
	 *
	 * @param array $info
	 *     Description du paquet concerné
	 */
	function invalider($info) {
		$this->log("-> invalider $info[p]");
		$this->remove($info); // suffisant ?
		$this->invalides[$info['p']] = $info;
		$this->annule_change($info);
		unset($this->todo[$info['i']]);
	}

	/**
	 * Teste qu'un paquet (via son préfixe) est déclaré invalide
	 *
	 * @param string $p
	 *     Prefixe du paquet
	 * @return bool
	 *     Le paquet est t'il invalide ?
	**/
	function sera_invalide($p) {
		return isset($this->invalides[$p]) ? $this->invalides[$p] : false;
	}

	/**
	 * Teste qu'une librairie (via son nom) est déjà présente
	 *
	 * @param string $lib
	 *     Nom de la librairie
	 * @return bool
	 *     La librairie est-elle présente ?
	**/
	function est_presente_lib($lib) {
		static $libs = false;
		if ($libs === false) {
			include_spip('inc/svp_outiller');
			$libs = svp_lister_librairies();
		}
		return isset($libs[$lib]) ? $libs[$lib] : false;
	}


	/**
	 * Ajoute les actions demandées au décideur
	 *
	 * Chaque action est analysée et elles sont redispatchées dans différents
	 * tableaux via les méthodes :
	 * - ask  : ce qui est demandé (ils y vont tous)
	 * - todo : ce qui est à faire (ils y vont tous aussi)
	 * - add  : les plugins activés,
	 * - off  : les plugins désactivés
	 *
	 * La fonction peut lever des erreurs sur les actions tel que :
	 * - Paquet demandé inconnu
	 * - Mise à jour introuvable
	 * - Paquet à désactiver mais qui n'est pas actif
	 *
	 * @param array $todo
	 *     Ce qui est demandé de faire
	 *     Tableau identifiant du paquet => type d'action (on, off, up...)
	 * @return bool
	 *     False en cas d'erreur, true sinon
	 */
	function actionner($todo = null) {
		if (is_array($todo)) {
			foreach ($todo as $id => $t) {
				// plusieurs choses nous interessent... Sauf... le simple telechargement
				// et la suppression des fichiers (qui ne peuvent etre fait
				// que si le plugin n'est pas actif)
				$this->log("-- todo: $id/$t");

				switch ($t) {
					case 'getlib':
						break;
					case 'on':
					case 'geton':
						// ajouter ce plugin dans la liste
						if (!$this->sera_actif_id($id)) {
							$i = $this->infos_courtes_id($id);
							if ($i = $i['i'][$id]) {
								$this->log("--> $t : " . $i['p'] . ' en version : ' . $i['v'] );

								// se mefier : on peut tenter d'activer
								// un plugin de meme prefixe qu'un autre deja actif
								// mais qui n'est pas de meme version ou de meme etat
								// par exemple un plugin obsolete ou un plugin au contraire plus a jour.
								// dans ce cas, on desactive l'ancien (sans desactiver les dependences)
								// et on active le nouveau.
								// Si une dependance ne suit pas, une erreur se produira du coup.
								if (isset($this->end['p'][ $i['p'] ])) {
									$old = $this->end['p'][ $i['p'] ];
									$this->log("-->> off : " . $old['p'] . ' en version : ' . $old['v'] );
									$this->ask($old, 'off');
									$this->todo($old, 'off');
									// désactive l'ancien plugin, mais pas les dépendances qui en dépendent
									// car normalement, ça devrait suivre...
									$this->off($old, false); 

								}
								
								// pas de prefixe equivalent actif...
								$this->add($i);
								$this->ask($i, $i['local'] ? 'on' : 'geton' );
								
							} else {
								// la c'est vraiment pas normal... Erreur plugin inexistant...
								// concurrence entre administrateurs ?
								$this->erreur($id, _T('svp:message_nok_plugin_inexistant',array('plugin' => $id)));
							}
						}
						break;
					case 'up':
					case 'upon':
						// le plugin peut etre actif !
						// ajouter ce plugin dans la liste et retirer l'ancien
						$i = $this->infos_courtes_id($id);
						if ($i = $i['i'][$id]) {
							$this->log("--> $t : " . $i['p'] . ' en version : ' . $i['v'] );

							// new : plugin a installer
							if ($new = $this->chercher_plugin_recent($i['p'], $i['v'])) {
								$this->log("--> maj : " . $new['p'] . ' en version : ' . $new['v'] );
								// ajouter seulement si on l'active !
								// ou si le plugin est actuellement actif
								if ($t == 'upon' or $this->sera_actif_id($id)) {
									$this->remove($i);
									$this->add($new);
								}
								$this->ask($i, $t);
							} else {
								if ($this->erreur_sur_maj_introuvable) {
									// on n'a pas trouve la nouveaute !!!
									$this->erreur($id, _T('svp:message_nok_maj_introuvable',array('plugin' => $i['n'],'id'=>$id)));
								}
							}
						} else {
							// mauvais identifiant ?
							// on n'a pas trouve le plugin !!!
							$this->erreur($id, _T('svp:message_erreur_maj_inconnu',array('id'=>$id)));
						}
						break;
					case 'off':
					case 'stop':
						// retirer ce plugin
						// (il l'est peut etre deja)
						if ($info = $this->sera_actif_id($id)
						or  $info_off = $this->sera_off_id($id)
						// un plugin en attente (desactive parce sa dependance a disparu certainement par ftp)
						// peut etre desactive
						or $info = $this->est_attente_id($id)) {
							// annuler le signalement en "proposition" (due a une mise a 'off' recursive)
							// de cet arret de plugin, vu qu'on le demande reellement
							if (!$info) {
								$info = $info_off;
								$this->annule_change($info);
							}
							$this->log("--> $t : " . $info['p'] . ' en version : ' . denormaliser_version($info['v']) );
							$this->ask($info, $t);
							$this->todo($info, $t);
							// désactive tous les plugins qui en dépendent aussi.
							$this->off($info, true);

						} else {
							// pas normal... plugin deja inactif...
							// concurrence entre administrateurs ?
							$this->erreur($id, _T('svp:message_erreur_plugin_non_actif'));
						}
						break;
					case 'null':
					case 'get':
					case 'kill':
						if ($info = $this->infos_courtes_id($id)) {
							$this->log("--> $t : " . $info['i'][$id]['p'] . ' en version : ' . $info['i'][$id]['v'] );
							$this->ask($info['i'][$id], $t);
						} else {
							// pas normal... plugin inconnu... concurrence entre administrateurs ?
							$this->erreur($id, _T('svp:message_erreur_plugin_introuvable',array('plugin'=>$id,'action'=>$t)));
						}
						break;
				}
			}
		}
		return $this->ok;
	}


	/**
	 * Initialise les listes de plugins pour le calcul de dépendances
	 *
	 * Les propriété $start et $end reçoivent la liste des plugins actifs
	 * $procure celle des plugins procurés par le Core
	 */
	function start() {
		$this->start = $this->end = $this->liste_plugins_actifs();
		$this->procure = $this->liste_plugins_procure();
	}

	/**
	 * Vérifier (et activer) les dépendances
	 *
	 * Pour chaque plugin qui sera actif, vérifie qu'il respecte
	 * ses dépendances.
	 *
	 * Si ce n'est pas le cas, le plugin n'est pas activé et le calcul
	 * de dépendances se refait sans lui. À un moment on a normalement
	 * rapidement une liste de plugins cohérents (au pire on ne boucle
	 * que 100 fois maximum - ce qui ne devrait jamais se produire).
	 *
	 * Des erreurs sont levées lorsqu'un plugin ne peut honorer son activation
	 * à cause d'un problème de dépendance. On peut les récupérer dans la
	 * propriété $err.
	 *
	 * @api
	 * @param array $todo
	 *     Ce qui est demandé de faire
	 *     Tableau identifiant du paquet => type d'action (on, off, up...)
	 * @return bool
	 *     False en cas d'erreur, true sinon
	 */
	function verifier_dependances($todo = null) {

		$this->start();

		// ajouter les actions
		if (!$this->actionner($todo)) {
			$this->log("! Todo en echec !");
			$this->log($decideur->err);
			return false;
		}

		// doit on reverifier les dependances ?
		// oui des qu'on modifie quelque chose...
		// attention a ne pas boucler infiniment !

		$supersticieux = 0;
		do {
			$try_again = 0;
			$supersticieux++;

			// verifier chaque dependance de chaque plugin a activer
			foreach ($this->end['i'] as $info) {
				if (!$this->verifier_dependances_plugin($info)) {
					$try_again = true;
				}
			}
			unset($id, $info);
			$this->log("--------> try_again: $try_again, supersticieux: $supersticieux");
		} while ($try_again > 0 and $supersticieux < 100); # and !count($this->err)

		$this->log("Fin !");
		$this->log("Ok: " . $this->ok);
		# $this->log($this->todo);

		return $this->ok;
	}


	/**
	 * Pour une description de paquet donnée, vérifie sa validité.
	 *
	 * Teste la version de SPIP, les librairies nécessitées, ses dépendances
	 * (et tente de les trouver et ajouter si elles ne sont pas là)
	 *
	 * Lorsqu'une dépendance est activée, on entre en récursion
	 * dans cette fonction avec la description de la dépendance
	 * 
	 * @param array $info
	 *     Description du paquet
	 * @param int $prof
	 *     Profondeur de récursion
	 * @return bool
	 *     false si erreur (dépendance non résolue, incompatibilité...), true sinon
	**/
	function verifier_dependances_plugin($info, $prof=0) {
		$this->log("- [$prof] verifier dependances " . $info['p']);
		$id = $info['i'];
		$err = false; // variable receptionnant parfois des erreurs
		$cache = array(); // cache des actions realisees dans ce tour

		// 1
		// tester la version de SPIP de notre paquet
		// si on ne valide pas, on retourne une erreur !
		// mais normalement, on ne devrait vraiment pas pouvoir tomber sur ce cas
		if (!svp_verifier_compatibilite_spip($info['compatibilite_spip'])) {
			$this->invalider($info);
			$this->erreur($id, _T('svp:message_incompatibilite_spip',array('plugin'=>$info['n'])));
			return false;
		}


		// 2
		// ajouter les librairies necessaires a notre paquet
		if (is_array($info['dl']) and count($info['dl'])) {
			foreach ($info['dl'] as $l) {
				// $l = array('nom' => 'x', 'lien' => 'url')
				$lib = $l['nom'];
				$this->log("## Necessite la librairie : " . $lib );

				// on verifie sa presence OU le fait qu'on pourra la telecharger
				if ($lib and !$this->est_presente_lib($lib)) {
					// peut on ecrire ?
					if (!is_writable(_DIR_LIB)) {
						$this->invalider($info);
						$this->erreur($id, _T('svp:message_erreur_ecriture_lib', array('plugin'=>$info['n'], 'lib_url'=>$l['lien'], 'lib'=>$lib)));
						$err = true;
					}
					// ajout, pour info
					// de la librairie dans la todo list
					else {
						$this->change(array(
							'i' => md5(serialize($l)),
							'p' => $lib,
							'n' => $lib,
							'v' => $l['lien'],
						), 'getlib');
						$this->log("- La librairie $lib sera a télécharger");
					}
				}
			}
			if ($err) {
				return false;
			}
		}

		// 3
		// Trouver les dependences aux necessites
		// et les activer au besoin
		if (is_array($info['dn']) and count($info['dn'])) {
			foreach ($info['dn'] as $n) {

				$p = $n['nom'];
				$v = $n['compatibilite'];

				if ($p == 'SPIP') {
					// c'est pas la que ça se fait !
					// ca ne devrait plus apparaitre comme dependence a un plugin.
				}

				// le core procure le paquet que l'on demande !
				elseif ((array_key_exists($p, $this->procure))
				  and (plugin_version_compatible($v, $this->procure[$p], 'spip'))) {
					// rien a faire...
					$this->log("-- est procure par le core ($p)");

				}

				// pas d'autre alternative qu'un vrai paquet a activer
				else {
					$this->log("-- verifier : $p");
					// nous sommes face a une dependance de plugin
					// on regarde s'il est present et a la bonne version
					// sinon on le cherche et on l'ajoute
					if ($ninfo = $this->sera_actif($p)
					and !$err = $this->en_erreur($ninfo['i'])
					and plugin_version_compatible($v, $ninfo['v'])) {
						// il est deja actif ou a activer, et tout est ok
						$this->log('-- dep OK pour '.$info['p'].' : '.$p);
					}
					// il faut le trouver et demander a l'activer
					else {
					
						// absent ou erreur ou pas compatible
						$etat = $err ? 'erreur' : ($ninfo ? 'conflit' : 'absent');
						// conflit signifie qu'il existe le prefixe actif, mais pas a la version demandee
						$this->log("Dedendance " . $p . " a resoudre ! ($etat)");

						switch ($etat) {
							// commencons par le plus simple :
							// en cas d'absence, on cherche ou est ce plugin !
							case 'absent':
								// on choisit par defaut le meilleur etat de plugin.
								// de preference dans les plugins locaux, sinon en distant.
								if (!$this->sera_off($p)
								and $new = $this->chercher_plugin_compatible($p, $v)
								and $this->verifier_dependances_plugin($new, ++$prof)) {
									// si le plugin existe localement et possede maj_version,
									// c'est que c'est peut etre une mise a jour + activation a faire
									// si le plugin
									// nouveau est local   => non
									// nouveau est distant => oui peut etre
									$cache[] = $new;
									$i = array();
									if (!$new['local']) {
										$i = $this->infos_courtes(array(
												'pl.prefixe=' . sql_quote($new['p']),
												'pa.maj_version=' . sql_quote($new['v'])
											), true);
									}
									if ($i and isset($i['p'][$new['p']]) and count($i['p'][$new['p']])) {
										// c'est une mise a jour
										$vieux = $i['p'][$new['p']][0];
										$this->change($vieux, 'upon');
										$this->log("-- update+active : $p");
									} else {
										// tout nouveau tout beau
										$this->change($new, $new['local'] ? 'on' : 'geton');
										if ($new['local']) {
											$this->log("-- nouveau present : $p");
										} else {
											$this->log("-- nouveau distant : $p");
										}
									}
									$this->add($new);
								} else {
									$this->log("-- !erreur : $p");
									// on ne trouve pas la dependance !
									$this->invalider($info);
									$this->erreur($id, $v ? _T('svp:message_dependance_plugin_version',array('plugin'=>$info['n'],'dependance'=>$p,'version'=>denormaliser_version($v))) : _T('svp:message_dependance_plugin',array('plugin'=>$info['n'],'dependance'=>$p)));
								}
								unset($new, $vieux);
								break;

							case 'erreur':
								break;

							// present, mais conflit de version
							// de deux choses l'une :
							// soit on trouve un paquet meilleur...
							// soit pas :)
							case 'conflit':
								$this->log("  conflit -> demande $v, present : " . $ninfo['v']);
								if (!$this->sera_off($p)
								and $new = $this->chercher_plugin_compatible($p, $v)
								and $this->verifier_dependances_plugin($new, ++$prof)) {
									// on connait le nouveau...
									$cache[] = $new;
									$this->remove($ninfo);
									$this->add($new);
									$this->change($ninfo,'up');
									$this->log("-- update : $p");
								} else {
									$this->log("-- !erreur : $p");
									// on ne trouve pas la dependance !
									$this->invalider($info);
									$this->erreur($id, $v ? _T('svp:message_dependance_plugin_version',array('plugin'=>$info['n'],'dependance'=>$p,'version'=>denormaliser_version($v))) : _T('svp:message_dependance_plugin',array('plugin'=>$info['n'],'dependance'=>$p)));
								}
								break;
						}

					} 
				}

				if ($this->sera_invalide($info['p'])) {
					break;
				}
			}
			unset($n, $v, $p, $ninfo, $present, $conflit, $erreur, $err);

			// si le plugin est devenu invalide...
			// on invalide toutes les actions qu'on vient de faire !
			if ($this->sera_invalide($info['p'])) {
				$this->log("> Purge du cache");
				foreach ($cache as $i) {
					$this->invalider($i);
				}
				return false;
			}
		}
		return true;
	}

	/**
	 * Retourne un tableau des différentes actions qui seront faites 
	 *
	 * @param string $quoi
	 *     Type de demande
	 *     - ask : les actions demandées
	 *     - changes : les actions en plus par rapport à ce qui était demandé
	 *     - todo : toutes les actions 
	 * @return array
	 *     Liste des actions (joliement traduites et expliquées)
	**/
	function presenter_actions($quoi) {
		$res = array();
		foreach ($this->$quoi as $id=>$info) {
			$res[] = _T('svp:message_action_'.$info['todo'], array(
				'plugin'=>$info['n'],
				'version'=>denormaliser_version($info['v']),
				'version_maj'=>denormaliser_version($info['maj'])));
		}
		return $res;
	}
}


/**
 * Gère la partie vérifier des formulaires utilisant le Décideur 
 *
 * @param array $a_actionner
 *     Tableau des actions par paquet (id_paquet => action)
 * @param array $erreurs
 *     Tableau d'erreurs de verifier (CVT)
 * @return bool
 *     true si tout va bien, false sinon (erreur pour trouver les dépendances, ...)
**/
function svp_decider_verifier_actions_demandees($a_actionner, &$erreurs) {
	$decideur = new Decideur;
	$decideur->erreur_sur_maj_introuvable = false;
	$decideur->verifier_dependances($a_actionner);

	if (!$decideur->ok) {
		$erreurs['decideur_erreurs'] = array();
		foreach ($decideur->err as $id=>$errs) {
			foreach($errs as $err) {
				$erreurs['decideur_erreurs'][] = $err;
			}
		}
		return false;
	}

	$erreurs['decideur_propositions'] = $decideur->presenter_actions('changes');
	$erreurs['decideur_demandes']     = $decideur->presenter_actions('ask');
	$erreurs['decideur_actions']      = $decideur->presenter_actions('todo');

	// On construit la liste des actions pour la passer au formulaire en hidden
	$todo = array();
	foreach ($decideur->todo as $_todo) {
		$todo[$_todo['i']] = $_todo['todo'];
	}
	set_request('_todo', serialize($todo));
	return true;
}
?>
where_compatible_spip.php000066600000003530152503074170011632 0ustar00<?php

/**
 * Gestion des recherches de plugins par version ou branche
 *
 * @plugin SVP pour SPIP
 * @license GPL
 * @package SPIP\SVP\Recherche
**/

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

/**
 * Construit le WHERE d'une requête SQL de selection des plugins ou paquets
 * compatibles avec une version ou une branche de spip.
 * 
 * Cette fonction est appelée par le critère {compatible_spip}
 * 
 * @used-by svp_compter()
 * @used-by critere_compatible_spip_dist()
 *
 * @param string $version
 *     Numéro de version de SPIP, tel que 2.0.8
 * @param string $table
 *     Table d'application ou son alias SQL
 * @param string $op
 *     Opérateur de comparaison, tel que '>' ou '='
 * @return string
 *     Expression where de la requête SQL
 */
function inc_where_compatible_spip($version='', $table, $op) {

	// le critere s'applique a une VERSION (1.9.2, 2.0.8, ...)
	if (count(explode('.', $version)) == 3) {
		$min = 'SUBSTRING_INDEX(' . $table . '.compatibilite_spip, \';\', 1)';
		$max = 'SUBSTRING_INDEX(' . $table . '.compatibilite_spip, \';\', -1)';

		$where = 'CASE WHEN '.$min.' = \'\'
	OR '.$min.' = \'[\'
	THEN \'1.9.0\' <= \''.$version.'\'
	ELSE TRIM(LEADING \'[\' FROM '.$min.') <= \''.$version.'\'
	END
AND
	CASE WHEN '.$max.' = \'\'
	OR '.$max.' = \']\'
	THEN \'99.99.99\' >= \''.$version.'\'
	WHEN '.$max.' = \')\'
	OR '.$max.' = \'[\'
	THEN \'99.99.99\' > \''.$version.'\'
	WHEN RIGHT('.$max.', 1) = \')\'
	OR RIGHT('.$max.', 1) = \'[\'
	THEN LEFT('.$max.', LENGTH('.$max.') - 1) > \''.$version.'\'
	ELSE LEFT('.$max.', LENGTH('.$max.') - 1) >= \''.$version.'\'
	END';
	}
	// le critere s'applique a une BRANCHE (1.9, 2.0, ...)
	elseif (count(explode('.', $version)) == 2) {
		$where = 'LOCATE(\''.$version.'\', '.$table.'.branches_spip) '.$op.' 0';
	}
	// le critere est vide ou mal specifie
	else {
		$where = '1=1';
	}

	return $where;
}
?>
pcltar.php000066600000374446152503074170006574 0ustar00<?php
// --------------------------------------------------------------------------------
// PhpConcept Library - Tar Module 1.3.1
// --------------------------------------------------------------------------------
// License GNU/GPL - Vincent Blavet - January 2003
// http://www.phpconcept.net
// --------------------------------------------------------------------------------
//
// Presentation :
//   PclTar is a library that allow you to create a GNU TAR + GNU ZIP archive,
//   to add files or directories, to extract all the archive or a part of it.
//   So far tests show that the files generated by PclTar are readable by
//   gzip tools and WinZip application.
//
// Description :
//   See readme.txt (English & Fran�ais) and http://www.phpconcept.net
//
// Warning :
//   This library and the associated files are non commercial, non professional
//   work.
//   It should not have unexpected results. However if any damage is caused by
//   this software the author can not be responsible.
//   The use of this software is at the risk of the user.
//
// --------------------------------------------------------------------------------

// ----- Look for double include
if (!defined("PCL_TAR"))
{
  define( "PCL_TAR", 1 );

  // ----- Configuration variable
  // Theses values may be changed by the user of PclTar library
  if (!isset($g_pcltar_lib_dir))
    $g_pcltar_lib_dir = find_in_path("lib/pcltar");

  // ----- Error codes
  //   -1 : Unable to open file in binary write mode
  //   -2 : Unable to open file in binary read mode
  //   -3 : Invalid parameters
  //   -4 : File does not exist
  //   -5 : Filename is too long (max. 99)
  //   -6 : Not a valid tar file
  //   -7 : Invalid extracted file size
  //   -8 : Unable to create directory
  //   -9 : Invalid archive extension
  //  -10 : Invalid archive format
  //  -11 : Unable to delete file (unlink)
  //  -12 : Unable to rename file (rename)
  //  -13 : Invalid header checksum


// --------------------------------------------------------------------------------
// ***** UNDER THIS LINE NOTHING NEEDS TO BE MODIFIED *****
// --------------------------------------------------------------------------------

  // ----- Global variables
  $g_pcltar_version = "1.3.1";

  // ----- Extract extension type (.php3/.php/...)
  $g_pcltar_extension = "php";

  // ----- Include other libraries
  // This library should be called by each script before the include of PhpZip
  // Library in order to limit the potential 'lib' directory path problem.
  if (!defined("PCLERROR_LIB"))
  {
    include($g_pcltar_lib_dir."/pclerror.lib.".$g_pcltar_extension);
  }
  if (!defined("PCLTRACE_LIB"))
  {
    include($g_pcltar_lib_dir."/pcltrace.lib.".$g_pcltar_extension);
  }

  // --------------------------------------------------------------------------------
  // Function : PclTarCreate()
  // Description :
  //   Creates a new archive with name $p_tarname containing the files and/or
  //   directories indicated in $p_list. If the tar filename extension is
  //   ".tar", the file will not be compressed. If it is ".tar.gz" or ".tgz"
  //   it will be a gzip compressed tar archive.
  //   If you want to use an other extension, you must indicate the mode in
  //   $p_mode ("tar" or "tgz").
  //   $p_add_dir and $p_remove_dir give you the ability to store a path
  //   which is not the real path of the files.
  // Parameters :
  //   $p_tarname : Name of an existing tar file
  //   $p_filelist : An array containing file or directory names, or
  //                 a string containing one filename or directory name, or
  //                 a string containing a list of filenames and/or directory
  //                 names separated by spaces.
  //   $p_mode : "tar" for normal tar archive, "tgz" for gzipped tar archive,
  //             if $p_mode is not specified, it will be determined by the extension.
  //   $p_add_dir : Path to add in the filename path archived
  //   $p_remove_dir : Path to remove in the filename path archived
  // Return Values :
  //   1 on success, or an error code (see table at the beginning).
  // --------------------------------------------------------------------------------
  function PclTarCreate($p_tarname, $p_filelist="", $p_mode="", $p_add_dir="", $p_remove_dir="")
  {
    TrFctStart(__FILE__, __LINE__, "PclTarCreate", "tar=$p_tarname, file='$p_filelist', mode=$p_mode, add_dir='$p_add_dir', remove_dir='$p_remove_dir'");
    $v_result=1;

    // ----- Look for default mode
    if (($p_mode == "") || (($p_mode!="tar") && ($p_mode!="tgz")))
    {
      // ----- Extract the tar format from the extension
      if (($p_mode = PclTarHandleExtension($p_tarname)) == "")
      {
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Trace
      TrFctMessage(__FILE__, __LINE__, 1, "Auto mode selected : found $p_mode");
    }

    // ----- Look if the $p_filelist is really an array
    if (is_array($p_filelist))
    {
      // ----- Call the create fct
      $v_result = PclTarHandleCreate($p_tarname, $p_filelist, $p_mode, $p_add_dir, $p_remove_dir);
    }

    // ----- Look if the $p_filelist is a string
    else if (is_string($p_filelist))
    {
      // ----- Create a list with the elements from the string
      $v_list = explode(" ", $p_filelist);

      // ----- Call the create fct
      $v_result = PclTarHandleCreate($p_tarname, $v_list, $p_mode, $p_add_dir, $p_remove_dir);
    }

    // ----- Invalid variable
    else
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid variable type p_filelist");
      $v_result = -3;
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarAdd()
  // Description :
  //   PLEASE DO NOT USE ANY MORE THIS FUNCTION. Use PclTarAddList().
  //
  //   This function is maintained only for compatibility reason
  //
  // Parameters :
  //   $p_tarname : Name of an existing tar file
  //   $p_filelist : An array containing file or directory names, or
  //                 a string containing one filename or directory name, or
  //                 a string containing a list of filenames and/or directory
  //                 names separated by spaces.
  // Return Values :
  //   1 on success,
  //   Or an error code (see list on top).
  // --------------------------------------------------------------------------------
  function PclTarAdd($p_tarname, $p_filelist)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarAdd", "tar=$p_tarname, file=$p_filelist");
    $v_result=1;
    $v_list_detail = array();

    // ----- Extract the tar format from the extension
    if (($p_mode = PclTarHandleExtension($p_tarname)) == "")
    {
      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Look if the $p_filelist is really an array
    if (is_array($p_filelist))
    {
      // ----- Call the add fct
      $v_result = PclTarHandleAppend($p_tarname, $p_filelist, $p_mode, $v_list_detail, "", "");
    }

    // ----- Look if the $p_filelist is a string
    else if (is_string($p_filelist))
    {
      // ----- Create a list with the elements from the string
      $v_list = explode(" ", $p_filelist);

      // ----- Call the add fct
      $v_result = PclTarHandleAppend($p_tarname, $v_list, $p_mode, $v_list_detail, "", "");
    }

    // ----- Invalid variable
    else
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid variable type p_filelist");
      $v_result = -3;
    }

    // ----- Cleaning
    unset($v_list_detail);

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarAddList()
  // Description :
  //   Add a list of files or directories ($p_filelist) in the tar archive $p_tarname.
  //   The list can be an array of file/directory names or a string with names
  //   separated by one space.
  //   $p_add_dir and $p_remove_dir will give the ability to memorize a path which is
  //   different from the real path of the file. This is usefull if you want to have PclTar
  //   running in any directory, and memorize relative path from an other directory.
  //   If $p_mode is not set it will be automatically computed from the $p_tarname
  //   extension (.tar, .tar.gz or .tgz).
  // Parameters :
  //   $p_tarname : Name of an existing tar file
  //   $p_filelist : An array containing file or directory names, or
  //                 a string containing one filename or directory name, or
  //                 a string containing a list of filenames and/or directory
  //                 names separated by spaces.
  //   $p_add_dir : Path to add in the filename path archived
  //   $p_remove_dir : Path to remove in the filename path archived
  //   $p_mode : 'tar' or 'tgz', if not set, will be determined by $p_tarname extension
  // Return Values :
  //   1 on success,
  //   Or an error code (see list on top).
  // --------------------------------------------------------------------------------
  function PclTarAddList($p_tarname, $p_filelist, $p_add_dir="", $p_remove_dir="", $p_mode="")
  {
    TrFctStart(__FILE__, __LINE__, "PclTarAddList", "tar=$p_tarname, file=$p_filelist, p_add_dir='$p_add_dir', p_remove_dir='$p_remove_dir', mode=$p_mode");
    $v_result=1;
    $p_list_detail = array();

    // ----- Extract the tar format from the extension
    if (($p_mode == "") || (($p_mode!="tar") && ($p_mode!="tgz")))
    {
      if (($p_mode = PclTarHandleExtension($p_tarname)) == "")
      {
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }
    }

    // ----- Look if the $p_filelist is really an array
    if (is_array($p_filelist))
    {
      // ----- Call the add fct
      $v_result = PclTarHandleAppend($p_tarname, $p_filelist, $p_mode, $p_list_detail, $p_add_dir, $p_remove_dir);
    }

    // ----- Look if the $p_filelist is a string
    else if (is_string($p_filelist))
    {
      // ----- Create a list with the elements from the string
      $v_list = explode(" ", $p_filelist);

      // ----- Call the add fct
      $v_result = PclTarHandleAppend($p_tarname, $v_list, $p_mode, $p_list_detail, $p_add_dir, $p_remove_dir);
    }

    // ----- Invalid variable
    else
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid variable type p_filelist");
      $v_result = -3;
    }

    // ----- Return
    if ($v_result != 1)
    {
      TrFctEnd(__FILE__, __LINE__, 0);
      return 0;
    }
    TrFctEnd(__FILE__, __LINE__, $p_list_detail);
    return $p_list_detail;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarList()
  // Description :
  //   Gives the list of all the files present in the tar archive $p_tarname.
  //   The list is the function result, it will be 0 on error.
  //   Depending on the $p_tarname extension (.tar, .tar.gz or .tgz) the
  //   function will determine the type of the archive.
  // Parameters :
  //   $p_tarname : Name of an existing tar file
  //   $p_mode : 'tar' or 'tgz', if not set, will be determined by $p_tarname extension
  // Return Values :
  //  0 on error (Use PclErrorCode() and PclErrorString() for more info)
  //  or
  //  An array containing file properties. Each file properties is an array of
  //  properties.
  //  The properties (array field names) are :
  //    filename, size, mode, uid, gid, mtime, typeflag, status
  //  Exemple : $v_list = PclTarList("my.tar");
  //            for ($i=0; $i<sizeof($v_list); $i++)
  //              echo "Filename :'".$v_list[$i][filename]."'<br>";
  // --------------------------------------------------------------------------------
  function PclTarList($p_tarname, $p_mode="")
  {
    TrFctStart(__FILE__, __LINE__, "PclTarList", "tar=$p_tarname, mode='$p_mode'");
    $v_result=1;

    // ----- Extract the tar format from the extension
    if (($p_mode == "") || (($p_mode!="tar") && ($p_mode!="tgz")))
    {
      if (($p_mode = PclTarHandleExtension($p_tarname)) == "")
      {
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return 0;
      }
    }

    // ----- Call the extracting fct
    $p_list = array();
    if (($v_result = PclTarHandleExtract($p_tarname, 0, $p_list, "list", "", $p_mode, "")) != 1)
    {
      unset($p_list);
      TrFctEnd(__FILE__, __LINE__, 0, PclErrorString());
      return(0);
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $p_list);
    return $p_list;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarExtract()
  // Description :
  //   Extract all the files present in the archive $p_tarname, in the directory
  //   $p_path. The relative path of the archived files are keep and become
  //   relative to $p_path.
  //   If a file with the same name already exists it will be replaced.
  //   If the path to the file does not exist, it will be created.
  //   Depending on the $p_tarname extension (.tar, .tar.gz or .tgz) the
  //   function will determine the type of the archive.
  // Parameters :
  //   $p_tarname : Name of an existing tar file.
  //   $p_path : Path where the files will be extracted. The files will use
  //             their memorized path from $p_path.
  //             If $p_path is "", files will be extracted in "./".
  //   $p_remove_path : Path to remove (from the file memorized path) while writing the
  //                    extracted files. If the path does not match the file path,
  //                    the file is extracted with its memorized path.
  //                    $p_path and $p_remove_path are commulative.
  //   $p_mode : 'tar' or 'tgz', if not set, will be determined by $p_tarname extension
  // Return Values :
  //   Same as PclTarList()
  // --------------------------------------------------------------------------------
  function PclTarExtract($p_tarname, $p_path="./", $p_remove_path="", $p_mode="")
  {
    TrFctStart(__FILE__, __LINE__, "PclTarExtract", "tar='$p_tarname', path='$p_path', remove_path='$p_remove_path', mode='$p_mode'");
    $v_result=1;

    // ----- Extract the tar format from the extension
    if (($p_mode == "") || (($p_mode!="tar") && ($p_mode!="tgz")))
    {
      if (($p_mode = PclTarHandleExtension($p_tarname)) == "")
      {
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return 0;
      }
    }

    // ----- Call the extracting fct
    if (($v_result = PclTarHandleExtract($p_tarname, 0, $p_list, "complete", $p_path, $v_tar_mode, $p_remove_path)) != 1)
    {
      TrFctEnd(__FILE__, __LINE__, 0, PclErrorString());
      return(0);
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $p_list);
    return $p_list;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarExtractList()
  // Description :
  //   Extract the files present in the archive $p_tarname and specified in
  //   $p_filelist, in the directory
  //   $p_path. The relative path of the archived files are keep and become
  //   relative to $p_path.
  //   If a directory is sp�cified in the list, all the files from this directory
  //   will be extracted.
  //   If a file with the same name already exists it will be replaced.
  //   If the path to the file does not exist, it will be created.
  //   Depending on the $p_tarname extension (.tar, .tar.gz or .tgz) the
  //   function will determine the type of the archive.
  // Parameters :
  //   $p_tarname : Name of an existing tar file
  //   $p_filelist : An array containing file or directory names, or
  //                 a string containing one filename or directory name, or
  //                 a string containing a list of filenames and/or directory
  //                 names separated by spaces.
  //   $p_path : Path where the files will be extracted. The files will use
  //             their memorized path from $p_path.
  //             If $p_path is "", files will be extracted in "./".
  //   $p_remove_path : Path to remove (from the file memorized path) while writing the
  //                    extracted files. If the path does not match the file path,
  //                    the file is extracted with its memorized path.
  //                    $p_path and $p_remove_path are commulative.
  //   $p_mode : 'tar' or 'tgz', if not set, will be determined by $p_tarname extension
  // Return Values :
  //   Same as PclTarList()
  // --------------------------------------------------------------------------------
  function PclTarExtractList($p_tarname, $p_filelist, $p_path="./", $p_remove_path="", $p_mode="")
  {
    TrFctStart(__FILE__, __LINE__, "PclTarExtractList", "tar=$p_tarname, list, path=$p_path, remove_path='$p_remove_path', mode='$p_mode'");
    $v_result=1;

    // ----- Extract the tar format from the extension
    if (($p_mode == "") || (($p_mode!="tar") && ($p_mode!="tgz")))
    {
      if (($p_mode = PclTarHandleExtension($p_tarname)) == "")
      {
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return 0;
      }
    }

    // ----- Look if the $p_filelist is really an array
    if (is_array($p_filelist))
    {
      // ----- Call the extracting fct
      if (($v_result = PclTarHandleExtract($p_tarname, $p_filelist, $p_list, "partial", $p_path, $v_tar_mode, $p_remove_path)) != 1)
      {
        TrFctEnd(__FILE__, __LINE__, 0, PclErrorString());
        return(0);
      }
    }

    // ----- Look if the $p_filelist is a string
    else if (is_string($p_filelist))
    {
      // ----- Create a list with the elements from the string
      $v_list = explode(" ", $p_filelist);

      // ----- Call the extracting fct
      if (($v_result = PclTarHandleExtract($p_tarname, $v_list, $p_list, "partial", $p_path, $v_tar_mode, $p_remove_path)) != 1)
      {
        TrFctEnd(__FILE__, __LINE__, 0, PclErrorString());
        return(0);
      }
    }

    // ----- Invalid variable
    else
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid variable type p_filelist");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return 0;
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $p_list);
    return $p_list;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarExtractIndex()
  // Description :
  //   Extract the files present in the archive $p_tarname and specified at
  //   the indexes in $p_index, in the directory
  //   $p_path. The relative path of the archived files are keep and become
  //   relative to $p_path.
  //   If a directory is specified in the list, the directory only is created. All
  //   the file stored in this archive for this directory
  //   are not extracted.
  //   If a file with the same name already exists it will be replaced.
  //   If the path to the file does not exist, it will be created.
  //   Depending on the $p_tarname extension (.tar, .tar.gz or .tgz) the
  //   function will determine the type of the archive.
  // Parameters :
  //   $p_tarname : Name of an existing tar file
  //   $p_index : A single index (integer) or a string of indexes of files to
  //              extract. The form of the string is "0,4-6,8-12" with only numbers
  //              and '-' for range or ',' to separate ranges. No spaces or ';'
  //              are allowed.
  //   $p_path : Path where the files will be extracted. The files will use
  //             their memorized path from $p_path.
  //             If $p_path is "", files will be extracted in "./".
  //   $p_remove_path : Path to remove (from the file memorized path) while writing the
  //                    extracted files. If the path does not match the file path,
  //                    the file is extracted with its memorized path.
  //                    $p_path and $p_remove_path are commulative.
  //   $p_mode : 'tar' or 'tgz', if not set, will be determined by $p_tarname extension
  // Return Values :
  //   Same as PclTarList()
  // --------------------------------------------------------------------------------
  function PclTarExtractIndex($p_tarname, $p_index, $p_path="./", $p_remove_path="", $p_mode="")
  {
    TrFctStart(__FILE__, __LINE__, "PclTarExtractIndex", "tar=$p_tarname, index='$p_index', path=$p_path, remove_path='$p_remove_path', mode='$p_mode'");
    $v_result=1;

    // ----- Extract the tar format from the extension
    if (($p_mode == "") || (($p_mode!="tar") && ($p_mode!="tgz")))
    {
      if (($p_mode = PclTarHandleExtension($p_tarname)) == "")
      {
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return 0;
      }
    }

    // ----- Look if the $p_index is really an integer
    if (is_integer($p_index))
    {
      // ----- Call the extracting fct
      if (($v_result = PclTarHandleExtractByIndexList($p_tarname, "$p_index", $p_list, $p_path, $p_remove_path, $v_tar_mode)) != 1)
      {
        TrFctEnd(__FILE__, __LINE__, 0, PclErrorString());
        return(0);
      }
    }

    // ----- Look if the $p_filelist is a string
    else if (is_string($p_index))
    {
      // ----- Call the extracting fct
      if (($v_result = PclTarHandleExtractByIndexList($p_tarname, $p_index, $p_list, $p_path, $p_remove_path, $v_tar_mode)) != 1)
      {
        TrFctEnd(__FILE__, __LINE__, 0, PclErrorString());
        return(0);
      }
    }

    // ----- Invalid variable
    else
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid variable type $p_index");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return 0;
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $p_list);
    return $p_list;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarDelete()
  // Description :
  //   This function deletes from the archive $p_tarname the files which are listed
  //   in $p_filelist. $p_filelist can be a string with file names separated by
  //   spaces, or an array containing the file names.
  // Parameters :
  //   $p_tarname : Name of an existing tar file
  //   $p_filelist : An array or a string containing file names to remove from the
  //                 archive.
  //   $p_mode : 'tar' or 'tgz', if not set, will be determined by $p_tarname extension
  // Return Values :
  //   List of the files which are kept in the archive (same format as PclTarList())
  // --------------------------------------------------------------------------------
  function PclTarDelete($p_tarname, $p_filelist, $p_mode="")
  {
    TrFctStart(__FILE__, __LINE__, "PclTarDelete", "tar='$p_tarname', list='$p_filelist', mode='$p_mode'");
    $v_result=1;

    // ----- Extract the tar format from the extension
    if (($p_mode == "") || (($p_mode!="tar") && ($p_mode!="tgz")))
    {
      if (($p_mode = PclTarHandleExtension($p_tarname)) == "")
      {
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return 0;
      }
    }

    // ----- Look if the $p_filelist is really an array
    if (is_array($p_filelist))
    {
      // ----- Call the extracting fct
      if (($v_result = PclTarHandleDelete($p_tarname, $p_filelist, $p_list, $p_mode)) != 1)
      {
        TrFctEnd(__FILE__, __LINE__, 0, PclErrorString());
        return(0);
      }
    }

    // ----- Look if the $p_filelist is a string
    else if (is_string($p_filelist))
    {
      // ----- Create a list with the elements from the string
      $v_list = explode(" ", $p_filelist);

      // ----- Call the extracting fct
      if (($v_result = PclTarHandleDelete($p_tarname, $v_list, $p_list, $p_mode)) != 1)
      {
        TrFctEnd(__FILE__, __LINE__, 0, PclErrorString());
        return(0);
      }
    }

    // ----- Invalid variable
    else
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid variable type p_filelist");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return 0;
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $p_list);
    return $p_list;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarUpdate()
  // Description :
  //   This function updates the files in $p_filelist which are already in the
  //   $p_tarname archive with an older last modified date. If the file does not
  //   exist, it is added at the end of the archive.
  // Parameters :
  //   $p_tarname : Name of an existing tar file
  //   $p_filelist : An array or a string containing file names to update from the
  //                 archive.
  //   $p_mode : 'tar' or 'tgz', if not set, will be determined by $p_tarname extension
  // Return Values :
  //   List of the files contained in the archive. The field status contains
  //   "updated", "not_updated", "added" or "ok" for the files not concerned.
  // --------------------------------------------------------------------------------
  function PclTarUpdate($p_tarname, $p_filelist, $p_mode="", $p_add_dir="", $p_remove_dir="")
  {
    TrFctStart(__FILE__, __LINE__, "PclTarUpdate", "tar='$p_tarname', list='$p_filelist', mode='$p_mode'");
    $v_result=1;

    // ----- Extract the tar format from the extension
    if (($p_mode == "") || (($p_mode!="tar") && ($p_mode!="tgz")))
    {
      if (($p_mode = PclTarHandleExtension($p_tarname)) == "")
      {
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return 0;
      }
    }

    // ----- Look if the $p_filelist is really an array
    if (is_array($p_filelist))
    {
      // ----- Call the extracting fct
      if (($v_result = PclTarHandleUpdate($p_tarname, $p_filelist, $p_list, $p_mode, $p_add_dir, $p_remove_dir)) != 1)
      {
        TrFctEnd(__FILE__, __LINE__, 0, PclErrorString());
        return(0);
      }
    }

    // ----- Look if the $p_filelist is a string
    else if (is_string($p_filelist))
    {
      // ----- Create a list with the elements from the string
      $v_list = explode(" ", $p_filelist);

      // ----- Call the extracting fct
      if (($v_result = PclTarHandleUpdate($p_tarname, $v_list, $p_list, $p_mode, $p_add_dir, $p_remove_dir)) != 1)
      {
        TrFctEnd(__FILE__, __LINE__, 0, PclErrorString());
        return(0);
      }
    }

    // ----- Invalid variable
    else
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid variable type p_filelist");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return 0;
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $p_list);
    return $p_list;
  }
  // --------------------------------------------------------------------------------


  // --------------------------------------------------------------------------------
  // Function : PclTarMerge()
  // Description :
  //   This function add the content of $p_tarname_add at the end of $p_tarname.
  // Parameters :
  //   $p_tarname : Name of an existing tar file
  //   $p_tarname_add : Name of an existing tar file taht will be added at the end
  //                    of $p_tarname.
  //   $p_mode : 'tar' or 'tgz', if not set, will be determined by $p_tarname extension
  //   $p_mode_add : 'tar' or 'tgz', if not set, will be determined by $p_tarname_add
  //                 extension
  // Return Values :
  //   List of the files contained in the archive. The field status contains
  //   "updated", "not_updated", "added" or "ok" for the files not concerned.
  // --------------------------------------------------------------------------------
  function PclTarMerge($p_tarname, $p_tarname_add, $p_mode="", $p_mode_add="")
  {
    TrFctStart(__FILE__, __LINE__, "PclTarMerge", "tar='$p_tarname', tar_add='$p_tarname_add', mode='$p_mode', mode_add='$p_mode_add'");
    $v_result=1;

    // ----- Check the parameters
    if (($p_tarname == "") || ($p_tarname_add == ""))
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid empty archive name");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Extract the tar format from the extension
    if (($p_mode == "") || (($p_mode!="tar") && ($p_mode!="tgz")))
    {
      if (($p_mode = PclTarHandleExtension($p_tarname)) == "")
      {
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return 0;
      }
    }
    if (($p_mode_add == "") || (($p_mode_add!="tar") && ($p_mode_add!="tgz")))
    {
      if (($p_mode_add = PclTarHandleExtension($p_tarname_add)) == "")
      {
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return 0;
      }
    }

    // ----- Clear filecache
    clearstatcache();

    // ----- Check the file size
    if ((!is_file($p_tarname)) ||
        (((($v_size = filesize($p_tarname)) % 512) != 0) && ($p_mode=="tar")))
    {
      // ----- Error log
      if (!is_file($p_tarname))
        PclErrorLog(-4, "Archive '$p_tarname' does not exist");
      else
        PclErrorLog(-6, "Archive '$p_tarname' has invalid size ".filesize($p_tarname)."(not a 512 block multiple)");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }
    if ((!is_file($p_tarname_add)) ||
        (((($v_size_add = filesize($p_tarname_add)) % 512) != 0) && ($p_mode_add=="tar")))
    {
      // ----- Error log
      if (!is_file($p_tarname_add))
        PclErrorLog(-4, "Archive '$p_tarname_add' does not exist");
      else
        PclErrorLog(-6, "Archive '$p_tarname_add' has invalid size ".filesize($p_tarname_add)."(not a 512 block multiple)");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Look for compressed archive
    if ($p_mode == "tgz")
    {
      // ----- Open the file in read mode
      if (($p_tar = @gzopen($p_tarname, "rb")) == 0)
      {
        // ----- Error log
        PclErrorLog(-2, "Unable to open file '$p_tarname' in binary read mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Open a temporary file in write mode
      $v_temp_tarname = uniqid("pcltar-").".tmp";
      TrFctMessage(__FILE__, __LINE__, 2, "Creating temporary archive file $v_temp_tarname");
      if (($v_temp_tar = @gzopen($v_temp_tarname, "wb")) == 0)
      {
        // ----- Close tar file
        gzclose($p_tar);

        // ----- Error log
        PclErrorLog(-1, "Unable to open file '$v_temp_tarname' in binary write mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Read the first 512 bytes block
      $v_buffer = gzread($p_tar, 512);

      // ----- Read the following blocks but not the last one
      if (!gzeof($p_tar))
      {
        TrFctMessage(__FILE__, __LINE__, 3, "More than one 512 block file");
        $i=1;

        // ----- Read new 512 block and write the already read
        do{
          // ----- Write the already read block
          $v_binary_data = pack("a512", "$v_buffer");
          gzputs($v_temp_tar, $v_binary_data);

          $i++;
          TrFctMessage(__FILE__, __LINE__, 3, "Reading block $i");

          // ----- Read next block
          $v_buffer = gzread($p_tar, 512);

        } while (!gzeof($p_tar));

        TrFctMessage(__FILE__, __LINE__, 3, "$i 512 bytes blocks");
      }
    }

    // ----- Look for uncompressed tar file
    else if ($p_mode=="tar")
    {
      // ----- Open the tar file
      if (($p_tar = fopen($p_tarname, "r+b")) == 0)
      {
        // ----- Error log
        PclErrorLog(-1, "Unable to open file '$p_tarname' in binary write mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Go to the beginning of last block
      TrFctMessage(__FILE__, __LINE__, 4, "Position before :".($p_mode=="tar"?ftell($p_tar):gztell($p_tar)));
      fseek($p_tar, $v_size-512);
      TrFctMessage(__FILE__, __LINE__, 4, "Position after :".($p_mode=="tar"?ftell($p_tar):gztell($p_tar)));
    }

    // ----- Look for unknown type
    else
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid tar mode $p_mode");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Look for type of archive to add
    if ($p_mode_add == "tgz")
    {
      TrFctMessage(__FILE__, __LINE__, 4, "Opening file $p_tarname_add");

      // ----- Open the file in read mode
      if (($p_tar_add = @gzopen($p_tarname_add, "rb")) == 0)
      {
        // ----- Error log
        PclErrorLog(-2, "Unable to open file '$p_tarname_add' in binary read mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Read the first 512 bytes block
      $v_buffer = gzread($p_tar_add, 512);

      // ----- Read the following blocks but not the last one
      if (!gzeof($p_tar_add))
      {
        TrFctMessage(__FILE__, __LINE__, 3, "More than one 512 block file");
        $i=1;

        // ----- Read new 512 block and write the already read
        do{
          // ----- Write the already read block
          $v_binary_data = pack("a512", "$v_buffer");
          if ($p_mode=="tar")
            fputs($p_tar, $v_binary_data);
          else
            gzputs($v_temp_tar, $v_binary_data);

          $i++;
          TrFctMessage(__FILE__, __LINE__, 3, "Reading block $i");

          // ----- Read next block
          $v_buffer = gzread($p_tar_add, 512);

        } while (!gzeof($p_tar_add));

        TrFctMessage(__FILE__, __LINE__, 3, "$i 512 bytes blocks");
      }

      // ----- Close the files
      gzclose($p_tar_add);
    }

    // ----- Look for uncompressed tar file
    else if ($p_mode=="tar")
    {
      // ----- Open the file in read mode
      if (($p_tar_add = @fopen($p_tarname_add, "rb")) == 0)
      {
        // ----- Error log
        PclErrorLog(-2, "Unable to open file '$p_tarname_add' in binary read mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Read the first 512 bytes block
      $v_buffer = fread($p_tar_add, 512);

      // ----- Read the following blocks but not the last one
      if (!feof($p_tar_add))
      {
        TrFctMessage(__FILE__, __LINE__, 3, "More than one 512 block file");
        $i=1;

        // ----- Read new 512 block and write the already read
        do{
          // ----- Write the already read block
          $v_binary_data = pack("a512", "$v_buffer");
          if ($p_mode=="tar")
            fputs($p_tar, $v_binary_data);
          else
            gzputs($v_temp_tar, $v_binary_data);

          $i++;
          TrFctMessage(__FILE__, __LINE__, 3, "Reading block $i");

          // ----- Read next block
          $v_buffer = fread($p_tar_add, 512);

        } while (!feof($p_tar_add));

        TrFctMessage(__FILE__, __LINE__, 3, "$i 512 bytes blocks");
      }

      // ----- Close the files
      fclose($p_tar_add);
    }

    // ----- Call the footer of the tar archive
    $v_result = PclTarHandleFooter($p_tar, $p_mode);

    // ----- Look for closing compressed archive
    if ($p_mode == "tgz")
    {
      // ----- Close the files
      gzclose($p_tar);
      gzclose($v_temp_tar);

      // ----- Unlink tar file
      if (!@unlink($p_tarname))
      {
        // ----- Error log
        PclErrorLog(-11, "Error while deleting archive name $p_tarname");
      }

      // ----- Rename tar file
      if (!@rename($v_temp_tarname, $p_tarname))
      {
        // ----- Error log
        PclErrorLog(-12, "Error while renaming temporary file $v_temp_tarname to archive name $p_tarname");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, $v_result);
      return $v_result;
    }

    // ----- Look for closing uncompressed tar file
    else if ($p_mode=="tar")
    {
      // ----- Close the tarfile
      fclose($p_tar);
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------


// --------------------------------------------------------------------------------
// ***** UNDER THIS LINE ARE DEFINED PRIVATE INTERNAL FUNCTIONS *****
// *****                                                        *****
// *****       THESES FUNCTIONS MUST NOT BE USED DIRECTLY       *****
// --------------------------------------------------------------------------------



  // --------------------------------------------------------------------------------
  // Function : PclTarHandleCreate()
  // Description :
  // Parameters :
  //   $p_tarname : Name of the tar file
  //   $p_list : An array containing the file or directory names to add in the tar
  //   $p_mode : "tar" for normal tar archive, "tgz" for gzipped tar archive
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleCreate($p_tarname, $p_list, $p_mode, $p_add_dir="", $p_remove_dir="")
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleCreate", "tar=$p_tarname, list, mode=$p_mode, add_dir='$p_add_dir', remove_dir='$p_remove_dir'");
    $v_result=1;
    $v_list_detail = array();

    // ----- Check the parameters
    if (($p_tarname == "") || (($p_mode != "tar") && ($p_mode != "tgz")))
    {
      // ----- Error log
      if ($p_tarname == "")
        PclErrorLog(-3, "Invalid empty archive name");
      else
        PclErrorLog(-3, "Unknown mode '$p_mode'");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Look for tar file
    if ($p_mode == "tar")
    {
      // ----- Open the tar file
      if (($p_tar = fopen($p_tarname, "wb")) == 0)
      {
        // ----- Error log
        PclErrorLog(-1, "Unable to open file [$p_tarname] in binary write mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Call the adding fct inside the tar
      if (($v_result = PclTarHandleAddList($p_tar, $p_list, $p_mode, $v_list_detail, $p_add_dir, $p_remove_dir)) == 1)
      {
        // ----- Call the footer of the tar archive
        $v_result = PclTarHandleFooter($p_tar, $p_mode);
      }

      // ----- Close the tarfile
      fclose($p_tar);
    }
    // ----- Look for tgz file
    else
    {
      // ----- Open the tar file
      if (($p_tar = @gzopen($p_tarname, "wb")) == 0)
      {
        // ----- Error log
        PclErrorLog(-1, "Unable to open file [$p_tarname] in binary write mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Call the adding fct inside the tar
      if (($v_result = PclTarHandleAddList($p_tar, $p_list, $p_mode, $v_list_detail, $p_add_dir, $p_remove_dir)) == 1)
      {
        // ----- Call the footer of the tar archive
        $v_result = PclTarHandleFooter($p_tar, $p_mode);
      }

      // ----- Close the tarfile
      gzclose($p_tar);
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleAppend()
  // Description :
  // Parameters :
  //   $p_tarname : Name of the tar file
  //   $p_list : An array containing the file or directory names to add in the tar
  //   $p_mode : "tar" for normal tar archive, "tgz" for gzipped tar archive
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleAppend($p_tarname, $p_list, $p_mode, &$p_list_detail, $p_add_dir, $p_remove_dir)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleAppend", "tar=$p_tarname, list, mode=$p_mode");
    $v_result=1;

    // ----- Check the parameters
    if ($p_tarname == "")
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid empty archive name");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    clearstatcache();

    // ----- Check the file size
    if ((!is_file($p_tarname)) ||
        (((($v_size = filesize($p_tarname)) % 512) != 0) && ($p_mode=="tar")))
    {
      // ----- Error log
      if (!is_file($p_tarname))
        PclErrorLog(-4, "Archive '$p_tarname' does not exist");
      else
        PclErrorLog(-6, "Archive '$p_tarname' has invalid size ".filesize($p_tarname)."(not a 512 block multiple)");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Look for compressed archive
    if ($p_mode == "tgz")
    {
      // ----- Open the file in read mode
      if (($p_tar = @gzopen($p_tarname, "rb")) == 0)
      {
        // ----- Error log
        PclErrorLog(-2, "Unable to open file '$p_tarname' in binary read mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Open a temporary file in write mode
      $v_temp_tarname = uniqid("pcltar-").".tmp";
      TrFctMessage(__FILE__, __LINE__, 2, "Creating temporary archive file $v_temp_tarname");
      if (($v_temp_tar = @gzopen($v_temp_tarname, "wb")) == 0)
      {
        // ----- Close tar file
        gzclose($p_tar);

        // ----- Error log
        PclErrorLog(-1, "Unable to open file '$v_temp_tarname' in binary write mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Read the first 512 bytes block
      $v_buffer = gzread($p_tar, 512);

      // ----- Read the following blocks but not the last one
      if (!gzeof($p_tar))
      {
        TrFctMessage(__FILE__, __LINE__, 3, "More than one 512 block file");
        $i=1;

        // ----- Read new 512 block and write the already read
        do{
          // ----- Write the already read block
          $v_binary_data = pack("a512", "$v_buffer");
          gzputs($v_temp_tar, $v_binary_data);

          $i++;
          TrFctMessage(__FILE__, __LINE__, 3, "Reading block $i");

          // ----- Read next block
          $v_buffer = gzread($p_tar, 512);

        } while (!gzeof($p_tar));

        TrFctMessage(__FILE__, __LINE__, 3, "$i 512 bytes blocks");
      }

      // ----- Call the adding fct inside the tar
      if (($v_result = PclTarHandleAddList($v_temp_tar, $p_list, $p_mode, $p_list_detail, $p_add_dir, $p_remove_dir)) == 1)
      {
        // ----- Call the footer of the tar archive
        $v_result = PclTarHandleFooter($v_temp_tar, $p_mode);
      }

      // ----- Close the files
      gzclose($p_tar);
      gzclose($v_temp_tar);

      // ----- Unlink tar file
      if (!@unlink($p_tarname))
      {
        // ----- Error log
        PclErrorLog(-11, "Error while deleting archive name $p_tarname");
      }

      // ----- Rename tar file
      if (!@rename($v_temp_tarname, $p_tarname))
      {
        // ----- Error log
        PclErrorLog(-12, "Error while renaming temporary file $v_temp_tarname to archive name $p_tarname");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, $v_result);
      return $v_result;
    }

    // ----- Look for uncompressed tar file
    else if ($p_mode=="tar")
    {
      // ----- Open the tar file
      if (($p_tar = fopen($p_tarname, "r+b")) == 0)
      {
        // ----- Error log
        PclErrorLog(-1, "Unable to open file '$p_tarname' in binary write mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Go to the beginning of last block
      TrFctMessage(__FILE__, __LINE__, 4, "Position before :".($p_mode=="tar"?ftell($p_tar):gztell($p_tar)));
      fseek($p_tar, $v_size-512);
      TrFctMessage(__FILE__, __LINE__, 4, "Position after :".($p_mode=="tar"?ftell($p_tar):gztell($p_tar)));

      // ----- Call the adding fct inside the tar
      if (($v_result = PclTarHandleAddList($p_tar, $p_list, $p_mode, $p_list_detail, $p_add_dir, $p_remove_dir)) == 1)
      {
        // ----- Call the footer of the tar archive
        $v_result = PclTarHandleFooter($p_tar, $p_mode);
      }

      // ----- Close the tarfile
      fclose($p_tar);
    }

    // ----- Look for unknown type
    else
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid tar mode $p_mode");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleAddList()
  // Description :
  //   $p_add_dir and $p_remove_dir will give the ability to memorize a path which is
  //   different from the real path of the file. This is usefull if you want to have PclTar
  //   running in any directory, and memorize relative path from an other directory.
  // Parameters :
  //   $p_tar : File descriptor of the tar archive
  //   $p_list : An array containing the file or directory names to add in the tar
  //   $p_mode : "tar" for normal tar archive, "tgz" for gzipped tar archive
  //   $p_list_detail : list of added files with their properties (specially the status field)
  //   $p_add_dir : Path to add in the filename path archived
  //   $p_remove_dir : Path to remove in the filename path archived
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleAddList($p_tar, $p_list, $p_mode, &$p_list_detail, $p_add_dir, $p_remove_dir)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleAddList", "tar='$p_tar', list, mode='$p_mode', add_dir='$p_add_dir', remove_dir='$p_remove_dir'");
    $v_result=1;
    $v_header = array();

    // ----- Recuperate the current number of elt in list
    $v_nb = sizeof($p_list_detail);

    // ----- Check the parameters
    if ($p_tar == 0)
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid file descriptor in file ".__FILE__.", line ".__LINE__);

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Check the arguments
    if (sizeof($p_list) == 0)
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid file list parameter (invalid or empty list)");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Loop on the files
    for ($j=0; ($j<count($p_list)) && ($v_result==1); $j++)
    {
      // ----- Recuperate the filename
      $p_filename = $p_list[$j];

      TrFctMessage(__FILE__, __LINE__, 2, "Looking for file [$p_filename]");

      // ----- Skip empty file names
      if ($p_filename == "")
      {
        TrFctMessage(__FILE__, __LINE__, 2, "Skip empty filename");
        continue;
      }

      // ----- Check the filename
      if (!file_exists($p_filename))
      {
        // ----- Error log
        TrFctMessage(__FILE__, __LINE__, 2, "File '$p_filename' does not exists");
        PclErrorLog(-4, "File '$p_filename' does not exists");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Check the path length
      if (strlen($p_filename) > 99)
      {
        // ----- Error log
        PclErrorLog(-5, "File name is too long (max. 99) : '$p_filename'");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      TrFctMessage(__FILE__, __LINE__, 4, "File position before header =".($p_mode=="tar"?ftell($p_tar):gztell($p_tar)));

      // ----- Add the file
      if (($v_result = PclTarHandleAddFile($p_tar, $p_filename, $p_mode, $v_header, $p_add_dir, $p_remove_dir)) != 1)
      {
        // ----- Return status
        TrFctEnd(__FILE__, __LINE__, $v_result);
        return $v_result;
      }

      // ----- Store the file infos
      $p_list_detail[$v_nb++] = $v_header;

      // ----- Look for directory
      if (is_dir($p_filename))
      {
        TrFctMessage(__FILE__, __LINE__, 2, "$p_filename is a directory");

        // ----- Look for path
        if ($p_filename != ".")
          $v_path = $p_filename."/";
        else
          $v_path = "";

        // ----- Read the directory for files and sub-directories
        $p_hdir = opendir($p_filename);
        $p_hitem = readdir($p_hdir); // '.' directory
        $p_hitem = readdir($p_hdir); // '..' directory
        while ($p_hitem = readdir($p_hdir))
        {
          // ----- Look for a file
          if (is_file($v_path.$p_hitem))
          {
            TrFctMessage(__FILE__, __LINE__, 4, "Add the file '".$v_path.$p_hitem."'");

            // ----- Add the file
            if (($v_result = PclTarHandleAddFile($p_tar, $v_path.$p_hitem, $p_mode, $v_header, $p_add_dir, $p_remove_dir)) != 1)
            {
              // ----- Return status
              TrFctEnd(__FILE__, __LINE__, $v_result);
              return $v_result;
            }

            // ----- Store the file infos
            $p_list_detail[$v_nb++] = $v_header;
          }

          // ----- Recursive call to PclTarHandleAddFile()
          else
          {
            TrFctMessage(__FILE__, __LINE__, 4, "'".$v_path.$p_hitem."' is a directory");

            // ----- Need an array as parameter
            $p_temp_list[0] = $v_path.$p_hitem;
            $v_result = PclTarHandleAddList($p_tar, $p_temp_list, $p_mode, $p_list_detail, $p_add_dir, $p_remove_dir);
          }
        }

        // ----- Free memory for the recursive loop
        unset($p_temp_list);
        unset($p_hdir);
        unset($p_hitem);
      }
      else
      {
        TrFctMessage(__FILE__, __LINE__, 4, "File position after blocks =".($p_mode=="tar"?ftell($p_tar):gztell($p_tar)));
      }
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleAddFile()
  // Description :
  // Parameters :
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleAddFile($p_tar, $p_filename, $p_mode, &$p_header, $p_add_dir, $p_remove_dir)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleAddFile", "tar='$p_tar', filename='$p_filename', p_mode='$p_mode', add_dir='$p_add_dir', remove_dir='$p_remove_dir'");
    $v_result=1;

    // ----- Check the parameters
    if ($p_tar == 0)
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid file descriptor in file ".__FILE__.", line ".__LINE__);

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Skip empty file names
    if ($p_filename == "")
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid file list parameter (invalid or empty list)");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Calculate the stored filename
    $v_stored_filename = $p_filename;
    if ($p_remove_dir != "")
    {
      if (substr($p_remove_dir, -1) != '/')
        $p_remove_dir .= "/";

      if ((substr($p_filename, 0, 2) == "./") || (substr($p_remove_dir, 0, 2) == "./"))
      {
        if ((substr($p_filename, 0, 2) == "./") && (substr($p_remove_dir, 0, 2) != "./"))
          $p_remove_dir = "./".$p_remove_dir;
        if ((substr($p_filename, 0, 2) != "./") && (substr($p_remove_dir, 0, 2) == "./"))
          $p_remove_dir = substr($p_remove_dir, 2);
      }

      if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir)
      {
        $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
        TrFctMessage(__FILE__, __LINE__, 3, "Remove path '$p_remove_dir' in file '$p_filename' = '$v_stored_filename'");
      }
    }
    if ($p_add_dir != "")
    {
      if (substr($p_add_dir, -1) == "/")
        $v_stored_filename = $p_add_dir.$v_stored_filename;
      else
        $v_stored_filename = $p_add_dir."/".$v_stored_filename;
      TrFctMessage(__FILE__, __LINE__, 3, "Add path '$p_add_dir' in file '$p_filename' = '$v_stored_filename'");
    }

    // ----- Check the path length
    if (strlen($v_stored_filename) > 99)
    {
      // ----- Error log
      PclErrorLog(-5, "Stored file name is too long (max. 99) : '$v_stored_filename'");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Look for a file
    if (is_file($p_filename))
    {
      // ----- Open the source file
      if (($v_file = fopen($p_filename, "rb")) == 0)
      {
        // ----- Error log
        PclErrorLog(-2, "Unable to open file '$p_filename' in binary read mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Call the header generation
      if (($v_result = PclTarHandleHeader($p_tar, $p_filename, $p_mode, $p_header, $v_stored_filename)) != 1)
      {
        // ----- Return status
        TrFctEnd(__FILE__, __LINE__, $v_result);
        return $v_result;
      }

      TrFctMessage(__FILE__, __LINE__, 4, "File position after header =".($p_mode=="tar"?ftell($p_tar):gztell($p_tar)));

      // ----- Read the file by 512 octets blocks
      $i=0;
      while (($v_buffer = fread($v_file, 512)) != "")
      {
        $v_binary_data = pack("a512", "$v_buffer");
        if ($p_mode == "tar")
          fputs($p_tar, $v_binary_data);
        else
          gzputs($p_tar, $v_binary_data);
        $i++;
      }
      TrFctMessage(__FILE__, __LINE__, 2, "$i 512 bytes blocks");

      // ----- Close the file
      fclose($v_file);

      TrFctMessage(__FILE__, __LINE__, 4, "File position after blocks =".($p_mode=="tar"?ftell($p_tar):gztell($p_tar)));
    }

    // ----- Look for a directory
    else
    {
      // ----- Call the header generation
      if (($v_result = PclTarHandleHeader($p_tar, $p_filename, $p_mode, $p_header, $v_stored_filename)) != 1)
      {
        // ----- Return status
        TrFctEnd(__FILE__, __LINE__, $v_result);
        return $v_result;
      }

      TrFctMessage(__FILE__, __LINE__, 4, "File position after header =".($p_mode=="tar"?ftell($p_tar):gztell($p_tar)));
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleHeader()
  // Description :
  //   This function creates in the TAR $p_tar, the TAR header for the file
  //   $p_filename.
  //
  //   1. The informations needed to compose the header are recuperated and formatted
  //   2. Two binary strings are composed for the first part of the header, before
  //      and after checksum field.
  //   3. The checksum is calculated from the two binary strings
  //   4. The header is write in the tar file (first binary string, binary string
  //      for checksum and last binary string).
  // Parameters :
  //   $p_tar : a valid file descriptor, opened in write mode,
  //   $p_filename : The name of the file the header is for,
  //   $p_mode : The mode of the archive ("tar" or "tgz").
  //   $p_header : A pointer to a array where will be set the file properties
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleHeader($p_tar, $p_filename, $p_mode, &$p_header, $p_stored_filename)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleHeader", "tar=$p_tar, file='$p_filename', mode='$p_mode', stored_filename='$p_stored_filename'");
    $v_result=1;

    // ----- Check the parameters
    if (($p_tar == 0) || ($p_filename == ""))
    {
      // ----- Error log
      PclErrorLog(-3, "Invalid file descriptor in file ".__FILE__.", line ".__LINE__);

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Filename (reduce the path of stored name)
    if ($p_stored_filename == "")
      $p_stored_filename = $p_filename;
    $v_reduce_filename = PclTarHandlePathReduction($p_stored_filename);
    TrFctMessage(__FILE__, __LINE__, 2, "Filename (reduced) '$v_reduce_filename', strlen ".strlen($v_reduce_filename));

    // ----- Get file info
    $v_info = stat($p_filename);
    $v_uid = sprintf("%6s ", DecOct($v_info[4]));
    $v_gid = sprintf("%6s ", DecOct($v_info[5]));
    TrFctMessage(__FILE__, __LINE__, 3, "uid=$v_uid, gid=$v_gid");
    $v_perms = sprintf("%6s ", DecOct(fileperms($p_filename)));
    TrFctMessage(__FILE__, __LINE__, 3, "file permissions $v_perms");

    // ----- File mtime
    $v_mtime_data = filemtime($p_filename);
    TrFctMessage(__FILE__, __LINE__, 2, "File mtime : $v_mtime_data");
    $v_mtime = sprintf("%11s", DecOct($v_mtime_data));

    // ----- File typeflag
    // '0' or '\0' is the code for regular file
    // '5' is directory
    if (is_dir($p_filename))
    {
      $v_typeflag = "5";
      $v_size = 0;
    }
    else
    {
      $v_typeflag = "";

      // ----- Get the file size
      clearstatcache();
      $v_size = filesize($p_filename);
    }

    TrFctMessage(__FILE__, __LINE__, 2, "File size : $v_size");
    $v_size = sprintf("%11s ", DecOct($v_size));

    TrFctMessage(__FILE__, __LINE__, 2, "File typeflag : $v_typeflag");

    // ----- Linkname
    $v_linkname = "";

    // ----- Magic
    $v_magic = "";

    // ----- Version
    $v_version = "";

    // ----- uname
    $v_uname = "";

    // ----- gname
    $v_gname = "";

    // ----- devmajor
    $v_devmajor = "";

    // ----- devminor
    $v_devminor = "";

    // ----- prefix
    $v_prefix = "";

    // ----- Compose the binary string of the header in two parts arround the checksum position
    $v_binary_data_first = pack("a100a8a8a8a12A12", $v_reduce_filename, $v_perms, $v_uid, $v_gid, $v_size, $v_mtime);
    $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", $v_typeflag, $v_linkname, $v_magic, $v_version, $v_uname, $v_gname, $v_devmajor, $v_devminor, $v_prefix, "");

    // ----- Calculate the checksum
    $v_checksum = 0;
    // ..... First part of the header
    for ($i=0; $i<148; $i++)
    {
      $v_checksum += ord(substr($v_binary_data_first,$i,1));
    }
    // ..... Ignore the checksum value and replace it by ' ' (space)
    for ($i=148; $i<156; $i++)
    {
      $v_checksum += ord(' ');
    }
    // ..... Last part of the header
    for ($i=156, $j=0; $i<512; $i++, $j++)
    {
      $v_checksum += ord(substr($v_binary_data_last,$j,1));
    }
    TrFctMessage(__FILE__, __LINE__, 3, "Calculated checksum : $v_checksum");

    // ----- Write the first 148 bytes of the header in the archive
    if ($p_mode == "tar")
      fputs($p_tar, $v_binary_data_first, 148);
    else
      gzputs($p_tar, $v_binary_data_first, 148);

    // ----- Write the calculated checksum
    $v_checksum = sprintf("%6s ", DecOct($v_checksum));
    $v_binary_data = pack("a8", $v_checksum);
    if ($p_mode == "tar")
      fputs($p_tar, $v_binary_data, 8);
    else
      gzputs($p_tar, $v_binary_data, 8);

    // ----- Write the last 356 bytes of the header in the archive
    if ($p_mode == "tar")
      fputs($p_tar, $v_binary_data_last, 356);
    else
      gzputs($p_tar, $v_binary_data_last, 356);

    // ----- Set the properties in the header "structure"
    $p_header[filename] = $v_reduce_filename;
    $p_header[mode] = $v_perms;
    $p_header[uid] = $v_uid;
    $p_header[gid] = $v_gid;
    $p_header[size] = $v_size;
    $p_header[mtime] = $v_mtime;
    $p_header[typeflag] = $v_typeflag;
    $p_header[status] = "added";

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleFooter()
  // Description :
  // Parameters :
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleFooter($p_tar, $p_mode)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleFooter", "tar='$p_tar', p_mode=$p_mode");
    $v_result=1;

    // ----- Write the last 0 filled block for end of archive
    $v_binary_data = pack("a512", "");
    if ($p_mode == "tar")
      fputs($p_tar, $v_binary_data);
    else
      gzputs($p_tar, $v_binary_data);

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleExtract()
  // Description :
  // Parameters :
  //   $p_tarname : Filename of the tar (or tgz) archive
  //   $p_file_list : An array which contains the list of files to extract, this
  //                  array may be empty when $p_mode is 'complete'
  //   $p_list_detail : An array where will be placed the properties of  each extracted/listed file
  //   $p_mode : 'complete' will extract all files from the archive,
  //             'partial' will look for files in $p_file_list
  //             'list' will only list the files from the archive without any extract
  //   $p_path : Path to add while writing the extracted files
  //   $p_tar_mode : 'tar' for GNU TAR archive, 'tgz' for compressed archive
  //   $p_remove_path : Path to remove (from the file memorized path) while writing the
  //                    extracted files. If the path does not match the file path,
  //                    the file is extracted with its memorized path.
  //                    $p_remove_path does not apply to 'list' mode.
  //                    $p_path and $p_remove_path are commulative.
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleExtract($p_tarname, $p_file_list, &$p_list_detail, $p_mode, $p_path, $p_tar_mode, $p_remove_path)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleExtract", "archive='$p_tarname', list, mode=$p_mode, path=$p_path, tar_mode=$p_tar_mode, remove_path='$p_remove_path'");
    $v_result=1;
    $v_nb = 0;
    $v_extract_all = TRUE;
    $v_listing = FALSE;

    // ----- Check the path
    if (($p_path == "") || ((substr($p_path, 0, 1) != "/") && (substr($p_path, 0, 3) != "../")))
      $p_path = "./".$p_path;

    // ----- Look for path to remove format (should end by /)
    if (($p_remove_path != "") && (substr($p_remove_path, -1) != '/'))
    {
      $p_remove_path .= '/';
    }
    $p_remove_path_size = strlen($p_remove_path);

    // ----- Study the mode
    switch ($p_mode) {
      case "complete" :
        // ----- Flag extract of all files
        $v_extract_all = TRUE;
        $v_listing = FALSE;
      break;
      case "partial" :
          // ----- Flag extract of specific files
          $v_extract_all = FALSE;
          $v_listing = FALSE;
      break;
      case "list" :
          // ----- Flag list of all files
          $v_extract_all = FALSE;
          $v_listing = TRUE;
      break;
      default :
        // ----- Error log
        PclErrorLog(-3, "Invalid extract mode ($p_mode)");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
    }

    // ----- Open the tar file
    if ($p_tar_mode == "tar")
    {
      TrFctMessage(__FILE__, __LINE__, 3, "Open file in binary read mode");
      $v_tar = fopen($p_tarname, "rb");
    }
    else
    {
      TrFctMessage(__FILE__, __LINE__, 3, "Open file in gzip binary read mode");
      $v_tar = @gzopen($p_tarname, "rb");
    }

    // ----- Check that the archive is open
    if ($v_tar == 0)
    {
      // ----- Error log
      PclErrorLog(-2, "Unable to open archive '$p_tarname' in binary read mode");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Read the blocks
    While (!($v_end_of_file = ($p_tar_mode == "tar"?feof($v_tar):gzeof($v_tar))))
    {
      TrFctMessage(__FILE__, __LINE__, 3, "Looking for next header ...");

      // ----- Clear cache of file infos
      clearstatcache();

      // ----- Reset extract tag
      $v_extract_file = FALSE;
      $v_extraction_stopped = 0;

      // ----- Read the 512 bytes header
      if ($p_tar_mode == "tar")
        $v_binary_data = fread($v_tar, 512);
      else
        $v_binary_data = gzread($v_tar, 512);

      // ----- Read the header properties
      if (($v_result = PclTarHandleReadHeader($v_binary_data, $v_header)) != 1)
      {
        // ----- Close the archive file
        if ($p_tar_mode == "tar")
          fclose($v_tar);
        else
          gzclose($v_tar);

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, $v_result);
        return $v_result;
      }

      // ----- Look for empty blocks to skip
      if ($v_header[filename] == "")
      {
        TrFctMessage(__FILE__, __LINE__, 2, "Empty block found. End of archive ?");
        continue;
      }

      TrFctMessage(__FILE__, __LINE__, 2, "Found file '$v_header[filename]', size '$v_header[size]'");

      // ----- Look for partial extract
      if ((!$v_extract_all) && (is_array($p_file_list)))
      {
        TrFctMessage(__FILE__, __LINE__, 2, "Look if the file '$v_header[filename]' need to be extracted");

        // ----- By default no unzip if the file is not found
        $v_extract_file = FALSE;

        // ----- Look into the file list
        for ($i=0; $i<sizeof($p_file_list); $i++)
        {
          TrFctMessage(__FILE__, __LINE__, 2, "Compare archived file '$v_header[filename]' from asked list file '".$p_file_list[$i]."'");

          // ----- Look if it is a directory
          if (substr($p_file_list[$i], -1) == "/")
          {
            TrFctMessage(__FILE__, __LINE__, 3, "Compare file '$v_header[filename]' with directory '$p_file_list[$i]'");

            // ----- Look if the directory is in the filename path
            if ((strlen($v_header[filename]) > strlen($p_file_list[$i])) && (substr($v_header[filename], 0, strlen($p_file_list[$i])) == $p_file_list[$i]))
            {
              // ----- The file is in the directory, so extract it
              TrFctMessage(__FILE__, __LINE__, 2, "File '$v_header[filename]' is in directory '$p_file_list[$i]' : extract it");
              $v_extract_file = TRUE;

              // ----- End of loop
              break;
            }
          }

          // ----- It is a file, so compare the file names
          else if ($p_file_list[$i] == $v_header[filename])
          {
            // ----- File found
            TrFctMessage(__FILE__, __LINE__, 2, "File '$v_header[filename]' should be extracted");
            $v_extract_file = TRUE;

            // ----- End of loop
            break;
          }
        }

        // ----- Trace
        if (!$v_extract_file)
        {
          TrFctMessage(__FILE__, __LINE__, 2, "File '$v_header[filename]' should not be extracted");
        }
      }
      else
      {
        // ----- All files need to be extracted
        $v_extract_file = TRUE;
      }

      // ----- Look if this file need to be extracted
      if (($v_extract_file) && (!$v_listing))
      {
        // ----- Look for path to remove
        if (($p_remove_path != "")
            && (substr($v_header[filename], 0, $p_remove_path_size) == $p_remove_path))
        {
          TrFctMessage(__FILE__, __LINE__, 3, "Found path '$p_remove_path' to remove in file '$v_header[filename]'");
          // ----- Remove the path
          $v_header[filename] = substr($v_header[filename], $p_remove_path_size);
          TrFctMessage(__FILE__, __LINE__, 3, "Reslting file is '$v_header[filename]'");
        }

        // ----- Add the path to the file
        if (($p_path != "./") && ($p_path != "/"))
        {
          // ----- Look for the path end '/'
          while (substr($p_path, -1) == "/")
          {
            TrFctMessage(__FILE__, __LINE__, 3, "Destination path [$p_path] ends by '/'");
            $p_path = substr($p_path, 0, strlen($p_path)-1);
            TrFctMessage(__FILE__, __LINE__, 3, "Modified to [$p_path]");
          }

          // ----- Add the path
          if (substr($v_header[filename], 0, 1) == "/")
              $v_header[filename] = $p_path.$v_header[filename];
          else
            $v_header[filename] = $p_path."/".$v_header[filename];
        }

        // ----- Trace
        TrFctMessage(__FILE__, __LINE__, 2, "Extracting file (with path) '$v_header[filename]', size '$v_header[size]'");

        // ----- Check that the file does not exists
        if (file_exists($v_header[filename]))
        {
          TrFctMessage(__FILE__, __LINE__, 2, "File '$v_header[filename]' already exists");

          // ----- Look if file is a directory
          if (is_dir($v_header[filename]))
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Existing file '$v_header[filename]' is a directory");

            // ----- Change the file status
            $v_header[status] = "already_a_directory";

            // ----- Skip the extract
            $v_extraction_stopped = 1;
            $v_extract_file = 0;
          }
          // ----- Look if file is write protected
          else if (!is_writeable($v_header[filename]))
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Existing file '$v_header[filename]' is write protected");

            // ----- Change the file status
            $v_header[status] = "write_protected";

            // ----- Skip the extract
            $v_extraction_stopped = 1;
            $v_extract_file = 0;
          }
          // ----- Look if the extracted file is older
          else if (filemtime($v_header[filename]) > $v_header[mtime])
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Existing file '$v_header[filename]' is newer (".date("l dS of F Y h:i:s A", filemtime($v_header[filename])).") than the extracted file (".date("l dS of F Y h:i:s A", $v_header[mtime]).")");

            // ----- Change the file status
            $v_header[status] = "newer_exist";

            // ----- Skip the extract
            $v_extraction_stopped = 1;
            $v_extract_file = 0;
          }
        }

        // ----- Check the directory availability and create it if necessary
        else
        {
          if ($v_header[typeflag]=="5")
            $v_dir_to_check = $v_header[filename];
          else if (!strstr($v_header[filename], "/"))
            $v_dir_to_check = "";
          else
            $v_dir_to_check = dirname($v_header[filename]);

          if (($v_result = PclTarHandlerDirCheck($v_dir_to_check)) != 1)
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Unable to create path for '$v_header[filename]'");

            // ----- Change the file status
            $v_header[status] = "path_creation_fail";

            // ----- Skip the extract
            $v_extraction_stopped = 1;
            $v_extract_file = 0;
          }
        }

        // ----- Do the extraction
        if (($v_extract_file) && ($v_header[typeflag]!="5"))
        {
          // ----- Open the destination file in write mode
          if (($v_dest_file = @fopen($v_header[filename], "wb")) == 0)
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Error while opening '$v_header[filename]' in write binary mode");

            // ----- Change the file status
            $v_header[status] = "write_error";

            // ----- Jump to next file
            TrFctMessage(__FILE__, __LINE__, 2, "Jump to next file");
            if ($p_tar_mode == "tar")
              fseek($v_tar, ftell($v_tar)+(ceil(($v_header[size]/512))*512));
            else
              gzseek($v_tar, gztell($v_tar)+(ceil(($v_header[size]/512))*512));
          }
          else
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Start extraction of '$v_header[filename]'");

            // ----- Read data
            $n = floor($v_header[size]/512);
            for ($i=0; $i<$n; $i++)
            {
              TrFctMessage(__FILE__, __LINE__, 3, "Read complete 512 bytes block number ".($i+1));
              if ($p_tar_mode == "tar")
                $v_content = fread($v_tar, 512);
              else
                $v_content = gzread($v_tar, 512);
              fwrite($v_dest_file, $v_content, 512);
            }
            if (($v_header[size] % 512) != 0)
            {
              TrFctMessage(__FILE__, __LINE__, 3, "Read last ".($v_header[size] % 512)." bytes in a 512 block");
              if ($p_tar_mode == "tar")
                $v_content = fread($v_tar, 512);
              else
                $v_content = gzread($v_tar, 512);
              fwrite($v_dest_file, $v_content, ($v_header[size] % 512));
            }

            // ----- Close the destination file
            fclose($v_dest_file);

            // ----- Change the file mode, mtime
            touch($v_header[filename], $v_header[mtime]);
            //chmod($v_header[filename], DecOct($v_header[mode]));
          }

          // ----- Check the file size
          clearstatcache();
          if (filesize($v_header[filename]) != $v_header[size])
          {
            // ----- Close the archive file
            if ($p_tar_mode == "tar")
              fclose($v_tar);
            else
              gzclose($v_tar);

            // ----- Error log
            PclErrorLog(-7, "Extracted file '$v_header[filename]' does not have the correct file size '".filesize($v_filename)."' ('$v_header[size]' expected). Archive may be corrupted.");

            // ----- Return
            TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
            return PclErrorCode();
          }

          // ----- Trace
          TrFctMessage(__FILE__, __LINE__, 2, "Extraction done");
        }

        else
        {
          TrFctMessage(__FILE__, __LINE__, 2, "Extraction of file '$v_header[filename]' skipped.");

          // ----- Jump to next file
          TrFctMessage(__FILE__, __LINE__, 2, "Jump to next file");
          if ($p_tar_mode == "tar")
            fseek($v_tar, ftell($v_tar)+(ceil(($v_header[size]/512))*512));
          else
            gzseek($v_tar, gztell($v_tar)+(ceil(($v_header[size]/512))*512));
        }
      }

      // ----- Look for file that is not to be unzipped
      else
      {
        // ----- Trace
        TrFctMessage(__FILE__, __LINE__, 2, "Jump file '$v_header[filename]'");
        TrFctMessage(__FILE__, __LINE__, 4, "Position avant jump [".($p_tar_mode=="tar"?ftell($v_tar):gztell($v_tar))."]");

        // ----- Jump to next file
        if ($p_tar_mode == "tar")
          fseek($v_tar, ($p_tar_mode=="tar"?ftell($v_tar):gztell($v_tar))+(ceil(($v_header[size]/512))*512));
        else
          gzseek($v_tar, gztell($v_tar)+(ceil(($v_header[size]/512))*512));

        TrFctMessage(__FILE__, __LINE__, 4, "Position apr�s jump [".($p_tar_mode=="tar"?ftell($v_tar):gztell($v_tar))."]");
      }

      if ($p_tar_mode == "tar")
        $v_end_of_file = feof($v_tar);
      else
        $v_end_of_file = gzeof($v_tar);

      // ----- File name and properties are logged if listing mode or file is extracted
      if ($v_listing || $v_extract_file || $v_extraction_stopped)
      {
        TrFctMessage(__FILE__, __LINE__, 2, "Memorize info about file '$v_header[filename]'");

        // ----- Log extracted files
        if (($v_file_dir = dirname($v_header[filename])) == $v_header[filename])
          $v_file_dir = "";
        if ((substr($v_header[filename], 0, 1) == "/") && ($v_file_dir == ""))
          $v_file_dir = "/";

        // ----- Add the array describing the file into the list
        $p_list_detail[$v_nb] = $v_header;

        // ----- Increment
        $v_nb++;
      }
    }

    // ----- Close the tarfile
    if ($p_tar_mode == "tar")
      fclose($v_tar);
    else
      gzclose($v_tar);

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleExtractByIndexList()
  // Description :
  //   Extract the files which are at the indexes specified. If the 'file' at the
  //   index is a directory, the directory only is created, not all the files stored
  //   for that directory.
  // Parameters :
  //   $p_index_string : String of indexes of files to extract. The form of the
  //                     string is "0,4-6,8-12" with only numbers and '-' for
  //                     for range, and ',' to separate ranges. No spaces or ';'
  //                     are allowed.
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleExtractByIndexList($p_tarname, $p_index_string, &$p_list_detail, $p_path, $p_remove_path, $p_tar_mode)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleExtractByIndexList", "archive='$p_tarname', index_string='$p_index_string', list, path=$p_path, remove_path='$p_remove_path', tar_mode=$p_tar_mode");
    $v_result=1;
    $v_nb = 0;

    // ----- TBC : I should check the string by a regexp

    // ----- Check the path
    if (($p_path == "") || ((substr($p_path, 0, 1) != "/") && (substr($p_path, 0, 3) != "../") && (substr($p_path, 0, 2) != "./")))
      $p_path = "./".$p_path;

    // ----- Look for path to remove format (should end by /)
    if (($p_remove_path != "") && (substr($p_remove_path, -1) != '/'))
    {
      $p_remove_path .= '/';
    }
    $p_remove_path_size = strlen($p_remove_path);

    // ----- Open the tar file
    if ($p_tar_mode == "tar")
    {
      TrFctMessage(__FILE__, __LINE__, 3, "Open file in binary read mode");
      $v_tar = @fopen($p_tarname, "rb");
    }
    else
    {
      TrFctMessage(__FILE__, __LINE__, 3, "Open file in gzip binary read mode");
      $v_tar = @gzopen($p_tarname, "rb");
    }

    // ----- Check that the archive is open
    if ($v_tar == 0)
    {
      // ----- Error log
      PclErrorLog(-2, "Unable to open archive '$p_tarname' in binary read mode");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Manipulate the index list
    $v_list = explode(",", $p_index_string);
    sort($v_list);

    // ----- Loop on the index list
    $v_index=0;
    for ($i=0; ($i<sizeof($v_list)) && ($v_result); $i++)
    {
      TrFctMessage(__FILE__, __LINE__, 3, "Looking for index part '$v_list[$i]'");

      // ----- Extract range
      $v_index_list = explode("-", $v_list[$i]);
      $v_size_index_list = sizeof($v_index_list);
      if ($v_size_index_list == 1)
      {
        TrFctMessage(__FILE__, __LINE__, 3, "Only one index '$v_index_list[0]'");

        // ----- Do the extraction
        $v_result = PclTarHandleExtractByIndex($v_tar, $v_index, $v_index_list[0], $v_index_list[0], $p_list_detail, $p_path, $p_remove_path, $p_tar_mode);
      }
      else if ($v_size_index_list == 2)
      {
        TrFctMessage(__FILE__, __LINE__, 3, "Two indexes '$v_index_list[0]' and '$v_index_list[1]'");

        // ----- Do the extraction
        $v_result = PclTarHandleExtractByIndex($v_tar, $v_index, $v_index_list[0], $v_index_list[1], $p_list_detail, $p_path, $p_remove_path, $p_tar_mode);
      }
    }

    // ----- Close the tarfile
    if ($p_tar_mode == "tar")
      fclose($v_tar);
    else
      gzclose($v_tar);

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleExtractByIndex()
  // Description :
  // Parameters :
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleExtractByIndex($p_tar, &$p_index_current, $p_index_start, $p_index_stop, &$p_list_detail, $p_path, $p_remove_path, $p_tar_mode)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleExtractByIndex", "archive_descr='$p_tar', index_current=$p_index_current, index_start='$p_index_start', index_stop='$p_index_stop', list, path=$p_path, remove_path='$p_remove_path', tar_mode=$p_tar_mode");
    $v_result=1;
    $v_nb = 0;

    // TBC : I should replace all $v_tar by $p_tar in this function ....
    $v_tar = $p_tar;

    // ----- Look the number of elements already in $p_list_detail
    $v_nb = sizeof($p_list_detail);

    // ----- Read the blocks
    While (!($v_end_of_file = ($p_tar_mode == "tar"?feof($v_tar):gzeof($v_tar))))
    {
      TrFctMessage(__FILE__, __LINE__, 3, "Looking for next file ...");
      TrFctMessage(__FILE__, __LINE__, 3, "Index current=$p_index_current, range=[$p_index_start, $p_index_stop])");

      if ($p_index_current > $p_index_stop)
      {
        TrFctMessage(__FILE__, __LINE__, 2, "Stop extraction, past stop index");
        break;
      }

      // ----- Clear cache of file infos
      clearstatcache();

      // ----- Reset extract tag
      $v_extract_file = FALSE;
      $v_extraction_stopped = 0;

      // ----- Read the 512 bytes header
      if ($p_tar_mode == "tar")
        $v_binary_data = fread($v_tar, 512);
      else
        $v_binary_data = gzread($v_tar, 512);

      // ----- Read the header properties
      if (($v_result = PclTarHandleReadHeader($v_binary_data, $v_header)) != 1)
      {
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, $v_result);
        return $v_result;
      }

      // ----- Look for empty blocks to skip
      if ($v_header[filename] == "")
      {
        TrFctMessage(__FILE__, __LINE__, 2, "Empty block found. End of archive ?");
        continue;
      }

      TrFctMessage(__FILE__, __LINE__, 2, "Found file '$v_header[filename]', size '$v_header[size]'");

      // ----- Look if file is in the range to be extracted
      if (($p_index_current >= $p_index_start) && ($p_index_current <= $p_index_stop))
      {
        TrFctMessage(__FILE__, __LINE__, 2, "File '$v_header[filename]' is in the range to be extracted");
        $v_extract_file = TRUE;
      }
      else
      {
        TrFctMessage(__FILE__, __LINE__, 2, "File '$v_header[filename]' is out of the range");
        $v_extract_file = FALSE;
      }

      // ----- Look if this file need to be extracted
      if ($v_extract_file)
      {
        if (($v_result = PclTarHandleExtractFile($v_tar, $v_header, $p_path, $p_remove_path, $p_tar_mode)) != 1)
        {
          // ----- Return
          TrFctEnd(__FILE__, __LINE__, $v_result);
          return $v_result;
        }
      }

      // ----- Look for file that is not to be extracted
      else
      {
        // ----- Trace
        TrFctMessage(__FILE__, __LINE__, 2, "Jump file '$v_header[filename]'");
        TrFctMessage(__FILE__, __LINE__, 4, "Position avant jump [".($p_tar_mode=="tar"?ftell($v_tar):gztell($v_tar))."]");

        // ----- Jump to next file
        if ($p_tar_mode == "tar")
          fseek($v_tar, ($p_tar_mode=="tar"?ftell($v_tar):gztell($v_tar))+(ceil(($v_header[size]/512))*512));
        else
          gzseek($v_tar, gztell($v_tar)+(ceil(($v_header[size]/512))*512));

        TrFctMessage(__FILE__, __LINE__, 4, "Position apr�s jump [".($p_tar_mode=="tar"?ftell($v_tar):gztell($v_tar))."]");
      }

      if ($p_tar_mode == "tar")
        $v_end_of_file = feof($v_tar);
      else
        $v_end_of_file = gzeof($v_tar);

      // ----- File name and properties are logged if listing mode or file is extracted
      if ($v_extract_file)
      {
        TrFctMessage(__FILE__, __LINE__, 2, "Memorize info about file '$v_header[filename]'");

        // ----- Log extracted files
        if (($v_file_dir = dirname($v_header[filename])) == $v_header[filename])
          $v_file_dir = "";
        if ((substr($v_header[filename], 0, 1) == "/") && ($v_file_dir == ""))
          $v_file_dir = "/";

        // ----- Add the array describing the file into the list
        $p_list_detail[$v_nb] = $v_header;

        // ----- Increment
        $v_nb++;
      }

      // ----- Increment the current file index
      $p_index_current++;
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleExtractFile()
  // Description :
  // Parameters :
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleExtractFile($p_tar, &$v_header, $p_path, $p_remove_path, $p_tar_mode)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleExtractFile", "archive_descr='$p_tar', path=$p_path, remove_path='$p_remove_path', tar_mode=$p_tar_mode");
    $v_result=1;

    // TBC : I should replace all $v_tar by $p_tar in this function ....
    $v_tar = $p_tar;
    $v_extract_file = 1;

    $p_remove_path_size = strlen($p_remove_path);

        // ----- Look for path to remove
        if (($p_remove_path != "")
            && (substr($v_header[filename], 0, $p_remove_path_size) == $p_remove_path))
        {
          TrFctMessage(__FILE__, __LINE__, 3, "Found path '$p_remove_path' to remove in file '$v_header[filename]'");
          // ----- Remove the path
          $v_header[filename] = substr($v_header[filename], $p_remove_path_size);
          TrFctMessage(__FILE__, __LINE__, 3, "Resulting file is '$v_header[filename]'");
        }

        // ----- Add the path to the file
        if (($p_path != "./") && ($p_path != "/"))
        {
          // ----- Look for the path end '/'
          while (substr($p_path, -1) == "/")
          {
            TrFctMessage(__FILE__, __LINE__, 3, "Destination path [$p_path] ends by '/'");
            $p_path = substr($p_path, 0, strlen($p_path)-1);
            TrFctMessage(__FILE__, __LINE__, 3, "Modified to [$p_path]");
          }

          // ----- Add the path
          if (substr($v_header[filename], 0, 1) == "/")
              $v_header[filename] = $p_path.$v_header[filename];
          else
            $v_header[filename] = $p_path."/".$v_header[filename];
        }

        // ----- Trace
        TrFctMessage(__FILE__, __LINE__, 2, "Extracting file (with path) '$v_header[filename]', size '$v_header[size]'");

        // ----- Check that the file does not exists
        if (file_exists($v_header[filename]))
        {
          TrFctMessage(__FILE__, __LINE__, 2, "File '$v_header[filename]' already exists");

          // ----- Look if file is a directory
          if (is_dir($v_header[filename]))
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Existing file '$v_header[filename]' is a directory");

            // ----- Change the file status
            $v_header[status] = "already_a_directory";

            // ----- Skip the extract
            $v_extraction_stopped = 1;
            $v_extract_file = 0;
          }
          // ----- Look if file is write protected
          else if (!is_writeable($v_header[filename]))
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Existing file '$v_header[filename]' is write protected");

            // ----- Change the file status
            $v_header[status] = "write_protected";

            // ----- Skip the extract
            $v_extraction_stopped = 1;
            $v_extract_file = 0;
          }
          // ----- Look if the extracted file is older
          else if (filemtime($v_header[filename]) > $v_header[mtime])
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Existing file '$v_header[filename]' is newer (".date("l dS of F Y h:i:s A", filemtime($v_header[filename])).") than the extracted file (".date("l dS of F Y h:i:s A", $v_header[mtime]).")");

            // ----- Change the file status
            $v_header[status] = "newer_exist";

            // ----- Skip the extract
            $v_extraction_stopped = 1;
            $v_extract_file = 0;
          }
        }

        // ----- Check the directory availability and create it if necessary
        else
        {
          if ($v_header[typeflag]=="5")
            $v_dir_to_check = $v_header[filename];
          else if (!strstr($v_header[filename], "/"))
            $v_dir_to_check = "";
          else
            $v_dir_to_check = dirname($v_header[filename]);

          if (($v_result = PclTarHandlerDirCheck($v_dir_to_check)) != 1)
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Unable to create path for '$v_header[filename]'");

            // ----- Change the file status
            $v_header[status] = "path_creation_fail";

            // ----- Skip the extract
            $v_extraction_stopped = 1;
            $v_extract_file = 0;
          }
        }

        // ----- Do the real bytes extraction (if not a directory)
        if (($v_extract_file) && ($v_header[typeflag]!="5"))
        {
          // ----- Open the destination file in write mode
          if (($v_dest_file = @fopen($v_header[filename], "wb")) == 0)
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Error while opening '$v_header[filename]' in write binary mode");

            // ----- Change the file status
            $v_header[status] = "write_error";

            // ----- Jump to next file
            TrFctMessage(__FILE__, __LINE__, 2, "Jump to next file");
            if ($p_tar_mode == "tar")
              fseek($v_tar, ftell($v_tar)+(ceil(($v_header[size]/512))*512));
            else
              gzseek($v_tar, gztell($v_tar)+(ceil(($v_header[size]/512))*512));
          }
          else
          {
            TrFctMessage(__FILE__, __LINE__, 2, "Start extraction of '$v_header[filename]'");

            // ----- Read data
            $n = floor($v_header[size]/512);
            for ($i=0; $i<$n; $i++)
            {
              TrFctMessage(__FILE__, __LINE__, 3, "Read complete 512 bytes block number ".($i+1));
              if ($p_tar_mode == "tar")
                $v_content = fread($v_tar, 512);
              else
                $v_content = gzread($v_tar, 512);
              fwrite($v_dest_file, $v_content, 512);
            }
            if (($v_header[size] % 512) != 0)
            {
              TrFctMessage(__FILE__, __LINE__, 3, "Read last ".($v_header[size] % 512)." bytes in a 512 block");
              if ($p_tar_mode == "tar")
                $v_content = fread($v_tar, 512);
              else
                $v_content = gzread($v_tar, 512);
              fwrite($v_dest_file, $v_content, ($v_header[size] % 512));
            }

            // ----- Close the destination file
            fclose($v_dest_file);

            // ----- Change the file mode, mtime
            touch($v_header[filename], $v_header[mtime]);
            //chmod($v_header[filename], DecOct($v_header[mode]));
          }

          // ----- Check the file size
          clearstatcache();
          if (filesize($v_header[filename]) != $v_header[size])
          {
            // ----- Error log
            PclErrorLog(-7, "Extracted file '$v_header[filename]' does not have the correct file size '".filesize($v_filename)."' ('$v_header[size]' expected). Archive may be corrupted.");

            // ----- Return
            TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
            return PclErrorCode();
          }

          // ----- Trace
          TrFctMessage(__FILE__, __LINE__, 2, "Extraction done");
        }
        else
        {
          TrFctMessage(__FILE__, __LINE__, 2, "Extraction of file '$v_header[filename]' skipped.");

          // ----- Jump to next file
          TrFctMessage(__FILE__, __LINE__, 2, "Jump to next file");
          if ($p_tar_mode == "tar")
            fseek($v_tar, ftell($v_tar)+(ceil(($v_header[size]/512))*512));
          else
            gzseek($v_tar, gztell($v_tar)+(ceil(($v_header[size]/512))*512));
        }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleDelete()
  // Description :
  // Parameters :
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleDelete($p_tarname, $p_file_list, &$p_list_detail, $p_tar_mode)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleDelete", "archive='$p_tarname', list, tar_mode=$p_tar_mode");
    $v_result=1;
    $v_nb=0;

    // ----- Look for regular tar file
    if ($p_tar_mode == "tar")
    {
      // ----- Open file
      TrFctMessage(__FILE__, __LINE__, 3, "Open file in binary read mode");
      if (($v_tar = @fopen($p_tarname, "rb")) == 0)
      {
        // ----- Error log
        PclErrorLog(-2, "Unable to open file '$p_tarname' in binary read mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Open a temporary file in write mode
      $v_temp_tarname = uniqid("pcltar-").".tmp";
      TrFctMessage(__FILE__, __LINE__, 2, "Creating temporary archive file $v_temp_tarname");
      if (($v_temp_tar = @fopen($v_temp_tarname, "wb")) == 0)
      {
        // ----- Close tar file
        fclose($v_tar);

        // ----- Error log
        PclErrorLog(-1, "Unable to open file '$v_temp_tarname' in binary write mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }
    }

    // ----- Look for compressed tar file
    else
    {
      // ----- Open the file in read mode
      TrFctMessage(__FILE__, __LINE__, 3, "Open file in gzip binary read mode");
      if (($v_tar = @gzopen($p_tarname, "rb")) == 0)
      {
        // ----- Error log
        PclErrorLog(-2, "Unable to open file '$p_tarname' in binary read mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Open a temporary file in write mode
      $v_temp_tarname = uniqid("pcltar-").".tmp";
      TrFctMessage(__FILE__, __LINE__, 2, "Creating temporary archive file $v_temp_tarname");
      if (($v_temp_tar = @gzopen($v_temp_tarname, "wb")) == 0)
      {
        // ----- Close tar file
        gzclose($v_tar);

        // ----- Error log
        PclErrorLog(-1, "Unable to open file '$v_temp_tarname' in binary write mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }
    }

    // ----- Read the blocks
    While (!($v_end_of_file = ($p_tar_mode == "tar"?feof($v_tar):gzeof($v_tar))))
    {
      TrFctMessage(__FILE__, __LINE__, 3, "Looking for next header ...");

      // ----- Clear cache of file infos
      clearstatcache();

      // ----- Reset delete tag
      $v_delete_file = FALSE;

      // ----- Read the first 512 block header
      if ($p_tar_mode == "tar")
        $v_binary_data = fread($v_tar, 512);
      else
        $v_binary_data = gzread($v_tar, 512);

      // ----- Read the header properties
      if (($v_result = PclTarHandleReadHeader($v_binary_data, $v_header)) != 1)
      {
        // ----- Close the archive file
        if ($p_tar_mode == "tar")
        {
          fclose($v_tar);
          fclose($v_temp_tar);
        }
        else
        {
          gzclose($v_tar);
          gzclose($v_temp_tar);
        }
        @unlink($v_temp_tarname);

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, $v_result);
        return $v_result;
      }

      // ----- Look for empty blocks to skip
      if ($v_header[filename] == "")
      {
        TrFctMessage(__FILE__, __LINE__, 2, "Empty block found. End of archive ?");
        continue;
      }

      TrFctMessage(__FILE__, __LINE__, 2, "Found file '$v_header[filename]', size '$v_header[size]'");

      // ----- Look for filenames to delete
      for ($i=0, $v_delete_file=FALSE; ($i<sizeof($p_file_list)) && (!$v_delete_file); $i++)
      {
        // ----- Compare the file names
//        if ($p_file_list[$i] == $v_header[filename])
        if (($v_len = strcmp($p_file_list[$i], $v_header[filename])) <= 0)
        {
          if ($v_len==0)
          {
            TrFctMessage(__FILE__, __LINE__, 3, "Found that '$v_header[filename]' need to be deleted");
            $v_delete_file = TRUE;
          }
          else
          {
            TrFctMessage(__FILE__, __LINE__, 3, "Look if '$v_header[filename]' is a file in $p_file_list[$i]");
            if (substr($v_header[filename], strlen($p_file_list[$i]), 1) == "/")
            {
              TrFctMessage(__FILE__, __LINE__, 3, "'$v_header[filename]' is a file in $p_file_list[$i]");
              $v_delete_file = TRUE;
            }
          }
        }
      }

      // ----- Copy files that do not need to be deleted
      if (!$v_delete_file)
      {
        TrFctMessage(__FILE__, __LINE__, 2, "Keep file '$v_header[filename]'");

        // ----- Write the file header
        if ($p_tar_mode == "tar")
        {
          fputs($v_temp_tar, $v_binary_data, 512);
        }
        else
        {
          gzputs($v_temp_tar, $v_binary_data, 512);
        }

        // ----- Write the file data
        $n = ceil($v_header[size]/512);
        for ($i=0; $i<$n; $i++)
        {
          TrFctMessage(__FILE__, __LINE__, 3, "Read complete 512 bytes block number ".($i+1));
          if ($p_tar_mode == "tar")
          {
            $v_content = fread($v_tar, 512);
            fwrite($v_temp_tar, $v_content, 512);
          }
          else
          {
            $v_content = gzread($v_tar, 512);
            gzwrite($v_temp_tar, $v_content, 512);
          }
        }

        // ----- File name and properties are logged if listing mode or file is extracted
        TrFctMessage(__FILE__, __LINE__, 2, "Memorize info about file '$v_header[filename]'");

        // ----- Add the array describing the file into the list
        $p_list_detail[$v_nb] = $v_header;
        $p_list_detail[$v_nb][status] = "ok";

        // ----- Increment
        $v_nb++;
      }

      // ----- Look for file that is to be deleted
      else
      {
        // ----- Trace
        TrFctMessage(__FILE__, __LINE__, 2, "Start deletion of '$v_header[filename]'");
        TrFctMessage(__FILE__, __LINE__, 4, "Position avant jump [".($p_tar_mode=="tar"?ftell($v_tar):gztell($v_tar))."]");

        // ----- Jump to next file
        if ($p_tar_mode == "tar")
          fseek($v_tar, ftell($v_tar)+(ceil(($v_header[size]/512))*512));
        else
          gzseek($v_tar, gztell($v_tar)+(ceil(($v_header[size]/512))*512));

        TrFctMessage(__FILE__, __LINE__, 4, "Position apr�s jump [".($p_tar_mode=="tar"?ftell($v_tar):gztell($v_tar))."]");
      }

      // ----- Look for end of file
      if ($p_tar_mode == "tar")
        $v_end_of_file = feof($v_tar);
      else
        $v_end_of_file = gzeof($v_tar);
    }

    // ----- Write the last empty buffer
    PclTarHandleFooter($v_temp_tar, $p_tar_mode);

    // ----- Close the tarfile
    if ($p_tar_mode == "tar")
    {
      fclose($v_tar);
      fclose($v_temp_tar);
    }
    else
    {
      gzclose($v_tar);
      gzclose($v_temp_tar);
    }

    // ----- Unlink tar file
    if (!@unlink($p_tarname))
    {
      // ----- Error log
      PclErrorLog(-11, "Error while deleting archive name $p_tarname");
    }


    // ----- Rename tar file
    if (!@rename($v_temp_tarname, $p_tarname))
    {
      // ----- Error log
      PclErrorLog(-12, "Error while renaming temporary file $v_temp_tarname to archive name $p_tarname");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleUpdate()
  // Description :
  // Parameters :
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleUpdate($p_tarname, $p_file_list, &$p_list_detail, $p_tar_mode, $p_add_dir, $p_remove_dir)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleUpdate", "archive='$p_tarname', list, tar_mode=$p_tar_mode");
    $v_result=1;
    $v_nb=0;
    $v_found_list = array();

    // ----- Look for regular tar file
    if ($p_tar_mode == "tar")
    {
      // ----- Open file
      TrFctMessage(__FILE__, __LINE__, 3, "Open file in binary read mode");
      if (($v_tar = @fopen($p_tarname, "rb")) == 0)
      {
        // ----- Error log
        PclErrorLog(-2, "Unable to open file '$p_tarname' in binary read mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Open a temporary file in write mode
      $v_temp_tarname = uniqid("pcltar-").".tmp";
      TrFctMessage(__FILE__, __LINE__, 2, "Creating temporary archive file $v_temp_tarname");
      if (($v_temp_tar = @fopen($v_temp_tarname, "wb")) == 0)
      {
        // ----- Close tar file
        fclose($v_tar);

        // ----- Error log
        PclErrorLog(-1, "Unable to open file '$v_temp_tarname' in binary write mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }
    }

    // ----- Look for compressed tar file
    else
    {
      // ----- Open the file in read mode
      TrFctMessage(__FILE__, __LINE__, 3, "Open file in gzip binary read mode");
      if (($v_tar = @gzopen($p_tarname, "rb")) == 0)
      {
        // ----- Error log
        PclErrorLog(-2, "Unable to open file '$p_tarname' in binary read mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }

      // ----- Open a temporary file in write mode
      $v_temp_tarname = uniqid("pcltar-").".tmp";
      TrFctMessage(__FILE__, __LINE__, 2, "Creating temporary archive file $v_temp_tarname");
      if (($v_temp_tar = @gzopen($v_temp_tarname, "wb")) == 0)
      {
        // ----- Close tar file
        gzclose($v_tar);

        // ----- Error log
        PclErrorLog(-1, "Unable to open file '$v_temp_tarname' in binary write mode");

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
        return PclErrorCode();
      }
    }

    // ----- Prepare the list of files
    for ($i=0; $i<sizeof($p_file_list); $i++)
    {
      // ----- Reset the found list
      $v_found_list[$i] = 0;

    // ----- Calculate the stored filename
    $v_stored_list[$i] = $p_file_list[$i];
    if ($p_remove_dir != "")
    {
      if (substr($p_file_list[$i], -1) != '/')
        $p_remove_dir .= "/";

      if (substr($p_file_list[$i], 0, strlen($p_remove_dir)) == $p_remove_dir)
      {
        $v_stored_list[$i] = substr($p_file_list[$i], strlen($p_remove_dir));
        TrFctMessage(__FILE__, __LINE__, 3, "Remove path '$p_remove_dir' in file '$p_file_list[$i]' = '$v_stored_list[$i]'");
      }
    }
    if ($p_add_dir != "")
    {
      if (substr($p_add_dir, -1) == "/")
        $v_stored_list[$i] = $p_add_dir.$v_stored_list[$i];
      else
        $v_stored_list[$i] = $p_add_dir."/".$v_stored_list[$i];
      TrFctMessage(__FILE__, __LINE__, 3, "Add path '$p_add_dir' in file '$p_file_list[$i]' = '$v_stored_list[$i]'");
    }
    $v_stored_list[$i] = PclTarHandlePathReduction($v_stored_list[$i]);
      TrFctMessage(__FILE__, __LINE__, 3, "After reduction '$v_stored_list[$i]'");
    }


    // ----- Update file cache
    clearstatcache();

    // ----- Read the blocks
    While (!($v_end_of_file = ($p_tar_mode == "tar"?feof($v_tar):gzeof($v_tar))))
    {
      TrFctMessage(__FILE__, __LINE__, 3, "Looking for next header ...");

      // ----- Clear cache of file infos
      clearstatcache();

      // ----- Reset current found filename
      $v_current_filename = "";

      // ----- Reset delete tag
      $v_delete_file = FALSE;

      // ----- Read the first 512 block header
      if ($p_tar_mode == "tar")
        $v_binary_data = fread($v_tar, 512);
      else
        $v_binary_data = gzread($v_tar, 512);

      // ----- Read the header properties
      if (($v_result = PclTarHandleReadHeader($v_binary_data, $v_header)) != 1)
      {
        // ----- Close the archive file
        if ($p_tar_mode == "tar")
        {
          fclose($v_tar);
          fclose($v_temp_tar);
        }
        else
        {
          gzclose($v_tar);
          gzclose($v_temp_tar);
        }
        @unlink($v_temp_tarname);

        // ----- Return
        TrFctEnd(__FILE__, __LINE__, $v_result);
        return $v_result;
      }

      // ----- Look for empty blocks to skip
      if ($v_header[filename] == "")
      {
        TrFctMessage(__FILE__, __LINE__, 2, "Empty block found. End of archive ?");
        continue;
      }

      TrFctMessage(__FILE__, __LINE__, 2, "Found file '$v_header[filename]', size '$v_header[size]'");

      // ----- Look for filenames to update
      for ($i=0, $v_update_file=FALSE, $v_found_file=FALSE; ($i<sizeof($v_stored_list)) && (!$v_update_file); $i++)
      {
        TrFctMessage(__FILE__, __LINE__, 4, "Compare with file '$v_stored_list[$i]'");

        // ----- Compare the file names
        if ($v_stored_list[$i] == $v_header[filename])
        {
          TrFctMessage(__FILE__, __LINE__, 3, "File '$v_stored_list[$i]' is present in archive");
          TrFctMessage(__FILE__, __LINE__, 3, "File '$v_stored_list[$i]' mtime=".filemtime($p_file_list[$i])." ".date("l dS of F Y h:i:s A", filemtime($p_file_list[$i])));
          TrFctMessage(__FILE__, __LINE__, 3, "Archived mtime=".$v_header[mtime]." ".date("l dS of F Y h:i:s A", $v_header[mtime]));

          // ----- Store found informations
          $v_found_file = TRUE;
          $v_current_filename = $p_file_list[$i];

          // ----- Look if the file need to be updated
          if (filemtime($p_file_list[$i]) > $v_header[mtime])
          {
            TrFctMessage(__FILE__, __LINE__, 3, "File '$p_file_list[$i]' need to be updated");
            $v_update_file = TRUE;
          }
          else
          {
            TrFctMessage(__FILE__, __LINE__, 3, "File '$p_file_list[$i]' does not need to be updated");
            $v_update_file = FALSE;
          }

          // ----- Flag the name in order not to add the file at the end
          $v_found_list[$i] = 1;
        }
        else
        {
          TrFctMessage(__FILE__, __LINE__, 4, "File '$p_file_list[$i]' is not '$v_header[filename]'");
        }
      }

      // ----- Copy files that do not need to be updated
      if (!$v_update_file)
      {
        TrFctMessage(__FILE__, __LINE__, 2, "Keep file '$v_header[filename]'");

        // ----- Write the file header
        if ($p_tar_mode == "tar")
        {
          fputs($v_temp_tar, $v_binary_data, 512);
        }
        else
        {
          gzputs($v_temp_tar, $v_binary_data, 512);
        }

        // ----- Write the file data
        $n = ceil($v_header[size]/512);
        for ($j=0; $j<$n; $j++)
        {
          TrFctMessage(__FILE__, __LINE__, 3, "Read complete 512 bytes block number ".($j+1));
          if ($p_tar_mode == "tar")
          {
            $v_content = fread($v_tar, 512);
            fwrite($v_temp_tar, $v_content, 512);
          }
          else
          {
            $v_content = gzread($v_tar, 512);
            gzwrite($v_temp_tar, $v_content, 512);
          }
        }

        // ----- File name and properties are logged if listing mode or file is extracted
        TrFctMessage(__FILE__, __LINE__, 2, "Memorize info about file '$v_header[filename]'");

        // ----- Add the array describing the file into the list
        $p_list_detail[$v_nb] = $v_header;
        $p_list_detail[$v_nb][status] = ($v_found_file?"not_updated":"ok");

        // ----- Increment
        $v_nb++;
      }

      // ----- Look for file that need to be updated
      else
      {
        // ----- Trace
        TrFctMessage(__FILE__, __LINE__, 2, "Start update of file '$v_current_filename'");

        // ----- Store the old file size
        $v_old_size = $v_header[size];

        // ----- Add the file
        if (($v_result = PclTarHandleAddFile($v_temp_tar, $v_current_filename, $p_tar_mode, $v_header, $p_add_dir, $p_remove_dir)) != 1)
        {
          // ----- Close the tarfile
          if ($p_tar_mode == "tar")
          {
            fclose($v_tar);
            fclose($v_temp_tar);
          }
          else
          {
            gzclose($v_tar);
            gzclose($v_temp_tar);
          }
          @unlink($p_temp_tarname);

          // ----- Return status
          TrFctEnd(__FILE__, __LINE__, $v_result);
          return $v_result;
        }

        // ----- Trace
        TrFctMessage(__FILE__, __LINE__, 2, "Skip old file '$v_header[filename]'");

        // ----- Jump to next file
        if ($p_tar_mode == "tar")
          fseek($v_tar, ftell($v_tar)+(ceil(($v_old_size/512))*512));
        else
          gzseek($v_tar, gztell($v_tar)+(ceil(($v_old_size/512))*512));

        // ----- Add the array describing the file into the list
        $p_list_detail[$v_nb] = $v_header;
        $p_list_detail[$v_nb][status] = "updated";

        // ----- Increment
        $v_nb++;
      }

      // ----- Look for end of file
      if ($p_tar_mode == "tar")
        $v_end_of_file = feof($v_tar);
      else
        $v_end_of_file = gzeof($v_tar);
    }

    // ----- Look for files that does not exists in the archive and need to be added
    for ($i=0; $i<sizeof($p_file_list); $i++)
    {
      // ----- Look if file not found in the archive
      if (!$v_found_list[$i])
      {
        TrFctMessage(__FILE__, __LINE__, 3, "File '$p_file_list[$i]' need to be added");

        // ----- Add the file
        if (($v_result = PclTarHandleAddFile($v_temp_tar, $p_file_list[$i], $p_tar_mode, $v_header, $p_add_dir, $p_remove_dir)) != 1)
        {
          // ----- Close the tarfile
          if ($p_tar_mode == "tar")
          {
            fclose($v_tar);
            fclose($v_temp_tar);
          }
          else
          {
            gzclose($v_tar);
            gzclose($v_temp_tar);
          }
          @unlink($p_temp_tarname);

          // ----- Return status
          TrFctEnd(__FILE__, __LINE__, $v_result);
          return $v_result;
        }

        // ----- Add the array describing the file into the list
        $p_list_detail[$v_nb] = $v_header;
        $p_list_detail[$v_nb][status] = "added";

        // ----- Increment
        $v_nb++;
      }
      else
      {
        TrFctMessage(__FILE__, __LINE__, 3, "File '$p_file_list[$i]' was already updated if needed");
      }
    }

    // ----- Write the last empty buffer
    PclTarHandleFooter($v_temp_tar, $p_tar_mode);

    // ----- Close the tarfile
    if ($p_tar_mode == "tar")
    {
      fclose($v_tar);
      fclose($v_temp_tar);
    }
    else
    {
      gzclose($v_tar);
      gzclose($v_temp_tar);
    }

    // ----- Unlink tar file
    if (!@unlink($p_tarname))
    {
      // ----- Error log
      PclErrorLog(-11, "Error while deleting archive name $p_tarname");
    }


    // ----- Rename tar file
    if (!@rename($v_temp_tarname, $p_tarname))
    {
      // ----- Error log
      PclErrorLog(-12, "Error while renaming temporary file $v_temp_tarname to archive name $p_tarname");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleReadHeader()
  // Description :
  // Parameters :
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleReadHeader($v_binary_data, &$v_header)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleReadHeader", "");
    $v_result=1;

    // ----- Read the 512 bytes header
    /*
    if ($p_tar_mode == "tar")
      $v_binary_data = fread($p_tar, 512);
    else
      $v_binary_data = gzread($p_tar, 512);
    */

    // ----- Look for no more block
    if (strlen($v_binary_data)==0)
    {
      $v_header[filename] = "";
      $v_header[status] = "empty";

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, $v_result, "End of archive found");
      return $v_result;
    }

    // ----- Look for invalid block size
    if (strlen($v_binary_data) != 512)
    {
      $v_header[filename] = "";
      $v_header[status] = "invalid_header";
      TrFctMessage(__FILE__, __LINE__, 2, "Invalid block size : ".strlen($v_binary_data));

      // ----- Error log
      PclErrorLog(-10, "Invalid block size : ".strlen($v_binary_data));

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Calculate the checksum
    $v_checksum = 0;
    // ..... First part of the header
    for ($i=0; $i<148; $i++)
    {
      $v_checksum+=ord(substr($v_binary_data,$i,1));
    }
    // ..... Ignore the checksum value and replace it by ' ' (space)
    for ($i=148; $i<156; $i++)
    {
      $v_checksum += ord(' ');
    }
    // ..... Last part of the header
    for ($i=156; $i<512; $i++)
    {
      $v_checksum+=ord(substr($v_binary_data,$i,1));
    }
    TrFctMessage(__FILE__, __LINE__, 3, "Calculated checksum : $v_checksum");

    // ----- Extract the values
    TrFctMessage(__FILE__, __LINE__, 2, "Header : '$v_binary_data'");
    $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $v_binary_data);

    // ----- Extract the checksum for check
    $v_header[checksum] = OctDec(trim($v_data[checksum]));
    TrFctMessage(__FILE__, __LINE__, 3, "File checksum : $v_header[checksum]");
    if ($v_header[checksum] != $v_checksum)
    {
      TrFctMessage(__FILE__, __LINE__, 2, "File checksum is invalid : $v_checksum calculated, $v_header[checksum] expected");

      $v_header[filename] = "";
      $v_header[status] = "invalid_header";

      // ----- Look for last block (empty block)
      if (($v_checksum == 256) && ($v_header[checksum] == 0))
      {
        $v_header[status] = "empty";
        // ----- Return
        TrFctEnd(__FILE__, __LINE__, $v_result, "End of archive found");
        return $v_result;
      }

      // ----- Error log
      PclErrorLog(-13, "Invalid checksum : $v_checksum calculated, $v_header[checksum] expected");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }
    TrFctMessage(__FILE__, __LINE__, 2, "File checksum is valid ($v_checksum)");

    // ----- Extract the properties
    $v_header[filename] = trim($v_data[filename]);
    TrFctMessage(__FILE__, __LINE__, 2, "Name : '$v_header[filename]'");
    $v_header[mode] = OctDec(trim($v_data[mode]));
    TrFctMessage(__FILE__, __LINE__, 2, "Mode : '".DecOct($v_header[mode])."'");
    $v_header[uid] = OctDec(trim($v_data[uid]));
    TrFctMessage(__FILE__, __LINE__, 2, "Uid : '$v_header[uid]'");
    $v_header[gid] = OctDec(trim($v_data[gid]));
    TrFctMessage(__FILE__, __LINE__, 2, "Gid : '$v_header[gid]'");
    $v_header[size] = OctDec(trim($v_data[size]));
    TrFctMessage(__FILE__, __LINE__, 2, "Size : '$v_header[size]'");
    $v_header[mtime] = OctDec(trim($v_data[mtime]));
    TrFctMessage(__FILE__, __LINE__, 2, "Date : ".date("l dS of F Y h:i:s A", $v_header[mtime]));
    if (($v_header[typeflag] = $v_data[typeflag]) == "5")
    {
      $v_header[size] = 0;
      TrFctMessage(__FILE__, __LINE__, 2, "Size (folder) : '$v_header[size]'");
    }
    TrFctMessage(__FILE__, __LINE__, 2, "File typeflag : $v_header[typeflag]");
    /* ----- All these fields are removed form the header because they do not carry interesting info
    $v_header[link] = trim($v_data[link]);
    TrFctMessage(__FILE__, __LINE__, 2, "Linkname : $v_header[linkname]");
    $v_header[magic] = trim($v_data[magic]);
    TrFctMessage(__FILE__, __LINE__, 2, "Magic : $v_header[magic]");
    $v_header[version] = trim($v_data[version]);
    TrFctMessage(__FILE__, __LINE__, 2, "Version : $v_header[version]");
    $v_header[uname] = trim($v_data[uname]);
    TrFctMessage(__FILE__, __LINE__, 2, "Uname : $v_header[uname]");
    $v_header[gname] = trim($v_data[gname]);
    TrFctMessage(__FILE__, __LINE__, 2, "Gname : $v_header[gname]");
    $v_header[devmajor] = trim($v_data[devmajor]);
    TrFctMessage(__FILE__, __LINE__, 2, "Devmajor : $v_header[devmajor]");
    $v_header[devminor] = trim($v_data[devminor]);
    TrFctMessage(__FILE__, __LINE__, 2, "Devminor : $v_header[devminor]");
    */

    // ----- Set the status field
    $v_header[status] = "ok";

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandlerDirCheck()
  // Description :
  //   Check if a directory exists, if not it creates it and all the parents directory
  //   which may be useful.
  // Parameters :
  //   $p_dir : Directory path to check (without / at the end).
  // Return Values :
  //    1 : OK
  //   -1 : Unable to create directory
  // --------------------------------------------------------------------------------
  function PclTarHandlerDirCheck($p_dir)
  {
    $v_result = 1;

    TrFctStart(__FILE__, __LINE__, "PclTarHandlerDirCheck", "$p_dir");

    // ----- Check the directory availability
    if ((is_dir($p_dir)) || ($p_dir == ""))
    {
      TrFctEnd(__FILE__, __LINE__, "'$p_dir' is a directory");
      return 1;
    }

    // ----- Look for file alone
    /*
    if (!strstr("$p_dir", "/"))
    {
      TrFctEnd(__FILE__, __LINE__,  "'$p_dir' is a file with no directory");
      return 1;
    }
    */

    // ----- Extract parent directory
    $p_parent_dir = dirname($p_dir);
    TrFctMessage(__FILE__, __LINE__, 3, "Parent directory is '$p_parent_dir'");

    // ----- Just a check
    if ($p_parent_dir != $p_dir)
    {
      // ----- Look for parent directory
      if ($p_parent_dir != "")
      {
        if (($v_result = PclTarHandlerDirCheck($p_parent_dir)) != 1)
        {
          TrFctEnd(__FILE__, __LINE__, $v_result);
          return $v_result;
        }
      }
    }

    // ----- Create the directory
    TrFctMessage(__FILE__, __LINE__, 3, "Create directory '$p_dir'");
    if (!@mkdir($p_dir, 0777))
    {
      // ----- Error log
      PclErrorLog(-8, "Unable to create directory '$p_dir'");

      // ----- Return
      TrFctEnd(__FILE__, __LINE__, PclErrorCode(), PclErrorString());
      return PclErrorCode();
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result, "Directory '$p_dir' created");
    return $v_result;
  }
  // --------------------------------------------------------------------------------

  // --------------------------------------------------------------------------------
  // Function : PclTarHandleExtension()
  // Description :
  // Parameters :
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandleExtension($p_tarname)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandleExtension", "tar=$p_tarname");

    // ----- Look for file extension
    if ((substr($p_tarname, -7) == ".tar.gz") || (substr($p_tarname, -4) == ".tgz"))
    {
      TrFctMessage(__FILE__, __LINE__, 2, "Archive is a gzip tar");
      $v_tar_mode = "tgz";
    }
    else if (substr($p_tarname, -4) == ".tar")
    {
      TrFctMessage(__FILE__, __LINE__, 2, "Archive is a tar");
      $v_tar_mode = "tar";
    }
    else
    {
      // ----- Error log
      PclErrorLog(-9, "Invalid archive extension");

      TrFctMessage(__FILE__, __LINE__, PclErrorCode(), PclErrorString());

      $v_tar_mode = "";
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_tar_mode);
    return $v_tar_mode;
  }
  // --------------------------------------------------------------------------------


  // --------------------------------------------------------------------------------
  // Function : PclTarHandlePathReduction()
  // Description :
  // Parameters :
  // Return Values :
  // --------------------------------------------------------------------------------
  function PclTarHandlePathReduction($p_dir)
  {
    TrFctStart(__FILE__, __LINE__, "PclTarHandlePathReduction", "dir='$p_dir'");
    $v_result = "";

    // ----- Look for not empty path
    if ($p_dir != "")
    {
      // ----- Explode path by directory names
      $v_list = explode("/", $p_dir);

      // ----- Study directories from last to first
      for ($i=sizeof($v_list)-1; $i>=0; $i--)
      {
        // ----- Look for current path
        if ($v_list[$i] == ".")
        {
          // ----- Ignore this directory
          // Should be the first $i=0, but no check is done
        }
        else if ($v_list[$i] == "..")
        {
          // ----- Ignore it and ignore the $i-1
          $i--;
        }
        else if (($v_list[$i] == "") && ($i!=(sizeof($v_list)-1)) && ($i!=0))
        {
          // ----- Ignore only the double '//' in path,
          // but not the first and last '/'
        }
        else
        {
          $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?"/".$v_result:"");
        }
      }
    }

    // ----- Return
    TrFctEnd(__FILE__, __LINE__, $v_result);
    return $v_result;
  }
  // --------------------------------------------------------------------------------


// ----- End of double include look
}
?>svp_rechercher.php000066600000023347152503074170010300 0ustar00<?php

/**
 * Gestion des recherches de plugins
 *
 * @plugin SVP pour SPIP
 * @license GPL
 * @package SPIP\SVP\Recherche
**/

if (!defined("_ECRIRE_INC_VERSION")) return;
include_spip('inc/plugin');

// ----------------------- Recherches de plugins ---------------------------------

/**
 * Calcule une liste des paquets en fonctions de critères de recherche
 *
 * Cette liste :
 * - est sans doublons, ie on ne garde que la version la plus récente
 * - correspond aux critères
 * - est compatible avec la version SPIP installée sur le site
 * - ne liste pas ceux étant déjà installés (ces paquets peuvent toutefois être affichés)
 * - est triée par nom ou score
 *
 * @param string $phrase
 *     Texte de la recherche
 * @param string $categorie
 *     Type de catégorie de plugin (auteur, date...)
 * @param string $etat
 *     État de plugin (stable, test...)
 * @param string|int $depot
 *     Identifiant de dépot
 * @param string $version_spip
 *     Version de SPIP dont le paquet doit être compatible
 * @param array $exclusions
 *     Liste d'identifiants de plugin à ne pas intégrer dans la liste
 * @param bool $afficher_exclusions
 *     Afficher aussi les paquets déjà installés (true)
 *     ou ceux qui ne le sont pas (false) ?
 * @param bool $doublon
 *     Afficher toutes les versions de paquet (true)
 *     ou seulement la plus récente (false) ?
 * @param string $tri
 *     Ordre du tri : nom | score
 * 
 * @return array
 *     Tableau classé par pertinence de résultat
 *     - 'prefixe' => tableau de description du paquet (si pas de doublons demandé)
 *     - n => tableau de descriptions du paquet (si doublons autorisés)
**/
function svp_rechercher_plugins_spip($phrase, $categorie, $etat, $depot, $version_spip='',
	$exclusions=array(), $afficher_exclusions=false, $doublon=false, $tri='nom')
{

	include_spip('inc/rechercher');
	
	$plugins = array();
	$scores = array();
	$ids_paquets = array();

	// On prepare l'utilisation de la recherche en base SPIP en la limitant aux tables spip_plugins
	// et spip_paquets  si elle n'est pas vide
	if ($phrase) {
		$liste = liste_des_champs();
		$tables = array('plugin' => $liste['plugin']);
		$options = array('jointures' => true, 'score' => true);
	
		// On cherche dans tous les enregistrements de ces tables des correspondances les plugins qui
		// correspondent a la phrase recherchee
		// -- On obtient une liste d'id de plugins et d'id de paquets
		$resultats = array('plugin' => array(), 'paquet' => array());
		$resultats = recherche_en_base($phrase, $tables, $options);
		// -- On prepare le tableau des scores avec les paquets trouves par la recherche
		if ($resultats) {
			// -- On convertit les id de plugins en id de paquets
			$ids = array();
			if (isset($resultats['plugin']) and $resultats['plugin']) {
				$ids_plugin = array_keys($resultats['plugin']);
				$where[] = sql_in('id_plugin', $ids_plugin);
				$ids = sql_allfetsel('id_paquet, id_plugin', 'spip_paquets', $where);
			}
			// -- On prepare les listes des id de paquet et des scores de ces memes paquets
			if (isset($resultats['paquet']) and $resultats['paquet']) {
				$ids_paquets = array_keys($resultats['paquet']);
				foreach ($resultats['paquet'] as $_id => $_score) {
					$scores[$_id] = intval($resultats['paquet'][$_id]['score']);
				}
			}
			// -- On merge les deux tableaux de paquets sans doublon en mettant a jour un tableau des scores
			foreach ($ids as $_ids) {
				$id_paquet = intval($_ids['id_paquet']);
				$id_plugin = intval($_ids['id_plugin']);
				if (array_search($id_paquet, $ids_paquets) === false) {
					$ids_paquets[] = $id_paquet;
					$scores[$id_paquet] = intval($resultats['plugin'][$id_plugin]['score']);
				}
				else {
					$scores[$id_paquet] = intval($resultats['paquet'][$id_paquet]['score']) 
										+ intval($resultats['plugin'][$id_plugin]['score']);
				}
			}
		}
	}
	else {
		if ($ids_paquets = sql_allfetsel('id_paquet', 'spip_paquets')) {
			$ids_paquets = array_map('reset', $ids_paquets);
			foreach ($ids_paquets as $_id)
				$scores[$_id] = 0;
		}
	}

	// Maintenant, on continue la recherche en appliquant, sur la liste des id de paquets,
	// les filtres complementaires : categorie, etat, exclusions et compatibilite spip
	// si on a bien trouve des resultats precedemment ou si aucune phrase n'a ete saisie
	// -- Preparation de la requete
	if ($ids_paquets) {
		$from = array('spip_plugins AS t1', 'spip_paquets AS t2', 'spip_depots AS t3');
		$select = array('t1.nom AS nom', 't1.slogan AS slogan', 't1.prefixe AS prefixe', 't1.id_plugin AS id_plugin', 
						't2.id_paquet AS id_paquet', 't2.description AS description', 't2.compatibilite_spip AS compatibilite_spip',
						't2.lien_doc AS lien_doc',
						't2.auteur AS auteur', 't2.licence AS licence', 't2.etat AS etat',
						't2.logo AS logo', 't2.version AS version', 't2.nom_archive AS nom_archive',
						't3.url_archives AS url_archives', );
		$where = array('t1.id_plugin=t2.id_plugin', 't2.id_depot=t3.id_depot');
		if ($ids_paquets)
			$where[] = sql_in('t2.id_paquet', $ids_paquets);
		if (($categorie) AND ($categorie != 'toute_categorie'))
			$where[] = 't1.categorie=' . sql_quote($categorie);
		if (($etat) AND ($etat != 'tout_etat'))
			$where[] = 't2.etat=' . sql_quote($etat);
		if (($depot) AND ($depot != 'tout_depot'))
			$where[] = 't2.id_depot=' . sql_quote($depot);
		if ($exclusions AND !$afficher_exclusions)
			$where[] = sql_in('t2.id_plugin', $exclusions, 'NOT');
	
		if ($resultats = sql_select($select, $from, $where)) {
			while ($paquets = sql_fetch($resultats)) {
				$prefixe = $paquets['prefixe'];
				$version = $paquets['version'];
				$nom = extraire_multi($paquets['nom']);
				$slogan = extraire_multi($paquets['slogan']);
				$description = extraire_multi($paquets['description']);
				if (svp_verifier_compatibilite_spip($paquets['compatibilite_spip'], $version_spip)) {
					// Le paquet remplit tous les criteres, on peut le selectionner
					// -- on utilise uniquement la langue du site
					$paquets['nom'] = $nom;
					$paquets['slogan'] = $slogan;
					$paquets['description'] = $description;
					// -- on ajoute le score si on a bien saisi une phrase
					if ($phrase)
						$paquets['score'] = $scores[intval($paquets['id_paquet'])];
					else
						$paquets['score'] = 0;
					// -- on construit l'url de l'archive
					$paquets['url_archive'] = $paquets['url_archives'] . '/' . $paquets['nom_archive'];
					// -- on gere les exclusions si elle doivent etre affichees
					if ($afficher_exclusions AND in_array($paquets['id_plugin'], $exclusions))
						$paquets['installe'] = true;
					else
						$paquets['installe'] = false;
					// -- On traite les doublons (meme plugin, versions differentes)
					if ($doublon)
						// ajout systematique du paquet
						$plugins[] = $paquets;
					else {
						// ajout 
						// - si pas encore trouve 
						// - ou si sa version est inferieure (on garde que la derniere version)
						if (!isset($plugins[$prefixe])
						OR !$plugins[$prefixe]
						OR ($plugins[$prefixe] AND spip_version_compare($plugins[$prefixe]['version'], $version, '<'))) {
							$plugins[$prefixe] = $paquets;
						}
					}
				}
			}
		}
		
		// On trie le tableau par score décroissant ou nom croissant
		$fonction = 'svp_trier_par_' . $tri;
		if ($doublon)
			usort($plugins, $fonction);
		else
			uasort($plugins, $fonction);
	}
	
	return $plugins;
}


/**
 * Récupère les identifiants des plugins déjà installés
 *
 * @return int[]
 *     Liste d'identifiants de plugins
 */
function svp_lister_plugins_installes(){

	$ids = array();

	// On recupere la liste des plugins installes physiquement sur le site
	// Pour l'instant ce n'est pas possible avec les fonctions natives de SPIP
	// donc on se contente des plugins actifs
	// - liste des prefixes en lettres majuscules des plugins actifs
	$plugins = liste_plugin_actifs();

	// - liste des id de plugin correspondants
	//   Il se peut que certains plugins ne soient pas trouves dans la bdd car aucun zip n'est disponible
	//   (donc pas inclus dans archives.xml). C'est le cas des plugins_dist du core
	$ids = sql_allfetsel('id_plugin', 'spip_plugins', sql_in('prefixe', array_keys($plugins)));
	$ids = array_map('reset', $ids);
	$ids = array_map('intval', $ids);

	return $ids;
}


/**
 * Teste la compatibilité d'un intervalle de compatibilité avec une version
 * donnée de SPIP
 *
 * @param string $intervalle
 *     Intervalle de compatibilité, tel que [2.1;3.0]
 * @param string $version_spip
 *     Version de SPIP, tel que 3.0.4 (par défaut la version de SPIP en cours)
 * @return bool
 *     true si l'intervalle est compatible, false sinon
 */
function svp_verifier_compatibilite_spip($intervalle, $version_spip = '') {
	if (!$version_spip)
		$version_spip = $GLOBALS['spip_version_branche'].".".$GLOBALS['spip_version_code'];
	return plugin_version_compatible($intervalle, $version_spip,'spip');
}


/**
 * Callback de tri pour trier les résultats de plugin par score décroissant.
 * 
 * Cette fonction est appelée par un usort ou uasort
 *
 * @param array $p1
 *     Plugin à comparer
 * @param array $p2
 *     Plugin à comparer
 * @return int
 */
function svp_trier_par_score($p1, $p2){
	if ($p1['score'] == $p2['score']) 
		$retour = 0;
	else 
		$retour = ($p1['score'] < $p2['score']) ? 1 : -1;
	return $retour;
}


 /**
 * Callback de tri pour trier les résultats de plugin par nom (alphabétique).
 *
 * Si le nom est identique on classe par version decroissante 
 * Cette fonction est appelée par un usort ou uasort
 *
 * @param array $p1
 *     Plugin à comparer
 * @param array $p2
 *     Plugin à comparer
 * @return int
 */
function svp_trier_par_nom($p1, $p2){
	$c1 = strcasecmp($p1['nom'], $p2['nom']);
	if ($c1 == 0) {
		$c2 = spip_version_compare($p1['version'], $p1['version'], '<');
		$retour = ($c2) ? 1 : -1;
	}
	else 
		$retour = ($c1 < 0) ? -1 : 1;
	return $retour;
}

?>
svp_phraser.php000066600000036035152503074170007630 0ustar00<?php

/**
 * Fichier permettant de phraser les XML des fichiers paquet.xml et plugin.xml
 * ainsi que des fichiers décrivant le contenu d'un dépot de paquets.
 * 
 * @plugin SVP pour SPIP
 * @license GPL
 * @package SPIP\SVP\Plugins
**/

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

include_spip('inc/xml');
include_spip('inc/config');

if (!defined('_SVP_MODE_RUNTIME')) {
/**
 * Mode d'utilisation de SVP runtime ou pas :
 * - En mode runtime (true), on ne charge que les plugins compatibles avec la version courante
 * - En mode non runtime (false) on charge tous les plugins : cas du site Plugins SPIP
 * Runtime est le mode par defaut
 * @var bool */
	define('_SVP_MODE_RUNTIME', (lire_config('svp/mode_runtime', 'oui') == 'oui' ? true : false));
}


// Type parseur XML à appliquer pour récupérer les infos du plugin
/** @var string  Phraseur à utiliser pour un XML de plugin.xml */
define('_SVP_DTD_PLUGIN', 'plugin'); 
/** @var string  Phraseur à utiliser pour un XML de paquet.xml */
define('_SVP_DTD_PAQUET', 'paquet'); 

// Regexp de recherche des balises principales de archives.xml
define('_SVP_REGEXP_BALISE_DEPOT', '#<depot[^>]*>(.*)</depot>#Uims');
define('_SVP_REGEXP_BALISE_ARCHIVES', '#<archives[^>]*>(.*)</archives>#Uims');
define('_SVP_REGEXP_BALISE_ARCHIVE', '#<archive[^s][^>]*>(.*)</archive>#Uims');
define('_SVP_REGEXP_BALISE_ZIP', '#<zip[^>]*>(.*)</zip>#Uims');
define('_SVP_REGEXP_BALISE_TRADUCTIONS', '#<traductions[^>]*>(.*)</traductions>#Uims');
define('_SVP_REGEXP_BALISE_PLUGIN', '#<plugin[^>]*>(.*)</plugin>#Uims');
define('_SVP_REGEXP_BALISE_PAQUET', '#<paquet[^>]*>(.*)</paquet>#Uims');
define('_SVP_REGEXP_BALISE_MULTIS', '#<multis[^>]*>(.*)</multis>#Uims');


// Liste des categories de plugin
# define('_CATEGORIES_PLUGIN', serialize($categories_plugin));
$GLOBALS['categories_plugin'] = array(
	'communication',
	'edition',
	'multimedia',
	'navigation',
	'date',
	'divers',
	'auteur',
	'statistique',
	'performance',
	'maintenance',
	'outil',
	'theme',
	'squelette',
	'aucune'
);

/** Liste des balises techniques autorisées dans la balise <spip> */
$GLOBALS['balises_techniques'] = array(
	'menu', 'chemin', 'lib', 'necessite', 'onglet', 'procure', 'pipeline', 'utilise',
	'options', 'fonctions', 'install');
# define('_BALISES_TECHNIQUES', serialize($balises_techniques));
	
/** Liste des balises autorisant une traduction */
$GLOBALS['balises_multis'] = array(
	'nom', 'slogan', 'description');
# define('_BALISES_MULTIS', serialize($balises_multis));


/**
 * Phrase un fichier décrivant un dépot, dont le chemin local est donné
 * 
 * Le fichier est au format XML et contient deux balises principales :
 * - <depot>...</depot> : informations de description du depot (facultatif)
 * - <archives>...</archives> : liste des informations sur chaque archive (obligatoire)
 *
 * La fonction met en cache le résultat du phrasage de chaque archive et ne
 * rephrase que les archives ayant changées.
 * 
 * @param string $fichier_xml
 *     Chemin local du fichier XML de description du dépot
 * @return array|bool
 *     false si erreur,
 *     Tableau de 2 index sinon :
 *     - depot : description du dépot
 *     - paquets : 
 */
function svp_phraser_depot($fichier_xml) {

	// le fichier xml fournit sous forme de fichier
	lire_fichier($fichier_xml,$xml);

	// Initialisation du tableau des informations
	// -- Si aucun bloc depot n'est trouve le titre et le type prennent une valeur par defaut
	$infos = array(
				'depot' => array(
							'titre' => _T('svp:titre_nouveau_depot'), 
							'type' => 'manuel'),
				'paquets' => array());


	// Extraction et phrasage du bloc depot si il existe
	// -- Si le bloc <depot> n'est pas renseigne on ne considere pas cela comme une erreur
	$balises_depot = array('titre', 'descriptif', 'type', 'url_serveur', 'url_brouteur', 'url_archives', 'url_commits');
	if (preg_match(_SVP_REGEXP_BALISE_DEPOT, $xml, $matches)) {
		if (is_array($arbre_depot = spip_xml_parse($matches[1]))) {
			$infos['depot'] = svp_aplatir_balises($balises_depot, $arbre_depot, 'nonvide', $infos['depot']);
		}
	}

	// Extraction et phrasage du bloc des archives si il existe
	// -- Le bloc <archives> peut etre une chaine de grande taille et provoquer une erreur 
	// sur une recherche de regexp. On ne teste donc pas l'existence de cette balise
	// -- Si aucun bloc <archive> c'est aussi une erreur
	if (!preg_match_all(_SVP_REGEXP_BALISE_ARCHIVE, $xml, $matches))
		return false;

	// lire le cache des md5 pour ne parser que ce qui a change
	$fichier_xml_md5 = $fichier_xml . ".md5.txt";
	lire_fichier($fichier_xml_md5,$cache_md5);
	if (!$cache_md5
	  OR !$cache_md5 = unserialize($cache_md5))
		$cache_md5 = array();

	$infos['paquets'] = svp_phraser_archives($matches[0], $cache_md5);
	ecrire_fichier($fichier_xml_md5,serialize($cache_md5));

	// -- Si aucun paquet extrait c'est aussi une erreur
	if (!$infos['paquets'])
		return false;

	return $infos;
}


/**
 * Phrase la liste des balises <archive>
 *
 * Chaque bloc XML est constitue de 3 sous-blocs principaux :
 * - <zip> : contient les balises d'information sur le zip (obligatoire)
 * - <traductions> : contient la compilation des informations de traduction (facultatif)
 * - <plugin> ou <paquet> suivant la DTD : le contenu du fichier plugin.xml ou paquet.xml (facultatif)
 *
 * @param array $archives
 *     Tableau de la liste des archives trouvées dans la description d'un dépot
 * @param array $md5_cache
 *     Tableau des descriptions d'archives déjà connues : on supprime
 *     à la fin celles qui ne font plus parties du dépot.
 * @return array
 *     Tableau décrivant chaque archive, avec en index l'url de l'archive.
 *     Tableau (url => Tableau de description de l'archive)
 */
function svp_phraser_archives($archives,&$md5_cache=array()) {
	include_spip('inc/plugin');
	$seen = array();

	$paquets = array();
	$version_spip = $GLOBALS['spip_version_branche'].".".$GLOBALS['spip_version_code'];

	// On verifie qu'il existe au moins une archive
	if (!$archives)
		return $paquets;

	// On phrase chacune des archives
	// Seul le bloc <zip> est obligatoire
	foreach ($archives as $_cle => $_archive){
		// quand version spip ou mode runtime changent,
		// il faut mettre le xml a jour pour voir les plugins compatibles ou non
		$md5 = md5($_archive.":$version_spip:"._SVP_MODE_RUNTIME);
		if (isset($md5_cache[$md5])){
			if (is_array($p=$md5_cache[$md5]))
				$paquets[$p['file']] = $p; // ce paquet est connu
			$seen[] = $md5;
		}
		elseif (preg_match(_SVP_REGEXP_BALISE_ZIP, $_archive, $matches)) {

			// Extraction de la balise <zip>
			$zip = svp_phraser_zip($matches[1]);

			if ($zip) {
				
				// Extraction de la balise traductions
				$traductions = array();
				if (preg_match(_SVP_REGEXP_BALISE_TRADUCTIONS, $_archive, $matches))
					$traductions = svp_phraser_traductions($matches[1]);

				
				// La balise <archive> peut posseder un attribut qui precise la DTD utilisee pour les plugins (plugin ou paquet)
				// Sinon, c'est la DTD plugin qui est utilisee
				list($tag, $attributs) = spip_xml_decompose_tag($_archive);
				// -- On stocke la DTD d'extraction des infos du plugin
				$dtd = (isset($attributs['dtd']) AND $attributs['dtd']) ? $attributs['dtd'] : _SVP_DTD_PLUGIN;

				// Extraction *des balises* plugin ou *de la balise* paquet suivant la DTD et la version SPIP
				// -- DTD : si on utilise plugin.xml on extrait la balise <plugin> sinon la balise <paquet>
				$xml = svp_phraser_plugin($dtd, $_archive);
			
				// Si on est en mode runtime, on est seulement interesse par les plugins compatibles avec
				// la version courant de SPIP. On ne stocke donc pas les autres plugins.
				// Si on est pas en mode runtime on prend tout !
				if (!_SVP_MODE_RUNTIME
				OR (_SVP_MODE_RUNTIME AND isset($xml['compatibilite']) AND plugin_version_compatible($xml['compatibilite'], $version_spip, 'spip'))) {
					$paquets[$zip['file']] = $zip;
					$paquets[$zip['file']]['traductions'] = $traductions;
					$paquets[$zip['file']]['dtd'] = $dtd;
					$paquets[$zip['file']]['plugin'] = $xml;
					$paquets[$zip['file']]['md5'] = $md5;
					$md5_cache[$md5] = $paquets[$zip['file']];
					$seen[] = $md5;
				}
				else{
					$md5_cache[$md5] = $zip['file'];
					$seen[] = $md5;
				}
			}
		}
	}

	// supprimer du cache les zip qui ne sont pas dans le nouveau $archives
	$oldies = array_diff(array_keys($md5_cache),$seen);
	foreach ($oldies as $old_md5){
		unset($md5_cache[$old_md5]);
	}

	return $paquets;
}



/**
 * Phrase le contenu du XML décrivant une archive suivant une DTD
 * de plugin.xml ou de paquet.xml donnée
 *
 * La fonction peut-être appelée via archives.xml ou via un xml de plugin.
 * Elle phrase la balise <multi> dans le cas d'une DTD paquet qui contient
 * les traductions du nom, slogan et description
 *
 * @global $balises_multis
 * 
 * @param string $dtd
 *     Nom du type de dtd : plugin ou paquet (pour phraser un plugin.xml ou un paquet.xml)
 * @param string $contenu
 *     Contenu XML à phraser
 * @return array
 *     Description du plugin
**/
function svp_phraser_plugin($dtd, $contenu) {
	global $balises_multis;
	static $informer = array();
	
	$plugin = array();
	
	// On initialise les informations du plugin avec le contenu du plugin.xml ou paquet.xml
	$regexp = ($dtd == 'plugin') ? _SVP_REGEXP_BALISE_PLUGIN : _SVP_REGEXP_BALISE_PAQUET;
	if ($nb_balises = preg_match_all($regexp, $contenu, $matches)) {
		$plugins = array();
		// Pour chacune des occurences de la balise on extrait les infos
		foreach ($matches[0] as $_balise_plugin) {
			// Extraction des informations du plugin suivant le standard SPIP
			if (!isset($informer[$dtd])) {
				$informer[$dtd] = charger_fonction('infos_' . $dtd, 'plugins');
			}
			$plugins[] = $informer[$dtd]($_balise_plugin);
		}

		// On appelle systematiquement une fonction de mise a jour de la structure de donnees du plugin :
		// -- Si DTD plugin et que le nombre de balises plugin > 1 ou si DTD paquet avec une presence de balise spip
		//    alors on fusionne donc les informations recoltees
		// -- sinon on arrange la structure pour deplacer le contenu des balises dites techniques dans un sous tableau
		//    d'index 0 par similitude avec la structure fusionnee
		$fusionner = charger_fonction('fusion_' . $dtd, 'plugins');
		if ($dtd == 'plugin')
			$plugin = $fusionner($plugins);
		else
			$plugin = $fusionner($plugins[0]);

		// Pour la DTD paquet, les traductions du nom, slogan et description sont compilees dans une balise
		// du fichier archives.xml. Il faut donc completer les informations precedentes avec cette balise
		if (($dtd == _SVP_DTD_PAQUET) AND (preg_match(_SVP_REGEXP_BALISE_MULTIS, $contenu, $matches))) {
			$multis = array();
			if (is_array($arbre = spip_xml_parse($matches[1])))
				$multis = svp_aplatir_balises($balises_multis, $arbre);
			// Le nom peut etre traduit ou pas, il faut donc le tester
			if ($multis['nom'])
				$plugin['nom'] = $multis['nom'];
			// Slogan et description sont forcement des items de langue 
			$plugin['slogan'] = $multis['slogan'];
			$plugin['description'] = $multis['description'];
		}
	}

	return $plugin;
}


/**
 * Phrase le contenu de la balise <zip>
 *
 * Extrait du XML les informations du zip
 * 
 * @param string $contenu
 *     Description XML de l'archive
 * @return array
 *     Description du zip.
 *     - Index 'file' : nom du zip
 *     - Index 'size' : taille
 *     - Index 'date' : date de création
 *     - Index 'last_commit' : date du dernier commit
 *     - Index 'source' : arborescence relative des sources
 */
function svp_phraser_zip($contenu) {
	static $balises_zip = array('file', 'size', 'date', 'source', 'last_commit');
	
	$zip = array();
	if (is_array($arbre = spip_xml_parse($contenu)))
		$zip = svp_aplatir_balises($balises_zip, $arbre);

	return $zip;
}


/**
 * Phrase le contenu d'une balise <traductions> en un tableau plus
 * facilement utilisable
 *
 * @param string $contenu
 *     Contenu XML de la balise <traductions>
 * @return array
 *     Tableau complexe avec pour index les noms des modules de langue et pour
 *     valeur leur description. Chaque description contient dedans 3 index :
 *     - reference : la langue de référence
 *     - gestionnaire : quel logiciel à servi à gérer les traductions
 *     - langues : tableau classé par langue puis par traducteurs, qui indique
 *       l'ensemble des traducteurs pour chacune des langues présentes
 */
function svp_phraser_traductions($contenu) {
	
	$traductions = array();
	if (is_array($arbre = spip_xml_parse($contenu))) {
		foreach ($arbre as $_tag => $_langues) {
			// On commence par les balises <traduction> et leurs attributs
			list($tag, $attributs_traduction) = spip_xml_decompose_tag($_tag);
			$traductions[$attributs_traduction['module']]['reference'] = $attributs_traduction['reference'];
			$traductions[$attributs_traduction['module']]['gestionnaire'] = isset($attributs_traduction['gestionnaire']) ? $attributs_traduction['gestionnaire'] : '' ;
	
			// On continue par les balises <langue> qui donnent le code en attribut
			// et les balises <traducteur> qui donnent uniquement le nom en attribut
			if (is_array($_langues[0])) {
				foreach ($_langues[0] as $_tag => $_traducteurs) {
					list($tag, $attributs_langue) = spip_xml_decompose_tag($_tag);
					$traducteurs = array();
					if (is_array($_traducteurs[0])) {
						foreach ($_traducteurs[0] as $_tag => $_vide) {
							list($tag, $attributs_traducteur) = spip_xml_decompose_tag($_tag);
							$traducteurs[] = $attributs_traducteur;
						}
					}
					$traductions[$attributs_traduction['module']]['langues'][$attributs_langue['code']] = $traducteurs;
				}
			}
		}
	}

	return $traductions;
}


/**
 * Aplatit plusieurs clés d'un arbre xml dans un tableau
 * 
 * Effectue un trim() de la valeur trouvée dans l'arbre
 *
 * @param array $balises
 *     Liste de noms de balises XML.
 *     Peut aussi être un tableau indiquant un renommage d'une balise
 *     au passage tel que 'x' => 'y' qui cherchera x dans l'arbre XML et
 *     l'applatira dans y.
 * @param array $arbre_xml
 *     Un arbre issu de spip_xml_parse()
 * @param string $mode
 *     Mode d'affectation des valeurs trouvées
 *     - 'vide_et_nonvide' : Affecte une chaine vide si la balise n'est
 *       pas trouvée dans l'arbre et affecte la valeur de la balise sinon.
 *     - 'nonvide' : Si la balise n'est pas trouvée dans l'arbre ou si son
 *       contenu est vide, affecte la valeur du tableau initial concernant
 *       cette balise si elle est connue.
 * @param array
 *     Tableau initial pouvant contenir des valeurs par défaut à affecter
 *     à chaque balise avec 'x' => 'valeur'
 */
function svp_aplatir_balises($balises, $arbre_xml, $mode='vide_et_nonvide', $tableau_initial=array()) {
	$tableau_aplati = array();

	if (!$balises)
		return $tableau_initial;

	foreach ($balises as $_cle => $_valeur){
		$tag = (is_string($_cle)) ? $_cle : $_valeur;
		$valeur_aplatie = '';
		if (isset($arbre_xml[$tag])) {
			$valeur_aplatie = trim(spip_xml_aplatit($arbre_xml[$tag]));
		}
		if (($mode == 'vide_et_nonvide')
		OR (($mode == 'nonvide') AND $valeur_aplatie))
			$tableau_aplati[$_valeur] = $valeur_aplatie;
		else
			$tableau_aplati[$_valeur] = isset($tableau_initial[$_valeur]) ? $tableau_initial[$_valeur] : '';
	}

	return $tableau_aplati;
}

?>
svp_depoter_local.php000066600000056767152503074170011016 0ustar00<?php

/**
 * Traitement du dépot local
 *
 * Le dépot local est la liste de paquets qui sont présents sur l'hébergement
 * dans un des répertoires de plugins, actifs ou non actifs.
 *
 * Dans la base, un dépot local est représenté avec un id_dépot nul.
 * Il n'y a cependant pas de ligne spécifique décrivant le dépot local
 * dans la table SQL spip_depots, mais juste des valeurs id_depot=0 dans
 * la table spip_paquets.
 * 
 * @plugin SVP pour SPIP
 * @license GPL
 * @package SPIP\SVP\Depots
 */

/**
 * Met à jour les tables SQL paquets et plugins pour qui concerne
 * les paquets locaux (ceux présents sur le site).
 *
 * On ne met à jour que ce qui a changé, sauf si on force le recalcule
 * de toutes les informations locales avec var_mode=vider_paquets_locaux
 * dans l'URL ou en mettant le paramètre $force à true.
 * 
 * @param bool $force
 *     - false : n'actualise que les paquets modifiés
 *     - true : efface et recrée la liste de tous les paquets locaux
 * @param array $erreurs_xml
 *     Si des erreurs XML sont présentes, elles se retrouvent dans ce tableau
 * @return string
 *     Temps d'exécution
**/
function svp_actualiser_paquets_locaux($force = false, &$erreurs_xml = array()) {

	spip_timer('paquets_locaux');
	$paquets = svp_descriptions_paquets_locaux($erreurs_xml);

	// un mode pour tout recalculer sans désinstaller le plugin... !
	if ($force OR _request('var_mode') == 'vider_paquets_locaux') { 
		svp_base_supprimer_paquets_locaux();
		svp_base_inserer_paquets_locaux($paquets);
	} else {
		svp_base_modifier_paquets_locaux($paquets);
	}
	svp_base_actualiser_paquets_actifs();

	$temps = spip_timer('paquets_locaux');
#spip_log('svp_actualiser_paquets_locaux', 'SVP');
#spip_log($temps, 'SVP');
	return "Éxécuté en : " . $temps;
	
}


/**
 * Calcule la description de chaque paquet local
 *
 * Les paquets peuvent être stockés à 3 endroits :
 * plugins, plugins-dist, plugins-supp définis par les constantes respectives
 * _DIR_PLUGINS, _DIR_PLUGINS_DIST, _DIR_PLUGINS_SUPP
 *
 * @param array $erreurs_xml
 *     Les erreurs XML éventuelles des paquet.xml se retrouvent dedans s'il y en a
 * @return array
 *     Descriptions des paquets (intégrant un hash), stockés par
 *     constante, puis par chemin.
 *     array[_DIR_PLUGIN*][$chemin] = description
**/
function svp_descriptions_paquets_locaux(&$erreurs_xml = array()) {
	include_spip('inc/plugin');
	liste_plugin_files(_DIR_PLUGINS);
	liste_plugin_files(_DIR_PLUGINS_DIST);
	$get_infos = charger_fonction('get_infos', 'plugins');
	$paquets_locaux = array(
		'_DIR_PLUGINS'    => $get_infos(array(), false, _DIR_PLUGINS),
		'_DIR_PLUGINS_DIST' => $get_infos(array(), false, _DIR_PLUGINS_DIST),
	);
	if (defined('_DIR_PLUGINS_SUPPL') and _DIR_PLUGINS_SUPPL) {
		liste_plugin_files(_DIR_PLUGINS_SUPPL);
		$paquets_locaux['_DIR_PLUGINS_SUPPL'] = $get_infos(array(), false, _DIR_PLUGINS_SUPPL);
	}

	// creer la liste des signatures
	foreach($paquets_locaux as $const_dir => $paquets) {
		foreach ($paquets as $chemin => $paquet) {
			// on propose le paquet uniquement s'il n'y a pas eu d'erreur de lecture XML bloquante 
			if (!isset($paquet['erreur'])) {
				$paquets_locaux[$const_dir][$chemin]['signature'] = md5($const_dir . $chemin . serialize($paquet));
			} else {
				// Erreur XML !
				unset($paquets_locaux[$const_dir][$chemin]);
				spip_log("Impossible de lire la description XML de $chemin . Erreurs :", 'svp.' . _LOG_ERREUR);
				spip_log($paquet['erreur'], 'svp.' . _LOG_ERREUR);
				$erreurs_xml[] = $paquet['erreur'][0];
			}
		}
	}

	return $paquets_locaux;
}


/**
 * Supprime tous les paquets et plugins locaux.
**/ 
function svp_base_supprimer_paquets_locaux() {
	sql_delete('spip_paquets', 'id_depot = ' . 0); //_paquets locaux en 0
	sql_delete('spip_plugins', sql_in('id_plugin', sql_get_select('DISTINCT(id_plugin)', 'spip_paquets'), 'NOT'));
}


/**
 * Actualise les informations en base sur les paquets locaux
 * en ne modifiant que ce qui a changé.
 *
 * @param array $paquets_locaux
 *     Descriptions des paquets (intégrant un hash), stockés par
 *     constante, puis par chemin.
 *     array[_DIR_PLUGIN*][$chemin] = description
**/
function svp_base_modifier_paquets_locaux($paquets_locaux) {
	include_spip('inc/svp_depoter_distant');

	// On ne va modifier QUE les paquets locaux qui ont change
	// Et cela en comparant les md5 des informations fouries.
	$signatures = array();

	// recuperer toutes les signatures 
	foreach($paquets_locaux as $const_dir => $paquets) {
		foreach ($paquets as $chemin => $paquet) {
			$signatures[$paquet['signature']] = array(
				'constante' => $const_dir,
				'chemin'    => $chemin,
				'paquet'    => $paquet,
			);
		}
	}

	// tous les paquets du depot qui ne font pas parti des signatures
	$anciens_paquets = sql_allfetsel('id_paquet', 'spip_paquets', array('id_depot=' . sql_quote(0), sql_in('signature', array_keys($signatures), 'NOT')));
	$anciens_paquets = array_map('array_shift', $anciens_paquets);

	// tous les plugins correspondants aux anciens paquets
	$anciens_plugins = sql_allfetsel('p.id_plugin',	array('spip_plugins AS p', 'spip_paquets AS pa'), array('p.id_plugin=pa.id_plugin', sql_in('pa.id_paquet', $anciens_paquets)));
	$anciens_plugins = array_map('array_shift', $anciens_plugins);

	// suppression des anciens paquets
	sql_delete('spip_paquets', sql_in('id_paquet', $anciens_paquets));
	
	// supprimer les plugins orphelins
	svp_supprimer_plugins_orphelins($anciens_plugins);

	// on ne garde que les paquets qui ne sont pas presents dans la base
	$signatures_base = sql_allfetsel('signature', 'spip_paquets', 'id_depot='.sql_quote(0));
	$signatures_base = array_map('array_shift', $signatures_base);
	$signatures = array_diff_key($signatures, array_flip($signatures_base));

	// on recree la liste des paquets locaux a inserer
	$paquets_locaux = array();
	foreach ($signatures as $s => $infos) {
		if (!isset($paquets_locaux[$infos['constante']])) {
			$paquets_locaux[$infos['constante']] = array();
		}
		$paquets_locaux[$infos['constante']][$infos['chemin']] = $infos['paquet'];
	}

	svp_base_inserer_paquets_locaux($paquets_locaux);
}


/**
 * Insère en base tous les paquets locaux transmis
 *
 * De chaque description est extrait la partie plugin (1 seul plugin
 * par préfixe de plugin connu) et la partie paquet (il peut y avoir plusieurs
 * paquets pour un même préfixe de plugin).
 * 
 * @note
 *     On essaie au mieux de faire des requêtes d'insertions multiples,
 *     mieux gérées par les moteurs SQL (particulièrement pour SQLite)
 * 
 * @param array $paquets_locaux
 *     Descriptions des paquets (intégrant un hash), stockés par
 *     constante, puis par chemin.
 *     array[_DIR_PLUGIN*][$chemin] = description
**/
function svp_base_inserer_paquets_locaux($paquets_locaux) {
	include_spip('inc/svp_depoter_distant');
	
	// On initialise les informations specifiques au paquet :
	// l'id du depot et les infos de l'archive
	$paquet_base = array(
		'id_depot' => 0,
		'nom_archive' => '',
		'nbo_archive' => '',
		'maj_archive' => '',
		'src_archive' => '',
		'date_modif' => '',
		'maj_version' => '',
		'signature' => '',
	);

	$preparer_sql_paquet = charger_fonction('preparer_sql_paquet', 'plugins');

	// pour chaque decouverte, on insere les paquets en base.
	// on evite des requetes individuelles, tres couteuses en sqlite...
	$cle_plugins    = array(); // prefixe => id
	$insert_plugins = array(); // insertion prefixe...
	$insert_plugins_vmax = array(); // vmax des nouveaux plugins...
	$insert_paquets = array(); // insertion de paquet...

	include_spip('inc/config');
	$recents = lire_config('plugins_interessants');
	$installes  = lire_config('plugin_installes');
	$actifs  = lire_config('plugin');
	$attentes  = lire_config('plugin_attente');

	foreach($paquets_locaux as $const_dir => $paquets) {
		foreach ($paquets as $chemin => $paquet) {
			// Si on est en presence d'un plugin dont la dtd est "paquet" on compile en multi
			// les nom, slogan et description a partir des fichiers de langue.
			// De cette façon, les informations des plugins locaux et distants seront identiques
			// => On evite l'utilisation de _T() dans les squelettes
			if ($paquet['dtd'] == 'paquet') {
				$multis = svp_compiler_multis($paquet['prefix'], constant($const_dir) . '/' . $chemin);
				if (isset($multis['nom']))
					$paquet['nom'] = $multis['nom'];
				$paquet['slogan'] = (isset($multis['slogan'])) ? $multis['slogan'] : '';
				$paquet['description'] = (isset($multis['description'])) ? $multis['description'] : '';
			}

			$le_paquet = $paquet_base;
			#$le_paquet['traductions'] = serialize($paquet['traductions']);

			if ($champs = $preparer_sql_paquet($paquet)) {

				// Eclater les champs recuperes en deux sous tableaux, un par table (plugin, paquet)
				$champs = eclater_plugin_paquet($champs);
				$paquet_plugin = true;
				
				// On complete les informations du paquet et du plugin
				$le_paquet = array_merge($le_paquet, $champs['paquet']);
				$le_plugin = $champs['plugin'];

				// On loge l'absence de categorie ou une categorie erronee et on positionne la categorie par defaut "aucune"
				if (!$le_plugin['categorie']) {
					$le_plugin['categorie'] = 'aucune';
				} else {
					if (!in_array($le_plugin['categorie'], $GLOBALS['categories_plugin'])) {
						$le_plugin['categorie'] = 'aucune';
					}
				}

				// creation du plugin...
				$prefixe = strtoupper( $le_plugin['prefixe'] );
				// on fait attention lorqu'on cherche ou ajoute un plugin
				// le nom et slogan est TOUJOURS celui de la plus haute version
				// et il faut donc possiblement mettre a jour la base...
				// 
				// + on est tolerant avec les versions identiques de plugin deja presentes
				//   on permet le recalculer le titre...
				if (!isset($cle_plugins[$prefixe])) {
					if (!$res = sql_fetsel('id_plugin, vmax', 'spip_plugins', 'prefixe = '.sql_quote($prefixe))) {
						// on ne stocke pas de vmax pour les plugins locaux dans la bdd... (parait il)
						if (!isset($insert_plugins[$prefixe])) {
							$insert_plugins[$prefixe] = $le_plugin;
							$insert_plugins_vmax[$prefixe] = $le_paquet['version'];
						} elseif (spip_version_compare($le_paquet['version'], $insert_plugins_vmax[$prefixe], '>')) {
							$insert_plugins[$prefixe] = $le_plugin;
							$insert_plugins_vmax[$prefixe] = $le_paquet['version'];
						}
					} else {
						$id_plugin = $res['id_plugin'];
						$cle_plugins[$prefixe] = $id_plugin;
						// comme justement on ne stocke pas de vmax pour les plugins locaux...
						// il est possible que ce test soit faux. pff.
						if (spip_version_compare($le_paquet['version'], $res['vmax'], '>=')) {
							sql_updateq('spip_plugins', $le_plugin, 'id_plugin='.sql_quote($id_plugin));
						}
					}
				}

				// ajout du prefixe dans le paquet
				$le_paquet['prefixe']     = $prefixe;
				$le_paquet['constante']   = $const_dir;
				$le_paquet['src_archive'] = $chemin;
				$le_paquet['recent']      = isset($recents[$chemin]) ? $recents[$chemin] : 0;
				$le_paquet['installe']    = in_array($chemin, $installes) ? 'oui': 'non'; // est desinstallable ?
				$le_paquet['obsolete']    = 'non';
				$le_paquet['signature']   = $paquet['signature'];

				// le plugin est il actuellement actif ?
				$actif = "non";
				if (isset($actifs[$prefixe])
					and ($actifs[$prefixe]['dir_type'] == $const_dir)
					and ($actifs[$prefixe]['dir'] == $chemin)) {
					$actif = "oui";
				}
				$le_paquet['actif'] = $actif;

				// le plugin etait il actif mais temporairement desactive
				// parce qu'une dependence a disparue ?
				$attente = "non";
				if (isset($attentes[$prefixe])
					and ($attentes[$prefixe]['dir_type'] == $const_dir)
					and ($attentes[$prefixe]['dir'] == $chemin)) {
					$attente = "oui";
					$le_paquet['actif'] = "oui"; // il est presenté dans la liste des actifs (en erreur).
				}
				$le_paquet['attente'] = $attente;

				// on recherche d'eventuelle mises a jour existantes
				if ($maj_version = svp_rechercher_maj_version($prefixe, $le_paquet['version'], $le_paquet['etatnum'])) {
					$le_paquet['maj_version'] = $maj_version;
				}

				$insert_paquets[] = $le_paquet;
			}
		}
	}

	if ($insert_plugins) {
		sql_insertq_multi('spip_plugins', $insert_plugins);
		$pls = sql_allfetsel(array('id_plugin', 'prefixe'), 'spip_plugins', sql_in('prefixe', array_keys($insert_plugins)));
		foreach ($pls as $p) {
			$cle_plugins[$p['prefixe']] = $p['id_plugin'];
		}
	}
	
	if ($insert_paquets) {

		// sert pour le calcul d'obsolescence
		$id_plugin_concernes = array();
		
		foreach ($insert_paquets as $c => $p) {
			$insert_paquets[$c]['id_plugin'] = $cle_plugins[$p['prefixe']];
			$id_plugin_concernes[ $insert_paquets[$c]['id_plugin'] ] = true;

			// remettre les necessite, utilise, librairie dans la cle 0
			// comme SVP
			if ($dep = unserialize($insert_paquets[$c]['dependances']) and is_array($dep)) {
				foreach ($dep as $d => $contenu) {
					if ($contenu) {
						$new = array();
						foreach($contenu as $n) {
							unset($n['id']);
							$new[ strtolower($n['nom']) ] = $n;
						}
						$dep[$d] = array($new);
					}
				}
				$insert_paquets[$c]['dependances'] = serialize($dep);
			}

		}

		sql_insertq_multi('spip_paquets', $insert_paquets);

		svp_corriger_obsolete_paquets( array_keys($id_plugin_concernes) );
	}
}


/**
 * Fait correspondre l'état des métas des plugins actifs & installés
 * avec ceux en base de données dans spip_paquets pour le dépot local 
**/
function svp_base_actualiser_paquets_actifs() {
	$installes  = lire_config('plugin_installes');
	$actifs  = lire_config('plugin');
	$attentes  = lire_config('plugin_attente');

	$locaux = sql_allfetsel(
		array('id_paquet', 'prefixe', 'actif', 'installe', 'attente', 'constante', 'src_archive'),
		'spip_paquets',
		'id_depot='.sql_quote(0));
	$changements = array();

	foreach ($locaux as $l) {
		$copie = $l;
		$prefixe = strtoupper($l['prefixe']);
		// actif ?
		if (isset($actifs[$prefixe])
			and ($actifs[$prefixe]['dir_type'] == $l['constante'])
			and ($actifs[$prefixe]['dir'] == $l['src_archive'])) {
			$copie['actif'] = "oui";
		} else {
			$copie['actif'] = "non";
		}
		
		// attente ?
		if (isset($attentes[$prefixe])
			and ($attentes[$prefixe]['dir_type'] == $l['constante'])
			and ($attentes[$prefixe]['dir'] == $l['src_archive'])) {
			$copie['attente'] = "oui";
			$copie['actif'] = "oui"; // il est presente dans la liste des actifs (en erreur). 
		} else {
			$copie['attente'] = "non";
		}
		
		// installe ?
		if (in_array($l['src_archive'], $installes)) {
			$copie['installe'] = "oui";
		} else {
			$copie['installe'] = "non";
		}

		if ($copie != $l) {
			$changements[ $l['id_paquet'] ] = array(
				'actif'    => $copie['actif'],
				'installe' => $copie['installe'],
				'attente'  => $copie['attente'] );
		}
	}

	if (count($changements)) {
		// On insere, en encapsulant pour sqlite...
		if (sql_preferer_transaction()) {
			sql_demarrer_transaction();
		}

		foreach ($changements as $id_paquet => $data) {
			sql_updateq('spip_paquets', $data, 'id_paquet=' . intval($id_paquet));
		}

		if (sql_preferer_transaction()) {
			sql_terminer_transaction();
		}
	}

}

/**
 * Construit le contenu multilangue (tag <multi>) des balises nom, slogan
 * et description à partir des items de langue contenus dans le fichier
 * paquet-prefixe_langue.php
 *
 * @param string $prefixe     Préfixe du plugin
 * @param string $dir_source  Chemin d'accès du plugin
 * @return array
 *     Tableau clé => texte multilangue entre <multi> et </multi>
 *     Les clés peuvent être 'nom', 'slogan' et 'description', mais
 *     seules les clés ayant une explication dans la chaine de langue
 *     sont retournées.
 */
function svp_compiler_multis($prefixe, $dir_source) {

	$multis =array();
	// ici on cherche le fichier et les cles avec un prefixe en minuscule systematiquement...
	$prefixe = strtolower($prefixe);
	$module = "paquet-$prefixe";
	$item_nom = $prefixe . "_nom";
	$item_slogan = $prefixe . "_slogan";
	$item_description = $prefixe . "_description";

	// On cherche tous les fichiers de langue destines a la traduction du paquet.xml
	if ($fichiers_langue = glob($dir_source . "/lang/{$module}_*.php")) {
		include_spip('inc/lang_liste');
		$nom = $slogan = $description = '';
		foreach ($fichiers_langue as $_fichier_langue) {
			$nom_fichier = basename($_fichier_langue, '.php');
			$langue = substr($nom_fichier, strlen($module) + 1 - strlen($nom_fichier));
			// Si la langue est reconnue, on traite la liste des items de langue
			if (isset($GLOBALS['codes_langues'][$langue])) {
				$GLOBALS['idx_lang'] = $langue;
				include($_fichier_langue);
				foreach ($GLOBALS[$langue] as $_item => $_traduction) {
					if ($_traduction = trim($_traduction)) {
						if ($_item == $item_nom)
							$nom .= "[$langue]$_traduction";
						if ($_item == $item_slogan)
							$slogan .= "[$langue]$_traduction";
						if ($_item == $item_description)
							$description .= "[$langue]$_traduction";
					}
				}
			}
		}

		// Finaliser la construction des balises multi
		if ($nom) $multis['nom'] = "<multi>$nom</multi>";
		if ($slogan) $multis['slogan'] = "<multi>$slogan</multi>";
		if ($description) $multis['description'] = "<multi>$description</multi>";
	}

	return $multis;
}


/**
 * Met à jour les informations d'obsolescence des paquets locaux.
 *
 * L'obsolescence indique qu'un paquet est plus ancien (de version ou état
 * moins avancé) qu'un autre également présent localement.
 * 
 * @param array $ids_plugin
 *     Liste d'identifiants de plugins
 *     En cas d'absence, passera sur tous les paquets locaux
**/
function svp_corriger_obsolete_paquets($ids_plugin = array()) {
	// on minimise au maximum le nombre de requetes.
	// 1 pour lister les paquets
	// 1 pour mettre à jour les obsoletes à oui
	// 1 pour mettre à jour les obsoletes à non

	$where = array('pa.id_plugin = pl.id_plugin', 'id_depot='.sql_quote(0));
	if ($ids_plugin) {
		$where[] = sql_in('pl.id_plugin', $ids_plugin);
	}

	// comme l'on a de nouveaux paquets locaux...
	// certains sont peut etre devenus obsoletes
	// parmis tous les plugins locaux presents
	// concernes par les memes prefixes que les plugins ajoutes.
	$obsoletes = array();
	$changements = array();

	$paquets = sql_allfetsel(
		array('pa.id_paquet', 'pl.prefixe', 'pa.version', 'pa.etatnum', 'pa.obsolete', 'pa.compatibilite_spip'),
		array('spip_paquets AS pa', 'spip_plugins AS pl'),
		$where);

	// L'obsolescence doit tenir compte de la compatibilité avec notre version de SPIP en cours
	foreach ($paquets as $c => $p) {
		$paquets[$c]['compatible'] = plugin_version_compatible($p['compatibilite_spip'], $GLOBALS['spip_version_branche'], 'spip');
	}

	foreach ($paquets as $c => $p) {

		$obsoletes[$p['prefixe']][] = $c;

		// si 2 paquet locaux ont le meme prefixe,
		// sont compatibles avec notre SPIP,
		// mais pas la meme version,
		// l'un est obsolete : la version la plus ancienne
		// Si version et etat sont egaux, on ne decide pas d'obsolescence.
		if (count($obsoletes[$p['prefixe']]) > 1) {
			foreach ($obsoletes[$p['prefixe']] as $cle) {
				if ($cle == $c) continue;
				if (!$paquets[$c]['compatible']) continue;

				// je suis plus petit qu'un autre
				if (spip_version_compare($paquets[$c]['version'], $paquets[$cle]['version'], '<')) {
					if ($paquets[$c]['etatnum'] <= $paquets[$cle]['etatnum']) {
						if ($paquets[$c]['obsolete'] != 'oui') {
							$paquets[$c]['obsolete'] = 'oui';
							$changements[$c] = true;
						}
					}
				}

				// je suis plus grand ou egal a un autre...
				else {
					// je suis strictement plus grand qu'un autre...
					if (spip_version_compare($paquets[$c]['version'], $paquets[$cle]['version'], '>')) {
						// si mon etat est meilleur, rendre obsolete les autres
						if ($paquets[$c]['etatnum'] >= $paquets[$cle]['etatnum']) {
								if ($paquets[$cle]['obsolete'] != 'oui') {
									$paquets[$cle]['obsolete'] = 'oui';
									$changements[$cle] = true;
								}
						}
					}

					// je suis egal a un autre
					// si mon etat est strictement meilleur, rendre obsolete les autres
					elseif ($paquets[$c]['etatnum'] > $paquets[$cle]['etatnum']) {
							if ($paquets[$cle]['obsolete'] != 'oui') {
								$paquets[$cle]['obsolete'] = 'oui';
								$changements[$cle] = true;
							}
					}
				}

			}
		} else {
			if ($paquets[$c]['obsolete'] != 'non') {
				$paquets[$c]['obsolete'] = 'non';
				$changements[$c] = true;
			}
		}
	}

	if (count($changements)) {
		$oui = $non = array();
		foreach ($changements as $c => $null) {
			if ($paquets[$c]['obsolete'] == 'oui') {
				$oui[] = $paquets[$c]['id_paquet'];
			} else {
				$non[] = $paquets[$c]['id_paquet'];
			}
		}

		if ($oui) {
			sql_updateq('spip_paquets', array('obsolete'=>'oui'), sql_in('id_paquet', $oui));
		}
		if ($non) {
			sql_updateq('spip_paquets', array('obsolete'=>'non'), sql_in('id_paquet', $non));
		}
	}
}




/**
 * Supprime les plugins devenus orphelins dans cette liste.
 *
 * @param array $ids_plugin
 *     Liste d'identifiants de plugins
 * @return array
 *     Liste de plugins non orphelins
**/
function svp_supprimer_plugins_orphelins($ids_plugin) {
	// tous les plugins encore lies a des depots...
	if ($ids_plugin) {
		$p = sql_allfetsel('DISTINCT(p.id_plugin)', array('spip_plugins AS p', 'spip_paquets AS pa'), array(sql_in('p.id_plugin', $ids_plugin), 'p.id_plugin=pa.id_plugin'));
		$p = array_map('array_shift', $p);
		$diff = array_diff($ids_plugin, $p);
		// pour chaque plugin non encore utilise, on les vire !
		sql_delete('spip_plugins', sql_in('id_plugin', $diff));
		return $p; // les plugins encore en vie !
	}
	return array();
}


/**
 * Cherche dans les dépots distants un plugin qui serait plus à jour
 * que le prefixe, version et état que l'on transmet 
 *
 * @param string $prefixe
 * 		Préfixe du plugin
 * @param string $version
 * 		Version du paquet à comparer
 * @param int $etatnum
 * 		État du paquet numérique
 * @return string
 * 		Version plus à jour, sinon rien
**/
function svp_rechercher_maj_version($prefixe, $version, $etatnum) {

	$maj_version = "";

	if ($res = sql_allfetsel(
		array('pl.id_plugin', 'pa.version'),
		array('spip_plugins AS pl', 'spip_paquets AS pa'),
		array(
			'pl.id_plugin = pa.id_plugin',
			'pa.id_depot>' . sql_quote(0),
			'pl.prefixe=' . sql_quote($prefixe),
			'pa.etatnum>=' . sql_quote($etatnum))))
		{

		foreach ($res as $paquet_distant) {
			// si version superieure et etat identique ou meilleur,
			// c'est que c'est une mise a jour possible !
			if (spip_version_compare($paquet_distant['version'],$version,'>')) {
				if (!strlen($maj_version) or spip_version_compare($paquet_distant['version'], $maj_version, '>')) {
					$maj_version = $paquet_distant['version'];
				}
				# a voir si on utilisera...
				# "superieur"		=> "varchar(3) DEFAULT 'non' NOT NULL",
				# // superieur : version plus recente disponible (distant) d'un plugin (actif?) existant
			}
		}
	}

	return $maj_version;
}




/**
 * Actualise l'information 'maj_version' pour tous les paquets locaux
**/
function svp_actualiser_maj_version() {
	$update = array();
	// tous les paquets locaux
	if ($locaux = sql_allfetsel(
		array('id_paquet', 'prefixe', 'version', 'maj_version', 'etatnum'),
		array('spip_paquets'),
		array('id_depot=' . sql_quote(0))))
	{
		foreach ($locaux as $paquet) {
			$new_maj_version = svp_rechercher_maj_version($paquet['prefixe'], $paquet['version'], $paquet['etatnum']);
			if ($new_maj_version != $paquet['maj_version']) {
				$update[$paquet['id_paquet']] = array('maj_version' => $new_maj_version);
			}
		}
	}
	if ($update) {
		// On insere, en encapsulant pour sqlite...
		if (sql_preferer_transaction()) {
			sql_demarrer_transaction();
		}

		foreach ($update as $id_paquet => $data) {
			sql_updateq('spip_paquets', $data, 'id_paquet=' . intval($id_paquet));
		}

		if (sql_preferer_transaction()) {
			sql_terminer_transaction();
		}
	}
}

?>
svp_outiller.php000066600000037244152503074170010026 0ustar00<?php

/**
 * Fichier de fonctions 
 *
 * @plugin SVP pour SPIP
 * @license GPL
 * @package SPIP\SVP\Fonctions
**/

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


if (!defined('_SVP_VERSION_SPIP_MIN')) {
/**
 * Version SPIP minimale quand un plugin ne le precise pas
 *
 * Version SPIP correspondant à l'apparition des plugins */
	define('_SVP_VERSION_SPIP_MIN', '1.9.0');
}

if (!defined('_SVP_VERSION_SPIP_MAX')) {
/**
 * Version SPIP maximale 
 *
 * Pour l'instant on ne connait pas la borne sup exacte */
	define('_SVP_VERSION_SPIP_MAX', '3.1.99');
}

/**
 * Liste des branches significatives de SPIP et de leurs bornes (versions min et max)
 *
 * À mettre a jour en fonction des sorties
 * @global array $GLOBALS['infos_branches_spip']
 */
$GLOBALS['infos_branches_spip'] = array(
	'1.9' => array(_SVP_VERSION_SPIP_MIN,'1.9.2'),
	'2.0' => array('2.0.0','2.0.99'),
	'2.1' => array('2.1.0','2.1.99'),
	'3.0' => array('3.0.0','3.0.99'),
	'3.1' => array('3.1.0',_SVP_VERSION_SPIP_MAX)
);
# define('_INFOS_BRANCHES_SPIP', serialize($infos_branches_spip));

/**
 * Liste des licences de plugin
 *
 * @global array $GLOBALS['licences_plugin']
 */
$GLOBALS['licences_plugin'] = array(
	'apache' => array(
		'versions' => array('2.0', '1.1', '1.0'),
		'nom' => 'Apache licence, version @version@',
		'url' => 'http://www.apache.org/licenses/LICENSE-@version@'),
	'art' => array(
		'versions' => array('1.3'),
		'nom' => 'Art libre @version@',
		'url' => 'http://artlibre.org/licence/lal'),
	'mit' => array(
		'versions' => array(),
		'nom' => 'MIT',
		'url' => 'http://opensource.org/licenses/mit-license.php'),
	'bsd' => array(
		'versions' => array(),
		'nom' => 'BSD',
		'url' => 'http://www.freebsd.org/copyright/license.html'),
	'agpl' => array(
		'versions' => array('3'),
		'nom' => 'AGPL @version@',
		'url' => 'http://www.gnu.org/licenses/agpl.html'),
	'fdl' => array(
		'versions' => array('1.3', '1.2', '1.1'),
		'nom' => 'FDL @version@',
		'url' => 'http://www.gnu.org/licenses/fdl-@version@.html'),
	'lgpl' => array(
		'versions' => array('3.0', '2.1'),
		'nom' => array('3.0' => 'LGPL 3', '2.1' => 'LGPL 2.1'),
		'url' => 'http://www.gnu.org/licenses/lgpl-@version@.html'),
	'gpl' => array(
		'versions' => array('3', '2', '1'),
		'nom' => 'GPL @version@',
		'url' => 'http://www.gnu.org/licenses/gpl-@version@.0.html'),
	'ccby' => array(
		'versions' => array('2.0', '2.5', '3.0'),
		'suffixes' => array('-sa', '-nc', '-nd', '-nc-nd', '-nc-sa'),
		'nom' => 'CC BY@suffixe@ @version@',
		'url' => 'http://creativecommons.org/licenses/by@suffixe@/@version@/')
);
# define('_LICENCES_PLUGIN', serialize($licences_plugin));

/**
 * Fusionne 2 intervalles de compatibilité 
 *
 * Soit '[1.9;2.1]' et '[2.1;3.0.*]', la fonction retourne '[1.9;3.0.*]'
 *
 * En gros la fonction est utilisé pour calculer l'intervalle de validité
 * d'un plugin ayant plusieurs paquets avec des compatibilités différentes.
 * La compatibilité du plugin est le total de toutes les compatibilités.
 * 
 * @param string $intervalle_a
 *     Intervalle de compatibilité
 * @param string $intervalle_b
 *     Intervalle de compatibilité
 * @return string
 *     Intervalle de compatibilité
**/
function fusionner_intervalles($intervalle_a, $intervalle_b) {

	// On recupere les bornes de chaque intervalle
	$borne_a = extraire_bornes($intervalle_a);
	$borne_b = extraire_bornes($intervalle_b);

	// On initialise la borne min de chaque intervalle a 1.9.0 inclus si vide
	if (!$borne_a['min']['valeur']) {
		$borne_a['min']['valeur'] = _SVP_VERSION_SPIP_MIN;
		$borne_a['min']['incluse'] = true;
	}
	if (!$borne_b['min']['valeur']) {
		$borne_b['min']['valeur'] = _SVP_VERSION_SPIP_MIN;
		$borne_b['min']['incluse'] = true;
	}

	// On initialise la borne max de chaque intervalle a la version SPIP max incluse si vide
	if (!$borne_a['max']['valeur']) {
		$borne_a['max']['valeur'] = _SVP_VERSION_SPIP_MAX;
		$borne_a['max']['incluse'] = true;
	}
	if (!$borne_b['max']['valeur']) {
		$borne_b['max']['valeur'] = _SVP_VERSION_SPIP_MAX;
		$borne_b['max']['incluse'] = true;
	}

	// On calcul maintenant :
	// -- la borne min de l'intervalle fusionne = min(min_a, min_b)
	if (spip_version_compare($borne_a['min']['valeur'], $borne_b['min']['valeur'], '<='))
		$bornes_fusionnees['min'] = $borne_a['min'];
	else
		$bornes_fusionnees['min'] = $borne_b['min'];
	// -- la borne max de l'intervalle fusionne = max(max_a, max_b)
	if (spip_version_compare($borne_a['max']['valeur'], $borne_b['max']['valeur'], '<='))
		$bornes_fusionnees['max'] = $borne_b['max'];
	else
		$bornes_fusionnees['max'] = $borne_a['max'];

	return construire_intervalle($bornes_fusionnees);
}

/**
 * Extrait les valeurs d'un intervalle de compatibilité.
 *
 * Calcule les valeurs min, max et si ces valeurs sont intégrées ou non
 * à l'intervalle.
 *
 * @param string $intervalle
 *     Intervalle de compatibilité, tel que '[2.1;3.0]'
 * @param bool $initialiser
 *     - True pour mettre les valeurs connues mini et maxi de SPIP lorsque
 *     les bornes ne sont pas renseignées dans l'intervalle.
 *     - False pour ne rien mettre sinon.
 * @return array
 *     Tableau avec les index :
 *     - min : la borne inférieure, qui contient les index 'valeur' et 'incluse'
 *     - max : la borne  supérieure, qui contient les index 'valeur' et 'incluse'
 *     Le sous index 'incluse' vaut true si cette borne est incluse dans l'intervalle.
**/
function extraire_bornes($intervalle, $initialiser=false) {
	static $borne_vide = array('valeur' => '', 'incluse' => false);
	static $borne_inf_init = array('valeur' => _SVP_VERSION_SPIP_MIN, 'incluse' => true);
	static $borne_sup_init = array('valeur' => _SVP_VERSION_SPIP_MAX, 'incluse' => true);

	if ($initialiser)
		$bornes = array('min' => $borne_inf_init, 'max' => $borne_sup_init);
	else
		$bornes = array('min' => $borne_vide, 'max' => $borne_vide);

	if ($intervalle
	AND preg_match(',^[\[\(\]]([0-9.a-zRC\s\-]*)[;]([0-9.a-zRC\s\-\*]*)[\]\)\[]$,Uis', $intervalle, $matches)) {
		if ($matches[1]) {
			$bornes['min']['valeur'] = trim($matches[1]);
			$bornes['min']['incluse'] = ($intervalle{0} == "[");
		}
		if ($matches[2]) {
			$bornes['max']['valeur'] = trim($matches[2]);
			$bornes['max']['incluse'] = (substr($intervalle,-1) == "]");
		}
	}

	return $bornes;
}

/**
 * Contruit un intervalle de compatibilité 
 *
 * @param array $bornes
 *     L'intervalle décrit sous forme de tableau avec pour index :
 *     - min : la borne inférieure, qui contient les index 'valeur' et 'incluse'
 *     - max : la borne  supérieure, qui contient les index 'valeur' et 'incluse'
 *     Le sous index 'incluse' vaut true si cette borne est incluse dans l'intervalle.
 * @param string $dtd
 *     DTD de destination (paquet ou plugin) qui influera sur l'écriture à faire
 *     en utilisant des parenthèses ou des crochets pour définir l'exclusion d'une intervalle
 *     tel que ']2.1.2,3.0.1[' (paquet) ou '(2.1.2,3.0.1)' (plugin)
 * @return string
 *     Intervalle de compatibilité tel que '[2.1;3.0]'
**/
function construire_intervalle($bornes, $dtd='paquet') {
	return ($bornes['min']['incluse'] ? '[' : ($dtd=='paquet' ? ']' : '('))
			. $bornes['min']['valeur'] . ';' . $bornes['max']['valeur']
			. ($bornes['max']['incluse'] ? ']' : ($dtd=='paquet' ? '[' : ')'));
}


/**
 * Retourne la liste des branches de SPIP comprises dans un intervalle
 * de compatibilité donné.
 *
 * @param string $intervalle
 *     Intervalle de compatibilité, tel que [2.0.0;3.0.0]
 * @return string
 *     Branches de SPIP séparées par des virgules, tel que 2.0,2.1,3.0
**/
function compiler_branches_spip($intervalle) {
	include_spip('plugins/installer');

	global $infos_branches_spip;
	$liste_branches_spip = array_keys($GLOBALS['infos_branches_spip']);
		
	$bornes = extraire_bornes($intervalle, false);
	// On traite d'abord les cas ou l'intervalle est :
	// - vide 
	// - non vide mais avec les deux bornes vides
	// Dans ces cas la compatibilite est totale, on renvoie toutes les branches
	if (!$intervalle OR (!$bornes['min']['valeur'] AND !$bornes['max']['valeur']))
		return implode(',', $liste_branches_spip);

	// On force l'initialisation des bornes et on les nettoie des suffixes d'etat
	$bornes = extraire_bornes($intervalle, true);
	// Si les bornes sont en dehors de l'intervalle [_SVP_VERSION_SPIP_MIN;_SVP_VERSION_SPIP_MAX] on le reduit
	if (spip_version_compare($bornes['min']['valeur'], _SVP_VERSION_SPIP_MIN, '<')) {
		$bornes['min']['valeur'] = _SVP_VERSION_SPIP_MIN;
		$bornes['min']['incluse'] = true;
	}
	if (spip_version_compare(_SVP_VERSION_SPIP_MAX, $bornes['max']['valeur'], '<=')) {
		$bornes['max']['valeur'] = _SVP_VERSION_SPIP_MAX;
		$bornes['max']['incluse'] = true;
	}
	// On les nettoie des suffixes d'etat
	$borne_inf = strtolower(preg_replace(',([0-9])[\s-.]?(dev|alpha|a|beta|b|rc|pl|p),i','\\1',$bornes['min']['valeur']));
	$borne_sup = strtolower(preg_replace(',([0-9])[\s-.]?(dev|alpha|a|beta|b|rc|pl|p),i','\\1',$bornes['max']['valeur']));

	// On determine les branches inf et sup issues du phrasage de l'intervalle
	// -- on initialise la branche inf de l'intervalle que l'on va preciser ensuite
	$t = explode('.', $borne_inf);
	$branche_inf = $t[0] . '.' . $t[1];
	// -- pour eviter toutes erreur fatale on verifie que la branche est bien dans la liste des possibles
	// -- -> si non, on renvoie vide
	if (!in_array($branche_inf, $liste_branches_spip))
		return '';
	// -- on complete la borne inf de l'intervalle de x.y en x.y.z et on determine la vraie branche
	if (!isset($t[2]) or !$t[2]) {
		if ($bornes['min']['incluse'])
			$borne_inf = $infos_branches_spip[$branche_inf][0];
		else {
			$branche_inf = $liste_branches_spip[array_search($branche_inf, $liste_branches_spip)+1];
			$borne_inf = $infos_branches_spip[$branche_inf][0];
		}
	}
	
	// -- on initialise la branche sup de l'intervalle que l'on va preciser ensuite
	$t = explode('.', $borne_sup);
	// des gens mettent juste * (pas glop)
	$branche_sup = $t[0] . (isset($t[1]) ? '.' . $t[1] : '');

	// -- pour eviter toutes erreur fatale on verifie que la branche est bien dans la liste des possibles
	// -- -> si non, on renvoie vide
	if (!in_array($branche_sup, $liste_branches_spip))
		return '';
	// -- on complete la borne sup de l'intervalle de x.y en x.y.z et on determine la vraie branche
	if (!isset($t[2]) or !$t[2]) {
		if ($bornes['max']['incluse'])
			$borne_sup = $infos_branches_spip[$branche_sup][1];
		else {
			$branche_sup = $liste_branches_spip[array_search($branche_sup, $liste_branches_spip)-1];
			$borne_sup = $infos_branches_spip[$branche_sup][1];
		}
	}

	// -- on verifie que les bornes sont bien dans l'ordre : 
	//    -> sinon on retourne la branche sup uniquement
	if (spip_version_compare($borne_inf, $borne_sup, '>='))
		return $branche_sup;

	// A ce stade, on a un intervalle ferme en bornes ou en branches
	// Il suffit de trouver les branches qui y sont incluses, sachant que les branches inf et sup 
	// le sont a coup sur maintenant
	$index_inf = array_search($branche_inf, $liste_branches_spip);
	$index_sup = array_search($branche_sup, $liste_branches_spip);
	$liste = array();
	for ($i = $index_inf; $i <= $index_sup; $i++) {
		$liste[] = $liste_branches_spip[$i];
	}

	return implode(',', $liste);
}


/**
 * Transforme un texte écrit en entités HTML, dans le charset du site 
 *
 * @param string $texte
 *     Texte avec des entités HTML
 * @return string $texte
 *     Texte dans le charset du site
**/
function entite2charset($texte) {
	if (!strlen($texte)) return '';
	include_spip('inc/charsets');
	return unicode2charset(html_entity_decode(preg_replace('/&([lg]t;)/S', '&amp;\1', $texte), ENT_NOQUOTES, $GLOBALS['meta']['charset']));
}

/**
 * Teste si 2 balises XML sont identiques 
 *
 * @param array|string $balise1
 *     Balise à comparer
 * @param array|string $balise2
 *     Balise à comparer
 * @return bool
 *     True si elles sont identiques, false sinon.
**/
function balise_identique($balise1, $balise2) {
	if (is_array($balise1)) {
		foreach ($balise1 as $_attribut1 => $_valeur1){
			if (!array_key_exists($_attribut1, $balise2))
				return false;
			else
				if ($_valeur1 != $balise2[$_attribut1])
					return false;
		}
		return true;
	}
	else
		return ($balise1 == $balise2);
}


/**
 * Déterminer la licence exacte avec un nom et un lien de doc standardisé
 *
 * @param string $prefixe
 *     Préfixe de la licence tel que gnu, free, cc, creative common
 * @param string $nom
 *     Nom de la licence tel que gpl, lgpl, agpl, fdl, mit, bsd...
 * @param string $suffixe
 *     Suffixe de la licence tel que licence, -sharealike, -nc-nd ...
 * @param string $version
 *     Version de la licence tel que 3.0
 * @return array
 *     Si la licence est connu, retourne 2 index :
 *     - nom : le nom le la licence
 *     - url : lien vers la licence
 */
function definir_licence($prefixe, $nom, $suffixe, $version) {
	global $licences_plugin;
	$licence = array();

	$prefixe = strtolower($prefixe);
	$nom = strtolower($nom);
	$suffixe = strtolower($suffixe);

	if (((trim($prefixe) == 'creative common') AND ($nom == 'attribution'))
	OR (($prefixe == 'cc') AND ($nom == 'by')))
		$nom = 'ccby';

	if (array_key_exists($nom, $licences_plugin)) {
		if (!$licences_plugin[$nom]['versions']) {
			// La licence n'est pas versionnee : on affecte donc directement le nom et l'url
			$licence['nom'] = $licences_plugin[$nom]['nom'];
			$licence['url'] = $licences_plugin[$nom]['url'];
		}
		else {
			// Si la version est pas bonne on prend la plus recente
			if (!$version OR !in_array($version, $licences_plugin[$nom]['versions'], true))
				$version = $licences_plugin[$nom]['versions'][0];
			if (is_array($licences_plugin[$nom]['nom']))
				$licence['nom'] = $licences_plugin[$nom]['nom'][$version];
			else
				$licence['nom'] = str_replace('@version@', $version, $licences_plugin[$nom]['nom']);
			$licence['url'] = str_replace('@version@', $version, $licences_plugin[$nom]['url']);

			if ($nom == 'ccby') {
				if ($suffixe == '-sharealike')
					$suffixe = '-sa';
				if (!$suffixe OR !in_array($suffixe, $licences_plugin[$nom]['suffixes'], true))
					$suffixe = '';
				$licence['nom'] = str_replace('@suffixe@', strtoupper($suffixe), $licence['nom']);
				$licence['url'] = str_replace('@suffixe@', $suffixe, $licence['url']);
			}
		}
	}

	return $licence;
}

/**
 * Liste les librairies présentes 
 *
 * Cherche des librairie dans tous les dossiers 'lib' présents dans chaque
 * chemin déclaré (plugins, squelettes, SPIP). Un répertoire dans un dossier
 * 'lib' est considéré comme une librairie, et le nom de ce répertoire est
 * utilisé comme nom de la librairie.
 * 
 * @return array
 *     Tableau de couples (nom de la librairie => répertoire de la librairie)
**/
function svp_lister_librairies() {
	$libs = array();
	foreach (array_reverse(creer_chemin()) as $d) {
		if (is_dir($dir = $d.'lib/') AND $t = @opendir($dir)) {
			while (($f = readdir($t)) !== false) {
				if ($f[0] != '.' AND is_dir("$dir/$f"))
					$libs[$f] = $dir;
			}
		}
	}
	return $libs;
}



/**
 * Retourne '00x.00y.00z' à partir de 'x.y.z'
 * 
 * Retourne la chaine de la version x.y.z sous une forme normalisée
 * permettant le tri naturel. On complète à gauche d'un nombre de zéro
 * manquant pour aller à 3 caractères entre chaque point.
 *
 * @see denormaliser_version()
 * @param string $version
 *     Numéro de version dénormalisée
 * @return string
 *     Numéro de version normalisée
**/
function normaliser_version($version='') {

	$version_normalisee = '';

	if (preg_match(',([0-9.]+)[\s-.]?(dev|alpha|a|beta|b|rc|pl|p)?,i', $version, $matches)) {
		if (isset($matches[1]) and $matches[1]) {
			$v = explode('.', $matches[1]);
			foreach($v as $_nombre) {
				$vn[] = str_pad($_nombre, 3, '0', STR_PAD_LEFT);
			}
			$version_normalisee = implode('.', $vn);
			if (isset($matches[2]) and $matches[2])
				$version_normalisee =  $version_normalisee . '-' . $matches[2];
		}
	}

	return $version_normalisee;
}

?>
svp_depoter_distant.php000066600000102534152503074170011352 0ustar00<?php

/**
 * Traitement des dépots distants
 *
 * Un dépot distant est une liste de paquets que l'on peut télécharger.
 * Cette liste est donnée par un fichier XML que l'on peut relire
 * régulièrement pour actualiser nos informations. Effectivement, chaque
 * paquet (et plugin) décrit est inséré en base de données pour nous
 * faciliter les recherches.
 * 
 * @plugin SVP pour SPIP
 * @license GPL
 * @package SPIP\SVP\Depots
 */
 
if (!defined("_ECRIRE_INC_VERSION")) return;
include_spip('inc/plugin');
include_spip('inc/svp_phraser');


/**
 * Ajout d'un dépot et de son contenu (paquets, plugins) dans la base de données
 *
 * Si une erreur survient (syntaxe XML incorrecte, pas de plugin dans le dépot),
 * son texte est placé dans le paramètre $erreur
 * 
 * @param string $url
 *     URL du fichier XML de description du dépot
 * @param string $erreur
 *     Texte d'un éventuel message d'erreur
 * @return bool
 *     true si le dépot est ajouté correctement, false sinon
 */
function svp_ajouter_depot($url, &$erreur='') {
	include_spip('inc/distant');

	// On considere que l'url a deja ete validee (correcte et nouveau depot)
	$url = trim($url);
	// Ajout du depot dans la table spip_depots. Les compteurs de paquets et de plugins
	// sont mis a jour apres le traitement des paquets

	// on recupère le XML
	$fichier_xml = copie_locale($url, 'modif');
	if (!$fichier_xml) {
		$erreur = _T('svp:message_nok_xml_non_recupere', array('fichier' => $url));
		return false;
	}

	$fichier_xml = _DIR_RACINE . $fichier_xml;

	// Lire les donnees d'un depot de paquets
	$infos = svp_phraser_depot($fichier_xml);
	if (!$infos) {
		$erreur = _T('svp:message_nok_xml_non_conforme', array('fichier' => $url));
		return false;
	}

	$titre = filtrer_entites($infos['depot']['titre']);
	$champs = array('titre' => $titre,
					'descriptif' => filtrer_entites($infos['depot']['descriptif']),
					'type' => $infos['depot']['type'],
					'url_serveur' => $infos['depot']['url_serveur'],
					'url_brouteur' => $infos['depot']['url_brouteur'],
					'url_archives' => $infos['depot']['url_archives'],
					'url_commits' => $infos['depot']['url_commits'],
					'xml_paquets'=> $url,
					'sha_paquets'=> sha1_file($fichier_xml),
					'nbr_paquets' => 0,
					'nbr_plugins' => 0,
					'nbr_autres' => 0);

	// verifier avant l'insertion que le depot n'existe pas deja
	// car la recuperation pouvant etre longue on risque le probleme en cas de concurrence
	if (sql_countsel('spip_depots','xml_paquets='.sql_quote($url))){
		$erreur = _T('svp:message_nok_depot_deja_ajoute', array('url' => $url));
		return false;
	}
	elseif (!$id_depot = sql_insertq('spip_depots', $champs)) {
		$erreur = _T('svp:message_nok_sql_insert_depot', array('objet' => "$titre ($url)"));
		return false;
	}

	// Ajout des paquets dans spip_paquets et actualisation des plugins dans spip_plugins
	$ok = svp_actualiser_paquets($id_depot, $infos['paquets'], $nb_paquets, $nb_plugins, $nb_autres);
	if (!$ok OR ($nb_paquets == 0)) {
		// Si une erreur s'est produite, on supprime le depot deja insere
		sql_delete('spip_depots','id_depot='.sql_quote($id_depot));
		if (!$ok)
			$erreur = _T('svp:message_nok_xml_non_conforme', array('fichier' => $url));
		else
			$erreur = _T('svp:message_nok_aucun_paquet_ajoute', array('url' => $url));
		return false;
	}

	// On met à jour le nombre de paquets et de plugins du depot maintenant !
	sql_updateq('spip_depots',
				array('nbr_paquets'=> $nb_paquets, 'nbr_plugins'=> $nb_plugins, 'nbr_autres'=> $nb_autres),
				'id_depot=' . sql_quote($id_depot));

	// On vide les paquets locaux pour mettre a jour leurs donnees relatives au depot
	// comme les mises a jour disponibles
	include_spip('inc/svp_depoter_local');
	svp_base_supprimer_paquets_locaux();
	
	return true;
}

/**
 * Suppression d'un dépot et de son contenu (paquets, plugins) dans la base de données
 *
 * Cette suppression entraîne des recalcul comme les versions maximales
 * des plugins téléchargeables qui peuvent changer.
 * 
 * @param int $id
 *     Identifiant du dépot
 * @return bool
 *     false si le dépot n'est pas trouvé, true sinon
 */
function svp_supprimer_depot($id){
	$id = intval($id);

	// Pas de depot a cet id ?
	if (!$id_depot = sql_getfetsel('id_depot', 'spip_depots', 'id_depot='. sql_quote($id)) ){
		return false;
	}

	// on calcule les versions max des plugins heberges par le depot
	$vmax =array();

	if ($resultats = sql_allfetsel('id_plugin, version', 'spip_paquets', 'id_depot='. sql_quote($id))) {
		foreach ($resultats as $paquet) {
			$id_plugin = $paquet['id_plugin'];
			if (!isset($vmax[$id_plugin])
			OR (spip_version_compare($vmax[$id_plugin], $paquet['version'], '<'))) 
				$vmax[$id_plugin] = $paquet['version'];
		}
	}

	// On supprime les paquets heberges par le depot
	sql_delete('spip_paquets','id_depot='.sql_quote($id_depot));

	// Si on est pas en mode runtime, on utilise surement l'espace public pour afficher les plugins.
	// Il faut donc verifier que les urls suivent bien la mise à jour
	// Donc avant de nettoyer la base des plugins du depot ayant disparus on supprime toutes les urls
	// associees a ce depot : on les recreera apres le nettoyage
	if (!_SVP_MODE_RUNTIME)
		svp_actualiser_url_plugins($id_depot);

	// Nettoyer les autres relations à ce dépot
	svp_nettoyer_apres_suppression($id_depot, $vmax);

	// Si on est pas en mode runtime, on utilise surement l'espace public pour afficher les plugins.
	// Il faut donc s'assurer que les urls suivent bien la mise à jour
	// - on supprime toutes les urls plugin
	// - on les regenere pour la liste des plugins mise a jour
	if (!_SVP_MODE_RUNTIME)
		svp_actualiser_url_plugins($id_depot);

	// On supprime le depot lui-meme
	sql_delete('spip_depots','id_depot='.sql_quote($id_depot));

	// on supprime les paquets locaux pour reactualisation
	include_spip('inc/svp_depoter_local');
	svp_base_supprimer_paquets_locaux();

	return true;
}


/**
 * Nettoyer la base de données après la suppression d'un dépot 
 *
 * Supprime
 * - les liens des plugins avec le dépot (table spip_depots_plugins)
 * - les plugins dont aucun paquet n'est encore hébergé par un dépot restant (table spip_plugins)
 * Remet à zéro la version maximale des plugins ayant vu leur paquet en version maximale supprimée
 * 
 * @param int $id_depot
 *     Identifiant du dépot
 * @param array $vmax
 *     Tableau de la version maximale des plugins du dépot supprimé
 *     Tableau (id_plugin => version maximale)
 * @return bool
 *     true toujours.
**/
function svp_nettoyer_apres_suppression($id_depot, $vmax) {

	// On rapatrie la liste des plugins du depot qui servira apres qu'on ait supprime les liens 
	// de la table spip_depots_plugins
	$liens = sql_allfetsel('id_plugin', 'spip_depots_plugins', 'id_depot='.sql_quote($id_depot));
	$plugins_depot = array_map('reset', $liens);

	// On peut donc supprimer tous ces liens *plugins-depots* du depot
	sql_delete('spip_depots_plugins', 'id_depot='.sql_quote($id_depot));

	// On verifie pour chaque plugin concerne par la disparition de paquets si c'est la version
	// la plus elevee qui a ete supprimee.
	// Si oui, on positionne le vmax a 0, ce qui permettra de remettre a jour le plugin systematiquement
	// a la prochaine actualisation. 
	// Cette operation est necessaire car on n'impose pas que les informations du plugin soient identiques
	// pour chaque paquet !!!
	
	// On insere, en encapsulant pour sqlite...
	if (sql_preferer_transaction()) {
		sql_demarrer_transaction();
	}
	
	if ($resultats = sql_allfetsel('id_plugin, vmax', 'spip_plugins', sql_in('id_plugin', $plugins_depot))) {
		foreach ($resultats as $plugin) {
			if (spip_version_compare($plugin['vmax'], $vmax[$plugin['id_plugin']], '='))
				sql_updateq('spip_plugins',	array('vmax' => ''),	'id_plugin=' . sql_quote($plugin['id_plugin']));
		}
	}

	if (sql_preferer_transaction()) {
		sql_terminer_transaction();
	}
	
	// Maintenant on calcule la liste des plugins du depot qui ne sont pas heberges 
	// par un autre depot => donc a supprimer
	// - Liste de tous les plugins encore lies a un autre depot
	// tous les plugins correspondants aux anciens paquets
	$plugins_restants = sql_allfetsel('DISTINCT(id_plugin)', 'spip_paquets', sql_in('id_plugin', $plugins_depot));
	$plugins_restants = array_map('array_shift', $plugins_restants);

	// - L'intersection des deux tableaux renvoie les plugins a supprimer	
	$plugins_a_supprimer = array_diff($plugins_depot, $plugins_restants);

	// On supprimer les plugins identifies
	sql_delete('spip_plugins', sql_in('id_plugin', $plugins_a_supprimer));	
	
	return true;
}


/**
 * Actualisation des plugins d'un dépot déjà crée
 *
 * Actualise les informations uniquement si la signature du fichier
 * XML de description du dépot a changé
 * 
 * @param int $id
 *     Identifiant du dépot
 * @return bool
 *     false si erreur, true sinon
 */
function svp_actualiser_depot($id){
	include_spip('inc/distant');

	$id = intval($id);
	
	// pas de depot a cet id ?
	if (!$depot = sql_fetsel('*', 'spip_depots', 'id_depot='. sql_quote($id)) ){
		return false;
	}
	
	$fichier_xml = _DIR_RACINE . copie_locale($depot['xml_paquets'], 'modif');

	$sha = sha1_file($fichier_xml);

	if ($depot['sha_paquets'] == $sha) {
		// Le fichier n'a pas change (meme sha1) alors on ne fait qu'actualiser la date 
		// de mise a jour du depot en mettant a jour *inutilement* le sha1
		spip_log('Aucune modification du fichier XML, actualisation non declenchee - id_depot = ' . $depot['id_depot'], 'svp_actions.' . _LOG_INFO);
		sql_replace('spip_depots', array_diff_key($depot, array('maj' => '')));
	}
	else {

		// Le fichier a bien change il faut actualiser tout le depot
		$infos = svp_phraser_depot($fichier_xml);

		if (!$infos)
			return false;

		// On actualise les paquets dans spip_paquets en premier lieu.
		// Lors de la mise a jour des paquets, les plugins aussi sont actualises
		$ok = svp_actualiser_paquets($depot['id_depot'], $infos['paquets'],
									$nb_paquets, $nb_plugins, $nb_autres);

		// apres la mise a jour des paquets d'un depot, on actualise les informations des paquets locaux
		// principalement l'info "maj_version" indiquant s'il existe un paquet plus recent
		include_spip('inc/svp_depoter_local');
		svp_actualiser_maj_version();

		if ($ok) {
			// On met à jour :
			// -- les infos ne pouvant pas etre editees par le formulaire d'edition
			//    d'un depot et extraites du xml
			// -- le nombre de paquets et de plugins du depot ainsi que le nouveau sha1
			// ce qui aura pour effet d'actualiser la date de mise a jour
			$champs = array('url_serveur'  => $infos['depot']['url_serveur'],
							'url_brouteur' => $infos['depot']['url_brouteur'],
							'url_archives' => $infos['depot']['url_archives'],
							'url_commits'  => $infos['depot']['url_commits'],
							'nbr_paquets'  => $nb_paquets,
							'nbr_plugins'  => $nb_plugins,
							'nbr_autres'   => $nb_autres,
							'sha_paquets'  => $sha);
			sql_updateq('spip_depots', $champs, 'id_depot=' . sql_quote($depot['id_depot']));
		}
	}

	return true;
}


/**
 * Actualisation de la table des paquets pour le dépot choisi
 *
 * Enlève de la base les paquets du dépots qui ne sont plus présents
 * dans la description du XML. Ajoute ou met à jour les autres.
 * 
 * @param int $id_depot
 *     Identifiant du dépot
 * @param array $paquets
 *     Tableau des paquets extraits du fichier XML
 *     L'index est le nom de l'archive (xxxx.zip) et le contenu est
 *     un tableau à deux entrées :
 *     - Index 'plugin' : le tableau des infos du plugin
 *     - Index 'file' : le nom de l'archive .zip
 * @param int $nb_paquets
 *     Nombre de paquets réellement inserés dans la base
 * @param int $nb_plugins
 *     Nombre de plugins parmi les paquets inserés
 * @param int &$nb_autres
 *     Nombre de contributions non issues de plugin parmi les paquets inserés
 * @return bool
 *     false si aucun dépot ou paquets, true sinon
 */
function svp_actualiser_paquets($id_depot, $paquets, &$nb_paquets, &$nb_plugins, &$nb_autres) {

	// Initialisation des compteurs
	$nb_paquets = 0;
	$nb_plugins = 0;
	$nb_autres = 0;

	// Si aucun depot ou aucun paquet on renvoie une erreur
	if ((!$id_depot) OR (!is_array($paquets)))
		return false;

	// On initialise l'url de base des logos du depot et son type afin de
	// calculer l'url complete de chaque logo
	$select = array('url_archives', 'type');
	$depot = sql_fetsel($select, 'spip_depots', 'id_depot=' . sql_quote($id_depot));


	// On supprime tous les paquets du depot
	// qui ont ete evacues, c'est a dire ceux dont les signatures
	// ne correspondent pas aux nouveaux...
	// et on retablit les vmax des plugins restants...
	$signatures = array();
	foreach ($paquets as $_paquet) {
		$signatures[] = $_paquet['md5'];
	}

	// tous les paquets du depot qui ne font pas parti des signatures
	$anciens_paquets = sql_allfetsel('id_paquet', 'spip_paquets', array('id_depot=' . sql_quote($id_depot), sql_in('signature', $signatures, 'NOT')));
	$anciens_paquets = array_map('array_shift', $anciens_paquets);

	// pour ces vieux paquets, on les nettoie de la base
	if ($anciens_paquets) {
		// tous les plugins correspondants aux anciens paquets
		$anciens_plugins = sql_allfetsel('pl.id_plugin', array('spip_plugins AS pl', 'spip_paquets AS pa'), array('pl.id_plugin=pa.id_plugin', sql_in('pa.id_paquet', $anciens_paquets)));
		$anciens_plugins = array_map('array_shift', $anciens_plugins);

		// suppression des anciens paquets
		sql_delete('spip_paquets', sql_in('id_paquet', $anciens_paquets));
		// suppressions des liaisons depots / anciens plugins
		// on enlève la liaison lorsqu'il n'y a plus aucun paquet lie a un des plugins qui ont vu un paquet enlevé

		// liste des plugins qui ont encore des paquets dans ce depot
		$plugins_restants = sql_allfetsel('pl.id_plugin',
			array('spip_plugins AS pl', 'spip_paquets AS pa'),
			array(sql_in('pl.id_plugin', $anciens_plugins), 'pl.id_plugin=pa.id_plugin', 'pa.id_depot=' . sql_quote($id_depot)));
		$plugins_restants = array_map('array_shift', $plugins_restants);
		// par opposition, on retrouve ceux qui n'en ont plus...
		$plugins_supprimes = array_diff($anciens_plugins, $plugins_restants);
		sql_delete('spip_depots_plugins', array('id_depot='. sql_quote($id_depot), sql_in('id_plugin', $plugins_supprimes)));
		unset($plugins_restants, $plugins_supprimes);

		// supprimer les plugins orphelins
		include_spip('inc/svp_depoter_local');
		svp_supprimer_plugins_orphelins($anciens_plugins);
		
		// corriger les vmax des plugins
		svp_corriger_vmax_plugins($anciens_plugins);

		// corriger les compats, branches aussi
		svp_completer_plugins($anciens_plugins);
	}

	// on ne garde que les paquets qui ne sont pas presents dans la base
	$signatures = sql_allfetsel('signature', 'spip_paquets', 'id_depot='.sql_quote($id_depot));
	$signatures = array_map('array_shift', $signatures);
	foreach ($paquets as $cle => $_infos) {
		if (in_array($_infos['md5'], $signatures)) {
			unset($paquets[$cle]);
		}
	}

	// tableaux d'actions
	$insert_paquets = array();
	$insert_plugins = array();
	$insert_contribs = array();
	$prefixes = array(); // prefixe => id_plugin
	
	// On met a jour ou on cree chaque paquet a partir du contenu du fichier xml
	// On ne fait pas cas de la compatibilite avec la version de SPIP installee
	// car l'operation doit permettre de collecter tous les paquets
	foreach ($paquets as $_archive => $_infos) {

		$insert_paquet = array();
		// On initialise les informations specifiques au paquet :
		// l'id du depot et les infos de l'archive
		$insert_paquet['id_depot'] = $id_depot;
		$insert_paquet['nom_archive'] = $_archive;
		$insert_paquet['nbo_archive'] = $_infos['size'];
		$insert_paquet['maj_archive'] = date('Y-m-d H:i:s', $_infos['date']);
		$insert_paquet['src_archive'] = $_infos['source'];
		$insert_paquet['date_modif'] = $_infos['last_commit'];
		// On serialise le tableau des traductions par module
		$insert_paquet['traductions'] = serialize($_infos['traductions']);
		// On ajoute la signature
		$insert_paquet['signature'] = $_infos['md5'];

		// On verifie si le paquet est celui d'un plugin ou pas
		// -- Les traitements du XML dependent de la DTD utilisee
		// Formatage des informations extraites du plugin pour insertion dans la base SVP
		$formater = charger_fonction('preparer_sql_' . $_infos['dtd'], 'plugins');
		if ($champs_aplat = $formater($_infos['plugin'])) {
			// Eclater les champs recuperes en deux sous tableaux, un par table (plugin, paquet)
			$champs = eclater_plugin_paquet($champs_aplat);

			$paquet_plugin = true;
			// On complete les informations du paquet et du plugin
			$insert_paquet = array_merge($insert_paquet, $champs['paquet']);
			$insert_plugin = $champs['plugin'];
			// On construit l'url complete du logo
			// Le logo est maintenant disponible a la meme adresse que le zip et porte le nom du zip.
			// Son extension originale est conservee
			if ($insert_paquet['logo'])
				$insert_paquet['logo'] = $depot['url_archives'] . '/'
									   . basename($insert_paquet['nom_archive'], '.zip') . '.'
									   . pathinfo($insert_paquet['logo'], PATHINFO_EXTENSION);

			// On loge l'absence de categorie ou une categorie erronee et on positionne la categorie
			// par defaut "aucune"
			// Provisoire tant que la DTD n'est pas en fonction
			if (!$insert_plugin['categorie']) {
				spip_log("Categorie absente dans le paquet issu de <". $insert_paquet['src_archive'] . 
						"> du depot <" . $insert_paquet['id_depot'] . ">\n", 'svp_paquets.' . _LOG_INFO_IMPORTANTE);
				$insert_plugin['categorie'] = 'aucune';
			}
			else {
				$svp_categories = $GLOBALS['categories_plugin'];
				if (!in_array($insert_plugin['categorie'], $svp_categories)) {
					spip_log("Categorie &#107;" . $insert_plugin['categorie'] . "&#108; incorrecte dans le paquet issu de <". $insert_paquet['src_archive'] . 
							"> du depot <" . $insert_paquet['id_depot'] . ">\n", 'svp_paquets.' . _LOG_INFO_IMPORTANTE);
					$insert_plugin['categorie'] = 'aucune';
				}
			}
		}
		else {
			$paquet_plugin = false;
		}
		// On teste l'existence du paquet dans la base avec les champs
		// id_depot, nom_archive et src_archive pour être sur de l'unicité.
		// - si le paquet n'existe pas, on le crée,
		// - sinon (et ça ne devrait pas arriver), on ne fait qu'un update
		if (!$paquet = sql_fetsel('*', 'spip_paquets', array('id_depot='. sql_quote($insert_paquet['id_depot']),
															'nom_archive='. sql_quote($insert_paquet['nom_archive']),
															'src_archive='. sql_quote($insert_paquet['src_archive'])))) {
			// Le paquet n'existe pas encore en base de donnees
			// ------------------------------------------------
			
			// On positionne la date de creation a celle du dernier commit ce qui est bien le cas
			$insert_paquet['date_crea'] = $insert_paquet['date_modif'];

			// Les collisions ne sont possibles que si on ajoute un nouveau paquet
			$collision = false;

			if ($paquet_plugin) {
				// On est en presence d'un PLUGIN
				// ------------------------------
				// On evite les doublons de paquet
				// Pour determiner un doublon on verifie actuellement :
				// - le prefixe
				// - la version du paquet et de la base
				// - l'etat
				$where = array('t1.id_plugin=t2.id_plugin',
						't1.version=' . sql_quote($insert_paquet['version']),
						't1.version_base=' . sql_quote($insert_paquet['version_base']),
						't1.etatnum=' . sql_quote($insert_paquet['etatnum']),
						't1.id_depot>' . intval(0),
						't2.prefixe=' . sql_quote($insert_plugin['prefixe']));
				if (!$id_paquet = sql_getfetsel('t1.id_paquet', 'spip_paquets AS t1, spip_plugins AS t2', $where)) {
					// On traite d'abord le plugin du paquet pour recuperer l'id_plugin
					// On rajoute le plugin dans la table spip_plugins si celui-ci n'y est pas encore ou on recupere
					// l'id si il existe deja et on le met a jour si la version du paquet est plus elevee

					$plugin = sql_fetsel('id_plugin, vmax', 'spip_plugins', array('prefixe=' . sql_quote($insert_plugin['prefixe'])));
					if (!$plugin AND !array_key_exists($insert_plugin['prefixe'], $insert_plugins)) {
						$insert_plugins[ $insert_plugin['prefixe'] ] = array_merge($insert_plugin, array('vmax' => $insert_paquet['version']));
					}
					else {
						if ($plugin) {
							$id_plugin = $plugin['id_plugin'];
							$prefixes[$insert_plugin['prefixe']] = $id_plugin;
						}
						if (array_key_exists($insert_plugin['prefixe'], $insert_plugins)
						AND (spip_version_compare($insert_plugins[ $insert_plugin['prefixe'] ]['vmax'], $insert_paquet['version'], '<='))) {
							// attribuer au plugin le nom et le slogan du paquet le plus à jour
							$insert_plugins[ $insert_plugin['prefixe'] ]['nom'] = $insert_plugin['nom'];
							$insert_plugins[ $insert_plugin['prefixe'] ]['slogan'] = $insert_plugin['slogan'];
							$insert_plugins[ $insert_plugin['prefixe'] ]['vmax'] = $insert_paquet['version'];
						}
					}

					// On traite maintenant le paquet connaissant l'id du plugin
					// temporaire qui sera supprime lors de la connaissance de l'id_paquet
					$insert_paquet['prefixe'] = $insert_plugin['prefixe']; 
					$insert_paquets[] = $insert_paquet;
				}
				else
					$collision = true;
			}
			else {
				// On est en presence d'une CONTRIBUTION NON PLUGIN
				// ------------------------------------------------
				$where = array(
						'id_depot=' . sql_quote($insert_paquet['id_depot']),
						'nom_archive=' . sql_quote($insert_paquet['nom_archive']));
				if (!$id_paquet = sql_getfetsel('id_paquet', 'spip_paquets', $where)) {
					// Ce n'est pas un plugin, donc id_plugin=0 et toutes les infos plugin sont nulles
					$insert_paquet['id_plugin'] = 0;
					$insert_contribs[] = $insert_paquet;
				} else
					$collision = true;
			}
			// On loge le paquet ayant ete refuse dans un fichier a part afin de les verifier
			// apres coup
			if ($collision) {
				spip_log("Collision avec le paquet <". $insert_paquet['nom_archive'] . 
						" / " . $insert_paquet['src_archive'] . "> du depot <" . $insert_paquet['id_depot'] . ">\n", 'svp_paquets.' . _LOG_INFO_IMPORTANTE);
			}
		}
		else {
			// Le paquet existe deja en base de donnees
			// ----------------------------------------

			// On ne devrait plus arriver ICI...
			// Code obsolete ?
			spip_log('!!!!!! Passage dans code obsolete (svp/svp_depoter_distant)', 'depoter');
			
			// on effectue les traitements en attente
			// pour que les updates soient corrects
			svp_inserer_multi($insert_plugins, $insert_paquets, $insert_contribs, $prefixes);
			
			
			// On met a jour le paquet en premier lieu qu'il soit un plugin ou une contribution
			sql_updateq('spip_paquets', $insert_paquet,
						'id_paquet=' . sql_quote($paquet['id_paquet']));

		}
	}

	// on effectue les traitements en attente
	// pour que les updates soient corrects
	svp_inserer_multi($insert_plugins, $insert_paquets, $insert_contribs, $prefixes);

	// On rajoute le plugin comme heberge par le depot si celui-ci n'est pas encore enregistre comme tel
	$ids = sql_allfetsel('p.id_plugin',
		array('spip_plugins AS p', 'spip_depots_plugins AS dp'),
		array('p.id_plugin=dp.id_plugin', 'dp.id_depot='.sql_quote($id_depot)));
	$ids = array_map('array_shift', $ids);

	// inserer les liens avec le depots
	$insert_dp = array();
	$news_id = array_diff(array_values($prefixes), $ids);
	foreach($news_id as $id) {
		$insert_dp[] = array('id_depot'=>$id_depot, 'id_plugin'=>$id);
	}
	if ($insert_dp) {
		sql_insertq_multi('spip_depots_plugins', $insert_dp);
	}

	// on recalcul les vmax des plugins de ce depot.
	svp_corriger_vmax_plugins(array_values($prefixes));

	// On compile maintenant certaines informations des paquets mis a jour dans les plugins
	// (date de creation, date de modif, version spip...)
	svp_completer_plugins_depot($id_depot);

	// Si on est pas en mode runtime, on utilise surement l'espace public pour afficher les plugins.
	// Il faut donc s'assurer que les urls suivent bien la mise à jour
	// - on supprime toutes les urls plugin
	// - on les regenere pour la liste des plugins mise a jour
	if (!_SVP_MODE_RUNTIME)
		svp_actualiser_url_plugins($id_depot);
		
	// Calcul des compteurs de paquets, plugins et contributions
	$nb_paquets = sql_countsel('spip_paquets', 'id_depot=' . sql_quote($id_depot));
	$nb_plugins = sql_countsel('spip_depots_plugins', 'id_depot=' . sql_quote($id_depot));
	$nb_autres = sql_countsel('spip_paquets', array('id_depot=' . sql_quote($id_depot), 'id_plugin=0'));
	
	return true;
}


/**
 * Insertion en masse de plugins ou de paquets.
 *
 * Les paquets peuvent de pas avoir d'info "prefixe" (à transformer en id_plugin)
 * lorsqu'ils ne proviennent pas de plugin (squelettes...)
 * 
 * @param array $insert_plugins
 *     Tableau de description de plugins.
 *     Une description est un tableau de couples (colonne sql => valeur)
 *     pour l'insertion en base de données.
 * @param array $insert_paquets
 *     Tableau de description de paquets.
 *     Une description est un tableau de couples (colonne sql => valeur)
 *     pour l'insertion en base de données.
 * @param array $insert_contribs
 *     Tableau de description de paquets (contributions non plugins).
 *     Une description est un tableau de couples (colonne sql => valeur)
 *     pour l'insertion en base de données.
 * @param array $prefixes
 *     Couples de relation (préfixe de plugin => identifiant de plugin) connues,
 *     pour limiter les accès SQL.
 * @return void
**/
function svp_inserer_multi(&$insert_plugins, &$insert_paquets, &$insert_contribs, &$prefixes) {

	if (count($insert_plugins)) {
		sql_insertq_multi('spip_plugins', $insert_plugins);
		$insert_plugins = array();
	}
	
	if (count($insert_paquets)) {

		// on cherche tous les id_plugin/prefixe que l'on a à récuperer
		// en une seule requete
		$prefixes_manquants = array();
		foreach ($insert_paquets as $p) {
			// on ne connait que le prefixe
			if (isset($p['prefixe']) and !isset($prefixes[ $p['prefixe'] ])) {
				$prefixes_manquants[] = $p['prefixe']; 
			} 
		}

		// recuperer les nouveaux prefixes :
		$new = sql_allfetsel(array('prefixe', 'id_plugin'), 'spip_plugins', sql_in('prefixe', $prefixes_manquants));
		foreach ($new as $p) {
			$prefixes[ $p['prefixe'] ] = $p['id_plugin'];
		}

		// inserer les id_plugin dans les paquets a inserer
		// inserer le prefixe dans le paquet (pour raccourcis de jointures)
		foreach ($insert_paquets as $c=>$p) {
			if (isset($p['prefixe'])) {
				$insert_paquets[$c]['id_plugin'] = $prefixes[ $insert_paquets[$c]['prefixe'] ];
			} else {
				$insert_paquets[$c]['prefixe'] = array_search($p['id_plugin'], $prefixes);
			}
		}

		// on insere tout !
		sql_insertq_multi('spip_paquets', $insert_paquets);
		$insert_paquets = array();
	}

	// les contribs n'ont pas le même nombre de champs dans les insertions
	// et n'ont pas de plugin rattachés.
	if (count($insert_contribs)) {
		sql_insertq_multi('spip_paquets', $insert_contribs);
		$insert_contribs = array();
	}
}

/**
 * Complète les informations des plugins contenus dans un depot
 * en compilant certaines informations (compatibilités, dates,  branches)
 *
 * @param int $id_depot
 *     Identifiant du depot à actualiser
**/
function svp_completer_plugins_depot($id_depot) {
	// On limite la revue des paquets a ceux des plugins heberges par le depot en cours d'actualisation
	$ids_plugins = sql_allfetsel('id_plugin', 'spip_depots_plugins', array('id_depot=' . sql_quote($id_depot)));
	$ids_plugins = array_map('reset', $ids_plugins);
	if ($ids_plugins) {
		svp_completer_plugins($ids_plugins);
	}
}

/**  
 * Complète les informations des plugins, d'une liste de plugins donnés,
 * en compilant certaines informations (compatibilités, dates,  branches)
 *
 * @param array $ids_plugin
 *     Liste d'identifiants de plugins
 * @return bool
 *     false si rien à faire, true sinon
**/
function svp_completer_plugins($ids_plugin) {

	if (!$ids_plugin) {
		return false;
	}

	include_spip('inc/svp_outiller');

	// -- on recupere tous les paquets associes aux plugins indiques et on compile les infos
	if ($resultats = sql_allfetsel('id_plugin, compatibilite_spip, date_crea, date_modif', 'spip_paquets', 
				array(sql_in('id_plugin', $ids_plugin), 'id_depot>'.intval(0)), '', 'id_plugin')) {

		$plugin_en_cours = 0;
		$inserts = array();
		
		foreach($resultats as $paquet) {
			// On finalise le plugin en cours et on passe au suivant 
			if ($plugin_en_cours != $paquet['id_plugin']) {
				// On met a jour le plugin en cours
				if ($plugin_en_cours) {
					// On deduit maintenant les branches de la compatibilite globale
					$complements['branches_spip'] = compiler_branches_spip($complements['compatibilite_spip']);
					$inserts[$plugin_en_cours] = $complements;
				}
				// On passe au plugin suivant
				$plugin_en_cours = $paquet['id_plugin'];
				$complements = array('compatibilite_spip' => '', 'branches_spip' => '', 'date_crea' => 0, 'date_modif' => 0);
			}
			
			// On compile les compléments du plugin avec le paquet courant sauf les branches
			// qui sont deduites en fin de compilation de la compatibilite
			if ($paquet['date_modif'] > $complements['date_modif'])
				$complements['date_modif'] = $paquet['date_modif'];
			if (($complements['date_crea'] === 0)
			OR ($paquet['date_crea'] < $complements['date_crea']))
				$complements['date_crea'] = $paquet['date_crea'];
			if ($paquet['compatibilite_spip'])
				if (!$complements['compatibilite_spip'])
					$complements['compatibilite_spip'] = $paquet['compatibilite_spip'];
				else
					$complements['compatibilite_spip'] = fusionner_intervalles($paquet['compatibilite_spip'], $complements['compatibilite_spip']);
		}
		// On finalise le dernier plugin en cours
		$complements['branches_spip'] = compiler_branches_spip($complements['compatibilite_spip']);
		$inserts[$plugin_en_cours] = $complements;

		// On insere, en encapsulant pour sqlite...
		if (sql_preferer_transaction()) {
			sql_demarrer_transaction();
		}
		
		foreach ($inserts as $id_plugin => $complements) {
			sql_updateq('spip_plugins', $complements, 'id_plugin=' . intval($id_plugin));
		}
		
		if (sql_preferer_transaction()) {
			sql_terminer_transaction();
		}

	}

	return true;
}


/**
 * Recrée toutes les URLs propres de plugin 
 *
 * Supprime toutes les urls de plugin de la table spip_urls puis les régénère.
 * 
 * @return int
 *     Nombre d'URLs de plugin régénérées
**/
function svp_actualiser_url_plugins () {
	$nb_plugins = 0;

	// On supprime toutes les urls de plugin
	sql_delete('spip_urls', array('type=\'plugin\''));

	// On recupere les ids des plugins et on regenere les urls
	if ($ids_plugin = sql_allfetsel('id_plugin', 'spip_plugins')) {
		$ids_plugin = array_map('reset', $ids_plugin);
		$nb_plugins = count($ids_plugin);

		foreach ($ids_plugin as $_id) {
			generer_url_entite($_id, 'plugin', '', '', true);
		}
	}
	
	return $nb_plugins;
}

/**
 * Éclate une description de paquet issu du XML du dépot en deux parties,
 * une pour le plugin, l'autre pour le paquet 
 *
 * Sépare en deux une description de champs désignant un paquet, en extrayant :
 * - la partie plugin, soit ce qui peut être propre à plusieurs paquets.
 *   On trouve dedans le prefixe, nom, slogan, catégorie, tags
 * - la partie paquet, soit ce qui est propre à ce conteneur là. On trouve
 *   dedans entre autres la description, la version, la compatibilité
 *   à SPIP, les dépendances, etc...
 * 
 * @param array $champs_aplat
 *     Couples (clé => valeur) d'un paquet issu de l'analyse XML du dépot
 * @return array
 *     Tableau de 2 index :
 *     - Index 'plugin' : couples (clé=>valeur) relatives au plugin
 *     - Index 'paquet' : couples (clé=>valeur) spécifiques au paquet
**/
function eclater_plugin_paquet($champs_aplat) {
	return array(
		'plugin' => array(
			'prefixe' => $champs_aplat['prefixe'],
			'nom' => $champs_aplat['nom'],
			'slogan' => $champs_aplat['slogan'],
			'categorie' => $champs_aplat['categorie'],
			'tags' => $champs_aplat['tags']),
		'paquet' => array(
			'logo' => $champs_aplat['logo'],
			'description' => $champs_aplat['description'],
			'auteur' => $champs_aplat['auteur'],
			'credit' => $champs_aplat['credit'],
			'version' => $champs_aplat['version'],
			'version_base' => $champs_aplat['version_base'],
			'compatibilite_spip' => $champs_aplat['compatibilite_spip'],
			'branches_spip' => $champs_aplat['branches_spip'],
			'etat' => $champs_aplat['etat'],
			'etatnum' => $champs_aplat['etatnum'],
			'licence' => $champs_aplat['licence'],
			'copyright' => $champs_aplat['copyright'],
			'lien_doc' => $champs_aplat['lien_doc'],
			'lien_demo' => $champs_aplat['lien_demo'],
			'lien_dev' => $champs_aplat['lien_dev'],
			'dependances' => $champs_aplat['dependances'])
	);
}



/**
 * Détermine la version max de chaque plugin, c'est à dire
 * la version maxi d'un des paquets qui lui est lié.
 * 
 * @param array $plugins Liste d'identifiant de plugins
**/
function svp_corriger_vmax_plugins($plugins) {
	// tous les plugins encore lies a des depots (hors local)...
	// la vmax est a retablir...
	if ($plugins) {
		$p = sql_allfetsel('DISTINCT(p.id_plugin)',
			array('spip_plugins AS p', 'spip_paquets AS pa'),
			array(sql_in('p.id_plugin', $plugins), 'p.id_plugin=pa.id_plugin', 'pa.id_depot>'.intval(0)));
		$p = array_map('array_shift', $p);

		// pour les autres, on la fixe correctement
		
		// On insere, en encapsulant pour sqlite...
		if (sql_preferer_transaction()) {
			sql_demarrer_transaction();
		}
		
		foreach ($p as $id_plugin) {
			$vmax = '';
			if ($pa = sql_allfetsel('version', 'spip_paquets', array('id_plugin='.$id_plugin, 'id_depot>'.intval(0)))) {
				foreach ($pa as $v) {
					if (spip_version_compare($v['version'], $vmax, '>')) {
						$vmax = $v['version'];
					}
				}
			}
			sql_updateq('spip_plugins', array('vmax'=>$vmax), 'id_plugin=' . intval($id_plugin));
		}
		
		if (sql_preferer_transaction()) {
			sql_terminer_transaction();
		}
	}
}



?>
svp_actionner.php000066600000120676152503074170010153 0ustar00<?php

/**
 * Gestion de l'actionneur : il effectue les actions sur les plugins 
 *
 * @plugin SVP pour SPIP
 * @license GPL
 * @package SPIP\SVP\Actionneur
 */
 
if (!defined("_ECRIRE_INC_VERSION")) return;


/**
 * L'actionneur calcule l'ordre des actions, permet de les stocker
 * dans un fichier cache et de les effectuer.
 *
 * @package SPIP\SVP\Actionner
**/
class Actionneur {

	/**
	 * Instance du décideur
	 * @var Decideur */
	var $decideur;

	/**
	 * Loguer les différents éléments
	 * 
	 * Sa valeur sera initialisée par la configuration 'mode_log_verbeux' de SVP
	 * 
	 * @var bool */
	var $log = false;

	/**
	 * Liste des actions à faire
	 * @var array
	 *     Tableau identifiant du paquet => type d'action
	 */
	var $start = array();

	/**
	 * Actions en cours d'analyse
	 *
	 * Lorsqu'on ajoute les actions à faire, elles sont réordonnées
	 * et classées dans ces quatre sous-tableaux
	 *
	 * Chaque sous-tableau est composé d'une description courte du paquet
	 * auquel est ajouté dans l'index 'todo' le type d'action à faire.
	 * 
	 * @var array
	 *     Index 'off' : les paquets à désactiver (ordre inverse des dépendances)
	 *     Index 'lib' : les librairies à installer
	 *     Index 'on' : les paquets à activer (ordre des dépendances)
	 *     Index 'neutre' : autres actions dont l'ordre a peu d'importance.
	 */
	var $middle = array(
		'off' => array(),
		'lib' => array(),
		'on' => array(),
		'neutre' => array(),
	);

	// actions à la fin (apres analyse, et dans l'ordre)

	/**
	 * Liste des actions à faire
	 * 
	 * Liste de description courtes des paquets + index 'todo' indiquant l'action
	 * @var array */
	var $end = array();

	/**
	 * Liste des actions faites
	 * Liste de description courtes des paquets + index 'todo' indiquant l'action
	 * @var array */
	var $done = array(); // faites

	/**
	 * Actions en cours
	 * Description courte du paquet + index 'todo' indiquant l'action
	 * @var array */
	var $work = array();

	/**
	 * Liste des erreurs
	 * 
	 * @var array Liste des erreurs */
	var $err = array();

	/**
	 * Verrou.
	 * Le verrou est posé au moment de passer à l'action.
	 * @var array
	 *     Index 'id_auteur' : Identifiant de l'auteur ayant déclenché des actions
	 *     Indix 'time' : timestamp de l'heure de déclenchement de l'action */
	var $lock = array('id_auteur'=>0, 'time'=>'');

	/**
	 * SVP (ce plugin) est-il à désactiver dans une des actions ?
	 *
	 * Dans ce cas, on tente de le désactiver après d'autres plugins à désactiver
	 * sinon l'ensemble des actions suivantes échoueraient.
	 * 
	 * @var bool
	 *     false si SVP n'est pas à désactiver, true sinon */
	var $svp_off = false;

	/**
	 * Constructeur
	 *
	 * Détermine si les logs sont activés et instancie un décideur.
	 */
	function Actionneur(){
		include_spip('inc/config');
		$this->log = (lire_config('svp/mode_log_verbeux') == 'oui');

		include_spip('inc/svp_decider');
		$this->decideur = new Decideur();
		#$this->decideur->start();

		// pour denormaliser_version()
		include_spip('svp_fonctions');
	}


	/**
	 * Ajoute un log
	 *
	 * Ajoute un log si la propriété $log l'autorise;
	 *
	 * @param mixed $quoi
	 *     La chose à logguer (souvent un texte)
	**/
	function log($quoi) {
		if ($this->log) {
			spip_log($quoi,'actionneur');
		}
	}

	/**
	 * Ajoute une erreur
	 * 
	 * Ajoute une erreur à la liste des erreurs présentées au moment
	 * de traiter les actions.
	 *
	 * @param string $erreur
	 *     Le texte de l'erreur
	**/
	function err($erreur) {
		if ($erreur) {
			$this->err[] = $erreur;
		}
	}

	/**
	 * Remet à zéro les tableaux d'actions
	 */
	function clear() {
		$this->middle = array(
			'off' => array(),
			'lib' => array(),
			'on' => array(),
			'neutre' => array(),
		);
		$this->end = array();
		$this->done = array();
		$this->work = array();
	}

	/**
	 * Ajoute les actions à faire dans l'actionneur 
	 *
	 * @param array $todo
	 *     Tableau des actions à faire (identifiant de paquet => type d'action)
	**/
	function ajouter_actions($todo) {
		foreach ($todo as $id => $action) {
			$this->start[$id] = $action;
		}
		$this->ordonner_actions();
	}


	/**
	 * Ajoute une librairie à installer
	 *
	 * Ajoute l'action de télécharger une librairie, si la libraire
	 * n'est pas déjà présente et si le répertoire de librairie est
	 * écrivable.
	 *
	 * @param string $nom Nom de la librairie
	 * @param string $source URL pour obtenir la librairie
	 */
	function add_lib($nom, $source) {
		if (!$this->decideur->est_presente_lib($nom)) {
			if (is_writable(_DIR_LIB)) {
				$this->middle['lib'][$nom] = array(
					'todo'=>'getlib',
					'n'=>$nom,
					'p'=>$nom,
					'v'=>$source,
					's'=>$source,
				);
			} else {
				// erreur : impossible d'ecrire dans _DIR_LIB !
				// TODO : message et retour d'erreur a gerer...
				return false;
			}
		}
		return true;
	}

	/**
	 * Ordonne les actions demandées
	 *
	 * La fonction définie quelles sont les actions graduellement réalisables.
	 * Si un plugin A dépend de B qui dépend de C
	 * - pour tout ce qui est à installer : ordre des dependances (d'abord C, puis B, puis A)
	 * - pour tout ce qui est à désinstaller : ordre inverse des dependances. (d'abord A, puis B, puis C)
	 *
	 * On commence donc par séparer
	 * - ce qui est à désinstaller,
	 * - ce qui est à installer,
	 * - les actions neutres (get, up sur non actif, kill)
	 *
	 * Dans les traitements, on commencera par faire
	 * - ce qui est à désinstaller (il est possible que certains plugins
	 * nécessitent la désinstallation d'autres présents - tel que : 1 seul
	 * service d'envoi de mail)
	 * - puis ce qui est a installer (à commencer par les librairies, puis paquets),
	 * - puis les actions neutres
	 */
	function ordonner_actions() {
		// nettoyer le terrain
		$this->clear();

		foreach ($this->start as $id=>$action) {
			$i = $this->decideur->infos_courtes_id($id);
			$i = $i['i'][$id];
			switch ($action) {
				case 'getlib':
					// le plugin en ayant besoin le fera
					// comme un grand...
					break;
				case 'geton':
				case 'on':
					$this->on($i, $action);
					break;
				case 'up':
					// si le plugin est actif
					if ($i['a'] == 'oui') { 
						$this->on($i, $action);
					} else {
						$this->neutre($i, $action);
					}
					break;
				case 'upon':
					$this->on($i, $action);
					break;
				case 'off':
				case 'stop':
					$this->off($i, $action);
					break;
				case 'get':
				case 'kill':
					$this->neutre($i, $action);
					break;
			}
		}

		// c'est termine, on passe tout dans la fin...
		foreach ($this->middle as $acts) {
			$this->end = array_merge($this->end, $acts);
		}

		// si on a vu une desactivation de SVP
		// on le met comme derniere action...
		// sinon on ne pourrait pas faire les suivantes !
		if ($this->svp_off) {
			$this->log("SVP a desactiver a la fin.");
			foreach ($this->end as $c => $info) {
				if ($info['p'] == 'SVP') {
					unset($this->end[$c]);
					$this->end[] = $info;
					break;
				}
			}
		}
	
		$this->log("------------");
		#$this->log("Fin du tri :");
		#$this->log($this->end);
	}


	/**
	 * Ajoute un paquet à activer 
	 *
	 * À chaque fois qu'un nouveau paquet arrive ici, on le compare
	 * avec ceux déjà présents pour savoir si on doit le traiter avant
	 * ou après un des paquets à activer déjà présent.
	 *
	 * Si le paquet est une dépendance d'un autre plugin, il faut le mettre
	 * avant (pour l'activer avant celui qui en dépend).
	 *
	 * Si le paquet demande une librairie, celle-ci est ajoutée (les
	 * librairies seront téléchargées avant l'activation des plugins,
	 * le plugin aura donc sa librairie lorsqu'il sera activé)
	 *
	 * 
	 * @param array $info
	 *     Description du paquet
	 * @param string $action
	 *     Action à réaliser (on, upon)
	 * @return void
	**/
	function on($info, $action) {
		$info['todo'] = $action;
		$p = $info['p'];
		$this->log("ON: $p $action");

		// si dependance, il faut le mettre avant !
		$in = $out = $deps = $deps_all = array();
		// raz des cles pour avoir les memes que $out (utile reellement ?)
		$this->middle['on'] = array_values($this->middle['on']);
		// ajout des dependance
		foreach ($info['dn'] as $dep) {
			$in[]  = $dep['nom'];
		}
		// ajout des librairies
		foreach ($info['dl'] as $lib) {
			// il faudrait gerer un retour d'erreur eventuel !
			$this->add_lib($lib['nom'], $lib['lien']);
		}

		// on recupere : tous les prefix de plugin a activer (out)
		// ie. ce plugin peut dependre d'un de ceux la
		//
		// ainsi que les dependences de ces plugins (deps)
		// ie. ces plugins peuvent dependre de ce nouveau a activer.
		foreach ($this->middle['on'] as $inf) {
			$out[] = $inf['p'];
			foreach ($inf['dn'] as $dep) {
				$deps[$inf['p']][] = $dep['nom'];
				$deps_all[] = $dep['nom'];
			}
		}


		if (!$in) {

			// pas de dependance, on le met en premier !
			$this->log("- placer $p tout en haut");
			array_unshift($this->middle['on'], $info);

		} else {

			// intersection = dependance presente aussi
			// on place notre action juste apres la derniere dependance
			if ($diff = array_intersect($in, $out)) {
				$key = array();
				foreach($diff as $d) {$key[] = array_search($d, $out);}
				$key = max($key);
				$this->log("- placer $p apres " . $this->middle['on'][$key]['p']);
				if ($key == count($this->middle['on'])) {
					$this->middle['on'][] = $info;
				} else {
					array_splice($this->middle['on'], $key+1, 0, array($info));
				}

			// intersection = plugin dependant de celui-ci
			// on place notre plugin juste avant la premiere dependance a lui trouvee
			} elseif (in_array($p, $deps_all)) {
				foreach ($deps as $prefix=>$dep) {
					if (in_array($p, $dep)) {
						$key = array_search($prefix, $out);
						$this->log("- placer $p avant $prefix qui en depend ($key)");
						if ($key == 0) {
							array_unshift($this->middle['on'], $info);
						} else {
							array_splice($this->middle['on'], $key, 0, array($info));
						}
						break;
					}
				}

			// rien de particulier, il a des dependances mais les plugins
			// ne sont pas encore la ou les dependances sont deja actives
			// donc on le place tout en bas
			} else {
				$this->log("- placer $p tout en bas");
				$this->middle['on'][] = $info;
			}
		}
		unset($diff, $in, $out);
	}


	/**
	 * Ajoute un paquet avec une action neutre 
	 *
	 * Ces actions seront traitées en dernier, et peu importe leur
	 * ordre car elles n'entrent pas en conflit avec des dépendances.
	 * 
	 * @param array $info
	 *     Description du paquet
	 * @param string $action
	 *     Action à réaliser (kill, get, up (sur plugin inactif))
	 * @return void
	**/
	function neutre($info, $action) {
		$info['todo'] = $action;
		$this->log("NEUTRE:  $info[p] $action");
		$this->middle['neutre'][] = $info;
	}

	/**
	 * Ajoute un paquet à désactiver
	 *
	 * Ces actions seront traitées en premier.
	 *
	 * À chaque fois qu'un nouveau paquet arrive ici, on le compare
	 * avec ceux déjà présents pour savoir si on doit le traiter avant
	 * ou après un des paquets à désactiver déjà présent.
	 *
	 * Si le paquet est une dépendance d'un autre plugin, il faut le mettre
	 * après (pour désactiver avant celui qui en dépend).
	 * 
	 * @param array $info
	 *     Description du paquet
	 * @param string $action
	 *     Action à réaliser (kill, get, up (sur plugin inactif))
	 * @return void
	**/
	function off($info, $action) {
		$info['todo'] = $action;
		$p = $info['p'];
		$this->log("OFF: $p $action");

		// signaler la desactivation de SVP
		if ($p == 'SVP') {
			$this->svp_off = true;
		}
		
		// si dependance, il faut le mettre avant !
		$in = $out = array();
		// raz des cles pour avoir les memes que $out (utile reellement ?)
		$this->middle['off'] = array_values($this->middle['off']);
		foreach ($info['dn'] as $dep) {
			$in[]  = $dep['nom'];
		}
		foreach ($this->middle['off'] as $inf) 	{
			$out[] = $inf['p'];
		}

		if (!$in) {
			// ce plugin n'a pas de dependance, on le met en dernier !
			$this->log("- placer $p tout en bas");
			$this->middle['off'][] = $info;
		} else {
			// ce plugin a des dependances,
			// on le desactive juste avant elles.
			
			// intersection = dependance presente aussi
			// on place notre action juste avant la premiere dependance
			if ($diff = array_intersect($in, $out)) {
				$key = array();
				foreach($diff as $d) {$key[] = array_search($d, $out);}
				$key = min($key);
				$this->log("- placer $p avant " . $this->middle['off'][$key]['p']);
				array_splice($this->middle['off'], $key, 0, array($info));
			} else {
				// aucune des dependances n'est a desactiver
				// (du moins à ce tour ci),
				// on le met en premier !
				$this->log("- placer $p tout en haut");
				array_unshift($this->middle['off'], $info); // etait ->middle['on'] ?? ...
			}
		}
		unset($diff, $in, $out);
	}


	/**
	 * Retourne un bilan, texte HTML, des actions qui ont été faites
	 *
	 * Si c'est un affichage du bilan de fin, et qu'il reste des actions
	 * à faire, un lien est proposé pour faire supprimer ces actions restantes
	 * et le verrou qui va avec.
	 * 
	 * @param bool $fin
	 *     Est-ce un affichage intermédiaire (false) ou le tout dernier (true).
	 * @return string
	 *     Bilan des actions au format HTML
	**/
	function presenter_actions($fin = false) {
		$affiche = "";

		include_spip('inc/filtres_boites');
		
		if (count($this->err)) {
			$erreurs = "<ul>";
			foreach ($this->err as $i) {
				$erreurs .= "\t<li class='erreur'>" . $i . "</li>\n";
			}
			$erreurs .= "</ul>"; 
			$affiche .= boite_ouvrir(_T('svp:actions_en_erreur'), 'error') . $erreurs . boite_fermer();
		}

		if (count($this->done)) {
			$oks = true;
			$done = "<ul>";
			foreach ($this->done as $i) {
				$ok = ($i['done'] ? true : false);
				$oks = &$ok;
				$ok_texte = $ok ? 'ok' : 'fail';
				$cle_t = 'svp:message_action_finale_' . $i['todo'] . '_' . $ok_texte;
				$texte = _T($cle_t, array(
					'plugin' => $i['n'],
					'version' => denormaliser_version($i['v']),
					'version_maj' => denormaliser_version($i['maj'])));
				if (is_string($i['done'])) {
					$texte .= " <span class='$ok_texte'>$i[done]</span>";
				}
				$done .= "\t<li class='$ok_texte'>$texte</li>\n";
			}
			$done .= "</ul>";
			$affiche .= boite_ouvrir(_T('svp:actions_realises'), ($oks ? 'success' : 'notice')) . $done . boite_fermer();
		}

		if (count($this->end)) {
			$todo = "<ul>";
			foreach ($this->end as $i) {
				$todo .= "\t<li>"._T('svp:message_action_'.$i['todo'],array(
					'plugin'=>$i['n'],
					'version'=>denormaliser_version($i['v']),
					'version_maj'=>denormaliser_version($i['maj'])))."</li>\n";
			}
			$todo .= "</ul>\n";
			$titre = ($fin ? _T('svp:actions_non_traitees') : _T('svp:actions_a_faire'));

			// s'il reste des actions à faire alors que c'est la fin qui est affichée,
			// on met un lien pour vider. C'est un cas anormal qui peut surgir :
			// - en cas d'erreur sur une des actions bloquant l'espace privé
			// - en cas d'appel d'admin_plugins concurrent par le même admin ou 2 admins...
			if ($fin) {
				include_spip('inc/filtres');
				if ($this->lock['time']) {
					$time = $this->lock['time'];
				} else {
					$time = time();
				}
				$date = date('Y-m-d H:i:s', $time);
				$todo .= "<br />\n";
				$todo .= "<p class='error'>" . _T('svp:erreur_actions_non_traitees', array(
					'auteur' => sql_getfetsel('nom', 'spip_auteurs', 'id_auteur=' . sql_quote($this->lock['id_auteur'])),
					'date' => affdate_heure($date)
				)) . "</p>\n";
				$todo .= "<a href='" . parametre_url(self(), 'nettoyer_actions', '1'). "'>" . _T('svp:nettoyer_actions') . "</a>\n";
			}
			$affiche .= boite_ouvrir($titre, 'notice') . $todo . boite_fermer();
		}

		if ($affiche) {
			include_spip('inc/filtres');
			$affiche = wrap($affiche, "<div class='svp_retour'>");
		}
		
		return $affiche;
	}

	/**
	 * Teste l'existance d'un verrou par un auteur ?
	 *
	 * Si un id_auteur est transmis, teste que c'est cet auteur
	 * précis qui a posé le verrou.
	 *
	 * @see Actionneur::verouiller()
	 * 
	 * @param int|string $id_auteur
	 *     Identifiant de l'auteur, ou vide
	 * @return bool
	 *     true si un verrou est là, false sinon
	**/
	function est_verrouille($id_auteur = '') {
		if ($id_auteur == '') {
			return ($this->lock['id_auteur'] ? true : false);
		}
		return ($this->lock['id_auteur'] == $id_auteur);
	}

	/**
	 * Pose un verrou
	 *
	 * Un verrou permet de garentir qu'une seule exécution d'actions
	 * est lancé à la fois, ce qui évite que deux administrateurs
	 * puissent demander en même temps des actions qui pourraient
	 * s'entrechoquer.
	 *
	 * Le verrou est signé par l'id_auteur de l'auteur actuellement identifié.
	 *
	 * Le verrou sera sauvegardé en fichier avec la liste des actions
	 * 
	 * @see Actionneur::sauver_actions()
	**/
	function verrouiller() {
		$this->lock = array(
			'id_auteur' => $GLOBALS['visiteur_session']['id_auteur'],
			'time' => time(),
		);
	}

	/**
	 * Enlève le verrou
	**/
	function deverrouiller() {
		$this->lock = array(
			'id_auteur' => 0,
			'time' => '',
		);
	}

	/**
	 * Sauvegarde en fichier cache la liste des actions et le verrou
	 *
	 * Crée un tableau contenant les informations principales qui permettront
	 * de retrouver ce qui est à faire comme action, ce qui a été fait,
	 * les erreurs générées, et le verrouillage.
	 *
	 * Le cache peut être lu avec la méthode get_actions()
	 * 
	 * @see Actionneur::get_actions()
	**/
	function sauver_actions() {
		$contenu = serialize(array(
			'todo' => $this->end,
			'done' => $this->done,
			'work' => $this->work,
			'err'  => $this->err,
			'lock' => $this->lock,
		));
		ecrire_fichier(_DIR_TMP . 'stp_actions.txt', $contenu);
	}

	/**
	 * Lit le fichier cache de la liste des actions et verrou
	 *
	 * Restaure les informations contenues dans le fichier de cache
	 * et écrites avec la méthode sauver_actions().
	 * 
	 * @see Actionneur::sauver_actions()
	**/
	function get_actions() {
		lire_fichier(_DIR_TMP . 'stp_actions.txt', $contenu);
		$infos = unserialize($contenu);
		$this->end  = $infos['todo'];
		$this->work = $infos['work'];
		$this->done = $infos['done'];
		$this->err  = $infos['err'];
		$this->lock = $infos['lock'];
	}

	/**
	 * Nettoyage des actions et verrou
	 *
	 * Remet tout à zéro pour pouvoir repartir d'un bon pied.
	**/
	function nettoyer_actions() {
		$this->todo = array();
		$this->done = array();
		$this->work = array();
		$this->err  = array();
		$this->deverrouiller();
		$this->sauver_actions();
	}

	/**
	 * Effectue une des actions qui reste à faire.
	 *
	 * Dépile une des actions à faire s'il n'y en a pas en cours
	 * au moment de l'appel et traite cette action
	 *
	 * @see Actionneur::do_action()
	 * @return bool|array
	 *     False si aucune action à faire,
	 *     sinon tableau de description courte du paquet + index 'todo' indiquant l'action
	**/
	function one_action() {
		// s'il reste des actions, on en prend une, et on la fait
		// de meme si une action est en cours mais pas terminee (timeout)
		// on tente de la refaire...
		if (count($this->end) OR $this->work) {
			// on verrouille avec l'auteur en cours pour
			// que seul lui puisse effectuer des actions a ce moment la
			if (!$this->est_verrouille()) {
				$this->verrouiller();
			}
			// si ce n'est pas verrouille par l'auteur en cours...
			// ce n'est pas normal, donc on quitte sans rien faire.
			elseif (!$this->est_verrouille($GLOBALS['visiteur_session']['id_auteur'])) {
				return false;
			}

			// si pas d'action en cours
			if (!$this->work) {
				// on prend une des actions en attente
				$this->work = array_shift($this->end);
			}
			$action = $this->work;
			$this->sauver_actions();
			// effectue l'action dans work
			$this->do_action();

			// si la liste des actions en attente est maintenant vide
			// on deverrouille aussitot.
			if (!count($this->end)) {
				$this->deverrouiller();
				$this->sauver_actions();
			}
			return $action;
		} else {
			// on ne devrait normalement plus tomber sur un cas de verrouillage ici
			// mais sait-on jamais. Tester ne couter rien :)
			if ($this->est_verrouille()) {
				$this->deverrouiller();
				$this->sauver_actions();
			}
		}
		return false;
	}

	/**
	 * Effectue l'action en attente.
	 *
	 * Appelle une methode do_{todo} de l'Actionneur où todo
	 * est le type d'action à faire.
	 *
	 * Place dans la clé 'done' de description courte du paquet
	 * le résultat de l'action (un booléen indiquant si elle s'est bien
	 * déroulée).
	**/
	function do_action() {
		if ($do = $this->work) {
			$todo = 'do_' . $do['todo'];
			lire_metas(); // avoir les metas a jour
			$this->log("Faire $todo avec $do[n]");
			$do['done'] = $this->$todo($do);
			$this->done[] = $do;
			$this->work = array();
			$this->sauver_actions();
		}
	}


	/**
	 * Attraper et activer un paquet
	 *
	 * @param array $info
	 *     Description courte du paquet
	 * @return bool
	 *     false si erreur, true sinon.
	 */
	function do_geton($info) {
		if (!$this->tester_repertoire_plugins_auto()) {
			return false;
		}
		$i = sql_fetsel('*','spip_paquets','id_paquet='.sql_quote($info['i']));
		if ($dirs = $this->get_paquet_id($i)) {
			$this->activer_plugin_dossier($dirs['dossier'], $i);
			return true;
		}
		
		$this->log("GetOn : Erreur de chargement du paquet " .$info['n']);
		return false;
	}

	/**
	 * Activer un paquet
	 *
	 * Soit il est là... soit il est à télécharger...
	 *
	 * @param array $info
	 *     Description courte du paquet
	 * @return bool
	 *     false si erreur, true sinon.
	 */
	function do_on($info) {
		$i = sql_fetsel('*','spip_paquets','id_paquet='.sql_quote($info['i']));
		// à télécharger ?
		if ($i['id_zone'] > 0) {
			return $this->do_geton($info);
		}
		
		// a activer uniquement
		// il faudra prendre en compte les autres _DIR_xx
		if (in_array($i['constante'], array('_DIR_PLUGINS','_DIR_PLUGINS_SUPPL'))) {
			$dossier = rtrim($i['src_archive'], '/');
			$this->activer_plugin_dossier($dossier, $i, $i['constante']);
			return true;
		}
		
		return false;
	}


	/**
	 * Mettre à jour un paquet
	 *
	 * @param array $info
	 *     Description courte du paquet
	 * @return bool|array
	 *     false si erreur,
	 *     description courte du nouveau plugin sinon.
	 */
	function do_up($info) {
		// ecriture du nouveau
		// suppression de l'ancien (si dans auto, et pas au meme endroit)
		// OU suppression des anciens fichiers
		if (!$this->tester_repertoire_plugins_auto()) {
			return false;
		}

		// $i est le paquet a mettre à jour (donc present)
		// $maj est le paquet a telecharger qui est a jour (donc distant)
		
		$i = sql_fetsel('*','spip_paquets','id_paquet='.sql_quote($info['i']));

		// on cherche la mise a jour...
		// c'est a dire le paquet source que l'on met a jour.
		if ($maj = sql_fetsel('pa.*',
			array('spip_paquets AS pa', 'spip_plugins AS pl'),
			array(
			'pl.prefixe='.sql_quote($info['p']),
			'pa.version='.sql_quote($info['maj']),
			'pa.id_plugin = pl.id_plugin',
			'pa.id_depot>'.sql_quote(0)),
			'', 'pa.etatnum DESC', '0,1')) {

			if ($dirs = $this->get_paquet_id($maj)) {
				// Si le plugin a jour n'est pas dans le meme dossier que l'ancien...
				// il faut :
				// - activer le plugin sur son nouvel emplacement (uniquement si l'ancien est actif)...
				// - supprimer l'ancien (si faisable)
				if (($dirs['dossier'] . '/') != $i['src_archive']) {
					if ($i['actif'] == 'oui') {
						$this->activer_plugin_dossier($dirs['dossier'], $maj);
					}

					// l'ancien repertoire a supprimer pouvait etre auto/X
					// alors que le nouveau est auto/X/Y ...
					// il faut prendre en compte ce cas particulier et ne pas ecraser auto/X !
					if (substr($i['src_archive'], 0, 5) == 'auto/' and (false === strpos($dirs['dossier'], $i['src_archive']))) {
						if (supprimer_repertoire( constant($i['constante']) . $i['src_archive']) ) {
							sql_delete('spip_paquets', 'id_paquet=' . sql_quote($info['i']));
						}
					}
				}

				$this->ajouter_plugin_interessants_meta($dirs['dossier']);
				return $dirs;
			}
		}
		return false;
	}


	/**
	 * Mettre à jour et activer un paquet
	 *
	 * @param array $info
	 *     Description courte du paquet
	 * @return bool
	 *     false si erreur, true sinon
	 */
	function do_upon($info) {
		$i = sql_fetsel('*', 'spip_paquets', 'id_paquet='.sql_quote($info['i']));
		if ($dirs = $this->do_up($info)) {
			$this->activer_plugin_dossier($dirs['dossier'], $i, $i['constante']);
			return true;
		}
		return false;
	}


	/**
	 * Désactiver un paquet
	 *
	 * @param array $info
	 *     Description courte du paquet
	 * @return bool
	 *     false si erreur, true sinon
	 */
	function do_off($info) {
		$i = sql_fetsel('*','spip_paquets','id_paquet='.sql_quote($info['i']));
		// il faudra prendre en compte les autres _DIR_xx
		if (in_array($i['constante'], array('_DIR_PLUGINS','_DIR_PLUGINS_SUPPL'))) {
			include_spip('inc/plugin');
			$dossier = rtrim($i['src_archive'], '/');
			ecrire_plugin_actifs(array(rtrim($dossier,'/')), false, 'enleve');
			sql_updateq('spip_paquets', array('actif'=>'non', 'installe'=>'non'), 'id_paquet='.sql_quote($info['i']));
			$this->actualiser_plugin_interessants();
			// ce retour est un rien faux...
			// il faudrait que la fonction ecrire_plugin_actifs()
			// retourne au moins d'eventuels message d'erreur !
			return true;
		}
		return false;
	}


	/**
	 * Désinstaller un paquet
	 *
	 * @param array $info
	 *     Description courte du paquet
	 * @return bool
	 *     false si erreur, true sinon
	 */
	function do_stop($info) {
		$i = sql_fetsel('*','spip_paquets','id_paquet=' . sql_quote($info['i']));
		// il faudra prendre en compte les autres _DIR_xx
		if (in_array($i['constante'], array('_DIR_PLUGINS','_DIR_PLUGINS_SUPPL'))) {
			include_spip('inc/plugin');
			$dossier = rtrim($i['src_archive'],'/');

			$installer_plugins = charger_fonction('installer', 'plugins');
			// retourne :
			// - false : pas de procedure d'install/desinstalle
			// - true : operation deja faite
			// - tableau : operation faite ce tour ci.
			$infos = $installer_plugins($dossier, 'uninstall');
			if (is_bool($infos) OR !$infos['install_test'][0]) {
				include_spip('inc/plugin');
				ecrire_plugin_actifs(array($dossier), false, 'enleve');
				sql_updateq('spip_paquets', array('actif'=>'non', 'installe'=>'non'), 'id_paquet='.sql_quote($info['i']));
				return true;
			} else {
				// echec
				$this->log("Échec de la désinstallation de " . $i['src_archive']);
			}
		}
		$this->actualiser_plugin_interessants();
		return false;
	}


	/**
	 * Effacer les fichiers d'un paquet
	 *
	 * @param array $info
	 *     Description courte du paquet
	 * @return bool
	 *     false si erreur, true sinon
	 */
	function do_kill($info) {
		// on reverifie que c'est bien un plugin auto !
		// il faudrait aussi faire tres attention sur un site mutualise
		// cette option est encore plus delicate que les autres...
		$i = sql_fetsel('*','spip_paquets','id_paquet='.sql_quote($info['i']));

		if (in_array($i['constante'], array('_DIR_PLUGINS','_DIR_PLUGINS_SUPPL'))
		and substr($i['src_archive'], 0, 5) == 'auto/') {
			
			$dir = constant($i['constante']) . $i['src_archive'];
			if (supprimer_repertoire($dir)) {
				$id_plugin = sql_getfetsel('id_plugin', 'spip_paquets', 'id_paquet=' . sql_quote($info['i']));

				// on supprime le paquet
				sql_delete('spip_paquets', 'id_paquet=' . sql_quote($info['i']));

				// ainsi que le plugin s'il n'est plus utilise
				$utilise = sql_allfetsel(
					'pl.id_plugin',
					array('spip_paquets AS pa', 'spip_plugins AS pl'),
					array('pa.id_plugin = pl.id_plugin', 'pa.id_plugin=' . sql_quote($id_plugin)));
				if (!$utilise) {
					sql_delete('spip_plugins', 'id_plugin=' . sql_quote($id_plugin));
				} else {
					// on met a jour d'eventuels obsoletes qui ne le sont plus maintenant
					// ie si on supprime une version superieure à une autre qui existe en local...
					include_spip('inc/svp_depoter_local');
					svp_corriger_obsolete_paquets(array($id_plugin));
				}

				// on tente un nettoyage jusqu'a la racine de auto/
				// si la suppression concerne une profondeur d'au moins 2
				// et que les repertoires sont vides
				$chemins = explode('/', $i['src_archive']); // auto / prefixe / version
				// le premier c'est auto
				array_shift($chemins);
				// le dernier est deja fait...
				array_pop($chemins);
				// entre les deux...
				while (count($chemins)) {
					$vide = true;
					$dir = constant($i['constante']) . 'auto/' . implode('/', $chemins);
					$fichiers = scandir($dir);
					if ($fichiers) {
						foreach ($fichiers as $f) {
							if ($f[0] != '.') {
								$vide = false;
								break;
							}
						}
					}
					// on tente de supprimer si c'est effectivement vide.
					if ($vide and !supprimer_repertoire($dir)) {
						break;
					}
					array_pop($chemins);
				}
				return true;
			}
		}

		return false;
	}


	/**
	 * Installer une librairie
	 *
	 * @param array $info
	 *     Description courte du paquet (une librairie ici)
	 * @return bool
	 *     false si erreur, true sinon
	 */
	function do_getlib($info) {
		if (!defined('_DIR_LIB') or !_DIR_LIB) {
			$this->err(_T('svp:erreur_dir_dib_indefini'));
			$this->log("/!\ Pas de _DIR_LIB defini !");
			return false;
		}
		if (!is_writable(_DIR_LIB)) {
			$this->err(_T('svp:erreur_dir_dib_ecriture', array('dir' => _DIR_LIB )));
			$this->log("/!\ Ne peut pas écrire dans _DIR_LIB !");
			return false;
		}
		if(!autoriser('plugins_ajouter')){
			$this->err(_T('svp:erreur_auth_plugins_ajouter_lib'));
			$this->log("/!\ Pas autorisé à ajouter des libs !");
			return false;
		}

		$this->log("Recuperer la librairie : " . $info['n'] );

		// on recupere la mise a jour...
		include_spip('action/teleporter');
		$teleporter_composant = charger_fonction('teleporter_composant', 'action');
		$ok = $teleporter_composant('http', $info['v'], _DIR_LIB . $info['n']);
		if ($ok === true) {
			return true;
		}
		
		$this->err($ok);
		$this->log("Téléporteur en erreur : " . $ok);
		return false;
	}


	/**
	 * Télécharger un paquet
	 *
	 * @param array $info
	 *     Description courte du paquet
	 * @return bool
	 *     false si erreur, true sinon
	 */
	function do_get($info) {
		if (!$this->tester_repertoire_plugins_auto()) {
			return false;
		}

		$i = sql_fetsel('*', 'spip_paquets', 'id_paquet=' . sql_quote($info['i']));
	
		if ($dirs = $this->get_paquet_id($info['i'])) {
			$this->ajouter_plugin_interessants_meta($dirs['dossier']);
			return true;
		}

		return false;
	}


	/**
	 * Lancer l'installation d'un paquet
	 *
	 * @param array $info
	 *     Description courte du paquet
	 * @return bool
	 *     false si erreur, true sinon
	 */
	function do_install($info) {
		return $this->installer_plugin($info);
	}


	/**
	 * Activer un plugin 
	 *
	 * @param string $dossier
	 *     Chemin du répertoire du plugin
	 * @param array $i
	 *     Description en BDD du paquet - row SQL (tableau clé => valeur)
	 * @param string $constante
	 *     Constante indiquant le chemin de base du plugin (_DIR_PLUGINS, _DIR_PLUGINS_SUPPL, _DIR_PLUGINS_DIST)
	 * @return void
	**/
	function activer_plugin_dossier($dossier, $i, $constante='_DIR_PLUGINS') {
		include_spip('inc/plugin');
		$this->log("Demande d'activation de : " . $dossier);
		
		//il faut absolument que tous les fichiers de cache
		// soient inclus avant modification, sinon un appel ulterieur risquerait
		// de charger des fichiers deja charges par un autre !
		// C'est surtout le ficher de fonction le probleme (options et pipelines
		// sont normalement deja charges).
		if (@is_readable(_CACHE_PLUGINS_OPT)) {include_once(_CACHE_PLUGINS_OPT);}
		if (@is_readable(_CACHE_PLUGINS_FCT)) {include_once(_CACHE_PLUGINS_FCT);}
		if (@is_readable(_CACHE_PIPELINES))   {include_once(_CACHE_PIPELINES);}

		include_spip('inc/plugin');
		ecrire_plugin_actifs(array($dossier), false, 'ajoute');
		$installe = $i['version_base'] ? 'oui' : 'non';
		if ($installe == 'oui') {
			if(!$i['constante'])
				$i['constante'] = '_DIR_PLUGINS';
			// installer le plugin au prochain tour
			$new_action = array_merge($this->work, array(
				'todo'=>'install',
				'dossier'=>rtrim($dossier,'/'),
				'constante'=>$i['constante'],
				'v'=>$i['version'], // pas forcement la meme version qu'avant lors d'une mise a jour.
			));
			array_unshift($this->end, $new_action);
			$this->log("Demande d'installation de $dossier");
			#$this->installer_plugin($dossier);
		}

		$this->ajouter_plugin_interessants_meta($dossier);
		$this->actualiser_plugin_interessants();
	}


	/**
	 * Actualiser les plugins intéressants
	 *
	 * Décrémente chaque score de plugin présent dans la méta
	 * 'plugins_interessants' et signifiant que ces plugins
	 * ont été utilisés récemment.
	 * 
	 * Les plugins atteignant un score de zéro sont évacués ce la liste.
	 */
	function actualiser_plugin_interessants() {
		// Chaque fois que l'on valide des plugins,
		// on memorise la liste de ces plugins comme etant "interessants",
		// avec un score initial, qui sera decremente a chaque tour :
		// ainsi un plugin active pourra reter visible a l'ecran,
		// jusqu'a ce qu'il tombe dans l'oubli.
		$plugins_interessants = @unserialize($GLOBALS['meta']['plugins_interessants']);
		if (!is_array($plugins_interessants)) {
			$plugins_interessants = array();
		}
		
		$dossiers = array();
		$dossiers_old = array();
		foreach($plugins_interessants as $p => $score) {
			if (--$score > 0) {
				$plugins_interessants[$p] = $score;
				$dossiers[$p.'/'] = true;
			} else {
				unset($plugins_interessants[$p]);
				$dossiers_old[$p.'/'] = true;
			}
		}

		// enlever les anciens
		if ($dossiers_old) {
			// ATTENTION, il faudra prendre en compte les _DIR_xx
			sql_updateq('spip_paquets', array('recent'=>0), sql_in('src_archive', array_keys($dossiers_old)));
		}

		$plugs = sql_allfetsel('src_archive','spip_paquets', 'actif='.sql_quote('oui'));
		$plugs = array_map('array_shift', $plugs);
		foreach ($plugs as $dossier) {
			$dossiers[$dossier] = true;
			$plugins_interessants[ rtrim($dossier, '/') ] = 30; // score initial
		}

		$plugs = sql_updateq('spip_paquets', array('recent'=>1), sql_in('src_archive', array_keys($dossiers)));
		ecrire_meta('plugins_interessants', serialize($plugins_interessants));
	}


	/**
	 * Ajoute un plugin dans les plugins intéressants
	 *
	 * Initialise à 30 le score du plugin indiqué par le chemin transmis,
	 * dans la liste des plugins intéressants.
	 *
	 * @param string $dir
	 *     Chemin du répertoire du plugin
	 */
	function ajouter_plugin_interessants_meta($dir) {
		$plugins_interessants = @unserialize($GLOBALS['meta']['plugins_interessants']);
		if (!is_array($plugins_interessants)) {
			$plugins_interessants = array();
		}
		$plugins_interessants[$dir] = 30;
		ecrire_meta('plugins_interessants', serialize($plugins_interessants));
	}

	/**
	 * Lancer l'installation d'un plugin
	 *
	 * @param array $info
	 *     Description courte du paquet
	 * @return bool
	 *     false si erreur, true sinon
	 */
	function installer_plugin($info){
		// il faut info['dossier'] et info['constante'] pour installer
		if ($plug = $info['dossier']) {
			$installer_plugins = charger_fonction('installer', 'plugins');
			$infos = $installer_plugins($plug, 'install', $info['constante']);
			if ($infos) {
				// en absence d'erreur, on met a jour la liste des plugins installes...
				if (!is_array($infos) OR $infos['install_test'][0]) {
					$meta_plug_installes = @unserialize($GLOBALS['meta']['plugin_installes']);
					if (!$meta_plug_installes) {
						$meta_plug_installes=array();
					}
					$meta_plug_installes[] = $plug;
					ecrire_meta('plugin_installes',serialize($meta_plug_installes),'non');
				}

				if (!is_array($infos)) {
					// l'installation avait deja ete faite un autre jour
					return true; 
				} else {
					// l'installation est neuve
					list($ok, $trace) = $infos['install_test'];
					if ($ok) {
						return true;
					}
					// l'installation est en erreur
					$this->err(_T('svp:message_action_finale_install_fail',
						array('plugin' => $info['n'], 'version'=>denormaliser_version($info['v']))) . "<br />" . $trace);
				}
			}
		}
		return false;
	}


	/**
	 * Télécharge un paquet
	 * 
	 * Supprime les fichiers obsolètes (si présents)
	 *
	 * @param int|array $id_or_row
	 *     Identifiant du paquet ou description ligne SQL du paquet
	 * @return bool|array
	 *     False si erreur.
	 *     Tableau de 2 index sinon :
	 *     - dir : Chemin du paquet téléchargé depuis la racine
	 *     - dossier : Chemin du paquet téléchargé, depuis _DIR_PLUGINS
	 */
	function get_paquet_id($id_or_row) {
		// on peut passer direct le row sql...
		if (!is_array($id_or_row)) {
			$i = sql_fetsel('*','spip_paquets','id_paquet='.sql_quote($id_or_row));
		} else {
			$i = $id_or_row;
		}
		unset($id_or_row);

		if ($i['nom_archive'] and $i['id_depot']) {
			$this->log("Recuperer l'archive : " . $i['nom_archive'] );
			if ($adresse = sql_getfetsel('url_archives', 'spip_depots', 'id_depot='.sql_quote($i['id_depot']))) {
				$zip = $adresse . '/' . $i['nom_archive'];

				// destination : auto/prefixe/version (sinon auto/nom_archive/version)
				$prefixe = sql_getfetsel('pl.prefixe',
					array('spip_paquets AS pa', 'spip_plugins AS pl'),
					array('pa.id_plugin = pl.id_plugin', 'pa.id_paquet=' . sql_quote($i['id_paquet'])));

				// prefixe
				$base =  ($prefixe ? strtolower($prefixe) : substr($i['nom_archive'], 0, -4) ); // enlever .zip ...

				// prefixe/version
				$dest = $base . '/v' . denormaliser_version($i['version']);
				
				// si on tombe sur un auto/X ayant des fichiers (et pas uniquement des dossiers)
				// ou un dossier qui ne commence pas par 'v'
				// c'est que auto/X n'était pas chargé avec SVP
				// ce qui peut arriver lorsqu'on migre de SPIP 2.1 à 3.0
				// dans ce cas, on supprime auto X pour mettre notre nouveau paquet.
				$ecraser_base = false;
				if (is_dir(_DIR_PLUGINS_AUTO . $base)) {
					$base_files = scandir(_DIR_PLUGINS_AUTO . $base);
					if (is_array($base_files)) {
						$base_files = array_diff($base_files, array('.', '..'));
						foreach ($base_files as $f) {
							if (($f[0] != '.' and $f[0] != 'v') // commence pas par v
							OR ($f[0] != '.' and !is_dir(_DIR_PLUGINS_AUTO . $base . '/' . $f))) { // commence par v mais pas repertoire
								$ecraser_base = true;
								break;
							}
						}
					}
				}
				if ($ecraser_base) {
					supprimer_repertoire(_DIR_PLUGINS_AUTO . $base);
				}


				// on recupere la mise a jour...
				include_spip('action/teleporter');
				$teleporter_composant = charger_fonction('teleporter_composant', 'action');
				$ok = $teleporter_composant('http', $zip, _DIR_PLUGINS_AUTO . $dest);
				if ($ok === true) {
					return array(
						'dir'=> _DIR_PLUGINS_AUTO . $dest,
						'dossier' => 'auto/' . $dest, // c'est depuis _DIR_PLUGINS ... pas bien en dur...
					);
				}
				$this->err($ok);
				$this->log("Téléporteur en erreur : " . $ok);
			} else {
				$this->log("Aucune adresse pour le dépot " . $i['id_depot'] );
			}
		}
		return false;
	}


	/**
	 * Teste que le répertoire plugins auto existe et
	 * que l'on peut ecrire dedans !
	 *
	 * @return bool
	 *     True si on peut écrire dedans, false sinon
	**/
	function tester_repertoire_plugins_auto() {
		include_spip('inc/plugin'); // pour _DIR_PLUGINS_AUTO
		if (!defined('_DIR_PLUGINS_AUTO') or !_DIR_PLUGINS_AUTO) {
			$this->err(_T('svp:erreur_dir_plugins_auto_indefini'));
			$this->log("/!\ Pas de _DIR_PLUGINS_AUTO defini !");
			return false;
		}
		if (!is_writable(_DIR_PLUGINS_AUTO)) {
			$this->err(_T('svp:erreur_dir_plugins_auto_ecriture', array('dir'=>_DIR_PLUGINS_AUTO)));
			$this->log("/!\ Ne peut pas écrire dans _DIR_PLUGINS_AUTO !");
			return false;
		}
		return true;
	}


	/**
	 * Teste si le plugin SVP (celui-ci donc) a
	 * été désinstallé / désactivé dans les actions réalisées 
	 *
	 * @note
	 *     On ne peut tester sa désactivation que dans le hit où la désinstallation
	 *     est réalisée, puisque après, s'il a été désactivé, au prochain hit
	 *     on ne connaîtra plus ce fichier !
	 * 
	 * @return bool
	 *     true si SVP a été désactivé, false sinon
	**/
	function tester_si_svp_desactive() {
		foreach ($this->done as $d) {
			if ($d['p'] == 'SVP'
			AND $d['done'] == true
			AND in_array($d['todo'], array('off', 'stop'))) {
				return true;
			}
		}
		return false;
	}
	
}


/**
 * Gère le traitement des actions des formulaires utilisant l'Actionneur
 *
 * @param array $actions
 *     Liste des actions a faire (id_paquet => action)
 * @param array $retour
 *     Tableau de retour du CVT dans la partie traiter
 * @param string $redirect
 *     URL de retour
 * @return void
**/
function svp_actionner_traiter_actions_demandees($actions, &$retour,$redirect=null) {
		$actionneur = new Actionneur();
		$actionneur->ajouter_actions($actions);
		$actionneur->verrouiller();
		$actionneur->sauver_actions();
		
		$redirect = $redirect ? $redirect : generer_url_ecrire('admin_plugin');
		$retour['redirect'] = generer_url_action('actionner', 'redirect='.urlencode($redirect));
		set_request('_todo', '');
		$retour['message_ok'] = _T("svp:action_patienter");
}
?>
messages.php000066600000014062152503076610007100 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2014                                                *
 *  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;

include_spip('inc/filtres');
include_spip('base/abstract_sql');

if (!defined('_EMAIL_GENERAL'))
	define('_EMAIL_GENERAL','general'); // permet aux admin d'envoyer un email a tout le monde

/**
 * Lister les statuts des auteurs pouvant recevoir un message
 * c'est tous les auteurs au moins redacteur
 * 
 * @return array
 */
function messagerie_statuts_destinataires_possibles(){
	include_spip('inc/filtres_ecrire');
	return pipeline('messagerie_statuts_destinataires_possibles', auteurs_lister_statuts('redacteurs',false));
}

/**
 * Nettoyer une liste de destinataires
 * @param $destinataires
 * @return array
 */
function messagerie_nettoyer_destinataires($destinataires){
	foreach ($destinataires as $k=>$id){
		// il se peut que l'id recupere l'ancre qui suit avec certains ie ... :(
		if (preg_match(',^[0-9]+#[a-z_0-9]+,',$id))
			$destinataires[$k] = intval($id);
	}
	return $destinataires;
}

/**
 * Fonction generique de verification des destinataires
 * lors de l'envoi d'un message ou de recommander
 * un destinataire peut etre un id_auteur numerique
 * ou une adresse mail valide, si l'options accepter_email est true
 *
 * @param array $destinataires
 * @param array $options
 * @return array
 */
function messagerie_verifier_destinataires($destinataires,$options=array('accepter_email'=>true)){
	$erreurs = array();

	$destinataires = messagerie_nettoyer_destinataires($destinataires);
	foreach ($destinataires as $id){
		if (is_numeric($id)){
			if (!$id)
				$erreurs[] = _T('organiseur:erreur_destinataire_invalide',array('dest'=>$id));
		}
		else {
			if (!$options['accepter_email']
				OR !email_valide($id))
				$erreurs[] = _T('organiseur:erreur_destinataire_invalide',array('dest'=>$id));
		}
	}

	return $erreurs;
}

/**
 * Selectionner les destinataires en distinguant emails et id_auteur
 *
 * @param array $dests
 * @return array
 */
function messagerie_destiner($dests){
	// separer les destinataires auteur des destinataires email
	$auteurs_dest = array();
	$email_dests = array();

	$dests = messagerie_nettoyer_destinataires($dests);
	foreach ($dests as $id){
		if (is_numeric($id))
			$auteurs_dest[] = $id;
		elseif (defined('_MESSAGERIE_EMAIL_GENERAL') AND $id!=_MESSAGERIE_EMAIL_GENERAL)
			$email_dests[] = $id;
	}
	if (count($email_dests)) {
		// retrouver les id des emails pour ceux qui sont en base
		$res = sql_select('id_auteur,email','spip_auteurs',sql_in('email', $email_dests));
		$auteurs_dest_found = array();
		while ($row = sql_fetch($res)){
			$auteurs_dest_found[] = $row['id_auteur'];
		}
		$auteurs_dest = array_merge($auteurs_dest,$auteurs_dest_found);
	}
	return array($auteurs_dest,$email_dests);
}

/**
 * Diffuser un message par la messagerie interne
 *
 * @param int $id_message
 * @param array $auteurs_dest
 * @return bool|int
 */
function messagerie_diffuser_message($id_message, $auteurs_dest=array()){
	$out = false;
	if ($id_message=intval($id_message)
	  AND count($auteurs_dest)){
		include_spip('action/editer_liens');
		$out = objet_associer(array('auteur'=>$auteurs_dest),array('message'=>$id_message),array('vu'=>'non'));
	}
	return $out;
}

/**
 * Envoyer un message par mail pour les destinataires externes
 *
 * @param int $id_message
 * @param array $emails_dest
 * @return bool
 */
function messagerie_mailer_message($id_message, $emails_dest=array()){
	if ($id_message=intval($id_message)
	  AND count($emails_dest)) {
		if ($row = sql_fetsel('titre,texte,id_auteur','spip_messages','id_message='.intval($id_message))){
			$from = sql_getfetsel('email','spip_auteurs','id_auteur='.$row['id_auteur']);
			foreach($emails_dest as $email)
				job_queue_add(
					'envoyer_mail',
					'messagerie mail',
				  array($email,$row['titre'],array('texte'=>$row['texte'],'from'=>$from)),
					'inc/'
				);
			return true;
		}
	}
	return false;
}

/**
 * Marquer un message dans l'etat indique par $vu
 *
 * @param int $id_auteur
 * @param array $liste
 * @param string $vu
 * @return void
 */
function messagerie_marquer_message($id_auteur,$liste,$vu){
	include_spip('action/editer_liens');
	if (!is_array($liste))
		$liste = array($liste);
	// completer les liens qui n'existent pas encore
	// ex : pour marquer lue une annonce, on ajoute le lien d'abord (n'existe pas)
	// puis on le marque 'oui'
	$liens = objet_trouver_liens(array('auteur'=>$id_auteur),array('message'=>$liste));
	$l = array();
	foreach($liens as $lien)
		$l[] = $lien['message'];
	objet_associer(array('auteur'=>$id_auteur),array('message'=>array_diff($liste,$l)),array('vu'=>$vu));
	// puis les marquer tous lus
	objet_qualifier_liens(array('auteur'=>$id_auteur),array('message'=>$liste),array('vu'=>$vu));
	include_spip('inc/invalideur');
	suivre_invalideur("message/".implode(',',$liste));
}

/**
 * Marquer un message comme lu
 *
 * @param int $id_auteur
 * @param array $liste_id_message
 */
function messagerie_marquer_lus($id_auteur,$liste_id_message){messagerie_marquer_message($id_auteur,$liste_id_message,'oui');}

/**
 * Marquer un message comme non lu
 *
 * @param int $id_auteur
 * @param array $liste_id_message
 */
function messagerie_marquer_non_lus($id_auteur,$liste_id_message){messagerie_marquer_message($id_auteur,$liste_id_message,'non');}

/**
 * Effacer un message recu
 *
 * @param int $id_auteur
 * @param array $liste_id_message
 */
function messagerie_effacer_message_recu($id_auteur,$liste_id_message){messagerie_marquer_message($id_auteur,$liste_id_message,'poub');}

?>
quete_calendrier.php000066600000021733152503076610010607 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2014                                                *
 *  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;

include_spip('inc/filtres');
// http://code.spip.net/@calendrier_categories
function calendrier_categories($table, $num, $objet)
{
  if (function_exists('generer_calendrier_class'))
    return generer_calendrier_class($table, $num, $objet);
  else {
    // cf agenda.css
    $num= sql_getfetsel((($objet != 'id_breve') ? 'id_secteur' : 'id_rubrique') . " AS id", $table, "$objet=$num");

    return 'calendrier-couleur' . (($num%14)+1);
  }
}

// ici on prend en fait le jour, la veille et le lendemain

// http://code.spip.net/@quete_calendrier_jour
function quete_calendrier_jour($annee,$mois,$jour) {
	$avant = "'" . date("Y-m-d", mktime(0,0,0,$mois,$jour-1,$annee)) . "'";
	$apres = "'" . date("Y-m-d", mktime(1,1,1,$mois,$jour+1,$annee)) .
	" 23:59:59'";
	return array($avant, $apres);
}

// retourne un tableau de 2 tableaux indexes par des dates
// - le premier indique les evenements du jour, sans indication de duree
// - le deuxime indique les evenements commencant ce jour, avec indication de duree

// http://code.spip.net/@quete_calendrier_interval
function quete_calendrier_interval($limites) {
	include_spip('inc/urls');
	list($avant, $apres) = $limites;
	$evt = array();
	quete_calendrier_interval_articles($avant, $apres, $evt);
	quete_calendrier_interval_breves($avant, $apres, $evt);
	quete_calendrier_interval_rubriques($avant, $apres, $evt);
	return array($evt, quete_calendrier_interval_rv($avant, $apres));
}

// http://code.spip.net/@quete_calendrier_interval_forums
function  quete_calendrier_interval_forums($limites, &$evenements) {
	list($avant, $apres) = $limites;
	$result=sql_select("DISTINCT titre, date_heure, id_forum",	"spip_forum", "date_heure >= $avant AND date_heure < $apres", '',  "date_heure");
	while($row=sql_fetch($result)){
		$amj = date_anneemoisjour($row['date_heure']);
		$id = $row['id_forum'];
		if (autoriser('voir','forum',$id))
			$evenements[$amj][]=
			array(
				'URL' => generer_url_entite($id, 'forum'),
				'CATEGORIES' => 'calendrier-couleur7',
				'SUMMARY' => $row['titre'],
				'DTSTART' => date_ical($row['date_heure']));
	}
}

# 3 fonctions retournant les evenements d'une periode
# le tableau retourne est indexe par les balises du format ics
# afin qu'il soit facile de produire de tels documents.
# L'URL de chacun de ces evenements est celle de l'espace prive
# pour faciliter la navigation, ce qu'on obtient utilisant
# le 4e argument des fonctions generer_url_ecrire_$table

// http://code.spip.net/@quete_calendrier_interval_articles
function quete_calendrier_interval_articles($avant, $apres, &$evenements) {

  $result=sql_select('id_article, titre, date, descriptif, chapo,  lang', 'spip_articles', "statut='publie' AND date >= $avant AND date < $apres", '', "date");

	// tables traduites
	$objets = explode(',', $GLOBALS['meta']['multi_objets']);

	if (in_array('spip_articles', $objets)) {
	  include_spip('inc/lang_liste');
	  $langues = $GLOBALS['codes_langues'];
	} else $langues = array();
	while($row=sql_fetch($result)){
		$amj = date_anneemoisjour($row['date']);
		$id = $row['id_article'];
		if (autoriser('voir','article',$id))
			$evenements[$amj][]=
			    array(
				'CATEGORIES' => calendrier_categories('spip_articles', $id, 'id_article'),
				'DESCRIPTION' => $row['descriptif'] ? $row['descriptif'] : $langues[$row['lang']],
				'SUMMARY' => $row['titre'],
				'URL' => generer_url_ecrire_objet('article',$id, '','','prop'));
	}
}

// http://code.spip.net/@quete_calendrier_interval_rubriques
function quete_calendrier_interval_rubriques($avant, $apres, &$evenements) {

  $result=sql_select('DISTINCT R.id_rubrique, titre, descriptif, date', 'spip_rubriques AS R, spip_documents_liens AS L', "statut='publie' AND	date >= $avant AND	date < $apres AND	R.id_rubrique = L.id_objet AND L.objet='rubrique'",'', "date");
	while($row=sql_fetch($result)){
		$amj = date_anneemoisjour($row['date']);
		$id = $row['id_rubrique'];
		if (autoriser('voir','rubrique',$id))
			$evenements[$amj][]=
			    array(
				'CATEGORIES' => calendrier_categories('spip_rubriques', $id, 'id_rubrique'),
				'DESCRIPTION' => $row['descriptif'],
				'SUMMARY' => $row['titre'],
				'URL' => generer_url_ecrire_objet('rubrique',$id, '','', 'prop'));
	}
}

// http://code.spip.net/@quete_calendrier_interval_breves
function quete_calendrier_interval_breves($avant, $apres, &$evenements) {
  $result=sql_select("id_breve, titre, date_heure, id_rubrique", 'spip_breves',	"statut='publie' AND date_heure >= $avant AND date_heure < $apres", '', "date_heure");
	while($row=sql_fetch($result)){
		$amj = date_anneemoisjour($row['date_heure']);
		$id = $row['id_breve'];
		$ir = $row['id_rubrique'];
		if (autoriser('voir','breve',$id))
			$evenements[$amj][]=
			array(
			      'URL' => generer_url_ecrire_objet('breve',$id, '','', 'prop'),
			      'CATEGORIES' => calendrier_categories('spip_breves', $ir, 'id_breve'),
			      'SUMMARY' => $row['titre']);
	}
}

// http://code.spip.net/@quete_calendrier_interval_rv
function quete_calendrier_interval_rv($avant, $apres) {
	include_spip('inc/session');
	$connect_id_auteur = session_get('id_auteur');

	$evenements= array();
	if (!$connect_id_auteur) return $evenements;
	$result=sql_select("M.id_message, M.titre, M.texte, M.date_heure, M.date_fin, M.type",
		"spip_messages AS M LEFT JOIN spip_auteurs_liens AS L ON (L.id_objet=M.id_message)",
		"((L.objet='message' AND (L.id_auteur=$connect_id_auteur OR M.type='affich')) OR (L.objet IS NULL AND M.id_auteur=$connect_id_auteur AND ".sql_in('M.type',array('pb','affich'))."))"
		." AND M.rv='oui' AND ((M.date_fin >= $avant OR M.date_heure >= $avant) AND M.date_heure <= $apres) AND M.statut='publie'",
		"M.id_message", "M.date_heure");
	while($row=sql_fetch($result)){
		$date_heure=$row["date_heure"];
		$date_fin=$row["date_fin"];
		$type=$row["type"];
		$id_message=$row['id_message'];

		if ($type=="pb")
		  $cat = 'calendrier-couleur2';
		else {
		  if ($type=="affich")
		  $cat = 'calendrier-couleur4';
		  else {
		    if ($type!="normal")
		      $cat = 'calendrier-couleur12';
		    else {
		      $cat = 'calendrier-couleur9';
		      $auteurs = array_map('array_shift', sql_allfetsel("nom", "spip_auteurs AS A LEFT JOIN spip_auteurs_liens AS L ON L.id_auteur=A.id_auteur", "(L.objet='message' AND L.id_objet=$id_message AND (A.id_auteur!=$connect_id_auteur))"));
		    }
		  }
		}

		$jour_avant = substr($avant, 9,2);
		$mois_avant = substr($avant, 6,2);
		$annee_avant = substr($avant, 1,4);
		$jour_apres = substr($apres, 9,2);
		$mois_apres = substr($apres, 6,2);
		$annee_apres = substr($apres, 1,4);
		$ical_apres = date_anneemoisjour("$annee_apres-$mois_apres-".sprintf("%02d",$jour_apres));

		// Calcul pour les semaines a cheval sur deux mois
 		$j = 0;
		$amj = date_anneemoisjour("$annee_avant-$mois_avant-".sprintf("%02d", $j+($jour_avant)));

		while ($amj <= $ical_apres) {
		if (!($amj == date_anneemoisjour($date_fin) AND preg_match(",00:00:00,", $date_fin)))  // Ne pas prendre la fin a minuit sur jour precedent
			$evenements[$amj][$id_message]=
			  array(
				'URL' => generer_url_ecrire("message","id_message=$id_message"),
				'DTSTART' => date_ical($date_heure),
				'DTEND' => date_ical($date_fin),
				'DESCRIPTION' => $row['texte'],
				'SUMMARY' => $row['titre'],
				'CATEGORIES' => $cat,
				'ATTENDEE' => (count($auteurs) == 0) ? '' : join($auteurs,", "));

			$j ++;
			$ladate = date("Y-m-d",mktime (1,1,1,$mois_avant, ($j + $jour_avant), $annee_avant));

			$amj = date_anneemoisjour($ladate);

		}

	}
  return $evenements;
}

// http://code.spip.net/@quete_calendrier_agenda
function quete_calendrier_agenda ($annee, $mois) {
	include_spip('inc/session');
	$connect_id_auteur = session_get('id_auteur');

	$rv = array();
	if (!$connect_id_auteur) return $rv;
	$date = date("Y-m-d", mktime(0,0,0,$mois, 1, $annee));
	$mois = mois($date);
	$annee = annee($date);

	// rendez-vous personnels dans le mois
	$result_messages = sql_select("M.titre AS summary, M.texte AS description, M.id_message AS uid, M.date_heure", "spip_messages AS M, spip_auteurs_liens AS L", "((L.id_auteur=$connect_id_auteur AND L.id_objet=M.id_message AND L.objet='message') OR M.type='affich') AND M.rv='oui' AND M.date_heure >='$annee-$mois-1' AND date_heure < DATE_ADD('$annee-$mois-1', INTERVAL 1 MONTH) AND M.statut='publie'");
	while($row=sql_fetch($result_messages)) {
		$rv[journum($row['date_heure'])] = $row;
	}
	return $rv;
}

?>
date_gestion.php000066600000003472152503076610007741 0ustar00<?php

/***************************************************************************\
 *  SPIP, Systeme de publication pour l'internet                           *
 *                                                                         *
 *  Copyright (c) 2001-2014                                                *
 *  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 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('organiseur:erreur_date');
	elseif (!$ret=mktime($date[3],$date[4],$date[5],$date[1],$date[2],$date[0]))
		$erreurs["date_$suffixe"] = _T('organiseur:erreur_heure');
	if ($ret){
		if (trim(_request("date_$suffixe")!==($d=date('d/m/Y',$ret)))){
			$erreurs["date_$suffixe"] = _T('organiseur:erreur_date_corrigee');
			set_request("date_$suffixe",$d);
		}
		if ($horaire AND trim(_request("heure_$suffixe")!==($h=date('H:i',$ret)))){
			$erreurs["heure_$suffixe"] = _T('organiseur:erreur_heure_corrigee');
			set_request("heure_$suffixe",$h);
		}
	}
	return $ret;
}

?>