<?php
/**
* TemplateThis - A PHP Templating Engine
*
* TemplateThis 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; either version 2 of
* the License, or (at your option) any later version.
*
* TemplateThis 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 recieved a copy of the GNU General Public License
* along with TemplateThis; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @package TemplateThis
* @author Nicholas Sack <hide@address.com>
* @link http://www.sourceforge.net/projects/ttc/ TemplateThis Project Page
* @copyright Copyright (C) 2004, Nicholas Sack
* @license http://www.gnu.org/licenses/gpl.txt GNU General Public License
* @version 0.2.8
*/
/* $Id: TemplateThis.class.php,v 1.5 2004/07/29 12:52:35 solias Exp $ */
/**
* This sets the base path to the TemplateThis library directory
* unless it has been already defined by the user. If this is not
* defined, PHP will try and use its include_path.
*/
if (!defined('TT_DIR')) define('TT_DIR', dirname(__FILE__) . DIRECTORY_SEPARATOR);
/**
* This define simple shortens PHPs built in global DIRECTORY_SEPARATOR.
*/
if (!defined('DIR_SEP')) define('DIR_SEP', DIRECTORY_SEPARATOR);
/**
* @package TemplateThis
*/
class TemplateThis
{
/**
* Name of the directory where templates are stored.
*
* @var string $dir_template
*/
var $dir_template = "templates";
/**
* Name of the directory where compiled templates are stored.
*
* @var string $dir_compiled
*/
var $dir_compiled = "cache";
/**
* Name of the directory where plugins are kept.
*
* @var string $dir_plugin
*/
var $dir_extension = "extensions";
/**
* Holds all of the template data.
*
* @var mixed $file_template
*/
var $file_template;
/**
* Name of the compiled template filename.
*
* @see output()
* @var string $file_compiled
*/
var $file_compiled;
/**
* Name of that template that is being parsed.
*
* @see compile()
* @var string $parser_template
*/
var $parser_template;
/**
* Holds the last error messaged used.
*
* @see error()
* @var string $last_error
*/
var $last_error;
/**
* Filename of the template being parsed.
*
* @var string $template_path
*/
var $template_path;
/**
* Array that holds all template data.
*
* @see output()
* @var array $template_data
*/
var $template_data = array();
/**
* Should we reuse code from the compiled template?
*
* @see output()
* @var boolean $reuse_code
*/
var $reuse_code = true;
/**
* Parser variable that holds tagged extensions.
*
* @see compile()
* @var array $extension_tagged
*/
var $extension_tagged = array();
/**
* Class constructor.
*
* @param string $template_file name of the template file
*/
function TemplateThis($template_file)
{
if (!file_exists($template_file)) {
exit($this->error("TemplateThis", "specified template does not exist"));
} else {
$this->template_path = $template_file;
}
}
/**
* Assigns specified value to tag name.
*
* @param mixed $tag_name
* @param mixed $tag_value
*/
function assign($tag_name, $tag_value)
{
if (is_array($tag_name)) {
foreach ($tag_name as $s => $v) {
$this->template_data[$s] = $v;
}
} else {
$this->template_data[$tag_name] = $tag_value;
}
}
/**
* Appends a value to an existing tag.
*
* @param mixed $tag_name
* @param mixed $tag_value
*/
function append($tag_name, $tag_value)
{
if (is_array($tag_value)) {
$this->template_data[$tag_name][] = $tag_value;
} elseif (!is_array($this->template_data[$tag_name])) {
$this->template_data[$tag_name] .= $tag_value;
}
}
/**
* Prepends a value to an existing tag.
*
* @param mixed $tag_name
* @param mixed $tag_value
*/
function prepend($tag_name, $tag_value)
{
if ((!is_array($tag_value)) && (!is_array($this->template_data[$tag_name]))) {
$this->template_data[$tag_name] = $tag_value . $this->template_data[$tag_name];
} else {
exit($this->error("prepend", "arrays are not supported by this function"));
}
}
/**
* Parses the template and returns it in a variable array.
*
* @param mixed $_data optional
* @return mixed
*/
function result($_data = "")
{
ob_start();
$this->output($_data);
$result = ob_get_contents();
ob_end_clean;
return $result;
}
/**
* Outputs the template to the web browser.
*
* @param string $_data optional
*/
function output($_data = "")
{
global $_data;
if (!is_array($_data)) {
if (strlen($_data)) {
$this->file_template = $_data;
}
$_data = &$this->template_data;
}
$_object = $_data;
$_stack_count = 0;
$_stack[$_stack_count++] = $_object;
$this->file_compiled = $this->dir_compiled . DIR_SEP . preg_replace("/[:\/.\\\\]/", "_", $this->template_path) . ".php";
$compile_template = true;
if ($this->reuse_code) {
if (is_file($this->file_compiled)) {
if ($this->modified($this->file_compiled) > $this->modified($this->template_path)) {
$compile_template = false;
}
}
}
if ($compile_template) {
if (!$this->compile($this->template_path)) {
exit($this->error("output", "could not compile template"));
}
}
include($this->file_compiled);
unset($GLOBALS['_data']);
}
/**
* Returns the modified time of the file.
*
* @param string $file
* @return string
*/
function modified($file)
{
if (is_file($file)) {
return filemtime($file);
} else {
exit($this->error("modified", "specified file is not a file"));
}
}
/**
* Main compiler function of the engine.
*
* Depending on whether the {@link $dir_compiled} directory is
* writable, this will either return the page for a boolean (if
* it cannot write the file).
*
* @param string $template_file optional
* @return mixed
*/
function compile($template_file = "")
{
if (empty($template_file)) {
$template_file = $this->template_path;
}
if ($file = fopen($template_file, "r")) {
$this->parser_template = fread($file, filesize($template_file));
fclose($file);
} else {
exit($this->error("compile", "could not open template file"));
}
$page = preg_replace("/<!-- ENDIF.+?-->/", "<?php\n }\n?>", $this->parser_template);
$page = preg_replace("/<!-- END[ a-zA-Z0-9_.]* -->/", "<?php\n }\n \$_object = \$_stack[--\$_stack_count];\n }\n?>", $page);
$page = str_replace("<!-- ELSE -->", "<?php\n } else {\n?>", $page);
if (preg_match_all("/<!-- BEGIN ([a-zA-Z0-9_.]+) -->/", $page, $var)) {
foreach ($var[1] as $tag) {
list($parent, $block) = $this->variable_name($tag);
$code = "<?php\n"
." if (!empty(\$$parent" . "['$block'])) {\n"
." if (!is_array(\$$parent" . "['$block'])) {\n"
." \$$parent" . "['$block'] = array(array('block' => \$$parent" . "['$block']));\n"
." }\n"
." \$_tmp_keys = array_keys(\$$parent" . "['$block']);\n"
." if (\$_tmp_keys[0] != \"0\") {\n"
." \$$parent" . "['$block'] = array(0 => \$$parent" . "['$block']);\n"
." }\n"
." \$_stack[\$_stack_count++] = \$_object;\n"
." foreach (\$$parent" . "['$block'] as \$r_count => \$$block) {\n"
." \$_object = &\$$block;\n?>";
$page = str_replace("<!-- BEGIN $tag -->", $code, $page);
}
}
if (preg_match_all("/<!-- (ELSE)?IF ([a-zA-Z0-9_.]+)([!=<>]+)\"([^\"]*)\" -->/", $page, $var)) {
foreach ($var[2] as $count => $tag) {
list($parent, $block) = $this->variable_name($tag);
$cmp = $var[3][$count];
$val = $var[4][$count];
$else = ($var[1][$count] == "ELSE") ? "} else" : "";
if ($cmp == "=") {
$cmp = "==";
}
$code = "<?php\n $else" . "if (\$$parent" . "['$block'] $cmp \"$val\") {\n?>";
$page = str_replace($var[0][$count], $code, $page);
}
}
if (preg_match_all("/<!-- (ELSE)?IF ([a-zA-Z0-9_.]+) -->/", $page, $var)) {
foreach ($var[2] as $count => $tag) {
$else = ($var[1][$count] == "ELSE") ? "} else" : "";
list($parent, $block) = $this->variable_name($tag);
$code = "<?php\n $else" . "if (!empty(\$$parent" . "['$block'])) {\n?>";
$page = str_replace($var[0][$count], $code, $page);
}
}
if (preg_match_all("/{([a-zA-Z0-9_. >]+)}/", $page, $var)) {
foreach ($var[1] as $fulltag) {
list($command, $tag) = $this->command_name($fulltag);
list($block, $scalar) = $this->variable_name($tag);
$code = "<?php $command \$$block" . "['$scalar']; ?>";
$page = str_replace("{" . $fulltag . "}", $code, $page);
}
}
if (preg_match_all("/<\"([a-zA-Z0-9_.]+)\">/", $page, $var)) {
foreach ($var[1] as $tag) {
list($block, $scalar) = $this->variable_name($tag);
$code = "<?php echo gettext('$scalar'); ?>";
$page = str_replace("<\"" . $tag . "\">", $code, $page);
}
}
if (preg_match_all("/{([a-zA-Z0-9_]+):([^}]*)}/", $page, $var)) {
foreach ($var[2] as $count => $tag) {
list($command, $tag) = $this->command_name($tag);
$extension = $var[1][$count];
if (!$this->extension_tagged[$extension]) {
$header .= "<?php\n include_once(\"" . TT_DIR . $this->dir_extension . DIR_SEP . "extension_$extension.php\");\n?>\n";
$this->extension_tagged[$extension] = true;
}
if (!strlen($tag)) {
$code = "<?php $command extension_$extension(); ?>";
} elseif (substr($tag, 0, 1) == "\"") {
$code = "<?php $command extension_$extension($tag); ?>";
} elseif (strpos($tag, ",")) {
list($tag, $addparam) = explode(",", $tag, 2);
list($block, $scalar) = $this->variable_name($tag);
if (preg_match("/^([a-zA-Z_]+)/", $addparam, $match)) {
$next_tag = $match[1];
list($next_block, $next_scalar) = $this->variable_name($next_tag);
$addparam = substr($addparam, strlen($next_tag));
$code = "<?php $command extension_$extension(\$$block" . "['$scalar'], \$$next_block" . "['$next_scalar']" . "$addparam); ?>";
} else {
$code = "<?php $command extension_$extension(\$$block" . "['$scalar'], $addparam); ?>";
}
} else {
list($block, $scalar) = $this->variable_name($tag);
$code = "<?php $command extension_$extension(\$$block" . "['$scalar']); ?>";
}
$page = str_replace($var[0][$count], $code, $page);
}
}
if (isset($header)) {
$page = "$header\n$page";
}
if (strlen($this->file_compiled)) {
if ($file = fopen($this->file_compiled, "w")) {
fwrite($file, $page);
fclose($file);
return true;
} else {
$this->error("compile", "could not write compiled file");
return false;
}
} else {
return $page;
}
}
/**
* Names a specified tag using a stack and returns it.
*
* @param mixed $tag
* @return array
*/
function variable_name($tag)
{
$p_level = 0;
while (substr($tag, 0, 7) == "parent.") {
$tag = substr($tag, 7);
$p_level++;
}
if (substr($tag, 0, 4) == "top.") {
$object = "_stack[0]";
$tag = substr($tag, 4);
} elseif ($p_level) {
$object = "_stack[$stack_count-" . $p_level . "]";
} else {
$object = "_object";
}
while (is_int(strpos($tag, "."))) {
list($parent, $tag) = explode(".", $tag, 2);
if (is_numeric($parent)) {
$object .= "[" . $parent . "]";
} else {
$object .= "['" . $parent . "']";
}
}
return array($object, $tag);
}
/**
* Names a command from a tag and returns it.
*
* @param mixed $tag
*/
function command_name($tag)
{
if (preg_match("/^(.+) > ([a-zA-Z0-9_.]+)$/", $tag, $tag_var)) {
$tag = $tag_var[1];
list($new_block, $new_scalar) = $this->variable_name($tag_var[2]);
$command = "\$$new_block" . "['$new_scalar'] = ";
} else {
$command = "echo";
}
return array($command, $tag);
}
/**
* Generates an error and sends it to the browser.
*
* @todo a better error reporting system
* @param string $function name of the function that has the error
* @param string $error error description
*/
function error($function, $error)
{
$this->last_error = "<b>" . $function . ":</b> " . $error;
print $this->last_error;
}
}