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