diff options
author | rvelices <rv-github@modusoptimus.com> | 2010-12-02 19:46:30 +0000 |
---|---|---|
committer | rvelices <rv-github@modusoptimus.com> | 2010-12-02 19:46:30 +0000 |
commit | af1cbac19d7478b935a991abddaa2dd4c8df6c34 (patch) | |
tree | 0c9e42aa73da4db5a99537641db9cb46db0f8fcb | |
parent | 208a5acddcfebc131af01855edbf7f6cdc903bf7 (diff) |
new template features: combine_script, footer_script and get_combined_scripts
migrated public templates only; need more code doc
git-svn-id: http://piwigo.org/svn/trunk@7975 68402e56-0260-453c-a942-63ccdbb3a9ee
Diffstat (limited to '')
-rw-r--r-- | include/template.class.php | 335 | ||||
-rw-r--r-- | themes/default/template/footer.tpl | 1 | ||||
-rw-r--r-- | themes/default/template/header.tpl | 5 | ||||
-rw-r--r-- | themes/default/template/include/datepicker.inc.tpl | 14 | ||||
-rw-r--r-- | themes/default/template/include/resize.inc.tpl | 13 | ||||
-rw-r--r-- | themes/default/template/index.tpl | 1 | ||||
-rw-r--r-- | themes/default/template/picture.tpl | 8 | ||||
-rw-r--r-- | themes/default/template/search.tpl | 15 |
8 files changed, 367 insertions, 25 deletions
diff --git a/include/template.class.php b/include/template.class.php index 161b7361e..d41cd3cf4 100644 --- a/include/template.class.php +++ b/include/template.class.php @@ -43,10 +43,14 @@ class Template { // used by html_head smarty block to add content before </head> var $html_head_elements = array(); + var $scriptLoader; + var $html_footer_raw_script = array(); + function Template($root = ".", $theme= "", $path = "template") { global $conf, $lang_info; + $this->scriptLoader = new ScriptLoader; $this->smarty = new Smarty; $this->smarty->debugging = $conf['debug_template']; $this->smarty->compile_check = $conf['template_compile_check']; @@ -82,6 +86,9 @@ class Template { $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_function('combine_script', array(&$this, 'func_combine_script') ); + $this->smarty->register_function('get_combined_scripts', array(&$this, 'func_get_combined_scripts') ); + $this->smarty->register_block('footer_script', array(&$this, 'block_footer_script') ); $this->smarty->register_function('known_script', array(&$this, 'func_known_script') ); $this->smarty->register_prefilter( array('Template', 'prefilter_white_space') ); if ( $conf['compiled_template_cache_language'] ) @@ -376,6 +383,26 @@ class Template { function flush() { + if (!$this->scriptLoader->did_head()) + { + $search = "\n</head>"; + $pos = strpos( $this->output, $search ); + if ($pos !== false) + { + $scripts = $this->scriptLoader->get_head_scripts(); + $content = array(); + foreach ($scripts as $id => $script) + { + $content[]= + '<script type="text/javascript" src="' + . Template::make_script_src($script) + .'"></script>'; + } + + $this->output = substr_replace( $this->output, "\n".implode( "\n", $content ), $pos, 0 ); + } //else maybe error or warning ? + } + if ( count($this->html_head_elements) ) { $search = "\n</head>"; @@ -472,6 +499,119 @@ class Template { $this->block_html_head(null, $content, $smarty, $repeat); } } + + function func_combine_script($params, &$smarty) + { + if (!isset($params['id'])) + { + $smarty->trigger_error("combine_script: missing 'id' parameter", E_USER_ERROR); + } + $load = 0; + if (isset($params['load'])) + { + switch ($params['load']) + { + case 'header': break; + case 'footer': $load=1; break; + case 'async': $load=2; break; + default: $smarty->trigger_error("combine_script: invalid 'load' parameter", E_USER_ERROR); + } + } + $this->scriptLoader->add( $params['id'], $load, + empty($params['require']) ? array() : explode( ',', $params['require'] ), + @$params['path'], + isset($params['version']) ? $params['version'] : 0 ); + } + + + function func_get_combined_scripts($params, &$smarty) + { + if (!isset($params['load'])) + { + $smarty->trigger_error("get_combined_scripts: missing 'load' parameter", E_USER_ERROR); + } + $load = $params['load']=='header' ? 0 : 1; + $content = array(); + + if ($load==0) + { + if ($this->scriptLoader->did_head()) + fatal_error('get_combined_scripts several times header'); + + $scripts = $this->scriptLoader->get_head_scripts(); + foreach ($scripts as $id => $script) + { + $content[]= + '<script type="text/javascript" src="' + . Template::make_script_src($script) + .'"></script>'; + } + } + else + { + if (!$this->scriptLoader->did_head()) + fatal_error('get_combined_scripts didn\'t call header'); + $scripts = $this->scriptLoader->get_footer_scripts(); + foreach ($scripts[0] as $id => $script) + { + $content[]= + '<script type="text/javascript" src="' + . Template::make_script_src($script) + .'"></script>'; + } + if (count($this->html_footer_raw_script)) + { + $content[]= '<script type="text/javascript">'; + $content = array_merge($content, $this->html_footer_raw_script); + $content[]= '</script>'; + } + + if (count($scripts[1])) + { + $content[]= '<script type="text/javascript">'; + $content[]= '(function() { + var after = document.getElementsByTagName(\'script\')[document.getElementsByTagName(\'script\').length-1]; + var s;'; + foreach ($scripts[1] as $id => $script) + { + $content[]= + 's=document.createElement(\'script\'); s.type = \'text/javascript\'; s.async = true; s.src = \'' + . Template::make_script_src($script) + .'\';'; + $content[]= 'after = after.parentNode.insertBefore(s, after);'; + } + $content[]= '})();'; + $content[]= '</script>'; + } + } + return implode("\n", $content); + } + + + private static function make_script_src( $script ) + { + $ret = ''; + if ( url_is_remote($script->path) ) + $ret = $script->path; + else + { + $ret = get_root_url().$script->path; + if ($script->version!==false) + { + $ret.= '?v'. ($script->version ? $script->version : PHPWG_VERSION); + } + } + return $ret; + } + + function block_footer_script($params, $content, &$smarty, &$repeat) + { + $content = trim($content); + if ( !empty($content) ) + { // second call + $this->html_footer_raw_script[] = $content; + } + } /** * This function allows to declare a Smarty prefilter from a plugin, thus allowing @@ -644,4 +784,199 @@ class PwgTemplateAdapter } } + +final class Script +{ + public $load_mode; + public $precedents = array(); + public $path; + public $version; + public $extra = array(); + + function Script($load_mode, $precedents, $path, $version) + { + $this->load_mode = $load_mode; + $this->precedents = $precedents; + $this->path = $path; + $this->version = $version; + } + + function set_path($path) + { + if (!empty($path)) + $this->path = $path; + } +} + + +/** 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.*/ +class ScriptLoader +{ + private $registered_scripts; + private $did_head; + private static $known_paths = array( + 'core.scripts' => 'themes/default/js/scripts.js', + 'jquery' => 'themes/default/js/jquery.min.js', + 'jquery.ui' => 'themes/default/js/ui/packed/ui.core.packed.js' + ); + + function __construct() + { + $this->clear(); + } + + function clear() + { + $this->registered_scripts = array(); + $this->did_head = false; + } + + function add($id, $load_mode, $require, $path, $version=0) + { + if ($this->did_head && $load_mode==0 ) + { + trigger_error("Attempt to add a new script $id but the head has been written", E_USER_WARNING); + } + if (! isset( $this->registered_scripts[$id] ) ) + { + $script = new Script($load_mode, $require, $path, $version); + self::fill_well_known($id, $script); + $this->registered_scripts[$id] = $script; + } + else + { + $script = & $this->registered_scripts[$id]; + if (count($require)) + { + $script->precedents = array_unique( array_merge($script->precedents, $require) ); + } + $script->set_path($path); + if ($version && version_compare($script->version, $version)<0 ) + $script->version = $version; + if ($load_mode < $script->load_mode) + $script->load_mode = $load_mode; + } + } + + function did_head() + { + return $this->did_head; + } + + private static function fill_well_known($id, $script) + { + if ( empty($script->path) && isset(self::$known_paths[$id])) + { + $script->path = self::$known_paths[$id]; + } + if ( strncmp($id, 'jquery.', 7)==0 ) + { + if ( !in_array('jquery', $script->precedents ) ) + $script->precedents[] = 'jquery'; + if ( strncmp($id, 'jquery.ui.', 10)==0 && !in_array('jquery.ui', $script->precedents ) ) + $script->precedents[] = 'jquery.ui'; + } + } + + function get_head_scripts() + { + do + { + $changed = false; + foreach( $this->registered_scripts as $id => $script) + { + $load = $script->load_mode; + if ($load==0) + continue; + if ($load==2) + $load=1; // we are async -> a predecessor cannot be async because the script execution order is not guaranteed + foreach( $script->precedents as $precedent) + { + if ( !isset($this->registered_scripts[$precedent] ) ) + { + trigger_error("Script $id requires undefined script $precedent", E_USER_WARNING); + continue; + } + if ( $this->registered_scripts[$precedent]->load_mode > $load ) + { + $this->registered_scripts[$precedent]->load_mode = $load; + $changed = true; + } + } + } + } + while ($changed); + + foreach( array_keys($this->registered_scripts) as $id ) + { + $this->compute_script_topological_order($id); + } + + uasort($this->registered_scripts, array('ScriptLoader', 'cmp_by_mode_and_order')); + + $result = array(); + foreach( $this->registered_scripts as $id => $script) + { + if ($script->load_mode > 0) + break; + if ( !empty($script->path) ) + $result[$id] = $script; + else + trigger_error("Script $id has an undefined path", E_USER_WARNING); + } + $this->did_head = true; + return $result; + } + + function get_footer_scripts() + { + if (!$this->did_head) + { + trigger_error("Attempt to write footer scripts without header scripts", E_USER_WARNING); + } + $result = array( array(), array() ); + foreach( $this->registered_scripts as $id => $script) + { + if ($script->load_mode > 0) + { + if ( !empty( $script->path ) ) + { + $result[$script->load_mode-1][$id] = $script; + } + else + trigger_error("Script $id has an undefined path", E_USER_WARNING); + } + } + return $result; + } + + private function compute_script_topological_order($script_id) + { + if (!isset($this->registered_scripts[$script_id])) + { + trigger_error("Undefined script $script_id is required by someone", E_USER_WARNING); + return 0; + } + $script = & $this->registered_scripts[$script_id]; + if (isset($script->extra['order'])) + return $script->extra['order']; + if (count($script->precedents) == 0) + return ($script->extra['order'] = 0); + $max = 0; + foreach( $script->precedents as $precedent) + $max = max($max, $this->compute_script_topological_order($precedent) ); + $max++; + return ($script->extra['order'] = $max); + } + + private static function cmp_by_mode_and_order($s1, $s2) + { + $ret = $s1->load_mode - $s2->load_mode; + if (!$ret) + $ret = $s1->extra['order'] - $s2->extra['order']; + return $ret; + } +} + ?> diff --git a/themes/default/template/footer.tpl b/themes/default/template/footer.tpl index 671c92d55..90b445ca4 100644 --- a/themes/default/template/footer.tpl +++ b/themes/default/template/footer.tpl @@ -18,6 +18,7 @@ <a href="mailto:{$CONTACT_MAIL}?subject={'A comment on your site'|@translate|@escape:url}">{'Webmaster'|@translate}</a> {/if} +{get_combined_scripts load='footer'} {if isset($footer_elements)} {foreach from=$footer_elements item=v} diff --git a/themes/default/template/header.tpl b/themes/default/template/header.tpl index bb42c7289..36222e268 100644 --- a/themes/default/template/header.tpl +++ b/themes/default/template/header.tpl @@ -40,12 +40,15 @@ {if isset($U_PREFETCH) }<link rel="prefetch" href="{$U_PREFETCH}">{/if} {if not empty($page_refresh) }<meta http-equiv="refresh" content="{$page_refresh.TIME};url={$page_refresh.U_REFRESH}">{/if} - +{* <script type="text/javascript" src="{$ROOT_URL}themes/default/js/scripts.js"></script> +*} <!--[if lt IE 7]> <script type="text/javascript" src="{$ROOT_URL}themes/default/js/pngfix.js"></script> <![endif]--> +{get_combined_scripts load='header'} + {if not empty($head_elements)} {foreach from=$head_elements item=elt}{$elt} {/foreach} diff --git a/themes/default/template/include/datepicker.inc.tpl b/themes/default/template/include/datepicker.inc.tpl index de3918cb3..0ed844942 100644 --- a/themes/default/template/include/datepicker.inc.tpl +++ b/themes/default/template/include/datepicker.inc.tpl @@ -1,24 +1,24 @@ -{known_script id="jquery" src=$ROOT_URL|@cat:"themes/default/js/jquery.packed.js"} -{known_script id="jquery.ui" src=$ROOT_URL|@cat:"themes/default/js/ui/packed/ui.core.packed.js"} -{known_script id="jquery.ui.datepicker" src=$ROOT_URL|@cat:"themes/default/js/ui/packed/ui.datepicker.packed.js"} -{known_script id="datepicker.js" src=$ROOT_URL|@cat:"themes/default/js/datepicker.js"} +{combine_script id='jquery' load='footer' path='themes/default/js/jquery.packed.js'} +{combine_script id='jquery.ui' load='footer' require='jquery' path='themes/default/js/ui/packed/ui.core.packed.js'} +{combine_script id='jquery.ui.datepicker' load='footer' require='jquery.ui' path='themes/default/js/ui/packed/ui.datepicker.packed.js'} +{combine_script id='datepicker.js' load='footer' require='jquery.ui.datepicker' path='themes/default/js/datepicker.js'} {assign var="datepicker_language" value="themes/default/js/ui/i18n/ui.datepicker-"|@cat:$lang_info.code|@cat:".js"} {if "PHPWG_ROOT_PATH"|@constant|@cat:$datepicker_language|@file_exists} -{known_script id="jquery.ui.datepicker-$lang_info.code" src=$ROOT_URL|@cat:$datepicker_language} +{combine_script id="jquery.ui.datepicker-$lang_info.code" path=$datepicker_language} {/if} {html_head} <link rel="stylesheet" type="text/css" href="{$ROOT_URL}themes/default/js/ui/theme/ui.datepicker.css"> {/html_head} -<script type="text/javascript"> +{footer_script} function pwg_initialization_datepicker(day, month, year, linked_date, checked_on_change, min_linked_date, max_linked_date) {ldelim} return pwg_common_initialization_datepicker( "{$ROOT_URL}{$themeconf.icon_dir}/datepicker.png", day, month, year, linked_date, checked_on_change, min_linked_date, max_linked_date); } -</script> +{/footer_script} diff --git a/themes/default/template/include/resize.inc.tpl b/themes/default/template/include/resize.inc.tpl index 30a637563..5165f49a5 100644 --- a/themes/default/template/include/resize.inc.tpl +++ b/themes/default/template/include/resize.inc.tpl @@ -1,10 +1,9 @@ -{known_script id="jquery" src=$ROOT_URL|@cat:"themes/default/js/jquery.packed.js"} -{known_script id="jquery.ui" src=$ROOT_URL|@cat:"themes/default/js/ui/packed/ui.core.packed.js"} -{known_script id="jquery.ui.resizable" src=$ROOT_URL|@cat:"themes/default/js/ui/packed/ui.resizable.packed.js"} +{combine_script id='jquery' load='footer' path='themes/default/js/jquery.packed.js'} +{combine_script id='jquery.ui' load='footer' require='jquery' path='themes/default/js/ui/packed/ui.core.packed.js'} +{combine_script id='jquery.ui.resizable' load='footer' require='jquery.ui' path='themes/default/js/ui/packed/ui.resizable.packed.js'} {* Resize possible *} -{literal} -<script type="text/javascript"> +{footer_script}{literal} jQuery().ready(function(){ // Resize possible for list jQuery(".categoryList").resizable({ @@ -18,5 +17,5 @@ ghost: true }); }); -</script> -{/literal} +{/literal}{/footer_script} + diff --git a/themes/default/template/index.tpl b/themes/default/template/index.tpl index 5db0b6e3c..37f577f84 100644 --- a/themes/default/template/index.tpl +++ b/themes/default/template/index.tpl @@ -27,6 +27,7 @@ {/if} {if isset($U_SEARCH_RULES) } + {combine_script id='core.scripts' load='async' path='themes/default/js/scripts.js'} <li><a href="{$U_SEARCH_RULES}" onclick="popuphelp(this.href); return false;" title="{'Search rules'|@translate}" rel="nofollow"><img src="{$ROOT_URL}{$themeconf.icon_dir}/search_rules.png" class="button" alt="(?)"></a></li> {/if} diff --git a/themes/default/template/picture.tpl b/themes/default/template/picture.tpl index 00e548609..220790059 100644 --- a/themes/default/template/picture.tpl +++ b/themes/default/template/picture.tpl @@ -59,6 +59,7 @@ <a href="{$U_ADMIN}" title="{'Modify information'|@translate}"><img src="{$ROOT_URL}{$themeconf.icon_dir}/preferences.png" class="button" alt="{'edit'|@translate}"></a> {/if} {if isset($U_CADDIE) }{*caddie management BEGIN*} +{combine_script id='core.scripts' load='async' path='themes/default/js/scripts.js'} <script type="text/javascript"> {literal}function addToCadie(aElement, rootUrl, id) { @@ -208,15 +209,17 @@ y.callService( <input type="submit" name="rate" value="{$mark}" class="rateButton" title="{$mark}"> {/if} {/foreach} + {combine_script id='core.scripts' load='async' path='themes/default/js/scripts.js'} + {combine_script id='rating' load='async' require='core.scripts' path='themes/default/js/rating.js'} <script type="text/javascript"> var _pwgRatingAutoQueue = _pwgRatingAutoQueue || []; _pwgRatingAutoQueue.push( {ldelim}rootUrl: '{$ROOT_URL|@escape:"javascript"}', image_id: {$current.id}, updateRateText: "{'Update your rating'|@translate|@escape:'javascript'}", updateRateElement: document.getElementById("updateRate"), ratingSummaryText: "{'%.2f (rated %d times)'|@translate|@escape:'javascript'}", ratingSummaryElement: document.getElementById("ratingSummary") {rdelim} ); - (function () {ldelim} + /*(function () {ldelim} var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = '{$ROOT_URL}themes/default/js/rating.js'; var s0 = document.getElementsByTagName('script')[0]; s0.parentNode.insertBefore(s, s0); - })(); + })();*/ </script> </div> </form> @@ -228,6 +231,7 @@ y.callService( <tr id="Privacy"> <td class="label">{'Who can see this photo?'|@translate}</td> <td class="value"> +{combine_script id='core.scripts' load='async' path='themes/default/js/scripts.js'} <script type="text/javascript"> {literal}function setPrivacyLevel(selectElement, rootUrl, id, level) { diff --git a/themes/default/template/search.tpl b/themes/default/template/search.tpl index 5ad8dee96..cc958f7dd 100644 --- a/themes/default/template/search.tpl +++ b/themes/default/template/search.tpl @@ -1,26 +1,25 @@ {* Example of resizeable *} -{* + {include file='include/resize.inc.tpl'} -*} + {* Example of datepicker *} -{* + {include file='include/datepicker.inc.tpl'} -{literal} -<script type="text/javascript"> +{footer_script}{literal} pwg_initialization_datepicker("#start_day", "#start_month", "#start_year", "#start_linked_date", null, null, "#end_linked_date"); pwg_initialization_datepicker("#end_day", "#end_month", "#end_year", "#end_linked_date", null, "#start_linked_date", null); jQuery().ready(function(){ $(".date_today").hide(); }); -</script> -{/literal} -*} +{/literal}{/footer_script} + <div id="content" class="content"> <div class="titrePage"> <ul class="categoryActions"> + {combine_script id='core.scripts' load='async' path='themes/default/js/scripts.js'} <li><a href="{$U_HELP}" onclick="popuphelp(this.href); return false;" title="{'Help'|@translate}" rel="nofollow"><img src="{$ROOT_URL}{$themeconf.icon_dir}/help.png" class="button" alt="(?)"></a></li> <li><a href="{$U_HOME}" title="{'Home'|@translate}" rel="Home"><img src="{$ROOT_URL}{$themeconf.icon_dir}/home.png" class="button" alt="{'Home'|@translate}"></a></li> </ul> |