123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- <?php
- namespace Juju\Data;
- require_once(realpath(dirname(__DIR__).'/events.trait.php'));
- require_once('earray.class.php');
- use Juju\{Events, Data\EArray};
- use \Exception;
- class Template {
- use Events;
- private static $templates = [];
- public static $cachedir;
- public static $basedir = __DIR__;
- private static $tidy;
- private static $tidyConfig = [
- 'indent'=>true,
- 'tab-size'=>4,
- 'wrap'=>0,
- 'wrap-asp'=>false,
- 'wrap-attributes'=>false,
- 'wrap-jste'=>false,
- 'wrap-php'=>false,
- 'wrap-script-literals'=>false,
- 'wrap-sections'=>false,
- 'char-encoding'=>'utf8',
- 'newline'=>'LF',
- 'tidy-mark'=>true,
- 'merge-divs'=>false,
- 'merge-spans'=>false,
- 'logical-emphasis'=>false,
- 'literal-attributes'=>true
- ];
- private static $regex = [
- 'getmatch'=>'/\{!([^#\/?_][^}\n]*?)\}/i',
- 'getparentmatch'=>'/\{!\.\.\/([^#\/?_][^}\n]*?)\}/i',
- 'getrawmatch'=>'/\{!@([^#\/?_][^}\n]*?)\}/i',
- 'getrawparentmatch'=>'/\{!@\.\.\/([^#\/?_][^}\n]*?)\}/i',
- 'match'=>'/\{([^#\/?_][^}\n]*?)\}/i',
- 'parentmatch'=>'/\{\.\.\/([^#\/?_][^}\n]*?)\}/i',
- 'rawmatch'=>'/\{@([^#\/?_][^}\n]*?)\}/i',
- 'rawparentmatch'=>'/\{@\.\.\/([^#\/?_][^}\n]*?)\}/i',
- 'each'=>'/\{#each ([^}]*)\}([\S\s]*)\{\/each \1\}/i',
- 'exist'=>'/\{#exist ([^}]*)\}([\S\s]*)\{\/exist \1\}/iU',
- 'existelse'=>'/\{#exist ([^}]*)\}([\S\s]*)\{#else \1\}([\S\s]*)\{\/exist \1\}/iU',
- 'ignore'=>'/\{#ignore\}([\S\s]*)\{\/ignore\}/i',
- 'ignored'=>'/\{#ignored (\d+?)\}/i',
- 'gettext'=>"/{_([^,}]+)(?:, ?([^},]+))*\}/i",
- 'gettext_string'=>'/^([\'"])(.+)\1$/i',
- 'echo'=>'/\{=([^}]+)\}/i',
- 'eval'=>'/\{\?([\W\w\S\s]+)\?\}/i',
- 'include'=>'/{#include ([^}]+)}/i',
- 'define'=>'/\{#define ([^}]*)\}([\S\s]*)\{\/define \1\}/i',
- 'widget'=>'/{#widget ([^ }]+)(?: ((?:[^=}]+=[^}&]+)*))?}/i'
- ];
- protected static $parsers;
- private $template;
- private $name;
- private $path;
- public function __construct(string $name, string $template, bool $is_file = false){
- if(is_null(static::$parsers)){
- static::$parsers = [
- 'ignore'=>function(&$output, &$ignored){
- $output = preg_replace_callback(static::$regex['ignore'], function($matches) use(&$ignored){
- $ignored[] = $matches[1];
- return '{#ignored '.(count($ignored) - 1).'}';
- }, $output);
- },
- 'include'=>function(&$output, &$ignored = null){
- while(preg_match(static::$regex['include'], $output)){
- $output = preg_replace_callback(static::$regex['include'], function($matches) use(&$ignored){
- $path = static::$basedir.'/'.$matches[1];
- if(file_exists($path)){
- $output = file_get_contents($path);
- if(!is_null($ignored)){
- static::$parsers['ignore']($output, $ignored);
- }
- return $output;
- }
- return '';
- }, $output);
- }
- },
- 'define'=>function(&$output, &$widgets){
- while(preg_match(static::$regex['define'], $output)){
- $output = preg_replace_callback(static::$regex['define'], function($matches) use(&$widgets){
- $name = $matches[1];
- if(isset($widgets[$name])){
- throw new \Exception("Widget {$name} is already defined");
- }
- $widgets[$name] = $matches[2];
- return '';
- }, $output);
- }
- },
- 'widget'=>function(&$output, $widgets){
- while(preg_match(static::$regex['widget'], $output)){
- $output = preg_replace_callback(static::$regex['widget'], function($matches) use(&$widgets){
- $name = $matches[1];
- if(!isset($widgets[$name])){
- throw new \Exception("Widget {$name} is not defined");
- }
- if(count($matches) > 2){
- $args = [];
- foreach(explode('&', $matches[2]) as $chunk){
- $param = explode('=', $chunk);
- if($param){
- $args[urldecode($param[0])] = urldecode($param[1]);
- }
- }
- $widget = "<?php \$widget_parent[] = \$data; \$data = array_merge(json_decode(json_encode(\$data), true), json_decode(base64_decode(".var_export(base64_encode(json_encode($args)), true)."), true)); ?>";
- $widget .= static::compile($widgets[$name]);
- $widget .= "<?php \$data = array_pop(\$widget_parent); ?>";
- }else{
- $widget = $widgets[$name];
- }
- return $widget;
- }, $output);
- }
- },
- 'each'=>function(&$output){
- $output = preg_replace_callback(static::$regex['each'], function($matches){
- $output = "<?php if(isset(\$data[".var_export($matches[1], true)."])): ";
- $output .= "foreach(\$data[".var_export($matches[1], true)."] as \$item): ";
- $output .= "\$parent[] = \$data; \$data = \$item; ?>";
- $output .= static::compile($matches[2]);
- $output .= "<?php \$data = array_pop(\$parent);";
- $output .= "endforeach;endif; ?>";
- return $output;
- }, $output);
- },
- 'existelse'=>function(&$output){
- $output = preg_replace_callback(static::$regex['existelse'], function($matches){
- $output = "<?php if(isset(\$data[".var_export($matches[1], true)."])): ?>";
- $output .= static::compile($matches[2]);
- $output .= "<?php else: ?>";
- $output .= static::compile($matches[3]);
- $output .= "<?php endif; ?>";
- return $output;
- }, $output);
- },
- 'exist'=>function(&$output){
- $output = preg_replace_callback(static::$regex['exist'], function($matches){
- $output = "<?php if(isset(\$data[".var_export($matches[1], true)."])): ?>";
- $output .= static::compile($matches[2]);
- $output .= "<?php endif; ?>";
- return $output;
- }, $output);
- },
- 'gettext'=>function(&$output){
- $output = preg_replace_callback(static::$regex['gettext'], function($matches){
- if(count($matches) > 2){
- $output = "<?=htmlentities(sprintf(_({$matches[1]})";
- foreach(array_slice($matches, 2) as $item){
- if(preg_match(static::$regex['gettext_string'], $item)){
- $output .= ", $item";
- }else{
- $output .= ", (\$data['{$item}'] ?? '')";
- }
- }
- }else{
- $output = "<?=htmlentities(_({$matches[1]}";
- }
- return "{$output})); ?>";
- }, $output);
- },
- 'echo'=>function(&$output){
- $output = preg_replace_callback(static::$regex['echo'], function($matches){
- return "<?= {$matches[1]}; ?>";
- }, $output);
- },
- 'eval'=>function(&$output){
- $output = preg_replace_callback(static::$regex['eval'], function($matches){
- return "<?php {$matches[1]}; ?>";
- }, $output);
- },
- 'getrawmatch'=>function(&$output){
- $output = preg_replace_callback(static::$regex['getrawmatch'], function($matches){
- return "<?=(\$data[\$data[".var_export($matches[1], true)."] ?? ''] ?? '');?>";
- }, $output);
- },
- 'getrawparentmatch'=>function(&$output){
- $output = preg_replace_callback(static::$regex['getrawparentmatch'], function($matches){
- return "<?=(\$parent[count(\$parent)-1][\$data[".var_export($matches[1], true)."] ?? ''] ?? '');?>";
- }, $output);
- },
- 'getmatch'=>function(&$output){
- $output = preg_replace_callback(static::$regex['getmatch'], function($matches){
- return "<?=htmlentities(\$data[\$data[".var_export($matches[1], true)."] ?? ''] ?? '');?>";
- }, $output);
- },
- 'getparentmatch'=>function(&$output){
- $output = preg_replace_callback(static::$regex['getparentmatch'], function($matches){
- return "<?=htmlentities(\$parent[count(\$parent)-1][\$data[".var_export($matches[1], true)."] ?? ''] ?? '');?>";
- }, $output);
- },
- 'rawmatch'=>function(&$output){
- $output = preg_replace_callback(static::$regex['rawmatch'], function($matches){
- return "<?=(\$data[".var_export($matches[1], true)."] ?? '');?>";
- }, $output);
- },
- 'rawparentmatch'=>function(&$output){
- $output = preg_replace_callback(static::$regex['rawparentmatch'], function($matches){
- return "<?=(\$parent[count(\$parent)-1][".var_export($matches[1], true)."] ?? '');?>";
- }, $output);
- },
- 'match'=>function(&$output){
- $output = preg_replace_callback(static::$regex['match'], function($matches){
- return "<?=htmlentities(\$data[".var_export($matches[1], true)."] ?? '');?>";
- }, $output);
- },
- 'parentmatch'=>function(&$output){
- $output = preg_replace_callback(static::$regex['parentmatch'], function($matches){
- return "<?=htmlentities(\$parent[count(\$parent)-1][".var_export($matches[1], true)."] ?? '');?>";
- }, $output);
- },
- 'ignored'=>function(&$output, $ignored){
- $output = preg_replace_callback(static::$regex['ignored'], function($matches) use($ignored){
- return htmlentities($ignored[(int)$matches[1]] ?? '');
- }, $output);
- }
- ];
- }
- if(isset(static::$templates[$name])){
- throw new Exception("Template {$name} already exists");
- }
- if($is_file){
- $path = realpath($template);
- if(!file_exists($path)){
- throw new Exception("Template file {$template} doesn't exist");
- }
- $template = file_get_contents($path);
- }
- static::$parsers['include']($template);
- $widgets = [];
- static::$parsers['define']($template, $widgets);
- static::$parsers['widget']($template, $widgets);
- $this->template = $template;
- $this->name = $name;
- $this->path = static::$cachedir."/{$this->name}.".md5($this->template).'.php';
- static::$templates[$name] = $this;
- }
- public function __get(string $name){
- switch($name){
- case 'name':case 'template':case 'path':
- return $this->$name;
- break;
- default:
- throw new \Exception("Property {$name} doesn't exist");
- }
- }
- public function to_file(){
- file_put_contents($this->path, static::compile($this->template));
- }
- public function run(array $data) : string{
- $data = EArray::from($data);
- if($this->fire('before', $data) === false){
- throw new Exception("Render on template {$this->name} cancelled. Before.");
- }
- if(!file_exists($this->path)){
- $this->to_file();
- }
- try{
- $output = static::execute($this->path, $data);
- }catch(Exception $e){
- $this->to_file();
- $output = static::execute($this->path, $data);
- }
- if(class_exists('tidy')){
- if(is_null(static::$tidy)){
- static::$tidy = new \tidy();
- }
- $tidy = static::$tidy;
- $tidy->parseString($output, static::$tidyConfig);
- if(!$tidy->cleanRepair()){
- throw new \Exception($tidy->errorBuffer);
- }
- $output = "{$tidy}";
- }
- if($this->fire('after', $output) === false){
- throw new Exception("Render on template {$this->name} cancelled. After");
- }
- return (string)$output;
- }
- public static function from(string $name, array $data = []) : string{
- $template = static::get($name);
- if(is_null($template)){
- throw new Exception("Template {$name} does not exist");
- }
- return $template->run($data);
- }
- public static function get(string $name){
- return static::$templates[$name] ?? null;
- }
- public static function compile(string $template) : string{
- $ignored = [];
- $output = $template;
- // Handle {#ignore code}
- static::$parsers['ignore']($output, $ignored);
- // Handle {#include path/to/file}
- static::$parsers['include']($output, $ignored);
- // Handle {#each name}{/each}
- static::$parsers['each']($output);
- // Handle {#exist name}{#else}{/exist}
- static::$parsers['existelse']($output);
- // Handle {#exist name}{/exist}
- static::$parsers['exist']($output);
- // Handle {gettext}
- static::$parsers['gettext']($output);
- // Handle {=expression}
- static::$parsers['echo']($output);
- // Handle {? expression ?}
- static::$parsers['eval']($output);
- // Handle {!@../name}
- static::$parsers['getrawparentmatch']($output);
- // Handle {!@name}
- static::$parsers['getrawmatch']($output);
- // Handle {!../name}
- static::$parsers['getparentmatch']($output);
- // Handle {!name}
- static::$parsers['getmatch']($output);
- // Handle {@../name}
- static::$parsers['rawparentmatch']($output);
- // Handle {@name}
- static::$parsers['rawmatch']($output);
- // Handle {../name}
- static::$parsers['parentmatch']($output);
- // Handle {name}
- static::$parsers['match']($output);
- // Handle {#ignored i}
- static::$parsers['ignored']($output, $ignored);
- return $output;
- }
- public static function parse(string $template, $data) : string{
- $id = md5($template);
- if(!isset(static::$templates[$id])){
- new Template($id, $template);
- }
- return static::$templates[$id]->run($data);
- }
- public static function execute(string $path, $data) : string{
- ob_start();
- include($path);
- $output = ob_get_contents();
- ob_end_clean();
- return $output;
- }
- public static function templates(){
- return static::$templates;
- }
- }
- ?>
|