» /rd/lib.framework/Template/Template.php

<?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 (
== 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 {

             include $__["tpl"];

         }
         
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 (
== strncmp('http://'$tpl7) )
        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;
}