/www/htdocs/include/Spoon/template/template.php
PHP | 1662 lines | 802 code | 257 blank | 603 comment | 102 complexity | b705ad61c3d37c1536c0495f2a66ce77 MD5 | raw file
Possible License(s): BSD-3-Clause
- <?php
- /**
- * Spoon Library
- *
- * This source file is part of the Spoon Library. More information,
- * documentation and tutorials can be found @ http://www.spoon-library.be
- *
- * @package template
- *
- *
- * @author Davy Hellemans <davy@spoon-library.be>
- * @author Tijs Verkoyen <tijs@spoon-library.be>
- * @author Dave Lens <dave@spoon-library.be>
- * @since 0.1.1
- */
- /**
- * This exception is used to handle template related exceptions.
- *
- * @package template
- *
- *
- * @author Davy Hellemans <davy@spoon-library.be>
- * @since 0.1.1
- */
- class SpoonTemplateException extends SpoonException {}
- /** Filesystem package */
- require_once 'spoon/filesystem/filesystem.php';
- /** SpoonDate class */
- require_once 'spoon/date/date.php';
- /**
- * Spoon Library
- *
- * This source file is part of the Spoon Library. More information,
- * documentation and tutorials can be found @ http://www.spoon-library.be
- *
- * @package template
- *
- *
- * @author Davy Hellemans <davy@spoon-library.be>
- * @author Tijs Verkoyen <tijs@spoon-library.be>
- * @since 0.1.1
- */
- class SpoonTemplate
- {
- /**
- * Cache names
- *
- * @var array
- */
- private $cache = array();
- /**
- * Cache directory location
- *
- * @var string
- */
- private $cacheDirectory = '.';
- /**
- * Compile directory location
- *
- * @var string
- */
- private $compileDirectory = '.';
- /**
- * Always recompile
- *
- * @var bool
- */
- private $forceCompile = false;
- /**
- * List of form objects
- *
- * @var array
- */
- private $forms = array();
- /**
- * Stack of variables & their replace values
- *
- * @var array
- */
- private $variables = array('CRLF' => "\n", 'TAB' => "\t");
- /**
- * Adds a form to this template.
- *
- * @return void
- * @param SpoonForm $form
- */
- public function addForm(SpoonForm $form)
- {
- $this->forms[$form->getName()] = $form;
- }
- /**
- * Assign values to variables.
- *
- * @return void
- * @param mixed $variable The key to search for or an array with keys & values.
- * @param mixed[optional] $value The value to replace the key with. If the first element is an array, this argument is not required.
- */
- public function assign($variable, $value = null)
- {
- // regular function use
- if($value !== null && $variable != '') $this->variables[(string) $variable] = $value;
- // only 1 argument
- else
- {
- // not an array
- if(!is_array($variable)) throw new SpoonTemplateException('If you provide one argument it needs to be an array');
- // loop array
- foreach($variable as $key => $value)
- {
- // key is NOT empty
- if($key !== '') $this->variables[(string) $key] = $value;
- }
- }
- }
- /**
- * Assign an entire array with keys & values.
- *
- * @return void
- * @param array $values This array with keys and values will be used to search and replace in the template file.
- * @param string[optional] $prefix An optional prefix eg. 'lbl' that can be used.
- * @param string[optional] $suffix An optional suffix eg. 'msg' that can be used
- */
- public function assignArray(array $values, $prefix = null, $suffix = null)
- {
- foreach($values as $key => $value)
- {
- $this->variables[(string) $prefix . $key . (string) $suffix] = $value;
- }
- }
- /**
- * Cache a certain block.
- *
- * @return void
- * @param string $name The name of the block that you want to cache.
- * @param int[optional] $lifetime The lifetime in seconds.
- */
- public function cache($name, $lifetime = 60)
- {
- // redefine lifetime
- $lifetime = (SpoonFilter::isBetween(10, 30758400, $lifetime)) ? (int) $lifetime : 60;
- // set lifetime
- $this->cache[(string) $name] = $lifetime;
- }
- /**
- * Clear the entire cache or a specific item.
- *
- * @return void
- * @param string[optional] $name The name of the cache block that you want to clear from the directory with cached files.
- */
- public function clearCache($name = null)
- {
- // specific cache
- if($name !== null) SpoonFile::delete($this->cacheDirectory .'/'. (string) $name .'_cache.tpl');
- // all cache files
- else
- {
- // list of *_cache.tpl files from cacheDirectory
- $files = SpoonFile::getList($this->cacheDirectory, '|.*\_cache\.tpl|');
- // delete
- foreach($files as $file) SpoonFile::delete($this->cacheDirectory .'/'. $file);
- }
- }
- /**
- * Clear the entire compiled directory or a specific template.
- *
- * @return void
- * @param string[optional] $template The filename of a specific template to mark for recompiling.
- */
- public function clearCompiled($template = null)
- {
- // specific template
- if($template !== null) SpoonFile::delete($this->compileDirectory .'/'. $this->getCompileName($template));
- // all compiled templates
- else
- {
- // list of *.tpl.php files from compileDirectory
- $files = SpoonFile::getList($this->compileDirectory, '|.*\.tpl\.php|');
- // delete
- foreach($files as $file) SpoonFile::delete($this->compileDirectory .'/'. $file);
- }
- }
- /**
- * Compile a given template.
- *
- * @return void
- * @param string $path The path to the template, excluding the template filename.
- * @param string $template The filename of the template within the path.
- */
- public function compile($path, $template)
- {
- // redefine template
- if(mb_substr($template, 0, 1, SPOON_CHARSET) != '/') $template = $path .'/'. $template;
- // create object
- $compiler = new SpoonTemplateCompiler($template, $this->variables);
- // set some options
- $compiler->setCacheDirectory($this->cacheDirectory);
- $compiler->setCompileDirectory($this->compileDirectory);
- $compiler->setForceCompile($this->forceCompile);
- $compiler->setForms($this->forms);
- // compile & save
- $compiler->parseToFile();
- }
- /**
- * Returns the correct element from the list, based on the counter.
- *
- * @return string The item based on the counter from $elements.
- * @param int $counter The index of the item to retrieve from the list $elements.
- * @param array $elements The list of elements to cycle through.
- */
- public function cycle($counter, array $elements)
- {
- // update counter
- $counter += 1;
- // number of elements
- $numElements = count($elements);
- // calculate modulus
- $modulus = $counter % $numElements;
- // leftovers?
- if($modulus == 0) return $elements[$numElements - 1];
- else return $elements[$modulus - 1];
- }
- /**
- * Deassign a variable.
- *
- * @return void
- * @param string $name The name of the key that you want to remove from the list of already assigned variables.
- */
- public function deAssign($name)
- {
- if(isset($this->variables[(string) $name])) unset($this->variables[(string) $name]);
- }
- /**
- * Display the output.
- *
- * @return void
- * @param string $template The filename of the template that you want to display.
- */
- public function display($template)
- {
- // validate name
- if(trim($template) == '') throw new SpoonTemplateException('Please provide a template.');
- // compiled name
- $compileName = $this->getCompileName((string) $template);
- // compiled if needed
- if(!SpoonFile::exists($this->compileDirectory .'/'. $compileName) || $this->forceCompile)
- {
- // create compiler
- $compiler = new SpoonTemplateCompiler((string) $template, $this->variables);
- // set some options
- $compiler->setCacheDirectory($this->cacheDirectory);
- $compiler->setCompileDirectory($this->compileDirectory);
- $compiler->setForceCompile($this->forceCompile);
- $compiler->setForms($this->forms);
- // compile & save
- $compiler->parseToFile();
- }
- // load template
- require $this->compileDirectory .'/'. $compileName;
- }
- /**
- * Retrieves the already assigned value.
- *
- * @return mixed Returns an array, string, int or null
- * @param string $variable The name of the variable that you want to retrieve the already assigned value from.
- */
- public function getAssignedValue($variable)
- {
- if(isset($this->variables[(string) $variable])) return $this->variables[(string) $variable];
- return null;
- }
- /**
- * Get the cache directory path.
- *
- * @return string The location of the cache directory.
- */
- public function getCacheDirectory()
- {
- return $this->cacheDirectory;
- }
- /**
- * Get the compile directory path.
- *
- * @return string The location of the compile directory.
- */
- public function getCompileDirectory()
- {
- return $this->compileDirectory;
- }
- /**
- * Retrieve the compiled name for this template.
- *
- * @return string The special unique name, used for storing this file once compiled in the compile directory.
- * @param string $template The filename of the template.
- * @param string[optional] $path The optional path to this template.
- */
- private function getCompileName($template, $path = null)
- {
- // redefine template
- if(mb_substr($template, 0, 1, SPOON_CHARSET) != '/' && $path !== null) $template = $path .'/'. $template;
- // return the correct full path
- return md5(realpath($template)) .'_'. basename($template) .'.php';
- }
- /**
- * Fetch the parsed content from this template.
- *
- * @return string The actual parsed content after executing this template.
- * @param string $template The location of the template file, used to display this template.
- */
- public function getContent($template)
- {
- // cache tags can not be combined with this method
- if(!empty($this->cache)) throw new SpoonTemplateException('You can not use this method when the template uses cache tags.');
- // all is fine
- // turn on output buffering
- ob_start();
- // show output
- $this->display($template);
- // return template content
- return ob_get_clean();
- }
- /**
- * Get the force compiling directive.
- *
- * @return bool Do we need to recompile this template every time it's loaded.
- */
- public function getForceCompile()
- {
- return $this->forceCompile;
- }
- /**
- * Get the template language.
- *
- * @return string The language that's being used as default for this template. (Check SpoonLocale for the list of available languages.)
- */
- public function getLanguage()
- {
- return $this->language;
- }
- /**
- * Is the cache for this item still valid.
- *
- * @return bool Is this template block cached?
- * @param string $name The name of the cached block.
- */
- public function isCached($name)
- {
- // doesnt exist
- if(!isset($this->cache[(string) $name])) throw new SpoonTemplateException('No cache with the name "'. (string) $name .'" is known.');
- // last modification date
- $time = @filemtime($this->cacheDirectory .'/'. (string) $name .'_cache.tpl');
- // doesn't exist
- if($time === false) return false;
- // not valid
- if((time() - (int) $time) > $this->cache[(string) $name]) return false;
- // still valid
- return true;
- }
- /**
- * Map a modifier to a given function/method.
- *
- * @return void
- * @param string $name The name that you wish to use in the templates as a modifier.
- * @param mixed $function The function or method to map this name to. In case it's a method, provide this as an array containing class and method name.
- */
- public function mapModifier($name, $function)
- {
- SpoonTemplateModifiers::mapModifier($name, $function);
- }
- /**
- * Set the cache directory.
- *
- * @return void
- * @param string $path The location of the directory where you want to store your cached template blocks.
- */
- public function setCacheDirectory($path)
- {
- $this->cacheDirectory = (string) $path;
- }
- /**
- * Set the compile directory.
- *
- * @return void
- * @param string $path The location of the directory where you want to store your compiled templates.
- */
- public function setCompileDirectory($path)
- {
- $this->compileDirectory = (string) $path;
- }
- /**
- * If enabled, recompiles a template even if it has already been compiled.
- *
- * @return void
- * @param bool[optional] $on Do we need to recompile the template every time it loads.
- */
- public function setForceCompile($on = true)
- {
- $this->forceCompile = (bool) $on;
- }
- }
- /**
- * Spoon Library
- *
- * This source file is part of the Spoon Library. More information,
- * documentation and tutorials can be found @ http://www.spoon-library.be
- *
- * @package template
- *
- *
- * @author Davy Hellemans <davy@spoon-library.be>
- * @since 1.0.0
- */
- class SpoonTemplateCompiler
- {
- /**
- * Cache directory location
- *
- * @var string
- */
- private $cacheDirectory = '.';
- /**
- * Compile directory location
- *
- * @var string
- */
- private $compileDirectory = '.';
- /**
- * Working content
- *
- * @var string
- */
- private $content;
- /**
- * Always recompile
- *
- * @var bool
- */
- private $foreCompile = false;
- /**
- * List of form objects
- *
- * @var array
- */
- private $forms = array();
- /**
- * Cached list of the modifiers
- *
- * @var array
- */
- private $modifiers = array();
- /**
- * Is the content already parsed
- *
- * @var bool
- */
- private $parsed = false;
- /**
- * Template file
- *
- * @var string
- */
- private $template;
- /**
- * List of variables
- *
- * @var array
- */
- private $variables = array();
- /**
- * Class constructor.
- *
- * @return void
- * @param string $template The name of the template to compile.
- * @param array $variables The list of possible variables.
- */
- public function __construct($template, array $variables)
- {
- $this->template = (string) $template;
- $this->variables = $variables;
- }
- /**
- * Retrieve the compiled name for this template.
- *
- * @return string The unique filename used to store the compiled template in the compile directory.
- * @param string $template The name of the template.
- */
- private function getCompileName($template)
- {
- return md5(realpath($template)) .'_'. basename($template) .'.php';
- }
- /**
- * Retrieve the content.
- *
- * @return string The php compiled template.
- */
- public function getContent()
- {
- if(!$this->parsed) $this->parse();
- return $this->content;
- }
- /**
- * Creates a string of the provided value with the variables encapsulated.
- *
- * @return string The variable value as php code.
- * @param string $value The value that needs to be compiled to php code.
- */
- private function getVariableString($value)
- {
- // init var
- $variables = array();
- // regex
- $pattern = '/\{\$([a-z0-9_])+(\.([a-z0-9_])+)?\}/i';
- // find variables
- if(preg_match_all($pattern, $value, $matches))
- {
- // loop variables
- foreach($matches[0] as $match)
- {
- $variables[] = $this->parseVariable($match);
- }
- }
- // replace the variables by %s
- $value = preg_replace($pattern, '%s', $value);
- // encapsulate the vars
- $value = "'". str_replace('%s', "'. %s .'", $value) ."'";
- // fix errors
- if(mb_substr($value, 0, 4, SPOON_CHARSET) == "''. ") $value = mb_substr($value, 4, mb_strlen($value, SPOON_CHARSET), SPOON_CHARSET);
- if(mb_substr($value, -4, mb_strlen($value, SPOON_CHARSET), SPOON_CHARSET) == " .''") $value = mb_substr($value, 0, -4, SPOON_CHARSET);
- // cleanup
- $value = str_replace(".''.", '.', $value);
- // add the variables
- return vsprintf($value, $variables);
- }
- /**
- * Check the string for syntax errors
- *
- * @return bool
- * @param string $string
- * @param string $type
- */
- private function isCorrectSyntax($string, $type)
- {
- // init vars
- $string = (string) $string;
- $type = SpoonFilter::getValue($type, array('cycle', 'iteration', 'option', 'variable'), 'variable', 'string');
- // types
- switch($type)
- {
- // cycle string
- case 'cycle':
- // the number of single qoutes should always be an even number
- if(!SpoonFilter::isEven(substr_count($string, "'"))) return false;
- break;
- // iteration string
- case 'iteration':
- // the number of square opening/closing brackets should be equal
- if(substr_count($string, '[') != substr_count($string, ']')) return false;
- // the number of single qoutes should always be an even number
- if(!SpoonFilter::isEven(substr_count($string, "'"))) return false;
- // first charachter should not be a number
- if(SpoonFilter::isInteger(substr($string, 2, 1))) return false;
- // square bracket followed by a dot is NOT allowed eg {option:variable[0].var}
- if(substr_count($string, '].') != 0) return false;
- // dot followed by a square bracket is NOT allowed eg {option:variable.['test']}
- if(substr_count($string, '.[') != 0) return false;
- // empty brackets are NOT allowed
- if(substr_count($string, '[]') != 0) return false;
- // empty inversed brackets are NOT allowed
- if(substr_count($string, '][') != 0) return false;
- break;
- // option string
- case 'option':
- // the number of square opening/closing brackets should be equal
- if(substr_count($string, '[') != substr_count($string, ']')) return false;
- // the number of single qoutes should always be an even number
- if(!SpoonFilter::isEven(substr_count($string, "'"))) return false;
- // first charachter should not be a number
- if(SpoonFilter::isInteger(substr($string, 2, 1))) return false;
- // square bracket followed by a dot is NOT allowed eg {option:variable[0].var}
- if(substr_count($string, '].') != 0) return false;
- // dot followed by a square bracket is NOT allowed eg {option:variable.['test']}
- if(substr_count($string, '.[') != 0) return false;
- // empty brackets are NOT allowed
- if(substr_count($string, '[]') != 0) return false;
- // empty inversed brackets are NOT allowed
- if(substr_count($string, '][') != 0) return false;
- break;
- // variable string
- case 'variable':
- // the number of square opening/closing brackets should be equal
- if(substr_count($string, '[') != substr_count($string, ']')) return false;
- // the number of single qoutes should always be an even number
- if(!SpoonFilter::isEven(substr_count($string, "'"))) return false;
- // first charachter should not be a number
- if(SpoonFilter::isInteger(substr($string, 2, 1))) return false;
- // square bracket followed by a dot is NOT allowed eg {$variable[0].var}
- if(substr_count($string, '].') != 0) return false;
- // dot followed by a square bracket is NOT allowed eg {$variable.['test']}
- if(substr_count($string, '.[') != 0) return false;
- // empty brackets are NOT allowed
- if(substr_count($string, '[]') != 0) return false;
- // empty inversed brackets are NOT allowed
- if(substr_count($string, '][') != 0) return false;
- break;
- }
- return true;
- }
- /**
- * Parse the template.
- *
- * @return void
- */
- private function parse()
- {
- // not yet parsed
- if(!$this->parsed)
- {
- // add to the list of parsed files
- $this->files[] = $this->getCompileName($this->template);
- // map modifiers
- $this->modifiers = SpoonTemplateModifiers::getModifiers();
- // set content
- $this->content = SpoonFile::getContent($this->template);
- // strip php code
- $this->content = $this->stripCode($this->content);
- // strip comments
- $this->content = $this->stripComments($this->content);
- // parse iterations
- $this->content = $this->parseIterations($this->content);
- // includes
- $this->content = $this->parseIncludes($this->content);
- // parse options
- $this->content = $this->parseOptions($this->content);
- // parse cache tags
- $this->content = $this->parseCache($this->content);
- // parse variables
- $this->content = $this->parseVariables($this->content);
- // parse forms
- $this->content = $this->parseForms($this->content);
- // while developing, you might want to know about the undefined indexes
- $errorReporting = (SPOON_DEBUG) ? 'E_ALL | E_STRICT' : 'E_WARNING';
- $displayErrors = (SPOON_DEBUG) ? 'On' : 'Off';
- // add error_reporting setting
- $this->content = '<?php error_reporting('. $errorReporting .'); ini_set(\'display_errors\', \''. $displayErrors .'\'); ?>'. "\n". $this->content;
- // parsed
- $this->parsed = true;
- }
- }
- /**
- * Parse the cache tags.
- *
- * @return string The updated content, containing the parsed cache tags.
- * @param string $content The content that may contain the parse tags.
- */
- private function parseCache($content)
- {
- // regex pattern
- $pattern = "/\{cache:([a-z0-9_\.\{\$\}]+)\}.*?\{\/cache:\\1\}/is";
- // find matches
- if(preg_match_all($pattern, $content, $matches))
- {
- // loop matches
- foreach($matches[1] as $match)
- {
- // variable
- $variable = $this->getVariableString($match);
- // init vars
- $search = array();
- $replace = array();
- // search for
- $search[0] = '{cache:'. $match .'}';
- $search[1] = '{/cache:'. $match .'}';
- // replace with
- $replace[0] = "<?php if(!\$this->isCached(". $variable .")): ?>\n<?php ob_start(); ?>";
- $replace[1] = "<?php SpoonFile::setContent(\$this->cacheDirectory .'/'. $variable .'_cache.tpl', ob_get_clean()); ?>\n<?php endif; ?>\n";
- $replace[1] .= "<?php require \$this->cacheDirectory .'/'. $variable .'_cache.tpl'; ?>";
- // execute
- $content = str_replace($search, $replace, $content);
- }
- }
- return $content;
- }
- /**
- * Parses the cycle tags in the given content.
- *
- * @return string The updated content, containing the parsed cycle tags.
- * @param string $content The content that may contain the cycle tags.
- */
- private function parseCycle($content, $iteration)
- {
- // regex pattern
- $pattern = "|\{cycle((:\'[a-z0-9\-_\<\/\>\s]+\')+)\}|is";
- // find matches
- if(preg_match_all($pattern, $content, $matches))
- {
- // chunks
- $chunks = explode('.', $iteration);
- // number of chunks
- $numChunks = count($chunks);
- // variable string
- $variable = '$'. SpoonFilter::toCamelCase(str_replace(array('[', ']', "'", '_'), ' ', $chunks[$numChunks - 1]), ' ', true, SPOON_CHARSET);
- // loop matches
- foreach($matches[1] as $i => $match)
- {
- // correct cycle
- if($this->isCorrectSyntax($match, 'cycle'))
- {
- // init vars
- $cycle = '';
- $inParameter = false;
- $parameters = trim($match, ':');
- // loop every character
- for($c = 0; $c < mb_strlen($parameters, SPOON_CHARSET); $c++)
- {
- // fetch character
- $string = mb_substr($parameters, $c, 1, SPOON_CHARSET);
- // single quote in parameter, indicating the end for this parameter
- if($string == "'" && $inParameter) $inParameter = false;
- // single quotes, indicating the start of a new parameter
- elseif($string == "'" && !$inParameter) $inParameter = true;
- // semicolon outside parameter
- elseif($string == ':' && !$inParameter) $string = ', ';
- // add character
- $cycle .= $string;
- }
- // search & replace
- $search = $matches[0][$i];
- $replace = '<?php echo $this->cycle('. $variable .'I, array('. $cycle .')); ?>';
- // init var
- $iterations = array();
- /*
- * The name of this iteration may contain characters that need to be escaped if you
- * want to use them as a literal string in a regex match.
- */
- $iterationPattern = str_replace(array('.', '[', ']', "'"), array('\.', '\[', '\]', "\'"), $iteration);
- // match current iteration
- preg_match_all('|{iteration:'. $iterationPattern .'}.*{/iteration:'. $iterationPattern .'}|ismU', $content, $iterations);
- // loop mathes
- foreach($iterations as $block)
- {
- // build new content
- $newContent = str_replace($search, $replace, $block);
- // replace in original content
- $content = str_replace($block, $newContent, $content);
- }
- }
- }
- }
- return $content;
- }
- /**
- * Parse the forms.
- *
- * @return string The updated content, containing the parsed form tags.
- * @param string $content The content that may contain the form tags.
- */
- private function parseForms($content)
- {
- // regex pattern
- $pattern = '/\{form:([a-z0-9_]+?)\}?/siU';
- // find matches
- if(preg_match_all($pattern, $content, $matches))
- {
- // loop matches
- foreach($matches[1] as $name)
- {
- // form object with that name exists
- if(isset($this->forms[$name]))
- {
- // init vars
- $search = array();
- $replace = array();
- // start & close tag
- $search = array('{form:'. $name .'}', '{/form:'. $name .'}');
- $replace[0] = '<form action="<?php echo $this->forms[\''. $name .'\']->getAction(); ?>" method="<?php echo $this->forms[\''. $name .'\']->getMethod(); ?>"<?php echo $this->forms[\''. $name .'\']->getParametersHTML(); ?>>' ."\n<div>\n";
- $replace[0] .= $this->forms[$name]->getField('form')->parse();
- $replace[1] = "\n</div>\n</form>";
- $content = str_replace($search, $replace, $content);
- }
- }
- }
- return $content;
- }
- /**
- * Parse the include tags.
- *
- * @return string The updated content, containing the parsed include tags.
- * @param string $content The content that may contain the include tags.
- */
- private function parseIncludes($content)
- {
- // regex pattern
- $pattern = "|\{include:file='([a-z0-9\-_\.:\{\$\}\/]+)'\}|is";
- // find matches
- if(preg_match_all($pattern, $content, $matches))
- {
- // loop matches
- foreach($matches[1] as $match)
- {
- // file
- $file = $this->getVariableString($match); // @todo herwerken.
- // template path
- $template = eval('error_reporting(0); return '. $file .';');
- // search string
- $search = '{include:file=\''. $match .'\'}';
- // replace string
- $replace = '<?php if($this->getForceCompile()) $this->compile(\''. dirname(realpath($this->template)) .'\', '. $file .'); ?>' ."\n";
- $replace .= '<?php $return = @include $this->getCompileDirectory() .\'/\'. $this->getCompileName('. $file .',\''. dirname(realpath($this->template)) .'\'); ?>' ."\n";
- $replace .= '<?php if($return === false): ?>' ."\n";
- $replace .= '<?php $this->compile(\''. dirname(realpath($this->template)) .'\', '. $file .'); ?>' ."\n";
- $replace .= '<?php @include $this->getCompileDirectory() .\'/\'. $this->getCompileName('. $file .',\''. dirname(realpath($this->template)) .'\'); ?>' ."\n";
- $replace .= '<?php endif; ?>' ."\n";
- // replace it
- $content = str_replace($search, $replace, $content);
- }
- }
- return $content;
- }
- /**
- * Parse the iterations (recursively).
- *
- * @return string The updated content, containing the parsed iteration tags.
- * @param string $content The content that my contain the iteration tags.
- */
- private function parseIterations($content)
- {
- // fetch iterations
- $pattern = '/\{iteration:(([a-z09\'\[\]])+(\.([a-z0-9\'\[\]])+)?)\}/is';
- // find matches
- if(preg_match_all($pattern, $content, $matches))
- {
- // init var
- $iterations = array();
- // loop matches
- foreach($matches[1] as $match)
- {
- if(!in_array($match, $iterations)) $iterations[] = $match;
- }
- // has iterations
- if(count($iterations) != 0)
- {
- // loop iterations
- foreach($iterations as $iteration)
- {
- // check iteration syntax
- if($this->isCorrectSyntax($iteration, 'iteration'))
- {
- // parse cycle tag
- $content = $this->parseCycle($content, $iteration);
- // init vars
- $search = array();
- $replace = array();
- // search
- $search[0] = '{iteration:'. $iteration .'}';
- $search[1] = '{/iteration:'. $iteration .'}';
- // convert multiple dots to a single one
- $name = preg_replace('/\.+/', '.', $iteration);
- $name = trim($name, '.');
- // explode using the dots
- $chunks = explode('.', $name);
- // number of chunks
- $numChunks = count($chunks);
- // define variable
- $variable = $this->parseVariable($name);
- // internal variable
- $internalVariable = SpoonFilter::toCamelCase(str_replace(array('[', ']', "'", '_'), ' ', $chunks[$numChunks - 1]), ' ', true, SPOON_CHARSET);
- // replace
- $replace[0] = '<?php $'. $internalVariable .'Count = count('. $variable ."); ?>\n";
- $replace[0] .= '<?php foreach((array) '. $variable .' as $'. $internalVariable .'I => $'. $internalVariable ."): ?>\n";
- $replace[0] .= "<?php
- if(!isset(\$". $internalVariable ."['first']) && \$". $internalVariable ."I == 0) \$". $internalVariable ."['first'] = true;
- if(!isset(\$". $internalVariable ."['last']) && \$". $internalVariable ."I == \$". $internalVariable ."Count - 1) \$". $internalVariable ."['last'] = true;
- if(isset(\$". $internalVariable ."['formElements']) && is_array(\$". $internalVariable ."['formElements']))
- {
- foreach(\$". $internalVariable ."['formElements'] as \$name => \$object)
- {
- \$". $internalVariable ."[\$name] = \$object->parse();
- \$". $internalVariable ."[\$name .'Error'] = (\$object->getErrors() == '') ? '' : '<span class=\"formError\">'. \$object->getErrors() .'</span>';
- }
- }
- ?>";
- $replace[1] = '<?php endforeach; ?>';
- // replace
- $content = str_replace($search, $replace, $content);
- }
- }
- }
- }
- return $content;
- }
- /**
- * Parse the options in the given content & scope.
- *
- * @return string The updated content, containing the parsed option tags.
- * @param string $content The content that may contain the option tags.
- */
- private function parseOptions($content)
- {
- // regex pattern
- $pattern = "/\{option:((\!)?[a-z0-9\-_\[\]\']+(\.([a-z0-9\-_\[\]\'])+)?)}.*?\{\/option:\\1\}/is";
- // init vars
- $options = array();
- // keep finding those options!
- while(1)
- {
- // find matches
- if(preg_match_all($pattern, $content, $matches))
- {
- // init var
- $correctOptions = false;
- // loop matches
- foreach($matches[1] as $match)
- {
- // correct syntax
- if($this->isCorrectSyntax($match, 'option'))
- {
- // redefine match
- $match = str_replace('!', '', $match);
- // fetch variable
- $variable = $this->parseVariable($match);
- // already matched
- if(in_array($match, $options)) continue;
- // init vars
- $search = array();
- $replace = array();
- // not yet used
- $options[] = $match;
- // search for
- $search[] = '{option:'. $match .'}';
- $search[] = '{/option:'. $match .'}';
- // inverse option
- $search[] = '{option:!'. $match .'}';
- $search[] = '{/option:!'. $match .'}';
- // replace with
- $replace[] = '<?php if(isset('. $variable .') && count('. $variable .') != 0 && '. $variable .' != \'\' && '. $variable .' !== false): ?>';
- $replace[] = '<?php endif; ?>';
- // inverse option
- $replace[] = '<?php if(!isset('. $variable .') || count('. $variable .') == 0 || '. $variable .' == \'\' || '. $variable .' === false): ?>';
- $replace[] = '<?php endif; ?>';
- // go replace
- $content = str_replace($search, $replace, $content);
- // reset vars
- unset($search);
- unset($replace);
- // at least one correct option
- $correctOptions = true;
- }
- }
- // no correct options were found
- if(!$correctOptions) break;
- }
- // no matches
- else break;
- }
- return $content;
- }
- /**
- * Parse the template to a file.
- *
- * @return void
- */
- public function parseToFile()
- {
- SpoonFile::setContent($this->compileDirectory .'/'. $this->getCompileName($this->template), $this->getContent());
- }
- /**
- * Parse a single variable.
- *
- * @return string The variable as PHP code.
- * @param string $variable The variable that needs to be converted to php code.
- */
- private function parseVariable($variable)
- {
- // strip '{$' and '}'
- $variable = ltrim($variable, '{$');
- $variable = rtrim($variable, '}');
- // fetch modifiers
- $var = explode('|', $variable);
- // base variable
- $variable = '';
- // explode using the dots
- $varChunks = explode('.', $var[0]);
- // number of chunks
- $numChunks = count($varChunks);
- // more than 2 chunks is NOT allowed
- if($numChunks > 2) return '\'{$'. implode('|', $var) .'}\'';
- // 2 chunks
- elseif($numChunks == 2)
- {
- // contains [
- if(strpos($varChunks[1],'[') !== false)
- {
- // get rid of ]
- $varChunks[1] = str_replace(']', '', $varChunks[1]);
- // create chunks
- $bracketChunks = explode('[', $varChunks[1]);
- // add first part
- $variable = '$'. $varChunks[0];
- // loop all chunks
- for($i = 0; $i < count($bracketChunks); $i++)
- {
- // explicitly add single quotes for the first element
- if($i == 0) $variable .= '[\''. $bracketChunks[$i] .'\']';
- // everything after first as is provided in the template
- else $variable .= '['. $bracketChunks[$i] .']';
- }
- }
- // no square bracketes used
- else $variable = '$'. $varChunks[0] .'[\''. $varChunks[1] .'\']';
- }
- // 1 chunk
- else
- {
- // contains [
- if(strpos($varChunks[0],'[') !== false)
- {
- // get rid of ]
- $varChunks[0] = str_replace(']', '', $varChunks[0]);
- // create chunks
- $bracketChunks = explode('[', $varChunks[0]);
- // add first part
- $variable = '$this->variables[\''. $bracketChunks[0] .'\']';
- // loop all chunks
- for($i = 1; $i < count($bracketChunks); $i++)
- {
- // add this chunk (as provided in the template)
- $variable .= '['. $bracketChunks[$i] .']';
- }
- }
- // no square brackets used
- else $variable = '$this->variables[\''. $var[0] .'\']';
- }
- // has modifiers ?
- if(isset($var[1]))
- {
- // loop modifiers
- foreach($var as $i => $modifier)
- {
- // skip first record
- if($i == 0) continue;
- // modifier + parameters
- $modifierChunks = explode(':', $modifier);
- // modifier doesn't exist
- if(!isset($this->modifiers[$modifierChunks[0]])) throw new SpoonTemplateException('The modifier ('. $modifierChunks[0] .') does not exist.');
- // add call
- else
- {
- // method call
- if(is_array($this->modifiers[$modifierChunks[0]])) $variable = implode('::', $this->modifiers[$modifierChunks[0]]) .'('. $variable;
- // function call
- else $variable = $this->modifiers[$modifierChunks[0]] .'('. $variable;
- }
- // has arguments
- if(count($modifierChunks) > 1)
- {
- // init vars
- $inParameter = false;
- $parameters = mb_substr($modifier, strlen($modifierChunks[0]), mb_strlen($modifier, SPOON_CHARSET), SPOON_CHARSET);
- // loop every character
- for($i = 0; $i < mb_strlen($parameters, SPOON_CHARSET); $i++)
- {
- // fetch character
- $string = mb_substr($parameters, $i, 1, SPOON_CHARSET);
- // single quote in parameter, indicating the end for this parameter
- if($string == "'" && $inParameter) $inParameter = false;
- // single quotes, indicating the start of a new parameter
- elseif($string == "'" && !$inParameter) $inParameter = true;
- // semicolon outside parameter
- elseif($string == ':' && !$inParameter) $string = ', ';
- // add character
- $variable .= $string;
- }
- }
- // add close tag
- $variable .= ')';
- }
- }
- return $variable;
- }
- /**
- * Parse all the variables in this string.
- *
- * @return string The updated content, containing the parsed variables.
- * @param string $content The content that may contain variables.
- */
- private function parseVariables($content)
- {
- // regex pattern
- $pattern = '/\{\$([a-z0-9_\'\[\]])+(\.([a-z0-9_\'\[\]])+)?(\|[a-z0-9\-_]+(:[\']?[a-z0-9\-_\s\$\[\]:]+[\']?)*)*\}/i';
- // temp variables
- $variables = array();
- /*
- * We willen een lijstje bijhouden van alle variabelen die wel gematched zijn, maar niet correct zijn.
- * Van zodra dit de enige variabelen zijn die nog overschieten, dang aan we de while loop breken.
- */
- // we want to keep parsing vars until none can be found.
- while(1)
- {
- // find matches
- if(preg_match_all($pattern, $content, $matches))
- {
- // init var
- $correctVariables = false;
- // loop matches
- foreach($matches[0] as $match)
- {
- // variable doesn't already exist
- if(array_search($match, $variables, true) === false)
- {
- // syntax check this match
- if($this->isCorrectSyntax($match, 'variable'))
- {
- // unique key
- $key = md5($match);
- // add parsed variable
- $variables[$key] = $this->parseVariable($match);
- // replace in content
- $content = str_replace($match, '[$'. $key .']', $content);
- // note that at least 1 good variable was found
- $correctVariables = true;
- }
- }
- }
- if(!$correctVariables) break;
- }
- // break the loop, no matches were found
- else break;
- }
- /**
- * Every variable needs to be searched & replaced one by one,
- * since only then the nesting process works as intended.
- */
- foreach($variables as $key => $value)
- {
- // loop each element except this one
- foreach($variables as $keyIndex => $valueContent)
- {
- // skip myself
- if($key == $keyIndex) continue;
- // replace myself in the other var
- $variables[$keyIndex] = str_replace('[$'. $key .']', $variables[$key], $variables[$keyIndex]);
- }
- }
- /**
- * Now loop these vars again, but this time parse them in the
- * content we're actually working with.
- */
- foreach($variables as $key => $value)
- {
- $content = str_replace('[$'. $key .']', '<?php echo '. $value .'; ?>', $content);
- }
- return $content;
- }
- /**
- * Set the cache directory.
- *
- * @return void
- * @param string $path The location of the cache directory to store cached template blocks.
- */
- public function setCacheDirectory($path)
- {
- $this->cacheDirectory = (string) $path;
- }
- /**
- * Set the compile directory.
- *
- * @return void
- * @param string $path The location of the compile directory to store compiled templates in.
- */
- public function setCompileDirectory($path)
- {
- $this->compileDirectory = (string) $path;
- }
- /**
- * If enabled, recompiles a template even if it has already been compiled.
- *
- * @return void
- * @param bool[optional] $on Should this template be recompiled every time it's loaded.
- */
- public function setForceCompile($on = true)
- {
- $this->foreCompile = (bool) $on;
- }
- /**
- * Sets the forms.
- *
- * @return void
- * @param array $forms An array of forms that need to be included in this template.
- */
- public function setForms(array $forms)
- {
- $this->forms = $forms;
- }
- /**
- * Strips php code from the content.
- *
- * @return string The updated content, no longer containing php code.
- * @param string $content The content that may contain php code.
- */
- private function stripCode($content)
- {
- return $content = preg_replace("/\<\?(php)?(.*)\?\>/siU", '', $content);
- }
- /**
- * Strip comments from the output.
- *
- * @return string The updated content, no longer containing template comments.
- * @param string $content The content that may contain template comments.
- */
- private function stripComments($content)
- {
- return $content = preg_replace("/\{\*(.+?)\*\}/s", '', $content);
- }
- }
- /**
- * This class implements modifier mapping for the template engine.
- *
- * @package template
- *
- *
- * @author Davy Hellemans <davy@spoon-library.be>
- * @since 1.0.0
- */
- class SpoonTemplateModifiers
- {
- /**
- * Default modifiers mapped to their functions
- *
- * @var array
- */
- private static $modifiers = array( 'addslashes' => 'addslashes',
- 'createhtmllinks' => array('SpoonTemplateModifiers', 'createHTMLLinks'),
- 'date' => array('SpoonTemplateModifiers', 'date'),
- 'htmlentities' => array('SpoonFilter', 'htmlentities'),
- 'lowercase' => array('SpoonTemplateModifiers', 'lowercase'),
- 'ltrim' => 'ltrim',
- 'nl2br' => 'nl2br',
- 'repeat' => 'str_repeat',
- 'rtrim' => 'rtrim',
- 'shuffle' => 'str_shuffle',
- 'sprintf' => 'sprintf',
- 'stripslashes' => 'stripslashes',
- 'substring' => 'substr',
- 'trim' => 'trim',
- 'ucfirst' => 'ucfirst',
- 'ucwords' => 'ucwords',
- 'uppercase' => array('SpoonTemplateModifiers', 'uppercase'));
- /**
- * Clears the entire modifiers list.
- *
- * @return void
- */
- public static function clearModifiers()
- {
- self::$modifiers = array();
- }
- /**
- * Converts links to HTML links (only to be used with cleartext).
- *
- * @return string The text containing the parsed html links.
- * @param string $text The cleartext that may contain urls that need to be transformed to html links.
- */
- public static function createHTMLLinks($text)
- {
- return SpoonFilter::replaceURLsWithAnchors($text, false);
- }
- /**
- * Formats a language specific date.
- *
- * @return string The formatted date according to the timestamp, format and provided language.
- * @param int $timestamp The timestamp that you want to apply the format to.
- * @param string[optional] $format The optional format that you want to apply on the provided timestamp.
- * @param string[optional] $language The optional language that you want this format in. (Check SpoonLocale for the possible languages)
- */
- public static function date($timestamp, $format = 'Y-m-d H:i:s', $language = 'en')
- {
- return SpoonDate::getDate($format, $timestamp, $language);
- }
- /**
- * Retrieves the modifiers.
- *
- * @return array The list of modifiers and the function/method that they're mapped to.
- */
- public static function getModifiers()
- {
- return self::$modifiers;
- }
- /**
- * Makes this string lowercase.
- *
- * @return string The string, completely lowercased.
- * @param string $string The string that you want to apply this method on.
- */
- public static function lowercase($string)
- {
- return mb_convert_case($string, MB_CASE_LOWER, SPOON_CHARSET);
- }
- /**
- * Maps a specific modifier to a function/method.
- *
- * @return void
- * @param string $name The name of the modifier that you want to map.
- * @param mixed $function The function or method that you want to map to the provided name. To map a method provided this argument as an array containing class and method.
- */
- public static function mapModifier($name, $function)
- {
- // validate modifier
- if(!SpoonFilter::isValidAgainstRegexp('/[a-zA-Z0-9\_\-]+/', (string) $name)) throw new SpoonTemplateException('Modifier names can only contain a-z, 0-9 and - and _');
- // class method
- if(is_array($function))
- {
- // not enough elements
- if(count($function) != 2) throw new SpoonTemplateException('The array should contain the class and static method.');
- // method doesn't exist
- if(!method_exists($function[0], $function[1])) throw new SpoonTemplateException('The method "'. $function[1] .'" in the class '. $function[0] .' does not exist.');
- // all fine
- self::$modifiers[(string) $name] = $function;
- }
- // regular function
- else
- {
- // function doesn't exist
- if(!function_exists((string) $function)) throw new SpoonTemplateException('The function "'. (string) $function .'" does not exist.');
- // all fine
- self::$modifiers[(string) $name] = $function;
- }
- }
- /**
- * Transform the string to uppercase.
- *
- * @return string The string, completly uppercased.
- * @param string $string The string that you want to apply this method on.
- */
- public static function uppercase($string)
- {
- return mb_convert_case($string, MB_CASE_UPPER, SPOON_CHARSET);
- }
- }
- ?>