1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4: */ 3// +----------------------------------------------------------------------+ 4// | PHP Version 4 | 5// +----------------------------------------------------------------------+ 6// | Copyright (c) 1997-2002 The PHP Group | 7// +----------------------------------------------------------------------+ 8// | This source file is subject to version 2.02 of the PHP license, | 9// | that is bundled with this package in the file LICENSE, and is | 10// | available at through the world-wide-web at | 11// | http://www.php.net/license/2_02.txt. | 12// | If you did not receive a copy of the PHP license and are unable to | 13// | obtain it through the world-wide-web, please send a note to | 14// | license@php.net so we can mail you a copy immediately. | 15// +----------------------------------------------------------------------+ 16// | Authors: Alan Knowles <alan@akbkhome.com> | 17// +----------------------------------------------------------------------+ 18// 19// $Id: Flexy.php 334846 2014-09-12 04:50:56Z alan_k $ 20// 21// Base Compiler Class 22// Standard 'Original Flavour' Flexy compiler 23 24// this does the main conversion, (eg. for {vars and methods}) 25// it relays into Compiler/Tag & Compiler/Flexy for tags and namespace handling. 26 27 28 29 30require_once 'HTML/Template/Flexy/Tokenizer.php'; 31require_once 'HTML/Template/Flexy/Token.php'; 32 33class HTML_Template_Flexy_Compiler_Flexy extends HTML_Template_Flexy_Compiler { 34 35 /** 36 * reference to calling controller 37 * 38 * @var $flexy HTML_Template_Flexy 39 * @access public 40 */ 41 var $flexy; 42 43 44 45 /** 46 * The current template (Full path) 47 * 48 * @var string 49 * @access public 50 */ 51 var $currentTemplate; 52 53 /** 54 * when using flexy::contents - this contains the map of 55 * <... flexy:conntents="KEY">.....VALUE ...<...> 56 * 57 */ 58 var $contentStrings = array(); 59 60 /** 61 * The compile method. 62 * 63 * @params object HTML_Template_Flexy 64 * @params string|false string to compile of false to use a file. 65 * @return string filename of template 66 * @access public 67 */ 68 function compile($flexy, $string=false) 69 { 70 // read the entire file into one variable 71 72 // note this should be moved to new HTML_Template_Flexy_Token 73 // and that can then manage all the tokens in one place.. 74 global $_HTML_TEMPLATE_FLEXY_COMPILER; 75 76 $this->flexy = $flexy; 77 78 $this->currentTemplate = $flexy->currentTemplate; 79 80 $gettextStrings = &$_HTML_TEMPLATE_FLEXY_COMPILER['gettextStrings']; 81 $gettextStrings = array(); // reset it. 82 83 if (@$this->options['debug']) { 84 echo "compiling template $flexy->currentTemplate<BR>"; 85 86 } 87 88 // reset the elements. 89 $flexy->_elements = array(); 90 91 // replace this with a singleton?? 92 93 $GLOBALS['_HTML_TEMPLATE_FLEXY']['currentOptions'] = $this->options; 94 $GLOBALS['_HTML_TEMPLATE_FLEXY']['elements'] = array(); 95 $GLOBALS['_HTML_TEMPLATE_FLEXY']['filename'] = $flexy->currentTemplate; 96 $GLOBALS['_HTML_TEMPLATE_FLEXY']['prefixOutput'] = ''; 97 $GLOBALS['_HTML_TEMPLATE_FLEXY']['compiledTemplate']= $flexy->compiledTemplate; 98 99 100 // initialize Translation 2, and 101 $this->flexy->initializeTranslator(); 102 103 104 // load the template! 105 $data = $string; 106 $res = false; 107 if ($string === false) { 108 $data = file_get_contents($flexy->currentTemplate); 109 } 110 111 // PRE PROCESS {_(.....)} translation markers. 112 if (strpos($data, '{_(') !== false) { 113 $data = $this->preProcessTranslation($data); 114 } 115 116 // Tree generation!!! 117 118 119 120 if (!$this->options['forceCompile'] && isset($_HTML_TEMPLATE_FLEXY_COMPILER['cache'][md5($data)])) { 121 $res = $_HTML_TEMPLATE_FLEXY_COMPILER['cache'][md5($data)]; 122 } else { 123 124 125 $tokenizer = new HTML_Template_Flexy_Tokenizer($data); 126 $tokenizer->fileName = $flexy->currentTemplate; 127 128 129 130 //$tokenizer->debug=1; 131 $tokenizer->options['ignore_html'] = $this->options['nonHTML']; 132 133 134 require_once 'HTML/Template/Flexy/Token.php'; 135 $res = HTML_Template_Flexy_Token::buildTokens($tokenizer); 136 if ($this->is_a($res, 'PEAR_Error')) { 137 return $res; 138 } 139 $_HTML_TEMPLATE_FLEXY_COMPILER['cache'][md5($data)] = $res; 140 141 } 142 143 144 // technically we shouldnt get here as we dont cache errors.. 145 if ($this->is_a($res, 'PEAR_Error')) { 146 return $res; 147 } 148 149 // turn tokens into Template.. 150 151 $data = $res->compile($this); 152 153 if ($this->is_a($data, 'PEAR_Error')) { 154 return $data; 155 } 156 157 $data = $GLOBALS['_HTML_TEMPLATE_FLEXY']['prefixOutput'] . $data; 158 159 if ( $flexy->options['debug'] > 1) { 160 echo "<B>Result: </B><PRE>".htmlspecialchars($data)."</PRE><BR>\n"; 161 } 162 163 if ($this->options['nonHTML']) { 164 $data = str_replace("?>\n", "?>\n\n", $data); 165 } 166 167 168 169 170 // at this point we are into writing stuff... 171 if ($flexy->options['compileToString']) { 172 if ( $flexy->options['debug']) { 173 echo "<B>Returning string:<BR>\n"; 174 } 175 176 $flexy->elements = $GLOBALS['_HTML_TEMPLATE_FLEXY']['elements']; 177 return $data; 178 } 179 180 181 182 183 // error checking? 184 $file = $flexy->compiledTemplate; 185 if (isset($flexy->options['output.block'])) { 186 list($file, $part) = explode('#', $file); 187 } 188 189 if( ($cfp = fopen($file, 'w')) ) { 190 if ($flexy->options['debug']) { 191 echo "<B>Writing: </B>$file<BR>\n"; 192 } 193 fwrite($cfp, $data); 194 fclose($cfp); 195 196 chmod($file, 0775); 197 // make the timestamp of the two items match. 198 clearstatcache(); 199 touch($file, filemtime($flexy->currentTemplate)); 200 if ($file != $flexy->compiledTemplate) { 201 chmod($flexy->compiledTemplate, 0775); 202 // make the timestamp of the two items match. 203 clearstatcache(); 204 touch($flexy->compiledTemplate, filemtime($flexy->currentTemplate)); 205 } 206 207 208 } else { 209 return HTML_Template_Flexy::staticRaiseError('HTML_Template_Flexy::failed to write to '.$flexy->compiledTemplate, 210 HTML_TEMPLATE_FLEXY_ERROR_FILE, HTML_TEMPLATE_FLEXY_ERROR_RETURN); 211 } 212 // gettext strings 213 214 if (file_exists($flexy->getTextStringsFile)) { 215 unlink($flexy->getTextStringsFile); 216 } 217 218 if($gettextStrings && ($cfp = fopen( $flexy->getTextStringsFile, 'w') ) ) { 219 220 fwrite($cfp, serialize(array_unique($gettextStrings))); 221 fclose($cfp); 222 chmod($flexy->getTextStringsFile, 0664); 223 } 224 225 // elements 226 if (file_exists($flexy->elementsFile)) { 227 unlink($flexy->elementsFile); 228 } 229 230 if( $GLOBALS['_HTML_TEMPLATE_FLEXY']['elements'] && 231 ($cfp = fopen( $flexy->elementsFile, 'w') ) ) { 232 fwrite($cfp, serialize( $GLOBALS['_HTML_TEMPLATE_FLEXY']['elements'])); 233 fclose($cfp); 234 chmod($flexy->elementsFile, 0664); 235 // now clear it. 236 237 } 238 239 return true; 240 } 241 242 243 244 245 246 247 /** 248 * do the early tranlsation of {_(......)_} text 249 * 250 * 251 * @param input string 252 * @return output string 253 * @access public 254 */ 255 function preProcessTranslation($data) { 256 global $_HTML_TEMPLATE_FLEXY_COMPILER; 257 $matches = array(); 258 $lmatches = explode ('{_(', $data); 259 array_shift($lmatches); 260 // shift the first.. 261 foreach ($lmatches as $k) { 262 if (false === strpos($k, ')_}')) { 263 continue; 264 } 265 $x = explode(')_}', $k); 266 $matches[] = $x[0]; 267 } 268 269 270 //echo '<PRE>';print_r($matches); 271 // we may need to do some house cleaning here... 272 $_HTML_TEMPLATE_FLEXY_COMPILER['gettextStrings'] = $matches; 273 274 275 // replace them now.. 276 // ** leaving in the tag (which should be ignored by the parser.. 277 // we then get rid of the tags during the toString method in this class. 278 foreach($matches as $v) { 279 $data = str_replace('{_('.$v.')_}', '{_('.$this->flexy->translateString($v).')_}', $data); 280 } 281 return $data; 282 } 283 284 285 286 287 288 /** 289 * Flag indicating compiler is inside {_( .... )_} block, and should not 290 * add to the gettextstrings array. 291 * 292 * @var boolean 293 * @access public 294 */ 295 var $inGetTextBlock = false; 296 297 /** 298 * This is the base toString Method, it relays into toString{TokenName} 299 * 300 * @param object HTML_Template_Flexy_Token_* 301 * 302 * @return string string to build a template 303 * @access public 304 * @see toString* 305 */ 306 307 308 function toString($element) 309 { 310 static $len = 26; // strlen('HTML_Template_Flexy_Token_'); 311 if ($this->options['debug'] > 1) { 312 $x = $element; 313 unset($x->children); 314 //echo htmlspecialchars(print_r($x,true))."<BR>\n"; 315 } 316 if ($element->token == 'GetTextStart') { 317 $this->inGetTextBlock = true; 318 return ''; 319 } 320 if ($element->token == 'GetTextEnd') { 321 $this->inGetTextBlock = false; 322 return ''; 323 } 324 325 326 $class = get_class($element); 327 if (strlen($class) >= $len) { 328 $type = substr($class, $len); 329 return $this->{'toString'.$type}($element); 330 } 331 332 $ret = $element->value; 333 $add = $element->compileChildren($this); 334 if ($this->is_a($add, 'PEAR_Error')) { 335 return $add; 336 } 337 $ret .= $add; 338 339 if ($element->close) { 340 $add = $element->close->compile($this); 341 if ($this->is_a($add, 'PEAR_Error')) { 342 return $add; 343 } 344 $ret .= $add; 345 } 346 347 return $ret; 348 } 349 350 351 /** 352 * HTML_Template_Flexy_Token_Else toString 353 * 354 * @param object HTML_Template_Flexy_Token_Else 355 * 356 * @return string string to build a template 357 * @access public 358 * @see toString* 359 */ 360 361 362 function toStringElse($element) 363 { 364 // pushpull states to make sure we are in an area.. - should really check to see 365 // if the state it is pulling is a if... 366 if ($element->pullState() === false) { 367 return $this->appendHTML( 368 "<font color=\"red\">Unmatched {else:} on line: {$element->line}</font>" 369 ); 370 } 371 $element->pushState(); 372 return $this->appendPhp("} else {"); 373 } 374 375 /** 376 * HTML_Template_Flexy_Token_End toString 377 * 378 * @param object HTML_Template_Flexy_Token_Else 379 * 380 * @return string string to build a template 381 * @access public 382 * @see toString* 383 */ 384 385 function toStringEnd($element) 386 { 387 // pushpull states to make sure we are in an area.. - should really check to see 388 // if the state it is pulling is a if... 389 if ($element->pullState() === false) { 390 return $this->appendHTML( 391 "<font color=\"red\">Unmatched {end:} on line: {$element->line}</font>" 392 ); 393 } 394 395 return $this->appendPhp("}"); 396 } 397 398 /** 399 * HTML_Template_Flexy_Token_EndTag toString 400 * 401 * @param object HTML_Template_Flexy_Token_EndTag 402 * 403 * @return string string to build a template 404 * @access public 405 * @see toString* 406 */ 407 408 409 410 function toStringEndTag($element) 411 { 412 return $this->toStringTag($element); 413 } 414 415 416 417 /** 418 * HTML_Template_Flexy_Token_Foreach toString 419 * 420 * @param object HTML_Template_Flexy_Token_Foreach 421 * 422 * @return string string to build a template 423 * @access public 424 * @see toString* 425 */ 426 427 428 function toStringForeach($element) 429 { 430 431 $loopon = $element->toVar($element->loopOn); 432 if ($this->is_a($loopon, 'PEAR_Error')) { 433 return $loopon; 434 } 435 436 $ret = 'if ($this->options[\'strict\'] || ('. 437 'is_array('. $loopon. ') || ' . 438 'is_object(' . $loopon . '))) ' . 439 'foreach(' . $loopon . " "; 440 441 $ret .= "as \${$element->key}"; 442 443 if ($element->value) { 444 $ret .= " => \${$element->value}"; 445 } 446 $ret .= ") {"; 447 448 $element->pushState(); 449 $element->pushVar($element->key); 450 $element->pushVar($element->value); 451 return $this->appendPhp($ret); 452 } 453 /** 454 * HTML_Template_Flexy_Token_If toString 455 * 456 * @param object HTML_Template_Flexy_Token_If 457 * 458 * @return string string to build a template 459 * @access public 460 * @see toString* 461 */ 462 463 function toStringIf($element) 464 { 465 466 $var = $element->toVar($element->condition); 467 if ($this->is_a($var, 'PEAR_Error')) { 468 return $var; 469 } 470 471 $ret = "if (".$element->isNegative . $var .") {"; 472 $element->pushState(); 473 return $this->appendPhp($ret); 474 } 475 476 /** 477 * get Modifier Wrapper 478 * 479 * converts :h, :u, :r , ..... 480 * @param object HTML_Template_Flexy_Token_Method|Var 481 * 482 * @return array prefix,suffix 483 * @access public 484 * @see toString* 485 */ 486 487 function getModifierWrapper($element) 488 { 489 $prefix = 'echo '; 490 491 $suffix = ''; 492 $modifier = strlen(trim($element->modifier)) ? $element->modifier : ' '; 493 494 switch ($modifier) { 495 case 'h': 496 break; 497 case 'u': 498 $prefix = 'echo urlencode('; 499 $suffix = ')'; 500 break; 501 case 'r': 502 $prefix = 'echo \'<pre>\'; echo htmlspecialchars(print_r('; 503 $suffix = ',true)); echo \'</pre>\';'; 504 break; 505 case 'n': 506 // blank or value.. 507 $numberformat = @$GLOBALS['_HTML_TEMPLATE_FLEXY']['currentOptions']['numberFormat']; 508 $prefix = 'echo number_format('; 509 $suffix = $GLOBALS['_HTML_TEMPLATE_FLEXY']['currentOptions']['numberFormat'] . ')'; 510 break; 511 case 'b': // nl2br + htmlspecialchars 512 $prefix = 'echo nl2br(htmlspecialchars('; 513 514 // add language ? 515 $suffix = '))'; 516 break; 517 case 'e': 518 $prefix = 'echo htmlentities('; 519 // add language ? 520 $suffix = ')'; 521 break; 522 523 case ' ': 524 $prefix = 'echo htmlspecialchars('; 525 // add language ? 526 $suffix = ')'; 527 break; 528 default: 529 $prefix = 'echo $this->plugin("'.trim($element->modifier) .'",'; 530 $suffix = ')'; 531 532 533 } 534 535 return array($prefix, $suffix); 536 } 537 538 539 540 /** 541 * HTML_Template_Flexy_Token_Var toString 542 * 543 * @param object HTML_Template_Flexy_Token_Method 544 * 545 * @return string string to build a template 546 * @access public 547 * @see toString* 548 */ 549 550 function toStringVar($element) 551 { 552 // ignore modifier at present!! 553 554 $var = $element->toVar($element->value); 555 if ($this->is_a($var, 'PEAR_Error')) { 556 return $var; 557 } 558 list($prefix, $suffix) = $this->getModifierWrapper($element); 559 return $this->appendPhp( $prefix . $var . $suffix .';'); 560 } 561 /** 562 * HTML_Template_Flexy_Token_Method toString 563 * 564 * @param object HTML_Template_Flexy_Token_Method 565 * 566 * @return string string to build a template 567 * @access public 568 * @see toString* 569 */ 570 571 function toStringMethod($element) 572 { 573 574 575 // set up the modifier at present!! 576 577 list($prefix, $suffix) = $this->getModifierWrapper($element); 578 579 // add the '!' to if 580 581 if ($element->isConditional) { 582 $prefix = 'if ('.$element->isNegative; 583 $element->pushState(); 584 $suffix = ')'; 585 } 586 587 588 // check that method exists.. 589 // if (method_exists($object,'method'); 590 $bits = explode('.', $element->method); 591 $method = array_pop($bits); 592 593 $object = implode('.', $bits); 594 595 $var = $element->toVar($object); 596 if ($this->is_a($var, 'PEAR_Error')) { 597 return $var; 598 } 599 600 if (($object == 'GLOBALS') && 601 $GLOBALS['_HTML_TEMPLATE_FLEXY']['currentOptions']['globalfunctions']) { 602 // we should check if they something weird like: GLOBALS.xxxx[sdf](....) 603 $var = $method; 604 } else { 605 $prefix = 'if ($this->options[\'strict\'] || (isset('.$var. 606 ') && method_exists('.$var .", '{$method}'))) " . $prefix; 607 $var = $element->toVar($element->method); 608 } 609 610 611 if ($this->is_a($var, 'PEAR_Error')) { 612 return $var; 613 } 614 615 $ret = $prefix; 616 $ret .= $var . "("; 617 $s =0; 618 619 620 621 foreach($element->args as $a) { 622 623 if ($s) { 624 $ret .= ","; 625 } 626 $s =1; 627 if ($a{0} == '#') { 628 if (is_numeric(substr($a, 1, -1))) { 629 $ret .= substr($a, 1, -1); 630 } else { 631 $ret .= '"'. addslashes(substr($a, 1, -1)) . '"'; 632 } 633 continue; 634 } 635 636 $var = $element->toVar($a); 637 if ($this->is_a($var, 'PEAR_Error')) { 638 return $var; 639 } 640 $ret .= $var; 641 642 } 643 $ret .= ")" . $suffix; 644 645 if ($element->isConditional) { 646 $ret .= ' { '; 647 } else { 648 $ret .= ";"; 649 } 650 651 652 653 return $this->appendPhp($ret); 654 655 656 657 } 658 /** 659 * HTML_Template_Flexy_Token_Processing toString 660 * 661 * @param object HTML_Template_Flexy_Token_Processing 662 * 663 * @return string string to build a template 664 * @access public 665 * @see toString* 666 */ 667 668 669 function toStringProcessing($element) 670 { 671 // if it's XML then quote it.. 672 if (strtoupper(substr($element->value, 2, 3)) == 'XML') { 673 return $this->appendPhp("echo '" . str_replace("'", "\\"."'", $element->value) . "';"); 674 } 675 // otherwise it's PHP code - so echo it.. 676 return $element->value; 677 } 678 679 /** 680 * HTML_Template_Flexy_Token_Text toString 681 * 682 * @param object HTML_Template_Flexy_Token_Text 683 * 684 * @return string string to build a template 685 * @access public 686 * @see toString* 687 */ 688 689 690 691 function toStringText($element) 692 { 693 694 // first get rid of stuff thats not translated etc. 695 // empty strings => output. 696 // comments -> just output 697 // our special tags -> output.. 698 699 if (!strlen(trim($element->value) )) { 700 return $this->appendHtml($element->value); 701 } 702 // dont add comments to translation lists. 703 704 if (substr($element->value, 0, 4) == '<!--') { 705 return $this->appendHtml($element->value); 706 } 707 // ignore anything wrapped with {_( .... )_} 708 if ($this->inGetTextBlock) { 709 return $this->appendHtml($element->value); 710 } 711 712 713 if (!$element->isWord()) { 714 return $this->appendHtml($element->value); 715 } 716 717 // grab the white space at start and end (and keep it! 718 719 $value = ltrim($element->value); 720 $front = substr($element->value, 0, -strlen($value)); 721 $value = rtrim($element->value); 722 $rear = substr($element->value, strlen($value)); 723 $value = trim($element->value); 724 725 726 // convert to escaped chars.. (limited..) 727 //$value = strtr($value,$cleanArray); 728 729 // this only applies to html templates 730 if (empty($this->flexy->options['nonHTML'])) { 731 $this->addStringToGettext($value); 732 $value = $this->flexy->translateString($value); 733 } 734 // its a simple word! 735 return $this->appendHtml($front . $value . $rear); 736 737 } 738 739 740 741 /** 742 * HTML_Template_Flexy_Token_Cdata toString 743 * 744 * @param object HTML_Template_Flexy_Token_Cdata ? 745 * 746 * @return string string to build a template 747 * @access public 748 * @see toString* 749 */ 750 751 752 753 function toStringCdata($element) 754 { 755 return $this->appendHtml($element->value); 756 } 757 758 759 760 761 762 763 764 765 766 767 /** 768 * addStringToGettext 769 * 770 * Adds a string to the gettext array. 771 * 772 * @param mixed preferably.. string to store 773 * 774 * @return none 775 * @access public 776 */ 777 778 function addStringToGettext($string) 779 { 780 if (!empty($this->options['disableTranslate'])) { 781 return; 782 } 783 if (!is_string($string)) { 784 return; 785 } 786 787 if (!preg_match('/\w+/i', $string)) { 788 return; 789 } 790 $string = trim($string); 791 792 if (substr($string, 0, 4) == '<!--') { 793 return; 794 } 795 796 $GLOBALS['_HTML_TEMPLATE_FLEXY_COMPILER']['gettextStrings'][] = $string; 797 } 798 799 800 801 /** 802 * HTML_Template_Flexy_Token_Tag toString 803 * 804 * @param object HTML_Template_Flexy_Token_Tag 805 * 806 * @return string string to build a template 807 * @access public 808 * @see toString* 809 */ 810 811 function toStringTag($element) { 812 813 $original = $element->getAttribute('ALT'); 814 // techncially only input type=(submit|button|input) alt=.. applies, but we may 815 // as well translate any occurance... 816 if ( (($element->tag == 'IMG') || ($element->tag == 'INPUT')) 817 && is_string($original) && strlen($original)) { 818 $this->addStringToGettext($original); 819 $quote = $element->ucAttributes['ALT']{0}; 820 $element->ucAttributes['ALT'] = $quote . $this->flexy->translateString($original). $quote; 821 } 822 $original = $element->getAttribute('TITLE'); 823 if (is_string($original) && strlen($original)) { 824 $this->addStringToGettext($original); 825 $quote = $element->ucAttributes['TITLE']{0}; 826 $element->ucAttributes['TITLE'] = $quote . $this->flexy->translateString($original). $quote; 827 } 828 829 830 if (strpos($element->tag, ':') === false) { 831 $namespace = 'Tag'; 832 } else { 833 $bits = explode(':', $element->tag); 834 $namespace = $bits[0]; 835 } 836 if ($namespace{0} == '/') { 837 $namespace = substr($namespace, 1); 838 } 839 if (empty($this->tagHandlers[$namespace])) { 840 841 require_once 'HTML/Template/Flexy/Compiler/Flexy/Tag.php'; 842 $this->tagHandlers[$namespace] = &HTML_Template_Flexy_Compiler_Flexy_Tag::factory($namespace, $this); 843 if (!$this->tagHandlers[$namespace] ) { 844 return HTML_Template_Flexy::staticRaiseError('HTML_Template_Flexy::failed to create Namespace Handler '.$namespace . 845 ' in file ' . $GLOBALS['_HTML_TEMPLATE_FLEXY']['filename'], 846 HTML_TEMPLATE_FLEXY_ERROR_SYNTAX, HTML_TEMPLATE_FLEXY_ERROR_RETURN); 847 } 848 849 } 850 return $this->tagHandlers[$namespace]->toString($element); 851 852 853 } 854 /** 855 * PHP5 compat - arg... 856 * - where else does this affect 857 */ 858 function classExists($class) 859 { 860 return (substr(phpversion(),0,1) < 5) ? class_exists($class) : class_exists($class,false); 861 } 862 863 864} 865