<?php
/** $Id: Template.php 24539 2010-07-29 14:17:39Z michael $
*
* SYNTAX:
*
* # fetch template content, cache page for 30min
* $data = fetch('/inc/some_tpl', ['a'=>$b, 'c'=>$d], '30 min');
*
* # display template, cached for 2hr for specific user
* show('/inc/other_tpl', ['a'=>$b], '2 hours', array('me'=>id()));
*
* # Template can be URL - NOT SUPPORTED ATM
* show('http://www.google.com', NULL, '2 hours');
* show('u:/internal_url', NULL, '2 hours');
*
* inside template:
* ...some html...
* <?include T('/inc/sub_tpl')?> # include template in the same lexical context
* <?show('inc/sub_tpl', ['a'=>$b])?> # include template in a separate context
* ...some other html...
* <? # special variable for postprocessing
* $TPL->title = 'My home page';
* $TPL->css = 'my.css';
* $TPL->js = 'my.js';
* $TPL->js_code = 'a = new Ajax.Request(...)';
* ?>
*
* DESCRIPTION:
*
* Simple template engine
*
**/
class Template {
static $Action; // Action instance (when action is defined as class method)
static $TPL; // Template Instance
static $OPTIONS; // options
/**
* @var
*/
protected $_tpl = [],
$_tpl_stack = [],
$_data_stack = [];
/**
* Initialize template engine.
*/
static public function init ($options=[]) { # void
self::$OPTIONS=$options;
self::$TPL=new Template;
}
//** Implementation
protected function __construct () {
}
//
function _push_args (&$args) { # void
$this->_data_stack[] =&$args;
}
//
function _pop_args () { # void
array_pop($this->_data_stack);
}
//
function _arg ($name) { #
$ds=count($this->_data_stack);
if (! $ds) return;
$d=$this->_data_stack[$ds-1];
if (isset( $d[$name] ) )
return $d[$name];
}
//
function _merge($data) { # void
foreach ($data as $k=>$v)
$this->_tpl[$k] = array_merge(@$this->_tpl[$k], $v);
}
//
function _step_in() { # void
$this->_tpl_stack[] = $this->_tpl;
$this->_tpl = [];
}
//
function _step_out() { # hash
$data = array_pop($this->_tpl_stack);
$this->_tpl = array_merge($data, $this->_tpl);
return $data;
}
/**
*
* @param string|hash $name Name of hash with values to set
* @return void
*/
public function set($name, $value = NULL) {
if (!is_array($name) )
$this->_tpl[$name] = $value;
else
foreach ($name as $k=>$v)
$this->_tpl[$k] = $v;
}
function value($name,$join=false) {
if (empty($this->_tpl[$name])) return;
$v=$this->_tpl[$name];
if ($join) return join($join,$v);
return reset($v);
}
// get specific keys as hash
function get_keys($keys) {
return hash_subset($this->_tpl, $keys);
}
// put keys into _tpl
function put_keys($keys) {
return $this->_tpl=$keys+$this->_tpl;
}
public function __get($name) { # list
return isset($this->_tpl[$name]) ? $this->_tpl[$name] : null;
}
// NULLs are not added
public function __set($name, $value) { # void
if ($value===NULL) return;
$this->_tpl[$name][] = $value;
}
// are errors present in action
static function errors() { # "" | (array)errors
if (! Template::$Action) return "";
if (! isset( Template::$Action->_["errors"])) return "";
if (self::$TPL->no_errors ) return "";
return (array) Template::$Action->_["errors"];
}
/**
* set_type_page - setting page type
*
* @param string $type
* @return bool
*/
public function set_type_page(string $type) {
try {
$this->_tpl['type_page'][] = $type;
return true;
} catch (Exception $e) {
//todo maybe logged...
}
return false;
}
/**
* get_type_page - getting page type
*
* @return string
*/
public function get_type_page() {
return !empty($this->_tpl['type_page']) ? end($this->_tpl['type_page']) : 'other';
}
}
/**
* Display template.
*
* @param string $TEMPLATE Template name, "http://url", "u:/internal_url"
* @param hash $ARGS Template args
* @param int|string $CACHE Cachning period. Number of seconds or time for tm() func.
* @param hash $CACHE_ARGS Caching args (optional)
* @return void
*/
function show($TEMPLATE, $_=NULL, $CACHE=0, $CACHE_ARGS=[]) {
// kill the fucking extract() !!!!!
$__ = [];
$CACHE = is_numeric($CACHE) ? $CACHE : tm($CACHE);
if ($CACHE ) {
$__["key"] = "TPL:$TEMPLATE-".md5(serialize($_).serialize($CACHE_ARGS));
if ($__["res"] = Cache::get($__["key"]) ) {
Template::$TPL->_merge($__["res"][0]);
echo $__["res"][1];
return;
}
Template::$TPL->_step_in();
ob_start();
}
if (! $TEMPLATE) \Log::alert("No TEMPLATE");
$__["state"]=Profiler::disable(); // avoid duplicate reporting
$__["tpl"] = T($TEMPLATE);
Profiler::enable($__["state"]);
if (!$__["tpl"]) \Log::alert("No template");
if (0 == strncmp('http://', $__["tpl"], 7) ) { // for URL, add params as REQUEST_QUERY
$__["tpl"] = url($__["tpl"], $_);
\Log::alert("unsecure call: tpl=".$__["tpl"]);
// Should be just curl there !!
} else { // for ordinary templates, extract variables
if (is_array($_) )
extract($_, EXTR_SKIP);
$TPL = Template::$TPL;
}
if (isset($TPL) )
$TPL->_push_args($_);
try {
if ($__["tpl"] == C("web-root").'/templates/_layout.phtml') {
Profiler::in("TPL-layout", NVL(x2s(gaTrackPage()),"")); // in_off
} else {
Profiler::in("TPL::show",$__["tpl"]);
}
if (! file_exists($__["tpl"]) ) {
if (Debug::is_admin()) {
Debug::alert("Missing template: ".$__["tpl"]);
}
//Log::notice("Template file missed: " . $__["tpl"], "", -1);
do404();
//Controller::dispatch("/page404", $_GET + $_POST, "_layout");
die;
} else {
}
Profiler::out();
} catch(Exception $e) {
$CACHE = false;
// ob_end_flush();
// Template::$TPL->_step_out();
// throw $e;
// Do not rethrow exception! It is useless in recursive templates model.
\Error\ErrorHandler::sentry()->captureException($e);
exception_handler($e);
}
if (isset($TPL) )
$TPL->_pop_args();
if ($CACHE ) {
$content = ob_get_clean();
Template::$TPL->_step_out();
Cache::put($__["key"], [$TPL, $content], $CACHE); // Seems like error, we better cache content only
echo $content;
}
}
// The same as show, but instead of printing template, returns it.
function fetch($TEMPLATE, $_=NULL, $CACHE=0, $CACHE_ARGS=[]) {
if (! $TEMPLATE) \Log::alert("no template");
ob_start();
show($TEMPLATE,$_,$CACHE,$CACHE_ARGS);
$r=ob_get_clean();
return $r;
}
// Returns full path or URL to template
// can be used as: include T('tpl');
function T($tpl, $root="templates") { # string
global $ROOT;
if (substr($tpl,-1)=='/') $tpl.="index";
if ($tpl[0]=='/') $tpl=substr($tpl,1);
if (0 == strncmp('http://', $tpl, 7) )
return $tpl;
if ($tpl[0] == 'u' && $tpl[1] == ':' )
return "http://".Template::$OPTIONS['INTERNAL_HOST']."/t/".substr($tpl,2);
$path = "$tpl.phtml";
Profiler::info("TPL", $tpl);
if (isset($_GET["TPL"]) && Debug::is_admin())
echo "<span class=alert><a href='".Debug::$class_browser."?cfn=".C("web-root")."/templates/$tpl.phtml'>$tpl</a></span>";
if (isset($_COOKIE['fork']) && file_exists("$ROOT/fork/".$_COOKIE['fork'].'/web'."/$root/$tpl.phtml"))
return "$ROOT/fork/".$_COOKIE['fork'].'/web'."/$root/$tpl.phtml";
$fn=C('web-root')."/$root/$tpl.phtml";
if (CC("template-proxy")) {
if (! file_exists($fn))
$fn=C('web-root-proxy')."/$root/$tpl.phtml";
}
// mobile template
if (CD("MOBILE_LAYOUT")) {
$fn_mobile = str_replace('.phtml', '.m.phtml', $fn);
if (file_exists($fn_mobile))
$fn = $fn_mobile;
}
return $fn;
}
/**
* Return data in JS format (json). Data can be string, array, hash etc.
* WARNING! Function returns COMPLETE JS VALUE:
* <script>
* var a = <?=js($a)?>;
* </script>
* Therefore, use single quotes in HTML!
* <a onclick='alert(<?=js($msg)?>);'>
*
* @param mixed $data Data.
*
* @return string
*/
function js($data) {
$data = json_encode($data);
$data = str_replace("'", "\'", $data);
return $data;
}