All pastes #947307 Raw Edit

Stuff

public php v1 · immutable
·published 2008-03-18 12:25 UTC
rendered paste body
<?php/**	*	Class gérant des TPLs, inspirée de celle de {@link http://www.fire-soft-board.com FSB2}.	*	*	Vous êtes libre d'utiliser et de distribuer ce script comme vous l'entendez, en gardant à l'esprit 	*	que ce script est, à l'origine, fait par des développeurs bénévoles : en conséquence, veillez à 	*	laisser le Copyright, par respect de ceux qui ont consacré du temps à la création du script. 	*	*	@package		Talus' TPL	*	@author		Talus <talusch@gmail.com>	*	@copyright		©Talus, Talus' Works 2006, 2009	*	@link			http://www.talus-works.net Talus' Works	*	@license		http://www.gnu.org/licenses/gpl.html GNU Public License 2+	*	@begin		23/12/2006, Talus	*	@last			12/03/2008, Talus*//**	*	Le moteur des TPLs :)	*	@package	Talus' TPL	*	@author	Talus <talusch@gmail.com>*/class Talus_TPL {	/**		*	Chemin des Templates, et celui du cache		*	@access	private		*	@var		string	*/	private $root = './';	private $cache = '/cache/';		/** 		*	Contenu des blocs.		*		*	Accès :		*	<code><?php		*		// -- Accès globaux aux variables de blocs :		*		$this->blocks['block1'][#iteration1][...]['blockN'][#iterationN][var] = 'valeur';		*		*		// -- Accès aux variables de root (n'appartenant pas à un bloc) :		*		$this->blocks[$tpl]['.'][0][var] = valeur;		*	?></code>		*	@more	Pour une variable "simple", voir $this->vars		*	@see		self::vars		*	@access	private		*	@var		array	*/	private $blocks = array(			'.' => array(					0 => array()				)		); 		/**		*	Variables globales (définies dans le block root)		*	@access	private		*	@var		ref	*/	private $vars = '';		/**		*	Contient les infos du Template en cours.		*		*	De la forme :		*	<code><?php		*	$this->infos[$tpl] = array(		*			 'file' => (string) 'Chemin du Tpl',		*			 'size' => (int) 'Taille du tpl',		*			 'last_modif' => (int) 'Derniere modif du tpl',		*			 'included' => (array) 'Liste des fichiers inclus (Array())',		*			 'cache' => (string) 'Hash du cache'		*		);		*	?></code>		*	@access	private		*	@var		array	**/	private $infos = array(); 		/**		*	Contient le fichier à compiler (cache 1), lors du parsage 		*	@access	private		*	@var		string	**/		private $tpl = ''; 		/**		*	Dernier alias défini.		*	@access	private		*	@var		string	**/	private $last_tpl = ''; 		/**		*	Liste des fichiers utilisés.		*	@access	private		*	@var		string	**/	private $files_list = array(); 		/**		*	Cache proche des blocs, lors de la compilation et de l'ajout d'un bloc.		*	@access	private		*	@var		array	**/	private $cache_blocks = array();		/**		*	Status du mode débug.		*	@access	private		*	@var		bool	*/	private $debug = true;		/**		*	Constante pour la gestion des erreurs.		*	@var	integer	*/	const NB_LINES = 5;		/**		*	Constructeur des Templates.		*	@param	string	$root		Le dossier contenant les templates.		*	@param	string	$cache	Le dossier contenant le cache.		*	@param	bool		$debug	Activer le debug (pour la compilation) ?		*	@access	public		*	@return	void	*/	public function __construct($root = './', $cache = './cache/', $debug = true){ //@public		// -- Destruction du cache des fichiers de PHP.		clearstatcache();				// -- Status du mode débug		$this->debug = (bool)$debug;				// -- Un petit alias....		$this->vars = &$this->blocks['.'][0];				// -- Et on selectionne le dossier du thème à utiliser :3		$this->set_dir($root, $cache);	}		/**		*	 Permet de choisir le dossier contenant les tpls.		*	@param	string	$root	Le dossier contenant les templates.		*	@access	public		*	@return	void	*/	public function set_dir($root = './', $cache = './cache/'){ //@public		// -- On ampute le root du slash final, si il existe.		$root = rtrim($root, '/');		$cache = rtrim($cache, '/');				// -- Le dossier existe-t-il ?		if( !is_dir($root) ){			exit('Template->set_dir :: Le répertoire <b>' . $root . '</b> n\'existe pas.');		}				// -- Le dossier de cache existe-t-il ?		if( !is_dir($cache) ){			exit('Template->set_dir :: Le répertoire de cache <b>' . $cache . '</b> n\'existe pas.');		}				$this->root = $root;		$this->cache = $cache;	}		/**		*	Ajoute un fichier à l'instance des templates.		*	@param	string	$fichier	nom du fichier à ajouter.		*	@access	public		*	@return	bool	*/	public function set_file($fichier){//@public		// -- Si l'alias est déjà déclaré, on renvoit false.		if( in_array($fichier, $this->files_list) ){			return false;		}				// -- Le fichier n'existe pas : on renvoi une erreur fatale.		if( !is_file($this->root . '/' . $fichier) ){			exit('Template->set_file :: Le modèle <em>' . $fichier . '</em> n\'existe pas.');		}				$this->infos[$fichier] = array(				'file' => $this->root . '/' . $fichier,				'size' => filesize($this->root . '/' . $fichier),				'last_modif' => filemtime($this->root . '/' . $fichier),				'included' => array(),				'cache' => str_replace('/', '_', $fichier)			);					$this->files_list[] = $fichier;		$this->last_tpl = $fichier;				return true;	}		/**		*	Ajoute un couple de variables au TPL.		*	@param	array	$ary_vars		Array de la forme (var => value), qui contient les variables et leurs valeurs.		*	@see		Talus_TPL::assign_var()		*	@access	public		*	@return	void	*/	public function assign_vars($ary_vars){//@public		$this->assign_var($ary_vars);	}		/**		*	Ajoute une simple variable, ou plusieurs au TPL.		*	@param	string	$var		Nom de la variable à ajouter, ou Array de la forme (var => value), qui contient les variables et leurs valeurs		*	@param	mixed		$value	Valeur de la variable à ajouter.		*	@access	public		*	@return	void	*/	public function assign_var($var, $value = ''){//@public		if( is_array($var) ){			$this->vars = array_merge($this->vars, $var);		}		else {			$this->vars[$var] = $value;		}	}	/**		*	Détruit une variable		*	@param	array|string	$variable	Variables à détruire.		*	@access	public		*	@return	void	*/	public function unset_var($variable){//@public		$variables = (array) $variables;				foreach( $variables as $variable ){			unset($this->vars[$variable]);		}	}		/**		*	Permet d'ajouter une itération d'un bloc et de ses variables.		*	@param	string		$block	Nom du bloc à ajouter.		*	@param	array|string		$ary_vars	Array de la forme (variable => valeur), qui contient les variable de cette itéaration du bloc, ou nom de la variable		*	@param	string		$value	Si $ary_vars dénomme qu'une seule variable, et n'est pas un array, alors celle-ci contiendra la valeur de la variable.		*	@access	public		*	@return	bool	*/	public function assign_block_vars($block, $ary_vars, $value = ''){//@public		if( !is_array($ary_vars) ){			$ary_vars = array($ary_vars => $value);		}				// -- On récupere tous les blocs, le nombre de blocs, et on met une référence sur la vriable globale des blocs.		$blocs = explode('.', $block);		$cur_bloc = array_pop($blocs);		$actuel = &$this->blocks;				// -- On parcourt chaque element de $actuel, et on change le groupe de fin. On peut ainsi accéder aux variables du bloc désiré :)		foreach( $blocs as &$bloc ){			if( !isset($actuel[$bloc]) ){				echo '<strong>Template->assign_block_vars ::</strong> Le bloc <em>' . $bloc . '</em> (' . $block . ') n\'est pas défini.';				return;			}						$actuel = &$actuel[$bloc];			$actuel = &$actuel[count($actuel) -  1];		}				if( !isset($actuel[$cur_bloc]) ){			$actuel[$cur_bloc] = array();			$nb_rows = 0;		}		else {			$nb_rows = count($actuel[$cur_bloc]);		}				// -- Variables spécifiques aux blocs (inutilisables autre part)		//	// -- CURRENT : Itération actuelle du bloc.		//	// -- FIRST : Est-ce la première itération (true/false) ?		//	// -- LAST : Est-ce la dernière itération (true/false) ?		//	// -- SIZE_OF : Taille totale du bloc (Nombre de répétitions totale)		$ary_vars['CURRENT'] = count($actuel[$cur_bloc]) + 1;		$ary_vars['LAST'] = true; // -- On est obligatoire à la dernière itération =P				if( $nb_rows ){ // -- Si le nombre d'itération est supérieur à 0, alors ce n'est pas la première itération, et celle d'avant n'etait pas la dernière (logique =P).			$ary_vars['FIRST'] = false;			$actuel[$cur_bloc][$nb_rows - 1]['LAST'] = false;						$ary_vars['SIZE_OF'] = &$actuel[$cur_bloc][0]['SIZE_OF'];		}		else { // -- Si il n'y aucun rangs, alors c'est la première itération.			$ary_vars['FIRST'] = true;		}				// -- Enfin, on ajoute le tableau de variables au bloc, et on incremente sa taille de 1.		$actuel[$cur_bloc][] = $ary_vars;		$actuel[$cur_bloc][0]['SIZE_OF'] = $nb_rows + 1;				return true;	}		/** 		*	Parse l'ensemble du TPL.		*	@param	mixed		$tpl		TPL concerné (null si non spécifié)		*	@access	public		*	@return	string	*/	public function parse($tpl = null){//@public		if( !$tpl ){			$tpl = $this->last_tpl;		}				if( !in_array($tpl, $this->files_list) ){			exit('Template->parse :: Le modèle <em>' . $tpl . '</em> n\'est pas répertorié.');		}				$this->tpl = $tpl;				// -- Si le cache existe, et qu'il est valide, on le charge... :)		if( is_cached($this->infos[$tpl]['cache'], $this->infos[$tpl]['last_modif'], $this->cache) ){			$data = cache_load($this->infos[$tpl]['cache'], $this->cache);		}		else { // -- Sinon, on doit le recompiler.			// -- On compile le TPL.			$data = $this->compile();						// -- Les commentaires de stats à insérer au début.			$comments = array(					'Talus\' TPLs',					'Original File : ' . $this->infos[$tpl]['file'],					'Cache Filename : ' . $this->infos[$tpl]['cache'],					'Date : ' . strftime('%d/%m/%Y @ %H:%M:%S', NOW),				);						// -- On stocke le tout dans le cache.			cache_put($this->infos[$tpl]['cache'], $data, $this->infos[$tpl]['last_modif'], $this->cache, $comments);		}				return @eval(' ?>' . $data . '<?php ');	}		/**		*	Parse le TPL, mais renvoi directement le résultat de celui-ci (entièrement parsé, et donc déj executé par PHP).		*	@param	string	$tpl		Nom du TPL à parser.		*	@param	integer	$lifetime	Temps de vie (en secondes) du cache de niveau 2.		*	@return	string		*	@access	public	*/	public function pparse($tpl = null, $lifetime = 0){		ob_start();		$this->parse($tpl);		$result = ob_get_clean();				return $result;	}		/**		*	Compile le TPL sélectionné		*	@access	private		*	@return	string	*/	private function compile(){		// -- On récupère le contenu du TPL...		$compile = file_get_contents($this->infos[$this->tpl]['file']);				// -- On transforme en "echo" les balises susceptibles d'etre du XML.		$compile = str_replace('<?' ,'<?php echo \'<?\'; ?>', $compile);					// -- On supprime les commentaires "multilignes" (de la forme /**/).		$compile = preg_replace('`/\*.*?\*/`s', '', $compile);				// -- Les inclusions (cache HTML en dev)		$compile = preg_replace('`<include tpl="(.+?\.html)"[\s]*/>`s', '<?php $this->include_tpl(\'$1\', false, 0); ?>', $compile);		$compile = preg_replace('`<include tpl="(.+?\.html)" once="(true|false)"[\s]*/>`s', '<?php $this->include_tpl(\'$1\', $2, 0); ?>', $compile); 				// -- Les fonctions		$compile = preg_replace_callback('`<function name="([a-zA-Z_][a-zA-Z0-9_]*)"((?: arg="[A-Z0-9_]+")*)>(.+?)</function>`s', array(&$this, 'parse_functions'), $compile);		$compile = preg_replace_callback('`<call name="([a-zA-Z_][a-zA-Z0-9_]*)"((?: arg="[^"]+")*) />`', array(&$this, 'call_functions'), $compile);				// -- Déclaration de variables...		$compile = preg_replace('`<var name="([A-Z0-9_]+)" value="([^"]+)"[\s]*/>`', '<?php $this->vars[\'$1\'] = \'$2\'; ?>', $compile);		$compile = preg_replace('`<var name="([a-z0-9_]+)\.([A-Z0-9_]+)" value="([^"]+)"[\s]*/>`', '<?php $__tpl_blocs[\'$1\'][\'$2\'] = \'$3\'; ?>', $compile);				// -- Les foreachs.		$compile = preg_replace('`<foreach ary="\{\$([a-zA-Z0-9_.]+)}">`i', '<?php foreach( {$$1} as $__tpl_foreach_key[\'$1\'] => $__tpl_foreach_value[\'$1\'] ){ ?>', $compile);		$compile = preg_replace('`<foreach ary="\{((?:VALUE,)?\$[a-zA-Z0-9_.]+)}" as="\{\$([A-Z0-9_]+)}">`i', '<?php foreach( {$1} as $__tpl_foreach_key[\'$2\'] => $__tpl_foreach_value[\'$2\'] ){ ?>', $compile);		$compile = preg_replace('`\{KEY,([A-Z0-9_]+)}`i', '<?php echo $__tpl_foreach_key[\'$1\']; ?>', $compile);		$compile = preg_replace('`\{VALUE,([A-Z0-9_]+)(\[[][{}$\w\'"_.-]+])*}`i', '<?php echo $__tpl_foreach_value[\'$1\']$2; ?>', $compile);		$compile = preg_replace('`\{KEY,\$([A-Z0-9_]+)}`i', '$__tpl_foreach_key[\'$1\']', $compile);		$compile = preg_replace('`\{VALUE,\$([A-Z0-9_]+)(\[[][{}$\w\'"_.-]+])*}`i', '$__tpl_foreach_value[\'$1\']$2', $compile);				// -- Constantes.		$compile = preg_replace('`\{__([A-Z0-9_]+)__}`i', '<?php echo $1; ?>', $compile);		$compile = preg_replace('`\{__\$([A-Z0-9_]+)__}`i', '$1', $compile);				// -- Les blocs et leurs variables (en incluant les arrays)		$compile = preg_replace_callback('`<block name="(([a-z0-9_]+)(\.([a-z0-9_]+))?)">`', array(&$this, 'compile_blocs'), $compile);		$compile = preg_replace('`\{([a-z0-9_]+)\.([A-Z0-9_]+)(\[.+?])*}`', '<?php echo $__tpl_blocs[\'$1\'][\'$2\']$3; ?>', $compile);		$compile = preg_replace('`\{\$([a-z0-9_]+)\.([A-Z0-9_]+)(\[.+?])*}`', '$__tpl_blocs[\'$1\'][\'$2\']$3', $compile);				// -- Variables "simples"		$compile = preg_replace('`\{([A-Z0-9_]+)(\[.+?])*}`', '<?php echo $this->vars[\'$1\']$2; ?>', $compile);		$compile = preg_replace('`\{\$([A-Z0-9_]+)(\[.+?])*}`', '$this->vars[\'$1\']$2', $compile);						// -- Les conditions (ancienne regex : [][a-zA-Z0-9.,_*+%/()$\^&!<>\s\\\'{}?|=-]		$compile = preg_replace('`<if cond(?:ition)?="([^"]+?)">`', '<?php if( $1 ){ ?>', $compile);		$compile = preg_replace('`<elseif cond(?:ition)?="([^"]+?)">`', '<?php } elseif( $1 ){ ?>', $compile);				// -- Les else		$compile = str_replace('<else />', '<?php } else { ?>', $compile);		$compile = str_replace('<blockelse />', '<?php } } else { if( true ) {?>', $compile);				// -- Les fin de blocs.		$compile = str_replace('</block>', '<?php } } ?>', $compile);				// -- Enfin, on transforme les fins de trucs spéciaux (conditions, foreach, etc)		$compile = str_replace(array(				'</if>', 				'</foreach>'			), '<?php } ?>', $compile);				// -- On enleve des echappements.		$compile = str_replace('{\\', '{', $compile);				// -- On teste le code avant de le nettoyer...		ob_start();		eval('?>' . $compile . '<?php');		ob_end_clean();				// -- La compilation a renvoyé une merde ? On affiche une erreur (les erreurs fatales stopperont directement le script) !. Enfin, si error_get_last() est définie et si le debug est activé :p		if( function_exists('error_get_last') && error_get_last() && (bool)$this->debug ){			echo $this->error($this->tpl);		}		// -- On nettoie le code		$compile = preg_replace('`\?>(?:[\s]*)<\?php`U', '$1', $compile);				// -- On retourne le code compilé.		return $compile;	}		/**		*	Inclue un TPL : Le parse si nécessaire (cf Ligne 422, 423)		*	@param	string	$file		Fichier à inclure.		*	@param	bool		$once 	N'inclure qu'une fois ?		*	@param	integer	$lifetime	Temps de vie en secondes du cache.		*	@see		compile()		*	@access	private		*	@return	string	*/	private function include_tpl($file, $once = false, $lifetime = 0){			if( !is_file($this->root . '/' . $file) ){			echo '<strong>Templates->include_tpl :: </strong> Le fichier ' . $file . ' n\'existe pas.';			return false;		}				$current_alias = $this->tpl;				// -- On vérifie que si on veut n'inclure qu'une seule fois un TPL, et qu'il est déjà présent, on ne l'inclue pas.		if( $once && in_array($file, $this->infos[$current_alias]['included']) ){			return false;		}		// -- Sinon, on l'ajoute dans la file des fichiers inclus.		else {			$this->infos[$current_alias]['included'][] = $file;		}			// -- Avant de faire quoique ce soit avec le cache... On copie les données du tpl actuel dans celles du tpl à inclure.		$this->infos[$file] = array(				'file' => $this->root . '/' . $file,				'size' => filesize($this->root . '/' . $file),				'last_modif' => filemtime($this->root . '/' . $file),				'included' => array(),				'cache' => str_replace('/', '_', $file),			);				if( !in_array($file, $this->files_list) ){			$this->files_list[] = $file;		}					// -- Enfin, on lance le parsage de ce tpl, et on récupère le résultat de celui-ci.		$data = $this->pparse($file, (int)$lifetime);				// -- On remet le bon alias à compiler (grâce à la copie faite plus haut...)		$this->tpl = $current_alias;				// -- Et on affiche le résultat :D		echo $data;	}		/**		*	Compile les blocs		*	@param	array		$match	array contenant les captures des blocs (cf Ligne 437)		*	@see		compile()		*	@access	private		*	@return	string	*/	private function compile_blocs($match){		if( isset($this->cache_blocks[$match[1]]['compilation']) ){ // -- Il existe un cache proche du bloc. Dans ce cas, on l'utilise.			$current = $this->cache_blocks[$match[1]]['compilation']['current'];			$bloc = $this->cache_blocks[$match[1]]['compilation']['bloc'];		}		else {			if( isset($match[3]) ){ // -- Il y a un bloc parent. Donc, on accede à la variable du bloc parent !				$current = $match[4];				$bloc = '$__tpl_blocs[\'' . $match[2] . '\']';			}			else {				$current = $match[2];				$bloc = '$this->blocks';			}						$this->cache_blocks[$match[1]]['compilation'] = array(					'bloc' => $bloc,					'current' => $current				);		}				return '<?php if( isset(' . $bloc . '[\'' . $current . '\']) ) { foreach( ' . $bloc . '[\'' . $current . '\'] as $key => $__tpl_blocs[\'' . $current . '\'] ){ ?>';	}		/**		*	Parse les déclarations de fonctions		*	@param	array		$matches	Capture (cf ligne 380)		*	@return	string		*	@access	private	*/	private function parse_functions($matches){		$php = '<?php function __tpl_' . $matches[1] . '(';				// -- Demande d'arguments...		if( isset($matches[2]) ){			$args = explode(' ', $matches[2]);			array_shift($args);			foreach( $args as $arg ){				$php .= '$' . substr($arg, 5, -1) . ', ';			}					}				$php = rtrim($php, ', ') . '){ ?>';				$script = preg_replace('`\{([A-Z0-9_]+)(\[.+?])*}`', '<?php echo $$1$2; ?>', $matches[3]);		$script = preg_replace('`\{\$([A-Z0-9_]+)(\[.+?])*}`', '$$1$2', $script);				return $php . $script . ' <?php } ?>';	}		/**		*	Parse les appels de fonctions		*	@param	array		$matches	Capture (cf ligne 381)		*	@return	string		*	@access	private	*/	private function call_functions($matches){		$php = '<?php if( function_exists(\'__tpl_' . $matches[1] . '\') ) { __tpl_' . $matches[1] . '(';				if( isset($matches[2]) ){			$args = explode(' ', $matches[2]);			array_shift($args);			foreach( $args as $arg ){				$arg = substr($arg, 5, -1);								if(  !($arg[0] == '{' && $arg[strlen($arg)-1] == '}') ){					$arg = '\'' . $arg . '\'';				}								$php .= $arg . ', ';			}						$php = rtrim($php, ', ');		}				return $php . '); } ?>';	}		/**		*	Gestionnaire des erreurs de compilation TPL.		*	@param	string	$tpl		TPL à utiliser. Si vide, c'est le dernier.		*	@return	string		*	@access	private	*/	private function error($tpl = ''){		// -- Qqs vars...		$error = array(				'level' => array(						E_ERROR =>'Erreur <strong>Fatale</strong>',						E_PARSE =>'Erreur <strong>Analytique</strong>',						E_WARNING => '<strong>Avertissement<strong>',						E_NOTICE => '<strong>Note</strong>'					),								'info' => error_get_last()			);		// -- Si le TPL est renseigné, on l'utilise, sinon on charge le derneir tpl connu ce jour.		if( !$tpl ){			$tpl = $this->last_tpl;		}				if( !in_array($tpl, $this->files_list) ){			exit('Template->error :: Le modèle <em>' . $tpl . '</em> n\'est pas répertorié.');		}				// -- On récupère les lignes du TPL.		$lines = array_map('htmlspecialchars', file($this->root . '/' . $tpl));				// -- Récupération deu total de lignes... et de la ligne fautive !		$total = count($lines);		$line = $error['info']['line'] - 1;				// -- On récupére les environs fautifs.		$begin = $line > (self::NB_LINES) ? ($line - self::NB_LINES) : 0;		$end = ($line + self::NB_LINES) < $total  ? ($line + self::NB_LINES) : ($total - 1);				$script = '';				for( $i = $begin; $i <= $end; $i++ ){			$script .= '<li style="margin:5px;"><code' . ($i  == $line ? ' style="background-color: #ffb;"' : '') . '>' . str_replace("\t", '&nbsp;&nbsp;&nbsp;&nbsp;', str_replace(' ', '&nbsp;', rtrim($lines[$i]))) . '</code></li>';		}				// -- On retourne le bloc de l'erreur.		return '<div style="clear:both; width:700px; overflow:auto; margin:auto; border:1px solid black; z-index:100; padding:5px;">					Template->error :: ' . $error['level'][$error['info']['type']] . ' (<b>' . $error['info']['message'] . '</b>), dans le TPL <b>' . $tpl . '</b>, à la ligne <b>' . ($line + 1). '</b> ! Voici les environs du tpl vers cette ligne (ligne <span style="background-color: #ffb;">surlignée</span> : ligne <span title="A prendre avec des pincettes :p">"fautive"</span>)...					<ol start="' . ($begin + 1) . '">' . $script . '</ol>				</div>';	}}?>