1<?php 2 3include dirname(__FILE__) . '/Compilation/Exception.php'; 4 5/** 6 * default dwoo compiler class, compiles dwoo templates into php 7 * 8 * This software is provided 'as-is', without any express or implied warranty. 9 * In no event will the authors be held liable for any damages arising from the use of this software. 10 * 11 * @author Jordi Boggiano <j.boggiano@seld.be> 12 * @copyright Copyright (c) 2008, Jordi Boggiano 13 * @license http://dwoo.org/LICENSE Modified BSD License 14 * @link http://dwoo.org/ 15 * @version 1.1.0 16 * @date 2009-07-18 17 * @package Dwoo 18 */ 19class Dwoo_Compiler implements Dwoo_ICompiler 20{ 21 /** 22 * constant that represents a php opening tag 23 * 24 * use it in case it needs to be adjusted 25 * 26 * @var string 27 */ 28 const PHP_OPEN = "<?php "; 29 30 /** 31 * constant that represents a php closing tag 32 * 33 * use it in case it needs to be adjusted 34 * 35 * @var string 36 */ 37 const PHP_CLOSE = "?>"; 38 39 /** 40 * boolean flag to enable or disable debugging output 41 * 42 * @var bool 43 */ 44 public $debug = false; 45 46 /** 47 * left script delimiter 48 * 49 * @var string 50 */ 51 protected $ld = '{'; 52 53 /** 54 * left script delimiter with escaped regex meta characters 55 * 56 * @var string 57 */ 58 protected $ldr = '\\{'; 59 60 /** 61 * right script delimiter 62 * 63 * @var string 64 */ 65 protected $rd = '}'; 66 67 /** 68 * right script delimiter with escaped regex meta characters 69 * 70 * @var string 71 */ 72 protected $rdr = '\\}'; 73 74 /** 75 * defines whether the nested comments should be parsed as nested or not 76 * 77 * defaults to false (classic block comment parsing as in all languages) 78 * 79 * @var bool 80 */ 81 protected $allowNestedComments = false; 82 83 /** 84 * defines whether opening and closing tags can contain spaces before valid data or not 85 * 86 * turn to true if you want to be sloppy with the syntax, but when set to false it allows 87 * to skip javascript and css tags as long as they are in the form "{ something", which is 88 * nice. default is false. 89 * 90 * @var bool 91 */ 92 protected $allowLooseOpenings = false; 93 94 /** 95 * defines whether the compiler will automatically html-escape variables or not 96 * 97 * default is false 98 * 99 * @var bool 100 */ 101 protected $autoEscape = false; 102 103 /** 104 * security policy object 105 * 106 * @var Dwoo_Security_Policy 107 */ 108 protected $securityPolicy; 109 110 /** 111 * stores the custom plugins registered with this compiler 112 * 113 * @var array 114 */ 115 protected $customPlugins = array(); 116 117 /** 118 * stores the template plugins registered with this compiler 119 * 120 * @var array 121 */ 122 protected $templatePlugins = array(); 123 124 /** 125 * stores the pre- and post-processors callbacks 126 * 127 * @var array 128 */ 129 protected $processors = array('pre'=>array(), 'post'=>array()); 130 131 /** 132 * stores a list of plugins that are used in the currently compiled 133 * template, and that are not compilable. these plugins will be loaded 134 * during the template's runtime if required. 135 * 136 * it is a 1D array formatted as key:pluginName value:pluginType 137 * 138 * @var array 139 */ 140 protected $usedPlugins; 141 142 /** 143 * stores the template undergoing compilation 144 * 145 * @var string 146 */ 147 protected $template; 148 149 /** 150 * stores the current pointer position inside the template 151 * 152 * @var int 153 */ 154 protected $pointer; 155 156 /** 157 * stores the current line count inside the template for debugging purposes 158 * 159 * @var int 160 */ 161 protected $line; 162 163 /** 164 * stores the current template source while compiling it 165 * 166 * @var string 167 */ 168 protected $templateSource; 169 170 /** 171 * stores the data within which the scope moves 172 * 173 * @var array 174 */ 175 protected $data; 176 177 /** 178 * variable scope of the compiler, set to null if 179 * it can not be resolved to a static string (i.e. if some 180 * plugin defines a new scope based on a variable array key) 181 * 182 * @var mixed 183 */ 184 protected $scope; 185 186 /** 187 * variable scope tree, that allows to rebuild the current 188 * scope if required, i.e. when going to a parent level 189 * 190 * @var array 191 */ 192 protected $scopeTree; 193 194 /** 195 * block plugins stack, accessible through some methods 196 * 197 * @see findBlock 198 * @see getCurrentBlock 199 * @see addBlock 200 * @see addCustomBlock 201 * @see injectBlock 202 * @see removeBlock 203 * @see removeTopBlock 204 * 205 * @var array 206 */ 207 protected $stack = array(); 208 209 /** 210 * current block at the top of the block plugins stack, 211 * accessible through getCurrentBlock 212 * 213 * @see getCurrentBlock 214 * 215 * @var Dwoo_Block_Plugin 216 */ 217 protected $curBlock; 218 219 /** 220 * current dwoo object that uses this compiler, or null 221 * 222 * @var Dwoo 223 */ 224 protected $dwoo; 225 226 /** 227 * holds an instance of this class, used by getInstance when you don't 228 * provide a custom compiler in order to save resources 229 * 230 * @var Dwoo_Compiler 231 */ 232 protected static $instance; 233 234 /** 235 * token types 236 * @var int 237 */ 238 const T_UNQUOTED_STRING = 1; 239 const T_NUMERIC = 2; 240 const T_NULL = 4; 241 const T_BOOL = 8; 242 const T_MATH = 16; 243 const T_BREAKCHAR = 32; 244 245 /** 246 * constructor 247 * 248 * saves the created instance so that child templates get the same one 249 */ 250 public function __construct() 251 { 252 self::$instance = $this; 253 } 254 255 /** 256 * sets the delimiters to use in the templates 257 * 258 * delimiters can be multi-character strings but should not be one of those as they will 259 * make it very hard to work with templates or might even break the compiler entirely : "\", "$", "|", ":" and finally "#" only if you intend to use config-vars with the #var# syntax. 260 * 261 * @param string $left left delimiter 262 * @param string $right right delimiter 263 */ 264 public function setDelimiters($left, $right) 265 { 266 $this->ld = $left; 267 $this->rd = $right; 268 $this->ldr = preg_quote($left, '/'); 269 $this->rdr = preg_quote($right, '/'); 270 } 271 272 /** 273 * returns the left and right template delimiters 274 * 275 * @return array containing the left and the right delimiters 276 */ 277 public function getDelimiters() 278 { 279 return array($this->ld, $this->rd); 280 } 281 282 /** 283 * sets the way to handle nested comments, if set to true 284 * {* foo {* some other *} comment *} will be stripped correctly. 285 * 286 * if false it will remove {* foo {* some other *} and leave "comment *}" alone, 287 * this is the default behavior 288 * 289 * @param bool $allow allow nested comments or not, defaults to true (but the default internal value is false) 290 */ 291 public function setNestedCommentsHandling($allow = true) { 292 $this->allowNestedComments = (bool) $allow; 293 } 294 295 /** 296 * returns the nested comments handling setting 297 * 298 * @see setNestedCommentsHandling 299 * @return bool true if nested comments are allowed 300 */ 301 public function getNestedCommentsHandling() { 302 return $this->allowNestedComments; 303 } 304 305 /** 306 * sets the tag openings handling strictness, if set to true, template tags can 307 * contain spaces before the first function/string/variable such as { $foo} is valid. 308 * 309 * if set to false (default setting), { $foo} is invalid but that is however a good thing 310 * as it allows css (i.e. #foo { color:red; }) to be parsed silently without triggering 311 * an error, same goes for javascript. 312 * 313 * @param bool $allow true to allow loose handling, false to restore default setting 314 */ 315 public function setLooseOpeningHandling($allow = false) 316 { 317 $this->allowLooseOpenings = (bool) $allow; 318 } 319 320 /** 321 * returns the tag openings handling strictness setting 322 * 323 * @see setLooseOpeningHandling 324 * @return bool true if loose tags are allowed 325 */ 326 public function getLooseOpeningHandling() 327 { 328 return $this->allowLooseOpenings; 329 } 330 331 /** 332 * changes the auto escape setting 333 * 334 * if enabled, the compiler will automatically html-escape variables, 335 * unless they are passed through the safe function such as {$var|safe} 336 * or {safe $var} 337 * 338 * default setting is disabled/false 339 * 340 * @param bool $enabled set to true to enable, false to disable 341 */ 342 public function setAutoEscape($enabled) 343 { 344 $this->autoEscape = (bool) $enabled; 345 } 346 347 /** 348 * returns the auto escape setting 349 * 350 * default setting is disabled/false 351 * 352 * @return bool 353 */ 354 public function getAutoEscape() 355 { 356 return $this->autoEscape; 357 } 358 359 /** 360 * adds a preprocessor to the compiler, it will be called 361 * before the template is compiled 362 * 363 * @param mixed $callback either a valid callback to the preprocessor or a simple name if the autoload is set to true 364 * @param bool $autoload if set to true, the preprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback 365 */ 366 public function addPreProcessor($callback, $autoload = false) 367 { 368 if ($autoload) { 369 $name = str_replace('Dwoo_Processor_', '', $callback); 370 $class = 'Dwoo_Processor_'.$name; 371 372 if (class_exists($class, false)) { 373 $callback = array(new $class($this), 'process'); 374 } elseif (function_exists($class)) { 375 $callback = $class; 376 } else { 377 $callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name); 378 } 379 380 $this->processors['pre'][] = $callback; 381 } else { 382 $this->processors['pre'][] = $callback; 383 } 384 } 385 386 /** 387 * removes a preprocessor from the compiler 388 * 389 * @param mixed $callback either a valid callback to the preprocessor or a simple name if it was autoloaded 390 */ 391 public function removePreProcessor($callback) 392 { 393 if (($index = array_search($callback, $this->processors['pre'], true)) !== false) { 394 unset($this->processors['pre'][$index]); 395 } elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['pre'], true)) !== false) { 396 unset($this->processors['pre'][$index]); 397 } else { 398 $class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback); 399 foreach ($this->processors['pre'] as $index=>$proc) { 400 if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) { 401 unset($this->processors['pre'][$index]); 402 break; 403 } 404 } 405 } 406 } 407 408 /** 409 * adds a postprocessor to the compiler, it will be called 410 * before the template is compiled 411 * 412 * @param mixed $callback either a valid callback to the postprocessor or a simple name if the autoload is set to true 413 * @param bool $autoload if set to true, the postprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback 414 */ 415 public function addPostProcessor($callback, $autoload = false) 416 { 417 if ($autoload) { 418 $name = str_replace('Dwoo_Processor_', '', $callback); 419 $class = 'Dwoo_Processor_'.$name; 420 421 if (class_exists($class, false)) { 422 $callback = array(new $class($this), 'process'); 423 } elseif (function_exists($class)) { 424 $callback = $class; 425 } else { 426 $callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name); 427 } 428 429 $this->processors['post'][] = $callback; 430 } else { 431 $this->processors['post'][] = $callback; 432 } 433 } 434 435 /** 436 * removes a postprocessor from the compiler 437 * 438 * @param mixed $callback either a valid callback to the postprocessor or a simple name if it was autoloaded 439 */ 440 public function removePostProcessor($callback) 441 { 442 if (($index = array_search($callback, $this->processors['post'], true)) !== false) { 443 unset($this->processors['post'][$index]); 444 } elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['post'], true)) !== false) { 445 unset($this->processors['post'][$index]); 446 } else { 447 $class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback); 448 foreach ($this->processors['post'] as $index=>$proc) { 449 if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) { 450 unset($this->processors['post'][$index]); 451 break; 452 } 453 } 454 } 455 } 456 457 /** 458 * internal function to autoload processors at runtime if required 459 * 460 * @param string $class the class/function name 461 * @param string $name the plugin name (without Dwoo_Plugin_ prefix) 462 */ 463 protected function loadProcessor($class, $name) 464 { 465 if (!class_exists($class, false) && !function_exists($class)) { 466 try { 467 $this->dwoo->getLoader()->loadPlugin($name); 468 } catch (Dwoo_Exception $e) { 469 throw new Dwoo_Exception('Processor '.$name.' could not be found in your plugin directories, please ensure it is in a file named '.$name.'.php in the plugin directory'); 470 } 471 } 472 473 if (class_exists($class, false)) { 474 return array(new $class($this), 'process'); 475 } 476 477 if (function_exists($class)) { 478 return $class; 479 } 480 481 throw new Dwoo_Exception('Wrong processor name, when using autoload the processor must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Processor_name"'); 482 } 483 484 /** 485 * adds an used plugin, this is reserved for use by the {template} plugin 486 * 487 * this is required so that plugin loading bubbles up from loaded 488 * template files to the current one 489 * 490 * @private 491 * @param string $name function name 492 * @param int $type plugin type (Dwoo_Core::*_PLUGIN) 493 */ 494 public function addUsedPlugin($name, $type) 495 { 496 $this->usedPlugins[$name] = $type; 497 } 498 499 /** 500 * returns all the plugins this template uses 501 * 502 * @private 503 * @return array the list of used plugins in the parsed template 504 */ 505 public function getUsedPlugins() 506 { 507 return $this->usedPlugins; 508 } 509 510 /** 511 * adds a template plugin, this is reserved for use by the {template} plugin 512 * 513 * this is required because the template functions are not declared yet 514 * during compilation, so we must have a way of validating their argument 515 * signature without using the reflection api 516 * 517 * @private 518 * @param string $name function name 519 * @param array $params parameter array to help validate the function call 520 * @param string $uuid unique id of the function 521 * @param string $body function php code 522 */ 523 public function addTemplatePlugin($name, array $params, $uuid, $body = null) 524 { 525 $this->templatePlugins[$name] = array('params'=> $params, 'body' => $body, 'uuid' => $uuid); 526 } 527 528 /** 529 * returns all the parsed sub-templates 530 * 531 * @private 532 * @return array the parsed sub-templates 533 */ 534 public function getTemplatePlugins() 535 { 536 return $this->templatePlugins; 537 } 538 539 /** 540 * marks a template plugin as being called, which means its source must be included in the compiled template 541 * 542 * @param string $name function name 543 */ 544 public function useTemplatePlugin($name) 545 { 546 $this->templatePlugins[$name]['called'] = true; 547 } 548 549 /** 550 * adds the custom plugins loaded into Dwoo to the compiler so it can load them 551 * 552 * @see Dwoo_Core::addPlugin 553 * @param array $customPlugins an array of custom plugins 554 */ 555 public function setCustomPlugins(array $customPlugins) 556 { 557 $this->customPlugins = $customPlugins; 558 } 559 560 /** 561 * sets the security policy object to enforce some php security settings 562 * 563 * use this if untrusted persons can modify templates, 564 * set it on the Dwoo object as it will be passed onto the compiler automatically 565 * 566 * @param Dwoo_Security_Policy $policy the security policy object 567 */ 568 public function setSecurityPolicy(Dwoo_Security_Policy $policy = null) 569 { 570 $this->securityPolicy = $policy; 571 } 572 573 /** 574 * returns the current security policy object or null by default 575 * 576 * @return Dwoo_Security_Policy|null the security policy object if any 577 */ 578 public function getSecurityPolicy() 579 { 580 return $this->securityPolicy; 581 } 582 583 /** 584 * sets the pointer position 585 * 586 * @param int $position the new pointer position 587 * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position 588 */ 589 public function setPointer($position, $isOffset = false) 590 { 591 if ($isOffset) { 592 $this->pointer += $position; 593 } else { 594 $this->pointer = $position; 595 } 596 } 597 598 /** 599 * returns the current pointer position, only available during compilation of a template 600 * 601 * @return int 602 */ 603 public function getPointer() 604 { 605 return $this->pointer; 606 } 607 608 /** 609 * sets the line number 610 * 611 * @param int $number the new line number 612 * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position 613 */ 614 public function setLine($number, $isOffset = false) 615 { 616 if ($isOffset) { 617 $this->line += $number; 618 } else { 619 $this->line = $number; 620 } 621 } 622 623 /** 624 * returns the current line number, only available during compilation of a template 625 * 626 * @return int 627 */ 628 public function getLine() 629 { 630 return $this->line; 631 } 632 633 /** 634 * returns the dwoo object that initiated this template compilation, only available during compilation of a template 635 * 636 * @return Dwoo 637 */ 638 public function getDwoo() 639 { 640 return $this->dwoo; 641 } 642 643 /** 644 * overwrites the template that is being compiled 645 * 646 * @param string $newSource the template source that must replace the current one 647 * @param bool $fromPointer if set to true, only the source from the current pointer position is replaced 648 * @return string the template or partial template 649 */ 650 public function setTemplateSource($newSource, $fromPointer = false) 651 { 652 if ($fromPointer === true) { 653 $this->templateSource = substr($this->templateSource, 0, $this->pointer) . $newSource; 654 } else { 655 $this->templateSource = $newSource; 656 } 657 } 658 659 /** 660 * returns the template that is being compiled 661 * 662 * @param mixed $fromPointer if set to true, only the source from the current pointer 663 * position is returned, if a number is given it overrides the current pointer 664 * @return string the template or partial template 665 */ 666 public function getTemplateSource($fromPointer = false) 667 { 668 if ($fromPointer === true) { 669 return substr($this->templateSource, $this->pointer); 670 } elseif (is_numeric($fromPointer)) { 671 return substr($this->templateSource, $fromPointer); 672 } else { 673 return $this->templateSource; 674 } 675 } 676 677 /** 678 * resets the compilation pointer, effectively restarting the compilation process 679 * 680 * this is useful if a plugin modifies the template source since it might need to be recompiled 681 */ 682 public function recompile() 683 { 684 $this->setPointer(0); 685 } 686 687 /** 688 * compiles the provided string down to php code 689 * 690 * @param string $tpl the template to compile 691 * @return string a compiled php string 692 */ 693 public function compile(Dwoo_Core $dwoo, Dwoo_ITemplate $template) 694 { 695 // init vars 696 $tpl = $template->getSource(); 697 $ptr = 0; 698 $this->dwoo = $dwoo; 699 $this->template = $template; 700 $this->templateSource =& $tpl; 701 $this->pointer =& $ptr; 702 703 while (true) { 704 // if pointer is at the beginning, reset everything, that allows a plugin to externally reset the compiler if everything must be reparsed 705 if ($ptr===0) { 706 // resets variables 707 $this->usedPlugins = array(); 708 $this->data = array(); 709 $this->scope =& $this->data; 710 $this->scopeTree = array(); 711 $this->stack = array(); 712 $this->line = 1; 713 $this->templatePlugins = array(); 714 // add top level block 715 $compiled = $this->addBlock('topLevelBlock', array(), 0); 716 $this->stack[0]['buffer'] = ''; 717 718 if ($this->debug) echo 'COMPILER INIT<br />'; 719 720 if ($this->debug) echo 'PROCESSING PREPROCESSORS ('.count($this->processors['pre']).')<br>'; 721 722 // runs preprocessors 723 foreach ($this->processors['pre'] as $preProc) { 724 if (is_array($preProc) && isset($preProc['autoload'])) { 725 $preProc = $this->loadProcessor($preProc['class'], $preProc['name']); 726 } 727 if (is_array($preProc) && $preProc[0] instanceof Dwoo_Processor) { 728 $tpl = call_user_func($preProc, $tpl); 729 } else { 730 $tpl = call_user_func($preProc, $this, $tpl); 731 } 732 } 733 unset($preProc); 734 735 // show template source if debug 736 if ($this->debug) echo '<pre>'.print_r(htmlentities($tpl), true).'</pre><hr />'; 737 738 // strips php tags if required by the security policy 739 if ($this->securityPolicy !== null) { 740 $search = array('{<\?php.*?\?>}'); 741 if (ini_get('short_open_tags')) { 742 $search = array('{<\?.*?\?>}', '{<%.*?%>}'); 743 } 744 switch($this->securityPolicy->getPhpHandling()) { 745 746 case Dwoo_Security_Policy::PHP_ALLOW: 747 break; 748 case Dwoo_Security_Policy::PHP_ENCODE: 749 $tpl = preg_replace_callback($search, array($this, 'phpTagEncodingHelper'), $tpl); 750 break; 751 case Dwoo_Security_Policy::PHP_REMOVE: 752 $tpl = preg_replace($search, '', $tpl); 753 754 } 755 } 756 } 757 758 $pos = strpos($tpl, $this->ld, $ptr); 759 760 if ($pos === false) { 761 $this->push(substr($tpl, $ptr), 0); 762 break; 763 } elseif (substr($tpl, $pos-1, 1) === '\\' && substr($tpl, $pos-2, 1) !== '\\') { 764 $this->push(substr($tpl, $ptr, $pos-$ptr-1) . $this->ld); 765 $ptr = $pos+strlen($this->ld); 766 } elseif (preg_match('/^'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . 'literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', substr($tpl, $pos), $litOpen)) { 767 if (!preg_match('/'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . '\/literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', $tpl, $litClose, PREG_OFFSET_CAPTURE, $pos)) { 768 throw new Dwoo_Compilation_Exception($this, 'The {literal} blocks must be closed explicitly with {/literal}'); 769 } 770 $endpos = $litClose[0][1]; 771 $this->push(substr($tpl, $ptr, $pos-$ptr) . substr($tpl, $pos + strlen($litOpen[0]), $endpos-$pos-strlen($litOpen[0]))); 772 $ptr = $endpos+strlen($litClose[0][0]); 773 } else { 774 if (substr($tpl, $pos-2, 1) === '\\' && substr($tpl, $pos-1, 1) === '\\') { 775 $this->push(substr($tpl, $ptr, $pos-$ptr-1)); 776 $ptr = $pos; 777 } 778 779 $this->push(substr($tpl, $ptr, $pos-$ptr)); 780 $ptr = $pos; 781 782 $pos += strlen($this->ld); 783 if ($this->allowLooseOpenings) { 784 while (substr($tpl, $pos, 1) === ' ') { 785 $pos+=1; 786 } 787 } else { 788 if (substr($tpl, $pos, 1) === ' ' || substr($tpl, $pos, 1) === "\r" || substr($tpl, $pos, 1) === "\n" || substr($tpl, $pos, 1) === "\t") { 789 $ptr = $pos; 790 $this->push($this->ld); 791 continue; 792 } 793 } 794 795 // check that there is an end tag present 796 if (strpos($tpl, $this->rd, $pos) === false) { 797 throw new Dwoo_Compilation_Exception($this, 'A template tag was not closed, started with "'.substr($tpl, $ptr, 30).'"'); 798 } 799 800 801 $ptr += strlen($this->ld); 802 $subptr = $ptr; 803 804 while (true) { 805 $parsed = $this->parse($tpl, $subptr, null, false, 'root', $subptr); 806 807 // reload loop if the compiler was reset 808 if ($ptr === 0) { 809 continue 2; 810 } 811 812 $len = $subptr - $ptr; 813 $this->push($parsed, substr_count(substr($tpl, $ptr, $len), "\n")); 814 $ptr += $len; 815 816 if ($parsed === false) { 817 break; 818 } 819 } 820 821 // adds additional line breaks between php closing and opening tags because the php parser removes those if there is just a single line break 822 if (substr($this->curBlock['buffer'], -2) === '?>' && preg_match('{^(([\r\n])([\r\n]?))}', substr($tpl, $ptr, 3), $m)) { 823 if ($m[3] === '') { 824 $ptr+=1; 825 $this->push($m[1].$m[1], 1); 826 } else { 827 $ptr+=2; 828 $this->push($m[1]."\n", 2); 829 } 830 } 831 } 832 } 833 834 $compiled .= $this->removeBlock('topLevelBlock'); 835 836 if ($this->debug) echo 'PROCESSING POSTPROCESSORS<br>'; 837 838 foreach ($this->processors['post'] as $postProc) { 839 if (is_array($postProc) && isset($postProc['autoload'])) { 840 $postProc = $this->loadProcessor($postProc['class'], $postProc['name']); 841 } 842 if (is_array($postProc) && $postProc[0] instanceof Dwoo_Processor) { 843 $compiled = call_user_func($postProc, $compiled); 844 } else { 845 $compiled = call_user_func($postProc, $this, $compiled); 846 } 847 } 848 unset($postProc); 849 850 if ($this->debug) echo 'COMPILATION COMPLETE : MEM USAGE : '.memory_get_usage().'<br>'; 851 852 $output = "<?php\n/* template head */\n"; 853 854 // build plugin preloader 855 foreach ($this->usedPlugins as $plugin=>$type) { 856 if ($type & Dwoo_Core::CUSTOM_PLUGIN) { 857 continue; 858 } 859 860 switch($type) { 861 862 case Dwoo_Core::BLOCK_PLUGIN: 863 case Dwoo_Core::CLASS_PLUGIN: 864 $output .= "if (class_exists('Dwoo_Plugin_$plugin', false)===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n"; 865 break; 866 case Dwoo_Core::FUNC_PLUGIN: 867 $output .= "if (function_exists('Dwoo_Plugin_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n"; 868 break; 869 case Dwoo_Core::SMARTY_MODIFIER: 870 $output .= "if (function_exists('smarty_modifier_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n"; 871 break; 872 case Dwoo_Core::SMARTY_FUNCTION: 873 $output .= "if (function_exists('smarty_function_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n"; 874 break; 875 case Dwoo_Core::SMARTY_BLOCK: 876 $output .= "if (function_exists('smarty_block_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n"; 877 break; 878 case Dwoo_Core::PROXY_PLUGIN: 879 $output .= $this->getDwoo()->getPluginProxy()->getPreloader($plugin); 880 break; 881 default: 882 throw new Dwoo_Compilation_Exception($this, 'Type error for '.$plugin.' with type'.$type); 883 884 } 885 } 886 887 foreach ($this->templatePlugins as $function => $attr) { 888 if (isset($attr['called']) && $attr['called'] === true && !isset($attr['checked'])) { 889 $this->resolveSubTemplateDependencies($function); 890 } 891 } 892 foreach ($this->templatePlugins as $function) { 893 if (isset($function['called']) && $function['called'] === true) { 894 $output .= $function['body'].PHP_EOL; 895 } 896 } 897 898 $output .= $compiled."\n?>"; 899 900 $output = preg_replace('/(?<!;|\}|\*\/|\n|\{)(\s*'.preg_quote(self::PHP_CLOSE, '/') . preg_quote(self::PHP_OPEN, '/').')/', ";\n", $output); 901 $output = str_replace(self::PHP_CLOSE . self::PHP_OPEN, "\n", $output); 902 903 // handle <?xml tag at the beginning 904 $output = preg_replace('#(/\* template body \*/ \?>\s*)<\?xml#is', '$1<?php echo \'<?xml\'; ?>', $output); 905 906 if ($this->debug) { 907 echo '<hr><pre>'; 908 $lines = preg_split('{\r\n|\n|<br />}', highlight_string(($output), true)); 909 array_shift($lines); 910 foreach ($lines as $i=>$line) { 911 echo ($i+1).'. '.$line."\r\n"; 912 } 913 } 914 if ($this->debug) echo '<hr></pre></pre>'; 915 916 $this->template = $this->dwoo = null; 917 $tpl = null; 918 919 return $output; 920 } 921 922 /** 923 * checks what sub-templates are used in every sub-template so that we're sure they are all compiled 924 * 925 * @param string $function the sub-template name 926 */ 927 protected function resolveSubTemplateDependencies($function) 928 { 929 $body = $this->templatePlugins[$function]['body']; 930 foreach ($this->templatePlugins as $func => $attr) { 931 if ($func !== $function && !isset($attr['called']) && strpos($body, 'Dwoo_Plugin_'.$func) !== false) { 932 $this->templatePlugins[$func]['called'] = true; 933 $this->resolveSubTemplateDependencies($func); 934 } 935 } 936 $this->templatePlugins[$function]['checked'] = true; 937 } 938 939 /** 940 * adds compiled content to the current block 941 * 942 * @param string $content the content to push 943 * @param int $lineCount newlines count in content, optional 944 */ 945 public function push($content, $lineCount = null) 946 { 947 if ($lineCount === null) { 948 $lineCount = substr_count($content, "\n"); 949 } 950 951 if ($this->curBlock['buffer'] === null && count($this->stack) > 1) { 952 // buffer is not initialized yet (the block has just been created) 953 $this->stack[count($this->stack)-2]['buffer'] .= (string) $content; 954 $this->curBlock['buffer'] = ''; 955 } else { 956 if (!isset($this->curBlock['buffer'])) { 957 throw new Dwoo_Compilation_Exception($this, 'The template has been closed too early, you probably have an extra block-closing tag somewhere'); 958 } 959 // append current content to current block's buffer 960 $this->curBlock['buffer'] .= (string) $content; 961 } 962 $this->line += $lineCount; 963 } 964 965 /** 966 * sets the scope 967 * 968 * set to null if the scope becomes "unstable" (i.e. too variable or unknown) so that 969 * variables are compiled in a more evaluative way than just $this->scope['key'] 970 * 971 * @param mixed $scope a string i.e. "level1.level2" or an array i.e. array("level1", "level2") 972 * @param bool $absolute if true, the scope is set from the top level scope and not from the current scope 973 * @return array the current scope tree 974 */ 975 public function setScope($scope, $absolute = false) 976 { 977 $old = $this->scopeTree; 978 979 if ($scope===null) { 980 unset($this->scope); 981 $this->scope = null; 982 } 983 984 if (is_array($scope)===false) { 985 $scope = explode('.', $scope); 986 } 987 988 if ($absolute===true) { 989 $this->scope =& $this->data; 990 $this->scopeTree = array(); 991 } 992 993 while (($bit = array_shift($scope)) !== null) { 994 if ($bit === '_parent' || $bit === '_') { 995 array_pop($this->scopeTree); 996 reset($this->scopeTree); 997 $this->scope =& $this->data; 998 $cnt = count($this->scopeTree); 999 for ($i=0;$i<$cnt;$i++) 1000 $this->scope =& $this->scope[$this->scopeTree[$i]]; 1001 } elseif ($bit === '_root' || $bit === '__') { 1002 $this->scope =& $this->data; 1003 $this->scopeTree = array(); 1004 } elseif (isset($this->scope[$bit])) { 1005 $this->scope =& $this->scope[$bit]; 1006 $this->scopeTree[] = $bit; 1007 } else { 1008 $this->scope[$bit] = array(); 1009 $this->scope =& $this->scope[$bit]; 1010 $this->scopeTree[] = $bit; 1011 } 1012 } 1013 1014 return $old; 1015 } 1016 1017 /** 1018 * adds a block to the top of the block stack 1019 * 1020 * @param string $type block type (name) 1021 * @param array $params the parameters array 1022 * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2 1023 * @return string the preProcessing() method's output 1024 */ 1025 public function addBlock($type, array $params, $paramtype) 1026 { 1027 $class = 'Dwoo_Plugin_'.$type; 1028 if (class_exists($class, false) === false) { 1029 $this->dwoo->getLoader()->loadPlugin($type); 1030 } 1031 1032 $params = $this->mapParams($params, array($class, 'init'), $paramtype); 1033 1034 $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null); 1035 $this->curBlock =& $this->stack[count($this->stack)-1]; 1036 return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type); 1037 } 1038 1039 /** 1040 * adds a custom block to the top of the block stack 1041 * 1042 * @param string $type block type (name) 1043 * @param array $params the parameters array 1044 * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2 1045 * @return string the preProcessing() method's output 1046 */ 1047 public function addCustomBlock($type, array $params, $paramtype) 1048 { 1049 $callback = $this->customPlugins[$type]['callback']; 1050 if (is_array($callback)) { 1051 $class = is_object($callback[0]) ? get_class($callback[0]) : $callback[0]; 1052 } else { 1053 $class = $callback; 1054 } 1055 1056 $params = $this->mapParams($params, array($class, 'init'), $paramtype); 1057 1058 $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => true, 'class' => $class, 'buffer' => null); 1059 $this->curBlock =& $this->stack[count($this->stack)-1]; 1060 return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type); 1061 } 1062 1063 /** 1064 * injects a block at the top of the plugin stack without calling its preProcessing method 1065 * 1066 * used by {else} blocks to re-add themselves after having closed everything up to their parent 1067 * 1068 * @param string $type block type (name) 1069 * @param array $params parameters array 1070 */ 1071 public function injectBlock($type, array $params) 1072 { 1073 $class = 'Dwoo_Plugin_'.$type; 1074 if (class_exists($class, false) === false) { 1075 $this->dwoo->getLoader()->loadPlugin($type); 1076 } 1077 $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null); 1078 $this->curBlock =& $this->stack[count($this->stack)-1]; 1079 } 1080 1081 /** 1082 * removes the closest-to-top block of the given type and all other 1083 * blocks encountered while going down the block stack 1084 * 1085 * @param string $type block type (name) 1086 * @return string the output of all postProcessing() method's return values of the closed blocks 1087 */ 1088 public function removeBlock($type) 1089 { 1090 $output = ''; 1091 1092 $pluginType = $this->getPluginType($type); 1093 if ($pluginType & Dwoo_Core::SMARTY_BLOCK) { 1094 $type = 'smartyinterface'; 1095 } 1096 while (true) { 1097 while ($top = array_pop($this->stack)) { 1098 if ($top['custom']) { 1099 $class = $top['class']; 1100 } else { 1101 $class = 'Dwoo_Plugin_'.$top['type']; 1102 } 1103 if (count($this->stack)) { 1104 $this->curBlock =& $this->stack[count($this->stack)-1]; 1105 $this->push(call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']), 0); 1106 } else { 1107 $null = null; 1108 $this->curBlock =& $null; 1109 $output = call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']); 1110 } 1111 1112 if ($top['type'] === $type) { 1113 break 2; 1114 } 1115 } 1116 1117 throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of type "'.$type.'" was closed but was not opened'); 1118 break; 1119 } 1120 1121 return $output; 1122 } 1123 1124 /** 1125 * returns a reference to the first block of the given type encountered and 1126 * optionally closes all blocks until it finds it 1127 * 1128 * this is mainly used by {else} plugins to close everything that was opened 1129 * between their parent and themselves 1130 * 1131 * @param string $type the block type (name) 1132 * @param bool $closeAlong whether to close all blocks encountered while going down the block stack or not 1133 * @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array, 1134 * 'custom'=>bool defining whether it's a custom plugin or not, for internal use) 1135 */ 1136 public function &findBlock($type, $closeAlong = false) 1137 { 1138 if ($closeAlong===true) { 1139 while ($b = end($this->stack)) { 1140 if ($b['type']===$type) { 1141 return $this->stack[key($this->stack)]; 1142 } 1143 $this->push($this->removeTopBlock(), 0); 1144 } 1145 } else { 1146 end($this->stack); 1147 while ($b = current($this->stack)) { 1148 if ($b['type']===$type) { 1149 return $this->stack[key($this->stack)]; 1150 } 1151 prev($this->stack); 1152 } 1153 } 1154 1155 throw new Dwoo_Compilation_Exception($this, 'A parent block of type "'.$type.'" is required and can not be found'); 1156 } 1157 1158 /** 1159 * returns a reference to the current block array 1160 * 1161 * @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array, 1162 * 'custom'=>bool defining whether it's a custom plugin or not, for internal use) 1163 */ 1164 public function &getCurrentBlock() 1165 { 1166 return $this->curBlock; 1167 } 1168 1169 /** 1170 * removes the block at the top of the stack and calls its postProcessing() method 1171 * 1172 * @return string the postProcessing() method's output 1173 */ 1174 public function removeTopBlock() 1175 { 1176 $o = array_pop($this->stack); 1177 if ($o === null) { 1178 throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of unknown type was closed but was not opened.'); 1179 } 1180 if ($o['custom']) { 1181 $class = $o['class']; 1182 } else { 1183 $class = 'Dwoo_Plugin_'.$o['type']; 1184 } 1185 1186 $this->curBlock =& $this->stack[count($this->stack)-1]; 1187 1188 return call_user_func(array($class, 'postProcessing'), $this, $o['params'], '', '', $o['buffer']); 1189 } 1190 1191 /** 1192 * returns the compiled parameters (for example a variable's compiled parameter will be "$this->scope['key']") out of the given parameter array 1193 * 1194 * @param array $params parameter array 1195 * @return array filtered parameters 1196 */ 1197 public function getCompiledParams(array $params) 1198 { 1199 foreach ($params as $k=>$p) { 1200 if (is_array($p)) { 1201 $params[$k] = $p[0]; 1202 } 1203 } 1204 return $params; 1205 } 1206 1207 /** 1208 * returns the real parameters (for example a variable's real parameter will be its key, etc) out of the given parameter array 1209 * 1210 * @param array $params parameter array 1211 * @return array filtered parameters 1212 */ 1213 public function getRealParams(array $params) 1214 { 1215 foreach ($params as $k=>$p) { 1216 if (is_array($p)) { 1217 $params[$k] = $p[1]; 1218 } 1219 } 1220 return $params; 1221 } 1222 1223 /** 1224 * returns the token of each parameter out of the given parameter array 1225 * 1226 * @param array $params parameter array 1227 * @return array tokens 1228 */ 1229 public function getParamTokens(array $params) 1230 { 1231 foreach ($params as $k=>$p) { 1232 if (is_array($p)) { 1233 $params[$k] = isset($p[2]) ? $p[2] : 0; 1234 } 1235 } 1236 return $params; 1237 } 1238 1239 /** 1240 * entry point of the parser, it redirects calls to other parse* functions 1241 * 1242 * @param string $in the string within which we must parse something 1243 * @param int $from the starting offset of the parsed area 1244 * @param int $to the ending offset of the parsed area 1245 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default 1246 * @param string $curBlock the current parser-block being processed 1247 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default 1248 * @return string parsed values 1249 */ 1250 protected function parse($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null) 1251 { 1252 if ($to === null) { 1253 $to = strlen($in); 1254 } 1255 $first = substr($in, $from, 1); 1256 1257 if ($first === false) { 1258 throw new Dwoo_Compilation_Exception($this, 'Unexpected EOF, a template tag was not closed'); 1259 } 1260 1261 while ($first===" " || $first==="\n" || $first==="\t" || $first==="\r") { 1262 if ($curBlock === 'root' && substr($in, $from, strlen($this->rd)) === $this->rd) { 1263 // end template tag 1264 $pointer += strlen($this->rd); 1265 if ($this->debug) echo 'TEMPLATE PARSING ENDED<br />'; 1266 return false; 1267 } 1268 $from++; 1269 if ($pointer !== null) { 1270 $pointer++; 1271 } 1272 if ($from >= $to) { 1273 if (is_array($parsingParams)) { 1274 return $parsingParams; 1275 } else { 1276 return ''; 1277 } 1278 } 1279 $first = $in[$from]; 1280 } 1281 1282 $substr = substr($in, $from, $to-$from); 1283 1284 if ($this->debug) echo '<br />PARSE CALL : PARSING "<b>'.htmlentities(substr($in, $from, min($to-$from, 50))).(($to-$from) > 50 ? '...':'').'</b>" @ '.$from.':'.$to.' in '.$curBlock.' : pointer='.$pointer.'<br/>'; 1285 $parsed = ""; 1286 1287 if ($curBlock === 'root' && $first === '*') { 1288 $src = $this->getTemplateSource(); 1289 $startpos = $this->getPointer() - strlen($this->ld); 1290 if (substr($src, $startpos, strlen($this->ld)) === $this->ld) { 1291 if ($startpos > 0) { 1292 do { 1293 $char = substr($src, --$startpos, 1); 1294 if ($char == "\n") { 1295 $startpos++; 1296 $whitespaceStart = true; 1297 break; 1298 } 1299 } while ($startpos > 0 && ($char == ' ' || $char == "\t")); 1300 } 1301 1302 if (!isset($whitespaceStart)) { 1303 $startpos = $this->getPointer(); 1304 } else { 1305 $pointer -= $this->getPointer() - $startpos; 1306 } 1307 1308 if ($this->allowNestedComments && strpos($src, $this->ld.'*', $this->getPointer()) !== false) { 1309 $comOpen = $this->ld.'*'; 1310 $comClose = '*'.$this->rd; 1311 $level = 1; 1312 $start = $startpos; 1313 $ptr = $this->getPointer() + '*'; 1314 1315 while ($level > 0 && $ptr < strlen($src)) { 1316 $open = strpos($src, $comOpen, $ptr); 1317 $close = strpos($src, $comClose, $ptr); 1318 1319 if ($open !== false && $close !== false) { 1320 if ($open < $close) { 1321 $ptr = $open + strlen($comOpen); 1322 $level++; 1323 } else { 1324 $ptr = $close + strlen($comClose); 1325 $level--; 1326 } 1327 } elseif ($open !== false) { 1328 $ptr = $open + strlen($comOpen); 1329 $level++; 1330 } elseif ($close !== false) { 1331 $ptr = $close + strlen($comClose); 1332 $level--; 1333 } else { 1334 $ptr = strlen($src); 1335 } 1336 } 1337 $endpos = $ptr - strlen('*'.$this->rd); 1338 } else { 1339 $endpos = strpos($src, '*'.$this->rd, $startpos); 1340 if ($endpos == false) { 1341 throw new Dwoo_Compilation_Exception($this, 'Un-ended comment'); 1342 } 1343 } 1344 $pointer += $endpos - $startpos + strlen('*'.$this->rd); 1345 if (isset($whitespaceStart) && preg_match('#^[\t ]*\r?\n#', substr($src, $endpos+strlen('*'.$this->rd)), $m)) { 1346 $pointer += strlen($m[0]); 1347 $this->curBlock['buffer'] = substr($this->curBlock['buffer'], 0, strlen($this->curBlock['buffer']) - ($this->getPointer() - $startpos - strlen($this->ld))); 1348 } 1349 return false; 1350 } 1351 } 1352 1353 if ($first==='$') { 1354 // var 1355 $out = $this->parseVar($in, $from, $to, $parsingParams, $curBlock, $pointer); 1356 $parsed = 'var'; 1357 } elseif ($first==='%' && preg_match('#^%[a-z_]#i', $substr)) { 1358 // const 1359 $out = $this->parseConst($in, $from, $to, $parsingParams, $curBlock, $pointer); 1360 } elseif (($first==='"' || $first==="'") && !(is_array($parsingParams) && preg_match('#^([\'"])[a-z0-9_]+\1\s*=>?(?:\s+|[^=])#i', $substr))) { 1361 // string 1362 $out = $this->parseString($in, $from, $to, $parsingParams, $curBlock, $pointer); 1363 } elseif (preg_match('/^\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?('.(is_array($parsingParams)||$curBlock!='root'?'':'\s+[^(]|').'\s*\(|\s*'.$this->rdr.'|\s*;)/i', $substr)) { 1364 // func 1365 $out = $this->parseFunction($in, $from, $to, $parsingParams, $curBlock, $pointer); 1366 $parsed = 'func'; 1367 } elseif ($first === ';') { 1368 // instruction end 1369 if ($this->debug) echo 'END OF INSTRUCTION<br />'; 1370 if ($pointer !== null) { 1371 $pointer++; 1372 } 1373 return $this->parse($in, $from+1, $to, false, 'root', $pointer); 1374 } elseif ($curBlock === 'root' && preg_match('#^/([a-z_][a-z0-9_]*)?#i', $substr, $match)) { 1375 // close block 1376 if (!empty($match[1]) && $match[1] == 'else') { 1377 throw new Dwoo_Compilation_Exception($this, 'Else blocks must not be closed explicitly, they are automatically closed when their parent block is closed'); 1378 } 1379 if (!empty($match[1]) && $match[1] == 'elseif') { 1380 throw new Dwoo_Compilation_Exception($this, 'Elseif blocks must not be closed explicitly, they are automatically closed when their parent block is closed or a new else/elseif block is declared after them'); 1381 } 1382 if ($pointer !== null) { 1383 $pointer += strlen($match[0]); 1384 } 1385 if (empty($match[1])) { 1386 if ($this->curBlock['type'] == 'else' || $this->curBlock['type'] == 'elseif') { 1387 $pointer -= strlen($match[0]); 1388 } 1389 if ($this->debug) echo 'TOP BLOCK CLOSED<br />'; 1390 return $this->removeTopBlock(); 1391 } else { 1392 if ($this->debug) echo 'BLOCK OF TYPE '.$match[1].' CLOSED<br />'; 1393 return $this->removeBlock($match[1]); 1394 } 1395 } elseif ($curBlock === 'root' && substr($substr, 0, strlen($this->rd)) === $this->rd) { 1396 // end template tag 1397 if ($this->debug) echo 'TAG PARSING ENDED<br />'; 1398 $pointer += strlen($this->rd); 1399 return false; 1400 } elseif (is_array($parsingParams) && preg_match('#^(([\'"]?)[a-z0-9_]+\2\s*='.($curBlock === 'array' ? '>?':'').')(?:\s+|[^=]).*#i', $substr, $match)) { 1401 // named parameter 1402 if ($this->debug) echo 'NAMED PARAM FOUND<br />'; 1403 $len = strlen($match[1]); 1404 while (substr($in, $from+$len, 1)===' ') { 1405 $len++; 1406 } 1407 if ($pointer !== null) { 1408 $pointer += $len; 1409 } 1410 1411 $output = array(trim($match[1], " \t\r\n=>'\""), $this->parse($in, $from+$len, $to, false, 'namedparam', $pointer)); 1412 1413 $parsingParams[] = $output; 1414 return $parsingParams; 1415 } elseif (preg_match('#^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*::\$[a-z0-9_]+)#i', $substr, $match)) { 1416 // static member access 1417 $parsed = 'var'; 1418 if (is_array($parsingParams)) { 1419 $parsingParams[] = array($match[1], $match[1]); 1420 $out = $parsingParams; 1421 } else { 1422 $out = $match[1]; 1423 } 1424 $pointer += strlen($match[1]); 1425 } elseif ($substr!=='' && (is_array($parsingParams) || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'expression')) { 1426 // unquoted string, bool or number 1427 $out = $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer); 1428 } else { 1429 // parse error 1430 throw new Dwoo_Compilation_Exception($this, 'Parse error in "'.substr($in, $from, $to-$from).'"'); 1431 } 1432 1433 if (empty($out)) { 1434 return ''; 1435 } 1436 1437 $substr = substr($in, $pointer, $to-$pointer); 1438 1439 // var parsed, check if any var-extension applies 1440 if ($parsed==='var') { 1441 if (preg_match('#^\s*([/%+*-])\s*([a-z0-9]|\$)#i', $substr, $match)) { 1442 if($this->debug) echo 'PARSING POST-VAR EXPRESSION '.$substr.'<br />'; 1443 // parse expressions 1444 $pointer += strlen($match[0]) - 1; 1445 if (is_array($parsingParams)) { 1446 if ($match[2] == '$') { 1447 $expr = $this->parseVar($in, $pointer, $to, array(), $curBlock, $pointer); 1448 } else { 1449 $expr = $this->parse($in, $pointer, $to, array(), 'expression', $pointer); 1450 } 1451 $out[count($out)-1][0] .= $match[1] . $expr[0][0]; 1452 $out[count($out)-1][1] .= $match[1] . $expr[0][1]; 1453 } else { 1454 if ($match[2] == '$') { 1455 $expr = $this->parseVar($in, $pointer, $to, false, $curBlock, $pointer); 1456 } else { 1457 $expr = $this->parse($in, $pointer, $to, false, 'expression', $pointer); 1458 } 1459 if (is_array($out) && is_array($expr)) { 1460 $out[0] .= $match[1] . $expr[0]; 1461 $out[1] .= $match[1] . $expr[1]; 1462 } elseif (is_array($out)) { 1463 $out[0] .= $match[1] . $expr; 1464 $out[1] .= $match[1] . $expr; 1465 } elseif (is_array($expr)) { 1466 $out .= $match[1] . $expr[0]; 1467 } else { 1468 $out .= $match[1] . $expr; 1469 } 1470 } 1471 } else if ($curBlock === 'root' && preg_match('#^(\s*(?:[+/*%-.]=|=|\+\+|--)\s*)(.*)#s', $substr, $match)) { 1472 if($this->debug) echo 'PARSING POST-VAR ASSIGNMENT '.$substr.'<br />'; 1473 // parse assignment 1474 $value = $match[2]; 1475 $operator = trim($match[1]); 1476 if (substr($value, 0, 1) == '=') { 1477 throw new Dwoo_Compilation_Exception($this, 'Unexpected "=" in <em>'.$substr.'</em>'); 1478 } 1479 1480 if ($pointer !== null) { 1481 $pointer += strlen($match[1]); 1482 } 1483 1484 if ($operator !== '++' && $operator !== '--') { 1485 $parts = array(); 1486 $ptr = 0; 1487 $parts = $this->parse($value, 0, strlen($value), $parts, 'condition', $ptr); 1488 $pointer += $ptr; 1489 1490 // load if plugin 1491 try { 1492 $this->getPluginType('if'); 1493 } catch (Dwoo_Exception $e) { 1494 throw new Dwoo_Compilation_Exception($this, 'Assignments require the "if" plugin to be accessible'); 1495 } 1496 1497 $parts = $this->mapParams($parts, array('Dwoo_Plugin_if', 'init'), 1); 1498 $tokens = $this->getParamTokens($parts); 1499 $parts = $this->getCompiledParams($parts); 1500 1501 $value = Dwoo_Plugin_if::replaceKeywords($parts['*'], $tokens['*'], $this); 1502 $echo = ''; 1503 } else { 1504 $value = array(); 1505 $echo = 'echo '; 1506 } 1507 1508 if ($this->autoEscape) { 1509 $out = preg_replace('#\(is_string\(\$tmp=(.+?)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $out); 1510 } 1511 $out = Dwoo_Compiler::PHP_OPEN. $echo . $out . $operator . implode(' ', $value) . Dwoo_Compiler::PHP_CLOSE; 1512 } else if ($curBlock === 'array' && is_array($parsingParams) && preg_match('#^(\s*=>?\s*)#', $substr, $match)) { 1513 // parse namedparam with var as name (only for array) 1514 if ($this->debug) echo 'VARIABLE NAMED PARAM (FOR ARRAY) FOUND<br />'; 1515 $len = strlen($match[1]); 1516 $var = $out[count($out)-1]; 1517 $pointer += $len; 1518 1519 $output = array($var[0], $this->parse($substr, $len, null, false, 'namedparam', $pointer)); 1520 1521 $parsingParams[] = $output; 1522 return $parsingParams; 1523 } 1524 } 1525 1526 if ($curBlock !== 'modifier' && ($parsed === 'func' || $parsed === 'var') && preg_match('#^\|@?[a-z0-9_]+(:.*)?#i', $substr, $match)) { 1527 // parse modifier on funcs or vars 1528 $srcPointer = $pointer; 1529 if (is_array($parsingParams)) { 1530 $tmp = $this->replaceModifiers(array(null, null, $out[count($out)-1][0], $match[0]), 'var', $pointer); 1531 $out[count($out)-1][0] = $tmp; 1532 $out[count($out)-1][1] .= substr($substr, $srcPointer, $srcPointer - $pointer); 1533 } else { 1534 $out = $this->replaceModifiers(array(null, null, $out, $match[0]), 'var', $pointer); 1535 } 1536 } 1537 1538 // func parsed, check if any func-extension applies 1539 if ($parsed==='func' && preg_match('#^->[a-z0-9_]+(\s*\(.+|->[a-z_].*)?#is', $substr, $match)) { 1540 // parse method call or property read 1541 $ptr = 0; 1542 1543 if (is_array($parsingParams)) { 1544 $output = $this->parseMethodCall($out[count($out)-1][1], $match[0], $curBlock, $ptr); 1545 1546 $out[count($out)-1][0] = $output; 1547 $out[count($out)-1][1] .= substr($match[0], 0, $ptr); 1548 } else { 1549 $out = $this->parseMethodCall($out, $match[0], $curBlock, $ptr); 1550 } 1551 1552 $pointer += $ptr; 1553 } 1554 1555 if ($curBlock === 'root' && substr($out, 0, strlen(self::PHP_OPEN)) !== self::PHP_OPEN) { 1556 return self::PHP_OPEN .'echo '.$out.';'. self::PHP_CLOSE; 1557 } else { 1558 return $out; 1559 } 1560 } 1561 1562 /** 1563 * parses a function call 1564 * 1565 * @param string $in the string within which we must parse something 1566 * @param int $from the starting offset of the parsed area 1567 * @param int $to the ending offset of the parsed area 1568 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default 1569 * @param string $curBlock the current parser-block being processed 1570 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default 1571 * @return string parsed values 1572 */ 1573 protected function parseFunction($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null) 1574 { 1575 $cmdstr = substr($in, $from, $to-$from); 1576 preg_match('/^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?)(\s*'.$this->rdr.'|\s*;)?/i', $cmdstr, $match); 1577 1578 if (empty($match[1])) { 1579 throw new Dwoo_Compilation_Exception($this, 'Parse error, invalid function name : '.substr($cmdstr, 0, 15)); 1580 } 1581 1582 $func = $match[1]; 1583 1584 if (!empty($match[2])) { 1585 $cmdstr = $match[1]; 1586 } 1587 1588 if ($this->debug) echo 'FUNC FOUND ('.$func.')<br />'; 1589 1590 $paramsep = ''; 1591 1592 if (is_array($parsingParams) || $curBlock != 'root') { 1593 $paramspos = strpos($cmdstr, '('); 1594 $paramsep = ')'; 1595 } elseif(preg_match_all('#^\s*[\\\\:a-z0-9_]+(\s*\(|\s+[^(])#i', $cmdstr, $match, PREG_OFFSET_CAPTURE)) { 1596 $paramspos = $match[1][0][1]; 1597 $paramsep = substr($match[1][0][0], -1) === '(' ? ')':''; 1598 if($paramsep === ')') { 1599 $paramspos += strlen($match[1][0][0]) - 1; 1600 if(substr($cmdstr, 0, 2) === 'if' || substr($cmdstr, 0, 6) === 'elseif') { 1601 $paramsep = ''; 1602 if(strlen($match[1][0][0]) > 1) { 1603 $paramspos--; 1604 } 1605 } 1606 } 1607 } else { 1608 $paramspos = false; 1609 } 1610 1611 $state = 0; 1612 1613 if ($paramspos === false) { 1614 $params = array(); 1615 1616 if ($curBlock !== 'root') { 1617 return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer); 1618 } 1619 } else { 1620 if ($curBlock === 'condition') { 1621 // load if plugin 1622 $this->getPluginType('if'); 1623 1624 if (Dwoo_Plugin_if::replaceKeywords(array($func), array(self::T_UNQUOTED_STRING), $this) !== array($func)) { 1625 return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer); 1626 } 1627 } 1628 $whitespace = strlen(substr($cmdstr, strlen($func), $paramspos-strlen($func))); 1629 $paramstr = substr($cmdstr, $paramspos+1); 1630 if (substr($paramstr, -1, 1) === $paramsep) { 1631 $paramstr = substr($paramstr, 0, -1); 1632 } 1633 1634 if (strlen($paramstr)===0) { 1635 $params = array(); 1636 $paramstr = ''; 1637 } else { 1638 $ptr = 0; 1639 $params = array(); 1640 if ($func === 'empty') { 1641 $params = $this->parseVar($paramstr, $ptr, strlen($paramstr), $params, 'root', $ptr); 1642 } else { 1643 while ($ptr < strlen($paramstr)) { 1644 while (true) { 1645 if ($ptr >= strlen($paramstr)) { 1646 break 2; 1647 } 1648 1649 if ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === ')') { 1650 if ($this->debug) echo 'PARAM PARSING ENDED, ")" FOUND, POINTER AT '.$ptr.'<br/>'; 1651 break 2; 1652 } elseif ($paramstr[$ptr] === ';') { 1653 $ptr++; 1654 if ($this->debug) echo 'PARAM PARSING ENDED, ";" FOUND, POINTER AT '.$ptr.'<br/>'; 1655 break 2; 1656 } elseif ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === '/') { 1657 if ($this->debug) echo 'PARAM PARSING ENDED, "/" FOUND, POINTER AT '.$ptr.'<br/>'; 1658 break 2; 1659 } elseif (substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) { 1660 if ($this->debug) echo 'PARAM PARSING ENDED, RIGHT DELIMITER FOUND, POINTER AT '.$ptr.'<br/>'; 1661 break 2; 1662 } 1663 1664 if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === ',' || $paramstr[$ptr] === "\r" || $paramstr[$ptr] === "\n" || $paramstr[$ptr] === "\t") { 1665 $ptr++; 1666 } else { 1667 break; 1668 } 1669 } 1670 1671 if ($this->debug) echo 'FUNC START PARAM PARSING WITH POINTER AT '.$ptr.'<br/>'; 1672 1673 if ($func === 'if' || $func === 'elseif' || $func === 'tif') { 1674 $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'condition', $ptr); 1675 } elseif ($func === 'array') { 1676 $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'array', $ptr); 1677 } else { 1678 $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'function', $ptr); 1679 } 1680 1681 if ($this->debug) echo 'PARAM PARSED, POINTER AT '.$ptr.' ('.substr($paramstr, $ptr-1, 3).')<br/>'; 1682 } 1683 } 1684 $paramstr = substr($paramstr, 0, $ptr); 1685 $state = 0; 1686 foreach ($params as $k=>$p) { 1687 if (is_array($p) && is_array($p[1])) { 1688 $state |= 2; 1689 } else { 1690 if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m) && $func !== 'array') { 1691 $params[$k] = array($m[2], array('true', 'true')); 1692 } else { 1693 if ($state & 2 && $func !== 'array') { 1694 throw new Dwoo_Compilation_Exception($this, 'You can not use an unnamed parameter after a named one'); 1695 } 1696 $state |= 1; 1697 } 1698 } 1699 } 1700 } 1701 } 1702 1703 if ($pointer !== null) { 1704 $pointer += (isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func) + (isset($whitespace) ? $whitespace : 0); 1705 if ($this->debug) echo 'FUNC ADDS '.((isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func)).' TO POINTER<br/>'; 1706 } 1707 1708 if ($curBlock === 'method' || $func === 'do' || strstr($func, '::') !== false) { 1709 // handle static method calls with security policy 1710 if (strstr($func, '::') !== false && $this->securityPolicy !== null && $this->securityPolicy->isMethodAllowed(explode('::', strtolower($func))) !== true) { 1711 throw new Dwoo_Security_Exception('Call to a disallowed php function : '.$func); 1712 } 1713 $pluginType = Dwoo::NATIVE_PLUGIN; 1714 } else { 1715 $pluginType = $this->getPluginType($func); 1716 } 1717 1718 // blocks 1719 if ($pluginType & Dwoo_Core::BLOCK_PLUGIN) { 1720 if ($curBlock !== 'root' || is_array($parsingParams)) { 1721 throw new Dwoo_Compilation_Exception($this, 'Block plugins can not be used as other plugin\'s arguments'); 1722 } 1723 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 1724 return $this->addCustomBlock($func, $params, $state); 1725 } else { 1726 return $this->addBlock($func, $params, $state); 1727 } 1728 } elseif ($pluginType & Dwoo_Core::SMARTY_BLOCK) { 1729 if ($curBlock !== 'root' || is_array($parsingParams)) { 1730 throw new Dwoo_Compilation_Exception($this, 'Block plugins can not be used as other plugin\'s arguments'); 1731 } 1732 1733 if ($state & 2) { 1734 array_unshift($params, array('__functype', array($pluginType, $pluginType))); 1735 array_unshift($params, array('__funcname', array($func, $func))); 1736 } else { 1737 array_unshift($params, array($pluginType, $pluginType)); 1738 array_unshift($params, array($func, $func)); 1739 } 1740 1741 return $this->addBlock('smartyinterface', $params, $state); 1742 } 1743 1744 // funcs 1745 if ($pluginType & Dwoo_Core::NATIVE_PLUGIN || $pluginType & Dwoo_Core::SMARTY_FUNCTION || $pluginType & Dwoo_Core::SMARTY_BLOCK) { 1746 $params = $this->mapParams($params, null, $state); 1747 } elseif ($pluginType & Dwoo_Core::CLASS_PLUGIN) { 1748 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 1749 $params = $this->mapParams($params, array($this->customPlugins[$func]['class'], $this->customPlugins[$func]['function']), $state); 1750 } else { 1751 $params = $this->mapParams($params, array('Dwoo_Plugin_'.$func, ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'), $state); 1752 } 1753 } elseif ($pluginType & Dwoo_Core::FUNC_PLUGIN) { 1754 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 1755 $params = $this->mapParams($params, $this->customPlugins[$func]['callback'], $state); 1756 } else { 1757 $params = $this->mapParams($params, 'Dwoo_Plugin_'.$func.(($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? '_compile' : ''), $state); 1758 } 1759 } elseif ($pluginType & Dwoo_Core::SMARTY_MODIFIER) { 1760 $output = 'smarty_modifier_'.$func.'('.implode(', ', $params).')'; 1761 } elseif ($pluginType & Dwoo_Core::PROXY_PLUGIN) { 1762 $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state); 1763 } elseif ($pluginType & Dwoo_Core::TEMPLATE_PLUGIN) { 1764 // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values)) 1765 $map = array(); 1766 foreach ($this->templatePlugins[$func]['params'] as $param=>$defValue) { 1767 if ($param == 'rest') { 1768 $param = '*'; 1769 } 1770 $hasDefault = $defValue !== null; 1771 if ($defValue === 'null') { 1772 $defValue = null; 1773 } elseif ($defValue === 'false') { 1774 $defValue = false; 1775 } elseif ($defValue === 'true') { 1776 $defValue = true; 1777 } elseif (preg_match('#^([\'"]).*?\1$#', $defValue)) { 1778 $defValue = substr($defValue, 1, -1); 1779 } 1780 $map[] = array($param, $hasDefault, $defValue); 1781 } 1782 1783 $params = $this->mapParams($params, null, $state, $map); 1784 } 1785 1786 // only keep php-syntax-safe values for non-block plugins 1787 $tokens = array(); 1788 foreach ($params as $k => $p) { 1789 $tokens[$k] = isset($p[2]) ? $p[2] : 0; 1790 $params[$k] = $p[0]; 1791 } 1792 if ($pluginType & Dwoo_Core::NATIVE_PLUGIN) { 1793 if ($func === 'do') { 1794 if (isset($params['*'])) { 1795 $output = implode(';', $params['*']).';'; 1796 } else { 1797 $output = ''; 1798 } 1799 1800 if (is_array($parsingParams) || $curBlock !== 'root') { 1801 throw new Dwoo_Compilation_Exception($this, 'Do can not be used inside another function or block'); 1802 } else { 1803 return self::PHP_OPEN.$output.self::PHP_CLOSE; 1804 } 1805 } else { 1806 if (isset($params['*'])) { 1807 $output = $func.'('.implode(', ', $params['*']).')'; 1808 } else { 1809 $output = $func.'()'; 1810 } 1811 } 1812 } elseif ($pluginType & Dwoo_Core::FUNC_PLUGIN) { 1813 if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) { 1814 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 1815 $funcCompiler = $this->customPlugins[$func]['callback']; 1816 } else { 1817 $funcCompiler = 'Dwoo_Plugin_'.$func.'_compile'; 1818 } 1819 array_unshift($params, $this); 1820 if ($func === 'tif') { 1821 $params[] = $tokens; 1822 } 1823 $output = call_user_func_array($funcCompiler, $params); 1824 } else { 1825 array_unshift($params, '$this'); 1826 $params = self::implode_r($params); 1827 1828 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 1829 $callback = $this->customPlugins[$func]['callback']; 1830 $output = 'call_user_func(\''.$callback.'\', '.$params.')'; 1831 } else { 1832 $output = 'Dwoo_Plugin_'.$func.'('.$params.')'; 1833 } 1834 } 1835 } elseif ($pluginType & Dwoo_Core::CLASS_PLUGIN) { 1836 if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) { 1837 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 1838 $callback = $this->customPlugins[$func]['callback']; 1839 if (!is_array($callback)) { 1840 if (!method_exists($callback, 'compile')) { 1841 throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use'); 1842 } 1843 if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) { 1844 $funcCompiler = array($callback, 'compile'); 1845 } else { 1846 $funcCompiler = array(new $callback, 'compile'); 1847 } 1848 } else { 1849 $funcCompiler = $callback; 1850 } 1851 } else { 1852 $funcCompiler = array('Dwoo_Plugin_'.$func, 'compile'); 1853 array_unshift($params, $this); 1854 } 1855 $output = call_user_func_array($funcCompiler, $params); 1856 } else { 1857 $params = self::implode_r($params); 1858 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 1859 $callback = $this->customPlugins[$func]['callback']; 1860 if (!is_array($callback)) { 1861 if (!method_exists($callback, 'process')) { 1862 throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "process" method to be usable, or you should provide a full callback to the method to use'); 1863 } 1864 if (($ref = new ReflectionMethod($callback, 'process')) && $ref->isStatic()) { 1865 $output = 'call_user_func(array(\''.$callback.'\', \'process\'), '.$params.')'; 1866 } else { 1867 $output = 'call_user_func(array($this->getObjectPlugin(\''.$callback.'\'), \'process\'), '.$params.')'; 1868 } 1869 } elseif (is_object($callback[0])) { 1870 $output = 'call_user_func(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), '.$params.')'; 1871 } elseif (($ref = new ReflectionMethod($callback[0], $callback[1])) && $ref->isStatic()) { 1872 $output = 'call_user_func(array(\''.$callback[0].'\', \''.$callback[1].'\'), '.$params.')'; 1873 } else { 1874 $output = 'call_user_func(array($this->getObjectPlugin(\''.$callback[0].'\'), \''.$callback[1].'\'), '.$params.')'; 1875 } 1876 if (empty($params)) { 1877 $output = substr($output, 0, -3).')'; 1878 } 1879 } else { 1880 $output = '$this->classCall(\''.$func.'\', array('.$params.'))'; 1881 } 1882 } 1883 } elseif ($pluginType & Dwoo_Core::PROXY_PLUGIN) { 1884 $output = call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params); 1885 } elseif ($pluginType & Dwoo_Core::SMARTY_FUNCTION) { 1886 if (isset($params['*'])) { 1887 $params = self::implode_r($params['*'], true); 1888 } else { 1889 $params = ''; 1890 } 1891 1892 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 1893 $callback = $this->customPlugins[$func]['callback']; 1894 if (is_array($callback)) { 1895 if (is_object($callback[0])) { 1896 $output = 'call_user_func_array(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array(array('.$params.'), $this))'; 1897 } else { 1898 $output = 'call_user_func_array(array(\''.$callback[0].'\', \''.$callback[1].'\'), array(array('.$params.'), $this))'; 1899 } 1900 } else { 1901 $output = $callback.'(array('.$params.'), $this)'; 1902 } 1903 } else { 1904 $output = 'smarty_function_'.$func.'(array('.$params.'), $this)'; 1905 } 1906 } elseif ($pluginType & Dwoo_Core::TEMPLATE_PLUGIN) { 1907 array_unshift($params, '$this'); 1908 $params = self::implode_r($params); 1909 $output = 'Dwoo_Plugin_'.$func.'_'.$this->templatePlugins[$func]['uuid'].'('.$params.')'; 1910 $this->templatePlugins[$func]['called'] = true; 1911 } 1912 1913 if (is_array($parsingParams)) { 1914 $parsingParams[] = array($output, $output); 1915 return $parsingParams; 1916 } elseif ($curBlock === 'namedparam') { 1917 return array($output, $output); 1918 } else { 1919 return $output; 1920 } 1921 } 1922 1923 /** 1924 * parses a string 1925 * 1926 * @param string $in the string within which we must parse something 1927 * @param int $from the starting offset of the parsed area 1928 * @param int $to the ending offset of the parsed area 1929 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default 1930 * @param string $curBlock the current parser-block being processed 1931 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default 1932 * @return string parsed values 1933 */ 1934 protected function parseString($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null) 1935 { 1936 $substr = substr($in, $from, $to-$from); 1937 $first = $substr[0]; 1938 1939 if ($this->debug) echo 'STRING FOUND (in '.htmlentities(substr($in, $from, min($to-$from, 50))).(($to-$from) > 50 ? '...':'').')<br />'; 1940 $strend = false; 1941 $o = $from+1; 1942 while ($strend === false) { 1943 $strend = strpos($in, $first, $o); 1944 if ($strend === false) { 1945 throw new Dwoo_Compilation_Exception($this, 'Unfinished string, started with '.substr($in, $from, $to-$from)); 1946 } 1947 if (substr($in, $strend-1, 1) === '\\') { 1948 $o = $strend+1; 1949 $strend = false; 1950 } 1951 } 1952 if ($this->debug) echo 'STRING DELIMITED: '.substr($in, $from, $strend+1-$from).'<br/>'; 1953 1954 $srcOutput = substr($in, $from, $strend+1-$from); 1955 1956 if ($pointer !== null) { 1957 $pointer += strlen($srcOutput); 1958 } 1959 1960 $output = $this->replaceStringVars($srcOutput, $first); 1961 1962 // handle modifiers 1963 if ($curBlock !== 'modifier' && preg_match('#^((?:\|(?:@?[a-z0-9_]+(?::.*)*))+)#i', substr($substr, $strend+1-$from), $match)) { 1964 $modstr = $match[1]; 1965 1966 if ($curBlock === 'root' && substr($modstr, -1) === '}') { 1967 $modstr = substr($modstr, 0, -1); 1968 } 1969 $modstr = str_replace('\\'.$first, $first, $modstr); 1970 $ptr = 0; 1971 $output = $this->replaceModifiers(array(null, null, $output, $modstr), 'string', $ptr); 1972 1973 $strend += $ptr; 1974 if ($pointer !== null) { 1975 $pointer += $ptr; 1976 } 1977 $srcOutput .= substr($substr, $strend+1-$from, $ptr); 1978 } 1979 1980 if (is_array($parsingParams)) { 1981 $parsingParams[] = array($output, substr($srcOutput, 1, -1)); 1982 return $parsingParams; 1983 } elseif ($curBlock === 'namedparam') { 1984 return array($output, substr($srcOutput, 1, -1)); 1985 } else { 1986 return $output; 1987 } 1988 } 1989 1990 /** 1991 * parses a constant 1992 * 1993 * @param string $in the string within which we must parse something 1994 * @param int $from the starting offset of the parsed area 1995 * @param int $to the ending offset of the parsed area 1996 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default 1997 * @param string $curBlock the current parser-block being processed 1998 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default 1999 * @return string parsed values 2000 */ 2001 protected function parseConst($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null) 2002 { 2003 $substr = substr($in, $from, $to-$from); 2004 2005 if ($this->debug) { 2006 echo 'CONST FOUND : '.$substr.'<br />'; 2007 } 2008 2009 if (!preg_match('#^%([a-z0-9_:]+)#i', $substr, $m)) { 2010 throw new Dwoo_Compilation_Exception($this, 'Invalid constant'); 2011 } 2012 2013 if ($pointer !== null) { 2014 $pointer += strlen($m[0]); 2015 } 2016 2017 $output = $this->parseConstKey($m[1], $curBlock); 2018 2019 if (is_array($parsingParams)) { 2020 $parsingParams[] = array($output, $m[1]); 2021 return $parsingParams; 2022 } elseif ($curBlock === 'namedparam') { 2023 return array($output, $m[1]); 2024 } else { 2025 return $output; 2026 } 2027 } 2028 2029 /** 2030 * parses a constant 2031 * 2032 * @param string $key the constant to parse 2033 * @param string $curBlock the current parser-block being processed 2034 * @return string parsed constant 2035 */ 2036 protected function parseConstKey($key, $curBlock) 2037 { 2038 if ($this->securityPolicy !== null && $this->securityPolicy->getConstantHandling() === Dwoo_Security_Policy::CONST_DISALLOW) { 2039 return 'null'; 2040 } 2041 2042 if ($curBlock !== 'root') { 2043 $output = '(defined("'.$key.'") ? '.$key.' : null)'; 2044 } else { 2045 $output = $key; 2046 } 2047 2048 return $output; 2049 } 2050 2051 /** 2052 * parses a variable 2053 * 2054 * @param string $in the string within which we must parse something 2055 * @param int $from the starting offset of the parsed area 2056 * @param int $to the ending offset of the parsed area 2057 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default 2058 * @param string $curBlock the current parser-block being processed 2059 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default 2060 * @return string parsed values 2061 */ 2062 protected function parseVar($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null) 2063 { 2064 $substr = substr($in, $from, $to-$from); 2065 2066 if (preg_match('#(\$?\.?[a-z0-9_:]*(?:(?:(?:\.|->)(?:[a-z0-9_:]+|(?R))|\[(?:[a-z0-9_:]+|(?R)|(["\'])[^\2]*?\2)\]))*)' . // var key 2067 ($curBlock==='root' || $curBlock==='function' || $curBlock==='namedparam' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='expression' ? '(\(.*)?' : '()') . // method call 2068 ($curBlock==='root' || $curBlock==='function' || $curBlock==='namedparam' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='delimited_string' ? '((?:(?:[+/*%=-])(?:(?<!=)=?-?[$%][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|(?<!=)=?-?[0-9.,]*|[+-]))*)':'()') . // simple math expressions 2069 ($curBlock!=='modifier' ? '((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').*?\5|:[^`]*))*))+)?':'(())') . // modifiers 2070 '#i', $substr, $match)) { 2071 $key = substr($match[1], 1); 2072 2073 $matchedLength = strlen($match[0]); 2074 $hasModifiers = !empty($match[5]); 2075 $hasExpression = !empty($match[4]); 2076 $hasMethodCall = !empty($match[3]); 2077 2078 if (substr($key, -1) == ".") { 2079 $key = substr($key, 0, -1); 2080 $matchedLength--; 2081 } 2082 2083 if ($hasMethodCall) { 2084 $matchedLength -= strlen($match[3]) + strlen(substr($match[1], strrpos($match[1], '->'))); 2085 $key = substr($match[1], 1, strrpos($match[1], '->')-1); 2086 $methodCall = substr($match[1], strrpos($match[1], '->')) . $match[3]; 2087 } 2088 2089 if ($hasModifiers) { 2090 $matchedLength -= strlen($match[5]); 2091 } 2092 2093 if ($pointer !== null) { 2094 $pointer += $matchedLength; 2095 } 2096 2097 // replace useless brackets by dot accessed vars and strip enclosing quotes if present 2098 $key = preg_replace('#\[(["\']?)([^$%\[.>-]+)\1\]#', '.$2', $key); 2099 2100 if ($this->debug) { 2101 if ($hasMethodCall) { 2102 echo 'METHOD CALL FOUND : $'.$key.substr($methodCall, 0, 30).'<br />'; 2103 } else { 2104 echo 'VAR FOUND : $'.$key.'<br />'; 2105 } 2106 } 2107 2108 $key = str_replace('"', '\\"', $key); 2109 2110 $cnt=substr_count($key, '$'); 2111 if ($cnt > 0) { 2112 $uid = 0; 2113 $parsed = array($uid => ''); 2114 $current =& $parsed; 2115 $curTxt =& $parsed[$uid++]; 2116 $tree = array(); 2117 $chars = str_split($key, 1); 2118 $inSplittedVar = false; 2119 $bracketCount = 0; 2120 2121 while (($char = array_shift($chars)) !== null) { 2122 if ($char === '[') { 2123 if (count($tree) > 0) { 2124 $bracketCount++; 2125 } else { 2126 $tree[] =& $current; 2127 $current[$uid] = array($uid+1 => ''); 2128 $current =& $current[$uid++]; 2129 $curTxt =& $current[$uid++]; 2130 continue; 2131 } 2132 } elseif ($char === ']') { 2133 if ($bracketCount > 0) { 2134 $bracketCount--; 2135 } else { 2136 $current =& $tree[count($tree)-1]; 2137 array_pop($tree); 2138 if (current($chars) !== '[' && current($chars) !== false && current($chars) !== ']') { 2139 $current[$uid] = ''; 2140 $curTxt =& $current[$uid++]; 2141 } 2142 continue; 2143 } 2144 } elseif ($char === '$') { 2145 if (count($tree) == 0) { 2146 $curTxt =& $current[$uid++]; 2147 $inSplittedVar = true; 2148 } 2149 } elseif (($char === '.' || $char === '-') && count($tree) == 0 && $inSplittedVar) { 2150 $curTxt =& $current[$uid++]; 2151 $inSplittedVar = false; 2152 } 2153 2154 $curTxt .= $char; 2155 } 2156 unset($uid, $current, $curTxt, $tree, $chars); 2157 2158 if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT : '.$key.'<br>'; 2159 2160 $key = $this->flattenVarTree($parsed); 2161 2162 if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT DONE : '.$key.'<br>'; 2163 2164 $output = preg_replace('#(^""\.|""\.|\.""$|(\()""\.|\.""(\)))#', '$2$3', '$this->readVar("'.$key.'")'); 2165 } else { 2166 $output = $this->parseVarKey($key, $hasModifiers ? 'modifier' : $curBlock); 2167 } 2168 2169 // methods 2170 if ($hasMethodCall) { 2171 $ptr = 0; 2172 2173 $output = $this->parseMethodCall($output, $methodCall, $curBlock, $ptr); 2174 2175 if ($pointer !== null) { 2176 $pointer += $ptr; 2177 } 2178 $matchedLength += $ptr; 2179 } 2180 2181 if ($hasExpression) { 2182 // expressions 2183 preg_match_all('#(?:([+/*%=-])(=?-?[%$][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|=?-?[0-9.,]+|\1))#i', $match[4], $expMatch); 2184 2185 foreach ($expMatch[1] as $k=>$operator) { 2186 if (substr($expMatch[2][$k], 0, 1)==='=') { 2187 $assign = true; 2188 if ($operator === '=') { 2189 throw new Dwoo_Compilation_Exception($this, 'Invalid expression <em>'.$substr.'</em>, can not use "==" in expressions'); 2190 } 2191 if ($curBlock !== 'root') { 2192 throw new Dwoo_Compilation_Exception($this, 'Invalid expression <em>'.$substr.'</em>, assignments can only be used in top level expressions like {$foo+=3} or {$foo="bar"}'); 2193 } 2194 $operator .= '='; 2195 $expMatch[2][$k] = substr($expMatch[2][$k], 1); 2196 } 2197 2198 if (substr($expMatch[2][$k], 0, 1)==='-' && strlen($expMatch[2][$k]) > 1) { 2199 $operator .= '-'; 2200 $expMatch[2][$k] = substr($expMatch[2][$k], 1); 2201 } 2202 if (($operator==='+'||$operator==='-') && $expMatch[2][$k]===$operator) { 2203 $output = '('.$output.$operator.$operator.')'; 2204 break; 2205 } elseif (substr($expMatch[2][$k], 0, 1) === '$') { 2206 $output = '('.$output.' '.$operator.' '.$this->parseVar($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').')'; 2207 } elseif (substr($expMatch[2][$k], 0, 1) === '%') { 2208 $output = '('.$output.' '.$operator.' '.$this->parseConst($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').')'; 2209 } elseif (!empty($expMatch[2][$k])) { 2210 $output = '('.$output.' '.$operator.' '.str_replace(',', '.', $expMatch[2][$k]).')'; 2211 } else { 2212 throw new Dwoo_Compilation_Exception($this, 'Unfinished expression <em>'.$substr.'</em>, missing var or number after math operator'); 2213 } 2214 } 2215 } 2216 2217 if ($this->autoEscape === true && $curBlock !== 'condition') { 2218 $output = '(is_string($tmp='.$output.') ? htmlspecialchars($tmp, ENT_QUOTES, $this->charset) : $tmp)'; 2219 } 2220 2221 // handle modifiers 2222 if ($curBlock !== 'modifier' && $hasModifiers) { 2223 $ptr = 0; 2224 $output = $this->replaceModifiers(array(null, null, $output, $match[5]), 'var', $ptr); 2225 if ($pointer !== null) { 2226 $pointer += $ptr; 2227 } 2228 $matchedLength += $ptr; 2229 } 2230 2231 if (is_array($parsingParams)) { 2232 $parsingParams[] = array($output, $key); 2233 return $parsingParams; 2234 } elseif ($curBlock === 'namedparam') { 2235 return array($output, $key); 2236 } elseif ($curBlock === 'string' || $curBlock === 'delimited_string') { 2237 return array($matchedLength, $output); 2238 } elseif ($curBlock === 'expression' || $curBlock === 'variable') { 2239 return $output; 2240 } elseif (isset($assign)) { 2241 return self::PHP_OPEN.$output.';'.self::PHP_CLOSE; 2242 } else { 2243 return $output; 2244 } 2245 } else { 2246 if ($curBlock === 'string' || $curBlock === 'delimited_string') { 2247 return array(0, ''); 2248 } else { 2249 throw new Dwoo_Compilation_Exception($this, 'Invalid variable name <em>'.$substr.'</em>'); 2250 } 2251 } 2252 } 2253 2254 /** 2255 * parses any number of chained method calls/property reads 2256 * 2257 * @param string $output the variable or whatever upon which the method are called 2258 * @param string $methodCall method call source, starting at "->" 2259 * @param string $curBlock the current parser-block being processed 2260 * @param int $pointer a reference to a pointer that will be increased by the amount of characters parsed 2261 * @return string parsed call(s)/read(s) 2262 */ 2263 protected function parseMethodCall($output, $methodCall, $curBlock, &$pointer) 2264 { 2265 $ptr = 0; 2266 $len = strlen($methodCall); 2267 2268 while ($ptr < $len) { 2269 if (strpos($methodCall, '->', $ptr) === $ptr) { 2270 $ptr += 2; 2271 } 2272 2273 if (in_array($methodCall[$ptr], array(';', ',', '/', ' ', "\t", "\r", "\n", ')', '+', '*', '%', '=', '-', '|')) || substr($methodCall, $ptr, strlen($this->rd)) === $this->rd) { 2274 // break char found 2275 break; 2276 } 2277 2278 if(!preg_match('/^([a-z0-9_]+)(\(.*?\))?/i', substr($methodCall, $ptr), $methMatch)) { 2279 throw new Dwoo_Compilation_Exception($this, 'Invalid method name : '.substr($methodCall, $ptr, 20)); 2280 } 2281 2282 if (empty($methMatch[2])) { 2283 // property 2284 if ($curBlock === 'root') { 2285 $output .= '->'.$methMatch[1]; 2286 } else { 2287 $output = '(($tmp = '.$output.') ? $tmp->'.$methMatch[1].' : null)'; 2288 } 2289 $ptr += strlen($methMatch[1]); 2290 } else { 2291 // method 2292 if (substr($methMatch[2], 0, 2) === '()') { 2293 $parsedCall = $methMatch[1].'()'; 2294 $ptr += strlen($methMatch[1]) + 2; 2295 } else { 2296 $parsedCall = $this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr); 2297 } 2298 if ($this->securityPolicy !== null) { 2299 $argPos = strpos($parsedCall, '('); 2300 $method = strtolower(substr($parsedCall, 0, $argPos)); 2301 $args = substr($parsedCall, $argPos); 2302 if ($curBlock === 'root') { 2303 $output = '$this->getSecurityPolicy()->callMethod($this, '.$output.', '.var_export($method, true).', array'.$args.')'; 2304 } else { 2305 $output = '(($tmp = '.$output.') ? $this->getSecurityPolicy()->callMethod($this, $tmp, '.var_export($method, true).', array'.$args.') : null)'; 2306 } 2307 } else { 2308 if ($curBlock === 'root') { 2309 $output .= '->'.$parsedCall; 2310 } else { 2311 $output = '(($tmp = '.$output.') ? $tmp->'.$parsedCall.' : null)'; 2312 } 2313 } 2314 } 2315 } 2316 2317 $pointer += $ptr; 2318 return $output; 2319 } 2320 2321 /** 2322 * parses a constant variable (a variable that doesn't contain another variable) and preprocesses it to save runtime processing time 2323 * 2324 * @param string $key the variable to parse 2325 * @param string $curBlock the current parser-block being processed 2326 * @return string parsed variable 2327 */ 2328 protected function parseVarKey($key, $curBlock) 2329 { 2330 if ($key === '') { 2331 return '$this->scope'; 2332 } 2333 if (substr($key, 0, 1) === '.') { 2334 $key = 'dwoo'.$key; 2335 } 2336 if (preg_match('#dwoo\.(get|post|server|cookies|session|env|request)((?:\.[a-z0-9_-]+)+)#i', $key, $m)) { 2337 $global = strtoupper($m[1]); 2338 if ($global === 'COOKIES') { 2339 $global = 'COOKIE'; 2340 } 2341 $key = '$_'.$global; 2342 foreach (explode('.', ltrim($m[2], '.')) as $part) 2343 $key .= '['.var_export($part, true).']'; 2344 if ($curBlock === 'root') { 2345 $output = $key; 2346 } else { 2347 $output = '(isset('.$key.')?'.$key.':null)'; 2348 } 2349 } elseif (preg_match('#dwoo\.const\.([a-z0-9_:]+)#i', $key, $m)) { 2350 return $this->parseConstKey($m[1], $curBlock); 2351 } elseif ($this->scope !== null) { 2352 if (strstr($key, '.') === false && strstr($key, '[') === false && strstr($key, '->') === false) { 2353 if ($key === 'dwoo') { 2354 $output = '$this->globals'; 2355 } elseif ($key === '_root' || $key === '__') { 2356 $output = '$this->data'; 2357 } elseif ($key === '_parent' || $key === '_') { 2358 $output = '$this->readParentVar(1)'; 2359 } elseif ($key === '_key') { 2360 $output = '$tmp_key'; 2361 } else { 2362 if ($curBlock === 'root') { 2363 $output = '$this->scope["'.$key.'"]'; 2364 } else { 2365 $output = '(isset($this->scope["'.$key.'"]) ? $this->scope["'.$key.'"] : null)'; 2366 } 2367 } 2368 } else { 2369 preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+|(\\\?[\'"])[^\3]*?\3)\]?#i', $key, $m); 2370 2371 $i = $m[2][0]; 2372 if ($i === '_parent' || $i === '_') { 2373 $parentCnt = 0; 2374 2375 while (true) { 2376 $parentCnt++; 2377 array_shift($m[2]); 2378 array_shift($m[1]); 2379 if (current($m[2]) === '_parent') { 2380 continue; 2381 } 2382 break; 2383 } 2384 2385 $output = '$this->readParentVar('.$parentCnt.')'; 2386 } else { 2387 if ($i === 'dwoo') { 2388 $output = '$this->globals'; 2389 array_shift($m[2]); 2390 array_shift($m[1]); 2391 } elseif ($i === '_root' || $i === '__') { 2392 $output = '$this->data'; 2393 array_shift($m[2]); 2394 array_shift($m[1]); 2395 } elseif ($i === '_key') { 2396 $output = '$tmp_key'; 2397 } else { 2398 $output = '$this->scope'; 2399 } 2400 2401 while (count($m[1]) && $m[1][0] !== '->') { 2402 $m[2][0] = preg_replace('/(^\\\([\'"])|\\\([\'"])$)/x', '$2$3', $m[2][0]); 2403 if(substr($m[2][0], 0, 1) == '"' || substr($m[2][0], 0, 1) == "'") { 2404 $output .= '['.$m[2][0].']'; 2405 } else { 2406 $output .= '["'.$m[2][0].'"]'; 2407 } 2408 array_shift($m[2]); 2409 array_shift($m[1]); 2410 } 2411 2412 if ($curBlock !== 'root') { 2413 $output = '(isset('.$output.') ? '.$output.':null)'; 2414 } 2415 } 2416 2417 if (count($m[2])) { 2418 unset($m[0]); 2419 $output = '$this->readVarInto('.str_replace("\n", '', var_export($m, true)).', '.$output.', '.($curBlock == 'root' ? 'false': 'true').')'; 2420 } 2421 } 2422 } else { 2423 preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+)\]?#i', $key, $m); 2424 unset($m[0]); 2425 $output = '$this->readVar('.str_replace("\n", '', var_export($m, true)).')'; 2426 } 2427 2428 return $output; 2429 } 2430 2431 /** 2432 * flattens a variable tree, this helps in parsing very complex variables such as $var.foo[$foo.bar->baz].baz, 2433 * it computes the contents of the brackets first and works out from there 2434 * 2435 * @param array $tree the variable tree parsed by he parseVar() method that must be flattened 2436 * @param bool $recursed leave that to false by default, it is only for internal use 2437 * @return string flattened tree 2438 */ 2439 protected function flattenVarTree(array $tree, $recursed=false) 2440 { 2441 $out = $recursed ? '".$this->readVarInto(' : ''; 2442 foreach ($tree as $bit) { 2443 if (is_array($bit)) { 2444 $out.='.'.$this->flattenVarTree($bit, false); 2445 } else { 2446 $key = str_replace('"', '\\"', $bit); 2447 2448 if (substr($key, 0, 1)==='$') { 2449 $out .= '".'.$this->parseVar($key, 0, strlen($key), false, 'variable').'."'; 2450 } else { 2451 $cnt = substr_count($key, '$'); 2452 2453 if ($this->debug) echo 'PARSING SUBVARS IN : '.$key.'<br>'; 2454 if ($cnt > 0) { 2455 while (--$cnt >= 0) { 2456 if (isset($last)) { 2457 $last = strrpos($key, '$', - (strlen($key) - $last + 1)); 2458 } else { 2459 $last = strrpos($key, '$'); 2460 } 2461 preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*'. 2462 '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', substr($key, $last), $submatch); 2463 2464 $len = strlen($submatch[0]); 2465 $key = substr_replace( 2466 $key, 2467 preg_replace_callback( 2468 '#(\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)'. 2469 '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', 2470 array($this, 'replaceVarKeyHelper'), substr($key, $last, $len) 2471 ), 2472 $last, 2473 $len 2474 ); 2475 if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT DONE : '.$key.'<br>'; 2476 } 2477 unset($last); 2478 2479 $out .= $key; 2480 } else { 2481 $out .= $key; 2482 } 2483 } 2484 } 2485 } 2486 $out .= $recursed ? ', true)."' : ''; 2487 return $out; 2488 } 2489 2490 /** 2491 * helper function that parses a variable 2492 * 2493 * @param array $match the matched variable, array(1=>"string match") 2494 * @return string parsed variable 2495 */ 2496 protected function replaceVarKeyHelper($match) 2497 { 2498 return '".'.$this->parseVar($match[0], 0, strlen($match[0]), false, 'variable').'."'; 2499 } 2500 2501 /** 2502 * parses various constants, operators or non-quoted strings 2503 * 2504 * @param string $in the string within which we must parse something 2505 * @param int $from the starting offset of the parsed area 2506 * @param int $to the ending offset of the parsed area 2507 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default 2508 * @param string $curBlock the current parser-block being processed 2509 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default 2510 * @return string parsed values 2511 */ 2512 protected function parseOthers($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null) 2513 { 2514 $first = $in[$from]; 2515 $substr = substr($in, $from, $to-$from); 2516 2517 $end = strlen($substr); 2518 2519 if ($curBlock === 'condition') { 2520 $breakChars = array('(', ')', ' ', '||', '&&', '|', '&', '>=', '<=', '===', '==', '=', '!==', '!=', '<<', '<', '>>', '>', '^', '~', ',', '+', '-', '*', '/', '%', '!', '?', ':', $this->rd, ';'); 2521 } elseif ($curBlock === 'modifier') { 2522 $breakChars = array(' ', ',', ')', ':', '|', "\r", "\n", "\t", ";", $this->rd); 2523 } elseif ($curBlock === 'expression') { 2524 $breakChars = array('/', '%', '+', '-', '*', ' ', ',', ')', "\r", "\n", "\t", ";", $this->rd); 2525 } else { 2526 $breakChars = array(' ', ',', ')', "\r", "\n", "\t", ";", $this->rd); 2527 } 2528 2529 $breaker = false; 2530 while (list($k,$char) = each($breakChars)) { 2531 $test = strpos($substr, $char); 2532 if ($test !== false && $test < $end) { 2533 $end = $test; 2534 $breaker = $k; 2535 } 2536 } 2537 2538 if ($curBlock === 'condition') { 2539 if ($end === 0 && $breaker !== false) { 2540 $end = strlen($breakChars[$breaker]); 2541 } 2542 } 2543 2544 if ($end !== false) { 2545 $substr = substr($substr, 0, $end); 2546 } 2547 2548 if ($pointer !== null) { 2549 $pointer += strlen($substr); 2550 } 2551 2552 $src = $substr; 2553 $substr = trim($substr); 2554 2555 if (strtolower($substr) === 'false' || strtolower($substr) === 'no' || strtolower($substr) === 'off') { 2556 if ($this->debug) echo 'BOOLEAN(FALSE) PARSED<br />'; 2557 $substr = 'false'; 2558 $type = self::T_BOOL; 2559 } elseif (strtolower($substr) === 'true' || strtolower($substr) === 'yes' || strtolower($substr) === 'on') { 2560 if ($this->debug) echo 'BOOLEAN(TRUE) PARSED<br />'; 2561 $substr = 'true'; 2562 $type = self::T_BOOL; 2563 } elseif ($substr === 'null' || $substr === 'NULL') { 2564 if ($this->debug) echo 'NULL PARSED<br />'; 2565 $substr = 'null'; 2566 $type = self::T_NULL; 2567 } elseif (is_numeric($substr)) { 2568 $substr = (float) $substr; 2569 if ((int) $substr == $substr) { 2570 $substr = (int) $substr; 2571 } 2572 $type = self::T_NUMERIC; 2573 if ($this->debug) echo 'NUMBER ('.$substr.') PARSED<br />'; 2574 } elseif (preg_match('{^-?(\d+|\d*(\.\d+))\s*([/*%+-]\s*-?(\d+|\d*(\.\d+)))+$}', $substr)) { 2575 if ($this->debug) echo 'SIMPLE MATH PARSED<br />'; 2576 $type = self::T_MATH; 2577 $substr = '('.$substr.')'; 2578 } elseif ($curBlock === 'condition' && array_search($substr, $breakChars, true) !== false) { 2579 if ($this->debug) echo 'BREAKCHAR ('.$substr.') PARSED<br />'; 2580 $type = self::T_BREAKCHAR; 2581 //$substr = '"'.$substr.'"'; 2582 } else { 2583 $substr = $this->replaceStringVars('\''.str_replace('\'', '\\\'', $substr).'\'', '\'', $curBlock); 2584 $type = self::T_UNQUOTED_STRING; 2585 if ($this->debug) echo 'BLABBER ('.$substr.') CASTED AS STRING<br />'; 2586 } 2587 2588 if (is_array($parsingParams)) { 2589 $parsingParams[] = array($substr, $src, $type); 2590 return $parsingParams; 2591 } elseif ($curBlock === 'namedparam') { 2592 return array($substr, $src, $type); 2593 } elseif ($curBlock === 'expression') { 2594 return $substr; 2595 } else { 2596 throw new Exception('Something went wrong'); 2597 } 2598 } 2599 2600 /** 2601 * replaces variables within a parsed string 2602 * 2603 * @param string $string the parsed string 2604 * @param string $first the first character parsed in the string, which is the string delimiter (' or ") 2605 * @param string $curBlock the current parser-block being processed 2606 * @return string the original string with variables replaced 2607 */ 2608 protected function replaceStringVars($string, $first, $curBlock='') 2609 { 2610 $pos = 0; 2611 if ($this->debug) echo 'STRING VAR REPLACEMENT : '.$string.'<br>'; 2612 // replace vars 2613 while (($pos = strpos($string, '$', $pos)) !== false) { 2614 $prev = substr($string, $pos-1, 1); 2615 if ($prev === '\\') { 2616 $pos++; 2617 continue; 2618 } 2619 2620 $var = $this->parse($string, $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string':'string'))); 2621 $len = $var[0]; 2622 $var = $this->parse(str_replace('\\'.$first, $first, $string), $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string':'string'))); 2623 2624 if ($prev === '`' && substr($string, $pos+$len, 1) === '`') { 2625 $string = substr_replace($string, $first.'.'.$var[1].'.'.$first, $pos-1, $len+2); 2626 } else { 2627 $string = substr_replace($string, $first.'.'.$var[1].'.'.$first, $pos, $len); 2628 } 2629 $pos += strlen($var[1]) + 2; 2630 if ($this->debug) echo 'STRING VAR REPLACEMENT DONE : '.$string.'<br>'; 2631 } 2632 2633 // handle modifiers 2634 // TODO Obsolete? 2635 $string = preg_replace_callback('#("|\')\.(.+?)\.\1((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\4|:[^`]*))*))+)#i', array($this, 'replaceModifiers'), $string); 2636 2637 // replace escaped dollar operators by unescaped ones if required 2638 if ($first==="'") { 2639 $string = str_replace('\\$', '$', $string); 2640 } 2641 2642 return $string; 2643 } 2644 2645 /** 2646 * replaces the modifiers applied to a string or a variable 2647 * 2648 * @param array $m the regex matches that must be array(1=>"double or single quotes enclosing a string, when applicable", 2=>"the string or var", 3=>"the modifiers matched") 2649 * @param string $curBlock the current parser-block being processed 2650 * @return string the input enclosed with various function calls according to the modifiers found 2651 */ 2652 protected function replaceModifiers(array $m, $curBlock = null, &$pointer = null) 2653 { 2654 if ($this->debug) echo 'PARSING MODIFIERS : '.$m[3].'<br />'; 2655 2656 if ($pointer !== null) { 2657 $pointer += strlen($m[3]); 2658 } 2659 // remove first pipe 2660 $cmdstrsrc = substr($m[3], 1); 2661 // remove last quote if present 2662 if (substr($cmdstrsrc, -1, 1) === $m[1]) { 2663 $cmdstrsrc = substr($cmdstrsrc, 0, -1); 2664 $add = $m[1]; 2665 } 2666 2667 $output = $m[2]; 2668 2669 $continue = true; 2670 while (strlen($cmdstrsrc) > 0 && $continue) { 2671 if ($cmdstrsrc[0] === '|') { 2672 $cmdstrsrc = substr($cmdstrsrc, 1); 2673 continue; 2674 } 2675 if ($cmdstrsrc[0] === ' ' || $cmdstrsrc[0] === ';' || substr($cmdstrsrc, 0, strlen($this->rd)) === $this->rd) { 2676 if ($this->debug) echo 'MODIFIER PARSING ENDED, RIGHT DELIMITER or ";" FOUND<br/>'; 2677 $continue = false; 2678 if ($pointer !== null) { 2679 $pointer -= strlen($cmdstrsrc); 2680 } 2681 break; 2682 } 2683 $cmdstr = $cmdstrsrc; 2684 $paramsep = ':'; 2685 if (!preg_match('/^(@{0,2}[a-z_][a-z0-9_]*)(:)?/i', $cmdstr, $match)) { 2686 throw new Dwoo_Compilation_Exception($this, 'Invalid modifier name, started with : '.substr($cmdstr, 0, 10)); 2687 } 2688 $paramspos = !empty($match[2]) ? strlen($match[1]) : false; 2689 $func = $match[1]; 2690 2691 $state = 0; 2692 if ($paramspos === false) { 2693 $cmdstrsrc = substr($cmdstrsrc, strlen($func)); 2694 $params = array(); 2695 if ($this->debug) echo 'MODIFIER ('.$func.') CALLED WITH NO PARAMS<br/>'; 2696 } else { 2697 $paramstr = substr($cmdstr, $paramspos+1); 2698 if (substr($paramstr, -1, 1) === $paramsep) { 2699 $paramstr = substr($paramstr, 0, -1); 2700 } 2701 2702 $ptr = 0; 2703 $params = array(); 2704 while ($ptr < strlen($paramstr)) { 2705 if ($this->debug) echo 'MODIFIER ('.$func.') START PARAM PARSING WITH POINTER AT '.$ptr.'<br/>'; 2706 if ($this->debug) echo $paramstr.'--'.$ptr.'--'.strlen($paramstr).'--modifier<br/>'; 2707 $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'modifier', $ptr); 2708 if ($this->debug) echo 'PARAM PARSED, POINTER AT '.$ptr.'<br/>'; 2709 2710 if ($ptr >= strlen($paramstr)) { 2711 if ($this->debug) echo 'PARAM PARSING ENDED, PARAM STRING CONSUMED<br/>'; 2712 break; 2713 } 2714 2715 if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === '|' || $paramstr[$ptr] === ';' || substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) { 2716 if ($this->debug) echo 'PARAM PARSING ENDED, " ", "|", RIGHT DELIMITER or ";" FOUND, POINTER AT '.$ptr.'<br/>'; 2717 if ($paramstr[$ptr] !== '|') { 2718 $continue = false; 2719 if ($pointer !== null) { 2720 $pointer -= strlen($paramstr) - $ptr; 2721 } 2722 } 2723 $ptr++; 2724 break; 2725 } 2726 if ($ptr < strlen($paramstr) && $paramstr[$ptr] === ':') { 2727 $ptr++; 2728 } 2729 } 2730 $cmdstrsrc = substr($cmdstrsrc, strlen($func)+1+$ptr); 2731 $paramstr = substr($paramstr, 0, $ptr); 2732 foreach ($params as $k=>$p) { 2733 if (is_array($p) && is_array($p[1])) { 2734 $state |= 2; 2735 } else { 2736 if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) { 2737 $params[$k] = array($m[2], array('true', 'true')); 2738 } else { 2739 if ($state & 2) { 2740 throw new Dwoo_Compilation_Exception($this, 'You can not use an unnamed parameter after a named one'); 2741 } 2742 $state |= 1; 2743 } 2744 } 2745 } 2746 } 2747 2748 // check if we must use array_map with this plugin or not 2749 $mapped = false; 2750 if (substr($func, 0, 1) === '@') { 2751 $func = substr($func, 1); 2752 $mapped = true; 2753 } 2754 2755 $pluginType = $this->getPluginType($func); 2756 2757 if ($state & 2) { 2758 array_unshift($params, array('value', array($output, $output))); 2759 } else { 2760 array_unshift($params, array($output, $output)); 2761 } 2762 2763 if ($pluginType & Dwoo_Core::NATIVE_PLUGIN) { 2764 $params = $this->mapParams($params, null, $state); 2765 2766 $params = $params['*'][0]; 2767 2768 $params = self::implode_r($params); 2769 2770 if ($mapped) { 2771 $output = '$this->arrayMap(\''.$func.'\', array('.$params.'))'; 2772 } else { 2773 $output = $func.'('.$params.')'; 2774 } 2775 } elseif ($pluginType & Dwoo_Core::PROXY_PLUGIN) { 2776 $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state); 2777 foreach ($params as &$p) 2778 $p = $p[0]; 2779 $output = call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params); 2780 } elseif ($pluginType & Dwoo_Core::SMARTY_MODIFIER) { 2781 $params = $this->mapParams($params, null, $state); 2782 $params = $params['*'][0]; 2783 2784 $params = self::implode_r($params); 2785 2786 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 2787 $callback = $this->customPlugins[$func]['callback']; 2788 if (is_array($callback)) { 2789 if (is_object($callback[0])) { 2790 $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array('.$params.'))'; 2791 } else { 2792 $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array(\''.$callback[0].'\', \''.$callback[1].'\'), array('.$params.'))'; 2793 } 2794 } elseif ($mapped) { 2795 $output = '$this->arrayMap(\''.$callback.'\', array('.$params.'))'; 2796 } else { 2797 $output = $callback.'('.$params.')'; 2798 } 2799 } elseif ($mapped) { 2800 $output = '$this->arrayMap(\'smarty_modifier_'.$func.'\', array('.$params.'))'; 2801 } else { 2802 $output = 'smarty_modifier_'.$func.'('.$params.')'; 2803 } 2804 } else { 2805 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 2806 $callback = $this->customPlugins[$func]['callback']; 2807 $pluginName = $callback; 2808 } else { 2809 $pluginName = 'Dwoo_Plugin_'.$func; 2810 2811 if ($pluginType & Dwoo_Core::CLASS_PLUGIN) { 2812 $callback = array($pluginName, ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'); 2813 } else { 2814 $callback = $pluginName . (($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? '_compile' : ''); 2815 } 2816 } 2817 2818 $params = $this->mapParams($params, $callback, $state); 2819 2820 foreach ($params as &$p) 2821 $p = $p[0]; 2822 2823 if ($pluginType & Dwoo_Core::FUNC_PLUGIN) { 2824 if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) { 2825 if ($mapped) { 2826 throw new Dwoo_Compilation_Exception($this, 'The @ operator can not be used on compiled plugins.'); 2827 } 2828 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 2829 $funcCompiler = $this->customPlugins[$func]['callback']; 2830 } else { 2831 $funcCompiler = 'Dwoo_Plugin_'.$func.'_compile'; 2832 } 2833 array_unshift($params, $this); 2834 $output = call_user_func_array($funcCompiler, $params); 2835 } else { 2836 array_unshift($params, '$this'); 2837 2838 $params = self::implode_r($params); 2839 if ($mapped) { 2840 $output = '$this->arrayMap(\''.$pluginName.'\', array('.$params.'))'; 2841 } else { 2842 $output = $pluginName.'('.$params.')'; 2843 } 2844 } 2845 } else { 2846 if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) { 2847 if ($mapped) { 2848 throw new Dwoo_Compilation_Exception($this, 'The @ operator can not be used on compiled plugins.'); 2849 } 2850 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 2851 $callback = $this->customPlugins[$func]['callback']; 2852 if (!is_array($callback)) { 2853 if (!method_exists($callback, 'compile')) { 2854 throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use'); 2855 } 2856 if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) { 2857 $funcCompiler = array($callback, 'compile'); 2858 } else { 2859 $funcCompiler = array(new $callback, 'compile'); 2860 } 2861 } else { 2862 $funcCompiler = $callback; 2863 } 2864 } else { 2865 $funcCompiler = array('Dwoo_Plugin_'.$func, 'compile'); 2866 array_unshift($params, $this); 2867 } 2868 $output = call_user_func_array($funcCompiler, $params); 2869 } else { 2870 $params = self::implode_r($params); 2871 2872 if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) { 2873 if (is_object($callback[0])) { 2874 $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array('.$params.'))'; 2875 } else { 2876 $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array(\''.$callback[0].'\', \''.$callback[1].'\'), array('.$params.'))'; 2877 } 2878 } elseif ($mapped) { 2879 $output = '$this->arrayMap(array($this->getObjectPlugin(\'Dwoo_Plugin_'.$func.'\'), \'process\'), array('.$params.'))'; 2880 } else { 2881 $output = '$this->classCall(\''.$func.'\', array('.$params.'))'; 2882 } 2883 } 2884 } 2885 } 2886 } 2887 2888 if ($curBlock === 'var' || $m[1] === null) { 2889 return $output; 2890 } elseif ($curBlock === 'string' || $curBlock === 'root') { 2891 return $m[1].'.'.$output.'.'.$m[1].(isset($add)?$add:null); 2892 } 2893 } 2894 2895 /** 2896 * recursively implodes an array in a similar manner as var_export() does but with some tweaks 2897 * to handle pre-compiled values and the fact that we do not need to enclose everything with 2898 * "array" and do not require top-level keys to be displayed 2899 * 2900 * @param array $params the array to implode 2901 * @param bool $recursiveCall if set to true, the function outputs key names for the top level 2902 * @return string the imploded array 2903 */ 2904 public static function implode_r(array $params, $recursiveCall = false) 2905 { 2906 $out = ''; 2907 foreach ($params as $k=>$p) { 2908 if (is_array($p)) { 2909 $out2 = 'array('; 2910 foreach ($p as $k2=>$v) 2911 $out2 .= var_export($k2, true).' => '.(is_array($v) ? 'array('.self::implode_r($v, true).')' : $v).', '; 2912 $p = rtrim($out2, ', ').')'; 2913 } 2914 if ($recursiveCall) { 2915 $out .= var_export($k, true).' => '.$p.', '; 2916 } else { 2917 $out .= $p.', '; 2918 } 2919 } 2920 return rtrim($out, ', '); 2921 } 2922 2923 /** 2924 * returns the plugin type of a plugin and adds it to the used plugins array if required 2925 * 2926 * @param string $name plugin name, as found in the template 2927 * @return int type as a multi bit flag composed of the Dwoo plugin types constants 2928 */ 2929 protected function getPluginType($name) 2930 { 2931 $pluginType = -1; 2932 2933 if (($this->securityPolicy === null && (function_exists($name) || strtolower($name) === 'isset' || strtolower($name) === 'empty')) || 2934 ($this->securityPolicy !== null && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) !== false)) { 2935 $phpFunc = true; 2936 } elseif ($this->securityPolicy !== null && function_exists($name) && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) === false) { 2937 throw new Dwoo_Security_Exception('Call to a disallowed php function : '.$name); 2938 } 2939 2940 while ($pluginType <= 0) { 2941 if (isset($this->templatePlugins[$name])) { 2942 $pluginType = Dwoo_Core::TEMPLATE_PLUGIN | Dwoo_Core::COMPILABLE_PLUGIN; 2943 } elseif (isset($this->customPlugins[$name])) { 2944 $pluginType = $this->customPlugins[$name]['type'] | Dwoo_Core::CUSTOM_PLUGIN; 2945 } elseif (class_exists('Dwoo_Plugin_'.$name, false) !== false) { 2946 if (is_subclass_of('Dwoo_Plugin_'.$name, 'Dwoo_Block_Plugin')) { 2947 $pluginType = Dwoo_Core::BLOCK_PLUGIN; 2948 } else { 2949 $pluginType = Dwoo_Core::CLASS_PLUGIN; 2950 } 2951 $interfaces = class_implements('Dwoo_Plugin_'.$name, false); 2952 if (in_array('Dwoo_ICompilable', $interfaces) !== false || in_array('Dwoo_ICompilable_Block', $interfaces) !== false) { 2953 $pluginType |= Dwoo_Core::COMPILABLE_PLUGIN; 2954 } 2955 } elseif (function_exists('Dwoo_Plugin_'.$name) !== false) { 2956 $pluginType = Dwoo_Core::FUNC_PLUGIN; 2957 } elseif (function_exists('Dwoo_Plugin_'.$name.'_compile')) { 2958 $pluginType = Dwoo_Core::FUNC_PLUGIN | Dwoo_Core::COMPILABLE_PLUGIN; 2959 } elseif (function_exists('smarty_modifier_'.$name) !== false) { 2960 $pluginType = Dwoo_Core::SMARTY_MODIFIER; 2961 } elseif (function_exists('smarty_function_'.$name) !== false) { 2962 $pluginType = Dwoo_Core::SMARTY_FUNCTION; 2963 } elseif (function_exists('smarty_block_'.$name) !== false) { 2964 $pluginType = Dwoo_Core::SMARTY_BLOCK; 2965 } else { 2966 if ($pluginType===-1) { 2967 try { 2968 $this->dwoo->getLoader()->loadPlugin($name, isset($phpFunc)===false); 2969 } catch (Exception $e) { 2970 if (isset($phpFunc)) { 2971 $pluginType = Dwoo_Core::NATIVE_PLUGIN; 2972 } elseif (is_object($this->dwoo->getPluginProxy()) && $this->dwoo->getPluginProxy()->handles($name)) { 2973 $pluginType = Dwoo_Core::PROXY_PLUGIN; 2974 break; 2975 } else { 2976 throw $e; 2977 } 2978 } 2979 } else { 2980 throw new Dwoo_Exception('Plugin "'.$name.'" could not be found'); 2981 } 2982 $pluginType++; 2983 } 2984 } 2985 2986 if (($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) === 0 && ($pluginType & Dwoo_Core::NATIVE_PLUGIN) === 0 && ($pluginType & Dwoo_Core::PROXY_PLUGIN) === 0) { 2987 $this->addUsedPlugin($name, $pluginType); 2988 } 2989 2990 return $pluginType; 2991 } 2992 2993 /** 2994 * allows a plugin to load another one at compile time, this will also mark 2995 * it as used by this template so it will be loaded at runtime (which can be 2996 * useful for compiled plugins that rely on another plugin when their compiled 2997 * code runs) 2998 * 2999 * @param string $name the plugin name 3000 */ 3001 public function loadPlugin($name) { 3002 $this->getPluginType($name); 3003 } 3004 3005 /** 3006 * runs htmlentities over the matched <?php ?> blocks when the security policy enforces that 3007 * 3008 * @param array $match matched php block 3009 * @return string the htmlentities-converted string 3010 */ 3011 protected function phpTagEncodingHelper($match) 3012 { 3013 return htmlspecialchars($match[0]); 3014 } 3015 3016 /** 3017 * maps the parameters received from the template onto the parameters required by the given callback 3018 * 3019 * @param array $params the array of parameters 3020 * @param callback $callback the function or method to reflect on to find out the required parameters 3021 * @param int $callType the type of call in the template, 0 = no params, 1 = php-style call, 2 = named parameters call 3022 * @param array $map the parameter map to use, if not provided it will be built from the callback 3023 * @return array parameters sorted in the correct order with missing optional parameters filled 3024 */ 3025 protected function mapParams(array $params, $callback, $callType=2, $map = null) 3026 { 3027 if (!$map) { 3028 $map = $this->getParamMap($callback); 3029 } 3030 3031 $paramlist = array(); 3032 3033 // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values)) 3034 $ps = array(); 3035 foreach ($params as $p) { 3036 if (is_array($p[1])) { 3037 $ps[$p[0]] = $p[1]; 3038 } else { 3039 $ps[] = $p; 3040 } 3041 } 3042 3043 // loops over the param map and assigns values from the template or default value for unset optional params 3044 while (list($k,$v) = each($map)) { 3045 if ($v[0] === '*') { 3046 // "rest" array parameter, fill every remaining params in it and then break 3047 if (count($ps) === 0) { 3048 if ($v[1]===false) { 3049 throw new Dwoo_Compilation_Exception($this, 'Rest argument missing for '.str_replace(array('Dwoo_Plugin_', '_compile'), '', (is_array($callback) ? $callback[0] : $callback))); 3050 } else { 3051 break; 3052 } 3053 } 3054 $tmp = array(); 3055 $tmp2 = array(); 3056 $tmp3 = array(); 3057 foreach ($ps as $i=>$p) { 3058 $tmp[$i] = $p[0]; 3059 $tmp2[$i] = $p[1]; 3060 $tmp3[$i] = isset($p[2]) ? $p[2] : 0; 3061 unset($ps[$i]); 3062 } 3063 $paramlist[$v[0]] = array($tmp, $tmp2, $tmp3); 3064 unset($tmp, $tmp2, $i, $p); 3065 break; 3066 } elseif (isset($ps[$v[0]])) { 3067 // parameter is defined as named param 3068 $paramlist[$v[0]] = $ps[$v[0]]; 3069 unset($ps[$v[0]]); 3070 } elseif (isset($ps[$k])) { 3071 // parameter is defined as ordered param 3072 $paramlist[$v[0]] = $ps[$k]; 3073 unset($ps[$k]); 3074 } elseif ($v[1]===false) { 3075 // parameter is not defined and not optional, throw error 3076 if (is_array($callback)) { 3077 if (is_object($callback[0])) { 3078 $name = get_class($callback[0]) . '::' . $callback[1]; 3079 } else { 3080 $name = $callback[0]; 3081 } 3082 } else { 3083 $name = $callback; 3084 } 3085 3086 throw new Dwoo_Compilation_Exception($this, 'Argument '.$k.'/'.$v[0].' missing for '.str_replace(array('Dwoo_Plugin_', '_compile'), '', $name)); 3087 } elseif ($v[2]===null) { 3088 // enforce lowercased null if default value is null (php outputs NULL with var export) 3089 $paramlist[$v[0]] = array('null', null, self::T_NULL); 3090 } else { 3091 // outputs default value with var_export 3092 $paramlist[$v[0]] = array(var_export($v[2], true), $v[2]); 3093 } 3094 } 3095 3096 if (count($ps)) { 3097 foreach ($ps as $i=>$p) { 3098 array_push($paramlist, $p); 3099 } 3100 } 3101 3102 return $paramlist; 3103 } 3104 3105 /** 3106 * returns the parameter map of the given callback, it filters out entries typed as Dwoo and Dwoo_Compiler and turns the rest parameter into a "*" 3107 * 3108 * @param callback $callback the function/method to reflect on 3109 * @return array processed parameter map 3110 */ 3111 protected function getParamMap($callback) 3112 { 3113 if (is_null($callback)) { 3114 return array(array('*', true)); 3115 } 3116 if (is_array($callback)) { 3117 $ref = new ReflectionMethod($callback[0], $callback[1]); 3118 } else { 3119 $ref = new ReflectionFunction($callback); 3120 } 3121 3122 $out = array(); 3123 foreach ($ref->getParameters() as $param) { 3124 if (($class = $param->getClass()) !== null && ($class->name === 'Dwoo' || $class->name === 'Dwoo_Core')) { 3125 continue; 3126 } 3127 if (($class = $param->getClass()) !== null && $class->name === 'Dwoo_Compiler') { 3128 continue; 3129 } 3130 if ($param->getName() === 'rest' && $param->isArray() === true) { 3131 $out[] = array('*', $param->isOptional(), null); 3132 continue; 3133 } 3134 $out[] = array($param->getName(), $param->isOptional(), $param->isOptional() ? $param->getDefaultValue() : null); 3135 } 3136 3137 return $out; 3138 } 3139 3140 /** 3141 * returns a default instance of this compiler, used by default by all Dwoo templates that do not have a 3142 * specific compiler assigned and when you do not override the default compiler factory function 3143 * 3144 * @see Dwoo_Core::setDefaultCompilerFactory() 3145 * @return Dwoo_Compiler 3146 */ 3147 public static function compilerFactory() 3148 { 3149 if (self::$instance === null) { 3150 new self; 3151 } 3152 return self::$instance; 3153 } 3154} 3155