1<?php 2 3define('DWOO_DIRECTORY', dirname(__FILE__) . DIRECTORY_SEPARATOR); 4 5/** 6 * main dwoo class, allows communication between the compiler, template and data classes 7 * 8 * <pre> 9 * requirements : 10 * php 5.2.0 or above (might work below, it's a rough estimate) 11 * SPL and PCRE extensions (for php versions prior to 5.3.0) 12 * mbstring extension for some string manipulation plugins (especially if you intend to use UTF-8) 13 * recommended : 14 * hash extension (for Dwoo_Template_String - minor performance boost) 15 * 16 * project created : 17 * 2008-01-05 18 * </pre> 19 * 20 * This software is provided 'as-is', without any express or implied warranty. 21 * In no event will the authors be held liable for any damages arising from the use of this software. 22 * 23 * @author Jordi Boggiano <j.boggiano@seld.be> 24 * @copyright Copyright (c) 2008, Jordi Boggiano 25 * @license http://dwoo.org/LICENSE Modified BSD License 26 * @link http://dwoo.org/ 27 * @version 1.1.0 28 * @date 2009-07-18 29 * @package Dwoo 30 */ 31class Dwoo 32{ 33 /** 34 * current version number 35 * 36 * @var string 37 */ 38 const VERSION = '1.1.0'; 39 40 /** 41 * unique number of this dwoo release 42 * 43 * this can be used by templates classes to check whether the compiled template 44 * has been compiled before this release or not, so that old templates are 45 * recompiled automatically when Dwoo is updated 46 */ 47 const RELEASE_TAG = 16; 48 49 /**#@+ 50 * constants that represents all plugin types 51 * 52 * these are bitwise-operation-safe values to allow multiple types 53 * on a single plugin 54 * 55 * @var int 56 */ 57 const CLASS_PLUGIN = 1; 58 const FUNC_PLUGIN = 2; 59 const NATIVE_PLUGIN = 4; 60 const BLOCK_PLUGIN = 8; 61 const COMPILABLE_PLUGIN = 16; 62 const CUSTOM_PLUGIN = 32; 63 const SMARTY_MODIFIER = 64; 64 const SMARTY_BLOCK = 128; 65 const SMARTY_FUNCTION = 256; 66 const PROXY_PLUGIN = 512; 67 const TEMPLATE_PLUGIN = 1024; 68 /**#@-*/ 69 70 /** 71 * character set of the template, used by string manipulation plugins 72 * 73 * it must be lowercase, but setCharset() will take care of that 74 * 75 * @see setCharset 76 * @see getCharset 77 * @var string 78 */ 79 protected $charset = 'utf-8'; 80 81 /** 82 * global variables that are accessible through $dwoo.* in the templates 83 * 84 * default values include: 85 * 86 * $dwoo.version - current version number 87 * $dwoo.ad - a Powered by Dwoo link pointing to dwoo.org 88 * $dwoo.now - the current time 89 * $dwoo.template - the current template filename 90 * $dwoo.charset - the character set used by the template 91 * 92 * on top of that, foreach and other plugins can store special values in there, 93 * see their documentation for more details. 94 * 95 * @private 96 * @var array 97 */ 98 public $globals; 99 100 /** 101 * directory where the compiled templates are stored 102 * 103 * defaults to DWOO_COMPILEDIR (= dwoo_dir/compiled by default) 104 * 105 * @var string 106 */ 107 protected $compileDir; 108 109 /** 110 * directory where the cached templates are stored 111 * 112 * defaults to DWOO_CACHEDIR (= dwoo_dir/cache by default) 113 * 114 * @var string 115 */ 116 protected $cacheDir; 117 118 /** 119 * defines how long (in seconds) the cached files must remain valid 120 * 121 * can be overriden on a per-template basis 122 * 123 * -1 = never delete 124 * 0 = disabled 125 * >0 = duration in seconds 126 * 127 * @var int 128 */ 129 protected $cacheTime = 0; 130 131 /** 132 * security policy object 133 * 134 * @var Dwoo_Security_Policy 135 */ 136 protected $securityPolicy = null; 137 138 /** 139 * stores the custom plugins callbacks 140 * 141 * @see addPlugin 142 * @see removePlugin 143 * @var array 144 */ 145 protected $plugins = array(); 146 147 /** 148 * stores the filter callbacks 149 * 150 * @see addFilter 151 * @see removeFilter 152 * @var array 153 */ 154 protected $filters = array(); 155 156 /** 157 * stores the resource types and associated 158 * classes / compiler classes 159 * 160 * @var array 161 */ 162 protected $resources = array 163 ( 164 'file' => array 165 ( 166 'class' => 'Dwoo_Template_File', 167 'compiler' => null 168 ), 169 'string' => array 170 ( 171 'class' => 'Dwoo_Template_String', 172 'compiler' => null 173 ) 174 ); 175 176 /** 177 * the dwoo loader object used to load plugins by this dwoo instance 178 * 179 * @var Dwoo_ILoader 180 */ 181 protected $loader = null; 182 183 /** 184 * currently rendered template, set to null when not-rendering 185 * 186 * @var Dwoo_ITemplate 187 */ 188 protected $template = null; 189 190 /** 191 * stores the instances of the class plugins during template runtime 192 * 193 * @var array 194 */ 195 protected $runtimePlugins; 196 197 /** 198 * stores the data during template runtime 199 * 200 * @var array 201 */ 202 protected $data; 203 204 /** 205 * stores the current scope during template runtime 206 * 207 * this should ideally not be accessed directly from outside template code 208 * 209 * @var mixed 210 * @private 211 */ 212 public $scope; 213 214 /** 215 * stores the scope tree during template runtime 216 * 217 * @var array 218 */ 219 protected $scopeTree; 220 221 /** 222 * stores the block plugins stack during template runtime 223 * 224 * @var array 225 */ 226 protected $stack; 227 228 /** 229 * stores the current block plugin at the top of the stack during template runtime 230 * 231 * @var Dwoo_Block_Plugin 232 */ 233 protected $curBlock; 234 235 /** 236 * stores the output buffer during template runtime 237 * 238 * @var string 239 */ 240 protected $buffer; 241 242 /** 243 * stores plugin proxy 244 * 245 * @var Dwoo_IPluginProxy 246 */ 247 protected $pluginProxy; 248 249 /** 250 * constructor, sets the cache and compile dir to the default values if not provided 251 * 252 * @param string $compileDir path to the compiled directory, defaults to lib/compiled 253 * @param string $cacheDir path to the cache directory, defaults to lib/cache 254 */ 255 public function __construct($compileDir = null, $cacheDir = null) 256 { 257 if ($compileDir !== null) { 258 $this->setCompileDir($compileDir); 259 } 260 if ($cacheDir !== null) { 261 $this->setCacheDir($cacheDir); 262 } 263 $this->initGlobals(); 264 } 265 266 /** 267 * resets some runtime variables to allow a cloned object to be used to render sub-templates 268 */ 269 public function __clone() 270 { 271 $this->template = null; 272 unset($this->data); 273 } 274 275 /** 276 * outputs the template instead of returning it, this is basically a shortcut for get(*, *, *, true) 277 * 278 * @see get 279 * @param mixed $tpl template, can either be a Dwoo_ITemplate object (i.e. Dwoo_Template_File), a valid path to a template, or 280 * a template as a string it is recommended to provide a Dwoo_ITemplate as it will probably make things faster, 281 * especially if you render a template multiple times 282 * @param mixed $data the data to use, can either be a Dwoo_IDataProvider object (i.e. Dwoo_Data) or an associative array. if you're 283 * rendering the template from cache, it can be left null 284 * @param Dwoo_ICompiler $compiler the compiler that must be used to compile the template, if left empty a default 285 * Dwoo_Compiler will be used. 286 * @return string nothing or the template output if $output is true 287 */ 288 public function output($tpl, $data = array(), Dwoo_ICompiler $compiler = null) 289 { 290 return $this->get($tpl, $data, $compiler, true); 291 } 292 293 /** 294 * returns the given template rendered using the provided data and optional compiler 295 * 296 * @param mixed $tpl template, can either be a Dwoo_ITemplate object (i.e. Dwoo_Template_File), a valid path to a template, or 297 * a template as a string it is recommended to provide a Dwoo_ITemplate as it will probably make things faster, 298 * especially if you render a template multiple times 299 * @param mixed $data the data to use, can either be a Dwoo_IDataProvider object (i.e. Dwoo_Data) or an associative array. if you're 300 * rendering the template from cache, it can be left null 301 * @param Dwoo_ICompiler $compiler the compiler that must be used to compile the template, if left empty a default 302 * Dwoo_Compiler will be used. 303 * @param bool $output flag that defines whether the function returns the output of the template (false, default) or echoes it directly (true) 304 * @return string nothing or the template output if $output is true 305 */ 306 public function get($_tpl, $data = array(), $_compiler = null, $_output = false) 307 { 308 // a render call came from within a template, so we need a new dwoo instance in order to avoid breaking this one 309 if ($this->template instanceof Dwoo_ITemplate) { 310 $proxy = clone $this; 311 return $proxy->get($_tpl, $data, $_compiler, $_output); 312 } 313 314 // auto-create template if required 315 if ($_tpl instanceof Dwoo_ITemplate) { 316 // valid, skip 317 } elseif (is_string($_tpl) && file_exists($_tpl)) { 318 $_tpl = new Dwoo_Template_File($_tpl); 319 } else { 320 throw new Dwoo_Exception('Dwoo->get/Dwoo->output\'s first argument must be a Dwoo_ITemplate (i.e. Dwoo_Template_File) or a valid path to a template file', E_USER_NOTICE); 321 } 322 323 // save the current template, enters render mode at the same time 324 // if another rendering is requested it will be proxied to a new Dwoo instance 325 $this->template = $_tpl; 326 327 // load data 328 if ($data instanceof Dwoo_IDataProvider) { 329 $this->data = $data->getData(); 330 } elseif (is_array($data)) { 331 $this->data = $data; 332 } else { 333 throw new Dwoo_Exception('Dwoo->get/Dwoo->output\'s data argument must be a Dwoo_IDataProvider object (i.e. Dwoo_Data) or an associative array', E_USER_NOTICE); 334 } 335 336 $this->globals['template'] = $_tpl->getName(); 337 $this->initRuntimeVars($_tpl); 338 339 // try to get cached template 340 $file = $_tpl->getCachedTemplate($this); 341 $doCache = $file === true; 342 $cacheLoaded = is_string($file); 343 344 if ($cacheLoaded === true) { 345 // cache is present, run it 346 if ($_output === true) { 347 include $file; 348 $this->template = null; 349 } else { 350 ob_start(); 351 include $file; 352 $this->template = null; 353 return ob_get_clean(); 354 } 355 } else { 356 // no cache present 357 358 if ($doCache === true) { 359 $dynamicId = uniqid(); 360 } 361 362 // render template 363 $out = include $_tpl->getCompiledTemplate($this, $_compiler); 364 365 // template returned false so it needs to be recompiled 366 if ($out === false) { 367 $_tpl->forceCompilation(); 368 $out = include $_tpl->getCompiledTemplate($this, $_compiler); 369 } 370 371 if ($doCache === true) { 372 $out = preg_replace('/(<%|%>|<\?php|<\?|\?>)/', '<?php /*'.$dynamicId.'*/ echo \'$1\'; ?>', $out); 373 if (!class_exists('Dwoo_plugin_dynamic', false)) { 374 $this->getLoader()->loadPlugin('dynamic'); 375 } 376 $out = Dwoo_Plugin_dynamic::unescape($out, $dynamicId); 377 } 378 379 // process filters 380 foreach ($this->filters as $filter) { 381 if (is_array($filter) && $filter[0] instanceof Dwoo_Filter) { 382 $out = call_user_func($filter, $out); 383 } else { 384 $out = call_user_func($filter, $this, $out); 385 } 386 } 387 388 if ($doCache === true) { 389 // building cache 390 $file = $_tpl->cache($this, $out); 391 392 // run it from the cache to be sure dynamics are rendered 393 if ($_output === true) { 394 include $file; 395 // exit render mode 396 $this->template = null; 397 } else { 398 ob_start(); 399 include $file; 400 // exit render mode 401 $this->template = null; 402 return ob_get_clean(); 403 } 404 } else { 405 // no need to build cache 406 // exit render mode 407 $this->template = null; 408 // output 409 if ($_output === true) { 410 echo $out; 411 } else { 412 return $out; 413 } 414 } 415 } 416 } 417 418 /** 419 * re-initializes the globals array before each template run 420 * 421 * this method is only callede once when the Dwoo object is created 422 */ 423 protected function initGlobals() 424 { 425 $this->globals = array 426 ( 427 'version' => self::VERSION, 428 'ad' => '<a href="http://dwoo.org/">Powered by Dwoo</a>', 429 'now' => $_SERVER['REQUEST_TIME'], 430 'charset' => $this->charset, 431 ); 432 } 433 434 /** 435 * re-initializes the runtime variables before each template run 436 * 437 * override this method to inject data in the globals array if needed, this 438 * method is called before each template execution 439 * 440 * @param Dwoo_ITemplate $tpl the template that is going to be rendered 441 */ 442 protected function initRuntimeVars(Dwoo_ITemplate $tpl) 443 { 444 $this->runtimePlugins = array(); 445 $this->scope =& $this->data; 446 $this->scopeTree = array(); 447 $this->stack = array(); 448 $this->curBlock = null; 449 $this->buffer = ''; 450 } 451 452 /* 453 * --------- settings functions --------- 454 */ 455 456 /** 457 * adds a custom plugin that is not in one of the plugin directories 458 * 459 * @param string $name the plugin name to be used in the templates 460 * @param callback $callback the plugin callback, either a function name, 461 * a class name or an array containing an object 462 * or class name and a method name 463 * @param bool $compilable if set to true, the plugin is assumed to be compilable 464 */ 465 public function addPlugin($name, $callback, $compilable = false) 466 { 467 $compilable = $compilable ? self::COMPILABLE_PLUGIN : 0; 468 if (is_array($callback)) { 469 if (is_subclass_of(is_object($callback[0]) ? get_class($callback[0]) : $callback[0], 'Dwoo_Block_Plugin')) { 470 $this->plugins[$name] = array('type'=>self::BLOCK_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>(is_object($callback[0]) ? get_class($callback[0]) : $callback[0])); 471 } else { 472 $this->plugins[$name] = array('type'=>self::CLASS_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>(is_object($callback[0]) ? get_class($callback[0]) : $callback[0]), 'function'=>$callback[1]); 473 } 474 } elseif (class_exists($callback, false)) { 475 if (is_subclass_of($callback, 'Dwoo_Block_Plugin')) { 476 $this->plugins[$name] = array('type'=>self::BLOCK_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>$callback); 477 } else { 478 $this->plugins[$name] = array('type'=>self::CLASS_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>$callback, 'function'=>'process'); 479 } 480 } elseif (function_exists($callback)) { 481 $this->plugins[$name] = array('type'=>self::FUNC_PLUGIN | $compilable, 'callback'=>$callback); 482 } else { 483 throw new Dwoo_Exception('Callback could not be processed correctly, please check that the function/class you used exists'); 484 } 485 } 486 487 /** 488 * removes a custom plugin 489 * 490 * @param string $name the plugin name 491 */ 492 public function removePlugin($name) 493 { 494 if (isset($this->plugins[$name])) { 495 unset($this->plugins[$name]); 496 } 497 } 498 499 /** 500 * adds a filter to this Dwoo instance, it will be used to filter the output of all the templates rendered by this instance 501 * 502 * @param mixed $callback a callback or a filter name if it is autoloaded from a plugin directory 503 * @param bool $autoload if true, the first parameter must be a filter name from one of the plugin directories 504 */ 505 public function addFilter($callback, $autoload = false) 506 { 507 if ($autoload) { 508 $class = 'Dwoo_Filter_'.$callback; 509 510 if (!class_exists($class, false) && !function_exists($class)) { 511 try { 512 $this->getLoader()->loadPlugin($callback); 513 } catch (Dwoo_Exception $e) { 514 if (strstr($callback, 'Dwoo_Filter_')) { 515 throw new Dwoo_Exception('Wrong filter name : '.$callback.', the "Dwoo_Filter_" prefix should not be used, please only use "'.str_replace('Dwoo_Filter_', '', $callback).'"'); 516 } else { 517 throw new Dwoo_Exception('Wrong filter name : '.$callback.', when using autoload the filter must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Filter_name"'); 518 } 519 } 520 } 521 522 if (class_exists($class, false)) { 523 $callback = array(new $class($this), 'process'); 524 } elseif (function_exists($class)) { 525 $callback = $class; 526 } else { 527 throw new Dwoo_Exception('Wrong filter name : '.$callback.', when using autoload the filter must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Filter_name"'); 528 } 529 530 $this->filters[] = $callback; 531 } else { 532 $this->filters[] = $callback; 533 } 534 } 535 536 /** 537 * removes a filter 538 * 539 * @param mixed $callback callback or filter name if it was autoloaded 540 */ 541 public function removeFilter($callback) 542 { 543 if (($index = array_search('Dwoo_Filter_'.$callback, $this->filters, true)) !== false) { 544 unset($this->filters[$index]); 545 } elseif (($index = array_search($callback, $this->filters, true)) !== false) { 546 unset($this->filters[$index]); 547 } else { 548 $class = 'Dwoo_Filter_' . $callback; 549 foreach ($this->filters as $index=>$filter) { 550 if (is_array($filter) && $filter[0] instanceof $class) { 551 unset($this->filters[$index]); 552 break; 553 } 554 } 555 } 556 } 557 558 /** 559 * adds a resource or overrides a default one 560 * 561 * @param string $name the resource name 562 * @param string $class the resource class (which must implement Dwoo_ITemplate) 563 * @param callback $compilerFactory the compiler factory callback, a function that must return a compiler instance used to compile this resource, if none is provided. by default it will produce a Dwoo_Compiler object 564 */ 565 public function addResource($name, $class, $compilerFactory = null) 566 { 567 if (strlen($name) < 2) { 568 throw new Dwoo_Exception('Resource names must be at least two-character long to avoid conflicts with Windows paths'); 569 } 570 571 if (!class_exists($class)) { 572 throw new Dwoo_Exception('Resource class does not exist'); 573 } 574 575 $interfaces = class_implements($class); 576 if (in_array('Dwoo_ITemplate', $interfaces) === false) { 577 throw new Dwoo_Exception('Resource class must implement Dwoo_ITemplate'); 578 } 579 580 $this->resources[$name] = array('class'=>$class, 'compiler'=>$compilerFactory); 581 } 582 583 /** 584 * removes a custom resource 585 * 586 * @param string $name the resource name 587 */ 588 public function removeResource($name) 589 { 590 unset($this->resources[$name]); 591 if ($name==='file') { 592 $this->resources['file'] = array('class'=>'Dwoo_Template_File', 'compiler'=>null); 593 } 594 } 595 596 /* 597 * --------- getters and setters --------- 598 */ 599 600 /** 601 * sets the loader object to use to load plugins 602 * 603 * @param Dwoo_ILoader $loader loader object 604 */ 605 public function setLoader(Dwoo_ILoader $loader) 606 { 607 $this->loader = $loader; 608 } 609 610 /** 611 * returns the current loader object or a default one if none is currently found 612 * 613 * @param Dwoo_ILoader 614 */ 615 public function getLoader() 616 { 617 if ($this->loader === null) { 618 $this->loader = new Dwoo_Loader($this->getCompileDir()); 619 } 620 621 return $this->loader; 622 } 623 624 /** 625 * returns the custom plugins loaded 626 * 627 * used by the Dwoo_ITemplate classes to pass the custom plugins to their Dwoo_ICompiler instance 628 * 629 * @return array 630 */ 631 public function getCustomPlugins() 632 { 633 return $this->plugins; 634 } 635 636 /** 637 * returns the cache directory with a trailing DIRECTORY_SEPARATOR 638 * 639 * @return string 640 */ 641 public function getCacheDir() 642 { 643 if ($this->cacheDir === null) { 644 $this->setCacheDir(dirname(__FILE__).DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR); 645 } 646 647 return $this->cacheDir; 648 } 649 650 /** 651 * sets the cache directory and automatically appends a DIRECTORY_SEPARATOR 652 * 653 * @param string $dir the cache directory 654 */ 655 public function setCacheDir($dir) 656 { 657 $this->cacheDir = rtrim($dir, '/\\').DIRECTORY_SEPARATOR; 658 if (is_writable($this->cacheDir) === false) { 659 throw new Dwoo_Exception('The cache directory must be writable, chmod "'.$this->cacheDir.'" to make it writable'); 660 } 661 } 662 663 /** 664 * returns the compile directory with a trailing DIRECTORY_SEPARATOR 665 * 666 * @return string 667 */ 668 public function getCompileDir() 669 { 670 if ($this->compileDir === null) { 671 $this->setCompileDir(dirname(__FILE__).DIRECTORY_SEPARATOR.'compiled'.DIRECTORY_SEPARATOR); 672 } 673 674 return $this->compileDir; 675 } 676 677 /** 678 * sets the compile directory and automatically appends a DIRECTORY_SEPARATOR 679 * 680 * @param string $dir the compile directory 681 */ 682 public function setCompileDir($dir) 683 { 684 $this->compileDir = rtrim($dir, '/\\').DIRECTORY_SEPARATOR; 685 if (is_writable($this->compileDir) === false) { 686 throw new Dwoo_Exception('The compile directory must be writable, chmod "'.$this->compileDir.'" to make it writable'); 687 } 688 } 689 690 /** 691 * returns the default cache time that is used with templates that do not have a cache time set 692 * 693 * @return int the duration in seconds 694 */ 695 public function getCacheTime() 696 { 697 return $this->cacheTime; 698 } 699 700 /** 701 * sets the default cache time to use with templates that do not have a cache time set 702 * 703 * @param int $seconds the duration in seconds 704 */ 705 public function setCacheTime($seconds) 706 { 707 $this->cacheTime = (int) $seconds; 708 } 709 710 /** 711 * returns the character set used by the string manipulation plugins 712 * 713 * the charset is automatically lowercased 714 * 715 * @return string 716 */ 717 public function getCharset() 718 { 719 return $this->charset; 720 } 721 722 /** 723 * sets the character set used by the string manipulation plugins 724 * 725 * the charset will be automatically lowercased 726 * 727 * @param string $charset the character set 728 */ 729 public function setCharset($charset) 730 { 731 $this->charset = strtolower((string) $charset); 732 } 733 734 /** 735 * returns the current template being rendered, when applicable, or null 736 * 737 * @return Dwoo_ITemplate|null 738 */ 739 public function getTemplate() 740 { 741 return $this->template; 742 } 743 744 /** 745 * sets the default compiler factory function for the given resource name 746 * 747 * a compiler factory must return a Dwoo_ICompiler object pre-configured to fit your needs 748 * 749 * @param string $resourceName the resource name (i.e. file, string) 750 * @param callback $compilerFactory the compiler factory callback 751 */ 752 public function setDefaultCompilerFactory($resourceName, $compilerFactory) 753 { 754 $this->resources[$resourceName]['compiler'] = $compilerFactory; 755 } 756 757 /** 758 * returns the default compiler factory function for the given resource name 759 * 760 * @param string $resourceName the resource name 761 * @return callback the compiler factory callback 762 */ 763 public function getDefaultCompilerFactory($resourceName) 764 { 765 return $this->resources[$resourceName]['compiler']; 766 } 767 768 /** 769 * sets the security policy object to enforce some php security settings 770 * 771 * use this if untrusted persons can modify templates 772 * 773 * @param Dwoo_Security_Policy $policy the security policy object 774 */ 775 public function setSecurityPolicy(Dwoo_Security_Policy $policy = null) 776 { 777 $this->securityPolicy = $policy; 778 } 779 780 /** 781 * returns the current security policy object or null by default 782 * 783 * @return Dwoo_Security_Policy|null the security policy object if any 784 */ 785 public function getSecurityPolicy() 786 { 787 return $this->securityPolicy; 788 } 789 790 /** 791 * sets the object that must be used as a plugin proxy when plugin can't be found 792 * by dwoo's loader 793 * 794 * @param Dwoo_IPluginProxy $pluginProxy the proxy object 795 */ 796 public function setPluginProxy(Dwoo_IPluginProxy $pluginProxy) { 797 $this->pluginProxy = $pluginProxy; 798 } 799 800 /** 801 * returns the current plugin proxy object or null by default 802 * 803 * @param Dwoo_IPluginProxy|null the proxy object if any 804 */ 805 public function getPluginProxy() { 806 return $this->pluginProxy; 807 } 808 809 /* 810 * --------- util functions --------- 811 */ 812 813 /** 814 * [util function] checks whether the given template is cached or not 815 * 816 * @param Dwoo_ITemplate $tpl the template object 817 * @return bool 818 */ 819 public function isCached(Dwoo_ITemplate $tpl) 820 { 821 return is_string($tpl->getCachedTemplate($this)); 822 } 823 824 /** 825 * [util function] clears the cached templates if they are older than the given time 826 * 827 * @param int $olderThan minimum time (in seconds) required for a cached template to be cleared 828 * @return int the amount of templates cleared 829 */ 830 public function clearCache($olderThan=-1) 831 { 832 $cacheDirs = new RecursiveDirectoryIterator($this->getCacheDir()); 833 $cache = new RecursiveIteratorIterator($cacheDirs); 834 $expired = time() - $olderThan; 835 $count = 0; 836 foreach ($cache as $file) { 837 if ($cache->isDot() || $cache->isDir() || substr($file, -5) !== '.html') { 838 continue; 839 } 840 if ($cache->getCTime() < $expired) { 841 $count += unlink((string) $file) ? 1 : 0; 842 } 843 } 844 return $count; 845 } 846 847 /** 848 * [util function] fetches a template object of the given resource 849 * 850 * @param string $resourceName the resource name (i.e. file, string) 851 * @param string $resourceId the resource identifier (i.e. file path) 852 * @param int $cacheTime the cache time setting for this resource 853 * @param string $cacheId the unique cache identifier 854 * @param string $compileId the unique compiler identifier 855 * @return Dwoo_ITemplate 856 */ 857 public function templateFactory($resourceName, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, Dwoo_ITemplate $parentTemplate = null) 858 { 859 if (isset($this->resources[$resourceName])) { 860 // TODO could be changed to $this->resources[$resourceName]['class']::templateFactory(..) in 5.3 maybe 861 return call_user_func(array($this->resources[$resourceName]['class'], 'templateFactory'), $this, $resourceId, $cacheTime, $cacheId, $compileId, $parentTemplate); 862 } else { 863 throw new Dwoo_Exception('Unknown resource type : '.$resourceName); 864 } 865 } 866 867 /** 868 * [util function] checks if the input is an array or an iterator object, optionally it can also check if it's empty 869 * 870 * @param mixed $value the variable to check 871 * @param bool $checkIsEmpty if true, the function will also check if the array is empty, 872 * and return true only if it's not empty 873 * @return bool true if it's an array (and not empty) or false if it's not an array (or if it's empty) 874 */ 875 public function isArray($value, $checkIsEmpty=false) 876 { 877 if (is_array($value) === true) { 878 if ($checkIsEmpty === false) { 879 return true; 880 } else { 881 return count($value) > 0; 882 } 883 } elseif ($value instanceof Iterator) { 884 if ($checkIsEmpty === false) { 885 return true; 886 } elseif ($value instanceof Countable) { 887 return count($value) > 0; 888 } else { 889 $value->rewind(); 890 return $value->valid(); 891 } 892 } elseif ($value instanceof ArrayAccess) { 893 if ($checkIsEmpty === false) { 894 return true; 895 } elseif ($value instanceof Countable) { 896 return count($value) > 0; 897 } else { 898 return $value->offsetExists(0); 899 } 900 } 901 return false; 902 } 903 904 /** 905 * [util function] triggers a dwoo error 906 * 907 * @param string $message the error message 908 * @param int $level the error level, one of the PHP's E_* constants 909 */ 910 public function triggerError($message, $level=E_USER_NOTICE) 911 { 912 if (!($tplIdentifier = $this->template->getResourceIdentifier())) { 913 $tplIdentifier = $this->template->getResourceName(); 914 } 915 trigger_error('Dwoo error (in '.$tplIdentifier.') : '.$message, $level); 916 } 917 918 /* 919 * --------- runtime functions --------- 920 */ 921 922 /** 923 * [runtime function] adds a block to the block stack 924 * 925 * @param string $blockName the block name (without Dwoo_Plugin_ prefix) 926 * @param array $args the arguments to be passed to the block's init() function 927 * @return Dwoo_Block_Plugin the newly created block 928 */ 929 public function addStack($blockName, array $args=array()) 930 { 931 if (isset($this->plugins[$blockName])) { 932 $class = $this->plugins[$blockName]['class']; 933 } else { 934 $class = 'Dwoo_Plugin_'.$blockName; 935 } 936 937 if ($this->curBlock !== null) { 938 $this->curBlock->buffer(ob_get_contents()); 939 ob_clean(); 940 } else { 941 $this->buffer .= ob_get_contents(); 942 ob_clean(); 943 } 944 945 $block = new $class($this); 946 947 $cnt = count($args); 948 if ($cnt===0) { 949 $block->init(); 950 } elseif ($cnt===1) { 951 $block->init($args[0]); 952 } elseif ($cnt===2) { 953 $block->init($args[0], $args[1]); 954 } elseif ($cnt===3) { 955 $block->init($args[0], $args[1], $args[2]); 956 } elseif ($cnt===4) { 957 $block->init($args[0], $args[1], $args[2], $args[3]); 958 } else { 959 call_user_func_array(array($block,'init'), $args); 960 } 961 962 $this->stack[] = $this->curBlock = $block; 963 return $block; 964 } 965 966 /** 967 * [runtime function] removes the plugin at the top of the block stack 968 * 969 * calls the block buffer() function, followed by a call to end() 970 * and finally a call to process() 971 */ 972 public function delStack() 973 { 974 $args = func_get_args(); 975 976 $this->curBlock->buffer(ob_get_contents()); 977 ob_clean(); 978 979 $cnt = count($args); 980 if ($cnt===0) { 981 $this->curBlock->end(); 982 } elseif ($cnt===1) { 983 $this->curBlock->end($args[0]); 984 } elseif ($cnt===2) { 985 $this->curBlock->end($args[0], $args[1]); 986 } elseif ($cnt===3) { 987 $this->curBlock->end($args[0], $args[1], $args[2]); 988 } elseif ($cnt===4) { 989 $this->curBlock->end($args[0], $args[1], $args[2], $args[3]); 990 } else { 991 call_user_func_array(array($this->curBlock, 'end'), $args); 992 } 993 994 $tmp = array_pop($this->stack); 995 996 if (count($this->stack) > 0) { 997 $this->curBlock = end($this->stack); 998 $this->curBlock->buffer($tmp->process()); 999 } else { 1000 $this->curBlock = null; 1001 echo $tmp->process(); 1002 } 1003 1004 unset($tmp); 1005 } 1006 1007 /** 1008 * [runtime function] returns the parent block of the given block 1009 * 1010 * @param Dwoo_Block_Plugin $block 1011 * @return Dwoo_Block_Plugin or false if the given block isn't in the stack 1012 */ 1013 public function getParentBlock(Dwoo_Block_Plugin $block) 1014 { 1015 $index = array_search($block, $this->stack, true); 1016 if ($index !== false && $index > 0) { 1017 return $this->stack[$index-1]; 1018 } 1019 return false; 1020 } 1021 1022 /** 1023 * [runtime function] finds the closest block of the given type, starting at the top of the stack 1024 * 1025 * @param string $type the type of plugin you want to find 1026 * @return Dwoo_Block_Plugin or false if no plugin of such type is in the stack 1027 */ 1028 public function findBlock($type) 1029 { 1030 if (isset($this->plugins[$type])) { 1031 $type = $this->plugins[$type]['class']; 1032 } else { 1033 $type = 'Dwoo_Plugin_'.str_replace('Dwoo_Plugin_', '', $type); 1034 } 1035 1036 $keys = array_keys($this->stack); 1037 while (($key = array_pop($keys)) !== false) { 1038 if ($this->stack[$key] instanceof $type) { 1039 return $this->stack[$key]; 1040 } 1041 } 1042 return false; 1043 } 1044 1045 /** 1046 * [runtime function] returns a Dwoo_Plugin of the given class 1047 * 1048 * this is so a single instance of every class plugin is created at each template run, 1049 * allowing class plugins to have "per-template-run" static variables 1050 * 1051 * @param string $class the class name 1052 * @return mixed an object of the given class 1053 */ 1054 protected function getObjectPlugin($class) 1055 { 1056 if (isset($this->runtimePlugins[$class])) { 1057 return $this->runtimePlugins[$class]; 1058 } 1059 return $this->runtimePlugins[$class] = new $class($this); 1060 } 1061 1062 /** 1063 * [runtime function] calls the process() method of the given class-plugin name 1064 * 1065 * @param string $plugName the class plugin name (without Dwoo_Plugin_ prefix) 1066 * @param array $params an array of parameters to send to the process() method 1067 * @return string the process() return value 1068 */ 1069 public function classCall($plugName, array $params = array()) 1070 { 1071 $class = 'Dwoo_Plugin_'.$plugName; 1072 1073 $plugin = $this->getObjectPlugin($class); 1074 1075 $cnt = count($params); 1076 if ($cnt===0) { 1077 return $plugin->process(); 1078 } elseif ($cnt===1) { 1079 return $plugin->process($params[0]); 1080 } elseif ($cnt===2) { 1081 return $plugin->process($params[0], $params[1]); 1082 } elseif ($cnt===3) { 1083 return $plugin->process($params[0], $params[1], $params[2]); 1084 } elseif ($cnt===4) { 1085 return $plugin->process($params[0], $params[1], $params[2], $params[3]); 1086 } else { 1087 return call_user_func_array(array($plugin, 'process'), $params); 1088 } 1089 } 1090 1091 /** 1092 * [runtime function] calls a php function 1093 * 1094 * @param string $callback the function to call 1095 * @param array $params an array of parameters to send to the function 1096 * @return mixed the return value of the called function 1097 */ 1098 public function arrayMap($callback, array $params) 1099 { 1100 if ($params[0] === $this) { 1101 $addThis = true; 1102 array_shift($params); 1103 } 1104 if ((is_array($params[0]) || ($params[0] instanceof Iterator && $params[0] instanceof ArrayAccess))) { 1105 if (empty($params[0])) { 1106 return $params[0]; 1107 } 1108 1109 // array map 1110 $out = array(); 1111 $cnt = count($params); 1112 1113 if (isset($addThis)) { 1114 array_unshift($params, $this); 1115 $items = $params[1]; 1116 $keys = array_keys($items); 1117 1118 if (is_string($callback) === false) { 1119 while (($i = array_shift($keys)) !== null) { 1120 $out[] = call_user_func_array($callback, array(1=>$items[$i]) + $params); 1121 } 1122 } elseif ($cnt===1) { 1123 while (($i = array_shift($keys)) !== null) { 1124 $out[] = $callback($this, $items[$i]); 1125 } 1126 } elseif ($cnt===2) { 1127 while (($i = array_shift($keys)) !== null) { 1128 $out[] = $callback($this, $items[$i], $params[2]); 1129 } 1130 } elseif ($cnt===3) { 1131 while (($i = array_shift($keys)) !== null) { 1132 $out[] = $callback($this, $items[$i], $params[2], $params[3]); 1133 } 1134 } else { 1135 while (($i = array_shift($keys)) !== null) { 1136 $out[] = call_user_func_array($callback, array(1=>$items[$i]) + $params); 1137 } 1138 } 1139 } else { 1140 $items = $params[0]; 1141 $keys = array_keys($items); 1142 1143 if (is_string($callback) === false) { 1144 while (($i = array_shift($keys)) !== null) { 1145 $out[] = call_user_func_array($callback, array($items[$i]) + $params); 1146 } 1147 } elseif ($cnt===1) { 1148 while (($i = array_shift($keys)) !== null) { 1149 $out[] = $callback($items[$i]); 1150 } 1151 } elseif ($cnt===2) { 1152 while (($i = array_shift($keys)) !== null) { 1153 $out[] = $callback($items[$i], $params[1]); 1154 } 1155 } elseif ($cnt===3) { 1156 while (($i = array_shift($keys)) !== null) { 1157 $out[] = $callback($items[$i], $params[1], $params[2]); 1158 } 1159 } elseif ($cnt===4) { 1160 while (($i = array_shift($keys)) !== null) { 1161 $out[] = $callback($items[$i], $params[1], $params[2], $params[3]); 1162 } 1163 } else { 1164 while (($i = array_shift($keys)) !== null) { 1165 $out[] = call_user_func_array($callback, array($items[$i]) + $params); 1166 } 1167 } 1168 } 1169 return $out; 1170 } else { 1171 return $params[0]; 1172 } 1173 } 1174 1175 /** 1176 * [runtime function] reads a variable into the given data array 1177 * 1178 * @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property") 1179 * @param mixed $data the data array or object to read from 1180 * @param bool $safeRead if true, the function will check whether the index exists to prevent any notices from being output 1181 * @return mixed 1182 */ 1183 public function readVarInto($varstr, $data, $safeRead = false) 1184 { 1185 if ($data === null) { 1186 return null; 1187 } 1188 1189 if (is_array($varstr) === false) { 1190 preg_match_all('#(\[|->|\.)?([^.[\]-]+)\]?#i', $varstr, $m); 1191 } else { 1192 $m = $varstr; 1193 } 1194 unset($varstr); 1195 1196 foreach ($m[1] as $k => $sep) { 1197 if ($sep === '.' || $sep === '[' || $sep === '') { 1198 if ((is_array($data) || $data instanceof ArrayAccess) && ($safeRead === false || isset($data[$m[2][$k]]))) { 1199 $data = $data[$m[2][$k]]; 1200 } else { 1201 return null; 1202 } 1203 } else { 1204 if (is_object($data) && ($safeRead === false || isset($data->$m[2][$k]))) { 1205 $data = $data->$m[2][$k]; 1206 } else { 1207 return null; 1208 } 1209 } 1210 } 1211 1212 return $data; 1213 } 1214 1215 /** 1216 * [runtime function] reads a variable into the parent scope 1217 * 1218 * @param int $parentLevels the amount of parent levels to go from the current scope 1219 * @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property") 1220 * @return mixed 1221 */ 1222 public function readParentVar($parentLevels, $varstr = null) 1223 { 1224 $tree = $this->scopeTree; 1225 $cur = $this->data; 1226 1227 while ($parentLevels--!==0) { 1228 array_pop($tree); 1229 } 1230 1231 while (($i = array_shift($tree)) !== null) { 1232 if (is_object($cur)) { 1233 $cur = $cur->$i; 1234 } else { 1235 $cur = $cur[$i]; 1236 } 1237 } 1238 1239 if ($varstr!==null) { 1240 return $this->readVarInto($varstr, $cur); 1241 } else { 1242 return $cur; 1243 } 1244 } 1245 1246 /** 1247 * [runtime function] reads a variable into the current scope 1248 * 1249 * @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property") 1250 * @return mixed 1251 */ 1252 public function readVar($varstr) 1253 { 1254 if (is_array($varstr)===true) { 1255 $m = $varstr; 1256 unset($varstr); 1257 } else { 1258 if (strstr($varstr, '.') === false && strstr($varstr, '[') === false && strstr($varstr, '->') === false) { 1259 if ($varstr === 'dwoo') { 1260 return $this->globals; 1261 } elseif ($varstr === '__' || $varstr === '_root' ) { 1262 return $this->data; 1263 $varstr = substr($varstr, 6); 1264 } elseif ($varstr === '_' || $varstr === '_parent') { 1265 $varstr = '.'.$varstr; 1266 $tree = $this->scopeTree; 1267 $cur = $this->data; 1268 array_pop($tree); 1269 1270 while (($i = array_shift($tree)) !== null) { 1271 if (is_object($cur)) { 1272 $cur = $cur->$i; 1273 } else { 1274 $cur = $cur[$i]; 1275 } 1276 } 1277 1278 return $cur; 1279 } 1280 1281 $cur = $this->scope; 1282 1283 if (isset($cur[$varstr])) { 1284 return $cur[$varstr]; 1285 } else { 1286 return null; 1287 } 1288 } 1289 1290 if (substr($varstr, 0, 1) === '.') { 1291 $varstr = 'dwoo'.$varstr; 1292 } 1293 1294 preg_match_all('#(\[|->|\.)?([^.[\]-]+)\]?#i', $varstr, $m); 1295 } 1296 1297 $i = $m[2][0]; 1298 if ($i === 'dwoo') { 1299 $cur = $this->globals; 1300 array_shift($m[2]); 1301 array_shift($m[1]); 1302 switch ($m[2][0]) { 1303 1304 case 'get': 1305 $cur = $_GET; 1306 break; 1307 case 'post': 1308 $cur = $_POST; 1309 break; 1310 case 'session': 1311 $cur = $_SESSION; 1312 break; 1313 case 'cookies': 1314 case 'cookie': 1315 $cur = $_COOKIE; 1316 break; 1317 case 'server': 1318 $cur = $_SERVER; 1319 break; 1320 case 'env': 1321 $cur = $_ENV; 1322 break; 1323 case 'request': 1324 $cur = $_REQUEST; 1325 break; 1326 case 'const': 1327 array_shift($m[2]); 1328 if (defined($m[2][0])) { 1329 return constant($m[2][0]); 1330 } else { 1331 return null; 1332 } 1333 1334 } 1335 if ($cur !== $this->globals) { 1336 array_shift($m[2]); 1337 array_shift($m[1]); 1338 } 1339 } elseif ($i === '__' || $i === '_root') { 1340 $cur = $this->data; 1341 array_shift($m[2]); 1342 array_shift($m[1]); 1343 } elseif ($i === '_' || $i === '_parent') { 1344 $tree = $this->scopeTree; 1345 $cur = $this->data; 1346 1347 while (true) { 1348 array_pop($tree); 1349 array_shift($m[2]); 1350 array_shift($m[1]); 1351 if (current($m[2]) === '_' || current($m[2]) === '_parent') { 1352 continue; 1353 } 1354 1355 while (($i = array_shift($tree)) !== null) { 1356 if (is_object($cur)) { 1357 $cur = $cur->$i; 1358 } else { 1359 $cur = $cur[$i]; 1360 } 1361 } 1362 break; 1363 } 1364 } else { 1365 $cur = $this->scope; 1366 } 1367 1368 foreach ($m[1] as $k => $sep) { 1369 if ($sep === '.' || $sep === '[' || $sep === '') { 1370 if ((is_array($cur) || $cur instanceof ArrayAccess) && isset($cur[$m[2][$k]])) { 1371 $cur = $cur[$m[2][$k]]; 1372 } else { 1373 return null; 1374 } 1375 } elseif ($sep === '->') { 1376 if (is_object($cur)) { 1377 $cur = $cur->$m[2][$k]; 1378 } else { 1379 return null; 1380 } 1381 } else { 1382 return null; 1383 } 1384 } 1385 1386 return $cur; 1387 } 1388 1389 /** 1390 * [runtime function] assign the value to the given variable 1391 * 1392 * @param mixed $value the value to assign 1393 * @param string $scope the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property") 1394 * @return bool true if assigned correctly or false if a problem occured while parsing the var string 1395 */ 1396 public function assignInScope($value, $scope) 1397 { 1398 $tree =& $this->scopeTree; 1399 $data =& $this->data; 1400 1401 if (!is_string($scope)) { 1402 return $this->triggerError('Assignments must be done into strings, ('.gettype($scope).') '.var_export($scope, true).' given', E_USER_ERROR); 1403 } 1404 if (strstr($scope, '.') === false && strstr($scope, '->') === false) { 1405 $this->scope[$scope] = $value; 1406 } else { 1407 // TODO handle _root/_parent scopes ? 1408 preg_match_all('#(\[|->|\.)?([^.[\]-]+)\]?#i', $scope, $m); 1409 1410 $cur =& $this->scope; 1411 $last = array(array_pop($m[1]), array_pop($m[2])); 1412 1413 foreach ($m[1] as $k => $sep) { 1414 if ($sep === '.' || $sep === '[' || $sep === '') { 1415 if (is_array($cur) === false) { 1416 $cur = array(); 1417 } 1418 $cur =& $cur[$m[2][$k]]; 1419 } elseif ($sep === '->') { 1420 if (is_object($cur) === false) { 1421 $cur = new stdClass; 1422 } 1423 $cur =& $cur->$m[2][$k]; 1424 } else { 1425 return false; 1426 } 1427 } 1428 1429 if ($last[0] === '.' || $last[0] === '[' || $last[0] === '') { 1430 if (is_array($cur) === false) { 1431 $cur = array(); 1432 } 1433 $cur[$last[1]] = $value; 1434 } elseif ($last[0] === '->') { 1435 if (is_object($cur) === false) { 1436 $cur = new stdClass; 1437 } 1438 $cur->$last[1] = $value; 1439 } else { 1440 return false; 1441 } 1442 } 1443 } 1444 1445 /** 1446 * [runtime function] sets the scope to the given scope string or array 1447 * 1448 * @param mixed $scope a string i.e. "level1.level2" or an array i.e. array("level1", "level2") 1449 * @param bool $absolute if true, the scope is set from the top level scope and not from the current scope 1450 * @return array the current scope tree 1451 */ 1452 public function setScope($scope, $absolute = false) 1453 { 1454 $old = $this->scopeTree; 1455 1456 if (is_string($scope)===true) { 1457 $scope = explode('.', $scope); 1458 } 1459 1460 if ($absolute===true) { 1461 $this->scope =& $this->data; 1462 $this->scopeTree = array(); 1463 } 1464 1465 while (($bit = array_shift($scope)) !== null) { 1466 if ($bit === '_' || $bit === '_parent') { 1467 array_pop($this->scopeTree); 1468 $this->scope =& $this->data; 1469 $cnt = count($this->scopeTree); 1470 for ($i=0;$i<$cnt;$i++) 1471 $this->scope =& $this->scope[$this->scopeTree[$i]]; 1472 } elseif ($bit === '__' || $bit === '_root') { 1473 $this->scope =& $this->data; 1474 $this->scopeTree = array(); 1475 } elseif (isset($this->scope[$bit])) { 1476 $this->scope =& $this->scope[$bit]; 1477 $this->scopeTree[] = $bit; 1478 } else { 1479 unset($this->scope); 1480 $this->scope = null; 1481 } 1482 } 1483 1484 return $old; 1485 } 1486 1487 /** 1488 * [runtime function] returns the entire data array 1489 * 1490 * @return array 1491 */ 1492 public function getData() 1493 { 1494 return $this->data; 1495 } 1496 1497 /** 1498 * [runtime function] returns a reference to the current scope 1499 * 1500 * @return &mixed 1501 */ 1502 public function &getScope() 1503 { 1504 return $this->scope; 1505 } 1506 1507 /** 1508 * Redirects all calls to unexisting to plugin proxy. 1509 * 1510 * @param string Method name 1511 * @param array List of arguments 1512 * @return mixed 1513 */ 1514 public function __call($method, $args) { 1515 return call_user_func_array($this->getPluginProxy()->getCallback($method), $args); 1516 } 1517} 1518