source: trunk/include/template.class.php @ 5126

Last change on this file since 5126 was 5126, checked in by patdenice, 14 years ago

Feature 1502
Extend for templates now work properly.
Bound template as to be renamed in bound theme...

File size: 17.0 KB
Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | Piwigo - a PHP based picture gallery                                  |
4// +-----------------------------------------------------------------------+
5// | Copyright(C) 2008-2009 Piwigo Team                  http://piwigo.org |
6// | Copyright(C) 2003-2008 PhpWebGallery Team    http://phpwebgallery.net |
7// | Copyright(C) 2002-2003 Pierrick LE GALL   http://le-gall.net/pierrick |
8// +-----------------------------------------------------------------------+
9// | This program is free software; you can redistribute it and/or modify  |
10// | it under the terms of the GNU General Public License as published by  |
11// | the Free Software Foundation                                          |
12// |                                                                       |
13// | This program is distributed in the hope that it will be useful, but   |
14// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
15// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
16// | General Public License for more details.                              |
17// |                                                                       |
18// | You should have received a copy of the GNU General Public License     |
19// | along with this program; if not, write to the Free Software           |
20// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
21// | USA.                                                                  |
22// +-----------------------------------------------------------------------+
23
24
25require_once(PHPWG_ROOT_PATH.'include/smarty/libs/Smarty.class.php');
26
27// migrate lang:XXX
28//    sed "s/{lang:\([^}]\+\)}/{\'\1\'|@translate}/g" my_template.tpl
29// migrate change root level vars {XXX}
30//    sed "s/{pwg_root}/{ROOT_URL}/g" my_template.tpl
31// migrate change root level vars {XXX}
32//    sed "s/{\([a-zA-Z_]\+\)}/{$\1}/g" my_template.tpl
33// migrate all
34//    cat my_template.tpl | sed "s/{lang:\([^}]\+\)}/{\'\1\'|@translate}/g" | sed "s/{pwg_root}/{ROOT_URL}/g" | sed "s/{\([a-zA-Z_]\+\)}/{$\1}/g"
35
36
37class Template {
38
39  var $smarty;
40
41  var $output = '';
42
43  // Hash of filenames for each template handle.
44  var $files = array();
45
46  // Template extents filenames for each template handle.
47  var $extents = array();
48
49  // Templates prefilter from external sources (plugins)
50  var $external_filters = array();
51 
52  // used by html_head smarty block to add content before </head>
53  var $html_head_elements = array();
54
55  function Template($root = ".", $theme= "", $path = "template")
56  {
57    global $conf, $lang_info;
58
59    $this->smarty = new Smarty;
60    $this->smarty->debugging = $conf['debug_template'];
61    $this->smarty->compile_check=$conf['template_compile_check'];
62
63    $compile_dir = $conf['local_data_dir'].'/templates_c';
64    mkgetdir( $compile_dir );
65
66    $this->smarty->compile_dir = $compile_dir;
67
68    $this->smarty->assign_by_ref( 'pwg', new PwgTemplateAdapter() );
69    $this->smarty->register_modifier( 'translate', array('Template', 'mod_translate') );
70    $this->smarty->register_modifier( 'explode', array('Template', 'mod_explode') );
71    $this->smarty->register_modifier( 'get_extent', array(&$this, 'get_extent') );
72    $this->smarty->register_block('html_head', array(&$this, 'block_html_head') );
73    $this->smarty->register_function('known_script', array(&$this, 'func_known_script') );
74    $this->smarty->register_prefilter( array('Template', 'prefilter_white_space') );
75    if ( $conf['compiled_template_cache_language'] )
76    {
77      $this->smarty->register_prefilter( array('Template', 'prefilter_language') );
78    }
79
80    $this->smarty->template_dir = array();
81    $this->set_theme($root, $theme, $path);
82
83    $this->smarty->assign('lang_info', $lang_info);
84
85    if (!defined('IN_ADMIN') and isset($conf['extents_for_templates']))
86    {
87      $tpl_extents = unserialize($conf['extents_for_templates']);
88      $this->set_extents($tpl_extents, './template-extension/', true, $theme);
89    }
90  }
91
92  /**
93   * Load theme's parameters.
94   */
95  function set_theme($root, $theme, $path)
96  {
97    $this->set_template_dir($root.'/'.$theme.'/'.$path);
98
99    include($root.'/'.$theme.'/themeconf.inc.php');
100
101    if (isset($themeconf['parent']))
102    {
103      $this->set_theme($root, $themeconf['parent'], $path);
104    }
105
106    $tpl_var = array('name' => $themeconf['theme']);
107    if (file_exists($root.'/'.$theme.'/local_head.tpl'))
108    {
109      $tpl_var['local_head'] = realpath($root.'/'.$theme.'/local_head.tpl');
110    }
111    $this->smarty->append('themes', $tpl_var);
112    $this->smarty->append('themeconf', $themeconf, true);
113  }
114
115  /**
116   * Add template directory for this Template object.
117   * Set compile id if not exists.
118   */
119  function set_template_dir($dir)
120  {
121    $this->smarty->template_dir[] = $dir;
122
123    if (!isset($this->smarty->compile_id))
124    {
125      $real_dir = realpath($dir);
126      $compile_id = crc32( $real_dir===false ? $dir : $real_dir);
127      $this->smarty->compile_id = base_convert($compile_id, 10, 36 );
128    }
129  }
130
131  /**
132   * Gets the template root directory for this Template object.
133   */
134  function get_template_dir()
135  {
136    return $this->smarty->template_dir;
137  }
138
139  /**
140   * Deletes all compiled templates.
141   */
142  function delete_compiled_templates()
143  {
144      $save_compile_id = $this->smarty->compile_id;
145      $this->smarty->compile_id = null;
146      $this->smarty->clear_compiled_tpl();
147      $this->smarty->compile_id = $save_compile_id;
148      file_put_contents($this->smarty->compile_dir.'/index.htm', 'Not allowed!');
149  }
150
151  function get_themeconf($val)
152  {
153    $tc = $this->smarty->get_template_vars('themeconf');
154    return isset($tc[$val]) ? $tc[$val] : '';
155  }
156
157  /**
158   * Sets the template filename for handle.
159   */
160  function set_filename($handle, $filename)
161  {
162    return $this->set_filenames( array($handle=>$filename) );
163  }
164
165  /**
166   * Sets the template filenames for handles. $filename_array should be a
167   * hash of handle => filename pairs.
168   */
169  function set_filenames($filename_array)
170  {
171    if (!is_array($filename_array))
172    {
173      return false;
174    }
175    reset($filename_array);
176    while(list($handle, $filename) = each($filename_array))
177    {
178      if (is_null($filename))
179      {
180        unset($this->files[$handle]);
181      }
182      else
183      {
184        $this->files[$handle] = $this->get_extent($filename, $handle);
185      }
186    }
187    return true;
188  }
189
190  /**
191   * Sets template extention filename for handles.
192   */
193  function set_extent($filename, $param, $dir='', $overwrite=true, $theme='N/A')
194  {
195    return $this->set_extents(array($filename => $param), $dir, $overwrite);
196  }
197
198  /**
199   * Sets template extentions filenames for handles.
200   * $filename_array should be an hash of filename => array( handle, param) or filename => handle
201   */
202  function set_extents($filename_array, $dir='', $overwrite=true, $theme='N/A')
203  {
204    if (!is_array($filename_array))
205    {
206      return false;
207    }
208    foreach ($filename_array as $filename => $value)
209    {
210      if (is_array($value))
211      {
212        $handle = $value[0];
213        $param = $value[1];
214        $thm = $value[2];
215      }
216      elseif (is_string($value))
217      {
218        $handle = $value;
219        $param = 'N/A';
220        $thm = 'N/A';
221      }
222      else
223      {
224        return false;
225      }
226
227      if ((stripos(implode('',array_keys($_GET)), '/'.$param) !== false or $param == 'N/A')
228        and ($thm == $theme)
229        and (!isset($this->extents[$handle]) or $overwrite)
230        and file_exists($dir . $filename))
231      {
232        $this->extents[$handle] = realpath($dir . $filename);
233      }
234    }
235    return true;
236  }
237
238  /** return template extension if exists  */
239  function get_extent($filename='', $handle='')
240  {
241    if (isset($this->extents[$handle]))
242    {
243      $filename = $this->extents[$handle];
244    }
245    return $filename;
246  }
247
248  /** see smarty assign http://www.smarty.net/manual/en/api.assign.php */
249  function assign($tpl_var, $value = null)
250  {
251    $this->smarty->assign( $tpl_var, $value );
252  }
253
254  /**
255   * Inserts the uncompiled code for $handle as the value of $varname in the
256   * root-level. This can be used to effectively include a template in the
257   * middle of another template.
258   * This is equivalent to assign($varname, $this->parse($handle, true))
259   */
260  function assign_var_from_handle($varname, $handle)
261  {
262    $this->assign($varname, $this->parse($handle, true));
263    return true;
264  }
265
266  /** see smarty append http://www.smarty.net/manual/en/api.append.php */
267  function append($tpl_var, $value=null, $merge=false)
268  {
269    $this->smarty->append( $tpl_var, $value, $merge );
270  }
271
272  /**
273   * Root-level variable concatenation. Appends a  string to an existing
274   * variable assignment with the same name.
275   */
276  function concat($tpl_var, $value)
277  {
278    $old_val = & $this->smarty->get_template_vars($tpl_var);
279    if ( isset($old_val) )
280    {
281      $old_val .= $value;
282    }
283    else
284    {
285      $this->assign($tpl_var, $value);
286    }
287  }
288
289  /** see smarty append http://www.smarty.net/manual/en/api.clear_assign.php */
290  function clear_assign($tpl_var)
291  {
292    $this->smarty->clear_assign( $tpl_var );
293  }
294
295  /** see smarty get_template_vars http://www.smarty.net/manual/en/api.get_template_vars.php */
296  function &get_template_vars($name=null)
297  {
298    return $this->smarty->get_template_vars( $name );
299  }
300
301
302  /**
303   * Load the file for the handle, eventually compile the file and run the compiled
304   * code. This will add the output to the results or return the result if $return
305   * is true.
306   */
307  function parse($handle, $return=false)
308  {
309    if ( !isset($this->files[$handle]) )
310    {
311      fatal_error("Template->parse(): Couldn't load template file for handle $handle");
312    }
313
314    $this->smarty->assign( 'ROOT_URL', get_root_url() );
315    $this->smarty->assign( 'TAG_INPUT_ENABLED',
316      ((is_adviser()) ? 'disabled="disabled" onclick="return false;"' : ''));
317
318    $save_compile_id = $this->smarty->compile_id;
319    $this->load_external_filters($handle);
320
321    global $conf, $lang_info;
322    if ( $conf['compiled_template_cache_language'] and isset($lang_info['code']) )
323    {
324      $this->smarty->compile_id .= '.'.$lang_info['code'];
325    }
326
327    $v = $this->smarty->fetch($this->files[$handle], null, null, false);
328
329    $this->smarty->compile_id = $save_compile_id;
330    $this->unload_external_filters($handle);
331
332    if ($return)
333    {
334      return $v;
335    }
336    $this->output .= $v;
337  }
338
339  /**
340   * Load the file for the handle, eventually compile the file and run the compiled
341   * code. This will print out the results of executing the template.
342   */
343  function pparse($handle)
344  {
345    $this->parse($handle, false);
346    $this->flush();
347  }
348
349  function flush()
350  {
351    if ( count($this->html_head_elements) )
352    {
353      $search = "\n</head>";
354      $pos = strpos( $this->output, $search );
355      if ($pos !== false)
356      {
357        $this->output = substr_replace( $this->output, "\n".implode( "\n", $this->html_head_elements ), $pos, 0 );
358      } //else maybe error or warning ?
359      $this->html_head_elements = array();
360    }
361
362    echo $this->output;
363    $this->output='';
364  }
365
366  /** flushes the output */
367  function p()
368  {
369    $this->flush();
370
371    if ($this->smarty->debugging)
372    {
373      global $t2;
374      $this->smarty->assign(
375        array(
376        'AAAA_DEBUG_TOTAL_TIME__' => get_elapsed_time($t2, get_moment())
377        )
378        );
379      require_once(SMARTY_CORE_DIR . 'core.display_debug_console.php');
380      echo smarty_core_display_debug_console(null, $this->smarty);
381    }
382  }
383
384  /**
385   * translate variable modifier - translates a text to the currently loaded
386   * language
387   */
388  static function mod_translate($text)
389  {
390    return l10n($text);
391  }
392
393  /**
394   * explode variable modifier - similar to php explode
395   * 'Yes;No'|@explode:';' -> array('Yes', 'No')
396   */
397  static function mod_explode($text, $delimiter=',')
398  {
399    return explode($delimiter, $text);
400  }
401
402  /**
403   * This smarty "html_head" block allows to add content just before
404   * </head> element in the output after the head has been parsed. This is
405   * handy in order to respect strict standards when <style> and <link>
406   * html elements must appear in the <head> element
407   */
408  function block_html_head($params, $content, &$smarty, &$repeat)
409  {
410    $content = trim($content);
411    if ( !empty($content) )
412    { // second call
413      $this->html_head_elements[] = $content;
414    }
415  }
416
417 /**
418   * This smarty "known_script" functions allows to insert well known java scripts
419   * such as prototype, jquery, etc... only once. Examples:
420   * {known_script id="jquery" src="{$ROOT_URL}template-common/lib/jquery.packed.js"}
421   */
422  function func_known_script($params, &$smarty )
423  {
424    if (!isset($params['id']))
425    {
426        $smarty->trigger_error("known_script: missing 'id' parameter");
427        return;
428    }
429    $id = $params['id'];
430    if (! isset( $this->known_scripts[$id] ) )
431    {
432      if (!isset($params['src']))
433      {
434          $smarty->trigger_error("known_script: missing 'src' parameter");
435          return;
436      }
437      $this->known_scripts[$id] = $params['src'];
438      $content = '<script type="text/javascript" src="'.$params['src'].'"></script>';
439      if (isset($params['now']) and $params['now'] and empty($this->output) )
440      {
441        return $content;
442      }
443      $repeat = false;
444      $this->block_html_head(null, $content, $smarty, $repeat);
445    }
446  }
447
448 /**
449   * This function allows to declare a Smarty prefilter from a plugin, thus allowing
450   * it to modify template source before compilation and without changing core files
451   * They will be processed by weight ascending.
452   * http://www.smarty.net/manual/en/advanced.features.prefilters.php
453   */
454  function set_prefilter($handle, $callback, $weight=50)
455  {
456    $this->external_filters[$handle][$weight][] = array('prefilter', $callback);
457    ksort($this->external_filters[$handle]);
458  }
459
460  function set_postfilter($handle, $callback, $weight=50)
461  {
462    $this->external_filters[$handle][$weight][] = array('postfilter', $callback);
463    ksort($this->external_filters[$handle]);
464  }
465
466  function set_outputfilter($handle, $callback, $weight=50)
467  {
468    $this->external_filters[$handle][$weight][] = array('outputfilter', $callback);
469    ksort($this->external_filters[$handle]);
470  }
471 
472 /**
473   * This function actually triggers the filters on the tpl files.
474   * Called in the parse method.
475   * http://www.smarty.net/manual/en/advanced.features.prefilters.php
476   */
477  function load_external_filters($handle)
478  {
479    if (isset($this->external_filters[$handle]))
480    {
481      $compile_id = '';
482      foreach ($this->external_filters[$handle] as $filters) 
483      {
484        foreach ($filters as $filter) 
485        {
486          list($type, $callback) = $filter;
487          $compile_id .= $type.( is_array($callback) ? implode('', $callback) : $callback );
488          call_user_func(array($this->smarty, 'register_'.$type), $callback);
489        }
490      }
491      $this->smarty->compile_id .= '.'.base_convert(crc32($compile_id), 10, 36);
492    }
493  }
494
495  function unload_external_filters($handle)
496  {
497    if (isset($this->external_filters[$handle]))
498    {
499      foreach ($this->external_filters[$handle] as $filters) 
500      {
501        foreach ($filters as $filter) 
502        {
503          list($type, $callback) = $filter;
504          call_user_func(array($this->smarty, 'unregister_'.$type), $callback);
505        }
506      }
507    }
508  }
509
510  static function prefilter_white_space($source, &$smarty)
511  {
512    $ld = $smarty->left_delimiter;
513    $rd = $smarty->right_delimiter;
514    $ldq = preg_quote($ld, '#');
515    $rdq = preg_quote($rd, '#');
516
517    $regex = array();
518    $tags = array('if', 'foreach', 'section');
519    foreach($tags as $tag)
520    {
521      array_push($regex, "#^[ \t]+($ldq$tag"."[^$ld$rd]*$rdq)\s*$#m");
522      array_push($regex, "#^[ \t]+($ldq/$tag$rdq)\s*$#m");
523    }
524    $tags = array('include', 'else', 'html_head');
525    foreach($tags as $tag)
526    {
527      array_push($regex, "#^[ \t]+($ldq$tag"."[^$ld$rd]*$rdq)\s*$#m");
528    }
529    $source = preg_replace( $regex, "$1", $source);
530    return $source;
531  }
532
533  /**
534   * Smarty prefilter to allow caching (whenever possible) language strings
535   * from templates.
536   */
537  static function prefilter_language($source, &$smarty)
538  {
539    global $lang;
540    $ldq = preg_quote($smarty->left_delimiter, '~');
541    $rdq = preg_quote($smarty->right_delimiter, '~');
542
543    $regex = "~$ldq *\'([^'$]+)\'\|@translate *$rdq~";
544    $source = preg_replace( $regex.'e', 'isset($lang[\'$1\']) ? $lang[\'$1\'] : \'$0\'', $source);
545
546    $regex = "~$ldq *\'([^'$]+)\'\|@translate\|~";
547    $source = preg_replace( $regex.'e', 'isset($lang[\'$1\']) ? \'{\'.var_export($lang[\'$1\'],true).\'|\' : \'$0\'', $source);
548
549    $regex = "~($ldq *assign +var=.+ +value=)\'([^'$]+)\'\|@translate~e";
550    $source = preg_replace( $regex, 'isset($lang[\'$2\']) ? \'$1\'.var_export($lang[\'$2\'],true) : \'$0\'', $source);
551
552    return $source;
553  }
554}
555
556
557/**
558 * This class contains basic functions that can be called directly from the
559 * templates in the form $pwg->l10n('edit')
560 */
561class PwgTemplateAdapter
562{
563  function l10n($text)
564  {
565    return l10n($text);
566  }
567
568  function l10n_dec($s, $p, $v)
569  {
570    return l10n_dec($s, $p, $v);
571  }
572
573  function sprintf()
574  {
575    $args = func_get_args();
576    return call_user_func_array('sprintf',  $args );
577  }
578}
579
580?>
Note: See TracBrowser for help on using the repository browser.