<?php
/**
* @package Habari
*
*/
/**
*
* Habari HiEngine class
*
* The HiEngine is a subclass of the RawPHPEngine class
* which is intended for those theme designers who want to use
* simple {hi:*} tags four output instead of PHP
*
* To use this engine, specify "hiengine" in the theme.xml of a theme.
*
* This engine behaves exactly like RawPHPEngine after the template tags are
* processed, so if an existing RawPHPEngine template is switched to HiEngine,
* it should still run without issue.
*/
class HiEngine extends RawPHPEngine {
/**
*
* Constructor for HiEngine
*
* Sets up the stream protocol handler
*/
public function __construct()
{
$streams = stream_get_wrappers();
if ( ! in_array( 'hi', $streams ) ) {
stream_wrapper_register( "hi", "HiEngineParser" )
or die( _t( "Failed to register HiEngine stream protocol" ) );
}
}
/**
*
* A function which outputs the result of a transposed
* template to the output stream
* @param template $ Name of template to display
*/
public function display( $template )
{
extract( $this->engine_vars );
//Utils::debug($this->engine_vars);die();
if ( $this->template_exists( $template ) ) {
$template_file = isset( $this->template_map[$template] ) ? $this->template_map[$template] : null;
$template_file = Plugins::filter( 'include_template_file', $template_file, $template, __CLASS__ );
$template_file = 'hi://' . $template_file;
$fc = file_get_contents( $template_file );
//echo($fc);
eval( '?'.'>' . $fc );
//include $template_file; // stopped working properly in PHP 5.2.8
}
}
}
/**
* HiEngineParser - A stream filtering class for the HiEngine
*/
class HiEngineParser
{
protected $file;
protected $filename;
protected $position;
protected $contexts;
protected $strings = array();
/**
* Open a HiEngineParser stream
*
* @param string $path Path of the opened resource, including the protocol specifier
* @param string $mode Mode used to open the file
* @param integer $options Bitmask options for opening this stream
* @param string $opened_path The actual path opened if using relative path, by reference
* @return boolean true on success
*/
function stream_open( $path, $mode, $options, &$opened_path )
{
$this->filename = substr( $path, 5 );
$this->file = file_get_contents( $this->filename );
// This processed value should cache and invalidate if a checksum of the template changes! :)
$this->file = $this->process( $this->file );
$this->position = 0;
return true;
}
/**
* Read data from a HiEngineParser stream
*
* @param integer $count Number of characters to read from the current position
* @return string Characters read from the stream
*/
function stream_read( $count )
{
if ( $this->stream_eof() ) {
return false;
}
$ret = substr( $this->file, $this->position, $count );
$this->position += strlen( $ret );
return $ret;
}
/**
* Srite data to a HiEngineParser stream
*
* @param string $data Data to write
* @return boolean false, since this stream type is read-only
*/
function stream_write( $data )
{
// HiEngineParser streams are read-only
return false;
}
/**
* Report the position in the stream
*
* @return integer the position in the stream
*/
function stream_tell()
{
return $this->position;
}
/**
* Report whether the stream is at the end of the file
*
* @return boolean true if the file pointer is at or beyond the end of the file
*/
function stream_eof()
{
return $this->position >= strlen( $this->file );
}
/**
* Seek to a specific position within the stream
*
* @param integer $offset The offset from the specified position
* @param integer $whence The position to seek from
* @return boolean true if seek was successful
*/
function stream_seek( $offset, $whence )
{
switch ( $whence ) {
case SEEK_SET:
if ( $offset < strlen( $this->file ) && $offset >= 0 ) {
$this->position = $offset;
return true;
}
else {
return false;
}
break;
case SEEK_CUR:
if ( $offset >= 0 ) {
$this->position += $offset;
return true;
}
else {
return false;
}
break;
case SEEK_END:
if ( strlen( $this->file ) + $offset >= 0 ) {
$this->position = strlen( $this->file ) + $offset;
return true;
}
else {
return false;
}
break;
default:
return false;
}
}
/**
* Return fstat() info as required when calling stats on the stream
* @return array An array of stat info
*/
function stream_stat()
{
return array();
}
/**
* Process the template file for template tags
*
* @param string $template The template file contents
* @return string The processed template
*/
function process( $template )
{
$template = preg_replace_callback( '/\{hi:(".+?")((?:\s*[\w\.]+){0,2})\s*\}/smu', array( $this, 'hi_quote' ), $template );
$template = preg_replace_callback( '%\{hi:([^\?]+?)\}(.+?)\{/hi:\1\}%ism', array( $this, 'hi_loop' ), $template );
$template = preg_replace_callback( '%\{hi:\?\s*(.+?)\}(.+?)\{/hi:\?\}%ismu', array( $this, 'hi_if' ), $template );
$template = preg_replace_callback( '%\{hi:([^:}]+?:.+?)\}%i', array( $this, 'hi_command' ), $template );
$template = preg_replace_callback( '%\{hi:(.+?)\}%i', array( $this, 'hi_var' ), $template );
return $template;
}
/**
* Replace a single function template tag with its PHP counterpart
*
* @param array $matches The match array found in HiEngineParser::process()
* @return string The PHP replacement for the function template tag
*/
function hi_command( $matches )
{
$cmd = trim( $matches[1] );
// Catch tags in the format {hi:command:parameter}
if ( preg_match( '/^(\w+):(.+)$/u', $cmd, $cmd_matches ) ) {
switch ( strtolower( $cmd_matches[1] ) ) {
case 'area':
return '<?php $theme->area(\'' . $cmd_matches[2] . '\'); ?>';
case 'display':
return '<?php $theme->display(\'' . $cmd_matches[2] . '\'); ?>';
case 'option':
case 'options':
return '<?php Options::out(\'' . $cmd_matches[2] . '\'); ?>';
case 'siteurl':
return '<?php Site::out_url( \'' . $cmd_matches[2] . '\' ); ?>';
case 'url':
return '<?php URL::out( \'' . $cmd_matches[2] . '\' ); ?>';
case 'session':
switch ( $cmd_matches[2] ) {
case 'messages':
return '<?php if (Session::has_messages()){Session::messages_out();} ?>';
case 'errors':
return '<?php if (Session::has_errors()){Session::messages_out();} ?>';
}
// this is an internal match
case 'context':
return $this->hi_to_var( $cmd_matches[2] );
case 'escape':
return '<?php echo Utils::htmlspecialchars( ' . $this->hi_to_var( $cmd_matches[2] ) . ' ); ?>';
}
}
return $matches[0];
}
/**
* Replace a single template tag with its PHP counterpart
*
* @param array $matches The match array found in HiEngineParser::process()
* @return string The PHP replacement for the template tag
*/
function hi_var( $matches )
{
$cmd = trim( $matches[1] );
$params = array();
$returnval = false;
if ( preg_match_all( '/(?<=\s)(?P<name>[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff.]*)\s*=\s*(?P<value>(?P<quot>["\']).+?\3|[^"\'\s]+)/i', $cmd, $foundparams, PREG_SET_ORDER ) ) {
foreach ( $foundparams as $p ) {
$params[$p['name']] = trim( $p['value'], $p['quot'] );
}
}
// Straight variable or property output, ala {hi:variable_name} or {hi:post.title}
if ( preg_match( '%(^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff.]*)%i', $cmd, $cmdmatch ) ) {
$cmdmatch = str_replace( '.', '->', $cmdmatch[1] );
if ( count( $this->contexts ) ) {
// Build a conditional that checks for the most specific, then the least
// eg.- $a->b->c->d->x, then $a->b->c->x, down to just $x
$ctx = $this->contexts;
$prefixes = array();
foreach ( $ctx as $void ) {
$prefixes[] = implode( '->', $this->contexts );
array_pop( $ctx );
}
$output = '';
foreach ( $prefixes as $prefix ) {
$output .= '(is_object($' . $prefix . ') && !'.'is_null($' . $prefix . '->' . $cmdmatch . ')) ? $' . $prefix . '->' . $cmdmatch . ' : ';
}
$output .= '$' . $cmdmatch;
$returnval = $output;
}
else {
$returnval = '$'. $cmdmatch;
}
}
if ( $returnval !== false ) {
$returnval = $this->apply_parameters( $returnval, $params );
return '<?php echo '. $returnval . '; ?>';
}
// Use tags in the format {hi:@foo} as theme functions, ala $theme->foo();
if ( $cmd[0] == '@' ) {
return '<?php $theme->' . substr( $cmd, 1 ) . '(); ?>';
}
// Didn't match anything we support so far
return $matches[0];
}
/**
* Take the found paramters on a variable tag and apply them to the output
*
* @param array $returnval The expression to be output
* @param array $params An associative array of parameters
* @return string The PHP expression with the paramters applied
*/
function apply_parameters( $returnval, $params )
{
foreach ( $params as $k => $v ) {
if ( $k[0] == '@' ) {
$returnval = '$theme->' . substr( $cmd, 1 ) . '(' . $returnval . ", '" . $v . "')";
}
switch ( $k ) {
case 'dateformat':
$returnval = "call_user_func(array(" . $returnval . ", 'format'), '" . addslashes( $v ) . "')";
break;
}
}
return $returnval;
}
/**
* Replace a loop tag section with its PHP counterpart, and add the context to the stack
*
* @param array $matches The match array found in HiEngineParser::process()
* @return string The PHP replacement for the template tag
*/
function hi_loop( $matches )
{
$hivar = $matches[1];
$phpvar = $this->hi_to_var( $hivar );
$iterator = strpos( $hivar, '.' ) ? substr( $hivar, strrpos( $hivar, '.' ) + 1 ) : $hivar;
$output = '<?php foreach(' . $phpvar . ' as $' . $iterator . '_index => $' . $iterator . '_1): ?>';
$this->contexts[] = "{$iterator}_1";
$output .= $this->process( $matches[2] );
$output .= '<?php endforeach; ?>';
array_pop( $this->contexts );
return $output;
}
/**
* Replace variables in the hiengine syntax with PHP varaibles
* @param array $matches The match array found in hi_if ()
* @returns string A PHP variable string to use as the replacement
*/
function var_replace( $matches )
{
$var = $matches[1];
if ( is_callable( $var ) ) {
return $var;
}
if ( preg_match( '/true|false|null|isset|empty/i', $var ) ) {
return $var;
}
$var = $this->hi_to_var( $var );
return $var;
}
function hi_to_var( $hisyntax )
{
$var = str_replace( '.', '->', $hisyntax );
if ( count( $this->contexts ) ) {
// Build a conditional that checks for the most specific, then the least
// eg.- $a->b->c->d->x, then $a->b->c->x, down to just $x
$ctx = $this->contexts;
$prefixes = array();
foreach ( $ctx as $void ) {
$prefixes[] = implode( '->', $this->contexts );
array_pop( $ctx );
}
$output = '';
foreach ( $prefixes as $prefix ) {
$output .= '(!is_null($' . $prefix . '->' . $var . ') ? $' . $prefix . '->' . $var . ' : ';
}
$output .= '$' . $var . ')';
return $output;
}
else {
return '$'. $var;
}
}
/**
* Creates a table of static strings in hiengine expressions to be replaced in later
* @param array $matches The match found in hi_if ()
* @returns string An uncommon string index for the stored static string.
*/
function string_stack( $matches )
{
$key = chr( 0 ) . count( $this->strings ) . chr( 1 );
$this->strings[$key] = $matches[0];
return $key;
}
/**
* Replace an if tag section with its PHP counterpart
*
* @param array $matches The match array found in HiEngineParser::process()
* @return string The PHP replacement for the template tag
*/
function hi_if ( $matches )
{
list( $void, $eval, $context ) = $matches;
$eval = preg_replace_callback( '/([\'"]).*?(?<!\\\\)\1/i', array( $this, 'string_stack' ), $eval );
$eval = preg_replace_callback( '/\b((?<!::)[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff.]*(?!::))\b/i', array( $this, 'var_replace' ), $eval );
$eval = preg_replace( '/(?<!=)=(?!=)/i', '==', $eval );
$eval = str_replace( array_keys( $this->strings ), $this->strings, $eval );
$context = preg_replace( '/\{hi:\?else\?\}/i', '<?php else: ?>', $context );
$output = '<?php if (' . $eval . '): ?>';
$output .= $this->process( $context );
$output .= '<?php endif; ?>';
return $output;
}
/**
* Prepare strings for translation
* @param array $matches Matches in HiEngineParser::process()
* @param string The PHP replacement for the template tag
*/
function hi_quote( $matches )
{
$args = preg_split( '/\s+/u', trim( $matches[2] ) );
preg_match_all( '/"(.+?)(?<!\\\\)"/', $matches[1], $quotes );
$count = 0;
$all_vars = array();
foreach ( $quotes[1] as $index => $quote ) {
preg_match_all( '/{hi:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff.]*)}/', $quote, $vars, PREG_SET_ORDER );
foreach ( $vars as $var ) {
$count++;
$quote = str_replace( $var[0], '%'.$count.'\$s', $quote );
$all_vars[] = '{hi:context:' . $var[1] . '}'; //$this->hi_to_var($var[1]);
}
$quotes[1][$index] = $quote;
}
if ( count( $quotes[1] ) > 1 ) {
$output = '<?php printf(_n("'.$quotes[1][0].'", "'.$quotes[1][1].'", {hi:context:'.$args[0].'})';
// Add vars
if ( count( $all_vars ) > 0 ) {
$output .= ', ' . implode( ', ', $all_vars );
}
array_shift( $args );
}
else {
$output = '<?php _e("'.$quotes[1][0].'"';
// Add vars
if ( count( $all_vars ) > 0 ) {
$output .= ', array(' . implode( ', ', $all_vars ) . ')';
}
}
// Add the domain, if any
if ( isset( $args[0] ) ) {
$output .= ', "'.$args[0].'"';
}
// Close the tag
$output .= '); ?>';
return $output;
}
}
?>