<?php
/**
* fxl_template
*
* a very tiny but flexible library for template processing
*
* Have a look into the examples directory
* for some useful examples incl. template files.
*
* Goals of this library:
*
* - plain text/html templates without any control mechanisms
* like loops, php code or sql queries
* - easy to learn template markup (only 2 elements)
* - flexibility: you can more or less assign everything to everywhere
* - speed
* - easy handling: It's just this tiny file you have to include
* to use fxl template
*
* @version 2.1.1
* @version 2.1.1mod1 2010-10-11 by <hide@address.com>
* @package fxl_template
*/
class fxl_template {
protected $tpl = array('block' => array(), 'place' => array(), 'template' => '');
protected $param = array('clipleft' => '{', 'clipright' => '}');
protected $halt_on_error = true;
protected $template_file = '';
/**
* fxl_template constructor
*
* example:
* <code>
* $tpl = new fxl_template('template.tpl');
* </code>
*
* @param string $content file name
* @param array $options not in use
* @return object fxl_template
*/
public function __construct ($content = false, $options = false)
{
if (!file_exists($content) || !is_readable($content)) die('Cannot open template file '.$content);
$this->template_file = $content;
$this->set_template($content, 'file');
if ($this->tpl['template']) $this->init();
}
/**
* Allows to extent the $param property by custom keys.
*
* @param string|array $key Array of key => value pairs or scalar param name
*[@param mixed $value If $key is a string, this holds the value, otherwise leave it NULL]
*/
public function set_custom_param($key, $value = null)
{
if (!is_null($value) && is_scalar($key)) {
$key = array($key => $value);
}
foreach ($key as $k => $v) {
if ($k == 'clipleft' || $k == 'clipright') continue;
$this->param[$k] = $v;
}
}
/**
* Retrieve the value of a custom param
*
* @param string $name Name of the param to retrieve
* @return mixed NULL, if param could not be found, the value of it otherwise
*/
public function get_custom_param($name)
{
return (isset($this->param[$name])) ? $this->param[$name] : null;
}
/**
* finish rendering process
*
* @since 1.0.0
* @return string rendered template
*/
public function get_content()
{
$tmp = preg_replace('/'.$this->param['clipleft'].'([a-z0-9\-\_]+)'.$this->param['clipright'].'/i', '', $this->get());
return preg_replace('/'.$this->param['clipleft'].'\\\/i', $this->param['clipleft'], $tmp);
}
/**
* displays the output
*
* same as:
* <code>
* echo $tpl->get_content();
* </code>
* @since 1.0.0
* @return void
*/
public function display()
{
echo $this->get_content();
}
/**
* new assignment
*
* complete example:
* <code>
* $tpl = new fxl_template('address_form.tpl');
* $tpl_address = $tpl->get_block('address');
* $tpl_address->assign('name', 'Peter');
* $tpl_address->assign(array('zip' => '10179', 'town' => 'Berlin'));
* $tpl->assign('address', $tpl_address);
* $tpl->display();
* </code>
*
* usage examples:
* <code>
* $tpl->assign(<string var>, <string val>);
* $tpl->assign(<string var>, <object fxl_template>);
* $tpl->assign(<string blockname>, <object fxl_template>);
* $tpl->assign(<array [var=val,var=val]>);
* $tpl->assign(<string blockname>); *
* $tpl->assign(<string blockname>, val);
* </code>
*
* * since version 2.1.1
*
* @since 1.0.0
*
* @param string|array $var string: name of the block / place holder OR array with key/value pairs
* @param string|object $val string OR object (another FXL Template object)
* @return void
*/
public function assign($var, $val = null)
{
if (is_array($var)) {
foreach ($var as $k => $v) {
$this->tpl['place'][$k][] = $v;
}
} elseif (is_object($val)) {
$this->tpl['place'][$var][] = clone $val;
} elseif (strlen($var)) {
if (is_null($val)) {
$this->assign_block($var);
} else {
$this->tpl['place'][$var][] = $val;
}
}
}
/**
* assigns a whole block in place
*
* <code>
* $tpl->assign_block('blockname');
*
* same as:
* $tpl_block = $tpl->get_block('blockname');
* $tpl->assign('blockname', $tpl_block);
*
* same as:
* $tpl->assign('blockname');
* </code>
*
* @param string $blockname block name
* @return void
*/
public function assign_block($blockname)
{
$this->assign($blockname, $this->get_block($blockname));
}
/**
* Wrapper for getting a block, assigining it one or more placeholders and then
* assigning the now filled block to its parent template again.
* @param string $blk Name of the block
* @param mixed $var See $this->assign()
* @param mixed $val See $this->assign()
* @since 2.0.5
*/
public function fill_block($blk, $var, $val = '')
{
$b = $this->get_block($blk);
$b->assign($var, $val);
$this->assign($blk, $b);
}
/**
* fetching a block for assignments
*
* @since 1.0.0
* @param string $blockname block name
* @return fxl_template
*/
public function get_block($blockname)
{
if (isset($this->tpl['block'][$blockname]) && is_object($this->tpl['block'][$blockname])) {
return clone $this->tpl['block'][$blockname];
} elseif ($this->halt_on_error) {
die('Block: '.$blockname.' not found in '.$this->template_file);
}
return false;
}
/**
* checks a block exists or not
*
* @since 2.0.0
* @param string $blockname block name
* @return bool
*/
public function block_exists($blockname)
{
if (isset($this->tpl['block'][$blockname]) && is_object($this->tpl['block'][$blockname])) {
return true;
}
return false;
}
/**
* refresh block for new assignments
*
* example:
* <code>
* $names = array('peter', 'nicole');
* $tpl_name = $tpl->get_block('name_block');
* foreach ($names as $name) {
* $tpl_name->assign('name', $name);
* $tpl->assign('name_block', $tpl_name);
* $tpl_name->clear();
* }
* </code>
*
* @since 1.0.0
* @return void
*/
public function clear()
{
$this->tpl['place'] = array();
}
/**
* pre-rendered content - not all replacements done
*
* @ignore
* @return string pre-rendered template
*/
public function get()
{
if (count($this->tpl['place'])) {
foreach ($this->tpl['place'] as $k => $v) {
$replace = '';
for ($i = 0, $j = count($this->tpl['place'][$k]); $i < $j; $i++) {
$replace .= is_object($this->tpl['place'][$k][$i]) ? $this->tpl['place'][$k][$i]->get() : $this->tpl['place'][$k][$i];
}
$this->tpl['template'] = str_replace($this->param['clipleft'].$k.$this->param['clipright'], $replace, $this->tpl['template']);
}
}
return $this->tpl['template'];
}
# INTERNAL METHODS #
/**
* sets the template
*
* @since 2.0.0
* @ignore
* @param string $data enum: file name, content string
* @param string $type enum: file, string
* @return bool
*/
public function set_template($data, $type = 'file')
{
if ($type == 'file') {
if (($this->tpl['template'] = file_get_contents($data))) return true;
} elseif ($type == 'string') {
$this->tpl['template'] = $data;
return true;
}
return false;
}
/**
* template initialization
*
* @ignore
* @since 2.0.0
* @return void
*/
public function init()
{
return $this->parse($this->tpl['template']);
}
/**
* parser
*
* @ignore
* @param string $tplstring
* @return void
*/
protected function parse($tplstring = '')
{
$this->tpl['template'] = $tplstring;
$m = $this->_match_block();
for ($x = 0, $y = count($m[0]); $x < $y; $x++) {
$this->tpl['template'] = $this->parse_block($m[1][$x], $this->tpl['template']);
$this->tpl['block'][$m[1][$x]] = clone $this;
$this->tpl['block'][$m[1][$x]]->tpl['place'] = array();
$this->tpl['block'][$m[1][$x]]->tpl['block'] = array();
$this->tpl['block'][$m[1][$x]]->parse($m[2][$x]);
}
}
/**
* block replacer
*
* @ignore
* @param string $blockname
* @param string $tplstring
* @return string
*/
protected function parse_block($blockname = '', $template = '')
{
$blockname = preg_quote($blockname);
return preg_replace
('/<!--\sSTART\s('.$blockname.')\s-->.*<!--\sEND\s('.$blockname.')\s-->/ms'
,$this->param['clipleft'].$blockname.$this->param['clipright']
,$template
);
}
/**
* block finder
*
* @ignore
* @return array matches
*/
protected function _match_block()
{
preg_match_all("/<!--\sSTART\s([a-z0-9_]+)\s-->(.*)<!--\sEND\s(\\1)\s-->/mis", $this->tpl['template'], $m);
return $m;
}
}
/**
* FXL Template - Memcache Extension (alpha, 0.5)
*
* for use with FXL Template v2.1+
*
* php memcache documentation:
* http://www.php.net/manual/en/book.memcache.php
*
* @package fxl_template
*/
class fxl_memcached_template extends fxl_template {
protected $check = 'always';
protected $validate = 'date';
protected $memcache_prefix = 'fxlt';
protected $memcache_flag = null;
protected $memcache_expire = null;
public $cached = false;
protected $cache_md5;
protected $cache_date;
/**
* Constructor
*
* possible options:
* - check
* value: 'never' never check for a new version of the template (fastest)
* value: 'always' always check for a newer version of the template (default, recommended)
* - validate
* value: 'date' (fastest)
* value: 'md5' (default, recommended)
*
* @param string $filename template filename
* @param Memcache $memcache Memcache object
* @param string $check (never|always) optional
* @param string $validate (date|md5) optional
* @param array $mc_option (0=>key, 1=>flag, 2=>expire) optional
*/
function __construct($filename, $memcache, $check = null, $validate = null, $mc_option = null) {
if (in_array($check, array('always', 'never'))) $this->check = $check;
if (in_array($validate, array('date', 'md5'))) $this->validate = $validate;
if (isset($mc_option[1])) $this->memcache_flag = $mc_option[1];
if (isset($mc_option[2])) $this->memcache_expire = $mc_option[2];
if (!file_exists($filename) || !is_readable($filename)) return false;
$key = (isset($mc_option[0]) && !is_null($mc_option[0])) ? $mc_option[0] : $this->memcache_prefix.realpath($filename);
if ($memcache->get($key)) {
$data = $memcache->get($key);
if ($this->check == 'always') {
if ($this->validate == 'md5') {
$content = file_get_contents($filename);
if (md5($content) == $data->cache_md5) {
$this->tpl = $data->tpl;
}
} elseif ($data->cache_date == filemtime($filename)) {
$this->tpl = $data->tpl;
}
} else {
$this->tpl = $data->tpl;
}
}
if (!$this->tpl['template']) {
$content = file_get_contents($filename);
$this->set_template($content, 'string');
$this->init();
$this->cache_md5 = md5($content);
$this->cache_date = filemtime($filename);
$memcache->set($key, $this, $this->memcache_flag, $this->memcache_expire);
} else {
$this->cached = true;
}
}
}
/**
* FXL TEMPLATE CACHE PLUGIN: ser v1.0.0
* base class for the caching implementation
*/
class fxl_ser_template extends fxl_template
{
protected $mode = '';
protected $check = true;
protected $force = false;
protected $cache_suffix = '.cache';
protected $cache_prefix = '';
protected $halt_on_error = false;
protected $cache_file = '';
protected $template_file = '';
protected $version = 1.0;
protected $sub = false;
public function __construct($template_file = '')
{
if ($template_file && !$this->set_template_file($template_file)) return false;
}
public function set_check($val)
{
$this->check = (bool) $val;
return true;
}
public function set_mode($val)
{
if (in_array($val, array('md5'))) {
$this->mode = $val;
return true;
}
return false;
}
public function set_template($data, $type = 'file')
{
if ($type == 'file') {
if (!file_exists($data) || !is_readable($data)) die('Cannot open template file '.$data);
$this->tpl['template'] = file_get_contents($data);
$this->template_file = $data;
return true;
}
if ($type == 'string') return (bool) $this->tpl['template'] = $data;
return false;
}
public function get_cache_file_name($template_file = '')
{
if ($template_file && ($this->cache_prefix || $this->cache_suffix)) {
return $this->cache_prefix.$this->template_file.$this->cache_suffix;
} elseif ($this->cache_file) {
return $this->cache_file;
} elseif ($this->template_file && ($this->cache_prefix || $this->cache_suffix)) {
return $this->cache_prefix.$this->template_file.$this->cache_suffix;
}
return false;
}
public function set_cache_file($filename)
{
return (bool) $this->cache_file = $filename;
}
public function init()
{
if (!$this->tpl['template']) return false;
if (!$cfile = $this->get_cache_file_name()) return false;
if ($this->check && file_exists($cfile) && is_readable($cfile)) {
$fp = fopen($cfile, 'r');
$header_line = fgets($fp, 256);
$header = explode(':', $header_line, 2);
if (!isset($header[1]) || (chop($header[1]) != md5($this->tpl['template']))) $this->force = true;
if (!$this->force) $ser = fread($fp, filesize($cfile) - strlen($header_line));
fclose($fp);
} elseif ($this->check && (!file_exists($cfile))) {
$this->force = true;
}
if ($this->force) {
$head = 'md5:'.md5($this->tpl['template'])."\n";
$this->parse($this->tpl['template']);
$cached = serialize($this);
file_put_contents($cfile, $head.$cached);
} else {
$cached = unserialize($ser);
$this->tpl = $cached->tpl;
}
}
protected function md5_check($val1, $val2)
{
return ($val1 == md5($val2));
}
protected function __clone()
{
$this->cache_file = ''; // cannot be the same
$this->template_file = ''; // cannot be the same
$this->sub = true;
}
public function version() { return $this->version; }
}
/**
* FXL CACHED TEMPLATE WRAPPER
*
* working cache plugin example for fxl_template based on md5 and php serialize
* Feel free to customize it for your needs ;-) Just use:
*
* $tpl = new fxl_cached_template($tpl_file, $cache_file);
*
* instead of:
*
* $tpl = new fxl_template($tpl_file);
*
* btw, you could also use:
* $tpl = new fxl_template($tpl_file, array('cache_file' => 'tpl.cache', 'cache_mode' => 'ser'));
*/
class fxl_cached_template extends fxl_ser_template
{
/**
* fxl_cached_template constructor
*
* make sure you have write permissions for your cache dir / cache file
*
* @param string $tpl template file
* @param string $ctpl cached template file
* @return object fxl_cached_template
*/
function __construct($tpl, $ctpl = false)
{
if (!$ctpl) $ctpl = $tpl.'.cache';
$this->set_template($tpl);
$this->set_cache_file($ctpl);
$this->set_mode('ser');
$this->init();
}
}
/**
* phlyMail specifics for using the FXL Template Engine
* It modifies the way, the caching is handled and automagically assigns
* translation values according to their prefix as HTML safe or Javascript safe
* values.
*/
class phm_template extends fxl_template
{
var $msg = null;
/**
* Constructor
*
* @param string $filename Full path to the template file
* @param string $cachepath Base path where to cache the file
* @param array $msg Localization strings, which get assigned to the template
*/
public function __construct($filename, $cachepath, $msg = null)
{
foreach ($msg as $k => $v) { // Flatten array messages into scalars
if (is_array($v)) {
foreach ($v as $k2 => $v2) $msg[$k.'_'.$k2] = $v2;
}
}
$this->msg = $msg;
$cachefile = $cachepath.basename($filename);
parent::__construct($filename, array('cache_file' => $cachefile, 'cache_mode' => 'ser'));
}
/**
* Variation of the original method: Replaces localization strings on the spot
*
* @param mixed $data Either a file name reference or a string
* @param string $type Denotes the type of $data
* @return bool
*/
public function set_template($data, $type = 'file')
{
if ($type == 'file') {
$tplstr = file_get_contents($data);
if (!is_null($this->msg)) {
$tplstr = preg_replace_callback("/%([hjt])([a-z0-9\_]+)%/", array(&$this, 'localize'), $tplstr);
}
if (($this->tpl['template'] = $tplstr)) return true;
} elseif ($type == 'string') {
$this->tpl['template'] = $data;
return true;
}
return false;
}
public function localize($array)
{
if (isset($this->msg[$array[2]])) {
if ($array[1] == 'h') return htmlentities($this->msg[$array[2]], null, 'utf-8');
if ($array[1] == 'j') return addslashes($this->msg[$array[2]]);
}
return false;
}
}
?>