diff options
Diffstat (limited to 'include/template.class.php')
-rw-r--r-- | include/template.class.php | 1275 |
1 files changed, 927 insertions, 348 deletions
diff --git a/include/template.class.php b/include/template.class.php index 63133384f..74a1eb36d 100644 --- a/include/template.class.php +++ b/include/template.class.php @@ -21,42 +21,74 @@ // | USA. | // +-----------------------------------------------------------------------+ +/** + * @package template + */ -class Template { +//require_once( PHPWG_ROOT_PATH .'include/smarty/libs/Smarty.class.php'); +require_once( PHPWG_ROOT_PATH .'include/smarty/libs/SmartyBC.class.php'); - var $smarty; +/** default rank for buttons */ +define('BUTTONS_RANK_NEUTRAL', 50); + +/** + * This a wrapper arround Smarty classes proving various custom mechanisms for templates. + */ +class Template +{ + /** @var Smarty */ + var $smarty; + /** @var string */ var $output = ''; - // Hash of filenames for each template handle. + /** @var string[] - Hash of filenames for each template handle. */ var $files = array(); - - // Template extents filenames for each template handle. + /** @var string[] - Template extents filenames for each template handle. */ var $extents = array(); - - // Templates prefilter from external sources (plugins) + /** @var array - Templates prefilter from external sources (plugins) */ var $external_filters = array(); - // used by html_head smarty block to add content before </head> + /** @var string - Content to add before </head> tag */ var $html_head_elements = array(); + /** @var string - Runtime CSS rules */ private $html_style = ''; + /** @const string */ const COMBINED_SCRIPTS_TAG = '<!-- COMBINED_SCRIPTS -->'; + /** @var ScriptLoader */ var $scriptLoader; + /** @const string */ const COMBINED_CSS_TAG = '<!-- COMBINED_CSS -->'; - var $css_by_priority = array(); - + /** @var CssLoader */ + var $cssLoader; + + /** @var array - Runtime buttons on picture page */ var $picture_buttons = array(); + /** @var array - Runtime buttons on index page */ var $index_buttons = array(); - function Template($root = ".", $theme= "", $path = "template") + + /** + * @var string $root + * @var string $theme + * @var string $path + */ + function __construct($root=".", $theme="", $path="template") { global $conf, $lang_info; + SmartyException::$escape = false; + $this->scriptLoader = new ScriptLoader; - $this->smarty = new Smarty; + $this->cssLoader = new CssLoader; + $this->smarty = new SmartyBC; $this->smarty->debugging = $conf['debug_template']; + if (!$this->smarty->debugging) + { + $this->smarty->error_reporting = error_reporting() & ~E_NOTICE; + } $this->smarty->compile_check = $conf['template_compile_check']; $this->smarty->force_compile = $conf['template_force_compile']; @@ -68,8 +100,8 @@ class Template { { load_language('admin.lang'); fatal_error( - sprintf( - l10n('Give write access (chmod 777) to "%s" directory at the root of your Piwigo installation'), + l10n( + 'Give write access (chmod 777) to "%s" directory at the root of your Piwigo installation', $conf['data_location'] ), l10n('an error happened'), @@ -84,27 +116,28 @@ class Template { $compile_dir = PHPWG_ROOT_PATH.$conf['data_location'].'templates_c'; mkgetdir( $compile_dir ); - $this->smarty->compile_dir = $compile_dir; - - $this->smarty->assign_by_ref( 'pwg', new PwgTemplateAdapter() ); - $this->smarty->register_modifier( 'translate', array('Template', 'mod_translate') ); - $this->smarty->register_modifier( 'explode', array('Template', 'mod_explode') ); - $this->smarty->register_modifier( 'get_extent', array(&$this, 'get_extent') ); - $this->smarty->register_block('html_head', array(&$this, 'block_html_head') ); - $this->smarty->register_block('html_style', array(&$this, 'block_html_style') ); - $this->smarty->register_function('combine_script', array(&$this, 'func_combine_script') ); - $this->smarty->register_function('get_combined_scripts', array(&$this, 'func_get_combined_scripts') ); - $this->smarty->register_function('combine_css', array(&$this, 'func_combine_css') ); - $this->smarty->register_function('define_derivative', array(&$this, 'func_define_derivative') ); - $this->smarty->register_compiler_function('get_combined_css', array(&$this, 'func_get_combined_css') ); - $this->smarty->register_block('footer_script', array(&$this, 'block_footer_script') ); - $this->smarty->register_prefilter( array('Template', 'prefilter_white_space') ); + $this->smarty->setCompileDir($compile_dir); + + $this->smarty->assign( 'pwg', new PwgTemplateAdapter() ); + $this->smarty->registerPlugin('modifiercompiler', 'translate', array('Template', 'modcompiler_translate') ); + $this->smarty->registerPlugin('modifiercompiler', 'translate_dec', array('Template', 'modcompiler_translate_dec') ); + $this->smarty->registerPlugin('modifier', 'explode', array('Template', 'mod_explode') ); + $this->smarty->registerPlugin('modifier', 'get_extent', array($this, 'get_extent') ); + $this->smarty->registerPlugin('block', 'html_head', array($this, 'block_html_head') ); + $this->smarty->registerPlugin('block', 'html_style', array($this, 'block_html_style') ); + $this->smarty->registerPlugin('function', 'combine_script', array($this, 'func_combine_script') ); + $this->smarty->registerPlugin('function', 'get_combined_scripts', array($this, 'func_get_combined_scripts') ); + $this->smarty->registerPlugin('function', 'combine_css', array($this, 'func_combine_css') ); + $this->smarty->registerPlugin('function', 'define_derivative', array($this, 'func_define_derivative') ); + $this->smarty->registerPlugin('compiler', 'get_combined_css', array($this, 'func_get_combined_css') ); + $this->smarty->registerPlugin('block', 'footer_script', array($this, 'block_footer_script') ); + $this->smarty->registerFilter('pre', array('Template', 'prefilter_white_space') ); if ( $conf['compiled_template_cache_language'] ) { - $this->smarty->register_prefilter( array('Template', 'prefilter_language') ); + $this->smarty->registerFilter('post', array('Template', 'postfilter_language') ); } - $this->smarty->template_dir = array(); + $this->smarty->setTemplateDir(array()); if ( !empty($theme) ) { $this->set_theme($root, $theme, $path); @@ -126,7 +159,13 @@ class Template { } /** - * Load theme's parameters. + * Loads theme's parameters. + * + * @param string $root + * @param string $theme + * @param string $path + * @param bool $load_css + * @param bool $load_local_head */ function set_theme($root, $theme, $path, $load_css=true, $load_local_head=true) { @@ -159,27 +198,31 @@ class Template { } /** - * Add template directory for this Template object. - * Set compile id if not exists. + * Adds template directory for this Template object. + * Also set compile id if not exists. + * + * @param string $dir */ function set_template_dir($dir) { - $this->smarty->template_dir[] = $dir; + $this->smarty->addTemplateDir($dir); if (!isset($this->smarty->compile_id)) { - $real_dir = realpath($dir); - $compile_id = crc32( $real_dir===false ? $dir : $real_dir); - $this->smarty->compile_id = base_convert($compile_id, 10, 36 ); + $compile_id = "1"; + $compile_id .= ($real_dir = realpath($dir))===false ? $dir : $real_dir; + $this->smarty->compile_id = base_convert(crc32($compile_id), 10, 36 ); } } /** * Gets the template root directory for this Template object. + * + * @return string */ function get_template_dir() { - return $this->smarty->template_dir; + return $this->smarty->getTemplateDir(); } /** @@ -189,19 +232,29 @@ class Template { { $save_compile_id = $this->smarty->compile_id; $this->smarty->compile_id = null; - $this->smarty->clear_compiled_tpl(); + $this->smarty->clearCompiledTemplate(); $this->smarty->compile_id = $save_compile_id; - file_put_contents($this->smarty->compile_dir.'/index.htm', 'Not allowed!'); + file_put_contents($this->smarty->getCompileDir().'/index.htm', 'Not allowed!'); } + /** + * Returns theme's parameter. + * + * @param string $val + * @return mixed + */ function get_themeconf($val) { - $tc = $this->smarty->get_template_vars('themeconf'); + $tc = $this->smarty->getTemplateVars('themeconf'); return isset($tc[$val]) ? $tc[$val] : ''; } /** * Sets the template filename for handle. + * + * @param string $handle + * @param string $filename + * @return bool */ function set_filename($handle, $filename) { @@ -209,8 +262,10 @@ class Template { } /** - * Sets the template filenames for handles. $filename_array should be a - * hash of handle => filename pairs. + * Sets the template filenames for handles. + * + * @param string[] $filename_array hashmap of handle=>filename + * @return true */ function set_filenames($filename_array) { @@ -235,6 +290,13 @@ class Template { /** * Sets template extention filename for handles. + * + * @param string $filename + * @param mixed $param + * @param string $dir + * @param bool $overwrite + * @param string $theme + * @return bool */ function set_extent($filename, $param, $dir='', $overwrite=true, $theme='N/A') { @@ -243,7 +305,12 @@ class Template { /** * Sets template extentions filenames for handles. - * $filename_array should be an hash of filename => array( handle, param) or filename => handle + * + * @param string[] $filename_array hashmap of handle=>filename + * @param string $dir + * @param bool $overwrite + * @param string $theme + * @return bool */ function set_extents($filename_array, $dir='', $overwrite=true, $theme='N/A') { @@ -281,7 +348,13 @@ class Template { return true; } - /** return template extension if exists */ + /** + * Returns template extension if exists. + * + * @param string $filename should be empty! + * @param string $handle + * @return string + */ function get_extent($filename='', $handle='') { if (isset($this->extents[$handle])) @@ -291,17 +364,27 @@ class Template { return $filename; } - /** see smarty assign http://www.smarty.net/manual/en/api.assign.php */ - function assign($tpl_var, $value = null) + /** + * Assigns a template variable. + * @see http://www.smarty.net/manual/en/api.assign.php + * + * @param string|array $tpl_var can be a var name or a hashmap of variables + * (in this case, do not use the _$value_ parameter) + * @param mixed $value + */ + function assign($tpl_var, $value=null) { $this->smarty->assign( $tpl_var, $value ); } /** - * Inserts the uncompiled code for $handle as the value of $varname in the - * root-level. This can be used to effectively include a template in the - * middle of another template. - * This is equivalent to assign($varname, $this->parse($handle, true)) + * Defines _$varname_ as the compiled result of _$handle_. + * This can be used to effectively include a template in another template. + * This is equivalent to assign($varname, $this->parse($handle, true)). + * + * @param string $varname + * @param string $handle + * @return true */ function assign_var_from_handle($varname, $handle) { @@ -309,46 +392,60 @@ class Template { return true; } - /** see smarty append http://www.smarty.net/manual/en/api.append.php */ + /** + * Appends a new value in a template array variable, the variable is created if needed. + * @see http://www.smarty.net/manual/en/api.append.php + * + * @param string $tpl_var + * @param mixed $value + * @param bool $merge + */ function append($tpl_var, $value=null, $merge=false) { $this->smarty->append( $tpl_var, $value, $merge ); } /** - * Root-level variable concatenation. Appends a string to an existing - * variable assignment with the same name. + * Performs a string concatenation. + * + * @param string $tpl_var + * @param string $value */ function concat($tpl_var, $value) { - $old_val = & $this->smarty->get_template_vars($tpl_var); - if ( isset($old_val) ) - { - $old_val .= $value; - } - else - { - $this->assign($tpl_var, $value); - } + $this->assign($tpl_var, + $this->smarty->getTemplateVars($tpl_var) . $value); } - /** see smarty append http://www.smarty.net/manual/en/api.clear_assign.php */ + /** + * Removes an assigned template variable. + * @see http://www.smarty.net/manual/en/api.clear_assign.php + * + * @param string $tpl_var + */ function clear_assign($tpl_var) { - $this->smarty->clear_assign( $tpl_var ); + $this->smarty->clearAssign( $tpl_var ); } - /** see smarty get_template_vars http://www.smarty.net/manual/en/api.get_template_vars.php */ - function &get_template_vars($name=null) + /** + * Returns an assigned template variable. + * @see http://www.smarty.net/manual/en/api.get_template_vars.php + * + * @param string $tpl_var + */ + function get_template_vars($tpl_var=null) { - return $this->smarty->get_template_vars( $name ); + return $this->smarty->getTemplateVars( $tpl_var ); } - /** - * Load the file for the handle, eventually compile the file and run the compiled - * code. This will add the output to the results or return the result if $return - * is true. + * Loads the template file of the handle, compiles it and appends the result to the output + * (or returns it if _$return_ is true). + * + * @param string $handle + * @param bool $return + * @return null|string */ function parse($handle, $return=false) { @@ -365,10 +462,10 @@ class Template { global $conf, $lang_info; if ( $conf['compiled_template_cache_language'] and isset($lang_info['code']) ) { - $this->smarty->compile_id .= '.'.$lang_info['code']; + $this->smarty->compile_id .= '_'.$lang_info['code']; } - $v = $this->smarty->fetch($this->files[$handle], null, null, false); + $v = $this->smarty->fetch($this->files[$handle]); $this->smarty->compile_id = $save_compile_id; $this->unload_external_filters($handle); @@ -381,8 +478,10 @@ class Template { } /** - * Load the file for the handle, eventually compile the file and run the compiled - * code. This will print out the results of executing the template. + * Loads the template file of the handle, compiles it and appends the result to the output, + * then sends the output to the browser. + * + * @param string $handle */ function pparse($handle) { @@ -390,6 +489,9 @@ class Template { $this->flush(); } + /** + * Load and compile JS & CSS into the template and sends the output to the browser. + */ function flush() { if (!$this->scriptLoader->did_head()) @@ -407,48 +509,26 @@ class Template { .'"></script>'; } - $this->output = substr_replace( $this->output, "\n".implode( "\n", $content ), $pos, strlen(self::COMBINED_SCRIPTS_TAG) ); + $this->output = substr_replace( $this->output, implode( "\n", $content ), $pos, strlen(self::COMBINED_SCRIPTS_TAG) ); } //else maybe error or warning ? } - if(!empty($this->css_by_priority)) - { - ksort($this->css_by_priority); - - global $conf; - $css = array(); - if ($conf['template_combine_files']) - { - $combiner = new FileCombiner('css'); - foreach ($this->css_by_priority as $files) - { - foreach ($files as $file_ver) - $combiner->add( $file_ver[0], $file_ver[1] ); - } - if ( $combiner->combine( $out_file, $out_version) ) - $css[] = array($out_file, $out_version); - } - else - { - foreach ($this->css_by_priority as $files) - $css = array_merge($css, $files); - } + $css = $this->cssLoader->get_css(); - $content = array(); - foreach( $css as $file_ver ) - { - $href = embellish_url(get_root_url().$file_ver[0]); - if ($file_ver[1] !== false) - $href .= '?v' . ($file_ver[1] ? $file_ver[1] : PHPWG_VERSION); - // trigger the event for eventual use of a cdn - $href = trigger_event('combined_css', $href, $file_ver[0], $file_ver[1]); - $content[] = '<link rel="stylesheet" type="text/css" href="'.$href.'">'; - } - $this->output = str_replace(self::COMBINED_CSS_TAG, - implode( "\n", $content ), - $this->output ); - $this->css_by_priority = array(); + $content = array(); + foreach( $css as $combi ) + { + $href = embellish_url(get_root_url().$combi->path); + if ($combi->version !== false) + $href .= '?v' . ($combi->version ? $combi->version : PHPWG_VERSION); + // trigger the event for eventual use of a cdn + $href = trigger_event('combined_css', $href, $combi); + $content[] = '<link rel="stylesheet" type="text/css" href="'.$href.'">'; } + $this->output = str_replace(self::COMBINED_CSS_TAG, + implode( "\n", $content ), + $this->output ); + $this->cssLoader->clear(); if ( count($this->html_head_elements) || strlen($this->html_style) ) { @@ -471,7 +551,10 @@ class Template { $this->output=''; } - /** flushes the output */ + /** + * Same as flush() but with optional debugging. + * @see Template::flush() + */ function p() { $this->flush(); @@ -484,23 +567,110 @@ class Template { 'AAAA_DEBUG_TOTAL_TIME__' => get_elapsed_time($t2, get_moment()) ) ); - require_once(SMARTY_CORE_DIR . 'core.display_debug_console.php'); - echo smarty_core_display_debug_console(null, $this->smarty); + Smarty_Internal_Debug::display_debug($this->smarty); } } /** - * translate variable modifier - translates a text to the currently loaded - * language + * Eval a temp string to retrieve the original PHP value. + * + * @param string $str + * @return mixed */ - static function mod_translate($text) + static function get_php_str_val($str) { - return l10n($text); + if (is_string($str) && strlen($str)>1) + { + if ( ($str[0]=='\'' && $str[strlen($str)-1]=='\'') + || ($str[0]=='"' && $str[strlen($str)-1]=='"')) + { + eval('$tmp='.$str.';'); + return $tmp; + } + } + return null; } /** - * explode variable modifier - similar to php explode - * 'Yes;No'|@explode:';' -> array('Yes', 'No') + * "translate" variable modifier. + * Usage : + * - {'Comment'|translate} + * - {'%d comments'|translate:$count} + * @see l10n() + * + * @param array $params + * @return string + */ + static function modcompiler_translate($params) + { + global $conf, $lang; + + switch (count($params)) + { + case 1: + if ($conf['compiled_template_cache_language'] + && ($key=self::get_php_str_val($params[0])) !== null + && isset($lang[$key]) + ) { + return var_export($lang[$key], true); + } + return 'l10n('.$params[0].')'; + + default: + if ($conf['compiled_template_cache_language']) + { + $ret = 'sprintf('; + $ret .= self::modcompiler_translate( array($params[0]) ); + $ret .= ','. implode(',', array_slice($params, 1)); + $ret .= ')'; + return $ret; + } + return 'l10n('.$params[0].','.implode(',', array_slice($params, 1)).')'; + } + } + + /** + * "translate_dec" variable modifier. + * Usage : + * - {$count|translate_dec:'%d comment':'%d comments'} + * @see l10n_dec() + * + * @param array $params + * @return string + */ + static function modcompiler_translate_dec($params) + { + global $conf, $lang, $lang_info; + if ($conf['compiled_template_cache_language']) + { + $ret = 'sprintf('; + if ($lang_info['zero_plural']) + { + $ret .= '($tmp=('.$params[0].'))>1||$tmp==0'; + } + else + { + $ret .= '($tmp=('.$params[0].'))>1'; + } + $ret .= '?'; + $ret .= self::modcompiler_translate( array($params[2]) ); + $ret .= ':'; + $ret .= self::modcompiler_translate( array($params[1]) ); + $ret .= ',$tmp'; + $ret .= ')'; + return $ret; + } + return 'l10n_dec('.$params[1].','.$params[2].','.$params[0].')'; + } + + /** + * "explode" variable modifier. + * Usage : + * - {assign var=valueExploded value=$value|@explode:','} + * + * @param string $text + * @param string $delimiter + * @return array */ static function mod_explode($text, $delimiter=',') { @@ -508,10 +678,11 @@ class Template { } /** - * This smarty "html_head" block allows to add content just before - * </head> element in the output after the head has been parsed. This is - * handy in order to respect strict standards when <style> and <link> - * html elements must appear in the <head> element + * The "html_head" block allows to add content just before + * </head> element in the output after the head has been parsed. + * + * @param array $params (unused) + * @param string $content */ function block_html_head($params, $content) { @@ -522,6 +693,13 @@ class Template { } } + /** + * The "html_style" block allows to add CSS juste before + * </head> element in the output after the head has been parsed. + * + * @param array $params (unused) + * @param string $content + */ function block_html_style($params, $content) { $content = trim($content); @@ -531,13 +709,27 @@ class Template { } } - function func_define_derivative($params) + /** + * The "define_derivative" function allows to define derivative from tpl file. + * It assigns a DerivativeParams object to _name_ template variable. + * + * @param array $params + * - name (required) + * - type (optional) + * - width (required if type is empty) + * - height (required if type is empty) + * - crop (optional, used if type is empty) + * - min_height (optional, used with crop) + * - min_height (optional, used with crop) + * @param Smarty $smarty + */ + function func_define_derivative($params, $smarty) { !empty($params['name']) or fatal_error('define_derivative missing name'); if (isset($params['type'])) { $derivative = ImageStdParams::get_by_type($params['type']); - $this->smarty->assign( $params['name'], $derivative); + $smarty->assign( $params['name'], $derivative); return; } !empty($params['width']) or fatal_error('define_derivative missing width'); @@ -569,26 +761,26 @@ class Template { } } - $this->smarty->assign( $params['name'], ImageStdParams::get_custom($w, $h, $crop, $minw, $minh) ); + $smarty->assign( $params['name'], ImageStdParams::get_custom($w, $h, $crop, $minw, $minh) ); } - /** - * combine_script smarty function allows inclusion of a javascript file in the current page. - * The engine will combine several js files into a single one in order to reduce the number of - * required http requests. - * param id - required - * param path - required - the path to js file RELATIVE to piwigo root dir - * param load - optional - header|footer|async, default header - * param require - optional - comma separated list of script ids required to be loaded and executed - before this one - * param version - optional - plugins could use this and change it in order to force a - browser refresh - */ + /** + * The "combine_script" functions allows inclusion of a javascript file in the current page. + * The engine will combine several js files into a single one. + * + * @param array $params + * - id (required) + * - path (required) + * - load (optional) 'header', 'footer' or 'async' + * - require (optional) comma separated list of script ids required to be loaded + * and executed before this one + * - version (optional) used to force a browser refresh + */ function func_combine_script($params) { if (!isset($params['id'])) { - $this->smarty->trigger_error("combine_script: missing 'id' parameter", E_USER_ERROR); + trigger_error("combine_script: missing 'id' parameter", E_USER_ERROR); } $load = 0; if (isset($params['load'])) @@ -598,29 +790,29 @@ class Template { case 'header': break; case 'footer': $load=1; break; case 'async': $load=2; break; - default: $this->smarty->trigger_error("combine_script: invalid 'load' parameter", E_USER_ERROR); + default: trigger_error("combine_script: invalid 'load' parameter", E_USER_ERROR); } } - // TEMP in 2.5 for backward compatibility - if(!empty($params['require'])) - { - $params['require'] = str_replace('jquery.effects.', 'jquery.ui.effect-', $params['require'] ); - $params['require'] = str_replace('jquery.effects', 'jquery.ui.effect', $params['require'] ); - } - $this->scriptLoader->add( $params['id'], $load, empty($params['require']) ? array() : explode( ',', $params['require'] ), @$params['path'], - isset($params['version']) ? $params['version'] : 0 ); + isset($params['version']) ? $params['version'] : 0, + @$params['template']); } - + /** + * The "get_combined_scripts" function returns HTML tag of combined scripts. + * It can returns a placeholder for delayed JS files combination and minification. + * + * @param array $params + * - load (required) + */ function func_get_combined_scripts($params) { if (!isset($params['load'])) { - $this->smarty->trigger_error("get_combined_scripts: missing 'load' parameter", E_USER_ERROR); + trigger_error("get_combined_scripts: missing 'load' parameter", E_USER_ERROR); } $load = $params['load']=='header' ? 0 : 1; $content = array(); @@ -667,8 +859,13 @@ var s,after = document.getElementsByTagName(\'script\')[document.getElementsByTa return implode("\n", $content); } - - private static function make_script_src( $script ) + /** + * Returns clean relative URL to script file. + * + * @param Combinable $script + * @return string + */ + private static function make_script_src($script) { $ret = ''; if ( $script->is_remote() ) @@ -686,19 +883,19 @@ var s,after = document.getElementsByTagName(\'script\')[document.getElementsByTa return embellish_url($ret); } + /** + * The "footer_script" block allows to add runtime script in the HTML page. + * + * @param array $params + * - require (optional) comma separated list of script ids + * @param string $content + */ function block_footer_script($params, $content) { $content = trim($content); if ( !empty($content) ) { // second call - // TEMP in 2.5 for backward compatibility - if(!empty($params['require'])) - { - $params['require'] = str_replace('jquery.effects.', 'jquery.ui.effect-', $params['require'] ); - $params['require'] = str_replace('jquery.effects', 'jquery.ui.effect', $params['require'] ); - } - $this->scriptLoader->add_inline( $content, empty($params['require']) ? array() : explode(',', $params['require']) @@ -707,55 +904,92 @@ var s,after = document.getElementsByTagName(\'script\')[document.getElementsByTa } /** - * combine_css smarty function allows inclusion of a css stylesheet file in the current page. - * The engine will combine several css files into a single one in order to reduce the number of - * required http requests. - * param path - required - the path to css file RELATIVE to piwigo root dir - * param version - optional - plugins could use this and change it in order to force a - browser refresh - */ + * The "combine_css" function allows inclusion of a css file in the current page. + * The engine will combine several css files into a single one. + * + * @param array $params + * - id (optional) used to deal with multiple inclusions from plugins + * - path (required) + * - version (optional) used to force a browser refresh + * - order (optional) + * - template (optional) set to true to allow smarty syntax in the css file + */ function func_combine_css($params) { - !empty($params['path']) || fatal_error('combine_css missing path'); - $order = (int)@$params['order']; - $version = isset($params['version']) ? $params['version'] : 0; - $this->css_by_priority[$order][] = array( $params['path'], $version); + if (empty($params['path'])) + { + fatal_error('combine_css missing path'); + } + + if (!isset($params['id'])) + { + $params['id'] = md5($params['path']); + } + + $this->cssLoader->add($params['id'], $params['path'], isset($params['version']) ? $params['version'] : 0, (int)@$params['order'], (bool)@$params['template']); } + /** + * The "get_combined_scripts" function returns a placeholder for delayed + * CSS files combination and minification. + * + * @param array $params (unused) + */ function func_get_combined_css($params) { - return 'echo '.var_export(self::COMBINED_CSS_TAG,true); + return self::COMBINED_CSS_TAG; } - - /** - * This function allows to declare a Smarty prefilter from a plugin, thus allowing - * it to modify template source before compilation and without changing core files + /** + * Declares a Smarty prefilter from a plugin, allowing it to modify template + * source before compilation and without changing core files. * They will be processed by weight ascending. - * http://www.smarty.net/manual/en/advanced.features.prefilters.php + * @see http://www.smarty.net/manual/en/advanced.features.prefilters.php + * + * @param string $handle + * @param Callable $callback + * @param int $weight */ function set_prefilter($handle, $callback, $weight=50) { - $this->external_filters[$handle][$weight][] = array('prefilter', $callback); + $this->external_filters[$handle][$weight][] = array('pre', $callback); ksort($this->external_filters[$handle]); } + /** + * Declares a Smarty postfilter. + * They will be processed by weight ascending. + * @see http://www.smarty.net/manual/en/advanced.features.postfilters.php + * + * @param string $handle + * @param Callable $callback + * @param int $weight + */ function set_postfilter($handle, $callback, $weight=50) { - $this->external_filters[$handle][$weight][] = array('postfilter', $callback); + $this->external_filters[$handle][$weight][] = array('post', $callback); ksort($this->external_filters[$handle]); } + /** + * Declares a Smarty outputfilter. + * They will be processed by weight ascending. + * @see http://www.smarty.net/manual/en/advanced.features.outputfilters.php + * + * @param string $handle + * @param Callable $callback + * @param int $weight + */ function set_outputfilter($handle, $callback, $weight=50) { - $this->external_filters[$handle][$weight][] = array('outputfilter', $callback); + $this->external_filters[$handle][$weight][] = array('output', $callback); ksort($this->external_filters[$handle]); } - /** - * This function actually triggers the filters on the tpl files. - * Called in the parse method. - * http://www.smarty.net/manual/en/advanced.features.prefilters.php + /** + * Register the filters for the tpl file. + * + * @param string $handle */ function load_external_filters($handle) { @@ -768,13 +1002,18 @@ var s,after = document.getElementsByTagName(\'script\')[document.getElementsByTa { list($type, $callback) = $filter; $compile_id .= $type.( is_array($callback) ? implode('', $callback) : $callback ); - call_user_func(array($this->smarty, 'register_'.$type), $callback); + $this->smarty->registerFilter($type, $callback); } } $this->smarty->compile_id .= '.'.base_convert(crc32($compile_id), 10, 36); } } + /** + * Unregister the filters for the tpl file. + * + * @param string $handle + */ function unload_external_filters($handle) { if (isset($this->external_filters[$handle])) @@ -784,13 +1023,20 @@ var s,after = document.getElementsByTagName(\'script\')[document.getElementsByTa foreach ($filters as $filter) { list($type, $callback) = $filter; - call_user_func(array($this->smarty, 'unregister_'.$type), $callback); + $this->smarty->unregisterFilter($type, $callback); } } } } - static function prefilter_white_space($source, &$smarty) + /** + * @toto : description of Template::prefilter_white_space + * + * @param string $source + * @param Smarty $smarty + * @param return string + */ + static function prefilter_white_space($source, $smarty) { $ld = $smarty->left_delimiter; $rd = $smarty->right_delimiter; @@ -801,55 +1047,57 @@ var s,after = document.getElementsByTagName(\'script\')[document.getElementsByTa $tags = array('if','foreach','section','footer_script'); foreach($tags as $tag) { - array_push($regex, "#^[ \t]+($ldq$tag"."[^$ld$rd]*$rdq)\s*$#m"); - array_push($regex, "#^[ \t]+($ldq/$tag$rdq)\s*$#m"); + $regex[] = "#^[ \t]+($ldq$tag"."[^$ld$rd]*$rdq)\s*$#m"; + $regex[] = "#^[ \t]+($ldq/$tag$rdq)\s*$#m"; } $tags = array('include','else','combine_script','html_head'); foreach($tags as $tag) { - array_push($regex, "#^[ \t]+($ldq$tag"."[^$ld$rd]*$rdq)\s*$#m"); + $regex[] = "#^[ \t]+($ldq$tag"."[^$ld$rd]*$rdq)\s*$#m"; } $source = preg_replace( $regex, "$1", $source); return $source; } /** - * Smarty prefilter to allow caching (whenever possible) language strings - * from templates. + * Postfilter used when $conf['compiled_template_cache_language'] is true. + * + * @param string $source + * @param Smarty $smarty + * @param return string */ - static function prefilter_language($source, &$smarty) + static function postfilter_language($source, $smarty) { - global $lang; - $ldq = preg_quote($smarty->left_delimiter, '~'); - $rdq = preg_quote($smarty->right_delimiter, '~'); - - $regex = "~$ldq *\'([^'$]+)\'\|@translate *$rdq~"; - $source = preg_replace_callback( $regex, create_function('$m', 'global $lang; return isset($lang[$m[1]]) ? $lang[$m[1]] : $m[0];'), $source); - - $regex = "~$ldq *\'([^'$]+)\'\|@translate\|~"; - $source = preg_replace_callback( $regex, create_function('$m', 'global $lang; return isset($lang[$m[1]]) ? \'{\'.var_export($lang[$m[1]],true).\'|\' : $m[0];'), $source); - - $regex = "~($ldq *assign +var=.+ +value=)\'([^'$]+)\'\|@translate~"; - $source = preg_replace_callback( $regex, create_function('$m', 'global $lang; return isset($lang[$m[2]]) ? $m[1].var_export($lang[$m[2]],true) : $m[0];'), $source); - + // replaces echo PHP_STRING_LITERAL; with the string literal value + $source = preg_replace_callback( + '/\\<\\?php echo ((?:\'(?:(?:\\\\.)|[^\'])*\')|(?:"(?:(?:\\\\.)|[^"])*"));\\?\\>\\n/', + create_function('$matches', 'eval(\'$tmp=\'.$matches[1].\';\');return $tmp;'), + $source); return $source; } - static function prefilter_local_css($source, &$smarty) + /** + * Prefilter used to add theme local CSS files. + * + * @param string $source + * @param Smarty $smarty + * @param return string + */ + static function prefilter_local_css($source, $smarty) { $css = array(); - foreach ($smarty->get_template_vars('themes') as $theme) + foreach ($smarty->getTemplateVars('themes') as $theme) { $f = PWG_LOCAL_DIR.'css/'.$theme['id'].'-rules.css'; if (file_exists(PHPWG_ROOT_PATH.$f)) { - array_push($css, "{combine_css path='$f' order=10}"); + $css[] = "{combine_css path='$f' order=10}"; } } $f = PWG_LOCAL_DIR.'css/rules.css'; if (file_exists(PHPWG_ROOT_PATH.$f)) { - array_push($css, "{combine_css path='$f' order=10}"); + $css[] = "{combine_css path='$f' order=10}"; } if (!empty($css)) @@ -860,6 +1108,12 @@ var s,after = document.getElementsByTagName(\'script\')[document.getElementsByTa return $source; } + /** + * Loads the configuration file from a theme directory and returns it. + * + * @param string $dir + * @return array + */ function load_themeconf($dir) { global $themeconfs, $conf; @@ -874,39 +1128,62 @@ var s,after = document.getElementsByTagName(\'script\')[document.getElementsByTa } return $themeconfs[$dir]; } - - function add_picture_button($content, $rank) + + /** + * Registers a button to be displayed on picture page. + * + * @param string $content + * @param int $rank + */ + function add_picture_button($content, $rank=BUTTONS_RANK_NEUTRAL) { $this->picture_buttons[$rank][] = $content; } - - function add_index_button($content, $rank) + + /** + * Registers a button to be displayed on index pages. + * + * @param string $content + * @param int $rank + */ + function add_index_button($content, $rank=BUTTONS_RANK_NEUTRAL) { $this->index_buttons[$rank][] = $content; } - + + /** + * Assigns PLUGIN_PICTURE_BUTTONS template variable with registered picture buttons. + */ function parse_picture_buttons() { if (!empty($this->picture_buttons)) { ksort($this->picture_buttons); - foreach ($this->picture_buttons as $ranked) - foreach ($ranked as $content) - $this->concat('PLUGIN_PICTURE_ACTIONS', $content); + $this->assign('PLUGIN_PICTURE_BUTTONS', + array_reduce( + $this->picture_buttons, + create_function('$v,$w', 'return array_merge($v, $w);'), + array() + )); } } - + + /** + * Assigns PLUGIN_INDEX_BUTTONS template variable with registered index buttons. + */ function parse_index_buttons() { if (!empty($this->index_buttons)) { ksort($this->index_buttons); - foreach ($this->index_buttons as $ranked) - foreach ($ranked as $content) - $this->concat('PLUGIN_INDEX_ACTIONS', $content); + $this->assign('PLUGIN_INDEX_BUTTONS', + array_reduce( + $this->index_buttons, + create_function('$v,$w', 'return array_merge($v, $w);'), + array() + )); } } - } @@ -916,27 +1193,46 @@ var s,after = document.getElementsByTagName(\'script\')[document.getElementsByTa */ class PwgTemplateAdapter { + /** + * @deprecated use "translate" modifier + */ function l10n($text) { return l10n($text); } + /** + * @deprecated use "translate_dec" modifier + */ function l10n_dec($s, $p, $v) { return l10n_dec($s, $p, $v); } + /** + * @deprecated use "translate" or "sprintf" modifier + */ function sprintf() { $args = func_get_args(); return call_user_func_array('sprintf', $args ); } + /** + * @param string $type + * @param array $img + * @return DerivativeImage + */ function derivative($type, $img) { return new DerivativeImage($type, $img); } + /** + * @param string $type + * @param array $img + * @return string + */ function derivative_url($type, $img) { return DerivativeImage::url($type, $img); @@ -944,47 +1240,189 @@ class PwgTemplateAdapter } -final class Script +/** + * A Combinable represents a JS or CSS file ready for cobination and minification. + */ +class Combinable { + /** @var string */ public $id; - public $load_mode; - public $precedents = array(); + /** @var string */ public $path; + /** @var string */ public $version; - public $extra = array(); + /** @var bool */ + public $is_template; - function Script($load_mode, $id, $path, $version, $precedents) + /** + * @param string $id + * @param string $path + * @param string $version + */ + function __construct($id, $path, $version=0) { $this->id = $id; - $this->load_mode = $load_mode; - $this->id = $id; $this->set_path($path); $this->version = $version; - $this->precedents = $precedents; + $this->is_template = false; } + /** + * @param string $path + */ function set_path($path) { if (!empty($path)) $this->path = $path; } + /** + * @return bool + */ function is_remote() { - return url_is_remote( $this->path ); + return url_is_remote($this->path) || strncmp($this->path, '//', 2)==0; } } +/** + * Implementation of Combinable for JS files. + */ +final class Script extends Combinable +{ + /** @var int 0,1,2 */ + public $load_mode; + /** @var array */ + public $precedents; + /** @var array */ + public $extra; + + /** + * @param int 0,1,2 + * @param string $id + * @param string $path + * @param string $version + * @param array $precedents + */ + function __construct($load_mode, $id, $path, $version=0, $precedents=array()) + { + parent::__construct($id, $path, $version); + $this->load_mode = $load_mode; + $this->precedents = $precedents; + $this->extra = array(); + } +} + +/** + * Implementation of Combinable for CSS files. + */ +final class Css extends Combinable +{ + /** @var int */ + public $order; + + /** + * @param string $id + * @param string $path + * @param string $version + * @param int $order + */ + function __construct($id, $path, $version=0, $order=0) + { + parent::__construct($id, $path, $version); + $this->order = $order; + } +} + + +/** + * Manages a list of CSS files and combining them in a unique file. + */ +class CssLoader +{ + /** @param Css[] */ + private $registered_css; + /** @param int used to keep declaration order */ + private $counter; + + function __construct() + { + $this->clear(); + } + + function clear() + { + $this->registered_css = array(); + $this->counter = 0; + } + + /** + * @return Combinable[] array of combined CSS. + */ + function get_css() + { + uasort($this->registered_css, array('CssLoader', 'cmp_by_order')); + $combiner = new FileCombiner('css', $this->registered_css); + return $combiner->combine(); + } + + /** + * Callback for CSS files sorting. + */ + private static function cmp_by_order($a, $b) + { + return $a->order - $b->order; + } + + /** + * Adds a new file, if a file with the same $id already exsists, the one with + * the higher $order or higher $version is kept. + * + * @param string $id + * @param string $path + * @param string $version + * @param int $order + * @param bool $is_template + */ + function add($id, $path, $version=0, $order=0, $is_template=false) + { + if (!isset($this->registered_css[$id])) + { + // costum order as an higher impact than declaration order + $css = new Css($id, $path, $version, $order*1000+$this->counter); + $css->is_template = $is_template; + $this->registered_css[$id] = $css; + $this->counter++; + } + else + { + $css = $this->registered_css[$id]; + if ($css->order<$order*1000 || version_compare($css->version, $version)<0) + { + unset($this->registered_css[$id]); + $this->add($id, $path, $version, $order, $is_template); + } + } + } +} -/** Manage a list of required scripts for a page, by optimizing their loading location (head, bottom, async) -and later on by combining them in a unique file respecting at the same time dependencies.*/ + +/** + * Manage a list of required scripts for a page, by optimizing their loading location (head, footer, async) + * and later on by combining them in a unique file respecting at the same time dependencies. + */ class ScriptLoader { + /** @var Script[] */ private $registered_scripts; + /** @var string[] */ public $inline_scripts; + /** @var bool */ private $did_head; + /** @var bool */ private $head_done_scripts; + /** @var bool */ private $did_footer; private static $known_paths = array( @@ -1013,11 +1451,26 @@ class ScriptLoader $this->did_head = $this->did_footer = false; } + /** + * @return bool + */ + function did_head() + { + return $this->did_head; + } + + /** + * @return Script[] + */ function get_all() { return $this->registered_scripts; } + /** + * @param string $code + * @param string[] $require + */ function add_inline($code, $require) { !$this->did_footer || trigger_error("Attempt to add inline script but the footer has been written", E_USER_WARNING); @@ -1035,7 +1488,14 @@ class ScriptLoader $this->inline_scripts[] = $code; } - function add($id, $load_mode, $require, $path, $version=0) + /** + * @param string $id + * @param int $load_mode + * @param string[] $require + * @param string $path + * @param string $version + */ + function add($id, $load_mode, $require, $path, $version=0, $is_template=false) { if ($this->did_head && $load_mode==0) { @@ -1048,6 +1508,7 @@ class ScriptLoader if (! isset( $this->registered_scripts[$id] ) ) { $script = new Script($load_mode, $id, $path, $version, $require); + $script->is_template = $is_template; self::fill_well_known($id, $script); $this->registered_scripts[$id] = $script; @@ -1078,14 +1539,13 @@ class ScriptLoader if ($load_mode < $script->load_mode) $script->load_mode = $load_mode; } - - } - - function did_head() - { - return $this->did_head; } + /** + * Returns combined scripts loaded in header. + * + * @return Combinable[] + */ function get_head_scripts() { self::check_load_dep($this->registered_scripts); @@ -1109,6 +1569,11 @@ class ScriptLoader return self::do_combine($this->head_done_scripts, 0); } + /** + * Returns combined scripts loaded in footer. + * + * @return Combinable[] + */ function get_footer_scripts() { if (!$this->did_head) @@ -1140,34 +1605,23 @@ class ScriptLoader return array( self::do_combine($result[0],1), self::do_combine($result[1],2) ); } + /** + * @param Script[] $scripts + * @param int $load_mode + * @return Combinable[] + */ private static function do_combine($scripts, $load_mode) { - global $conf; - if (count($scripts)<2 or !$conf['template_combine_files']) - return $scripts; - $combiner = new FileCombiner('js'); - $result = array(); - foreach ($scripts as $script) - { - if ($script->is_remote()) - { - if ( $combiner->combine( $out_file, $out_version) ) - { - $results[] = new Script($load_mode, 'combi', $out_file, $out_version, array() ); - } - $results[] = $script; - } - else - $combiner->add( $script->path, $script->version ); - } - if ( $combiner->combine( $out_file, $out_version) ) - { - $results[] = new Script($load_mode, 'combi', $out_file, $out_version, array() ); - } - return $results; + $combiner = new FileCombiner('js', $scripts); + return $combiner->combine(); } - // checks that if B depends on A, then B->load_mode >= A->load_mode in order to respect execution order + /** + * Checks dependencies among Scripts. + * Checks that if B depends on A, then B->load_mode >= A->load_mode in order to respect execution order. + * + * @param Script[] $scripts + */ private static function check_load_dep($scripts) { global $conf; @@ -1197,7 +1651,12 @@ class ScriptLoader while ($changed); } - + /** + * Fill a script dependancies with the known jQuery UI scripts. + * + * @param string $id in FileCombiner::$known_paths + * @param Script $script + */ private static function fill_well_known($id, $script) { if ( empty($script->path) && isset(self::$known_paths[$id])) @@ -1232,6 +1691,13 @@ class ScriptLoader } } + /** + * Add a known jQuery UI script to loaded scripts. + * + * @param string $id in FileCombiner::$known_paths + * @param int $load_mode + * @return bool + */ private function load_known_required_script($id, $load_mode) { if ( isset(self::$known_paths[$id]) or strncmp($id, 'jquery.ui.', 10)==0 ) @@ -1242,6 +1708,14 @@ class ScriptLoader return false; } + /** + * Compute script order depending on dependencies. + * Assigned to $script->extra['order']. + * + * @param string $script_id + * @param int $recursion_limiter + * @return int + */ private function compute_script_topological_order($script_id, $recursion_limiter=0) { if (!isset($this->registered_scripts[$script_id])) @@ -1262,6 +1736,9 @@ class ScriptLoader return ($script->extra['order'] = $max); } + /** + * Callback for scripts sorter. + */ private static function cmp_by_mode_and_order($s1, $s2) { $ret = $s1->load_mode - $s2->load_mode; @@ -1279,18 +1756,32 @@ class ScriptLoader } -/*Allows merging of javascript and css files into a single one.*/ +/** + * Allows merging of javascript and css files into a single one. + */ final class FileCombiner { - private $type; // js or css - private $files = array(); - private $versions = array(); + /** @var string 'js' or 'css' */ + private $type; + /** @var bool */ + private $is_css; + /** @var Combinable[] */ + private $combinables; - function FileCombiner($type) + /** + * @param string $type 'js' or 'css' + * @param Combinable[] $combinables + */ + function __construct($type, $combinables=array()) { $this->type = $type; + $this->is_css = $type=='css'; + $this->combinables = $combinables; } + /** + * Deletes all combined files from cache directory. + */ static function clear_combined_files() { $dir = opendir(PHPWG_ROOT_PATH.PWG_COMBINED_DIR); @@ -1302,92 +1793,165 @@ final class FileCombiner closedir($dir); } - function add($file, $version) - { - $this->files[] = $file; - $this->versions[] = $version; - } - - function clear() - { - $this->files = array(); - $this->versions = array(); - } - - function combine(&$out_file, &$out_version) + /** + * @param Combinable|Combinable[] $combinable + */ + function add($combinable) { - if (count($this->files) == 0) + if (is_array($combinable)) { - return false; + $this->combinables = array_merge($this->combinables, $combinable); } - if (count($this->files) == 1) + else { - $out_file = $this->files[0]; - $out_version = $this->versions[0]; - $this->clear(); - return 1; + $this->combinables[] = $combinable; } + } - $is_css = $this->type == "css"; + /** + * @return Combinable[] + */ + function combine() + { global $conf; - $key = array(); - if ($is_css) - $key[] = get_absolute_root_url(false);//because we modify bg url - for ($i=0; $i<count($this->files); $i++) + $force = false; + if (is_admin() && ($this->is_css || !$conf['template_compile_check']) ) { - $key[] = $this->files[$i]; - $key[] = $this->versions[$i]; - if ($conf['template_compile_check']) $key[] = filemtime( PHPWG_ROOT_PATH . $this->files[$i] ); + $force = (isset($_SERVER['HTTP_CACHE_CONTROL']) && strpos($_SERVER['HTTP_CACHE_CONTROL'], 'max-age=0') !== false) + || (isset($_SERVER['HTTP_PRAGMA']) && strpos($_SERVER['HTTP_PRAGMA'], 'no-cache')); } - $key = join('>', $key); - $file = base_convert(crc32($key),10,36); - $file = PWG_COMBINED_DIR . $file . '.' . $this->type; + $result = array(); + $pending = array(); + $ini_key = $this->is_css ? array(get_absolute_root_url(false)): array(); //because for css we modify bg url; + $key = $ini_key; - $exists = file_exists( PHPWG_ROOT_PATH . $file ); - if ($exists) + foreach ($this->combinables as $combinable) { - $is_reload = - (isset($_SERVER['HTTP_CACHE_CONTROL']) && strpos($_SERVER['HTTP_CACHE_CONTROL'], 'max-age=0') !== false) - || (isset($_SERVER['HTTP_PRAGMA']) && strpos($_SERVER['HTTP_PRAGMA'], 'no-cache')); - if (is_admin() && $is_reload) - {// the user pressed F5 in the browser - if ($is_css || $conf['template_compile_check']==false) - $exists = false; // we foce regeneration of css because @import sub-files are never checked for modification + if ($combinable->is_remote()) + { + $this->flush_pending($result, $pending, $key, $force); + $key = $ini_key; + $result[] = $combinable; + continue; + } + elseif (!$conf['template_combine_files']) + { + $this->flush_pending($result, $pending, $key, $force); + $key = $ini_key; } + + $key[] = $combinable->path; + $key[] = $combinable->version; + if ($conf['template_compile_check']) + $key[] = filemtime( PHPWG_ROOT_PATH . $combinable->path ); + $pending[] = $combinable; } + $this->flush_pending($result, $pending, $key, $force); + return $result; + } - if ($exists) + /** + * Process a set of pending files. + * + * @param array &$result + * @param array &$pending + * @param string[] $key + * @param bool $force + */ + private function flush_pending(&$result, &$pending, $key, $force) + { + if (count($pending)>1) { - $out_file = $file; - $out_version = false; - $this->clear(); - return 2; + $key = join('>', $key); + $file = PWG_COMBINED_DIR . base_convert(crc32($key),10,36) . '.' . $this->type; + if ($force || !file_exists(PHPWG_ROOT_PATH.$file) ) + { + $output = ''; + foreach ($pending as $combinable) + { + $output .= "/*BEGIN $combinable->path */\n"; + $output .= $this->process_combinable($combinable, true, $force); + $output .= "\n"; + } + mkgetdir( dirname(PHPWG_ROOT_PATH.$file) ); + file_put_contents( PHPWG_ROOT_PATH.$file, $output ); + @chmod(PHPWG_ROOT_PATH.$file, 0644); + } + $result[] = new Combinable("combi", $file, false); + } + elseif ( count($pending)==1) + { + $this->process_combinable($pending[0], false, $force); + $result[] = $pending[0]; } + $key = array(); + $pending = array(); + } + + /** + * Process one combinable file. + * + * @param Combinable $combinable + * @param bool $return_content + * @param bool $force + * @return null|string + */ + private function process_combinable($combinable, $return_content, $force) + { + global $conf; + if ($combinable->is_template) + { + if (!$return_content) + { + $key = array($combinable->path, $combinable->version); + if ($conf['template_compile_check']) + $key[] = filemtime( PHPWG_ROOT_PATH . $combinable->path ); + $file = PWG_COMBINED_DIR . 't' . base_convert(crc32(implode(',',$key)),10,36) . '.' . $this->type; + if (!$force && file_exists(PHPWG_ROOT_PATH.$file) ) + { + $combinable->path = $file; + $combinable->version = false; + return; + } + } + + global $template; + $handle = $this->type. '.' .$combinable->id; + $template->set_filename($handle, realpath(PHPWG_ROOT_PATH.$combinable->path)); + trigger_action( 'combinable_preparse', $template, $combinable, $this); //allow themes and plugins to set their own vars to template ... + $content = $template->parse($handle, true); + + if ($this->is_css) + $content = self::process_css($content, $combinable->path ); + else + $content = self::process_js($content, $combinable->path ); - $output = ''; - foreach ($this->files as $input_file) + if ($return_content) + return $content; + file_put_contents( PHPWG_ROOT_PATH.$file, $content ); + $combinable->path = $file; + } + elseif ($return_content) { - $output .= "/*BEGIN $input_file */\n"; - if ($is_css) - $output .= self::process_css($input_file); + $content = file_get_contents(PHPWG_ROOT_PATH . $combinable->path); + if ($this->is_css) + $content = self::process_css($content, $combinable->path ); else - $output .= self::process_js($input_file); - $output .= "\n"; + $content = self::process_js($content, $combinable->path ); + return $content; } - - mkgetdir( dirname(PHPWG_ROOT_PATH.$file) ); - file_put_contents( PHPWG_ROOT_PATH.$file, $output ); - @chmod(PHPWG_ROOT_PATH.$file, 0644); - $out_file = $file; - $out_version = false; - $this->clear(); - return 2; } - private static function process_js($file) + /** + * Process a JS file. + * + * @param string $js file content + * @param string $file + * @return string + */ + private static function process_js($js, $file) { - $js = file_get_contents(PHPWG_ROOT_PATH . $file); if (strpos($file, '.min')===false and strpos($file, '.packed')===false ) { require_once(PHPWG_ROOT_PATH.'include/jshrink.class.php'); @@ -1396,10 +1960,17 @@ final class FileCombiner return trim($js, " \t\r\n;").";\n"; } - private static function process_css($file) + /** + * Process a CSS file. + * + * @param string $css file content + * @param string $file + * @return string + */ + private static function process_css($css, $file) { - $css = self::process_css_rec($file); - if (version_compare(PHP_VERSION, '5.2.4', '>=')) + $css = self::process_css_rec($css, dirname($file)); + if (strpos($file, '.min')===false and version_compare(PHP_VERSION, '5.2.4', '>=')) { require_once(PHPWG_ROOT_PATH.'include/cssmin.class.php'); $css = CssMin::minify($css, array('Variables'=>false)); @@ -1408,18 +1979,26 @@ final class FileCombiner return $css; } - private static function process_css_rec($file) + /** + * Resolves relative links in CSS file. + * + * @param string $css file content + * @param string $dir + * @return string + */ + private static function process_css_rec($css, $dir) { - static $PATTERN = "#url\(\s*['|\"]{0,1}(.*?)['|\"]{0,1}\s*\)#"; - $css = file_get_contents(PHPWG_ROOT_PATH . $file); - if (preg_match_all($PATTERN, $css, $matches, PREG_SET_ORDER)) + static $PATTERN_URL = "#url\(\s*['|\"]{0,1}(.*?)['|\"]{0,1}\s*\)#"; + static $PATTERN_IMPORT = "#@import\s*['|\"]{0,1}(.*?)['|\"]{0,1};#"; + + if (preg_match_all($PATTERN_URL, $css, $matches, PREG_SET_ORDER)) { $search = $replace = array(); foreach ($matches as $match) { - if ( !url_is_remote($match[1]) && $match[1][0] != '/') + if ( !url_is_remote($match[1]) && $match[1][0] != '/' && strpos($match[1], 'data:image/')===false) { - $relative = dirname($file) . "/$match[1]"; + $relative = $dir . "/$match[1]"; $search[] = $match[0]; $replace[] = 'url('.embellish_url(get_absolute_root_url(false).$relative).')'; } @@ -1427,14 +2006,14 @@ final class FileCombiner $css = str_replace($search, $replace, $css); } - $imports = preg_match_all("#@import\s*['|\"]{0,1}(.*?)['|\"]{0,1};#", $css, $matches, PREG_SET_ORDER); - if ($imports) + if (preg_match_all($PATTERN_IMPORT, $css, $matches, PREG_SET_ORDER)) { $search = $replace = array(); foreach ($matches as $match) { $search[] = $match[0]; - $replace[] = self::process_css_rec(dirname($file) . "/$match[1]"); + $sub_css = file_get_contents(PHPWG_ROOT_PATH . $dir . "/$match[1]"); + $replace[] = self::process_css_rec($sub_css, dirname($dir . "/$match[1]") ); } $css = str_replace($search, $replace, $css); } |