1 <?php\r
2 /**\r
3 * Smarty Internal Plugin Smarty Template Compiler Base\r
4 * \r
5 * This file contains the basic classes and methodes for compiling Smarty templates with lexer/parser\r
6 * \r
7 * @package Smarty\r
8 * @subpackage Compiler\r
9 * @author Uwe Tews \r
10 */\r
11 /**\r
12 * Main compiler class\r
13 */\r
14 class Smarty_Internal_TemplateCompilerBase {\r
15 // hash for nocache sections\r
16 private $nocache_hash = null; \r
17 // suppress generation of nocache code\r
18 public $suppressNocacheProcessing = false; \r
19 // compile tag objects\r
20 static $_tag_objects = array(); \r
21 // tag stack\r
22 public $_tag_stack = array(); \r
23 // current template\r
24 public $template = null;\r
25 \r
26 /**\r
27 * Initialize compiler\r
28 */\r
29 public function __construct()\r
30 {\r
31 $this->nocache_hash = str_replace('.', '-', uniqid(rand(), true));\r
32 } \r
33 // abstract function doCompile($_content);\r
34 /**\r
35 * Methode to compile a Smarty template\r
36 * \r
37 * @param $template template object to compile\r
38 * @return bool true if compiling succeeded, false if it failed\r
39 */\r
40 public function compileTemplate($template)\r
41 {\r
42 if (empty($template->properties['nocache_hash'])) {\r
43 $template->properties['nocache_hash'] = $this->nocache_hash;\r
44 } else {\r
45 $this->nocache_hash = $template->properties['nocache_hash'];\r
46 } \r
47 /* here is where the compiling takes place. Smarty\r
48 tags in the templates are replaces with PHP code,\r
49 then written to compiled files. */\r
50 // flag for nochache sections\r
51 $this->nocache = false;\r
52 $this->tag_nocache = false; \r
53 // assume successfull compiling\r
54 $this->compile_error = false; \r
55 // save template object in compiler class\r
56 $this->template = $template;\r
57 $this->smarty->_current_file = $this->template->getTemplateFilepath(); \r
58 // template header code\r
59 $template_header = '';\r
60 if (!$template->suppressHeader) {\r
61 $template_header .= "<?php /* Smarty version " . Smarty::SMARTY_VERSION . ", created on " . strftime("%Y-%m-%d %H:%M:%S") . "\n";\r
62 $template_header .= " compiled from \"" . $this->template->getTemplateFilepath() . "\" */ ?>\n";\r
63 } \r
64 \r
65 do {\r
66 // flag for aborting current and start recompile\r
67 $this->abort_and_recompile = false; \r
68 // get template source\r
69 $_content = $template->getTemplateSource(); \r
70 // run prefilter if required\r
71 if (isset($this->smarty->autoload_filters['pre']) || isset($this->smarty->registered_filters['pre'])) {\r
72 $_content = Smarty_Internal_Filter_Handler::runFilter('pre', $_content, $this->smarty);\r
73 } \r
74 // on empty template just return header\r
75 if ($_content == '') {\r
76 if ($template->suppressFileDependency) {\r
77 $template->compiled_template = '';\r
78 } else {\r
79 $template->compiled_template = $template_header . $template->createPropertyHeader();\r
80 } \r
81 return true;\r
82 } \r
83 // call compiler\r
84 $_compiled_code = $this->doCompile($_content);\r
85 } while ($this->abort_and_recompile);\r
86 if (!$this->compile_error) {\r
87 // return compiled code to template object\r
88 if ($template->suppressFileDependency) {\r
89 $template->compiled_template = $_compiled_code;\r
90 } else {\r
91 $template->compiled_template = $template_header . $template->createPropertyHeader() . $_compiled_code;\r
92 } \r
93 // run postfilter if required\r
94 if (isset($this->smarty->autoload_filters['post']) || isset($this->smarty->registered_filters['post'])) {\r
95 $template->compiled_template = Smarty_Internal_Filter_Handler::runFilter('post', $template->compiled_template, $this->smarty);\r
96 } \r
97 return true;\r
98 } else {\r
99 // compilation error\r
100 return false;\r
101 } \r
102 } \r
103 \r
104 /**\r
105 * Compile Tag\r
106 * \r
107 * This is a call back from the lexer/parser\r
108 * It executes the required compile plugin for the Smarty tag\r
109 * \r
110 * @param string $tag tag name\r
111 * @param array $args array with tag attributes\r
112 * @return string compiled code\r
113 */\r
114 public function compileTag($tag, $args)\r
115 { \r
116 // $args contains the attributes parsed and compiled by the lexer/parser\r
117 // assume that tag does compile into code, but creates no HTML output\r
118 $this->has_code = true;\r
119 $this->has_output = false; \r
120 // compile the smarty tag (required compile classes to compile the tag are autoloaded)\r
121 if (($_output = $this->callTagCompiler($tag, $args)) === false) {\r
122 if (isset($this->smarty->template_functions[$tag])) {\r
123 // template defined by {template} tag\r
124 $args['name'] = "'" . $tag . "'";\r
125 $_output = $this->callTagCompiler('call', $args);\r
126 } \r
127 } \r
128 if ($_output !== false) {\r
129 if ($_output !== true) {\r
130 // did we get compiled code\r
131 if ($this->has_code) {\r
132 // Does it create output?\r
133 if ($this->has_output) {\r
134 $_output .= "\n";\r
135 } \r
136 // return compiled code\r
137 return $_output;\r
138 } \r
139 } \r
140 // tag did not produce compiled code\r
141 return '';\r
142 } else {\r
143 // not an internal compiler tag\r
144 if (strlen($tag) < 6 || substr_compare($tag, 'close', -5, 5) != 0) {\r
145 // check if tag is a registered object\r
146 if (isset($this->smarty->registered_objects[$tag]) && isset($args['object_methode'])) {\r
147 $methode = $args['object_methode'];\r
148 unset ($args['object_methode']);\r
149 if (!in_array($methode, $this->smarty->registered_objects[$tag][3]) &&\r
150 (empty($this->smarty->registered_objects[$tag][1]) || in_array($methode, $this->smarty->registered_objects[$tag][1]))) {\r
151 return $this->callTagCompiler('private_object_function', $args, $tag, $methode);\r
152 } elseif (in_array($methode, $this->smarty->registered_objects[$tag][3])) {\r
153 return $this->callTagCompiler('private_object_block_function', $args, $tag, $methode);\r
154 } else {\r
155 return $this->trigger_template_error ('unallowed methode "' . $methode . '" in registered object "' . $tag . '"', $this->lex->taglineno);\r
156 } \r
157 } \r
158 // check if tag is registered\r
159 foreach (array('compiler', 'function', 'block') as $type) {\r
160 if (isset($this->smarty->registered_plugins[$type][$tag])) {\r
161 // if compiler function plugin call it now\r
162 if ($type == 'compiler') {\r
163 if (!$this->smarty->registered_plugins[$type][$tag][1]) {\r
164 $this->tag_nocache = true;\r
165 } \r
166 return call_user_func_array($this->smarty->registered_plugins[$type][$tag][0], array($args, $this));\r
167 } \r
168 // compile registered function or block function\r
169 if ($type == 'function' || $type == 'block') {\r
170 return $this->callTagCompiler('private_registered_' . $type, $args, $tag);\r
171 } \r
172 } \r
173 } \r
174 // check plugins from plugins folder\r
175 foreach ($this->smarty->plugin_search_order as $plugin_type) {\r
176 if ($plugin_type == 'compiler' && $this->smarty->loadPlugin('smarty_compiler_' . $tag)) {\r
177 $plugin = 'smarty_compiler_' . $tag;\r
178 if (is_callable($plugin)) {\r
179 return $plugin($args, $this->smarty);\r
180 } \r
181 if (class_exists($plugin, false)) {\r
182 $plugin_object = new $plugin;\r
183 if (method_exists($plugin_object, 'compile')) {\r
184 return $plugin_object->compile($args, $this);\r
185 } \r
186 } \r
187 throw new Exception("Plugin \"{$tag}\" not callable");\r
188 } else {\r
189 if ($function = $this->getPlugin($tag, $plugin_type)) {\r
190 return $this->callTagCompiler('private_' . $plugin_type . '_plugin', $args, $tag, $function);\r
191 } \r
192 } \r
193 } \r
194 } else {\r
195 // compile closing tag of block function\r
196 $base_tag = substr($tag, 0, -5); \r
197 // check if closing tag is a registered object\r
198 if (isset($this->smarty->registered_objects[$base_tag]) && isset($args['object_methode'])) {\r
199 $methode = $args['object_methode'];\r
200 unset ($args['object_methode']);\r
201 if (in_array($methode, $this->smarty->registered_objects[$base_tag][3])) {\r
202 return $this->callTagCompiler('private_object_block_function', $args, $tag, $methode);\r
203 } else {\r
204 return $this->trigger_template_error ('unallowed closing tag methode "' . $methode . '" in registered object "' . $base_tag . '"', $this->lex->taglineno);\r
205 } \r
206 } \r
207 // registered block tag ?\r
208 if (isset($this->smarty->registered_plugins['block'][$base_tag])) {\r
209 return $this->callTagCompiler('private_registered_block', $args, $tag);\r
210 } \r
211 // block plugin?\r
212 if ($function = $this->getPlugin($base_tag, 'block')) {\r
213 return $this->callTagCompiler('private_block_plugin', $args, $tag, $function);\r
214 } \r
215 if ($this->smarty->loadPlugin('smarty_compiler_' . $tag)) {\r
216 $plugin = 'smarty_compiler_' . $tag;\r
217 if (is_callable($plugin)) {\r
218 return $plugin($args, $this->smarty);\r
219 } \r
220 if (class_exists($plugin, false)) {\r
221 $plugin_object = new $plugin;\r
222 if (method_exists($plugin_object, 'compile')) {\r
223 return $plugin_object->compile($args, $this);\r
224 } \r
225 } \r
226 throw new Exception("Plugin \"{$tag}\" not callable");\r
227 } \r
228 } \r
229 $this->trigger_template_error ("unknown tag \"" . $tag . "\"", $this->lex->taglineno);\r
230 } \r
231 } \r
232 \r
233 /**\r
234 * lazy loads internal compile plugin for tag and calls the compile methode\r
235 * \r
236 * compile objects cached for reuse.\r
237 * class name format: Smarty_Internal_Compile_TagName\r
238 * plugin filename format: Smarty_Internal_Tagname.php\r
239 * \r
240 * @param $tag string tag name\r
241 * @param $args array with tag attributes\r
242 * @param $param1 optional parameter\r
243 * @param $param2 optional parameter\r
244 * @param $param3 optional parameter\r
245 * @return string compiled code\r
246 */\r
247 public function callTagCompiler($tag, $args, $param1 = null, $param2 = null, $param3 = null)\r
248 { \r
249 // re-use object if already exists\r
250 if (isset(self::$_tag_objects[$tag])) {\r
251 // compile this tag\r
252 return self::$_tag_objects[$tag]->compile($args, $this, $param1, $param2, $param3);\r
253 } \r
254 // lazy load internal compiler plugin\r
255 $class_name = 'Smarty_Internal_Compile_' . $tag;\r
256 if ($this->smarty->loadPlugin($class_name)) {\r
257 // use plugin if found\r
258 self::$_tag_objects[$tag] = new $class_name; \r
259 // compile this tag\r
260 return self::$_tag_objects[$tag]->compile($args, $this, $param1, $param2, $param3);\r
261 } \r
262 // no internal compile plugin for this tag\r
263 return false;\r
264 } \r
265 \r
266 /**\r
267 * Check for plugins and return function name\r
268 * \r
269 * @param $pugin_name string name of plugin or function\r
270 * @param $type string type of plugin\r
271 * @return string call name of function\r
272 */\r
273 public function getPlugin($plugin_name, $type)\r
274 {\r
275 $function = null;\r
276 if ($this->template->caching && ($this->nocache || $this->tag_nocache)) {\r
277 if (isset($this->template->required_plugins['nocache'][$plugin_name][$type])) {\r
278 $function = $this->template->required_plugins['nocache'][$plugin_name][$type]['function'];\r
279 } else if (isset($this->template->required_plugins['compiled'][$plugin_name][$type])) {\r
280 $this->template->required_plugins['nocache'][$plugin_name][$type] = $this->template->required_plugins['compiled'][$plugin_name][$type];\r
281 $function = $this->template->required_plugins['nocache'][$plugin_name][$type]['function'];\r
282 } \r
283 } else {\r
284 if (isset($this->template->required_plugins['compiled'][$plugin_name][$type])) {\r
285 $function = $this->template->required_plugins['compiled'][$plugin_name][$type]['function'];\r
286 } else if (isset($this->template->required_plugins['compiled'][$plugin_name][$type])) {\r
287 $this->template->required_plugins['compiled'][$plugin_name][$type] = $this->template->required_plugins['nocache'][$plugin_name][$type];\r
288 $function = $this->template->required_plugins['compiled'][$plugin_name][$type]['function'];\r
289 } \r
290 } \r
291 if (isset($function)) {\r
292 if ($type == 'modifier') {\r
293 $this->template->saved_modifer[$plugin_name] = true;\r
294 } \r
295 return $function;\r
296 } \r
297 /**\r
298 * if (isset($this->template->required_plugins_call[$plugin_name][$type])) {\r
299 * if ($this->template->caching && ($this->nocache || $this->tag_nocache)) {\r
300 * if (isset($this->template->required_plugins['compiled'][$plugin_name])) {\r
301 * $this->template->required_plugins['cache'][$plugin_name] = $this->template->required_plugins['compiled'][$plugin_name];\r
302 * } \r
303 * } else {\r
304 * if (isset($this->template->required_plugins['cache'][$plugin_name])) {\r
305 * $this->template->required_plugins['compiled'][$plugin_name] = $this->template->required_plugins['cache'][$plugin_name];\r
306 * } \r
307 * } \r
308 * if ($type == 'modifier') {\r
309 * $this->template->saved_modifer[$plugin_name] = true;\r
310 * } \r
311 * return $this->template->required_plugins_call[$plugin_name][$type];\r
312 * }\r
313 */\r
314 // loop through plugin dirs and find the plugin\r
315 $function = 'smarty_' . $type . '_' . $plugin_name;\r
316 $found = false;\r
317 foreach((array)$this->smarty->plugins_dir as $_plugin_dir) {\r
318 $file = rtrim($_plugin_dir, '/\\') . DS . $type . '.' . $plugin_name . '.php';\r
319 if (file_exists($file)) {\r
320 // require_once($file);\r
321 $found = true;\r
322 break;\r
323 } \r
324 } \r
325 if ($found) {\r
326 // if (is_callable($plugin)) {\r
327 if ($this->template->caching && ($this->nocache || $this->tag_nocache)) {\r
328 $this->template->required_plugins['nocache'][$plugin_name][$type]['file'] = $file;\r
329 $this->template->required_plugins['nocache'][$plugin_name][$type]['function'] = $function;\r
330 } else {\r
331 $this->template->required_plugins['compiled'][$plugin_name][$type]['file'] = $file;\r
332 $this->template->required_plugins['compiled'][$plugin_name][$type]['function'] = $function;\r
333 } \r
334 /**\r
335 * $this->template->required_plugins_call[$plugin_name][$type] = $plugin;\r
336 * if ($this->template->caching && ($this->nocache || $this->tag_nocache)) {\r
337 * $this->template->required_plugins['cache'][$plugin_name]['file'] = $file;\r
338 * $this->template->required_plugins['cache'][$plugin_name]['type'] = $type;\r
339 * } else {\r
340 * $this->template->required_plugins['compiled'][$plugin_name]['file'] = $file;\r
341 * $this->template->required_plugins['compiled'][$plugin_name]['type'] = $type;\r
342 * }\r
343 */\r
344 if ($type == 'modifier') {\r
345 $this->template->saved_modifer[$plugin_name] = true;\r
346 } \r
347 return $function;\r
348 } \r
349 if (is_callable($function)) {\r
350 // plugin function is defined in the script\r
351 return $function;\r
352 } \r
353 return false;\r
354 } \r
355 /**\r
356 * Inject inline code for nocache template sections\r
357 * \r
358 * This method gets the content of each template element from the parser.\r
359 * If the content is compiled code and it should be not cached the code is injected\r
360 * into the rendered output.\r
361 * \r
362 * @param string $content content of template element\r
363 * @param boolean $tag_nocache true if the parser detected a nocache situation\r
364 * @param boolean $is_code true if content is compiled code\r
365 * @return string content\r
366 */\r
367 public function processNocacheCode ($content, $is_code)\r
368 { \r
369 // If the template is not evaluated and we have a nocache section and or a nocache tag\r
370 if ($is_code && !empty($content)) {\r
371 // generate replacement code\r
372 if ((!$this->template->resource_object->isEvaluated || $this->template->forceNocache) && $this->template->caching && !$this->suppressNocacheProcessing &&\r
373 ($this->nocache || $this->tag_nocache || $this->template->forceNocache == 2)) {\r
374 $this->tag_nocache = false;\r
375 $this->template->has_nocache_code = true;\r
376 $_output = str_replace("'", "\'", $content);\r
377 $_output = "<?php echo '/*%%SmartyNocache:{$this->nocache_hash}%%*/" . $_output . "/*/%%SmartyNocache:{$this->nocache_hash}%%*/';?>"; \r
378 // make sure we include modifer plugins for nocache code\r
379 if (isset($this->template->saved_modifer)) {\r
380 foreach ($this->template->saved_modifer as $plugin_name => $dummy) {\r
381 if (isset($this->template->required_plugins['compiled'][$plugin_name]['modifier'])) {\r
382 $this->template->required_plugins['nocache'][$plugin_name]['modifier'] = $this->template->required_plugins['compiled'][$plugin_name]['modifier'];\r
383 } \r
384 } \r
385 unset($this->template->saved_modifer);\r
386 } \r
387 } else {\r
388 $_output = $content;\r
389 } \r
390 } else {\r
391 $_output = $content;\r
392 } \r
393 $this->suppressNocacheProcessing = false;\r
394 return $_output;\r
395 } \r
396 /**\r
397 * display compiler error messages without dying\r
398 * \r
399 * If parameter $args is empty it is a parser detected syntax error.\r
400 * In this case the parser is called to obtain information about expected tokens.\r
401 * \r
402 * If parameter $args contains a string this is used as error message\r
403 * \r
404 * @param $args string individual error message or null\r
405 */\r
406 public function trigger_template_error($args = null, $line = null)\r
407 { \r
408 // get template source line which has error\r
409 if (!isset($line)) {\r
410 $line = $this->lex->line;\r
411 } \r
412 $match = preg_split("/\n/", $this->lex->data);\r
413 $error_text = 'Syntax Error in template "' . $this->template->getTemplateFilepath() . '" on line ' . $line . ' "' . htmlspecialchars($match[$line-1]) . '" ';\r
414 if (isset($args)) {\r
415 // individual error message\r
416 $error_text .= $args;\r
417 } else {\r
418 // expected token from parser\r
419 foreach ($this->parser->yy_get_expected_tokens($this->parser->yymajor) as $token) {\r
420 $exp_token = $this->parser->yyTokenName[$token];\r
421 if (isset($this->lex->smarty_token_names[$exp_token])) {\r
422 // token type from lexer\r
423 $expect[] = '"' . $this->lex->smarty_token_names[$exp_token] . '"';\r
424 } else {\r
425 // otherwise internal token name\r
426 $expect[] = $this->parser->yyTokenName[$token];\r
427 } \r
428 } \r
429 // output parser error message\r
430 $error_text .= ' - Unexpected "' . $this->lex->value . '", expected one of: ' . implode(' , ', $expect);\r
431 } \r
432 throw new Exception($error_text); \r
433 // set error flag\r
434 $this->compile_error = true;\r
435 } \r
436 } \r
437 \r
438 ?>