Skip to content

Commit af1cbac

Browse files
author
rvelices
committed
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
1 parent 208a5ac commit af1cbac

File tree

8 files changed

+367
-25
lines changed

8 files changed

+367
-25
lines changed

include/template.class.php

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,14 @@ class Template {
4343
// used by html_head smarty block to add content before </head>
4444
var $html_head_elements = array();
4545

46+
var $scriptLoader;
47+
var $html_footer_raw_script = array();
48+
4649
function Template($root = ".", $theme= "", $path = "template")
4750
{
4851
global $conf, $lang_info;
4952

53+
$this->scriptLoader = new ScriptLoader;
5054
$this->smarty = new Smarty;
5155
$this->smarty->debugging = $conf['debug_template'];
5256
$this->smarty->compile_check = $conf['template_compile_check'];
@@ -82,6 +86,9 @@ function Template($root = ".", $theme= "", $path = "template")
8286
$this->smarty->register_modifier( 'explode', array('Template', 'mod_explode') );
8387
$this->smarty->register_modifier( 'get_extent', array(&$this, 'get_extent') );
8488
$this->smarty->register_block('html_head', array(&$this, 'block_html_head') );
89+
$this->smarty->register_function('combine_script', array(&$this, 'func_combine_script') );
90+
$this->smarty->register_function('get_combined_scripts', array(&$this, 'func_get_combined_scripts') );
91+
$this->smarty->register_block('footer_script', array(&$this, 'block_footer_script') );
8592
$this->smarty->register_function('known_script', array(&$this, 'func_known_script') );
8693
$this->smarty->register_prefilter( array('Template', 'prefilter_white_space') );
8794
if ( $conf['compiled_template_cache_language'] )
@@ -376,6 +383,26 @@ function pparse($handle)
376383

377384
function flush()
378385
{
386+
if (!$this->scriptLoader->did_head())
387+
{
388+
$search = "\n</head>";
389+
$pos = strpos( $this->output, $search );
390+
if ($pos !== false)
391+
{
392+
$scripts = $this->scriptLoader->get_head_scripts();
393+
$content = array();
394+
foreach ($scripts as $id => $script)
395+
{
396+
$content[]=
397+
'<script type="text/javascript" src="'
398+
. Template::make_script_src($script)
399+
.'"></script>';
400+
}
401+
402+
$this->output = substr_replace( $this->output, "\n".implode( "\n", $content ), $pos, 0 );
403+
} //else maybe error or warning ?
404+
}
405+
379406
if ( count($this->html_head_elements) )
380407
{
381408
$search = "\n</head>";
@@ -472,6 +499,119 @@ function func_known_script($params, &$smarty )
472499
$this->block_html_head(null, $content, $smarty, $repeat);
473500
}
474501
}
502+
503+
function func_combine_script($params, &$smarty)
504+
{
505+
if (!isset($params['id']))
506+
{
507+
$smarty->trigger_error("combine_script: missing 'id' parameter", E_USER_ERROR);
508+
}
509+
$load = 0;
510+
if (isset($params['load']))
511+
{
512+
switch ($params['load'])
513+
{
514+
case 'header': break;
515+
case 'footer': $load=1; break;
516+
case 'async': $load=2; break;
517+
default: $smarty->trigger_error("combine_script: invalid 'load' parameter", E_USER_ERROR);
518+
}
519+
}
520+
$this->scriptLoader->add( $params['id'], $load,
521+
empty($params['require']) ? array() : explode( ',', $params['require'] ),
522+
@$params['path'],
523+
isset($params['version']) ? $params['version'] : 0 );
524+
}
525+
526+
527+
function func_get_combined_scripts($params, &$smarty)
528+
{
529+
if (!isset($params['load']))
530+
{
531+
$smarty->trigger_error("get_combined_scripts: missing 'load' parameter", E_USER_ERROR);
532+
}
533+
$load = $params['load']=='header' ? 0 : 1;
534+
$content = array();
535+
536+
if ($load==0)
537+
{
538+
if ($this->scriptLoader->did_head())
539+
fatal_error('get_combined_scripts several times header');
540+
541+
$scripts = $this->scriptLoader->get_head_scripts();
542+
foreach ($scripts as $id => $script)
543+
{
544+
$content[]=
545+
'<script type="text/javascript" src="'
546+
. Template::make_script_src($script)
547+
.'"></script>';
548+
}
549+
}
550+
else
551+
{
552+
if (!$this->scriptLoader->did_head())
553+
fatal_error('get_combined_scripts didn\'t call header');
554+
$scripts = $this->scriptLoader->get_footer_scripts();
555+
foreach ($scripts[0] as $id => $script)
556+
{
557+
$content[]=
558+
'<script type="text/javascript" src="'
559+
. Template::make_script_src($script)
560+
.'"></script>';
561+
}
562+
if (count($this->html_footer_raw_script))
563+
{
564+
$content[]= '<script type="text/javascript">';
565+
$content = array_merge($content, $this->html_footer_raw_script);
566+
$content[]= '</script>';
567+
}
568+
569+
if (count($scripts[1]))
570+
{
571+
$content[]= '<script type="text/javascript">';
572+
$content[]= '(function() {
573+
var after = document.getElementsByTagName(\'script\')[document.getElementsByTagName(\'script\').length-1];
574+
var s;';
575+
foreach ($scripts[1] as $id => $script)
576+
{
577+
$content[]=
578+
's=document.createElement(\'script\'); s.type = \'text/javascript\'; s.async = true; s.src = \''
579+
. Template::make_script_src($script)
580+
.'\';';
581+
$content[]= 'after = after.parentNode.insertBefore(s, after);';
582+
}
583+
$content[]= '})();';
584+
$content[]= '</script>';
585+
}
586+
}
587+
return implode("\n", $content);
588+
}
589+
590+
591+
private static function make_script_src( $script )
592+
{
593+
$ret = '';
594+
if ( url_is_remote($script->path) )
595+
$ret = $script->path;
596+
else
597+
{
598+
$ret = get_root_url().$script->path;
599+
if ($script->version!==false)
600+
{
601+
$ret.= '?v'. ($script->version ? $script->version : PHPWG_VERSION);
602+
}
603+
}
604+
return $ret;
605+
}
606+
607+
function block_footer_script($params, $content, &$smarty, &$repeat)
608+
{
609+
$content = trim($content);
610+
if ( !empty($content) )
611+
{ // second call
612+
$this->html_footer_raw_script[] = $content;
613+
}
614+
}
475615

476616
/**
477617
* This function allows to declare a Smarty prefilter from a plugin, thus allowing
@@ -644,4 +784,199 @@ function sprintf()
644784
}
645785
}
646786

787+
788+
final class Script
789+
{
790+
public $load_mode;
791+
public $precedents = array();
792+
public $path;
793+
public $version;
794+
public $extra = array();
795+
796+
function Script($load_mode, $precedents, $path, $version)
797+
{
798+
$this->load_mode = $load_mode;
799+
$this->precedents = $precedents;
800+
$this->path = $path;
801+
$this->version = $version;
802+
}
803+
804+
function set_path($path)
805+
{
806+
if (!empty($path))
807+
$this->path = $path;
808+
}
809+
}
810+
811+
812+
/** Manage a list of required scripts for a page, by optimizing their loading location (head, bottom, async)
813+
and later on by combining them in a unique file respecting at the same time dependencies.*/
814+
class ScriptLoader
815+
{
816+
private $registered_scripts;
817+
private $did_head;
818+
private static $known_paths = array(
819+
'core.scripts' => 'themes/default/js/scripts.js',
820+
'jquery' => 'themes/default/js/jquery.min.js',
821+
'jquery.ui' => 'themes/default/js/ui/packed/ui.core.packed.js'
822+
);
823+
824+
function __construct()
825+
{
826+
$this->clear();
827+
}
828+
829+
function clear()
830+
{
831+
$this->registered_scripts = array();
832+
$this->did_head = false;
833+
}
834+
835+
function add($id, $load_mode, $require, $path, $version=0)
836+
{
837+
if ($this->did_head && $load_mode==0 )
838+
{
839+
trigger_error("Attempt to add a new script $id but the head has been written", E_USER_WARNING);
840+
}
841+
if (! isset( $this->registered_scripts[$id] ) )
842+
{
843+
$script = new Script($load_mode, $require, $path, $version);
844+
self::fill_well_known($id, $script);
845+
$this->registered_scripts[$id] = $script;
846+
}
847+
else
848+
{
849+
$script = & $this->registered_scripts[$id];
850+
if (count($require))
851+
{
852+
$script->precedents = array_unique( array_merge($script->precedents, $require) );
853+
}
854+
$script->set_path($path);
855+
if ($version && version_compare($script->version, $version)<0 )
856+
$script->version = $version;
857+
if ($load_mode < $script->load_mode)
858+
$script->load_mode = $load_mode;
859+
}
860+
}
861+
862+
function did_head()
863+
{
864+
return $this->did_head;
865+
}
866+
867+
private static function fill_well_known($id, $script)
868+
{
869+
if ( empty($script->path) && isset(self::$known_paths[$id]))
870+
{
871+
$script->path = self::$known_paths[$id];
872+
}
873+
if ( strncmp($id, 'jquery.', 7)==0 )
874+
{
875+
if ( !in_array('jquery', $script->precedents ) )
876+
$script->precedents[] = 'jquery';
877+
if ( strncmp($id, 'jquery.ui.', 10)==0 && !in_array('jquery.ui', $script->precedents ) )
878+
$script->precedents[] = 'jquery.ui';
879+
}
880+
}
881+
882+
function get_head_scripts()
883+
{
884+
do
885+
{
886+
$changed = false;
887+
foreach( $this->registered_scripts as $id => $script)
888+
{
889+
$load = $script->load_mode;
890+
if ($load==0)
891+
continue;
892+
if ($load==2)
893+
$load=1; // we are async -> a predecessor cannot be async because the script execution order is not guaranteed
894+
foreach( $script->precedents as $precedent)
895+
{
896+
if ( !isset($this->registered_scripts[$precedent] ) )
897+
{
898+
trigger_error("Script $id requires undefined script $precedent", E_USER_WARNING);
899+
continue;
900+
}
901+
if ( $this->registered_scripts[$precedent]->load_mode > $load )
902+
{
903+
$this->registered_scripts[$precedent]->load_mode = $load;
904+
$changed = true;
905+
}
906+
}
907+
}
908+
}
909+
while ($changed);
910+
911+
foreach( array_keys($this->registered_scripts) as $id )
912+
{
913+
$this->compute_script_topological_order($id);
914+
}
915+
916+
uasort($this->registered_scripts, array('ScriptLoader', 'cmp_by_mode_and_order'));
917+
918+
$result = array();
919+
foreach( $this->registered_scripts as $id => $script)
920+
{
921+
if ($script->load_mode > 0)
922+
break;
923+
if ( !empty($script->path) )
924+
$result[$id] = $script;
925+
else
926+
trigger_error("Script $id has an undefined path", E_USER_WARNING);
927+
}
928+
$this->did_head = true;
929+
return $result;
930+
}
931+
932+
function get_footer_scripts()
933+
{
934+
if (!$this->did_head)
935+
{
936+
trigger_error("Attempt to write footer scripts without header scripts", E_USER_WARNING);
937+
}
938+
$result = array( array(), array() );
939+
foreach( $this->registered_scripts as $id => $script)
940+
{
941+
if ($script->load_mode > 0)
942+
{
943+
if ( !empty( $script->path ) )
944+
{
945+
$result[$script->load_mode-1][$id] = $script;
946+
}
947+
else
948+
trigger_error("Script $id has an undefined path", E_USER_WARNING);
949+
}
950+
}
951+
return $result;
952+
}
953+
954+
private function compute_script_topological_order($script_id)
955+
{
956+
if (!isset($this->registered_scripts[$script_id]))
957+
{
958+
trigger_error("Undefined script $script_id is required by someone", E_USER_WARNING);
959+
return 0;
960+
}
961+
$script = & $this->registered_scripts[$script_id];
962+
if (isset($script->extra['order']))
963+
return $script->extra['order'];
964+
if (count($script->precedents) == 0)
965+
return ($script->extra['order'] = 0);
966+
$max = 0;
967+
foreach( $script->precedents as $precedent)
968+
$max = max($max, $this->compute_script_topological_order($precedent) );
969+
$max++;
970+
return ($script->extra['order'] = $max);
971+
}
972+
973+
private static function cmp_by_mode_and_order($s1, $s2)
974+
{
975+
$ret = $s1->load_mode - $s2->load_mode;
976+
if (!$ret)
977+
$ret = $s1->extra['order'] - $s2->extra['order'];
978+
return $ret;
979+
}
980+
}
981+
647982
?>

themes/default/template/footer.tpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<a href="mailto:{$CONTACT_MAIL}?subject={'A comment on your site'|@translate|@escape:url}">{'Webmaster'|@translate}</a>
1919
{/if}
2020

21+
{get_combined_scripts load='footer'}
2122

2223
{if isset($footer_elements)}
2324
{foreach from=$footer_elements item=v}

themes/default/template/header.tpl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,15 @@
4040
{if isset($U_PREFETCH) }<link rel="prefetch" href="{$U_PREFETCH}">{/if}
4141

4242
{if not empty($page_refresh) }<meta http-equiv="refresh" content="{$page_refresh.TIME};url={$page_refresh.U_REFRESH}">{/if}
43-
43+
{*
4444
<script type="text/javascript" src="{$ROOT_URL}themes/default/js/scripts.js"></script>
45+
*}
4546
<!--[if lt IE 7]>
4647
<script type="text/javascript" src="{$ROOT_URL}themes/default/js/pngfix.js"></script>
4748
<![endif]-->
4849

50+
{get_combined_scripts load='header'}
51+
4952
{if not empty($head_elements)}
5053
{foreach from=$head_elements item=elt}{$elt}
5154
{/foreach}

0 commit comments

Comments
 (0)