1 2/* 3 +------------------------------------------------------------------------+ 4 | Phalcon Framework | 5 +------------------------------------------------------------------------+ 6 | Copyright (c) 2011-2017 Phalcon Team (https://phalconphp.com) | 7 +------------------------------------------------------------------------+ 8 | This source file is subject to the New BSD License that is bundled | 9 | with this package in the file LICENSE.txt. | 10 | | 11 | If you did not receive a copy of the license and are unable to | 12 | obtain it through the world-wide-web, please send an email | 13 | to license@phalconphp.com so we can send you a copy immediately. | 14 +------------------------------------------------------------------------+ 15 | Authors: Andres Gutierrez <andres@phalconphp.com> | 16 | Eduar Carvajal <eduar@phalconphp.com> | 17 +------------------------------------------------------------------------+ 18 */ 19 20namespace Phalcon\Mvc\View\Engine\Volt; 21 22use Phalcon\DiInterface; 23use Phalcon\Mvc\ViewBaseInterface; 24use Phalcon\Di\InjectionAwareInterface; 25use Phalcon\Mvc\View\Engine\Volt\Exception; 26 27/** 28 * Phalcon\Mvc\View\Engine\Volt\Compiler 29 * 30 * This class reads and compiles Volt templates into PHP plain code 31 * 32 *<code> 33 * $compiler = new \Phalcon\Mvc\View\Engine\Volt\Compiler(); 34 * 35 * $compiler->compile("views/partials/header.volt"); 36 * 37 * require $compiler->getCompiledTemplatePath(); 38 *</code> 39 */ 40class Compiler implements InjectionAwareInterface 41{ 42 43 protected _dependencyInjector; 44 45 protected _view; 46 47 protected _options; 48 49 protected _arrayHelpers; 50 51 protected _level = 0; 52 53 protected _foreachLevel = 0; 54 55 protected _blockLevel = 0; 56 57 protected _exprLevel = 0; 58 59 protected _extended = false; 60 61 protected _autoescape = false; 62 63 protected _extendedBlocks; 64 65 protected _currentBlock; 66 67 protected _blocks; 68 69 protected _forElsePointers; 70 71 protected _loopPointers; 72 73 protected _extensions; 74 75 protected _functions; 76 77 protected _filters; 78 79 protected _macros; 80 81 protected _prefix; 82 83 protected _currentPath; 84 85 protected _compiledTemplatePath; 86 87 /** 88 * Phalcon\Mvc\View\Engine\Volt\Compiler 89 */ 90 public function __construct(<ViewBaseInterface> view = null) 91 { 92 if typeof view == "object" { 93 let this->_view = view; 94 } 95 } 96 97 /** 98 * Sets the dependency injector 99 */ 100 public function setDI(<DiInterface> dependencyInjector) 101 { 102 let this->_dependencyInjector = dependencyInjector; 103 } 104 105 /** 106 * Returns the internal dependency injector 107 */ 108 public function getDI() -> <DiInterface> 109 { 110 return this->_dependencyInjector; 111 } 112 113 /** 114 * Sets the compiler options 115 */ 116 public function setOptions(array! options) 117 { 118 let this->_options = options; 119 } 120 121 /** 122 * Sets a single compiler option 123 * 124 * @param string option 125 * @param mixed value 126 */ 127 public function setOption(string! option, value) 128 { 129 let this->_options[option] = value; 130 } 131 132 /** 133 * Returns a compiler's option 134 * 135 * @param string option 136 * @return string 137 */ 138 public function getOption(string! option) 139 { 140 var value; 141 if fetch value, this->_options[option] { 142 return value; 143 } 144 return null; 145 } 146 147 /** 148 * Returns the compiler options 149 */ 150 public function getOptions() -> array 151 { 152 return this->_options; 153 } 154 155 /** 156 * Fires an event to registered extensions 157 * 158 * @param string name 159 * @param array arguments 160 * @return mixed 161 */ 162 public final function fireExtensionEvent(string! name, arguments = null) 163 { 164 var extensions, extension, status; 165 166 let extensions = this->_extensions; 167 if typeof extensions == "array" { 168 for extension in extensions { 169 170 /** 171 * Check if the extension implements the required event name 172 */ 173 if method_exists(extension, name) { 174 175 if typeof arguments == "array" { 176 let status = call_user_func_array([extension, name], arguments); 177 } else { 178 let status = call_user_func([extension, name]); 179 } 180 181 /** 182 * Only string statuses means the extension process something 183 */ 184 if typeof status == "string" { 185 return status; 186 } 187 } 188 } 189 } 190 } 191 192 /** 193 * Registers a Volt's extension 194 */ 195 public function addExtension(extension) -> <Compiler> 196 { 197 if typeof extension != "object" { 198 throw new Exception("The extension is not valid"); 199 } 200 201 /** 202 * Initialize the extension 203 */ 204 if method_exists(extension, "initialize") { 205 extension->initialize(this); 206 } 207 208 let this->_extensions[] = extension; 209 return this; 210 } 211 212 /** 213 * Returns the list of extensions registered in Volt 214 */ 215 public function getExtensions() -> array 216 { 217 return this->_extensions; 218 } 219 220 /** 221 * Register a new function in the compiler 222 */ 223 public function addFunction(string! name, var definition) -> <Compiler> 224 { 225 let this->_functions[name] = definition; 226 return this; 227 } 228 229 /** 230 * Register the user registered functions 231 */ 232 public function getFunctions() -> array 233 { 234 return this->_functions; 235 } 236 237 /** 238 * Register a new filter in the compiler 239 */ 240 public function addFilter(string! name, var definition) -> <Compiler> 241 { 242 let this->_filters[name] = definition; 243 return this; 244 } 245 246 /** 247 * Register the user registered filters 248 */ 249 public function getFilters() -> array 250 { 251 return this->_filters; 252 } 253 254 /** 255 * Set a unique prefix to be used as prefix for compiled variables 256 */ 257 public function setUniquePrefix(string! prefix) -> <Compiler> 258 { 259 let this->_prefix = prefix; 260 return this; 261 } 262 263 /** 264 * Return a unique prefix to be used as prefix for compiled variables and contexts 265 */ 266 public function getUniquePrefix() -> string 267 { 268 /** 269 * If the unique prefix is not set we use a hash using the modified Berstein algotithm 270 */ 271 if !this->_prefix { 272 let this->_prefix = unique_path_key(this->_currentPath); 273 } 274 275 /** 276 * The user could use a closure generator 277 */ 278 if typeof this->_prefix == "object" { 279 if this->_prefix instanceof \Closure { 280 let this->_prefix = call_user_func_array(this->_prefix, [this]); 281 } 282 } 283 284 if typeof this->_prefix != "string" { 285 throw new Exception("The unique compilation prefix is invalid"); 286 } 287 288 return this->_prefix; 289 } 290 291 /** 292 * Resolves attribute reading 293 */ 294 public function attributeReader(array! expr) -> string 295 { 296 var exprCode, left, leftType, variable, 297 level, dependencyInjector, leftCode, right; 298 299 let exprCode = null; 300 301 let left = expr["left"]; 302 303 if left["type"] == PHVOLT_T_IDENTIFIER { 304 305 let variable = left["value"]; 306 307 /** 308 * Check if the variable is the loop context 309 */ 310 if variable == "loop" { 311 let level = this->_foreachLevel, 312 exprCode .= "$" . this->getUniquePrefix() . level . "loop", 313 this->_loopPointers[level] = level; 314 } else { 315 316 /** 317 * Services registered in the dependency injector container are available always 318 */ 319 let dependencyInjector = this->_dependencyInjector; 320 if typeof dependencyInjector == "object" && dependencyInjector->has(variable) { 321 let exprCode .= "$this->" . variable; 322 } else { 323 let exprCode .= "$" . variable; 324 } 325 } 326 327 } else { 328 let leftCode = this->expression(left), leftType = left["type"]; 329 if leftType != PHVOLT_T_DOT && leftType != PHVOLT_T_FCALL { 330 let exprCode .= leftCode; 331 } else { 332 let exprCode .= leftCode; 333 } 334 } 335 336 let exprCode .= "->"; 337 338 let right = expr["right"]; 339 340 if right["type"] == PHVOLT_T_IDENTIFIER { 341 let exprCode .= right["value"]; 342 } else { 343 let exprCode .= this->expression(right); 344 } 345 346 return exprCode; 347 } 348 349 /** 350 * Resolves function intermediate code into PHP function calls 351 */ 352 public function functionCall(array! expr) -> string 353 { 354 var code, funcArguments, arguments, nameExpr, 355 nameType, name, extensions, functions, definition, 356 extendedBlocks, block, currentBlock, exprLevel, escapedCode, 357 method, arrayHelpers, className; 358 359 let code = null; 360 361 let funcArguments = null; 362 if fetch funcArguments, expr["arguments"] { 363 let arguments = this->expression(funcArguments); 364 } else { 365 let arguments = ""; 366 } 367 368 let nameExpr = expr["name"], nameType = nameExpr["type"]; 369 370 /** 371 * Check if it's a single function 372 */ 373 if nameType == PHVOLT_T_IDENTIFIER { 374 375 let name = nameExpr["value"]; 376 377 /** 378 * Check if any of the registered extensions provide compilation for this function 379 */ 380 let extensions = this->_extensions; 381 if typeof extensions == "array" { 382 383 /** 384 * Notify the extensions about being compiling a function 385 */ 386 let code = this->fireExtensionEvent("compileFunction", [name, arguments, funcArguments]); 387 if typeof code == "string" { 388 return code; 389 } 390 } 391 392 /** 393 * Check if it's a user defined function 394 */ 395 let functions = this->_functions; 396 if typeof functions == "array" { 397 if fetch definition, functions[name] { 398 399 /** 400 * Use the string as function 401 */ 402 if typeof definition == "string" { 403 return definition . "(" . arguments . ")"; 404 } 405 406 /** 407 * Execute the function closure returning the compiled definition 408 */ 409 if typeof definition == "object" { 410 411 if definition instanceof \Closure { 412 return call_user_func_array(definition, [arguments, funcArguments]); 413 } 414 } 415 416 throw new Exception( 417 "Invalid definition for user function '" . name . "' in " . expr["file"] . " on line " . expr["line"] 418 ); 419 } 420 } 421 422 /** 423 * This function includes the previous rendering stage 424 */ 425 if name == "get_content" || name == "content" { 426 return "$this->getContent()"; 427 } 428 429 /** 430 * This function includes views of volt or others template engines dynamically 431 */ 432 if name == "partial" { 433 return "$this->partial(" . arguments . ")"; 434 } 435 436 /** 437 * This function embeds the parent block in the current block 438 */ 439 if name == "super" { 440 let extendedBlocks = this->_extendedBlocks; 441 if typeof extendedBlocks == "array" { 442 443 let currentBlock = this->_currentBlock; 444 if fetch block, extendedBlocks[currentBlock] { 445 446 let exprLevel = this->_exprLevel; 447 if typeof block == "array" { 448 let code = this->_statementListOrExtends(block); 449 if exprLevel == 1 { 450 let escapedCode = code; 451 } else { 452 let escapedCode = addslashes(code); 453 } 454 } else { 455 if exprLevel == 1 { 456 let escapedCode = block; 457 } else { 458 let escapedCode = addslashes(block); 459 } 460 } 461 462 /** 463 * If the super() is the first level we don't escape it 464 */ 465 if exprLevel == 1 { 466 return escapedCode; 467 } 468 return "'" . escapedCode . "'"; 469 } 470 } 471 return "''"; 472 } 473 474 let method = lcfirst(camelize(name)), 475 className = "Phalcon\\Tag"; 476 477 /** 478 * Check if it's a method in Phalcon\Tag 479 */ 480 if method_exists(className, method) { 481 482 let arrayHelpers = this->_arrayHelpers; 483 if typeof arrayHelpers != "array" { 484 let arrayHelpers = [ 485 "check_field": true, 486 "color_field": true, 487 "date_field": true, 488 "date_time_field": true, 489 "date_time_local_field": true, 490 "email_field": true, 491 "file_field": true, 492 "form": true, 493 "hidden_field": true, 494 "image": true, 495 "image_input": true, 496 "link_to": true, 497 "month_field": true, 498 "numeric_field": true, 499 "password_field": true, 500 "radio_field": true, 501 "range_field": true, 502 "search_field": true, 503 "select": true, 504 "select_static": true, 505 "submit_button": true, 506 "tel_field": true, 507 "text_area": true, 508 "text_field": true, 509 "time_field": true, 510 "url_field": true, 511 "week_field": true 512 ]; 513 let this->_arrayHelpers = arrayHelpers; 514 } 515 516 if isset arrayHelpers[name] { 517 return "$this->tag->" . method . "([" . arguments . "])"; 518 } 519 return "$this->tag->" . method . "(" . arguments . ")"; 520 } 521 522 /** 523 * Get a dynamic URL 524 */ 525 if name == "url" { 526 return "$this->url->get(" . arguments . ")"; 527 } 528 529 /** 530 * Get a static URL 531 */ 532 if name == "static_url" { 533 return "$this->url->getStatic(" . arguments . ")"; 534 } 535 536 if name == "date" { 537 return "date(" . arguments . ")"; 538 } 539 540 if name == "time" { 541 return "time()"; 542 } 543 544 if name == "dump" { 545 return "var_dump(" . arguments . ")"; 546 } 547 548 if name == "version" { 549 return "Phalcon\\Version::get()"; 550 } 551 552 if name == "version_id" { 553 return "Phalcon\\Version::getId()"; 554 } 555 556 /** 557 * Read PHP constants in templates 558 */ 559 if name == "constant" { 560 return "constant(" . arguments . ")"; 561 } 562 563 /** 564 * By default it tries to call a macro 565 */ 566 return "$this->callMacro('" . name . "', [" . arguments . "])"; 567 } 568 569 return this->expression(nameExpr) . "(" . arguments . ")"; 570 } 571 572 /** 573 * Resolves filter intermediate code into a valid PHP expression 574 */ 575 public function resolveTest(array! test, string left) -> string 576 { 577 var type, name, testName; 578 579 let type = test["type"]; 580 581 /** 582 * Check if right part is a single identifier 583 */ 584 if type == PHVOLT_T_IDENTIFIER { 585 586 let name = test["value"]; 587 588 /** 589 * Empty uses the PHP's empty operator 590 */ 591 if name == "empty" { 592 return "empty(" . left . ")"; 593 } 594 595 /** 596 * Check if a value is even 597 */ 598 if name == "even" { 599 return "(((" . left . ") % 2) == 0)"; 600 } 601 602 /** 603 * Check if a value is odd 604 */ 605 if name == "odd" { 606 return "(((" . left . ") % 2) != 0)"; 607 } 608 609 /** 610 * Check if a value is numeric 611 */ 612 if name == "numeric" { 613 return "is_numeric(" . left . ")"; 614 } 615 616 /** 617 * Check if a value is scalar 618 */ 619 if name == "scalar" { 620 return "is_scalar(" . left . ")"; 621 } 622 623 /** 624 * Check if a value is iterable 625 */ 626 if name == "iterable" { 627 return "(is_array(" . left . ") || (" . left . ") instanceof Traversable)"; 628 } 629 630 } 631 632 /** 633 * Check if right part is a function call 634 */ 635 if type == PHVOLT_T_FCALL { 636 637 let testName = test["name"]; 638 if fetch name, testName["value"] { 639 640 if name == "divisibleby" { 641 return "(((" . left . ") % (" . this->expression(test["arguments"]) . ")) == 0)"; 642 } 643 644 /** 645 * Checks if a value is equals to other 646 */ 647 if name == "sameas" { 648 return "(" . left . ") === (" . this->expression(test["arguments"]) . ")"; 649 } 650 651 /** 652 * Checks if a variable match a type 653 */ 654 if name == "type" { 655 return "gettype(" . left . ") === (" . this->expression(test["arguments"]) . ")"; 656 } 657 } 658 } 659 660 /** 661 * Fall back to the equals operator 662 */ 663 return left . " == " . this->expression(test); 664 } 665 666 /** 667 * Resolves filter intermediate code into PHP function calls 668 */ 669 final protected function resolveFilter(array! filter, string left) -> string 670 { 671 var code, type, functionName, name, file, line, 672 extensions, filters, funcArguments, arguments, definition; 673 674 let code = null, type = filter["type"]; 675 676 /** 677 * Check if the filter is a single identifier 678 */ 679 if type == PHVOLT_T_IDENTIFIER { 680 let name = filter["value"]; 681 } else { 682 683 if type != PHVOLT_T_FCALL { 684 685 /** 686 * Unknown filter throw an exception 687 */ 688 throw new Exception("Unknown filter type in " . filter["file"] . " on line " . filter["line"]); 689 } 690 691 let functionName = filter["name"], 692 name = functionName["value"]; 693 } 694 695 let funcArguments = null, arguments = null; 696 697 /** 698 * Resolve arguments 699 */ 700 if fetch funcArguments, filter["arguments"] { 701 702 /** 703 * "default" filter is not the first argument, improve this! 704 */ 705 if name != "default" { 706 707 let file = filter["file"], line = filter["line"]; 708 709 /** 710 * TODO: Implement this function directly 711 */ 712 array_unshift(funcArguments, [ 713 "expr": [ 714 "type": 364, 715 "value": left, 716 "file": file, 717 "line": line 718 ], 719 "file": file, 720 "line": line 721 ]); 722 } 723 724 let arguments = this->expression(funcArguments); 725 } else { 726 let arguments = left; 727 } 728 729 /** 730 * Check if any of the registered extensions provide compilation for this filter 731 */ 732 let extensions = this->_extensions; 733 if typeof extensions == "array" { 734 735 /** 736 * Notify the extensions about being compiling a function 737 */ 738 let code = this->fireExtensionEvent("compileFilter", [name, arguments, funcArguments]); 739 if typeof code == "string" { 740 return code; 741 } 742 } 743 744 /** 745 * Check if it's a user defined filter 746 */ 747 let filters = this->_filters; 748 if typeof filters == "array" { 749 if fetch definition, filters[name] { 750 751 /** 752 * The definition is a string 753 */ 754 if typeof definition == "string" { 755 return definition . "(" . arguments . ")"; 756 } 757 758 /** 759 * The definition is a closure 760 */ 761 if typeof definition == "object" { 762 if definition instanceof \Closure { 763 return call_user_func_array(definition, [arguments, funcArguments]); 764 } 765 } 766 767 /** 768 * Invalid filter definition throw an exception 769 */ 770 throw new Exception( 771 "Invalid definition for user filter '" . name . "' in " . filter["file"] . " on line " . filter["line"] 772 ); 773 } 774 } 775 776 /** 777 * "length" uses the length method implemented in the Volt adapter 778 */ 779 if name == "length" { 780 return "$this->length(" . arguments . ")"; 781 } 782 783 /** 784 * "e"/"escape" filter uses the escaper component 785 */ 786 if name == "e" || name == "escape" { 787 return "$this->escaper->escapeHtml(" . arguments . ")"; 788 } 789 790 /** 791 * "escape_css" filter uses the escaper component to filter css 792 */ 793 if name == "escape_css" { 794 return "$this->escaper->escapeCss(" . arguments . ")"; 795 } 796 797 /** 798 * "escape_js" filter uses the escaper component to escape javascript 799 */ 800 if name == "escape_js" { 801 return "$this->escaper->escapeJs(" . arguments . ")"; 802 } 803 804 /** 805 * "escape_attr" filter uses the escaper component to escape html attributes 806 */ 807 if name == "escape_attr" { 808 return "$this->escaper->escapeHtmlAttr(" . arguments . ")"; 809 } 810 811 /** 812 * "trim" calls the "trim" function in the PHP userland 813 */ 814 if name == "trim" { 815 return "trim(" . arguments . ")"; 816 } 817 818 /** 819 * "left_trim" calls the "ltrim" function in the PHP userland 820 */ 821 if name == "left_trim" { 822 return "ltrim(" . arguments . ")"; 823 } 824 825 /** 826 * "right_trim" calls the "rtrim" function in the PHP userland 827 */ 828 if name == "right_trim" { 829 return "rtrim(" . arguments . ")"; 830 } 831 832 /** 833 * "striptags" calls the "strip_tags" function in the PHP userland 834 */ 835 if name == "striptags" { 836 return "strip_tags(" . arguments . ")"; 837 } 838 839 /** 840 * "url_encode" calls the "urlencode" function in the PHP userland 841 */ 842 if name == "url_encode" { 843 return "urlencode(" . arguments . ")"; 844 } 845 846 /** 847 * "slashes" calls the "addslashes" function in the PHP userland 848 */ 849 if name == "slashes" { 850 return "addslashes(" . arguments . ")"; 851 } 852 853 /** 854 * "stripslashes" calls the "stripslashes" function in the PHP userland 855 */ 856 if name == "stripslashes" { 857 return "stripslashes(" . arguments . ")"; 858 } 859 860 /** 861 * "nl2br" calls the "nl2br" function in the PHP userland 862 */ 863 if name == "nl2br" { 864 return "nl2br(" . arguments . ")"; 865 } 866 867 /** 868 * "keys" uses calls the "array_keys" function in the PHP userland 869 */ 870 if name == "keys" { 871 return "array_keys(" . arguments . ")"; 872 } 873 874 /** 875 * "join" uses calls the "join" function in the PHP userland 876 */ 877 if name == "join" { 878 return "join(" . arguments . ")"; 879 } 880 881 /** 882 * "lower"/"lowercase" calls the "strtolower" function or "mb_strtolower" if the mbstring extension is loaded 883 */ 884 if name == "lower" || name == "lowercase" { 885 return "Phalcon\\Text::lower(" . arguments . ")"; 886 } 887 888 /** 889 * "upper"/"uppercase" calls the "strtoupper" function or "mb_strtoupper" if the mbstring extension is loaded 890 */ 891 if name == "upper" || name == "uppercase" { 892 return "Phalcon\\Text::upper(" . arguments . ")"; 893 } 894 895 /** 896 * "capitalize" filter calls "ucwords" 897 */ 898 if name == "capitalize" { 899 return "ucwords(" . arguments . ")"; 900 } 901 902 /** 903 * "sort" calls "sort" method in the engine adapter 904 */ 905 if name == "sort" { 906 return "$this->sort(" . arguments . ")"; 907 } 908 909 /** 910 * "json_encode" calls the "json_encode" function in the PHP userland 911 */ 912 if name == "json_encode" { 913 return "json_encode(" . arguments . ")"; 914 } 915 916 /** 917 * "json_decode" calls the "json_decode" function in the PHP userland 918 */ 919 if name == "json_decode" { 920 return "json_decode(" . arguments . ")"; 921 } 922 923 /** 924 * "format" calls the "sprintf" function in the PHP userland 925 */ 926 if name == "format" { 927 return "sprintf(" . arguments . ")"; 928 } 929 930 /** 931 * "abs" calls the "abs" function in the PHP userland 932 */ 933 if name == "abs" { 934 return "abs(" . arguments . ")"; 935 } 936 937 /** 938 * "slice" slices string/arrays/traversable objects 939 */ 940 if name == "slice" { 941 return "$this->slice(" . arguments . ")"; 942 } 943 944 /** 945 * "default" checks if a variable is empty 946 */ 947 if name == "default" { 948 return "(empty(" . left . ") ? (" . arguments . ") : (" . left . "))"; 949 } 950 951 /** 952 * This function uses mbstring or iconv to convert strings from one charset to another 953 */ 954 if name == "convert_encoding" { 955 return "$this->convertEncoding(" . arguments . ")"; 956 } 957 958 /** 959 * Unknown filter throw an exception 960 */ 961 throw new Exception("Unknown filter \"" . name . "\" in " . filter["file"] . " on line " . filter["line"]); 962 } 963 964 /** 965 * Resolves an expression node in an AST volt tree 966 */ 967 final public function expression(array! expr) -> string 968 { 969 var exprCode, extensions, items, singleExpr, singleExprCode, name, 970 left, leftCode, right, rightCode, type, startCode, endCode, start, end; 971 972 let exprCode = null, this->_exprLevel++; 973 974 /** 975 * Check if any of the registered extensions provide compilation for this expression 976 */ 977 let extensions = this->_extensions; 978 979 loop { 980 981 if typeof extensions == "array" { 982 983 /** 984 * Notify the extensions about being resolving an expression 985 */ 986 let exprCode = this->fireExtensionEvent("resolveExpression", [expr]); 987 if typeof exprCode == "string" { 988 break; 989 } 990 } 991 992 if !fetch type, expr["type"] { 993 let items = []; 994 for singleExpr in expr { 995 let singleExprCode = this->expression(singleExpr["expr"]); 996 if fetch name, singleExpr["name"] { 997 let items[] = "'" . name . "' => " . singleExprCode; 998 } else { 999 let items[] = singleExprCode; 1000 } 1001 } 1002 let exprCode = join(", ", items); 1003 break; 1004 } 1005 1006 /** 1007 * Attribute reading needs special handling 1008 */ 1009 if type == PHVOLT_T_DOT { 1010 let exprCode = this->attributeReader(expr); 1011 break; 1012 } 1013 1014 /** 1015 * Left part of expression is always resolved 1016 */ 1017 if fetch left, expr["left"] { 1018 let leftCode = this->expression(left); 1019 } 1020 1021 /** 1022 * Operator "is" also needs special handling 1023 */ 1024 if type == PHVOLT_T_IS { 1025 let exprCode = this->resolveTest(expr["right"], leftCode); 1026 break; 1027 } 1028 1029 /** 1030 * We don't resolve the right expression for filters 1031 */ 1032 if type == 124 { 1033 let exprCode = this->resolveFilter(expr["right"], leftCode); 1034 break; 1035 } 1036 1037 /** 1038 * From here, right part of expression is always resolved 1039 */ 1040 if fetch right, expr["right"] { 1041 let rightCode = this->expression(right); 1042 } 1043 1044 let exprCode = null; 1045 switch type { 1046 1047 case PHVOLT_T_NOT: 1048 let exprCode = "!" . rightCode; 1049 break; 1050 1051 case PHVOLT_T_MUL: 1052 let exprCode = leftCode . " * " . rightCode; 1053 break; 1054 1055 case PHVOLT_T_ADD: 1056 let exprCode = leftCode . " + " . rightCode; 1057 break; 1058 1059 case PHVOLT_T_SUB: 1060 let exprCode = leftCode . " - " . rightCode; 1061 break; 1062 1063 case PHVOLT_T_DIV: 1064 let exprCode = leftCode . " / " . rightCode; 1065 break; 1066 1067 case 37: 1068 let exprCode = leftCode . " % " . rightCode; 1069 break; 1070 1071 case PHVOLT_T_LESS: 1072 let exprCode = leftCode . " < " . rightCode; 1073 break; 1074 1075 case 61: 1076 let exprCode = leftCode . " > " . rightCode; 1077 break; 1078 1079 case 62: 1080 let exprCode = leftCode . " > " . rightCode; 1081 break; 1082 1083 case 126: 1084 let exprCode = leftCode . " . " . rightCode; 1085 break; 1086 1087 case 278: 1088 let exprCode = "pow(" . leftCode . ", " . rightCode . ")"; 1089 break; 1090 1091 case PHVOLT_T_ARRAY: 1092 if isset expr["left"] { 1093 let exprCode = "[" . leftCode . "]"; 1094 } else { 1095 let exprCode = "[]"; 1096 } 1097 break; 1098 1099 case 258: 1100 let exprCode = expr["value"]; 1101 break; 1102 1103 case 259: 1104 let exprCode = expr["value"]; 1105 break; 1106 1107 case PHVOLT_T_STRING: 1108 let exprCode = "'" . str_replace("'", "\\'", expr["value"]) . "'"; 1109 break; 1110 1111 case PHVOLT_T_NULL: 1112 let exprCode = "null"; 1113 break; 1114 1115 case PHVOLT_T_FALSE: 1116 let exprCode = "false"; 1117 break; 1118 1119 case PHVOLT_T_TRUE: 1120 let exprCode = "true"; 1121 break; 1122 1123 case PHVOLT_T_IDENTIFIER: 1124 let exprCode = "$" . expr["value"]; 1125 break; 1126 1127 case PHVOLT_T_AND: 1128 let exprCode = leftCode . " && " . rightCode; 1129 break; 1130 1131 case 267: 1132 let exprCode = leftCode . " || " . rightCode; 1133 break; 1134 1135 case PHVOLT_T_LESSEQUAL: 1136 let exprCode = leftCode . " <= " . rightCode; 1137 break; 1138 1139 case 271: 1140 let exprCode = leftCode . " >= " . rightCode; 1141 break; 1142 1143 case 272: 1144 let exprCode = leftCode . " == " . rightCode; 1145 break; 1146 1147 case 273: 1148 let exprCode = leftCode . " != " . rightCode; 1149 break; 1150 1151 case 274: 1152 let exprCode = leftCode . " === " . rightCode; 1153 break; 1154 1155 case 275: 1156 let exprCode = leftCode . " !== " . rightCode; 1157 break; 1158 1159 case PHVOLT_T_RANGE: 1160 let exprCode = "range(" . leftCode . ", " . rightCode . ")"; 1161 break; 1162 1163 case PHVOLT_T_FCALL: 1164 let exprCode = this->functionCall(expr); 1165 break; 1166 1167 case PHVOLT_T_ENCLOSED: 1168 let exprCode = "(" . leftCode . ")"; 1169 break; 1170 1171 case PHVOLT_T_ARRAYACCESS: 1172 let exprCode = leftCode . "[" . rightCode . "]"; 1173 break; 1174 1175 case PHVOLT_T_SLICE: 1176 1177 /** 1178 * Evaluate the start part of the slice 1179 */ 1180 if fetch start, expr["start"] { 1181 let startCode = this->expression(start); 1182 } else { 1183 let startCode = "null"; 1184 } 1185 1186 /** 1187 * Evaluate the end part of the slice 1188 */ 1189 if fetch end, expr["end"] { 1190 let endCode = this->expression(end); 1191 } else { 1192 let endCode = "null"; 1193 } 1194 1195 let exprCode = "$this->slice(" . leftCode . ", " . startCode . ", " . endCode . ")"; 1196 break; 1197 1198 case PHVOLT_T_NOT_ISSET: 1199 let exprCode = "!isset(" . leftCode . ")"; 1200 break; 1201 1202 case PHVOLT_T_ISSET: 1203 let exprCode = "isset(" . leftCode . ")"; 1204 break; 1205 1206 case PHVOLT_T_NOT_ISEMPTY: 1207 let exprCode = "!empty(" . leftCode . ")"; 1208 break; 1209 1210 case PHVOLT_T_ISEMPTY: 1211 let exprCode = "empty(" . leftCode . ")"; 1212 break; 1213 1214 case PHVOLT_T_NOT_ISEVEN: 1215 let exprCode = "!(((" . leftCode . ") % 2) == 0)"; 1216 break; 1217 1218 case PHVOLT_T_ISEVEN: 1219 let exprCode = "(((" . leftCode . ") % 2) == 0)"; 1220 break; 1221 1222 case PHVOLT_T_NOT_ISODD: 1223 let exprCode = "!(((" . leftCode . ") % 2) != 0)"; 1224 break; 1225 1226 case PHVOLT_T_ISODD: 1227 let exprCode = "(((" . leftCode . ") % 2) != 0)"; 1228 break; 1229 1230 case PHVOLT_T_NOT_ISNUMERIC: 1231 let exprCode = "!is_numeric(" . leftCode . ")"; 1232 break; 1233 1234 case PHVOLT_T_ISNUMERIC: 1235 let exprCode = "is_numeric(" . leftCode . ")"; 1236 break; 1237 1238 case PHVOLT_T_NOT_ISSCALAR: 1239 let exprCode = "!is_scalar(" . leftCode . ")"; 1240 break; 1241 1242 case PHVOLT_T_ISSCALAR: 1243 let exprCode = "is_scalar(" . leftCode . ")"; 1244 break; 1245 1246 case PHVOLT_T_NOT_ISITERABLE: 1247 let exprCode = "!(is_array(" . leftCode . ") || (" . leftCode . ") instanceof Traversable)"; 1248 break; 1249 1250 case PHVOLT_T_ISITERABLE: 1251 let exprCode = "(is_array(" . leftCode . ") || (" . leftCode . ") instanceof Traversable)"; 1252 break; 1253 1254 case PHVOLT_T_IN: 1255 let exprCode = "$this->isIncluded(" . leftCode . ", " . rightCode . ")"; 1256 break; 1257 1258 case PHVOLT_T_NOT_IN: 1259 let exprCode = "!$this->isIncluded(" . leftCode . ", " . rightCode . ")"; 1260 break; 1261 1262 case PHVOLT_T_TERNARY: 1263 let exprCode = "(" . this->expression(expr["ternary"]) . " ? " . leftCode . " : " . rightCode . ")"; 1264 break; 1265 1266 case PHVOLT_T_MINUS: 1267 let exprCode = "-" . rightCode; 1268 break; 1269 1270 case PHVOLT_T_PLUS: 1271 let exprCode = "+" . rightCode; 1272 break; 1273 1274 case PHVOLT_T_RESOLVED_EXPR: 1275 let exprCode = expr["value"]; 1276 break; 1277 1278 default: 1279 throw new Exception("Unknown expression " . type . " in " . expr["file"] . " on line " . expr["line"]); 1280 } 1281 1282 break; 1283 } 1284 1285 let this->_exprLevel--; 1286 1287 return exprCode; 1288 } 1289 1290 /** 1291 * Compiles a block of statements 1292 * 1293 * @param array statements 1294 * @return string|array 1295 */ 1296 final protected function _statementListOrExtends(var statements) 1297 { 1298 var statement; 1299 boolean isStatementList; 1300 1301 /** 1302 * Resolve the statement list as normal 1303 */ 1304 if typeof statements != "array" { 1305 return statements; 1306 } 1307 1308 /** 1309 * If all elements in the statement list are arrays we resolve this as a statementList 1310 */ 1311 let isStatementList = true; 1312 if !isset statements["type"] { 1313 for statement in statements { 1314 if typeof statement != "array" { 1315 let isStatementList = false; 1316 break; 1317 } 1318 } 1319 } 1320 1321 /** 1322 * Resolve the statement list as normal 1323 */ 1324 if isStatementList === true { 1325 return this->_statementList(statements); 1326 } 1327 1328 /** 1329 * Is an array but not a statement list? 1330 */ 1331 return statements; 1332 } 1333 1334 /** 1335 * Compiles a "foreach" intermediate code representation into plain PHP code 1336 */ 1337 public function compileForeach(array! statement, boolean extendsMode = false) -> string 1338 { 1339 var compilation, prefix, level, prefixLevel, expr, 1340 exprCode, bstatement, type, blockStatements, forElse, code, 1341 loopContext, iterator, key, ifExpr, variable; 1342 1343 /** 1344 * A valid expression is required 1345 */ 1346 if !isset statement["expr"] { 1347 throw new Exception("Corrupted statement"); 1348 } 1349 1350 let compilation = "", forElse = null; 1351 1352 let this->_foreachLevel++; 1353 1354 let prefix = this->getUniquePrefix(); 1355 let level = this->_foreachLevel; 1356 1357 /** 1358 * prefixLevel is used to prefix every temporal variable 1359 */ 1360 let prefixLevel = prefix . level; 1361 1362 /** 1363 * Evaluate common expressions 1364 */ 1365 let expr = statement["expr"]; 1366 let exprCode = this->expression(expr); 1367 1368 /** 1369 * Process the block statements 1370 */ 1371 let blockStatements = statement["block_statements"]; 1372 1373 let forElse = false; 1374 if typeof blockStatements == "array" { 1375 1376 for bstatement in blockStatements { 1377 1378 if typeof bstatement != "array" { 1379 break; 1380 } 1381 1382 /** 1383 * Check if the statement is valid 1384 */ 1385 if !fetch type, bstatement["type"] { 1386 break; 1387 } 1388 1389 if type == PHVOLT_T_ELSEFOR { 1390 let compilation .= "<?php $" . prefixLevel . "iterated = false; ?>"; 1391 let forElse = prefixLevel; 1392 let this->_forElsePointers[level] = forElse; 1393 break; 1394 } 1395 1396 } 1397 } 1398 1399 /** 1400 * Process statements block 1401 */ 1402 let code = this->_statementList(blockStatements, extendsMode); 1403 1404 let loopContext = this->_loopPointers; 1405 1406 /** 1407 * Generate the loop context for the "foreach" 1408 */ 1409 if isset loopContext[level] { 1410 let compilation .= "<?php $" . prefixLevel . "iterator = " . exprCode . "; "; 1411 let compilation .= "$" . prefixLevel . "incr = 0; "; 1412 let compilation .= "$" . prefixLevel . "loop = new stdClass(); "; 1413 let compilation .= "$" . prefixLevel . "loop->self = &$" . prefixLevel . "loop; "; 1414 let compilation .= "$" . prefixLevel . "loop->length = count($" . prefixLevel . "iterator); "; 1415 let compilation .= "$" . prefixLevel . "loop->index = 1; "; 1416 let compilation .= "$" . prefixLevel . "loop->index0 = 1; "; 1417 let compilation .= "$" . prefixLevel . "loop->revindex = $" . prefixLevel . "loop->length; "; 1418 let compilation .= "$" . prefixLevel . "loop->revindex0 = $" . prefixLevel . "loop->length - 1; ?>"; 1419 let iterator = "$" . prefixLevel . "iterator"; 1420 } else { 1421 let iterator = exprCode; 1422 } 1423 1424 /** 1425 * Foreach statement 1426 */ 1427 let variable = statement["variable"]; 1428 1429 /** 1430 * Check if a "key" variable needs to be calculated 1431 */ 1432 if fetch key, statement["key"] { 1433 let compilation .= "<?php foreach (" . iterator . " as $" . key . " => $" . variable . ") { "; 1434 } else { 1435 let compilation .= "<?php foreach (" . iterator . " as $" . variable . ") { "; 1436 } 1437 1438 /** 1439 * Check for an "if" expr in the block 1440 */ 1441 if fetch ifExpr, statement["if_expr"] { 1442 let compilation .= "if (" . this->expression(ifExpr) . ") { ?>"; 1443 } else { 1444 let compilation .= "?>"; 1445 } 1446 1447 /** 1448 * Generate the loop context inside the cycle 1449 */ 1450 if isset loopContext[level] { 1451 let compilation .= "<?php $" . prefixLevel . "loop->first = ($" . prefixLevel . "incr == 0); "; 1452 let compilation .= "$" . prefixLevel . "loop->index = $" . prefixLevel . "incr + 1; "; 1453 let compilation .= "$" . prefixLevel . "loop->index0 = $" . prefixLevel . "incr; "; 1454 let compilation .= "$" . prefixLevel . "loop->revindex = $" . prefixLevel . "loop->length - $" . prefixLevel . "incr; "; 1455 let compilation .= "$" . prefixLevel . "loop->revindex0 = $" . prefixLevel . "loop->length - ($" . prefixLevel . "incr + 1); "; 1456 let compilation .= "$" . prefixLevel . "loop->last = ($" . prefixLevel . "incr == ($" . prefixLevel . "loop->length - 1)); ?>"; 1457 } 1458 1459 /** 1460 * Update the forelse var if it's iterated at least one time 1461 */ 1462 if typeof forElse == "string" { 1463 let compilation .= "<?php $" . forElse . "iterated = true; ?>"; 1464 } 1465 1466 /** 1467 * Append the internal block compilation 1468 */ 1469 let compilation .= code; 1470 1471 if isset statement["if_expr"] { 1472 let compilation .= "<?php } ?>"; 1473 } 1474 1475 if typeof forElse == "string" { 1476 let compilation .= "<?php } ?>"; 1477 } else { 1478 if isset loopContext[level] { 1479 let compilation .= "<?php $" . prefixLevel . "incr++; } ?>"; 1480 } else { 1481 let compilation .= "<?php } ?>"; 1482 } 1483 } 1484 1485 let this->_foreachLevel--; 1486 1487 return compilation; 1488 } 1489 1490 /** 1491 * Generates a 'forelse' PHP code 1492 */ 1493 public function compileForElse() -> string 1494 { 1495 var level, prefix; 1496 1497 let level = this->_foreachLevel; 1498 if fetch prefix, this->_forElsePointers[level] { 1499 if isset this->_loopPointers[level] { 1500 return "<?php $" . prefix . "incr++; } if (!$" . prefix . "iterated) { ?>"; 1501 } 1502 return "<?php } if (!$" . prefix . "iterated) { ?>"; 1503 } 1504 return ""; 1505 } 1506 1507 /** 1508 * Compiles a 'if' statement returning PHP code 1509 */ 1510 public function compileIf(array! statement, boolean extendsMode = false) -> string 1511 { 1512 var compilation, blockStatements, expr; 1513 1514 /** 1515 * A valid expression is required 1516 */ 1517 if !fetch expr, statement["expr"] { 1518 throw new Exception("Corrupt statement", statement); 1519 } 1520 1521 /** 1522 * Process statements in the "true" block 1523 */ 1524 let compilation = "<?php if (" . this->expression(expr) . ") { ?>" . this->_statementList(statement["true_statements"], extendsMode); 1525 1526 /** 1527 * Check for a "else"/"elseif" block 1528 */ 1529 if fetch blockStatements, statement["false_statements"] { 1530 1531 /** 1532 * Process statements in the "false" block 1533 */ 1534 let compilation .= "<?php } else { ?>" . this->_statementList(blockStatements, extendsMode); 1535 } 1536 1537 let compilation .= "<?php } ?>"; 1538 1539 return compilation; 1540 } 1541 1542 /** 1543 * Compiles a 'switch' statement returning PHP code 1544 */ 1545 public function compileSwitch(array! statement, boolean extendsMode = false) -> string 1546 { 1547 var compilation, caseClauses, expr, lines; 1548 1549 /** 1550 * A valid expression is required 1551 */ 1552 if !fetch expr, statement["expr"] { 1553 throw new Exception("Corrupt statement", statement); 1554 } 1555 1556 /** 1557 * Process statements in the "true" block 1558 */ 1559 let compilation = "<?php switch (" . this->expression(expr) . "): ?>"; 1560 1561 /** 1562 * Check for a "case"/"default" blocks 1563 */ 1564 if fetch caseClauses, statement["case_clauses"] { 1565 let lines = this->_statementList(caseClauses, extendsMode); 1566 1567 /** 1568 * Any output (including whitespace) between a switch statement and the first case will result in 1569 * a syntax error. This is the responsibility of the user. However, we can clear empty lines 1570 * and whitespaces here to reduce the number of errors. 1571 * 1572 * http://php.net/control-structures.alternative-syntax 1573 */ 1574 if strlen(lines) !== 0 { 1575 /** 1576 * (*ANYCRLF) - specifies a newline convention: (*CR), (*LF) or (*CRLF) 1577 * \h+ - 1+ horizontal whitespace chars 1578 * $ - end of line (now, before CR or LF) 1579 * m - multiline mode on ($ matches at the end of a line). 1580 * u - unicode 1581 * 1582 * g - global search, - is implicit with preg_replace(), you don't need to include it. 1583 */ 1584 let lines = preg_replace("/(*ANYCRLF)^\h+|\h+$|(\h){2,}/mu", "", lines); 1585 } 1586 1587 let compilation .= lines; 1588 } 1589 1590 let compilation .= "<?php endswitch ?>"; 1591 1592 return compilation; 1593 } 1594 1595 /** 1596 * Compiles a "case"/"default" clause returning PHP code 1597 */ 1598 public function compileCase(array! statement, bool caseClause = true) -> string 1599 { 1600 var expr; 1601 1602 if unlikely caseClause === false { 1603 /** 1604 * "default" statement 1605 */ 1606 return "<?php default: ?>"; 1607 } 1608 1609 /** 1610 * A valid expression is required 1611 */ 1612 if !fetch expr, statement["expr"] { 1613 throw new Exception("Corrupt statement", statement); 1614 } 1615 1616 /** 1617 * "case" statement 1618 */ 1619 return "<?php case " . this->expression(expr) . ": ?>"; 1620 } 1621 1622 /** 1623 * Compiles a "elseif" statement returning PHP code 1624 */ 1625 public function compileElseIf(array! statement) -> string 1626 { 1627 var expr; 1628 1629 /** 1630 * A valid expression is required 1631 */ 1632 if !fetch expr, statement["expr"] { 1633 throw new Exception("Corrupt statement", statement); 1634 } 1635 1636 /** 1637 * "elseif" statement 1638 */ 1639 return "<?php } elseif (" . this->expression(expr) . ") { ?>"; 1640 } 1641 1642 /** 1643 * Compiles a "cache" statement returning PHP code 1644 */ 1645 public function compileCache(array! statement, boolean extendsMode = false) -> string 1646 { 1647 var compilation, expr, exprCode, lifetime; 1648 1649 /** 1650 * A valid expression is required 1651 */ 1652 if !fetch expr, statement["expr"] { 1653 throw new Exception("Corrupt statement", statement); 1654 } 1655 1656 /** 1657 * Cache statement 1658 */ 1659 let exprCode = this->expression(expr); 1660 let compilation = "<?php $_cache[" . this->expression(expr) . "] = $this->di->get('viewCache'); "; 1661 if fetch lifetime, statement["lifetime"] { 1662 let compilation .= "$_cacheKey[" . exprCode . "]"; 1663 if lifetime["type"] == PHVOLT_T_IDENTIFIER { 1664 let compilation .= " = $_cache[" . exprCode . "]->start(" . exprCode . ", $" . lifetime["value"] . "); "; 1665 } else { 1666 let compilation .= " = $_cache[" . exprCode . "]->start(" . exprCode . ", " . lifetime["value"] . "); "; 1667 } 1668 } else { 1669 let compilation .= "$_cacheKey[" . exprCode . "] = $_cache[" . exprCode."]->start(" . exprCode . "); "; 1670 } 1671 let compilation .= "if ($_cacheKey[" . exprCode . "] === null) { ?>"; 1672 1673 /** 1674 * Get the code in the block 1675 */ 1676 let compilation .= this->_statementList(statement["block_statements"], extendsMode); 1677 1678 /** 1679 * Check if the cache has a lifetime 1680 */ 1681 if fetch lifetime, statement["lifetime"] { 1682 if lifetime["type"] == PHVOLT_T_IDENTIFIER { 1683 let compilation .= "<?php $_cache[" . exprCode . "]->save(" . exprCode . ", null, $" . lifetime["value"] . "); "; 1684 } else { 1685 let compilation .= "<?php $_cache[" . exprCode . "]->save(" . exprCode . ", null, " . lifetime["value"] . "); "; 1686 } 1687 let compilation .= "} else { echo $_cacheKey[" . exprCode . "]; } ?>"; 1688 } else { 1689 let compilation .= "<?php $_cache[" . exprCode . "]->save(" . exprCode . "); } else { echo $_cacheKey[" . exprCode . "]; } ?>"; 1690 } 1691 1692 return compilation; 1693 } 1694 1695 /** 1696 * Compiles a "set" statement returning PHP code 1697 */ 1698 public function compileSet(array! statement) -> string 1699 { 1700 var assignments, assignment, exprCode, target, compilation; 1701 1702 /** 1703 * A valid assignment list is required 1704 */ 1705 if !fetch assignments, statement["assignments"] { 1706 throw new Exception("Corrupted statement"); 1707 } 1708 1709 let compilation = "<?php"; 1710 1711 /** 1712 * A single set can have several assignments 1713 */ 1714 for assignment in assignments { 1715 1716 let exprCode = this->expression(assignment["expr"]); 1717 1718 /** 1719 * Resolve the expression assigned 1720 */ 1721 let target = this->expression(assignment["variable"]); 1722 1723 /** 1724 * Assignment operator 1725 * Generate the right operator 1726 */ 1727 switch assignment["op"] { 1728 1729 case PHVOLT_T_ADD_ASSIGN: 1730 let compilation .= " " . target . " += " . exprCode . ";"; 1731 break; 1732 1733 case PHVOLT_T_SUB_ASSIGN: 1734 let compilation .= " " . target . " -= " . exprCode . ";"; 1735 break; 1736 1737 case PHVOLT_T_MUL_ASSIGN: 1738 let compilation .= " " . target . " *= " . exprCode . ";"; 1739 break; 1740 1741 case PHVOLT_T_DIV_ASSIGN: 1742 let compilation .= " " . target . " /= " . exprCode . ";"; 1743 break; 1744 1745 default: 1746 let compilation .= " " . target . " = " . exprCode . ";"; 1747 break; 1748 } 1749 1750 } 1751 1752 let compilation .= " ?>"; 1753 return compilation; 1754 } 1755 1756 /** 1757 * Compiles a "do" statement returning PHP code 1758 */ 1759 public function compileDo(array! statement) -> string 1760 { 1761 var expr; 1762 1763 /** 1764 * A valid expression is required 1765 */ 1766 if !fetch expr, statement["expr"] { 1767 throw new Exception("Corrupted statement"); 1768 } 1769 1770 /** 1771 * "Do" statement 1772 */ 1773 return "<?php " . this->expression(expr) . "; ?>"; 1774 } 1775 1776 /** 1777 * Compiles a "return" statement returning PHP code 1778 */ 1779 public function compileReturn(array! statement) -> string 1780 { 1781 var expr; 1782 1783 /** 1784 * A valid expression is required 1785 */ 1786 if !fetch expr, statement["expr"] { 1787 throw new Exception("Corrupted statement"); 1788 } 1789 1790 /** 1791 * "Return" statement 1792 */ 1793 return "<?php return " . this->expression(expr) . "; ?>"; 1794 } 1795 1796 /** 1797 * Compiles a "autoescape" statement returning PHP code 1798 */ 1799 public function compileAutoEscape(array! statement, boolean extendsMode) -> string 1800 { 1801 var autoescape, oldAutoescape, compilation; 1802 1803 /** 1804 * A valid option is required 1805 */ 1806 if !fetch autoescape, statement["enable"] { 1807 throw new Exception("Corrupted statement"); 1808 } 1809 1810 /** 1811 * "autoescape" mode 1812 */ 1813 let oldAutoescape = this->_autoescape, 1814 this->_autoescape = autoescape; 1815 1816 let compilation = this->_statementList(statement["block_statements"], extendsMode), 1817 this->_autoescape = oldAutoescape; 1818 1819 return compilation; 1820 } 1821 1822 /** 1823 * Compiles a '{{' '}}' statement returning PHP code 1824 * 1825 * @param array statement 1826 * @param boolean extendsMode 1827 * @return string 1828 */ 1829 public function compileEcho(array! statement) -> string 1830 { 1831 var expr, exprCode, name; 1832 1833 /** 1834 * A valid expression is required 1835 */ 1836 if !fetch expr, statement["expr"] { 1837 throw new Exception("Corrupt statement", statement); 1838 } 1839 1840 /** 1841 * Evaluate common expressions 1842 */ 1843 let exprCode = this->expression(expr); 1844 1845 if expr["type"] == PHVOLT_T_FCALL { 1846 1847 let name = expr["name"]; 1848 1849 if name["type"] == PHVOLT_T_IDENTIFIER { 1850 1851 /** 1852 * super() is a function however the return of this function must be output as it is 1853 */ 1854 if name["value"] == "super" { 1855 return exprCode; 1856 } 1857 } 1858 } 1859 1860 /** 1861 * Echo statement 1862 */ 1863 if this->_autoescape { 1864 return "<?= $this->escaper->escapeHtml(" . exprCode . ") ?>"; 1865 } 1866 1867 return "<?= " . exprCode . " ?>"; 1868 } 1869 1870 /** 1871 * Compiles a 'include' statement returning PHP code 1872 */ 1873 public function compileInclude(array! statement) -> string 1874 { 1875 var pathExpr, path, subCompiler, finalPath, compilation, params; 1876 1877 /** 1878 * Include statement 1879 * A valid expression is required 1880 */ 1881 if !fetch pathExpr, statement["path"] { 1882 throw new Exception("Corrupted statement"); 1883 } 1884 1885 /** 1886 * Check if the expression is a string 1887 * If the path is an string try to make an static compilation 1888 */ 1889 if pathExpr["type"] == 260 { 1890 1891 /** 1892 * Static compilation cannot be performed if the user passed extra parameters 1893 */ 1894 if !isset statement["params"] { 1895 1896 /** 1897 * Get the static path 1898 */ 1899 let path = pathExpr["value"]; 1900 1901 let finalPath = this->getFinalPath(path); 1902 1903 /** 1904 * Clone the original compiler 1905 * Perform a sub-compilation of the included file 1906 * If the compilation doesn't return anything we include the compiled path 1907 */ 1908 let subCompiler = clone this; 1909 let compilation = subCompiler->compile(finalPath, false); 1910 if typeof compilation == "null" { 1911 1912 /** 1913 * Use file-get-contents to respect the openbase_dir directive 1914 */ 1915 let compilation = file_get_contents(subCompiler->getCompiledTemplatePath()); 1916 } 1917 1918 return compilation; 1919 } 1920 1921 } 1922 1923 /** 1924 * Resolve the path's expression 1925 */ 1926 let path = this->expression(pathExpr); 1927 1928 /** 1929 * Use partial 1930 */ 1931 if !fetch params, statement["params"] { 1932 return "<?php $this->partial(" . path . "); ?>"; 1933 } 1934 1935 return "<?php $this->partial(" . path . ", " . this->expression(params) . "); ?>"; 1936 } 1937 1938 /** 1939 * Compiles macros 1940 */ 1941 public function compileMacro(array! statement, boolean extendsMode) -> string 1942 { 1943 var code, name, defaultValue, macroName, parameters, position, parameter, variableName, blockStatements; 1944 1945 /** 1946 * A valid name is required 1947 */ 1948 if !fetch name, statement["name"] { 1949 throw new Exception("Corrupted statement"); 1950 } 1951 1952 /** 1953 * Check if the macro is already defined 1954 */ 1955 if isset this->_macros[name] { 1956 throw new Exception("Macro '" . name . "' is already defined"); 1957 } 1958 1959 /** 1960 * Register the macro 1961 */ 1962 let this->_macros[name] = name; 1963 1964 let macroName = "$this->_macros['" . name . "']"; 1965 1966 let code = "<?php "; 1967 1968 if !fetch parameters, statement["parameters"] { 1969 let code .= macroName . " = function() { ?>"; 1970 } else { 1971 1972 /** 1973 * Parameters are always received as an array 1974 */ 1975 let code .= macroName . " = function($__p = null) { "; 1976 for position, parameter in parameters { 1977 1978 let variableName = parameter["variable"]; 1979 1980 let code .= "if (isset($__p[" . position . "])) { "; 1981 let code .= "$" . variableName . " = $__p[" . position ."];"; 1982 let code .= " } else { "; 1983 let code .= "if (isset($__p[\"" . variableName."\"])) { "; 1984 let code .= "$" . variableName . " = $__p[\"" . variableName ."\"];"; 1985 let code .= " } else { "; 1986 if fetch defaultValue, parameter["default"] { 1987 let code .= "$" . variableName . " = " . this->expression(defaultValue) . ";"; 1988 } else { 1989 let code .= " throw new \\Phalcon\\Mvc\\View\\Exception(\"Macro '" . name . "' was called without parameter: " . variableName . "\"); "; 1990 } 1991 let code .= " } } "; 1992 } 1993 1994 let code .= " ?>"; 1995 } 1996 1997 /** 1998 * Block statements are allowed 1999 */ 2000 if fetch blockStatements, statement["block_statements"] { 2001 2002 /** 2003 * Process statements block 2004 */ 2005 let code .= this->_statementList(blockStatements, extendsMode) . "<?php }; "; 2006 } else { 2007 let code .= "<?php }; "; 2008 } 2009 2010 /** 2011 * Bind the closure to the $this object allowing to call services 2012 */ 2013 let code .= macroName . " = \\Closure::bind(" . macroName . ", $this); ?>"; 2014 2015 return code; 2016 } 2017 2018 /** 2019 * Compiles calls to macros 2020 * 2021 * @param array statement 2022 * @param boolean extendsMode 2023 * @return string 2024 */ 2025 public function compileCall(array! statement, boolean extendsMode) 2026 { 2027 2028 } 2029 2030 /** 2031 * Traverses a statement list compiling each of its nodes 2032 */ 2033 final protected function _statementList(array! statements, boolean extendsMode = false) -> string 2034 { 2035 var extended, blockMode, compilation, extensions, 2036 statement, tempCompilation, type, blockName, blockStatements, 2037 blocks, path, finalPath, subCompiler, level; 2038 2039 /** 2040 * Nothing to compile 2041 */ 2042 if !count(statements) { 2043 return ""; 2044 } 2045 2046 /** 2047 * Increase the statement recursion level in extends mode 2048 */ 2049 let extended = this->_extended; 2050 let blockMode = extended || extendsMode; 2051 if blockMode === true { 2052 let this->_blockLevel++; 2053 } 2054 2055 let this->_level++; 2056 2057 let compilation = null; 2058 2059 let extensions = this->_extensions; 2060 for statement in statements { 2061 2062 /** 2063 * All statements must be arrays 2064 */ 2065 if typeof statement != "array" { 2066 throw new Exception("Corrupted statement"); 2067 } 2068 2069 /** 2070 * Check if the statement is valid 2071 */ 2072 if !isset statement["type"] { 2073 throw new Exception("Invalid statement in " . statement["file"] . " on line " . statement["line"], statement); 2074 } 2075 2076 /** 2077 * Check if extensions have implemented custom compilation for this statement 2078 */ 2079 if typeof extensions == "array" { 2080 2081 /** 2082 * Notify the extensions about being resolving a statement 2083 */ 2084 let tempCompilation = this->fireExtensionEvent("compileStatement", [statement]); 2085 if typeof tempCompilation == "string" { 2086 let compilation .= tempCompilation; 2087 continue; 2088 } 2089 } 2090 2091 /** 2092 * Get the statement type 2093 */ 2094 let type = statement["type"]; 2095 2096 /** 2097 * Compile the statement according to the statement's type 2098 */ 2099 switch type { 2100 2101 case PHVOLT_T_RAW_FRAGMENT: 2102 let compilation .= statement["value"]; 2103 break; 2104 2105 case PHVOLT_T_IF: 2106 let compilation .= this->compileIf(statement, extendsMode); 2107 break; 2108 2109 case PHVOLT_T_ELSEIF: 2110 let compilation .= this->compileElseIf(statement); 2111 break; 2112 2113 case PHVOLT_T_SWITCH: 2114 let compilation .= this->compileSwitch(statement, extendsMode); 2115 break; 2116 2117 case PHVOLT_T_CASE: 2118 let compilation .= this->compileCase(statement); 2119 break; 2120 2121 case PHVOLT_T_DEFAULT: 2122 let compilation .= this->compileCase(statement, false); 2123 break; 2124 2125 case PHVOLT_T_FOR: 2126 let compilation .= this->compileForeach(statement, extendsMode); 2127 break; 2128 2129 case PHVOLT_T_SET: 2130 let compilation .= this->compileSet(statement); 2131 break; 2132 2133 case PHVOLT_T_ECHO: 2134 let compilation .= this->compileEcho(statement); 2135 break; 2136 2137 case PHVOLT_T_BLOCK: 2138 2139 /** 2140 * Block statement 2141 */ 2142 let blockName = statement["name"]; 2143 2144 fetch blockStatements, statement["block_statements"]; 2145 2146 let blocks = this->_blocks; 2147 if blockMode { 2148 2149 if typeof blocks != "array" { 2150 let blocks = []; 2151 } 2152 2153 /** 2154 * Create a unamed block 2155 */ 2156 if typeof compilation != "null" { 2157 let blocks[] = compilation; 2158 let compilation = null; 2159 } 2160 2161 /** 2162 * In extends mode we add the block statements to the blocks variable 2163 */ 2164 let blocks[blockName] = blockStatements; 2165 let this->_blocks = blocks; 2166 2167 } else { 2168 if typeof blockStatements == "array" { 2169 let compilation .= this->_statementList(blockStatements, extendsMode); 2170 } 2171 } 2172 break; 2173 2174 case PHVOLT_T_EXTENDS: 2175 2176 /** 2177 * Extends statement 2178 */ 2179 let path = statement["path"]; 2180 2181 let finalPath = this->getFinalPath(path["value"]); 2182 2183 let extended = true; 2184 2185 /** 2186 * Perform a sub-compilation of the extended file 2187 */ 2188 let subCompiler = clone this; 2189 let tempCompilation = subCompiler->compile(finalPath, extended); 2190 2191 /** 2192 * If the compilation doesn't return anything we include the compiled path 2193 */ 2194 if typeof tempCompilation == "null" { 2195 let tempCompilation = file_get_contents(subCompiler->getCompiledTemplatePath()); 2196 } 2197 2198 let this->_extended = true; 2199 let this->_extendedBlocks = tempCompilation; 2200 let blockMode = extended; 2201 break; 2202 2203 case PHVOLT_T_INCLUDE: 2204 let compilation .= this->compileInclude(statement); 2205 break; 2206 2207 case PHVOLT_T_CACHE: 2208 let compilation .= this->compileCache(statement, extendsMode); 2209 break; 2210 2211 case PHVOLT_T_DO: 2212 let compilation .= this->compileDo(statement); 2213 break; 2214 2215 case PHVOLT_T_RETURN: 2216 let compilation .= this->compileReturn(statement); 2217 break; 2218 2219 case PHVOLT_T_AUTOESCAPE: 2220 let compilation .= this->compileAutoEscape(statement, extendsMode); 2221 break; 2222 2223 case PHVOLT_T_CONTINUE: 2224 /** 2225 * "Continue" statement 2226 */ 2227 let compilation .= "<?php continue; ?>"; 2228 break; 2229 2230 case PHVOLT_T_BREAK: 2231 /** 2232 * "Break" statement 2233 */ 2234 let compilation .= "<?php break; ?>"; 2235 break; 2236 2237 case 321: 2238 /** 2239 * "Forelse" condition 2240 */ 2241 let compilation .= this->compileForElse(); 2242 break; 2243 2244 case PHVOLT_T_MACRO: 2245 /** 2246 * Define a macro 2247 */ 2248 let compilation .= this->compileMacro(statement, extendsMode); 2249 break; 2250 2251 case 325: 2252 /** 2253 * "Call" statement 2254 */ 2255 let compilation .= this->compileCall(statement, extendsMode); 2256 break; 2257 2258 case 358: 2259 /** 2260 * Empty statement 2261 */ 2262 break; 2263 2264 default: 2265 throw new Exception("Unknown statement " . type . " in " . statement["file"] . " on line " . statement["line"]); 2266 2267 } 2268 } 2269 2270 /** 2271 * Reduce the statement level nesting 2272 */ 2273 if blockMode === true { 2274 let level = this->_blockLevel; 2275 if level == 1 { 2276 if typeof compilation != "null" { 2277 let this->_blocks[] = compilation; 2278 } 2279 } 2280 let this->_blockLevel--; 2281 } 2282 2283 let this->_level--; 2284 2285 return compilation; 2286 } 2287 2288 /** 2289 * Compiles a Volt source code returning a PHP plain version 2290 */ 2291 protected function _compileSource(string! viewCode, boolean extendsMode = false) -> string 2292 { 2293 var currentPath, intermediate, extended, 2294 finalCompilation, blocks, extendedBlocks, name, block, 2295 blockCompilation, localBlock, compilation, options, autoescape; 2296 2297 let currentPath = this->_currentPath; 2298 2299 /** 2300 * Check for compilation options 2301 */ 2302 let options = this->_options; 2303 if typeof options == "array" { 2304 2305 /** 2306 * Enable autoescape globally 2307 */ 2308 if fetch autoescape, options["autoescape"] { 2309 if typeof autoescape != "bool" { 2310 throw new Exception("'autoescape' must be boolean"); 2311 } 2312 let this->_autoescape = autoescape; 2313 } 2314 } 2315 2316 let intermediate = phvolt_parse_view(viewCode, currentPath); 2317 2318 /** 2319 * The parsing must return a valid array 2320 */ 2321 if typeof intermediate != "array" { 2322 throw new Exception("Invalid intermediate representation"); 2323 } 2324 2325 let compilation = this->_statementList(intermediate, extendsMode); 2326 2327 /** 2328 * Check if the template is extending another 2329 */ 2330 let extended = this->_extended; 2331 if extended === true { 2332 2333 /** 2334 * Multiple-Inheritance is allowed 2335 */ 2336 if extendsMode === true { 2337 let finalCompilation = []; 2338 } else { 2339 let finalCompilation = null; 2340 } 2341 2342 let blocks = this->_blocks; 2343 let extendedBlocks = this->_extendedBlocks; 2344 2345 for name, block in extendedBlocks { 2346 2347 /** 2348 * If name is a string then is a block name 2349 */ 2350 if typeof name == "string" { 2351 2352 if isset blocks[name] { 2353 /** 2354 * The block is set in the local template 2355 */ 2356 let localBlock = blocks[name], 2357 this->_currentBlock = name, 2358 blockCompilation = this->_statementList(localBlock); 2359 } else { 2360 if typeof block == "array" { 2361 /** 2362 * The block is not set local only in the extended template 2363 */ 2364 let blockCompilation = this->_statementList(block); 2365 } else { 2366 let blockCompilation = block; 2367 } 2368 } 2369 2370 if extendsMode === true { 2371 let finalCompilation[name] = blockCompilation; 2372 } else { 2373 let finalCompilation .= blockCompilation; 2374 } 2375 } else { 2376 2377 /** 2378 * Here the block is an already compiled text 2379 */ 2380 if extendsMode === true { 2381 let finalCompilation[] = block; 2382 } else { 2383 let finalCompilation .= block; 2384 } 2385 } 2386 } 2387 2388 return finalCompilation; 2389 } 2390 2391 if extendsMode === true { 2392 /** 2393 * In extends mode we return the template blocks instead of the compilation 2394 */ 2395 return this->_blocks; 2396 } 2397 return compilation; 2398 } 2399 2400 /** 2401 * Compiles a template into a string 2402 * 2403 *<code> 2404 * echo $compiler->compileString('{{ "hello world" }}'); 2405 *</code> 2406 */ 2407 public function compileString(string! viewCode, boolean extendsMode = false) -> string 2408 { 2409 let this->_currentPath = "eval code"; 2410 return this->_compileSource(viewCode, extendsMode); 2411 } 2412 2413 /** 2414 * Compiles a template into a file forcing the destination path 2415 * 2416 *<code> 2417 * $compiler->compileFile("views/layouts/main.volt", "views/layouts/main.volt.php"); 2418 *</code> 2419 * 2420 * @param string path 2421 * @param string compiledPath 2422 * @param boolean extendsMode 2423 * @return string|array 2424 */ 2425 public function compileFile(string! path, string! compiledPath, boolean extendsMode = false) 2426 { 2427 var viewCode, compilation, finalCompilation; 2428 2429 if path == compiledPath { 2430 throw new Exception("Template path and compilation template path cannot be the same"); 2431 } 2432 2433 /** 2434 * Check if the template does exist 2435 */ 2436 if !file_exists(path) { 2437 throw new Exception("Template file " . path . " does not exist"); 2438 } 2439 2440 /** 2441 * Always use file_get_contents instead of read the file directly, this respect the open_basedir directive 2442 */ 2443 let viewCode = file_get_contents(path); 2444 if viewCode === false { 2445 throw new Exception("Template file " . path . " could not be opened"); 2446 } 2447 2448 let this->_currentPath = path; 2449 let compilation = this->_compileSource(viewCode, extendsMode); 2450 2451 /** 2452 * We store the file serialized if it's an array of blocks 2453 */ 2454 if typeof compilation == "array" { 2455 let finalCompilation = serialize(compilation); 2456 } else { 2457 let finalCompilation = compilation; 2458 } 2459 2460 /** 2461 * Always use file_put_contents to write files instead of write the file 2462 * directly, this respect the open_basedir directive 2463 */ 2464 if file_put_contents(compiledPath, finalCompilation) === false { 2465 throw new Exception("Volt directory can't be written"); 2466 } 2467 2468 return compilation; 2469 } 2470 2471 /** 2472 * Compiles a template into a file applying the compiler options 2473 * This method does not return the compiled path if the template was not compiled 2474 * 2475 *<code> 2476 * $compiler->compile("views/layouts/main.volt"); 2477 * 2478 * require $compiler->getCompiledTemplatePath(); 2479 *</code> 2480 */ 2481 public function compile(string! templatePath, boolean extendsMode = false) 2482 { 2483 var stat, compileAlways, prefix, compiledPath, compiledSeparator, blocksCode, 2484 compiledExtension, compilation, options, realCompiledPath, 2485 compiledTemplatePath, templateSepPath; 2486 2487 /** 2488 * Re-initialize some properties already initialized when the object is cloned 2489 */ 2490 let this->_extended = false; 2491 let this->_extendedBlocks = false; 2492 let this->_blocks = null; 2493 let this->_level = 0; 2494 let this->_foreachLevel = 0; 2495 let this->_blockLevel = 0; 2496 let this->_exprLevel = 0; 2497 2498 let stat = true; 2499 let compileAlways = false; 2500 let compiledPath = ""; 2501 let prefix = null; 2502 let compiledSeparator = "%%"; 2503 let compiledExtension = ".php"; 2504 let compilation = null; 2505 2506 let options = this->_options; 2507 if typeof options == "array" { 2508 2509 /** 2510 * This makes that templates will be compiled always 2511 */ 2512 if isset options["compileAlways"] { 2513 let compileAlways = options["compileAlways"]; 2514 if typeof compileAlways != "boolean" { 2515 throw new Exception("'compileAlways' must be a bool value"); 2516 } 2517 } 2518 2519 /** 2520 * Prefix is prepended to the template name 2521 */ 2522 if isset options["prefix"] { 2523 let prefix = options["prefix"]; 2524 if typeof prefix != "string" { 2525 throw new Exception("'prefix' must be a string"); 2526 } 2527 } 2528 2529 /** 2530 * Compiled path is a directory where the compiled templates will be located 2531 */ 2532 if isset options["compiledPath"] { 2533 let compiledPath = options["compiledPath"]; 2534 if typeof compiledPath != "string" { 2535 if typeof compiledPath != "object" { 2536 throw new Exception("'compiledPath' must be a string or a closure"); 2537 } 2538 } 2539 } 2540 2541 /** 2542 * There is no compiled separator by default 2543 */ 2544 if isset options["compiledSeparator"] { 2545 let compiledSeparator = options["compiledSeparator"]; 2546 if typeof compiledSeparator != "string" { 2547 throw new Exception("'compiledSeparator' must be a string"); 2548 } 2549 } 2550 2551 /** 2552 * By default the compile extension is .php 2553 */ 2554 if isset options["compiledExtension"] { 2555 let compiledExtension = options["compiledExtension"]; 2556 if typeof compiledExtension != "string" { 2557 throw new Exception("'compiledExtension' must be a string"); 2558 } 2559 } 2560 2561 /** 2562 * Stat option assumes the compilation of the file 2563 */ 2564 if isset options["stat"] { 2565 let stat = options["stat"]; 2566 } 2567 } 2568 2569 /** 2570 * Check if there is a compiled path 2571 */ 2572 if typeof compiledPath == "string" { 2573 2574 /** 2575 * Calculate the template realpath's 2576 */ 2577 if !empty compiledPath { 2578 /** 2579 * Create the virtual path replacing the directory separator by the compiled separator 2580 */ 2581 let templateSepPath = prepare_virtual_path(realpath(templatePath), compiledSeparator); 2582 } else { 2583 let templateSepPath = templatePath; 2584 } 2585 2586 /** 2587 * In extends mode we add an additional 'e' suffix to the file 2588 */ 2589 if extendsMode === true { 2590 let compiledTemplatePath = compiledPath . prefix . templateSepPath . compiledSeparator . "e" . compiledSeparator . compiledExtension; 2591 } else { 2592 let compiledTemplatePath = compiledPath . prefix . templateSepPath . compiledExtension; 2593 } 2594 2595 } else { 2596 2597 /** 2598 * A closure can dynamically compile the path 2599 */ 2600 if typeof compiledPath == "object" { 2601 2602 if compiledPath instanceof \Closure { 2603 2604 let compiledTemplatePath = call_user_func_array(compiledPath, [templatePath, options, extendsMode]); 2605 2606 /** 2607 * The closure must return a valid path 2608 */ 2609 if typeof compiledTemplatePath != "string" { 2610 throw new Exception("compiledPath closure didn't return a valid string"); 2611 } 2612 } else { 2613 throw new Exception("compiledPath must be a string or a closure"); 2614 } 2615 } 2616 } 2617 2618 /** 2619 * Use the real path to avoid collisions 2620 */ 2621 let realCompiledPath = compiledTemplatePath; 2622 2623 if compileAlways { 2624 2625 /** 2626 * Compile always must be used only in the development stage 2627 */ 2628 let compilation = this->compileFile(templatePath, realCompiledPath, extendsMode); 2629 } else { 2630 if stat === true { 2631 if file_exists(compiledTemplatePath) { 2632 2633 /** 2634 * Compare modification timestamps to check if the file needs to be recompiled 2635 */ 2636 if compare_mtime(templatePath, realCompiledPath) { 2637 let compilation = this->compileFile(templatePath, realCompiledPath, extendsMode); 2638 } else { 2639 2640 if extendsMode === true { 2641 2642 /** 2643 * In extends mode we read the file that must contains a serialized array of blocks 2644 */ 2645 let blocksCode = file_get_contents(realCompiledPath); 2646 if blocksCode === false { 2647 throw new Exception("Extends compilation file " . realCompiledPath . " could not be opened"); 2648 } 2649 2650 /** 2651 * Unserialize the array blocks code 2652 */ 2653 if blocksCode { 2654 let compilation = unserialize(blocksCode); 2655 } else { 2656 let compilation = []; 2657 } 2658 } 2659 } 2660 } else { 2661 2662 /** 2663 * The file doesn't exist so we compile the php version for the first time 2664 */ 2665 let compilation = this->compileFile(templatePath, realCompiledPath, extendsMode); 2666 } 2667 } else { 2668 2669 /** 2670 * Stat is off but the compiled file doesn't exist 2671 */ 2672 if !file_exists(realCompiledPath) { 2673 /** 2674 * The file doesn't exist so we compile the php version for the first time 2675 */ 2676 let compilation = this->compileFile(templatePath, realCompiledPath, extendsMode); 2677 } 2678 2679 } 2680 } 2681 2682 let this->_compiledTemplatePath = realCompiledPath; 2683 2684 return compilation; 2685 } 2686 2687 /** 2688 * Returns the path that is currently being compiled 2689 */ 2690 public function getTemplatePath() -> string 2691 { 2692 return this->_currentPath; 2693 } 2694 2695 /** 2696 * Returns the path to the last compiled template 2697 */ 2698 public function getCompiledTemplatePath() -> string 2699 { 2700 return this->_compiledTemplatePath; 2701 } 2702 2703 /** 2704 * Parses a Volt template returning its intermediate representation 2705 * 2706 *<code> 2707 * print_r( 2708 * $compiler->parse("{{ 3 + 2 }}") 2709 * ); 2710 *</code> 2711 * 2712 * @param string viewCode 2713 * @return array 2714 */ 2715 public function parse(string! viewCode) 2716 { 2717 var currentPath = "eval code"; 2718 return phvolt_parse_view(viewCode, currentPath); 2719 } 2720 2721 /** 2722 * Gets the final path with VIEW 2723 */ 2724 protected function getFinalPath(string path) 2725 { 2726 var view, viewsDirs, viewsDir; 2727 let view = this->_view; 2728 2729 if typeof view == "object" { 2730 let viewsDirs = view->getViewsDir(); 2731 2732 if typeof viewsDirs == "array" { 2733 for viewsDir in viewsDirs { 2734 if file_exists(viewsDir . path) { 2735 return viewsDir . path; 2736 } 2737 } 2738 2739 // Otherwise, take the last viewsDir 2740 return viewsDir . path; 2741 2742 } else { 2743 return viewsDirs . path; 2744 } 2745 } 2746 2747 return path; 2748 } 2749} 2750