<?php
/*
Vemplator 0.6.1 - Making MVC (Model/Vemplator/Controller) a reality
Copyright (C) 2005-2008 Alan Szlosek
See LICENSE for license information.
20071217
- thought about flags to turn off caching ... but this needs more thought
20070819
- tweaks to variable transformation patterns
*/
class vemplator {
private $baseDirectory; // optional base path for template and compile directories
private $compileDirectory; // directory to cache template files in. defaults to /tmp/
private $templateDirectories; // array of directories to look for templates. can be relative to the baseDirectory
private $data; // a stdClass object to hold the data passed to the template
private $outputModifiers;
/*
public $doCache; // flag for whether to cache
public $doStripCode;
*/
/**
* Notable actions:
* Sets the baseDirectory to the web server's document root
* Sets default compile path to /tmp/HTTPHOST
*/
function __construct() {
$this->baseDirectory = $this->appendSeparator($_SERVER['DOCUMENT_ROOT']); // default to document root
$this->compileDirectory = '/tmp/' . $_SERVER['HTTP_HOST'] . '/';
$this->data = new stdClass;
$this->outputModifiers = array();
//$this->doCache = false;
//$this->doStripCode = true;
}
/*
* Makes sure folders have a trailing slash
*/
private function appendSeparator($path) {
$path = trim($path);
if(substr($path, strlen($path)-1, 1) != DIRECTORY_SEPARATOR)
$path .= DIRECTORY_SEPARATOR;
return $path;
}
public function compilePath($path) {
// prepend with base path if specified path doesn't start with a slash
$temp = $this->appendSeparator($path);
$this->compileDirectory = ($temp{0} != '/' ? $this->baseDirectory . $temp : $temp);
}
/**
* Assign a template variable.
* This can be a single key and value pair, or an associate array of key=>value pairs
*/
public function assign($key, $value = '') {
if(is_array($key)) {
foreach($key as $n=>$v)
$this->data->$n = $v;
} elseif(is_object($key)) {
foreach(get_object_vars($key) as $n=>$v)
$this->data->$n = $v;
} else {
$this->data->$key = $value;
}
}
/**
* Alias for assign()
*/
public function set($key, $value = '') {
$this->assign($key, $value);
}
public function append($key, $value = '') {
if(!property_exists($this->data, $key)) {
$this->data->$key = '';
}
$this->data->$key .= $value;
}
public function push($key, $value = null) {
if(!property_exists($this->data, $key)) {
$this->data->$key = array();
}
$data = $this->data->$key;
$data[] = $value;
$this->data->$key = $data;
}
/**
* Resets all previously-set template variables
*/
public function clear() {
$this->data = new stdClass;
}
/**
* In charge of fetching and rendering the specified template
*/
public function output($template) {
if(!is_array($template))
$template = explode('|',$template);
// go through and prepend template and compile directories with baseDirectory if needed
$out = '';
$foundTemplate = false;
foreach($template as $t) {
foreach(explode(PATH_SEPARATOR, get_include_path()) as $path) {
//foreach($this->templateDirectories as $templateDirectory) {
//$path = ($templateDirectory{0} != '/' ? $this->baseDirectory . $templateDirectory : $templateDirectory);
$path = $this->appendSeparator($path);
if(file_exists($path . $t)) {
$out .= $this->bufferedOutput($path, $t);
$foundTemplate = true;
break; // found the template, so don't check any more directories
}
}
}
if(!$foundTemplate)
die('Template (' . $t . ') not found in ' . $path);
return $out;
}
/**
* Fetches the specified template, finding it in the specified path ... but only after trying to compile it first
*/
private function bufferedOutput($path, $template) {
$this->compile($path, $template);
ob_start();
include($this->compileDirectory . $template . '.php');
$out = ob_get_clean();
return $out;
}
/**
* Compiles the template to PHP code and saves to file ... but only if the template has been updated since the last caching
* Uses Regular Expressions to identify template syntax
* Passes each match on to the callback for conversion to PHP
*/
private function compile($path, $template) {
// moved from constructor
if(!file_exists($this->compileDirectory))
mkdir($this->compileDirectory);
$templateFile = $path . $template;
$compiledFile = $this->compileDirectory . $template . '.php';
// don't spend time compiling if nothing has changed
if(file_exists($compiledFile) && filemtime($compiledFile) >= filemtime($templateFile))
return;
$lines = file($templateFile);
$newLines = array();
$matches = null;
foreach($lines as $line) {
$num = preg_match_all('/\{([^{}]+)\}/', $line, &$matches);
if($num > 0) {
for($i = 0; $i < $num; $i++) {
$match = $matches[0][$i];
$new = $this->transformSyntax($matches[1][$i]);
$line = str_replace($match, $new, $line);
}
}
$newLines[] = $line;
}
$f = fopen($compiledFile, 'w');
fwrite($f, implode('',$newLines));
fclose($f);
}
/**
* This is where the generation of PHP code takes place
*/
private function transformSyntax($input) {
$from = array(
//'/(^|\[|,|\(| |\+)([a-zA-Z_][a-zA-Z0-9_]*)($|\W|\.)/',
//'/(^|\[|,|\(| |\+)([a-zA-Z_][a-zA-Z0-9_]*)($|\W|\.)/',
'/(^|\[|,|\(|\+| )([a-zA-Z_][a-zA-Z0-9_]*)($|\.|\)|\[|\]|\+)/',
'/(^|\[|,|\(|\+| )([a-zA-Z_][a-zA-Z0-9_]*)($|\.|\)|\[|\]|\+)/', // again to catch those bypassed by overlapping start/end characters
'/\./',
);
$to = array(
'$1$this->data->$2$3',
'$1$this->data->$2$3',
'->'
);
$parts = explode(':', $input);
$string = '<?php ';
switch($parts[0]) { // check for a template statement
case 'if':
case 'switch':
$string .= $parts[0] . '(' . preg_replace($from, $to, $parts[1]) . ') { ' . ($parts[0] == 'switch' ? 'default: ' : '');
break;
case 'foreach':
$pieces = explode(',', $parts[1]);
$string .= 'foreach(' . preg_replace($from, $to, $pieces[0]) . ' as ';
$string .= preg_replace($from, $to, $pieces[1]);
if(sizeof($pieces) == 3) // prepares the $value portion of foreach($var as $key=>$value)
$string .= '=>' . preg_replace($from, $to, $pieces[2]);
$string .= ') { ';
break;
case 'end':
case 'endswitch':
$string .= '}';
break;
case 'else':
$string .= '} else {';
break;
case 'case':
$string .= 'break; case ' . preg_replace($from, $to, $parts[1]) . ':';
break;
case 'include':
$string .= 'echo $this->output("' . $parts[1] . '");';
break;
default:
$string .= 'echo ' . preg_replace($from, $to, $parts[0]) . ';';
break;
}
$string .= ' ?>';
return $string;
}
}
?>