Preparing to remove the bundled PHPTAL and let the PHPTAL skins (if enabled) work...
[lhc/web/wiklou.git] / PHPTAL-NP-0.7.0 / libs / PHPTAL / Template.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
3 //
4 // Copyright (c) 2003 Laurent Bedubourg
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 //
20 // Authors: Laurent Bedubourg <laurent.bedubourg@free.fr>
21 //
22
23
24 class PHPTAL_Template
25 {
26 var $_ctx;
27 var $_code;
28 var $_codeFile;
29 var $_funcName;
30 var $_sourceFile;
31 var $_error = false;
32 var $_repository = false;
33 var $_cacheDir = false;
34 var $_parent = false;
35 var $_parentPath = false;
36 var $_prepared = false;
37 var $_cacheManager;
38
39 var $_outputMode = PHPTAL_XHTML;
40
41 var $_inputFilters;
42 var $_outputFilters;
43 var $_resolvers;
44 var $_locator;
45
46 var $_headers = false;
47
48 var $_translator;
49
50 var $_encoding = 'UTF-8';
51
52 /**
53 * Template object constructor.
54 *
55 * @param string $file -- The source file name
56 * @param string $repository optional -- Your templates root.
57 * @param string $cache_dir optional -- Intermediate php code repository.
58 */
59 function PHPTAL_Template($file, $repository=false, $cache_dir=false)
60 {
61 $this->_sourceFile = $file;
62 $this->_repository = $repository;
63
64 // deduce intermediate php code cache directory
65 if (!$cache_dir) {
66 if (defined('PHPTAL_CACHE_DIR')) {
67 $cache_dir = PHPTAL_CACHE_DIR;
68 } else {
69 $cache_dir = PHPTAL_DEFAULT_CACHE_DIR;
70 }
71 }
72 $this->_cacheDir = $cache_dir;
73
74 // instantiate a new context for this template
75 // !!! this context may be overwritten by a parent context
76 $this->_ctx = new PHPTAL_Context();
77
78 // create resolver vector and the default filesystem resolver
79 $this->_resolvers = new OArray();
80 $this->_resolvers->push(new PHPTAL_SourceResolver());
81
82 // vector for source filters
83 $this->_inputFilters = new OArray();
84 $this->_outputFilters = new OArray();
85
86 // if no cache manager set, we instantiate default dummy one
87 if (!isset($this->_cacheManager)) {
88 $this->_cacheManager = new PHPTAL_Cache();
89 }
90 }
91
92 /**
93 * Set template ouput type.
94 *
95 * Default output is XHTML, so you'll have to call this method only for
96 * specific xml documents with PHPTAL_XML parameter.
97 *
98 * @param int $mode -- output mode (PHPTAL_XML) as default system use XHTML
99 */
100 function setOutputMode($mode)
101 {
102 $this->_outputMode = $mode;
103 }
104
105 /**
106 * Replace template context with specified hashtable.
107 *
108 * @param hash hashtable -- Associative array.
109 */
110 function setAll($hash)
111 {
112 $this->_ctx = new PHPTAL_Context($hash);
113 }
114
115 /**
116 * Set a template context value.
117 *
118 * @param string $key -- The context key
119 * @param mixed $value -- The context value
120 */
121 function set($name, $value)
122 {
123 $this->_ctx->set($name, $value);
124 }
125
126 /**
127 * Set a template context value by reference.
128 *
129 * @param string $name -- The template context key
130 * @param mixed $value -- The template context value
131 */
132 function setRef($name, &$value)
133 {
134 $this->_ctx->setRef($name, $value);
135 }
136
137 /**
138 * Retrieve template context object.
139 *
140 * @return PHPTAL_Context
141 */
142 function &getContext()
143 {
144 return $this->_ctx;
145 }
146
147 /**
148 * Set the template context object.
149 *
150 * @param PHPTAL_Context $ctx -- The context object
151 */
152 function setContext(&$ctx)
153 {
154 $this->_ctx =& $ctx;
155 }
156
157 /**
158 * Set the cache manager to use for Template an Macro calls.
159 *
160 * @param PHPTAL_Cache $mngr -- Cache object that will be used to cache
161 * template and macros results.
162 */
163 function setCacheManager(&$mngr)
164 {
165 $this->_cacheManager =& $mngr;
166 }
167
168 /**
169 * Retrieve the cache manager used in this template.
170 *
171 * @return PHPTAL_Cache
172 */
173 function &getCacheManager()
174 {
175 return $this->_cacheManager;
176 }
177
178 /**
179 * Set the I18N implementation to use in this template.
180 *
181 * @param PHPTAL_I18N $tr -- I18N implementation
182 */
183 function setTranslator(&$tr)
184 {
185 $this->_translator =& $tr;
186 }
187
188 /**
189 * The translator used by this template.
190 *
191 * @return PHPTAL_I18N
192 */
193 function &getTranslator()
194 {
195 return $this->_translator;
196 }
197
198 /**
199 * Test if the template file exists.
200 * @deprecated use isValid() instead
201 * @return boolean
202 */
203 function fileExists()
204 {
205 return $this->isValid();
206 }
207
208 /**
209 * Test if the template resource exists.
210 *
211 * @return boolean
212 */
213 function isValid()
214 {
215 if (isset($this->_locator)) {
216 return true;
217 }
218
219 // use template resolvers to locate template source data
220 // in most cases, there will be only one resolver in the
221 // resolvers list (the default one) which look on the file
222 // system.
223
224 $i = $this->_resolvers->getNewIterator();
225 while ($i->isValid()) {
226 $resolver =& $i->value();
227 $locator =& $resolver->resolve($this->_sourceFile,
228 $this->_repository,
229 $this->_parentPath);
230 if ($locator && !PEAR::isError($locator)) {
231 $this->_locator =& $locator;
232 $this->_real_path = $this->_locator->realPath();
233 return true;
234 }
235 $i->next();
236 }
237 return false;
238 }
239
240 /**
241 * Add a source resolver to the template.
242 *
243 * @param PHPTAL_SourceResolver $resolver
244 * The source resolver.
245 */
246 function addSourceResolver(&$resolver)
247 {
248 $this->_resolvers->pushRef($resolver);
249 }
250
251 /**
252 * Add filter to this template input filters list.
253 *
254 * @param PHPTAL_Filter $filter
255 * A filter which will be invoked on template source.
256 */
257 function addInputFilter(&$filter)
258 {
259 $this->_inputFilters->pushRef($filter);
260 }
261
262 /**
263 * Add an output filter to this template output filters list.
264 *
265 * @param PHPTAL_Filter $filter
266 * A filter which will be invoked on template output.
267 */
268 function addOutputFilter(&$filter)
269 {
270 $this->_outputFilters->pushRef($filter);
271 }
272
273 /**
274 * Retrieve the source template real path.
275 *
276 * This method store its result internally if no $file attribute is
277 * specified (work on template internals).
278 *
279 * If a file name is specified, this method will try to locate it
280 * exploring current path (PWD), the current template location,
281 * the repository and parent template location.
282 *
283 * @param string $file optional
284 * some file name to locate.
285 *
286 * @throws FileNotFound
287 * @return string
288 */
289 function realpath($file=false)
290 {
291 // real template path
292 if (!$file) {
293 if ($this->isValid()) {
294 return $this->_real_path;
295 } else {
296 $ex = new FileNotFound($this->_sourceFile . ' not found');
297 return PEAR::raiseError($ex);
298 }
299 }
300
301 //
302 // path to some file relative to this template
303 //
304 $i = $this->_resolvers->getNewIterator();
305 while ($i->isValid()) {
306 $resolver =& $i->value();
307 $locator =& $resolver->resolve($file,
308 $this->_repository,
309 $this->_real_path);
310 if ($locator) {
311 return $locator->realPath();
312 }
313 $i->next();
314 }
315
316 $ex = new FileNotFound($file . ' not found');
317 return PEAR::raiseError($ex);
318 }
319
320 /**
321 * Set the template result encoding.
322 *
323 * Changing this encoding will change htmlentities behaviour.
324 *
325 * Example:
326 *
327 * $tpl->setEncoding('ISO-8859-1");
328 *
329 * See http://fr2.php.net/manual/en/function.htmlentities.php for a list of
330 * supported encodings.
331 *
332 * @param $enc string Template encoding
333 */
334 function setEncoding($enc)
335 {
336 $this->_encoding = $enc;
337 }
338
339 /**
340 * Retrieve the template result encoding.
341 *
342 * @return string
343 */
344 function getEncoding()
345 {
346 return $this->_encoding;
347 }
348
349 // ----------------------------------------------------------------------
350 // private / protected methods
351 // ----------------------------------------------------------------------
352
353 /**
354 * Set the called template. (internal)
355 *
356 * @access package
357 */
358 function setParent(&$tpl)
359 {
360 $this->_parent =& $tpl;
361 $this->_resolvers = $tpl->_resolvers;
362 $this->_inputFilters = $tpl->_inputFilters;
363 $this->_parentPath = $tpl->realPath();
364 $this->_cacheManager =& $tpl->getCacheManager();
365 $this->_translator =& $tpl->_translator;
366 $this->setOutputMode($tpl->_outputMode);
367 }
368
369 /**
370 * Prepare template execution.
371 *
372 * @access private
373 */
374 function _prepare()
375 {
376 if ($this->_prepared) return;
377 $this->_sourceFile = $this->realpath();
378
379 // ensure that no error remain
380 if (PEAR::isError($this->_sourceFile)) {
381 return $this->_sourceFile;
382 }
383 $this->_funcName = "tpl_" . PHPTAL_MARK . md5($this->_sourceFile);
384 $this->_codeFile = $this->_cacheDir . $this->_funcName . '.php';
385 $this->_prepared = true;
386 }
387
388 /**
389 * Generate php code from template source
390 *
391 * @access private
392 * @throws PHPTALParseException
393 */
394 function _generateCode()
395 {
396 require_once _phptal_os_path_join(dirname(__FILE__), 'Parser.php');
397
398 $parser = new PHPTAL_Parser();
399 $parser->_outputMode($this->_outputMode);
400 $data = $this->_locator->data();
401
402 // activate prefilters on data
403 $i = $this->_inputFilters->getNewIterator();
404 while ($i->isValid()){
405 $filter =& $i->value();
406 $data = $filter->filter($data);
407 $i->next();
408 }
409
410 // parse source
411 $result = $parser->parse($this->_real_path, $data);
412 if (PEAR::isError($result)) {
413 return $result;
414 }
415
416 // generate and store intermediate php code
417 $this->_code = $parser->generateCode($this->_funcName);
418 if (PEAR::isError($this->_code)) {
419 return $this->_code;
420 }
421 }
422
423 /**
424 * Load cached php code
425 *
426 * @access private
427 */
428 function _loadCachedCode()
429 {
430 include_once($this->_codeFile);
431 $this->_code = "#loaded";
432 }
433
434 /**
435 * Cache generated php code.
436 *
437 * @access private
438 */
439 function _cacheCode()
440 {
441 $fp = @fopen($this->_codeFile, "w");
442 if (!$fp) {
443 return PEAR::raiseError($php_errormsg);
444 }
445 fwrite($fp, $this->_code);
446 fclose($fp);
447 }
448
449 /**
450 * Load or generate php code.
451 *
452 * @access private
453 */
454 function _load()
455 {
456 if (isset($this->_code) && !PEAR::isError($this->_code)) {
457 return;
458 }
459
460 if (!defined('PHPTAL_NO_CACHE')
461 && file_exists($this->_codeFile)
462 && filemtime($this->_codeFile) >= $this->_locator->lastModified()) {
463 return $this->_loadCachedCode();
464 }
465
466 $err = $this->_generateCode();
467 if (PEAR::isError($err)) {
468 return $err;
469 }
470
471 $err = $this->_cacheCode();
472 if (PEAR::isError($err)) {
473 return $err;
474 }
475
476 $err = $this->_loadCachedCode();
477 if (PEAR::isError($err)) {
478 return $err;
479 }
480 }
481
482 /**
483 * Execute template with prepared context.
484 *
485 * This method execute the template file and returns the produced string.
486 *
487 * @return string
488 * @throws
489 */
490 function execute()
491 {
492 $err = $this->_prepare();
493 if (PEAR::isError($err)) {
494 $this->_ctx->_errorRaised = true;
495 return $err;
496 }
497 return $this->_cacheManager->template($this,
498 $this->_sourceFile,
499 $this->_ctx);
500 }
501
502 /**
503 * Really load/parse/execute the template and process output filters.
504 *
505 * This method is called by cache manager to retrieve the real template
506 * execution value.
507 *
508 * IMPORTANT : The result is post-filtered here !
509 *
510 * @return string
511 * @access private
512 */
513 function _process()
514 {
515 $err = $this->_load();
516 if (PEAR::isError($err)) {
517 $this->_ctx->_errorRaised = true;
518 return $err;
519 }
520
521 $this->_ctx->_errorRaised = false;
522 $func = $this->_funcName;
523 if (!function_exists($func)) {
524 $err = "Template function '$func' not found (template source : $this->_sourceFile";
525 return PEAR::raiseError($err);
526 }
527
528 // ensure translator exists
529 if (!isset($this->_translator)) {
530 $this->_translator = new PHPTAL_I18N();
531 }
532
533 $res = $func($this);
534 if ($this->_headers) {
535 $res = $this->_headers . $res;
536 }
537
538 // activate post filters
539 $i = $this->_outputFilters->getNewIterator();
540 while ($i->isValid()) {
541 $filter =& $i->value();
542 $res = $filter->filter($this, $res, PHPTAL_POST_FILTER);
543 $i->next();
544 }
545 return $res;
546 }
547
548 function _translate($key)
549 {
550 return $this->_translator->translate($key);
551 }
552
553 function _setTranslateVar($name, $value)
554 {
555 if (is_object($value)) {
556 $value = $value->toString();
557 }
558 $this->_translator->set($name, $value);
559 }
560 }
561
562 ?>