+++ /dev/null
-<?php
-
-class IPF_Template_Exception extends Exception
-{
-}
-
+++ /dev/null
-<?php
-
-abstract class IPF_Template
-{
- protected $environment;
-
- public function __construct(IPF_Template_Environment $environment)
- {
- $this->environment = $environment;
- }
-
- abstract public function __toString();
-
- abstract protected function content();
-
- public function compile()
- {
- $compiler = new IPF_Template_Compiler($this->content(), $this->environment);
- return $compiler->getCompiledTemplate();
- }
-
- public function render($c=null)
- {
- $compiled_template = $this->environment->getCompiledTemplateName($this);
- ob_start();
- $t = $c;
- try {
- include $compiled_template;
- } catch (Exception $e) {
- ob_clean();
- throw $e;
- }
- $a = ob_get_contents();
- ob_end_clean();
- return $a;
- }
-}
-
+++ /dev/null
-<?php
-
-class IPF_Template_Compiler
-{
- private static $allowedInVar = null,
- $allowedInExpr = null,
- $allowedAssign = null,
- $allowedForeach = null;
-
- public static function init()
- {
- $vartype = array(T_CHARACTER, T_CONSTANT_ENCAPSED_STRING, T_DNUMBER,
- T_ENCAPSED_AND_WHITESPACE, T_LNUMBER, T_OBJECT_OPERATOR, T_STRING,
- T_WHITESPACE, T_ARRAY, T_VARIABLE);
-
- $assignOp = array(T_AND_EQUAL, T_DIV_EQUAL, T_MINUS_EQUAL, T_MOD_EQUAL,
- T_MUL_EQUAL, T_OR_EQUAL, T_PLUS_EQUAL, T_PLUS_EQUAL, T_SL_EQUAL,
- T_SR_EQUAL, T_XOR_EQUAL);
-
- $op = array(T_BOOLEAN_AND, T_BOOLEAN_OR, T_EMPTY, T_INC, T_ISSET,
- T_IS_EQUAL, T_IS_GREATER_OR_EQUAL, T_IS_IDENTICAL, T_IS_NOT_EQUAL,
- T_IS_NOT_IDENTICAL, T_IS_SMALLER_OR_EQUAL, T_LOGICAL_AND,
- T_LOGICAL_OR, T_LOGICAL_XOR, T_SR, T_SL, T_DOUBLE_ARROW);
-
- self::$allowedInVar = array_merge($vartype, $op);
- self::$allowedInExpr = array_merge($vartype, $op);
- self::$allowedAssign = array_merge($vartype, $op, $assignOp);
- self::$allowedForeach = array_merge(self::$allowedInExpr, array(T_AS));
- }
-
- protected $_literals;
-
- protected $_blockStack = array();
-
- protected $_transStack = array();
- protected $_transPlural = false;
-
- private $environment;
-
- public $templateContent = '';
-
- public $_extendBlocks = array();
-
- function __construct($templateContent, $environment)
- {
- $this->templateContent = $templateContent;
- $this->environment = $environment;
- }
-
- private function compile()
- {
- $this->compileBlocks();
- $tplcontent = $this->templateContent;
- $tplcontent = preg_replace('!{\*(.*?)\*}!s', '', $tplcontent);
- $tplcontent = preg_replace('!<\?php(.*?)\?>!s', '', $tplcontent);
- preg_match_all('!{literal}(.*?){/literal}!s', $tplcontent, $_match);
- $this->_literals = $_match[1];
- $tplcontent = preg_replace("!{literal}(.*?){/literal}!s", '{literal}', $tplcontent);
- // Core regex to parse the template
- $result = preg_replace_callback('/{((.).*?)}/s',
- array($this, '_callback'),
- $tplcontent);
- if (count($this->_blockStack)) {
- trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
- }
- return $result;
- }
-
- public function getCompiledTemplate()
- {
- $result = $this->compile();
- $result = str_replace(array('?><?php', '<?php ?>', '<?php ?>'), '', $result);
- $result = str_replace("?>\n", "?>\n\n", $result);
- return $result;
- }
-
- private static function toplevelBlocks($content)
- {
- preg_match_all("!{block\s*([^} \r\t\n]*)}|{/block}!", $content, $tags, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
-
- $count = 0;
- $result = array();
- foreach ($tags as $tag) {
- $text = $tag[0][0];
-
- if (substr($text, 0, 6) === '{block') {
- if ($count == 0) {
- $result[] = array(
- 'name' => $tag[1][0],
- 'start' => $tag[1][1] + strlen($tag[1][0]) + 1,
- );
- }
- $count++;
- } elseif (substr($text, 0, 7) === '{/block') {
- $count--;
- if ($count == 0) {
- $result[count($result)-1]['finish'] = $tag[0][1];
- }
- }
- }
-
- if ($count != 0)
- throw new IPF_Template_Exception('Blocks are not nested properly.');
-
- return $result;
- }
-
- function compileBlocks()
- {
- $tplcontent = $this->templateContent;
- $extendedTemplate = '';
- // Match extends on the first line of the template
- if (preg_match("!{extends\s['\"](.*?)['\"]}!", $tplcontent, $_match)) {
- $extendedTemplate = $_match[1];
- }
- // Get the blocks in the current template
- $blocks = self::toplevelBlocks($this->templateContent);
- $cnt = count($blocks);
- // Compile the blocks
- for ($i=0; $i<$cnt; $i++) {
- $blockName = $blocks[$i]['name'];
- if (!isset($this->_extendBlocks[$blockName]) or false !== strpos($this->_extendBlocks[$blockName], '~~{~~superblock~~}~~')) {
- $compiler = clone($this);
- $compiler->templateContent = substr($this->templateContent, $blocks[$i]['start'], $blocks[$i]['finish'] - $blocks[$i]['start']);
- $_tmp = $compiler->compile();
- if (!isset($this->_extendBlocks[$blockName])) {
- $this->_extendBlocks[$blockName] = $_tmp;
- } else {
- $this->_extendBlocks[$blockName] = str_replace('~~{~~superblock~~}~~', $_tmp, $this->_extendBlocks[$blockName]);
- }
- }
- }
- if (strlen($extendedTemplate) > 0) {
- // The template of interest is now the extended template
- // as we are not in a base template
- $this->templateContent = $this->environment->loadTemplateFile($extendedTemplate);
- $this->compileBlocks(); //It will recurse to the base template.
- } else {
- // Replace the current blocks by a place holder
- if ($cnt) {
- $this->templateContent = preg_replace("!{block\s(\S+?)}(.*?){/block}!s", "{block $1}", $tplcontent, -1);
- }
- }
- }
-
- private function _callback($matches)
- {
- list(,$tag, $firstcar) = $matches;
- if (!preg_match('/^\$|[\'"]|[a-zA-Z\/]$/', $firstcar)) {
- trigger_error(sprintf('Invalid tag syntax: %s', $tag), E_USER_ERROR);
- return '';
- }
- if (in_array($firstcar, array('$', '\'', '"'))) {
- if ('blocktrans' !== end($this->_blockStack)) {
- return '<?php echo IPF_Template_SafeString::value('.$this->_parseVariable($tag).'); ?>';
- } else {
- $tok = explode('|', $tag);
- $this->_transStack[substr($tok[0], 1)] = $this->_parseVariable($tag);
- return '%%'.substr($tok[0], 1).'%%';
- }
- } else {
- if (!preg_match('/^(\/?[a-zA-Z0-9_]+)(?:(?:\s+(.*))|(?:\((.*)\)))?$/', $tag, $m)) {
- trigger_error(sprintf('Invalid function syntax: %s', $tag), E_USER_ERROR);
- return '';
- }
- if (count($m) == 4){
- $m[2] = $m[3];
- }
- if (!isset($m[2])) $m[2] = '';
- if($m[1] == 'ldelim') return '{';
- if($m[1] == 'rdelim') return '}';
- if ($m[1] != 'include') {
- return '<?php '.$this->_parseFunction($m[1], $m[2]).'?>';
- } else {
- return $this->_parseFunction($m[1], $m[2]);
- }
- }
- }
-
- private function _parseVariable($expr)
- {
- $tok = explode('|', $expr);
- $res = $this->_parseFinal(array_shift($tok), self::$allowedInVar);
- foreach ($tok as $modifier) {
- if (!preg_match('/^(\w+)(?:\:(.*))?$/', $modifier, $m)) {
- trigger_error(sprintf('Invalid modifier syntax: (%s) %s', $expr, $modifier), E_USER_ERROR);
- return '';
- }
-
- if ($this->environment->hasModifier($m[1])) {
- trigger_error(sprintf('Unknown modifier: (%s) %s', $expr, $m[1]), E_USER_ERROR);
- return '';
- }
-
- $mod = $this->environment->getModifier($m[1]);
-
- if (isset($m[2])) {
- $res = $mod.'('.$res.','.$m[2].')';
- } else {
- $res = $mod.'('.$res.')';
- }
- }
- return $res;
- }
-
- private function _parseFunction($name, $args)
- {
- switch ($name) {
- case 'if':
- $res = 'if ('.$this->_parseFinal($args, self::$allowedInExpr).'): ';
- $this->_blockStack[] = 'if';
- break;
- case 'else':
- if (end($this->_blockStack) != 'if') {
- trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
- }
- $res = 'else: ';
- break;
- case 'elseif':
- if (end($this->_blockStack) != 'if') {
- trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
- }
- $res = 'elseif('.$this->_parseFinal($args, self::$allowedInExpr).'):';
- break;
- case 'foreach':
- $tokens = $this->tokenize($args, self::$allowedForeach, array(';'));
- $asFound = false;
- $scopeVars = array('foreach_counter0', 'foreach_counter', 'foreach_first');
- foreach ($tokens as $token) {
- if (!is_array($token))
- continue;
- if ($asFound) {
- if ($token[0] == T_VARIABLE)
- $scopeVars[] = substr($token[1], 1);
- } else {
- if ($token[0] == T_AS)
- $asFound = true;
- }
- }
- $res =
- '$t->push(\'' . implode('\', \'', $scopeVars) . '\'); ' .
- '$t->_vars[\'foreach_counter0\'] = 0;' .
- '$t->_vars[\'foreach_counter\'] = 1;' .
- '$t->_vars[\'foreach_first\'] = true;' .
- 'foreach ('.$this->compileExpression($tokens).'): ';
- $this->_blockStack[] = 'foreach';
- break;
- case 'while':
- $res = 'while('.$this->_parseFinal($args, self::$allowedInExpr).'):';
- $this->_blockStack[] = 'while';
- break;
- case '/foreach':
- if(end($this->_blockStack) != 'foreach'){
- trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
- }
- array_pop($this->_blockStack);
- $res =
- '$t->_vars[\'foreach_counter0\'] = $t->_vars[\'foreach_counter0\'] + 1;' .
- '$t->_vars[\'foreach_counter\'] = $t->_vars[\'foreach_counter\'] + 1;' .
- '$t->_vars[\'foreach_first\'] = false;' .
- 'endforeach; ' .
- '$t->pop(); ';
- break;
- case '/if':
- case '/while':
- $short = substr($name,1);
- if(end($this->_blockStack) != $short){
- trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
- }
- array_pop($this->_blockStack);
- $res = 'end'.$short.'; ';
- break;
- case 'assign':
- $res = $this->_parseFinal($args, self::$allowedAssign).'; ';
- break;
- case 'literal':
- if (count($this->_literals)) {
- $res = '?>'.array_shift($this->_literals).'<?php ';
- } else {
- trigger_error('End tag of a block missing: literal', E_USER_ERROR);
- }
- break;
- case '/literal':
- trigger_error('Start tag of a block missing: literal', E_USER_ERROR);
- break;
- case 'block':
- if (isset($this->_extendBlocks[$args]))
- $res = '?>'.$this->_extendBlocks[$args].'<?php ';
- else
- $res = '';
- break;
- case 'superblock':
- $res = '?>~~{~~superblock~~}~~<?php ';
- break;
- case 'trans':
- $argfct = $this->_parseFinal($args, self::$allowedAssign);
- $res = 'echo(__('.$argfct.'));';
- break;
- case 'blocktrans':
- $this->_blockStack[] = 'blocktrans';
- $res = '';
- $this->_transStack = array();
- if ($args) {
- $this->_transPlural = true;
- $_args = $this->_parseFinal($args, self::$allowedAssign, array(';', '[', ']'));
- $res .= '$_b_t_c='.trim($_args).'; ';
- }
- $res .= 'ob_start(); ';
- break;
- case '/blocktrans':
- $short = substr($name,1);
- if(end($this->_blockStack) != $short){
- trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
- }
- $res = '';
- if ($this->_transPlural) {
- $res .= '$_b_t_p=ob_get_contents(); ob_end_clean(); echo(';
- $res .= 'IPF_Translation::sprintf(_n($_b_t_s, $_b_t_p, $_b_t_c), array(';
- $_tmp = array();
- foreach ($this->_transStack as $key=>$_trans) {
- $_tmp[] = '\''.addslashes($key).'\' => IPF_Template_SafeString::value('.$_trans.')';
- }
- $res .= implode(', ', $_tmp);
- unset($_trans, $_tmp);
- $res .= ')));';
- $this->_transStack = array();
- } else {
- $res .= '$_b_t_s=ob_get_contents(); ob_end_clean(); ';
- if (count($this->_transStack) == 0) {
- $res .= 'echo(__($_b_t_s)); ';
- } else {
- $res .= 'echo(IPF_Translation::sprintf(__($_b_t_s), array(';
- $_tmp = array();
- foreach ($this->_transStack as $key=>$_trans) {
- $_tmp[] = '\''.addslashes($key).'\' => IPF_Template_SafeString::value('.$_trans.')';
- }
- $res .= implode(', ', $_tmp);
- unset($_trans, $_tmp);
- $res .= '))); ';
- $this->_transStack = array();
- }
- }
- $this->_transPlural = false;
- array_pop($this->_blockStack);
- break;
- case 'plural':
- $res = '$_b_t_s=ob_get_contents(); ob_end_clean(); ob_start(); ';
- break;
- case 'include':
- $argfct = preg_replace('!^[\'"](.*)[\'"]$!', '$1', $args);
- $includedTemplateContent = $this->environment->loadTemplateFile($argfct);
- $_comp = new IPF_Template_Compiler($includedTemplateContent, $this->environment);
- $res = $_comp->compile();
- break;
- default:
- $_start = true;
- $oname = $name;
- if (substr($name, 0, 1) == '/') {
- $_start = false;
- $name = substr($name, 1);
- }
- // Here we should allow custom blocks.
-
- // Here we start the template tag calls at the template tag
- // {tag ...} is not a block, so it must be a function.
- if (!$this->environment->isTagAllowed($name)) {
- trigger_error(sprintf('The function tag "%s" is not allowed.', $name), E_USER_ERROR);
- }
- // $argfct is a string that can be copy/pasted in the PHP code
- // but we need the array of args.
- $argfct = $this->_parseFinal($args, self::$allowedAssign);
-
- $res = '$_extra_tag = new '.$this->environment->getTag($name).'($t);';
- if ($_start) {
- $res .= '$_extra_tag->start('.$argfct.'); ';
- } else {
- $res .= '$_extra_tag->end('.$argfct.'); ';
- }
- }
- return $res;
- }
-
- private function _parseFinal($string, &$allowed, $exceptchar=array(';'))
- {
- $tokens = $this->tokenize($string, $allowed, $exceptchar);
- return $this->compileExpression($tokens);
- }
-
- private function tokenize($string, $allowed, $exceptchar)
- {
- $tokens = token_get_all('<?php '.$string.'?>');
- $result = array();
- foreach ($tokens as $tok) {
- if (is_array($tok)) {
- list($type, $str) = $tok;
- if ($type == T_OPEN_TAG || $type == T_CLOSE_TAG) {
- // skip
- } elseif (in_array($type, $allowed)) {
- $result[] = $tok;
- } else {
- throw new IPF_Template_Exception(sprintf('Invalid syntax: (%s) %s.', $string, $str));
- }
- } else {
- if (in_array($tok, $exceptchar)) {
- trigger_error(sprintf('Invalid character: (%s) %s.', $string, $tok), E_USER_ERROR);
- } else {
- $result[] = $tok;
- }
- }
- }
- return $result;
- }
-
- private function compileExpression($tokens)
- {
- $result = '';
- foreach ($tokens as $tok) {
- if (is_array($tok)) {
- list($type, $str) = $tok;
- if ($type == T_VARIABLE) {
- $result .= '$t->_vars[\''.substr($str, 1).'\']';
- } else {
- $result .= $str;
- }
- } else {
- if ($tok == '.') {
- $result .= '->';
- } elseif ($tok == '~') {
- $result .= '.';
- } else {
- $result .= $tok;
- }
- }
- }
- return $result;
- }
-}
-
-IPF_Template_Compiler::init();
-
+++ /dev/null
-<?php
-
-class IPF_Template_Context
-{
- public $_vars;
- private $stack = array();
-
- function __construct($vars=array())
- {
- $this->_vars = new IPF_Template_ContextVars($vars);
- }
-
- function get($var)
- {
- if (isset($this->_vars[$var])) {
- return $this->_vars[$var];
- }
- return '';
- }
-
- function set($var, $value)
- {
- $this->_vars[$var] = $value;
- }
-
- public function push()
- {
- $vars = func_get_args();
- $frame = array();
- foreach ($vars as $var) {
- $frame[$var] = $this->get($var);
- }
- $this->stack[] = $frame;
- }
-
- public function pop()
- {
- $frame = array_pop($this->stack);
- foreach ($frame as $var => $val)
- $this->set($var, $val);
- }
-}
-
+++ /dev/null
-<?php
-
-class IPF_Template_ContextVars extends ArrayObject
-{
- function offsetGet($prop)
- {
- if (!$this->offsetExists($prop)) {
- return '';
- }
- return parent::offsetGet($prop);
- }
-
- function __get($prop)
- {
- if (isset($this->$prop)) {
- return $this->$prop;
- } else {
- return $this->offsetGet($prop);
- }
- }
-
- function __toString()
- {
- return var_export($this, true);
- }
-}
+++ /dev/null
-<?php
-
-abstract class IPF_Template_Environment
-{
- abstract public function loadTemplateFile($filename);
-
- abstract public function getCompiledTemplateName($template);
-
- // Dictionary of allowed tags (tag name => class)
- public $tags = array();
-
- public function isTagAllowed($name)
- {
- return isset($this->tags[$name]);
- }
-
- public function getTag($name)
- {
- if (isset($this->tags[$name]))
- return $this->tags[$name];
- else
- throw new IPF_Template_Exception('Tag '.$name.' is not defined.');
- }
-
- // Dictionary of modifiers (modifier name => function)
- public $modifiers = array(
- 'upper' => 'strtoupper',
- 'lower' => 'strtolower',
- 'escxml' => 'htmlspecialchars',
- 'escape' => 'IPF_Template_Modifier::escape',
- 'strip_tags' => 'strip_tags',
- 'escurl' => 'rawurlencode',
- 'capitalize' => 'ucwords',
- 'debug' => 'print_r', // Not var_export because of recursive issues.
- 'fulldebug' => 'var_export',
- 'count' => 'count',
- 'nl2br' => 'nl2br',
- 'trim' => 'trim',
- 'unsafe' => 'IPF_Template_SafeString::markSafe',
- 'safe' => 'IPF_Template_SafeString::markSafe',
- 'date' => 'IPF_Template_Modifier::dateFormat',
- 'time' => 'IPF_Template_Modifier::timeFormat',
- 'floatformat' => 'IPF_Template_Modifier::floatFormat',
- 'limit_words' => 'IPF_Template_Modifier::limitWords',
- 'limit_chars' => 'IPF_Template_Modifier::limitCharacters',
- );
-
- public function hasModifier($name)
- {
- return isset($this->modifiers[$name]);
- }
-
- public function getModifier($name)
- {
- if (isset($this->modifiers[$name]))
- return $this->modifiers[$name];
- else
- throw new IPF_Template_Exception('Modifier '.$name.' is not defined.');
- }
-}
-
+++ /dev/null
-<?php
-
-class IPF_Template_Environment_FileSystem extends IPF_Template_Environment
-{
- public $folders = array();
- public $cache = '/tmp';
- public $debug = false;
-
- public function loadTemplateFile($filename)
- {
- // FIXME: Very small security check, could be better.
- if (strpos($filename, '..') !== false) {
- throw new IPF_Template_Exception(sprintf('Template file contains invalid characters: %s', $filename));
- }
- foreach ($this->folders as $folder) {
- if (file_exists($folder.'/'.$filename)) {
- return file_get_contents($folder.'/'.$filename);
- }
- }
- throw new IPF_Template_Exception(sprintf('Template file not found: %s', $filename));
- }
-
- public function getCompiledTemplateName($template)
- {
- $_tmp = var_export($this->folders, true);
- $filename = $this->cache.'/IPF_Template-'.md5($_tmp.(string)$template).'.phps';
- if ($this->debug or !file_exists($filename)) {
- $this->write($filename, $template->compile());
- }
- return $filename;
- }
-
- private function write($filename, $content)
- {
- $fp = @fopen($filename, 'a');
- if ($fp !== false) {
- flock($fp, LOCK_EX);
- ftruncate($fp, 0);
- rewind($fp);
- fwrite($fp, $content, strlen($content));
- flock($fp, LOCK_UN);
- fclose($fp);
- @chmod($filename, 0777);
- return true;
- } else {
- throw new IPF_Template_Exception(sprintf('Cannot write the compiled template: %s', $filename));
- }
- return false;
- }
-}
-
+++ /dev/null
-<?php
-
-class IPF_Template_File extends IPF_Template
-{
- private $tpl;
-
- function __construct($template, IPF_Template_Environment $environment)
- {
- parent::__construct($environment);
- $this->tpl = $template;
- }
-
- public function __toString()
- {
- return $this->tpl;
- }
-
- protected function content()
- {
- return $this->environment->loadTemplateFile($this->tpl);
- }
-}
-
+++ /dev/null
-<?php
-
-final class IPF_Template_Modifier
-{
- public static function escape($string)
- {
- return htmlspecialchars((string)$string, ENT_COMPAT, 'UTF-8');
- }
-
- public static function dateFormat($date, $format='%b %e, %Y')
- {
- if (substr(PHP_OS,0,3) == 'WIN') {
- $_win_from = array ('%e', '%T', '%D');
- $_win_to = array ('%#d', '%H:%M:%S', '%m/%d/%y');
- $format = str_replace($_win_from, $_win_to, $format);
- }
- $date = date('Y-m-d H:i:s', strtotime($date.' GMT'));
- return strftime($format, strtotime($date));
- }
-
- public static function timeFormat($time, $format='Y-m-d H:i:s')
- {
- return date($format, $time);
- }
-
- public static function floatFormat($number, $decimals=2, $dec_point='.', $thousands_sep=',')
- {
- return number_format($number, $decimals, $dec_point, $thousands_sep);
- }
-
- /**
- * Word Limiter
- *
- * Limits a string to X number of words.
- *
- * @param string
- * @param integer
- * @param string the end character. Usually an ellipsis
- * @return string
- */
- public static function limitWords($str, $limit=100, $end_char='…')
- {
- if (trim($str) == '')
- return $str;
- preg_match('/^\s*+(?:\S++\s*+){1,'.(int) $limit.'}/', $str, $matches);
- if (strlen($str) == strlen($matches[0]))
- $end_char = '';
- return rtrim($matches[0]).$end_char;
- }
-
- /**
- * Character Limiter
- *
- * Limits the string based on the character count. Preserves complete words
- * so the character count may not be exactly as specified.
- *
- * @param string
- * @param integer
- * @param string the end character. Usually an ellipsis
- * @return string
- */
- function limitCharacters($str, $n=500, $end_char='…')
- {
- if (strlen($str) < $n)
- return $str;
-
- $str = preg_replace("/\s+/", ' ', str_replace(array("\r\n", "\r", "\n"), ' ', $str));
-
- if (strlen($str) <= $n)
- return $str;
-
- $out = "";
- foreach (explode(' ', trim($str)) as $val) {
- $out .= $val.' ';
- if (strlen($out) >= $n) {
- $out = trim($out);
- return (strlen($out) == strlen($str)) ? $out : $out.$end_char;
- }
- }
- }
-}
-
+++ /dev/null
-<?php
-
-class IPF_Template_SafeString
-{
- public $value = '';
-
- public static function value($mixed, $safe=false)
- {
- if (is_object($mixed) and 'IPF_Template_SafeString' == get_class($mixed))
- return $mixed->value;
- if ($safe)
- return $mixed;
- return htmlspecialchars($mixed, ENT_COMPAT, 'UTF-8');
- }
-
- function __construct($mixed, $safe=false)
- {
- $this->value = self::value($mixed, $safe);
- }
-
- function __toString()
- {
- return $this->value;
- }
-
- public static function markSafe($string)
- {
- return new IPF_Template_SafeString($string, true);
- }
-}
-
+++ /dev/null
-<?php
-
-class IPF_Template_String extends IPF_Template
-{
- private $tpl;
-
- function __construct($template, IPF_Template_Environment $environment)
- {
- parent::__construct($environment);
- $this->tpl = $template;
- }
-
- public function __toString()
- {
- return $this->tpl;
- }
-
- protected function content()
- {
- return $this->tpl;
- }
-}
-
+++ /dev/null
-<?php
-
-class IPF_Template_Tag
-{
- protected $context;
-
- function __construct($context=null)
- {
- $this->context = $context;
- }
-
- // variable list of arguments
- function start()
- {
- }
-
- // variable list of arguments
- function end()
- {
- }
-}
-
--- /dev/null
+<?php
+
+class IPF_Template_Compiler
+{
+ private static $allowedInVar = null,
+ $allowedInExpr = null,
+ $allowedAssign = null,
+ $allowedForeach = null;
+
+ public static function init()
+ {
+ $vartype = array(T_CHARACTER, T_CONSTANT_ENCAPSED_STRING, T_DNUMBER,
+ T_ENCAPSED_AND_WHITESPACE, T_LNUMBER, T_OBJECT_OPERATOR, T_STRING,
+ T_WHITESPACE, T_ARRAY, T_VARIABLE);
+
+ $assignOp = array(T_AND_EQUAL, T_DIV_EQUAL, T_MINUS_EQUAL, T_MOD_EQUAL,
+ T_MUL_EQUAL, T_OR_EQUAL, T_PLUS_EQUAL, T_PLUS_EQUAL, T_SL_EQUAL,
+ T_SR_EQUAL, T_XOR_EQUAL);
+
+ $op = array(T_BOOLEAN_AND, T_BOOLEAN_OR, T_EMPTY, T_INC, T_ISSET,
+ T_IS_EQUAL, T_IS_GREATER_OR_EQUAL, T_IS_IDENTICAL, T_IS_NOT_EQUAL,
+ T_IS_NOT_IDENTICAL, T_IS_SMALLER_OR_EQUAL, T_LOGICAL_AND,
+ T_LOGICAL_OR, T_LOGICAL_XOR, T_SR, T_SL, T_DOUBLE_ARROW);
+
+ self::$allowedInVar = array_merge($vartype, $op);
+ self::$allowedInExpr = array_merge($vartype, $op);
+ self::$allowedAssign = array_merge($vartype, $op, $assignOp);
+ self::$allowedForeach = array_merge(self::$allowedInExpr, array(T_AS));
+ }
+
+ protected $_literals;
+
+ protected $_blockStack = array();
+
+ protected $_transStack = array();
+ protected $_transPlural = false;
+
+ private $environment;
+
+ public $templateContent = '';
+
+ public $_extendBlocks = array();
+
+ function __construct($templateContent, $environment)
+ {
+ $this->templateContent = $templateContent;
+ $this->environment = $environment;
+ }
+
+ private function compile()
+ {
+ $this->compileBlocks();
+ $tplcontent = $this->templateContent;
+ $tplcontent = preg_replace('!{\*(.*?)\*}!s', '', $tplcontent);
+ $tplcontent = preg_replace('!<\?php(.*?)\?>!s', '', $tplcontent);
+ preg_match_all('!{literal}(.*?){/literal}!s', $tplcontent, $_match);
+ $this->_literals = $_match[1];
+ $tplcontent = preg_replace("!{literal}(.*?){/literal}!s", '{literal}', $tplcontent);
+ // Core regex to parse the template
+ $result = preg_replace_callback('/{((.).*?)}/s',
+ array($this, '_callback'),
+ $tplcontent);
+ if (count($this->_blockStack)) {
+ trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
+ }
+ return $result;
+ }
+
+ public function getCompiledTemplate()
+ {
+ $result = $this->compile();
+ $result = str_replace(array('?><?php', '<?php ?>', '<?php ?>'), '', $result);
+ $result = str_replace("?>\n", "?>\n\n", $result);
+ return $result;
+ }
+
+ private static function toplevelBlocks($content)
+ {
+ preg_match_all("!{block\s*([^} \r\t\n]*)}|{/block}!", $content, $tags, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
+
+ $count = 0;
+ $result = array();
+ foreach ($tags as $tag) {
+ $text = $tag[0][0];
+
+ if (substr($text, 0, 6) === '{block') {
+ if ($count == 0) {
+ $result[] = array(
+ 'name' => $tag[1][0],
+ 'start' => $tag[1][1] + strlen($tag[1][0]) + 1,
+ );
+ }
+ $count++;
+ } elseif (substr($text, 0, 7) === '{/block') {
+ $count--;
+ if ($count == 0) {
+ $result[count($result)-1]['finish'] = $tag[0][1];
+ }
+ }
+ }
+
+ if ($count != 0)
+ throw new IPF_Template_Exception('Blocks are not nested properly.');
+
+ return $result;
+ }
+
+ function compileBlocks()
+ {
+ $tplcontent = $this->templateContent;
+ $extendedTemplate = '';
+ // Match extends on the first line of the template
+ if (preg_match("!{extends\s['\"](.*?)['\"]}!", $tplcontent, $_match)) {
+ $extendedTemplate = $_match[1];
+ }
+ // Get the blocks in the current template
+ $blocks = self::toplevelBlocks($this->templateContent);
+ $cnt = count($blocks);
+ // Compile the blocks
+ for ($i=0; $i<$cnt; $i++) {
+ $blockName = $blocks[$i]['name'];
+ if (!isset($this->_extendBlocks[$blockName]) or false !== strpos($this->_extendBlocks[$blockName], '~~{~~superblock~~}~~')) {
+ $compiler = clone($this);
+ $compiler->templateContent = substr($this->templateContent, $blocks[$i]['start'], $blocks[$i]['finish'] - $blocks[$i]['start']);
+ $_tmp = $compiler->compile();
+ if (!isset($this->_extendBlocks[$blockName])) {
+ $this->_extendBlocks[$blockName] = $_tmp;
+ } else {
+ $this->_extendBlocks[$blockName] = str_replace('~~{~~superblock~~}~~', $_tmp, $this->_extendBlocks[$blockName]);
+ }
+ }
+ }
+ if (strlen($extendedTemplate) > 0) {
+ // The template of interest is now the extended template
+ // as we are not in a base template
+ $this->templateContent = $this->environment->loadTemplateFile($extendedTemplate);
+ $this->compileBlocks(); //It will recurse to the base template.
+ } else {
+ // Replace the current blocks by a place holder
+ if ($cnt) {
+ $this->templateContent = preg_replace("!{block\s(\S+?)}(.*?){/block}!s", "{block $1}", $tplcontent, -1);
+ }
+ }
+ }
+
+ private function _callback($matches)
+ {
+ list(,$tag, $firstcar) = $matches;
+ if (!preg_match('/^\$|[\'"]|[a-zA-Z\/]$/', $firstcar)) {
+ trigger_error(sprintf('Invalid tag syntax: %s', $tag), E_USER_ERROR);
+ return '';
+ }
+ if (in_array($firstcar, array('$', '\'', '"'))) {
+ if ('blocktrans' !== end($this->_blockStack)) {
+ return '<?php echo IPF_Template_SafeString::value('.$this->_parseVariable($tag).'); ?>';
+ } else {
+ $tok = explode('|', $tag);
+ $this->_transStack[substr($tok[0], 1)] = $this->_parseVariable($tag);
+ return '%%'.substr($tok[0], 1).'%%';
+ }
+ } else {
+ if (!preg_match('/^(\/?[a-zA-Z0-9_]+)(?:(?:\s+(.*))|(?:\((.*)\)))?$/', $tag, $m)) {
+ trigger_error(sprintf('Invalid function syntax: %s', $tag), E_USER_ERROR);
+ return '';
+ }
+ if (count($m) == 4){
+ $m[2] = $m[3];
+ }
+ if (!isset($m[2])) $m[2] = '';
+ if($m[1] == 'ldelim') return '{';
+ if($m[1] == 'rdelim') return '}';
+ if ($m[1] != 'include') {
+ return '<?php '.$this->_parseFunction($m[1], $m[2]).'?>';
+ } else {
+ return $this->_parseFunction($m[1], $m[2]);
+ }
+ }
+ }
+
+ private function _parseVariable($expr)
+ {
+ $tok = explode('|', $expr);
+ $res = $this->_parseFinal(array_shift($tok), self::$allowedInVar);
+ foreach ($tok as $modifier) {
+ if (!preg_match('/^(\w+)(?:\:(.*))?$/', $modifier, $m)) {
+ trigger_error(sprintf('Invalid modifier syntax: (%s) %s', $expr, $modifier), E_USER_ERROR);
+ return '';
+ }
+
+ if ($this->environment->hasModifier($m[1])) {
+ trigger_error(sprintf('Unknown modifier: (%s) %s', $expr, $m[1]), E_USER_ERROR);
+ return '';
+ }
+
+ $mod = $this->environment->getModifier($m[1]);
+
+ if (isset($m[2])) {
+ $res = $mod.'('.$res.','.$m[2].')';
+ } else {
+ $res = $mod.'('.$res.')';
+ }
+ }
+ return $res;
+ }
+
+ private function _parseFunction($name, $args)
+ {
+ switch ($name) {
+ case 'if':
+ $res = 'if ('.$this->_parseFinal($args, self::$allowedInExpr).'): ';
+ $this->_blockStack[] = 'if';
+ break;
+ case 'else':
+ if (end($this->_blockStack) != 'if') {
+ trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
+ }
+ $res = 'else: ';
+ break;
+ case 'elseif':
+ if (end($this->_blockStack) != 'if') {
+ trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
+ }
+ $res = 'elseif('.$this->_parseFinal($args, self::$allowedInExpr).'):';
+ break;
+ case 'foreach':
+ $tokens = $this->tokenize($args, self::$allowedForeach, array(';'));
+ $asFound = false;
+ $scopeVars = array('foreach_counter0', 'foreach_counter', 'foreach_first');
+ foreach ($tokens as $token) {
+ if (!is_array($token))
+ continue;
+ if ($asFound) {
+ if ($token[0] == T_VARIABLE)
+ $scopeVars[] = substr($token[1], 1);
+ } else {
+ if ($token[0] == T_AS)
+ $asFound = true;
+ }
+ }
+ $res =
+ '$t->push(\'' . implode('\', \'', $scopeVars) . '\'); ' .
+ '$t->_vars[\'foreach_counter0\'] = 0;' .
+ '$t->_vars[\'foreach_counter\'] = 1;' .
+ '$t->_vars[\'foreach_first\'] = true;' .
+ 'foreach ('.$this->compileExpression($tokens).'): ';
+ $this->_blockStack[] = 'foreach';
+ break;
+ case 'while':
+ $res = 'while('.$this->_parseFinal($args, self::$allowedInExpr).'):';
+ $this->_blockStack[] = 'while';
+ break;
+ case '/foreach':
+ if(end($this->_blockStack) != 'foreach'){
+ trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
+ }
+ array_pop($this->_blockStack);
+ $res =
+ '$t->_vars[\'foreach_counter0\'] = $t->_vars[\'foreach_counter0\'] + 1;' .
+ '$t->_vars[\'foreach_counter\'] = $t->_vars[\'foreach_counter\'] + 1;' .
+ '$t->_vars[\'foreach_first\'] = false;' .
+ 'endforeach; ' .
+ '$t->pop(); ';
+ break;
+ case '/if':
+ case '/while':
+ $short = substr($name,1);
+ if(end($this->_blockStack) != $short){
+ trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
+ }
+ array_pop($this->_blockStack);
+ $res = 'end'.$short.'; ';
+ break;
+ case 'assign':
+ $res = $this->_parseFinal($args, self::$allowedAssign).'; ';
+ break;
+ case 'literal':
+ if (count($this->_literals)) {
+ $res = '?>'.array_shift($this->_literals).'<?php ';
+ } else {
+ trigger_error('End tag of a block missing: literal', E_USER_ERROR);
+ }
+ break;
+ case '/literal':
+ trigger_error('Start tag of a block missing: literal', E_USER_ERROR);
+ break;
+ case 'block':
+ if (isset($this->_extendBlocks[$args]))
+ $res = '?>'.$this->_extendBlocks[$args].'<?php ';
+ else
+ $res = '';
+ break;
+ case 'superblock':
+ $res = '?>~~{~~superblock~~}~~<?php ';
+ break;
+ case 'trans':
+ $argfct = $this->_parseFinal($args, self::$allowedAssign);
+ $res = 'echo(__('.$argfct.'));';
+ break;
+ case 'blocktrans':
+ $this->_blockStack[] = 'blocktrans';
+ $res = '';
+ $this->_transStack = array();
+ if ($args) {
+ $this->_transPlural = true;
+ $_args = $this->_parseFinal($args, self::$allowedAssign, array(';', '[', ']'));
+ $res .= '$_b_t_c='.trim($_args).'; ';
+ }
+ $res .= 'ob_start(); ';
+ break;
+ case '/blocktrans':
+ $short = substr($name,1);
+ if(end($this->_blockStack) != $short){
+ trigger_error(sprintf('End tag of a block missing: %s', end($this->_blockStack)), E_USER_ERROR);
+ }
+ $res = '';
+ if ($this->_transPlural) {
+ $res .= '$_b_t_p=ob_get_contents(); ob_end_clean(); echo(';
+ $res .= 'IPF_Translation::sprintf(_n($_b_t_s, $_b_t_p, $_b_t_c), array(';
+ $_tmp = array();
+ foreach ($this->_transStack as $key=>$_trans) {
+ $_tmp[] = '\''.addslashes($key).'\' => IPF_Template_SafeString::value('.$_trans.')';
+ }
+ $res .= implode(', ', $_tmp);
+ unset($_trans, $_tmp);
+ $res .= ')));';
+ $this->_transStack = array();
+ } else {
+ $res .= '$_b_t_s=ob_get_contents(); ob_end_clean(); ';
+ if (count($this->_transStack) == 0) {
+ $res .= 'echo(__($_b_t_s)); ';
+ } else {
+ $res .= 'echo(IPF_Translation::sprintf(__($_b_t_s), array(';
+ $_tmp = array();
+ foreach ($this->_transStack as $key=>$_trans) {
+ $_tmp[] = '\''.addslashes($key).'\' => IPF_Template_SafeString::value('.$_trans.')';
+ }
+ $res .= implode(', ', $_tmp);
+ unset($_trans, $_tmp);
+ $res .= '))); ';
+ $this->_transStack = array();
+ }
+ }
+ $this->_transPlural = false;
+ array_pop($this->_blockStack);
+ break;
+ case 'plural':
+ $res = '$_b_t_s=ob_get_contents(); ob_end_clean(); ob_start(); ';
+ break;
+ case 'include':
+ $argfct = preg_replace('!^[\'"](.*)[\'"]$!', '$1', $args);
+ $includedTemplateContent = $this->environment->loadTemplateFile($argfct);
+ $_comp = new IPF_Template_Compiler($includedTemplateContent, $this->environment);
+ $res = $_comp->compile();
+ break;
+ default:
+ $_start = true;
+ $oname = $name;
+ if (substr($name, 0, 1) == '/') {
+ $_start = false;
+ $name = substr($name, 1);
+ }
+ // Here we should allow custom blocks.
+
+ // Here we start the template tag calls at the template tag
+ // {tag ...} is not a block, so it must be a function.
+ if (!$this->environment->isTagAllowed($name)) {
+ trigger_error(sprintf('The function tag "%s" is not allowed.', $name), E_USER_ERROR);
+ }
+ // $argfct is a string that can be copy/pasted in the PHP code
+ // but we need the array of args.
+ $argfct = $this->_parseFinal($args, self::$allowedAssign);
+
+ $res = '$_extra_tag = new '.$this->environment->getTag($name).'($t);';
+ if ($_start) {
+ $res .= '$_extra_tag->start('.$argfct.'); ';
+ } else {
+ $res .= '$_extra_tag->end('.$argfct.'); ';
+ }
+ }
+ return $res;
+ }
+
+ private function _parseFinal($string, &$allowed, $exceptchar=array(';'))
+ {
+ $tokens = $this->tokenize($string, $allowed, $exceptchar);
+ return $this->compileExpression($tokens);
+ }
+
+ private function tokenize($string, $allowed, $exceptchar)
+ {
+ $tokens = token_get_all('<?php '.$string.'?>');
+ $result = array();
+ foreach ($tokens as $tok) {
+ if (is_array($tok)) {
+ list($type, $str) = $tok;
+ if ($type == T_OPEN_TAG || $type == T_CLOSE_TAG) {
+ // skip
+ } elseif (in_array($type, $allowed)) {
+ $result[] = $tok;
+ } else {
+ throw new IPF_Template_Exception(sprintf('Invalid syntax: (%s) %s.', $string, $str));
+ }
+ } else {
+ if (in_array($tok, $exceptchar)) {
+ trigger_error(sprintf('Invalid character: (%s) %s.', $string, $tok), E_USER_ERROR);
+ } else {
+ $result[] = $tok;
+ }
+ }
+ }
+ return $result;
+ }
+
+ private function compileExpression($tokens)
+ {
+ $result = '';
+ foreach ($tokens as $tok) {
+ if (is_array($tok)) {
+ list($type, $str) = $tok;
+ if ($type == T_VARIABLE) {
+ $result .= '$t->_vars[\''.substr($str, 1).'\']';
+ } else {
+ $result .= $str;
+ }
+ } else {
+ if ($tok == '.') {
+ $result .= '->';
+ } elseif ($tok == '~') {
+ $result .= '.';
+ } else {
+ $result .= $tok;
+ }
+ }
+ }
+ return $result;
+ }
+}
+
+IPF_Template_Compiler::init();
+
--- /dev/null
+<?php
+
+class IPF_Template_Context
+{
+ public $_vars;
+ private $stack = array();
+
+ function __construct($vars=array())
+ {
+ $this->_vars = new IPF_Template_ContextVars($vars);
+ }
+
+ function get($var)
+ {
+ if (isset($this->_vars[$var])) {
+ return $this->_vars[$var];
+ }
+ return '';
+ }
+
+ function set($var, $value)
+ {
+ $this->_vars[$var] = $value;
+ }
+
+ public function push()
+ {
+ $vars = func_get_args();
+ $frame = array();
+ foreach ($vars as $var) {
+ $frame[$var] = $this->get($var);
+ }
+ $this->stack[] = $frame;
+ }
+
+ public function pop()
+ {
+ $frame = array_pop($this->stack);
+ foreach ($frame as $var => $val)
+ $this->set($var, $val);
+ }
+}
+
+class IPF_Template_ContextVars extends ArrayObject
+{
+ function offsetGet($prop)
+ {
+ if (!$this->offsetExists($prop)) {
+ return '';
+ }
+ return parent::offsetGet($prop);
+ }
+
+ function __get($prop)
+ {
+ if (isset($this->$prop)) {
+ return $this->$prop;
+ } else {
+ return $this->offsetGet($prop);
+ }
+ }
+
+ function __toString()
+ {
+ return var_export($this, true);
+ }
+}
+
--- /dev/null
+<?php
+
+abstract class IPF_Template_Environment
+{
+ abstract public function loadTemplateFile($filename);
+
+ abstract public function getCompiledTemplateName($template);
+
+ // Dictionary of allowed tags (tag name => class)
+ public $tags = array();
+
+ public function isTagAllowed($name)
+ {
+ return isset($this->tags[$name]);
+ }
+
+ public function getTag($name)
+ {
+ if (isset($this->tags[$name]))
+ return $this->tags[$name];
+ else
+ throw new IPF_Template_Exception('Tag '.$name.' is not defined.');
+ }
+
+ // Dictionary of modifiers (modifier name => function)
+ public $modifiers = array(
+ 'upper' => 'strtoupper',
+ 'lower' => 'strtolower',
+ 'escxml' => 'htmlspecialchars',
+ 'escape' => 'IPF_Template_Modifier::escape',
+ 'strip_tags' => 'strip_tags',
+ 'escurl' => 'rawurlencode',
+ 'capitalize' => 'ucwords',
+ 'debug' => 'print_r', // Not var_export because of recursive issues.
+ 'fulldebug' => 'var_export',
+ 'count' => 'count',
+ 'nl2br' => 'nl2br',
+ 'trim' => 'trim',
+ 'unsafe' => 'IPF_Template_SafeString::markSafe',
+ 'safe' => 'IPF_Template_SafeString::markSafe',
+ 'date' => 'IPF_Template_Modifier::dateFormat',
+ 'time' => 'IPF_Template_Modifier::timeFormat',
+ 'floatformat' => 'IPF_Template_Modifier::floatFormat',
+ 'limit_words' => 'IPF_Template_Modifier::limitWords',
+ 'limit_chars' => 'IPF_Template_Modifier::limitCharacters',
+ );
+
+ public function hasModifier($name)
+ {
+ return isset($this->modifiers[$name]);
+ }
+
+ public function getModifier($name)
+ {
+ if (isset($this->modifiers[$name]))
+ return $this->modifiers[$name];
+ else
+ throw new IPF_Template_Exception('Modifier '.$name.' is not defined.');
+ }
+}
+
--- /dev/null
+<?php
+
+class IPF_Template_Environment_FileSystem extends IPF_Template_Environment
+{
+ public $folders = array();
+ public $cache = '/tmp';
+ public $debug = false;
+
+ public function loadTemplateFile($filename)
+ {
+ // FIXME: Very small security check, could be better.
+ if (strpos($filename, '..') !== false) {
+ throw new IPF_Template_Exception(sprintf('Template file contains invalid characters: %s', $filename));
+ }
+ foreach ($this->folders as $folder) {
+ if (file_exists($folder.'/'.$filename)) {
+ return file_get_contents($folder.'/'.$filename);
+ }
+ }
+ throw new IPF_Template_Exception(sprintf('Template file not found: %s', $filename));
+ }
+
+ public function getCompiledTemplateName($template)
+ {
+ $_tmp = var_export($this->folders, true);
+ $filename = $this->cache.'/IPF_Template-'.md5($_tmp.(string)$template).'.phps';
+ if ($this->debug or !file_exists($filename)) {
+ $this->write($filename, $template->compile());
+ }
+ return $filename;
+ }
+
+ private function write($filename, $content)
+ {
+ $fp = @fopen($filename, 'a');
+ if ($fp !== false) {
+ flock($fp, LOCK_EX);
+ ftruncate($fp, 0);
+ rewind($fp);
+ fwrite($fp, $content, strlen($content));
+ flock($fp, LOCK_UN);
+ fclose($fp);
+ @chmod($filename, 0777);
+ return true;
+ } else {
+ throw new IPF_Template_Exception(sprintf('Cannot write the compiled template: %s', $filename));
+ }
+ return false;
+ }
+}
+
--- /dev/null
+<?php
+
+class IPF_Template_Exception extends Exception
+{
+}
+
--- /dev/null
+<?php
+
+final class IPF_Template_Modifier
+{
+ public static function escape($string)
+ {
+ return htmlspecialchars((string)$string, ENT_COMPAT, 'UTF-8');
+ }
+
+ public static function dateFormat($date, $format='%b %e, %Y')
+ {
+ if (substr(PHP_OS,0,3) == 'WIN') {
+ $_win_from = array ('%e', '%T', '%D');
+ $_win_to = array ('%#d', '%H:%M:%S', '%m/%d/%y');
+ $format = str_replace($_win_from, $_win_to, $format);
+ }
+ $date = date('Y-m-d H:i:s', strtotime($date.' GMT'));
+ return strftime($format, strtotime($date));
+ }
+
+ public static function timeFormat($time, $format='Y-m-d H:i:s')
+ {
+ return date($format, $time);
+ }
+
+ public static function floatFormat($number, $decimals=2, $dec_point='.', $thousands_sep=',')
+ {
+ return number_format($number, $decimals, $dec_point, $thousands_sep);
+ }
+
+ /**
+ * Word Limiter
+ *
+ * Limits a string to X number of words.
+ *
+ * @param string
+ * @param integer
+ * @param string the end character. Usually an ellipsis
+ * @return string
+ */
+ public static function limitWords($str, $limit=100, $end_char='…')
+ {
+ if (trim($str) == '')
+ return $str;
+ preg_match('/^\s*+(?:\S++\s*+){1,'.(int) $limit.'}/', $str, $matches);
+ if (strlen($str) == strlen($matches[0]))
+ $end_char = '';
+ return rtrim($matches[0]).$end_char;
+ }
+
+ /**
+ * Character Limiter
+ *
+ * Limits the string based on the character count. Preserves complete words
+ * so the character count may not be exactly as specified.
+ *
+ * @param string
+ * @param integer
+ * @param string the end character. Usually an ellipsis
+ * @return string
+ */
+ function limitCharacters($str, $n=500, $end_char='…')
+ {
+ if (strlen($str) < $n)
+ return $str;
+
+ $str = preg_replace("/\s+/", ' ', str_replace(array("\r\n", "\r", "\n"), ' ', $str));
+
+ if (strlen($str) <= $n)
+ return $str;
+
+ $out = "";
+ foreach (explode(' ', trim($str)) as $val) {
+ $out .= $val.' ';
+ if (strlen($out) >= $n) {
+ $out = trim($out);
+ return (strlen($out) == strlen($str)) ? $out : $out.$end_char;
+ }
+ }
+ }
+}
+
--- /dev/null
+<?php
+
+class IPF_Template_SafeString
+{
+ public $value = '';
+
+ public static function value($mixed, $safe=false)
+ {
+ if (is_object($mixed) and 'IPF_Template_SafeString' == get_class($mixed))
+ return $mixed->value;
+ if ($safe)
+ return $mixed;
+ return htmlspecialchars($mixed, ENT_COMPAT, 'UTF-8');
+ }
+
+ function __construct($mixed, $safe=false)
+ {
+ $this->value = self::value($mixed, $safe);
+ }
+
+ function __toString()
+ {
+ return $this->value;
+ }
+
+ public static function markSafe($string)
+ {
+ return new IPF_Template_SafeString($string, true);
+ }
+}
+
--- /dev/null
+<?php
+
+class IPF_Template_Tag
+{
+ protected $context;
+
+ function __construct($context=null)
+ {
+ $this->context = $context;
+ }
+
+ // variable list of arguments
+ function start()
+ {
+ }
+
+ // variable list of arguments
+ function end()
+ {
+ }
+}
+
--- /dev/null
+<?php
+
+abstract class IPF_Template
+{
+ protected $environment;
+
+ public function __construct(IPF_Template_Environment $environment)
+ {
+ $this->environment = $environment;
+ }
+
+ abstract public function __toString();
+
+ abstract protected function content();
+
+ public function compile()
+ {
+ $compiler = new IPF_Template_Compiler($this->content(), $this->environment);
+ return $compiler->getCompiledTemplate();
+ }
+
+ public function render($c=null)
+ {
+ $compiled_template = $this->environment->getCompiledTemplateName($this);
+ ob_start();
+ $t = $c;
+ try {
+ include $compiled_template;
+ } catch (Exception $e) {
+ ob_clean();
+ throw $e;
+ }
+ $a = ob_get_contents();
+ ob_end_clean();
+ return $a;
+ }
+}
+
+class IPF_Template_File extends IPF_Template
+{
+ private $filename;
+
+ function __construct($filename, IPF_Template_Environment $environment)
+ {
+ parent::__construct($environment);
+ $this->filename = $filename;
+ }
+
+ public function __toString()
+ {
+ return $this->filename;
+ }
+
+ protected function content()
+ {
+ return $this->environment->loadTemplateFile($this->filename);
+ }
+}
+
+class IPF_Template_String extends IPF_Template
+{
+ private $template;
+
+ function __construct($template, IPF_Template_Environment $environment)
+ {
+ parent::__construct($environment);
+ $this->template = $template;
+ }
+
+ public function __toString()
+ {
+ return $this->template;
+ }
+
+ protected function content()
+ {
+ return $this->template;
+ }
+}
+