aboutsummaryrefslogtreecommitdiffstats
path: root/include/template.class.php
diff options
context:
space:
mode:
Diffstat (limited to 'include/template.class.php')
-rw-r--r--include/template.class.php1275
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);
}