]> git.mxchange.org Git - friendica.git/blob - include/template_processor.php
template engine rework
[friendica.git] / include / template_processor.php
1 <?php
2 require_once 'object/TemplateEngine.php';
3
4 define("KEY_NOT_EXISTS", '^R_key_not_Exists^');
5
6 class Template implements ITemplateEngine {
7         static $name ="internal";
8         
9         var $r;
10         var $search;
11         var $replace;
12         var $stack = array();
13         var $nodes = array();
14         var $done = false;
15         var $d = false;
16         var $lang = null;
17         var $debug = false;
18
19         private function _preg_error() {
20
21                 switch (preg_last_error()) {
22                         case PREG_INTERNAL_ERROR: echo('PREG_INTERNAL_ERROR');
23                                 break;
24                         case PREG_BACKTRACK_LIMIT_ERROR: echo('PREG_BACKTRACK_LIMIT_ERROR');
25                                 break;
26                         case PREG_RECURSION_LIMIT_ERROR: echo('PREG_RECURSION_LIMIT_ERROR');
27                                 break;
28                         case PREG_BAD_UTF8_ERROR: echo('PREG_BAD_UTF8_ERROR');
29                                 break;
30 //                      This is only valid for php > 5.3, not certain how to code around it for unit tests
31 //                      case PREG_BAD_UTF8_OFFSET_ERROR: echo('PREG_BAD_UTF8_OFFSET_ERROR'); break;
32                         default:
33                                 //die("Unknown preg error.");
34                                 return;
35                 }
36                 echo "<hr><pre>";
37                 debug_print_backtrace();
38                 die();
39         }
40
41         private function _push_stack() {
42                 $this->stack[] = array($this->r, $this->nodes);
43         }
44
45         private function _pop_stack() {
46                 list($this->r, $this->nodes) = array_pop($this->stack);
47         }
48
49         private function _get_var($name, $retNoKey = false) {
50                 $keys = array_map('trim', explode(".", $name));
51                 if ($retNoKey && !array_key_exists($keys[0], $this->r))
52                         return KEY_NOT_EXISTS;
53                 $val = $this->r;
54                 foreach ($keys as $k) {
55                         $val = (isset($val[$k]) ? $val[$k] : null);
56                 }
57                 return $val;
58         }
59
60         /**
61          * IF node
62          * 
63          * {{ if <$var> }}...[{{ else }} ...] {{ endif }}
64          * {{ if <$var>==<val|$var> }}...[{{ else }} ...]{{ endif }}
65          * {{ if <$var>!=<val|$var> }}...[{{ else }} ...]{{ endif }}
66          */
67         private function _replcb_if($args) {
68                 if (strpos($args[2], "==") > 0) {
69                         list($a, $b) = array_map("trim", explode("==", $args[2]));
70                         $a = $this->_get_var($a);
71                         if ($b[0] == "$")
72                                 $b = $this->_get_var($b);
73                         $val = ($a == $b);
74                 } else if (strpos($args[2], "!=") > 0) {
75                         list($a, $b) = array_map("trim", explode("!=", $args[2]));
76                         $a = $this->_get_var($a);
77                         if ($b[0] == "$")
78                                 $b = $this->_get_var($b);
79                         $val = ($a != $b);
80                 } else {
81                         $val = $this->_get_var($args[2]);
82                 }
83                 $x = preg_split("|{{ *else *}}|", $args[3]);
84                 return ( $val ? $x[0] : (isset($x[1]) ? $x[1] : ""));
85         }
86
87         /**
88          * FOR node
89          * 
90          * {{ for <$var> as $name }}...{{ endfor }}
91          * {{ for <$var> as $key=>$name }}...{{ endfor }}
92          */
93         private function _replcb_for($args) {
94                 $m = array_map('trim', explode(" as ", $args[2]));
95                 $x = explode("=>", $m[1]);
96                 if (count($x) == 1) {
97                         $varname = $x[0];
98                         $keyname = "";
99                 } else {
100                         list($keyname, $varname) = $x;
101                 }
102                 if ($m[0] == "" || $varname == "" || is_null($varname))
103                         die("template error: 'for " . $m[0] . " as " . $varname . "'");
104                 //$vals = $this->r[$m[0]];
105                 $vals = $this->_get_var($m[0]);
106                 $ret = "";
107                 if (!is_array($vals))
108                         return $ret;
109                 foreach ($vals as $k => $v) {
110                         $this->_push_stack();
111                         $r = $this->r;
112                         $r[$varname] = $v;
113                         if ($keyname != '')
114                                 $r[$keyname] = (($k === 0) ? '0' : $k);
115                         $ret .= $this->replace($args[3], $r);
116                         $this->_pop_stack();
117                 }
118                 return $ret;
119         }
120
121         /**
122          * INC node
123          * 
124          * {{ inc <templatefile> [with $var1=$var2] }}{{ endinc }}
125          */
126         private function _replcb_inc($args) {
127                 if (strpos($args[2], "with")) {
128                         list($tplfile, $newctx) = array_map('trim', explode("with", $args[2]));
129                 } else {
130                         $tplfile = trim($args[2]);
131                         $newctx = null;
132                 }
133
134                 if ($tplfile[0] == "$")
135                         $tplfile = $this->_get_var($tplfile);
136
137                 $this->_push_stack();
138                 $r = $this->r;
139                 if (!is_null($newctx)) {
140                         list($a, $b) = array_map('trim', explode("=", $newctx));
141                         $r[$a] = $this->_get_var($b);
142                 }
143                 $this->nodes = Array();
144                 $tpl = get_markup_template($tplfile);
145                 $ret = $this->replace($tpl, $r);
146                 $this->_pop_stack();
147                 return $ret;
148         }
149
150         /**
151          * DEBUG node
152          * 
153          * {{ debug $var [$var [$var [...]]] }}{{ enddebug }}
154          * 
155          * replace node with <pre>var_dump($var, $var, ...);</pre>
156          */
157         private function _replcb_debug($args) {
158                 $vars = array_map('trim', explode(" ", $args[2]));
159                 $vars[] = $args[1];
160
161                 $ret = "<pre>";
162                 foreach ($vars as $var) {
163                         $ret .= htmlspecialchars(var_export($this->_get_var($var), true));
164                         $ret .= "\n";
165                 }
166                 $ret .= "</pre>";
167                 return $ret;
168         }
169
170         private function _replcb_node($m) {
171                 $node = $this->nodes[$m[1]];
172                 if (method_exists($this, "_replcb_" . $node[1])) {
173                         $s = call_user_func(array($this, "_replcb_" . $node[1]), $node);
174                 } else {
175                         $s = "";
176                 }
177                 $s = preg_replace_callback('/\|\|([0-9]+)\|\|/', array($this, "_replcb_node"), $s);
178                 return $s;
179         }
180
181         private function _replcb($m) {
182                 //var_dump(array_map('htmlspecialchars', $m));
183                 $this->done = false;
184                 $this->nodes[] = (array) $m;
185                 return "||" . (count($this->nodes) - 1) . "||";
186         }
187
188         private function _build_nodes($s) {
189                 $this->done = false;
190                 while (!$this->done) {
191                         $this->done = true;
192                         $s = preg_replace_callback('|{{ *([a-z]*) *([^}]*)}}([^{]*({{ *else *}}[^{]*)?){{ *end\1 *}}|', array($this, "_replcb"), $s);
193                         if ($s == Null)
194                                 $this->_preg_error();
195                 }
196                 //({{ *else *}}[^{]*)?
197                 krsort($this->nodes);
198                 return $s;
199         }
200
201         private function var_replace($s) {
202                 $m = array();
203                 /** regexp:
204                  * \$                                           literal $
205                  * (\[)?                                        optional open square bracket
206                  * ([a-zA-Z0-9-_]+\.?)+         var name, followed by optional
207                  *                                                      dot, repeated at least 1 time
208                  * (|[a-zA-Z0-9-_:]+)*          pipe followed by filter name and args, zero or many
209                  * (?(1)\])                                     if there was opened square bracket
210                  *                                                      (subgrup 1), match close bracket
211                  */
212                 if (preg_match_all('/\$(\[)?([a-zA-Z0-9-_]+\.?)+(\|[a-zA-Z0-9-_:]+)*(?(1)\])/', $s, $m)) {
213                         foreach ($m[0] as $var) {
214
215                                 $exp = str_replace(array("[", "]"), array("", ""), $var);
216                                 $exptks = explode("|", $exp);
217
218                                 $varn = $exptks[0];
219                                 unset($exptks[0]);
220                                 $val = $this->_get_var($varn, true);
221                                 if ($val != KEY_NOT_EXISTS) {
222                                         /* run filters */
223                                         /*
224                                          * Filter are in form of:
225                                          * filtername:arg:arg:arg
226                                          * 
227                                          * "filtername" is function name
228                                          * "arg"s are optional, var value is appended to the end
229                                          *                      if one "arg"==='x' , is replaced with var value
230                                          * 
231                                          * examples:
232                                          * $item.body|htmlspecialchars                          // escape html chars
233                                          * $item.body|htmlspecialchars|strtoupper   // escape html and uppercase result
234                                          * $item.created|date:%Y %M %j                          // format date (created is a timestamp)
235                                          * $item.body|str_replace:cat:dog                       // replace all "cat" with "dog"
236                                          * $item.body|str_replace:cat:dog:x:1           // replace one "cat" with "dog"
237                                          
238                                          */
239                                         foreach ($exptks as $filterstr) {
240                                                 $filter = explode(":", $filterstr);
241                                                 $filtername = $filter[0];
242                                                 unset($filter[0]);
243                                                 $valkey = array_search("x", $filter);
244                                                 if ($valkey === false) {
245                                                         $filter[] = $val;
246                                                 } else {
247                                                         $filter[$valkey] = $val;
248                                                 }
249                                                 if (function_exists($filtername)) {
250                                                         $val = call_user_func_array($filtername, $filter);
251                                                 }
252                                         }
253                                         $s = str_replace($var, $val, $s);
254                                 }
255                         }
256                 }
257
258                 return $s;
259         }
260
261         // TemplateEngine interface
262         public function replace_macros($s, $r) {
263                 $this->r = $r;
264
265                 // remove comments block
266                 $s = preg_replace('/{#(.*?\s*?)*?#}/', "", $s);
267
268                 $s = $this->_build_nodes($s);
269
270                 $s = preg_replace_callback('/\|\|([0-9]+)\|\|/', array($this, "_replcb_node"), $s);
271                 if ($s == Null)
272                         $this->_preg_error();
273
274                 // replace strings recursively (limit to 10 loops)
275                 $os = "";
276                 $count = 0;
277                 while ($os != $s && $count < 10) {
278                         $os = $s;
279                         $count++;
280                         $s = $this->var_replace($s);
281                 }
282                 return template_unescape($s);
283         }
284         
285         public function get_template_file($file, $root='') {
286                 $a = get_app();
287                 $template_file = get_template_file($a, $file, $root);
288                 $content = file_get_contents($template_file);
289                 return $content;                
290         }
291         
292 }
293
294
295 function template_escape($s) {
296
297         return str_replace(array('$', '{{'), array('!_Doll^Ars1Az_!', '!_DoubLe^BraceS4Rw_!'), $s);
298 }
299
300 function template_unescape($s) {
301
302         return str_replace(array('!_Doll^Ars1Az_!', '!_DoubLe^BraceS4Rw_!'), array('$', '{{'), $s);
303 }
304