00001 <?php
00002 namespace Habari;
00022 class HiEngine extends RawPHPEngine {
00029 public function __construct()
00030 {
00031 $streams = stream_get_wrappers();
00032 if ( ! in_array( 'hi', $streams ) ) {
00033 stream_wrapper_register( "hi", "\Habari\HiEngineParser" )
00034 or die( _t( "Failed to register HiEngine stream protocol" ) );
00035 }
00036 }
00037
00044 public function display( $template )
00045 {
00046 extract( $this->engine_vars );
00047
00048 if ( $this->template_exists( $template ) ) {
00049 $template_file = isset( $this->template_map[$template] ) ? $this->template_map[$template] : null;
00050 $template_file = Plugins::filter( 'include_template_file', $template_file, $template, __CLASS__ );
00051 $template_file = 'hi://' . $template_file;
00052 $fc = file_get_contents( $template_file );
00053
00054 eval( 'namespace Habari; ?'.'>' . $fc );
00055
00056 }
00057 }
00058 }
00059
00063 class HiEngineParser
00064 {
00065 protected $file;
00066 protected $filename;
00067 protected $position;
00068 protected $contexts;
00069 protected $strings = array();
00070
00080 function stream_open( $path, $mode, $options, &$opened_path )
00081 {
00082 $this->filename = substr( $path, 5 );
00083 $this->file = file_get_contents( $this->filename );
00084
00085
00086 $this->file = $this->process( $this->file );
00087
00088 $this->position = 0;
00089
00090 return true;
00091 }
00092
00099 function stream_read( $count )
00100 {
00101 if ( $this->stream_eof() ) {
00102 return false;
00103 }
00104 $ret = substr( $this->file, $this->position, $count );
00105 $this->position += strlen( $ret );
00106 return $ret;
00107 }
00108
00115 function stream_write( $data )
00116 {
00117
00118 return false;
00119 }
00120
00126 function stream_tell()
00127 {
00128 return $this->position;
00129 }
00130
00136 function stream_eof()
00137 {
00138 return $this->position >= strlen( $this->file );
00139 }
00140
00148 function stream_seek( $offset, $whence )
00149 {
00150 switch ( $whence ) {
00151 case SEEK_SET:
00152 if ( $offset < strlen( $this->file ) && $offset >= 0 ) {
00153 $this->position = $offset;
00154 return true;
00155 }
00156 else {
00157 return false;
00158 }
00159 break;
00160
00161 case SEEK_CUR:
00162 if ( $offset >= 0 ) {
00163 $this->position += $offset;
00164 return true;
00165 }
00166 else {
00167 return false;
00168 }
00169 break;
00170
00171 case SEEK_END:
00172 if ( strlen( $this->file ) + $offset >= 0 ) {
00173 $this->position = strlen( $this->file ) + $offset;
00174 return true;
00175 }
00176 else {
00177 return false;
00178 }
00179 break;
00180
00181 default:
00182 return false;
00183 }
00184
00185 }
00186
00191 function stream_stat()
00192 {
00193 return array();
00194 }
00195
00202 function process( $template )
00203 {
00204 $template = preg_replace_callback( '/\{hi:(".+?")((?:\s*[\w\.]+){0,2})\s*\}/smu', array( $this, 'hi_quote' ), $template );
00205 $template = preg_replace_callback( '%\{hi:([^\?]+?)\}(.+?)\{/hi:\1\}%ism', array( $this, 'hi_loop' ), $template );
00206 $template = preg_replace_callback( '%\{hi:\?\s*(.+?)\}(.+?)\{/hi:\?\}%ismu', array( $this, 'hi_if' ), $template );
00207 $template = preg_replace_callback( '%\{hi:([^:}]+?:.+?)\}%i', array( $this, 'hi_command' ), $template );
00208 $template = preg_replace_callback( '%\{hi:(.+?)\}%i', array( $this, 'hi_var' ), $template );
00209 return $template;
00210 }
00211
00218 function hi_command( $matches )
00219 {
00220 $cmd = trim( $matches[1] );
00221
00222
00223 if ( preg_match( '/^(\w+):(.+)$/u', $cmd, $cmd_matches ) ) {
00224 switch ( strtolower( $cmd_matches[1] ) ) {
00225 case 'area':
00226 return '<?php echo $theme->area(\'' . $cmd_matches[2] . '\'); ?>';
00227 case 'display':
00228 return '<?php $theme->display(\'' . $cmd_matches[2] . '\'); ?>';
00229 case 'option':
00230 case 'options':
00231 return '<?php Options::out(\'' . $cmd_matches[2] . '\'); ?>';
00232 case 'siteurl':
00233 return '<?php Site::out_url( \'' . $cmd_matches[2] . '\' ); ?>';
00234 case 'url':
00235 return '<?php URL::out( \'' . $cmd_matches[2] . '\' ); ?>';
00236 case 'session':
00237 switch ( $cmd_matches[2] ) {
00238 case 'messages':
00239 return '<?php if (Session::has_messages()){Session::messages_out();} ?>';
00240 case 'errors':
00241 return '<?php if (Session::has_errors()){Session::messages_out();} ?>';
00242 }
00243 // this is an internal match
00244 case 'context':
00245 return $this->hi_to_var( $cmd_matches[2] );
00246 case 'escape':
00247 return '<?php echo Utils::htmlspecialchars( ' . $this->hi_to_var( $cmd_matches[2] ) . ' ); ?>';
00248 }
00249 }
00250
00251 return $matches[0];
00252 }
00253
00260 function hi_var( $matches )
00261 {
00262 $cmd = trim( $matches[1] );
00263 $params = array();
00264 $returnval = false;
00265
00266 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 ) ) {
00267 foreach ( $foundparams as $p ) {
00268 $params[$p['name']] = trim( $p['value'], $p['quot'] );
00269 }
00270 }
00271
00272
00273 if ( preg_match( '%(^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff.]*)%i', $cmd, $cmdmatch ) ) {
00274 $cmdmatch = str_replace( '.', '->', $cmdmatch[1] );
00275 if ( count( $this->contexts ) ) {
00276
00277
00278 $ctx = $this->contexts;
00279 $prefixes = array();
00280 foreach ( $ctx as $void ) {
00281 $prefixes[] = implode( '->', $this->contexts );
00282 array_pop( $ctx );
00283 }
00284 $output = '';
00285 foreach ( $prefixes as $prefix ) {
00286 $output .= '(is_object($' . $prefix . ') && !'.'is_null($' . $prefix . '->' . $cmdmatch . ')) ? $' . $prefix . '->' . $cmdmatch . ' : ';
00287 }
00288 $output .= '$' . $cmdmatch;
00289 $returnval = $output;
00290 }
00291 else {
00292 $returnval = '$'. $cmdmatch;
00293 }
00294 }
00295 if ( $returnval !== false ) {
00296 $returnval = $this->apply_parameters( $returnval, $params );
00297 return '<?php echo '. $returnval . '; ?>';
00298 }
00299
00300
00301 if ( $cmd[0] == '@' ) {
00302 return '<?php $theme->' . substr( $cmd, 1 ) . '(); ?>';
00303 }
00304
00305
00306 return $matches[0];
00307 }
00308
00316 function apply_parameters( $returnval, $params )
00317 {
00318 foreach ( $params as $k => $v ) {
00319 if ( $k[0] == '@' ) {
00320 $returnval = '$theme->' . substr( $k, 1 ) . '(' . $returnval . ", '" . $v . "')";
00321 }
00322 switch ( $k ) {
00323 case 'dateformat':
00324 $returnval = "call_user_func(array(" . $returnval . ", 'format'), '" . addslashes( $v ) . "')";
00325 break;
00326 }
00327 }
00328 return $returnval;
00329 }
00330
00337 function hi_loop( $matches )
00338 {
00339 $hivar = $matches[1];
00340 $phpvar = $this->hi_to_var( $hivar );
00341 $iterator = strpos( $hivar, '.' ) ? substr( $hivar, strrpos( $hivar, '.' ) + 1 ) : $hivar;
00342 $output = '<?php foreach(' . $phpvar . ' as $' . $iterator . '_index => $' . $iterator . '_1): ?>';
00343 $this->contexts[] = "{$iterator}_1";
00344 $output .= $this->process( $matches[2] );
00345 $output .= '<?php endforeach; ?>';
00346 array_pop( $this->contexts );
00347 return $output;
00348 }
00349
00355 function var_replace( $matches )
00356 {
00357 $var = $matches[1];
00358
00359 if ( is_callable( $var ) ) {
00360 return $var;
00361 }
00362 if ( preg_match( '/true|false|null|isset|empty/i', $var ) ) {
00363 return $var;
00364 }
00365
00366 $var = $this->hi_to_var( $var );
00367
00368 return $var;
00369 }
00370
00371 function hi_to_var( $hisyntax )
00372 {
00373 $var = str_replace( '.', '->', $hisyntax );
00374 if ( count( $this->contexts ) ) {
00375
00376
00377 $ctx = $this->contexts;
00378 $prefixes = array();
00379 foreach ( $ctx as $void ) {
00380 $prefixes[] = implode( '->', $this->contexts );
00381 array_pop( $ctx );
00382 }
00383 $output = '';
00384 foreach ( $prefixes as $prefix ) {
00385 $output .= '(!is_null($' . $prefix . '->' . $var . ') ? $' . $prefix . '->' . $var . ' : ';
00386 }
00387 $output .= '$' . $var . ')';
00388 return $output;
00389 }
00390 else {
00391 return '$'. $var;
00392 }
00393 }
00394
00400 function string_stack( $matches )
00401 {
00402 $key = chr( 0 ) . count( $this->strings ) . chr( 1 );
00403 $this->strings[$key] = $matches[0];
00404 return $key;
00405 }
00406
00413 function hi_if ( $matches )
00414 {
00415 list( $void, $eval, $context ) = $matches;
00416
00417 $eval = preg_replace_callback( '/([\'"]).*?(?<!\\\\)\1/i', array( $this, 'string_stack' ), $eval );
00418 $eval = preg_replace_callback( '/\b((?<!::)[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff.]*(?!::))\b/i', array( $this, 'var_replace' ), $eval );
00419 $eval = preg_replace( '/(?<!=)=(?!=)/i', '==', $eval );
00420 $eval = str_replace( array_keys( $this->strings ), $this->strings, $eval );
00421
00422 $context = preg_replace( '/\{hi:\?else\?\}/i', '<?php else: ?>', $context );
00423
00424 $output = '<?php if (' . $eval . '): ?>';
00425 $output .= $this->process( $context );
00426 $output .= '<?php endif; ?>';
00427 return $output;
00428 }
00429
00435 function hi_quote( $matches )
00436 {
00437 $args = preg_split( '/\s+/u', trim( $matches[2] ) );
00438
00439 preg_match_all( '/"(.+?)(?<!\\\\)"/', $matches[1], $quotes );
00440 $count = 0;
00441 $all_vars = array();
00442 foreach ( $quotes[1] as $index => $quote ) {
00443 preg_match_all( '/{hi:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff.]*)}/', $quote, $vars, PREG_SET_ORDER );
00444 foreach ( $vars as $var ) {
00445 $count++;
00446 $quote = str_replace( $var[0], '%'.$count.'\$s', $quote );
00447 $all_vars[] = '{hi:context:' . $var[1] . '}';
00448 }
00449 $quotes[1][$index] = $quote;
00450 }
00451 if ( count( $quotes[1] ) > 1 ) {
00452 $output = '<?php printf(_n("'.$quotes[1][0].'", "'.$quotes[1][1].'", {hi:context:'.$args[0].'})';
00453
00454 if ( count( $all_vars ) > 0 ) {
00455 $output .= ', ' . implode( ', ', $all_vars );
00456 }
00457 array_shift( $args );
00458 }
00459 else {
00460 $output = '<?php _e("'.$quotes[1][0].'"';
00461
00462 if ( count( $all_vars ) > 0 ) {
00463 $output .= ', array(' . implode( ', ', $all_vars ) . ')';
00464 }
00465 }
00466
00467
00468 if ( isset( $args[0] ) ) {
00469 $output .= ', "'.$args[0].'"';
00470 }
00471
00472
00473 $output .= '); ?>';
00474
00475 return $output;
00476 }
00477 }
00478
00479 ?>