diff --git a/NEWS b/NEWS index 1c5427b1..a0b33864 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,5 @@ + - added $cacheable-parameter with default=true to register_function() + and register_block() (messju) - add math speedup to core (Dominik, Monte) - fix newlines for tags without template output (Monte) - added config-option "request_use_auto_globals" to make auto-globals be diff --git a/libs/Smarty.class.php b/libs/Smarty.class.php index 55de3aee..56435740 100644 --- a/libs/Smarty.class.php +++ b/libs/Smarty.class.php @@ -563,6 +563,10 @@ reques * @var string 'resource' => array(), 'insert' => array()); + + var $_cache_serial = null; + var $_cache_serials = array(); + /**#@-*/ /** * The class constructor. @@ -708,10 +712,10 @@ reques * @var string * @param string $function the name of the template function * @param string $function_impl the name of the PHP function to register */ - function register_function($function, $function_impl) + function register_function($function, $function_impl, $cacheable=true) { $this->_plugins['function'][$function] = - array($function_impl, null, null, false); + array($function_impl, null, null, false, $cacheable); } /** @@ -758,10 +762,10 @@ reques * @var string * @param string $block name of template block * @param string $block_impl PHP function to register */ - function register_block($block, $block_impl) + function register_block($block, $block_impl, $cacheable=true) { $this->_plugins['block'][$block] = - array($block_impl, null, null, false); + array($block_impl, null, null, false, $cacheable); } /** @@ -1195,6 +1199,13 @@ reques * @var string require_once(SMARTY_DIR . 'core/core.process_cached_inserts.php'); $_smarty_results = smarty_core_process_cached_inserts($_params, $this); } + if (@count($this->_cache_info['cache_serials'])) { + $_params = array('results' => $_smarty_results); + require_once(SMARTY_DIR . 'core/core.process_compiled_include.php'); + $_smarty_results = smarty_core_process_compiled_include($_params, $this); + } + + if ($display) { if ($this->debugging) { @@ -1209,6 +1220,7 @@ reques * @var string $_last_modified_date = substr($GLOBALS['HTTP_SERVER_VARS']['HTTP_IF_MODIFIED_SINCE'], 0, strpos($GLOBALS['HTTP_SERVER_VARS']['HTTP_IF_MODIFIED_SINCE'], 'GMT') + 3); $_gmt_mtime = gmdate('D, d M Y H:i:s', $this->_cache_info['timestamp']).' GMT'; if (@count($this->_cache_info['insert_tags']) == 0 + && !$this->_cache_serials && $_gmt_mtime == $_last_modified_date) { header("HTTP/1.1 304 Not Modified"); } else { @@ -1279,6 +1291,13 @@ reques * @var string smarty_core_write_cache_file($_params, $this); require_once(SMARTY_DIR . 'core/core.process_cached_inserts.php'); $_smarty_results = smarty_core_process_cached_inserts($_params, $this); + + if ($this->_cache_serials) { + // strip nocache-tags from output + $_smarty_results = preg_replace('!(\{/?nocache\:[0-9a-f]{32}#\d+\})!s' + ,'' + ,$_smarty_results); + } // restore initial cache_info $this->_cache_info = array_pop($_cache_info); } @@ -1490,10 +1509,21 @@ reques * @var string $smarty_compiler->request_use_auto_globals = $this->request_use_auto_globals; + $smarty_compiler->_cache_serial = null; + $smarty_compiler->_cache_include = substr($compile_path, 0, -4).'.inc'; + if ($smarty_compiler->_compile_file($tpl_file, $_file_source, $_file_compiled)) { $_params = array('compile_path' => $compile_path, 'file_compiled' => $_file_compiled, 'file_timestamp' => $_file_timestamp); require_once(SMARTY_DIR . 'core/core.write_compiled_template.php'); smarty_core_write_compiled_template($_params, $this); + + // if a _cache_serial was set, we also have to write an include-file: + if ($this->_cache_serial = $smarty_compiler->_cache_serial) { + $_params['plugins_code'] = $smarty_compiler->_plugins_code; + $_params['include_file_path'] = $smarty_compiler->_cache_include; + require_once(SMARTY_DIR . 'core/core.write_compiled_include.php'); + smarty_core_write_compiled_include($_params, $this); + } return true; } else { $this->trigger_error($smarty_compiler->_error_msg); @@ -1719,14 +1749,31 @@ reques * @var string } /** - * check if the function or method exists * @return bool - */ + * check if the function or method exists + * @return bool + */ function _plugin_implementation_exists($function) { return (is_array($function)) ? method_exists($function[0], $function[1]) : function_exists($function); } /**#@-*/ + + + + /** + * callback function for preg_replace, to call a non-cacheable block + * @return string + */ + function _process_compiled_include_callback($match) { + $_func = '_smarty_tplfunc_'.$match[2].'_'.$match[3]; + ob_start(); + $_func($this); + $_ret = ob_get_contents(); + ob_end_clean(); + return $_ret; + } + } /* vim: set expandtab: */ diff --git a/libs/Smarty_Compiler.class.php b/libs/Smarty_Compiler.class.php index 36e73770..8a9d2578 100644 --- a/libs/Smarty_Compiler.class.php +++ b/libs/Smarty_Compiler.class.php @@ -79,6 +79,12 @@ class Smarty_Compiler extends Smarty { var $_obj_start_regexp = null; var $_obj_params_regexp = null; var $_obj_call_regexp = null; + + var $_cacheable_state = 0; + var $_nocache_count = 0; + var $_cache_serial = null; + var $_cache_include = null; + /**#@-*/ /** * The class constructor. @@ -204,7 +210,7 @@ class Smarty_Compiler extends Smarty { // foo123($foo,$foo->bar(),"foo") $this->_func_call_regexp = '(?:' . $this->_func_regexp . '\s*(?:' . $this->_parenth_param_regexp . '))'; - } + } /** * compile a template file @@ -325,6 +331,10 @@ class Smarty_Compiler extends Smarty { $file_compiled = substr($file_compiled, 0, -1); } + if (!empty($this->_cache_serial)) { + $file_compiled = "_cache_serials['".$this->_cache_include."'] = '".$this->_cache_serial."'; ?>" . $file_compiled; + } + // remove unnecessary close/open tags $file_compiled = preg_replace('!\?>\n?<\?php!', '', $file_compiled); @@ -347,6 +357,7 @@ class Smarty_Compiler extends Smarty { $template_header .= " compiled from ".$tpl_file." */ ?>\n"; /* Emit code to load needed plugins. */ + $this->_plugins_code = ''; if (count($this->_plugin_info)) { $_plugins_params = "array('plugins' => array("; foreach ($this->_plugin_info as $plugin_type => $plugins) { @@ -359,6 +370,7 @@ class Smarty_Compiler extends Smarty { $plugins_code = "\n"; $template_header .= $plugins_code; $this->_plugin_info = array(); + $this->_plugins_code = $plugins_code; } if ($this->_init_smarty_vars) { @@ -545,7 +557,7 @@ class Smarty_Compiler extends Smarty { $message = "plugin function $plugin_func() not found in $plugin_file\n"; $have_function = false; } else { - $this->_plugins['compiler'][$tag_command] = array($plugin_func, null, null); + $this->_plugins['compiler'][$tag_command] = array($plugin_func, null, null, null, true); } } @@ -559,7 +571,9 @@ class Smarty_Compiler extends Smarty { if ($have_function) { $output = call_user_func_array($plugin_func, array($tag_args, &$this)); if($output != '') { - $output = ''; + $output = '_push_cacheable_state('compiler', $tag_command) + . $output + . $this->_pop_cacheable_state('compiler', $tag_command) . ' ?>'; } } else { $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__); @@ -645,7 +659,8 @@ class Smarty_Compiler extends Smarty { $arg_list[] = "'$arg_name' => $arg_value"; } - $output = "_tag_stack[] = array('$tag_command', array(".implode(',', (array)$arg_list).')); '; + $output = '_push_cacheable_state('block', $tag_command); + $output .= "\$this->_tag_stack[] = array('$tag_command', array(".implode(',', (array)$arg_list).')); '; $output .= $this->_compile_plugin_call('block', $tag_command).'(array('.implode(',', (array)$arg_list).'), null, $this, $_block_repeat=true);'; $output .= 'while ($_block_repeat) { ob_start(); ?>'; } else { @@ -655,7 +670,7 @@ class Smarty_Compiler extends Smarty { $this->_parse_modifiers($_out_tag_text, $tag_modifier); } $output .= 'echo '.$_out_tag_text.'; } '; - $output .= " array_pop(\$this->_tag_stack); ?>"; + $output .= " array_pop(\$this->_tag_stack); " . $this->_pop_cacheable_state('block', $tag_command) . '?>'; } return true; @@ -684,14 +699,14 @@ class Smarty_Compiler extends Smarty { $arg_value = 'null'; $arg_list[] = "'$arg_name' => $arg_value"; } - $_return = $this->_compile_plugin_call('function', $tag_command).'(array('.implode(',', (array)$arg_list)."), \$this)"; if($tag_modifier != '') { $this->_parse_modifiers($return, $tag_modifier); } - + if($_return != '') { - $_return = ''; + $_return = '_push_cacheable_state('function', $tag_command) + . 'echo ' . $_return . ';' . $this->_pop_cacheable_state('function', $tag_command) . "?>\n"; } return $_return; @@ -1084,6 +1099,7 @@ class Smarty_Compiler extends Smarty { * @param string $tag_args * @return string */ + function _compile_capture_tag($start, $tag_args = '') { $attrs = $this->_parse_attrs($tag_args); @@ -1963,6 +1979,41 @@ class Smarty_Compiler extends Smarty { trigger_error('Smarty: [in ' . $this->_current_file . ' line ' . $this->_current_line_no . "]: syntax error: $error_msg$info", $error_type); } + + + /** + * check if the compilation changes from cacheable to + * non-cacheable state with the beginning of the current + * plugin. return php-code to reflect the transition. + * @return string + */ + function _push_cacheable_state($type, $name) { + $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4]; + if ($_cacheable + || $this->_cacheable_state++) return ''; + if (!isset($this->_cache_serial)) $this->_cache_serial = md5(uniqid('Smarty')); + $_ret = 'if ($this->caching) { echo \'{nocache:' + . $this->_cache_serial . '#' . $this->_nocache_count + . '}\';}'; + return $_ret; + } + + + /** + * check if the compilation changes from non-cacheable to + * cacheable state with the end of the current plugin return + * php-code to reflect the transition. + * @return string + */ + function _pop_cacheable_state($type, $name) { + $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4]; + if ($_cacheable + || --$this->_cacheable_state>0) return ''; + return 'if ($this->caching) { echo \'{/nocache:' + . $this->_cache_serial . '#' . ($this->_nocache_count++) + . '}\';}'; + } + } /** diff --git a/libs/core/core.load_plugins.php b/libs/core/core.load_plugins.php index a1cf32d3..c5ba61a2 100644 --- a/libs/core/core.load_plugins.php +++ b/libs/core/core.load_plugins.php @@ -38,6 +38,7 @@ function smarty_core_load_plugins($params, &$this) $_plugin[1] = $_tpl_file; $_plugin[2] = $_tpl_line; $_plugin[3] = true; + $_plugin[4] = true; /* cacheable */ } } continue; @@ -48,7 +49,7 @@ function smarty_core_load_plugins($params, &$this) */ $_plugin_func = 'insert_' . $_name; if (function_exists($_plugin_func)) { - $_plugin = array($_plugin_func, $_tpl_file, $_tpl_line, true); + $_plugin = array($_plugin_func, $_tpl_file, $_tpl_line, true, false); continue; } } @@ -111,7 +112,7 @@ function smarty_core_load_plugins($params, &$this) } if ($_found) { - $this->_plugins[$_type][$_name] = array($_plugin_func, $_tpl_file, $_tpl_line, true); + $this->_plugins[$_type][$_name] = array($_plugin_func, $_tpl_file, $_tpl_line, true, true); } else { // output error $this->_trigger_fatal_error('[plugin] ' . $_message, $_tpl_file, $_tpl_line, __FILE__, __LINE__); diff --git a/libs/core/core.process_compiled_include.php b/libs/core/core.process_compiled_include.php new file mode 100644 index 00000000..fd3dbb5d --- /dev/null +++ b/libs/core/core.process_compiled_include.php @@ -0,0 +1,29 @@ +_cache_serials as $_include_file_path=>$_cache_serial) { + include_once($_include_file_path); + $_return = preg_replace_callback('!(\{nocache\:('.$_cache_serial.')#(\d+)\})!s', + array(&$this, '_process_compiled_include_callback'), + $_return); + } + return $_return; +} + +?> diff --git a/libs/core/core.read_cache_file.php b/libs/core/core.read_cache_file.php index dade4c7c..e0b8f88a 100644 --- a/libs/core/core.read_cache_file.php +++ b/libs/core/core.read_cache_file.php @@ -81,7 +81,7 @@ function smarty_core_read_cache_file(&$params, &$this) if (isset($this->_cache_info['config'])) { require_once(SMARTY_DIR . 'core/core.fetch_file_info.php'); foreach (array_keys($this->_cache_info['config']) as $_config_dep) { - $_params = array('file_path' => $this->config_dir . '/' . $_config_dep); + $_params = array('file_path' => $_config_dep); smarty_core_fetch_file_info($_params, $this); if ($this->_cache_info['timestamp'] < $_params['file_timestamp']) { // config file has changed, regenerate cache @@ -91,6 +91,9 @@ function smarty_core_read_cache_file(&$params, &$this) } } + $this->_cache_serials = array_merge($this->_cache_serials, + $this->_cache_info['cache_serials']); + $params['results'] = $cache_split[1]; $content_cache[$params['tpl_file'].','.$params['cache_id'].','.$params['compile_id']] = array($params['results'], $this->_cache_info); diff --git a/libs/core/core.write_cache_file.php b/libs/core/core.write_cache_file.php index ea95dcac..6d6a75a5 100644 --- a/libs/core/core.write_cache_file.php +++ b/libs/core/core.write_cache_file.php @@ -20,7 +20,7 @@ function smarty_core_write_cache_file($params, &$this) { - + if(!@is_writable($this->cache_dir)) { // cache_dir not writable, see if it exists if(!@is_dir($this->cache_dir)) { @@ -41,6 +41,14 @@ function smarty_core_write_cache_file($params, &$this) $this->_cache_info['expires'] = -1; } + // collapse {nocache...}-tags + $params['results'] = preg_replace('!((\{nocache\:([0-9a-f]{32})#(\d+)\})' + .'.*' + .'{/nocache\:\\3#\\4\})!Us' + ,'\\2' + ,$params['results']); + $this->_cache_info['cache_serials'] = $this->_cache_serials; + // prepend the cache header info into cache file $params['results'] = serialize($this->_cache_info)."\n".$params['results']; @@ -54,7 +62,7 @@ function smarty_core_write_cache_file($params, &$this) $_cache_file = $this->_get_auto_filename($this->cache_dir, $params['tpl_file'], $_auto_id); $_params = array('filename' => $_cache_file, 'contents' => $params['results'], 'create_dirs' => true); require_once(SMARTY_DIR . 'core/core.write_file.php'); - smarty_core_write_file($_params, $this); + smarty_core_write_file($_params, $this); return true; } } diff --git a/libs/core/core.write_compiled_include.php b/libs/core/core.write_compiled_include.php new file mode 100644 index 00000000..b99acf77 --- /dev/null +++ b/libs/core/core.write_compiled_include.php @@ -0,0 +1,60 @@ +caching\) \{ echo \'\{nocache\:('.$this->_cache_serial.')#(\d+)\}\';\}'; + $_tag_end = 'if \(\$this->caching\) \{ echo \'\{/nocache\:(\\2)#(\\3)\}\';\}'; + + preg_match_all('!('.$_tag_start.'(.*)'.$_tag_end.')!Us', + $params['file_compiled'], $_match_source, PREG_SET_ORDER); + + // no nocache-parts found: done + if (count($_match_source)==0) return; + + // convert the matched php-code to functions + $_include_compiled = "_cache_serials[$_compile_path] = $this->_cache_serial; + $_include_compiled .= "\$this->_cache_serials['".$_compile_path."'] = '".$this->_cache_serial."';\n\n?>"; + + $_include_compiled .= $params['plugins_code']; + $_include_compiled .= "\n"; + + $_params = array('filename' => $_compile_path, + 'contents' => $_include_compiled, 'create_dirs' => true); + + require_once(SMARTY_DIR . 'core/core.write_file.php'); + smarty_core_write_file($_params, $this); + return true; +} + + +?>