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