<?php
/*******************************************************************************
*
* [ ./includes/class/class_tpl.php (français) ]
*
* --------------------------------------------------------------------------
* Commencé le : 25 septembre 2005
* Copyright : Groupe Fire Soft Board
* Site Web :
http://www.fire-soft-board.com
* E-mail :
support@fire-soft-board.com
* --------------------------------------------------------------------------
* Ce programme est libre, vous pouvez le redistribuer et/ou le modifier selon les
* termes de la Licence Publique Générale GNU publiée par la Free Software Foundation
* (version 2). Reportez-vous à la Licence Publique Générale GNU pour plus de détails.
* Vous devez avoir reçu une copie de la Licence Publique Générale GNU en même temps
* que ce programme ; si ce n'est pas le cas, écrivez à la Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, États-Unis.
* --------------------------------------------------------------------------
*
* Dernière modification le 25 septembre 2005 par Genova
* Templates : 0
*
*******************************************************************************/
/*
** Classe Tpl, codée par Genova, inspirée de la classe originale PHPLib.
**
** Cette classe permet la manipultion simple et efficace de fichiers
** templates, avec gestion de blocks. Une gestion de structure de controle
** est aussi disponible (IF, ELSE, etc...) ainsi qu'une gestion automatique
** du cache des fichiers templates.
*/
define('PREG_LIMIT', 1);
define('ECHO_CODE', 1);
define('GET_CODE', 2);
class Tpl
{
var $root;
var $cache_dir;
var $use_cache;
var $data = array();
var $last_mask;
var $stack = array();
var $curent_stack = 0;
var $switch = array();
/*
** Constructeur de la classe.
** Assigne les chemins d'accès à la racine du dossier du
** thèmes ainsi que le chemin d'accès au dossier du cache
** si on utilise le cache.
** -----
** $root :: Chemin d'accès au thème.
** $cache_dir :: Chemin d'accès au dossier du cache.
*/
function Tpl($root, $cache_dir = '')
{
$this->root = $root;
if (!empty($cache_dir))
{
$this->use_cache = TRUE;
$this->cache_dir = $cache_dir;
}
else
{
$this->use_cache = FALSE;
}
$this->data['parent'] = array(
'file' => '',
'cache_file' => $this->cache_dir . '/' . $file . '.php',
'vars' => array(),
'blocks' => array()
);
return (TRUE);
}
/*
** Assigne un masque au fichier chargé. Ce masque servira a
** assigner des variables ou des blocks pour ce fichier, il sera
** donc possible de charger plusieurs fichiers templates de manière
** indépendante.
** -----
** $file :: Fichier a charger, chemin relatif à $this->root.
** $mask :: Masque du fichier.
*/
function set_filenames($array)
{
list($mask, $file) = each($array);
if (file_exists($this->root . $file))
{
$this->last_mask = $mask;
$this->curent_stack++;
$this->stack[$this->curent_stack] = $mask;
$this->data[$mask] = array(
'file' => $this->root . $file,
'cache_file' => $this->cache_dir . '/' . $file . '.php',
'vars' => array(),
'blocks' => array(),
);
}
else
{
die("Tpl->load_file() :: Le fichier $this->root$file n'existe pas.");
}
return (TRUE);
}
/*
** Assigne un paquet de variables globales au masque. Une variable
** globale a un masque est accessible partout dans le fichier template
** a condition de ne pas utiliser la notation de block (::) devant.
** Pour rendre ces variables utilisables pour n'importe quel masque
** utilisez le mot clé "parent".
** -----
** $mask :: Masque du fichier cible.
** $vars :: Variables à assigner, chaque clé du tableau sera récupérée dans le
** fichier template par les variables de type {KEY}.
*/
function assign_vars($vars = array(), $mask = 'parent')
{
$this->data[$mask]['vars'] = array_merge($this->data[$mask]['vars'], $vars);
return (TRUE);
}
/*
** Rajoute le tableau $vars à la fin du dernier block désigné par
** $block. Par exemple si on prend test::bidule on va
** effectuer $this->data[$mask]['block']['test'][count($this->data[$mask]['block']['test']) - 1]['bidule'][] = $vars.
** -----
** $mask :: Masque du fichier cible.
** $block :: Contient les blocks séparés entre eux par des ::.
** $vars :: Variables à assigner, chaque clé du tableau sera récupérée dans le
** fichier template par les variables de type {$block::KEY}.
** En prenant l'exemple ci dessus ce serait {test::bidule::KEY}
*/
function assign_block_vars($block, $vars, $mask = '')
{
if (empty($mask))
{
$mask = $this->stack[$this->curent_stack];
}
$get_blocks = $this->get_blocks($block, $mask);
eval('$this->data[\'' . $mask . '\'][\'blocks\']' . $get_blocks . '[] = $vars;');
return (TRUE);
}
/*
** Permet de créer un block, son existence sera testée dans le template
** avec IFEXIST.
** -----
** $block :: Block à créer.
** $mask :: Masque du fichier cible.
*/
function create_block($block)
{
$this->switch[$block] = TRUE;
}
/*
** Renvoie sous forme de cases de tableaux PHP les blocks, la meilleure
** facon de comprendre est par un exemple:
** echo get_blocks('bloc1::bloc2::block3', 'test');
** affichera ['bloc1'][count_bloc1 - 1]['bloc2'][count_bloc2 - 1]['bloc3']
** -----
** $block :: recoit les blocks, separés par des ::.
** $mask :: Masque du fichier cible.
*/
function get_blocks($block, $mask)
{
$blocks_str = '';
if (preg_match('/\./', $block))
{
$blocks_array = explode('.', $block);
$count_blocks = count($blocks_array);
for ($i = 0; $i < $count_blocks - 1; $i++)
{
$blocks_str .= '[\'' . $blocks_array[$i] . '\']';
eval('$count = count($this->data[\'' . $mask . '\'][\'blocks\']' . $blocks_str . ');');
$blocks_str .= '[' . ($count - 1) . ']';
}
$blocks_str .= '[\'' . $blocks_array[$count_blocks - 1] . '\']';
}
else
{
$blocks_str = '[\'' . $block . '\']';
}
return ($blocks_str);
}
/*
** Renvoie sous forme de cases de tableaux PHP les blocks courants, la meilleure
** facon de comprendre est par un exemple:
** echo get_current_blocks(array('bloc1', 'bloc2', 'bloc3'), 2);
** affichera ['bloc1'][$i_0]['bloc2'][$i_1]['bloc3']
** -----
** $block_array :: recoit les blocks sous forme de tableau.
*/
function get_current_blocks($blocks_array, $iteration)
{
$blocks_str = '';
$count_array = count($blocks_array);
for ($i = $count_array - 2; $i >= 0; $i--)
{
$blocks_str = '[\'' . $blocks_array[$i] . '\'][$i_' . ($iteration - ($count_array - $i) + 1) . ']' . $blocks_str;
}
$blocks_str .= '[\'' . $blocks_array[$count_array - 1] . '\']';
return ($blocks_str);
}
/*
** Même effet que la fonction $this->get_current_blocks mais
** prend en paramètre une chaine de block au lieu d'un tableau.
** -----
** $blocks :: Chaine de blocks (par exemple bloc1::bloc2::)
*/
function get_vars_blocks($blocks)
{
$blocks_array = explode('.', $blocks);
array_pop($blocks_array);
$count = count($blocks_array) - 1;
return ($this->get_current_blocks($blocks_array, $count) . '[$i_' . $count .']');
}
/*
** Parse le code template fourni a la fonction.
** -----
** $str :: Code à parser.
** $mask :: Masque du fichier cible.
** $type :: Type du parsage. Si type vaut ECHO_CODE on renvoie le
** code a evaluer avec un echo ce qui génèrera un affichage.
** Si $type vaut GET_CODE on renvoie le code à evaluer en
** assignement a une variable.
*/
function parse_code($str, $mask, $type)
{
$assign = ($type == ECHO_CODE) ? 'echo ' : '$_result_tpl .= ';
/*
** On remplace les <!-- INCLUDE_TPL file --> par le contenu du
** fichier file, qui doit être relatif à la racine du template
** ($this->root). Les inclusions de fichiers templates dans les
** autres fichiers sont aussi prises en compte.
*/
$total_inc_tpl = 0;
$f = 0;
do
{
preg_match_all('/<!-- INCLUDE_TPL ([a-zA-Z0-9\/._-]+) -->/si', $str, $inc_tpl);
$count_inc_tpl = count($inc_tpl[1]);
$total_inc_tpl += $count_inc_tpl;
for ($v = 0; $v < $count_inc_tpl; $v++)
{
if (file_exists($this->root . $inc_tpl[1][$v]))
{
$str = str_replace($inc_tpl[0][$v], implode("", file($this->root . $inc_tpl[1][$v])), $str);
}
else
{
die("Tpl->get_code() :: Le fichier " . $inc_tpl[1][$v] . " n'existe pas.");
}
}
unset($inc_tpl);
$f++;
}
while ($f < $total_inc_tpl + 1);
$str = str_replace('\'', '\\\'', str_replace('\\', '\\\\', $str));
/*
** On récupère le code PHP compris entre les balises
** <!-- BEGIN_PHP --> et <!-- END_PHP -->.
*/
preg_match_all('/<!-- BEGIN_PHP -->(.*?)<!-- END_PHP -->/si', $str, $php_code);
$count_php_code = count($php_code);
for ($v = 0; $v < $count_php_code; $v++)
{
$str = str_replace($php_code[0][$v], "';\n" . str_replace('\\\'', '\'', str_replace('\\\\', '\\', $php_code[1][$v])) . "\n$assign '", $str);
}
/*
** On parse la variable spéciale [bloc1::etc..blocn::ITERATION] qui
** contient la valeur de l'itération actuelle.
*/
$str = $this->parse_iteration_var($str);
/*
** On parse les variables "globales" du template, à savoir les {VARIABLE}.
** Elles sont remplacées par $this->data[$mask]['vars']['VARIABLE'].
*/
$str = preg_replace('/\{([A-Z0-9\-_]+)\}/s', '\' . ((isset($this->data[\'' . $mask . '\'][\'vars\'][\'\\1\'])) ? $this->data[\'' . $mask . '\'][\'vars\'][\'\\1\'] : $this->data[\'parent\'][\'vars\'][\'\\1\']) . \'', $str);
/*
** On parse les variables de blocks. Le nombre de blocks détermine la
** position dans le tableau $this->data[$mask]['blocks'].
*/
preg_match_all('/\{(([a-z0-9\-_]+?\.)+?)([A-Z0-9\-_]+?)\}/s', $str, $vars_b);
$count_vars_b = count($vars_b[1]);
for ($v = 0; $v < $count_vars_b; $v++)
{
$str = str_replace($vars_b[0][$v], '\' . $this->data[\'' . $mask . '\'][\'blocks\']' . $this->get_vars_blocks($vars_b[1][$v]) . '[\'' . $vars_b[3][$v] . '\'] . \'', $str);
}
/*
** On parse les blocks conditionels (IF, ELSEIF, ELSE, ENDIF, IFEXIST).
*/
$str = $this->parse_cond_blocks($str, $assign, $mask);
/*
** On parcourt ligne par ligne le fichier template en remplacant
** les débuts de blocks par des ouvertures de boucles et les fermetures
** de blocks par des fins de boucles.
*/
$line = explode("\n", $str);
if ($count_line = count($line))
{
$block_array = array();
$b = 0;
$code = '';
for ($i = 0; $i < $count_line; $i++)
{
if (preg_match('/<!-- BEGIN ([a-z0-9\-_]+) -->/si', $line[$i], $matches))
{
$block_array[$b] = $matches[1];
$code .= preg_replace('/<!-- BEGIN ([a-z0-9\-_]+) -->/si', "';\n" . '$count_i_' . $b . ' = count($this->data[\'' . $mask . '\'][\'blocks\']' . $this->get_current_blocks($block_array, $b) . ')' . ";\n" . 'for($i_' . $b . ' = 0; $i_' . $b . ' < $count_i_' . $b . '; $i_' . $b . '++)' . "\n" . '{' . "\n$assign '", $line[$i], PREG_LIMIT);
$b++;
}
else if (preg_match('/<!-- END ([a-z0-9\-_]+) -->/', $line[$i]))
{
array_pop($block_array);
$code .= preg_replace('/<!-- END ([a-z0-9\-_]+ )?-->/si', "';\n}\n$assign '", $line[$i], PREG_LIMIT);
$b--;
}
else
{
$code .= $line[$i] . "\n";
}
unset($matches);
}
}
else
{
die("Tpl->get_code() :: Le fichier $this->data[$mask]['file'] est vide.");
}
return ($assign . '\'' . $code . '\';');
}
/*
** Parse les structures conditionelles IF, ELSEIF, ELSE, ENDIF et IFEXIST.
** -----
** $str :: Code.
** $assign :: Assignement du code (affichage avec echo ou recuperation
** dans la variable $_result_tpl).
** $mask :: Masque du fichier cible.
*/
function parse_cond_blocks($str, $assign, $mask)
{
/*
** On parse les variables globales et de blocks des structures
** conditionelles. On les parse de la même façon que les autres variables
** à la différence prèt que leur structure est de la forme [VAR] ou bien
** [bloc1::bloc2::VAR].
*/
$str = preg_replace('/\[([A-Z0-9\-_]+)\]/s', '(isset($this->data[\'' . $mask . '\'][\'vars\'][\'\\1\']) ? $this->data[\'' . $mask . '\'][\'vars\'][\'\\1\'] : $this->data[\'parent\'][\'vars\'][\'\\1\'])', $str);
preg_match_all('/\[(([a-z0-9\-_]+?\.)+?)([A-Z0-9\-_]+?)\]/s', $str, $vars_b);
$count_vars_b = count($vars_b[1]);
for ($v = 0; $v < $count_vars_b; $v++)
{
$str = str_replace($vars_b[0][$v], '$this->data[\'' . $mask . '\'][\'blocks\']' . $this->get_vars_blocks($vars_b[1][$v]) . '[\'' . $vars_b[3][$v] . '\']', $str);
}
/*
** On parse les blocks conditionels IF et ELSEIF, ces blocks doivent
** automatiquement être suivi d'une condition.
*/
preg_match_all('/<!-- (ELSE)?IF (.+?) -->/si', $str, $if);
$count_if = count($if);
for ($v = 0; $v < $count_if; $v++)
{
$str = str_replace($if[0][$v], "';\n" . (($if[1][$v] == 'ELSE') ? "}\nelse " : '') . "if (" . $if[2][$v] . ")\n{\n$assign '", $str);
}
/*
** On parse le block IFEXIST. Ce block permet directement de tester si un block
** a été défini a l'aide de la fonction $this->create_block($mask, 'BLOCK');.
*/
$str = preg_replace('/<!-- IFEXIST ([a-zA-Z0-9\-_]+?) -->/si', "';\n" . 'if ($this->switch[\'\\1\'])' . "\n{\n$assign '", $str);
/*
** On parse très simplement le block ELSE et ENDIF avec une fin de
** boucle.
*/
$str = str_replace('<!-- ELSE -->', "';\n}\nelse\n{\n$assign '", $str);
$str = str_replace('<!-- ENDIF -->', "';\n}\n$assign '", $str);
return ($str);
}
/*
** Parse la variable spéciale [ITERATION] (ou {ITERATION}) par l'itération
** actuelle de la boucle. Cette variable doit donc toujours être précédée
** d'un block, par exemple [bloc1::ITERATION].
** -----
** $str :: Code à parser.
*/
function parse_iteration_var($str)
{
preg_match_all('/(([a-z0-9\-_]+?\.)+?)ITERATION/si', $str, $it);
$count = count($it[0]);
for ($i = 0; $i < $count; $i++)
{
$count_elmt = substr_count($it[1][$i], '.');
$str = str_replace('{' . $it[0][$i] . '}', '\' . $i_' . ($count_elmt - 1) . ' . \'', $str);
$str = str_replace('[' . $it[0][$i] . ']', '$i_' . ($count_elmt - 1), $str);
}
preg_match_all('/(([a-z0-9\-_]+?\.)+?)COUNT_ITERATION/si', $str, $it);
$count = count($it[0]);
for ($i = 0; $i < $count; $i++)
{
$count_elmt = substr_count($it[1][$i], '.');
$str = str_replace('{' . $it[0][$i] . '}', '\' . $count_i_' . ($count_elmt - 1) . ' . \'', $str);
$str = str_replace('[' . $it[0][$i] . ']', '$count_i_' . ($count_elmt - 1), $str);
}
return ($str);
}
/*
** Parse le fichier tpl associé au masque passé.
** -----
** $mask :: Masque du fichier cible.
** $type :: Si $type vaut ECHO_CODE le code parsé sera
** directement affiché, si cependant $type vaut
** GET_CODE, la fonction renvera le code créé.
** $interfer :: Empèche la mise en cache si TRUE.
*/
function pparse($mask, $type = ECHO_CODE, $interfer = FALSE)
{
/*
** Gestion automatique des masques
*/
$this->curent_stack--;
array_pop($this->stack);
/*
** Si on utilise le cache du template on inclu le fichier
** du cache à condition que la dernière date de modification
** du fichier .tpl corespondant et celle du fichier d'origine
** concordent.
*/
unset($_result_tpl);
if (!$interfer && $this->use_cache && file_exists($this->data[$mask]['cache_file']) && filemtime($this->data[$mask]['file']) == filemtime($this->data[$mask]['cache_file']))
{
include($this->data[$mask]['cache_file']);
}
else if (file_exists($this->data[$mask]['file']))
{
$str = implode("", file($this->data[$mask]['file']));
$code = $this->parse_code($str, $mask, GET_CODE);
if (!$interfer && $this->use_cache)
{
$this->write_cache_tpl($this->data[$mask]['cache_file'], "<?php\n$code\n?>", filemtime($this->data[$mask]['file']));
}
eval($code);
unset($code);
}
else
{
;die("Tpl->load_file() :: Le fichier $this->data[$mask]['file'] n'existe pas.");
}
if ($type == ECHO_CODE)
{
echo $_result_tpl;
}
return ($_result_tpl);
}
/*
** Ecrit des données dans un fichier et modifie sa derniere date de
** modification vers la même date de modification que le fichier
** template.
** -----
** $file :: Fichier ou les données seront écrites.
** $code :: Données à écrire.
** $time :: Dernière date de modification du fichier.
*/
function write_cache_tpl($file, $code, $time)
{
$fd = @fopen($file, 'w+');
@flock($fd, LOCK_EX);
$result = @fwrite($fd, $code);
@flock($fd, LOCK_UN);
@fclose($fd);
@touch($file, $time);
@umask(0);
@chmod($file, 0666);
return ($result);
}
}
?>