1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
25: class Horde_Template
26: {
27:
28: const TEMPLATE_STRING = '**string';
29:
30: 31: 32: 33: 34:
35: protected $_cache;
36:
37: 38: 39: 40: 41:
42: protected $_logger;
43:
44: 45: 46: 47: 48:
49: protected $_options = array();
50:
51: 52: 53: 54: 55:
56: protected $_basepath = '';
57:
58: 59: 60: 61: 62:
63: protected $_scalars = array();
64:
65: 66: 67: 68: 69:
70: protected $_arrays = array();
71:
72: 73: 74: 75: 76:
77: protected $_templateFile = null;
78:
79: 80: 81: 82: 83:
84: protected $_template = null;
85:
86: 87: 88: 89: 90:
91: protected $_foreachMap = array();
92:
93: 94: 95: 96: 97:
98: protected $_foreachVar = 0;
99:
100: 101: 102: 103: 104:
105: protected $_pregcache = array();
106:
107: 108: 109: 110: 111: 112: 113: 114: 115: 116:
117: public function __construct($params = array())
118: {
119: if (isset($params['basepath'])) {
120: $this->_basepath = $params['basepath'];
121: }
122:
123: if (isset($params['cacheob'])) {
124: $this->_cache = $params['cacheob'];
125: }
126:
127: if (isset($params['logger'])) {
128: $this->_logger = $params['logger'];
129: }
130: }
131:
132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143:
144: public function setOption($option, $val)
145: {
146: $this->_options[$option] = $val;
147: }
148:
149: 150: 151: 152: 153:
154: public function setTemplate($template)
155: {
156: $this->_template = $template;
157: $this->_parse();
158: $this->_templateFile = self::TEMPLATE_STRING;
159: }
160:
161: 162: 163: 164: 165: 166: 167:
168: public function getOption($option)
169: {
170: return isset($this->_options[$option])
171: ? $this->_options[$option]
172: : null;
173: }
174:
175: 176: 177: 178: 179: 180: 181:
182: public function set($tag, $var)
183: {
184: if (is_array($tag)) {
185: foreach ($tag as $tTag => $tVar) {
186: $this->set($tTag, $tVar);
187: }
188: } elseif (is_array($var)) {
189: $this->_arrays[$tag] = $var;
190: } else {
191: $this->_scalars[$tag] = (string) $var;
192: }
193: }
194:
195: 196: 197: 198: 199: 200: 201:
202: public function get($tag)
203: {
204: if (isset($this->_arrays[$tag])) {
205: return $this->_arrays[$tag];
206: }
207: if (isset($this->_scalars[$tag])) {
208: return $this->_scalars[$tag];
209: }
210: return null;
211: }
212:
213: 214: 215: 216: 217: 218: 219: 220:
221: public function fetch($filename = null)
222: {
223: $file = $this->_basepath . $filename;
224: $force = $this->getOption('forcecompile');
225:
226: if (!is_null($filename) && ($file != $this->_templateFile)) {
227: $this->_template = $this->_templateFile = null;
228: }
229:
230:
231: $parts = array(
232: 'horde_template',
233: filemtime($file),
234: $file
235: );
236: if ($this->getOption('gettext')) {
237: $parts[] = setlocale(LC_ALL, 0);
238: }
239: $cacheid = implode('|', $parts);
240:
241: if (!$force && is_null($this->_template) && $this->_cache) {
242: $this->_template = $this->_cache->get($cacheid, 0);
243: if ($this->_template === false) {
244: $this->_template = null;
245: }
246: }
247:
248:
249: if ($force || is_null($this->_template)) {
250: $this->_template = str_replace("\n", " \n", file_get_contents($file));
251: $this->_parse();
252: if ($this->_cache) {
253: $this->_cache->set($cacheid, $this->_template);
254: if ($this->_logger) {
255: $this->_logger->log(sprintf('Saved compiled template file for "%s".', $file), 'DEBUG');
256: }
257: }
258: }
259:
260: $this->_templateFile = $file;
261:
262:
263: if ($this->getOption('debug')) {
264: echo '<pre>' . htmlspecialchars($this->_template) . '</pre>';
265: }
266:
267: return $this->parse();
268: }
269:
270: 271: 272: 273: 274: 275: 276:
277: public function parse($contents = null)
278: {
279: if (!is_null($contents)) {
280: $this->setTemplate(str_replace("\n", " \n", $contents));
281: }
282:
283:
284: ob_start();
285: eval('?>' . $this->_template);
286: return is_null($contents)
287: ? ob_get_clean()
288: : str_replace(" \n", "\n", ob_get_clean());
289: }
290:
291: 292: 293:
294: protected function _parse()
295: {
296:
297: $this->_template = preg_replace('/\?>|<\?/', '<?php echo \'$0\' ?>', $this->_template);
298:
299:
300: if ($this->getOption('gettext')) {
301: $this->_parseGettext();
302: }
303:
304:
305: $this->_parseIf();
306:
307:
308: $this->_parseLoop();
309:
310:
311:
312: $this->_parseTags();
313:
314:
315: $this->_parseAssociativeTags();
316: }
317:
318: 319: 320: 321: 322:
323: protected function _parseGettext()
324: {
325: if (preg_match_all("/<gettext>(.+?)<\/gettext>/s", $this->_template, $matches, PREG_SET_ORDER)) {
326: $replace = array();
327: foreach ($matches as $val) {
328:
329: $code = 'echo _(\'' . str_replace("'", "\\'", $val[1]) . '\');';
330: ob_start();
331: eval($code);
332: $replace[$val[0]] = ob_get_clean();
333: }
334:
335: $this->_doReplace($replace);
336: }
337: }
338:
339: 340: 341: 342: 343:
344: protected function _parseIf($key = null)
345: {
346: $replace = array();
347:
348: foreach ($this->_doSearch('if', $key) as $val) {
349: $replace[$val[0]] = '<?php if (!empty(' . $this->_generatePHPVar('scalars', $val[1]) . ') || !empty(' . $this->_generatePHPVar('arrays', $val[1]) . ')): ?>';
350: $replace[$val[2]] = '<?php endif; ?>';
351:
352:
353: foreach ($this->_doSearch('else', $key) as $val2) {
354: $replace[$val2[0]] = '<?php else: ?>';
355: $replace[$val2[2]] = '';
356: }
357: }
358:
359: $this->_doReplace($replace);
360: }
361:
362: 363: 364: 365: 366:
367: protected function _parseLoop($key = null)
368: {
369: $replace = array();
370:
371: foreach ($this->_doSearch('loop', $key) as $val) {
372: $divider = null;
373:
374:
375: if (preg_match("/<divider:" . $val[1] . ">(.*)<\/divider:" . $val[1] . ">/sU", $this->_template, $m)) {
376: $divider = $m[1];
377: $replace[$m[0]] = '';
378: }
379:
380: if (!isset($this->_foreachMap[$val[1]])) {
381: $this->_foreachMap[$val[1]] = ++$this->_foreachVar;
382: }
383: $varId = $this->_foreachMap[$val[1]];
384: $var = $this->_generatePHPVar('arrays', $val[1]);
385:
386: $replace[$val[0]] = '<?php ' .
387: (($divider) ? '$i' . $varId . ' = count(' . $var . '); ' : '') .
388: 'foreach (' . $this->_generatePHPVar('arrays', $val[1]) . ' as $k' . $varId . ' => $v' . $varId . '): ?>';
389: $replace[$val[2]] = '<?php ' .
390: (($divider) ? 'if (--$i' . $varId . ' != 0) { echo \'' . $divider . '\'; }; ' : '') .
391: 'endforeach; ?>';
392:
393:
394: $this->_parseIf($val[1]);
395:
396:
397: $this->_parseLoop($val[1]);
398:
399:
400: $this->_parseTags($val[1]);
401: }
402:
403: $this->_doReplace($replace);
404: }
405:
406: 407: 408: 409: 410:
411: protected function _parseTags($key = null)
412: {
413: $replace = array();
414:
415: foreach ($this->_doSearch('tag', $key, true) as $val) {
416: $replace_text = '<?php ';
417: if (isset($this->_foreachMap[$val[1]])) {
418: $var = $this->_foreachMap[$val[1]];
419: $replace_text .= 'if (isset($v' . $var . ')) { echo is_array($v' . $var . ') ? $k' . $var . ' : $v' . $var . '; } else';
420: }
421: $var = $this->_generatePHPVar('scalars', $val[1]);
422: $replace[$val[0]] = $replace_text . 'if (isset(' . $var . ')) { echo ' . $var . '; } ?>';
423: }
424:
425: $this->_doReplace($replace);
426: }
427:
428: 429: 430:
431: protected function _parseAssociativeTags()
432: {
433: $replace = array();
434:
435: foreach ($this->_pregcache['tag'] as $key => $val) {
436: $parts = explode('.', $val[1]);
437: $var = '$this->_arrays[\'' . $parts[0] . '\'][\'' . $parts[1] . '\']';
438: $replace[$val[0]] = '<?php if (isset(' . $var . ')) { echo ' . $var . '; } ?>';
439: unset($this->_pregcache['tag'][$key]);
440: }
441:
442: $this->_doReplace($replace);
443: }
444:
445: 446: 447:
448: protected function _generatePHPVar($tag, $key)
449: {
450: $out = '';
451:
452: $a = explode('.', $key);
453: $a_count = count($a);
454:
455: if ($a_count == 1) {
456: switch ($tag) {
457: case 'arrays':
458: $out = '$this->_arrays';
459: break;
460:
461: case 'scalars':
462: $out = '$this->_scalars';
463: break;
464: }
465: } else {
466: $out = '$v' . $this->_foreachMap[implode('.', array_slice($a, 0, -1))];
467: }
468:
469: return $out . '[\'' . end($a) . '\']';
470: }
471:
472: 473: 474:
475: protected function _doSearch($tag, $key, $noclose = false)
476: {
477: $out = array();
478: $level = (is_null($key)) ? 0 : substr_count($key, '.') + 1;
479:
480: if (!isset($this->_pregcache[$key])) {
481: $regex = ($noclose) ?
482: "/<" . $tag . ":(.+?)\s\/>/" :
483: "/<" . $tag . ":([^>]+)>/";
484: preg_match_all($regex, $this->_template, $this->_pregcache[$tag], PREG_SET_ORDER);
485: }
486:
487: foreach ($this->_pregcache[$tag] as $pkey => $val) {
488: $val_level = substr_count($val[1], '.');
489: $add = false;
490: if (is_null($key)) {
491: $add = !$val_level;
492: } else {
493: $add = (($val_level == $level) &&
494: (strpos($val[1], $key . '.') === 0));
495: }
496: if ($add) {
497: if (!$noclose) {
498: $val[2] = '</' . $tag . ':' . $val[1] . '>';
499: }
500: $out[] = $val;
501: unset($this->_pregcache[$tag][$pkey]);
502: }
503: }
504:
505: return $out;
506: }
507:
508: 509: 510:
511: protected function _doReplace($replace)
512: {
513: if (empty($replace)) {
514: return;
515: }
516:
517: $search = array();
518:
519: foreach (array_keys($replace) as $val) {
520: $search[] = '/' . preg_quote($val, '/') . '/';
521: }
522:
523: $this->_template = preg_replace($search, array_values($replace), $this->_template);
524: }
525:
526: }
527: