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