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", ' ', str_replace(' ', ' ', 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>'; }}?>