<?php
/*=====================================================================*\
|| ###################################################################
|| # Blue Static ISSO Framework
|| # Copyright (c)2005-2009 Blue Static
|| #
|| # This program is free software; you can redistribute it and/or modify
|| # it under the terms of the GNU General Public License as published by
|| # the Free Software Foundation; version 2 of the License.
|| #
|| # This program is distributed in the hope that it will be useful, but
|| # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|| # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|| # more details.
|| #
|| # You should have received a copy of the GNU General Public License along
|| # with this program; if not, write to the Free Software Foundation, Inc.,
|| # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|| ###################################################################
\*=====================================================================*/
/**
* Database-Driven Template System (template.php)
*
* @package ISSO
*/
require_once(ISSO . '/Functions.php');
/**
* File-Based Template System
*
* This framework merely replaces the template loading functions with
* file-system based ones. It has an optional caching system in which
* template data will remain stored in the database as long as the filesystem
* file is modified. To do this, pass a table name to setDatabaseCache() and make sure
* there's a DB module that has access to a table with this schema:
*
* CREATE TABLE template (filename VARCHAR (250) NOT NULL, template TEXT NOT NULL, timestamp INT NOT NULL);
*
* @author Blue Static
* @copyright Copyright (c)2005 - 2009, Blue Static
* @package ISSO
*
*/
class BSTemplate
{
/**
* The name of a function that is called before template parsing of phrases and conditionals occurs
* @var string
*/
public static $preParseHook = ':undefined:';
/**
* The database table name for the template cache
* @var string
*/
public static $dbCacheTable = null;
/**
* The name of the function phrases are fetched with
* @var string
*/
public static $langcall = 'gettext';
/**
* The template path pattern; for instance, this could be: ./templates/%s.tpl
* @var string
*/
public static $templatePath = '%s';
/**
* Array of pre-compiled templates that are stored for optimization
* @var array
*/
protected static $cache = array();
/**
* The name of the function phrases are sprintf() parsed with
* @var string
*/
public static $langconst = 'sprintf';
/**
* Template variables to populate
* @var array
*/
public $vars = array();
/**
* Global variables
* @var array
*/
public static $globalVars = array();
/**
* The file name of the template
* @var string
*/
protected $filename;
/**
* Template contents
* @var string
*/
protected $template;
/**
* Takes an array of template names, loads them, and then stores a
* parsed version for optimum speed.
*
* @param array List of template names to be cached
*/
public static function cache($namearray)
{
if (!self::$dbCacheTable)
{
return; // there's no point in pre-caching file templates
}
$cache = BSApp::$db->query("SELECT * FROM " . self::$dbCacheTable . " WHERE filename IN ('" . implode("', '", $namearray) . "')");
while ($tpl = $cache->fetchArray())
{
self::$cache[$tpl['filename']] = $tpl;
}
}
/**
* Fluent interface-compatible constructor
*/
public static function fetch()
{
$obj = new ReflectionClass(__CLASS__);
$args = func_get_args();
return $obj->newInstanceArgs($args);
}
/**
* Constructor
*
* @param string File name
*/
public function __construct($path)
{
$this->file = $path;
// checks to see if the template has been cached
if (isset(self::$cache[$this->file]))
{
if (!self::$dbCacheTable || filemtime(sprintf(self::$templatePath, $this->file)) <= self::$cache[$this->file]['timestamp'])
{
$this->template = self::$cache[$this->file]['template'];
return;
}
}
// it hasn't been cached
$path = sprintf(self::$templatePath, $this->file);
if (!is_file($path) || !is_readable($path))
{
throw new Exception("Could not load the template $path");
}
$this->template = $this->_parseTemplate(file_get_contents($path));
self::$cache[$this->file]['template'] = $this->template;
// store the template in the database
if (self::$dbCacheTable)
{
BSApp::$db->query("REPLACE INTO " . self::$dbCacheTable . " SET template = '" . BSApp::$db->escapeString($this->template) . "', timestamp = " . TIMENOW . ", filename = '" . $this->file . "'");
self::$cache[$this->file]['time'] = TIMENOW;
}
}
/**
* Returns the template data
*
* @return string Final template data
*/
public function getTemplate()
{
return $this->template;
}
/**
* This function globalizes/extracts the assigned variables and then
* returns the output buffer
*
* @param string Unevaluated template
*
* @return fluent interface
*/
public function evaluate()
{
extract($this->vars);
extract(self::$globalVars);
ob_start();
$this->template = str_replace(array('$this->', 'self::'), 'null', $this->template); // don't want internal access coming from a template
$this->template = '?>' . $this->template;
$test = eval($this->template);
$output = ob_get_clean();
if ($output === false)
{
throw new Exception('A parse error was encountered while evaluating the template');
}
$this->template = $output;
return $this;
}
/**
* Output a template fully compiled to the browser
*/
public function flush()
{
ob_start();
if (empty($this->template))
{
throw new Exception('There is no output to print');
}
$template = $this->template;
$debugBlock = '';
if (BSApp::get_debug() && strpos($template, '</body>') !== false)
{
$debugBlock .= "\n<div align=\"center\">Executed in " . round(BSFunctions::fetch_microtime_diff('0 ' . $_SERVER['REQUEST_TIME']), 10) . ' seconds</div>';
$debugBlock .= "\n<br /><div align=\"center\">" . BSApp::get_debug_list() . "</div>";
if (BSApp::$db)
{
$queries = BSApp::$db->getHistory();
$debugBlock .= "<br />\n" . '<table cellpadding="4" cellspacing="1" border="0" align="center" width="30%" style="background-color: rgb(60, 60, 60); color: white">' . "\n\t" . '<tr><td><strong>Query Debug</strong></td></tr>';
foreach ($queries as $query)
{
$debugBlock .= "\n\t<tr style=\"background-color: rgb(230, 230, 230); color: black\">";
$debugBlock .= "\n\t\t<td>";
$debugBlock .= "\n\t\t\t$query[query]\n\n\t\t\t<div style=\"font-size: 9px;\">($query[time])</div>\n<!--\n$query[trace]\n-->\n\t\t</td>\n\t</tr>";
}
$debugBlock .= "\n</table>\n\n\n";
}
$template = str_replace('</body>', $debugBlock . '</body>', $template);
}
print($template);
}
/**
* A wrapper for all the parsing functions and compiling functins
*
* @param string Unparsed template data
*
* @return string Parsed template data
*/
protected function _parseTemplate($template)
{
if (function_exists(self::$preParseHook))
{
$template = call_user_func(self::$preParseHook, $template, $this);
}
$template = $this->_parseTokens($template);
return $template;
}
/**
* Parses tokens <% %>
*
* @param string Template data
*
* @return string Parsed template data
*/
protected function _parseTokens($template)
{
$stack = array();
$tokens = array();
for ($i = 0; $i < strlen($template); $i++)
{
// opening tag
if ($template[$i] == '<' && $template[$i + 1] == '%')
{
array_push($stack, $i);
}
// closing tag
else if ($template[$i] == '%' && $template[$i + 1] == '>')
{
// there's no stack, so it's a bad template
if (sizeof($stack) == 0)
{
throw new Exception('Malformed template data: unexpected closing substitution tag');
}
// we're good and nested
else if (sizeof($stack) == 1)
{
$open = array_pop($stack);
$echo = ($template[$open + 2] == '-' ? 'echo ' : '');
$replace = '<?php ' . $echo . BSFunctions::substring($template, $open + ($echo ? 3 : 2), $i) . ' ?>';
$template = substr_replace($template, $replace, $open, ($i + 2) - $open);
}
// just pop it off
else
{
array_pop($stack);
} // end else
} // end if
} // end for
return $template;
}
}
?>