1<?php 2/* 3 * e107 website system 4 * 5 * Copyright (C) 2008-2012 e107 Inc (e107.org) 6 * Released under the terms and conditions of the 7 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) 8 * 9*/ 10 11 12/** 13 * Class e_url 14 * New v2.1.6 15 */ 16class e_url 17{ 18 19 private static $_instance; 20 21 private $_request = null; 22 23 private $_config = array(); 24 25 private $_include = null; 26 27 private $_rootnamespace = null; 28 29 private $_alias = array(); 30 31 private $_legacy = array(); 32 33 private $_legacyAliases = array(); 34 35 36 /** 37 * e_url constructor. 38 */ 39 function __construct() 40 { 41 $this->_request = (e_HTTP === '/') ? ltrim(e_REQUEST_URI,'/') : str_replace(e_HTTP,'', e_REQUEST_URI) ; 42 43 $this->_config = e107::getUrlConfig(); 44 $this->_alias = e107::getPref('e_url_alias'); 45 46 $this->_rootnamespace = e107::getPref('url_main_module'); 47 $this->_legacy = e107::getPref('url_config'); 48 $this->_legacyAliases = e107::getPref('url_aliases'); 49 50 51 $this->setRootNamespace(); 52 53 } 54 55 /** 56 * Detect older e_url system. 57 * @return bool 58 */ 59 private function isLegacy() 60 { 61 62 $arr = (!empty($this->_legacyAliases[e_LAN])) ? array_merge($this->_legacy,$this->_legacyAliases[e_LAN]) : $this->_legacy; 63 64 $list = array_keys($arr); 65 66 foreach($list as $leg) 67 { 68 if(strpos($this->_request,$leg.'/') === 0 || $this->_request === $leg) 69 { 70 return true; 71 } 72 73 } 74 75 return false; 76 } 77 78 79 /** 80 * @return string 81 */ 82 public function getInclude() 83 { 84 return $this->_include; 85 } 86 87 88 89 private function setRootNamespace() 90 { 91 92 $plugs = array_keys($this->_config); 93 94 if(!empty($this->_rootnamespace) && in_array($this->_rootnamespace,$plugs)) // Move rootnamespace check to the end of the list. 95 { 96 $v = $this->_config[$this->_rootnamespace]; 97 unset($this->_config[$this->_rootnamespace]); 98 $this->_config[$this->_rootnamespace] = $v; 99 } 100 101 } 102 103 104 public function run() 105 { 106 $pref = e107::getPref(); 107 $tp = e107::getParser(); 108 109 if(empty($this->_config) || empty($this->_request) || $this->_request === 'index.php' || $this->isLegacy() === true) 110 { 111 return false; 112 } 113 114 $replaceAlias = array('{alias}\/?','{alias}/?','{alias}\/','{alias}/',); 115 116 foreach($this->_config as $plug=>$cfg) 117 { 118 if(empty($pref['e_url_list'][$plug])) // disabled. 119 { 120 e107::getDebug()->log('e_URL for <b>'.$plug.'</b> is disabled.'); 121 continue; 122 } 123 124 foreach($cfg as $k=>$v) 125 { 126 127 if(empty($v['regex'])) 128 { 129 // e107::getMessage()->addDebug("Skipping empty regex: <b>".$k."</b>"); 130 continue; 131 } 132 133 134 if(!empty($v['alias'])) 135 { 136 $alias = (!empty($this->_alias[e_LAN][$plug][$k])) ? $this->_alias[e_LAN][$plug][$k] : $v['alias']; 137 // e107::getMessage()->addDebug("e_url alias found: <b>".$alias."</b>"); 138 if(!empty($this->_rootnamespace) && $this->_rootnamespace === $plug) 139 { 140 $v['regex'] = str_replace($replaceAlias, '', $v['regex']); 141 } 142 else 143 { 144 145 $v['regex'] = str_replace('{alias}', $alias, $v['regex']); 146 } 147 } 148 149 150 $regex = '#'.$v['regex'].'#'; 151 152 if(empty($v['redirect'])) 153 { 154 continue; 155 } 156 157 158 $newLocation = preg_replace($regex, $v['redirect'], $this->_request); 159 160 if($newLocation != $this->_request) 161 { 162 $redirect = e107::getParser()->replaceConstants($newLocation); 163 list($file,$query) = explode("?", $redirect,2); 164 165 $get = array(); 166 if(!empty($query)) 167 { 168 // issue #3171 fix double ampersand in case of wrong query definition 169 $query = str_replace('&&', '&', $query); 170 parse_str($query,$get); 171 } 172 173 174 foreach($get as $gk=>$gv) 175 { 176 $_GET[$gk] = $gv; 177 } 178 179 e107::getDebug()->log('e_URL in <b>'.$plug.'</b> with key: <b>'.$k.'</b> matched <b>'.$v['regex'].'</b> and included: <b>'.$file.'</b> with $_GET: '.print_a($_GET,true),1); 180 181 if(file_exists($file)) 182 { 183 define('e_CURRENT_PLUGIN', $plug); 184 define('e_QUERY', str_replace('&&', '&', $query)); // do not add to e107_class.php 185 define('e_URL_LEGACY', $redirect); 186 if(!defined('e_PAGE')) 187 { 188 define('e_PAGE', basename($file)); 189 } 190 $this->_include= $file; 191 return true; 192 // exit; 193 } 194 elseif(getperms('0')) 195 { 196 197 echo "<div class='alert alert-warning'>"; 198 echo "<h3>SEF Debug Info</h3>"; 199 echo "File missing: ".$file; 200 echo "<br />Matched key: <b>".$k."</b>"; 201 print_a($v); 202 echo "</div>"; 203 204 } 205 206 } 207 } 208 209 } 210 211 212 213 214 } 215 216 217 /** 218 * Singleton implementation 219 * @return e_url 220 */ 221 public static function instance() 222 { 223 if(null == self::$_instance) 224 { 225 self::$_instance = new self(); 226 } 227 return self::$_instance; 228 } 229 230 231 232} 233 234 235 236 237 238/** 239 * e107 Front controller 240 */ 241class eFront 242{ 243 /** 244 * Singleton instance 245 * @var eFront 246 */ 247 private static $_instance; 248 249 /** 250 * @var eDispatcher 251 */ 252 protected $_dispatcher; 253 254 /** 255 * @var eRequest 256 */ 257 protected $_request; 258 259 /** 260 * @var eRouter 261 */ 262 protected $_router; 263 264 265 protected $_response; 266 267 /** 268 * @var string path to file to include - the old deprecated way of delivering content 269 */ 270 protected static $_legacy = ''; 271 272 /** 273 * Constructor 274 */ 275 private function __construct() 276 { 277 } 278 279 /** 280 * Cloning not allowed 281 * 282 */ 283 private function __clone() 284 { 285 } 286 287 /** 288 * Singleton implementation 289 * @return eFront 290 */ 291 public static function instance() 292 { 293 if(null == self::$_instance) 294 { 295 self::$_instance = new self(); 296 } 297 return self::$_instance; 298 } 299 300 /** 301 * Dispatch 302 */ 303 public function dispatch(eRequest $request = null, eResponse $response = null, eDispatcher $dispatcher = null) 304 { 305 if(null === $request) 306 { 307 if(null === $this->getRequest()) 308 { 309 $request = new eRequest(); 310 $this->setRequest($request); 311 } 312 else $request = $this->getRequest(); 313 } 314 elseif(null === $this->getRequest()) $this->setRequest($request); 315 316 if(null === $response) 317 { 318 if(null === $this->getResponse()) 319 { 320 $response = new eResponse(); 321 $this->setResponse($response); 322 } 323 else $response = $this->getResponse(); 324 } 325 elseif(null === $this->getRequest()) $this->setRequest($request); 326 327 328 if(null === $dispatcher) 329 { 330 if(null === $this->getDispatcher()) 331 { 332 $dispatcher = new eDispatcher(); 333 $this->setDispatcher($dispatcher); 334 } 335 else $dispatcher = $this->getDispatcher(); 336 } 337 elseif(null === $this->getDispatcher()) $this->setDispatcher($dispatcher); 338 339 340 // set dispatched status true, required for checkLegacy() 341 $request->setDispatched(true); 342 343 $router = $this->getRouter(); 344 345 // If current request not already routed outside the dispatch method, route it 346 if(!$request->routed) $router->route($request); 347 348 $c = 0; 349 // dispatch loop 350 do 351 { 352 $c++; 353 if($c > 100) 354 { 355 throw new eException("Too much dispatch loops", 1); 356 } 357 358 // dispatched status true on first loop 359 $router->checkLegacy($request); 360 361 // dispatched by default - don't allow legacy to alter dispatch status 362 $request->setDispatched(true); 363 364 // legacy mod - return control to the bootstrap 365 if(self::isLegacy()) 366 { 367 return; 368 } 369 370 // for the good players - dispatch loop - no more BC! 371 try 372 { 373 $dispatcher->dispatch($request, $response); 374 } 375 catch(eException $e) 376 { 377 echo $request->getRoute().' - '.$e->getMessage(); 378 exit; 379 } 380 381 382 } while (!$request->isDispatched()); 383 } 384 385 /** 386 * Init all objects required for request dispatching 387 * @return eFront 388 */ 389 public function init() 390 { 391 $request = new eRequest(); 392 $this->setRequest($request); 393 394 $dispatcher = new eDispatcher(); 395 $this->setDispatcher($dispatcher); 396 397 $router = new eRouter(); 398 $this->setRouter($router); 399 400 /** @var eResponse $response */ 401 $response = e107::getSingleton('eResponse'); 402 $this->setResponse($response); 403 404 return $this; 405 } 406 407 /** 408 * Dispatch 409 * @param string|eRequest $route 410 */ 411 public function run($route = null) 412 { 413 if($route) 414 { 415 if(is_object($route) && ($route instanceof eRequest)) $this->setRequest($route); 416 elseif(null !== $route && null !== $this->getRequest()) $this->getRequest()->setRoute($route); 417 } 418 try 419 { 420 $this->dispatch(); 421 } 422 catch(eException $e) 423 { 424 echo $e->getMessage(); 425 exit; 426 } 427 428 } 429 430 /** 431 * Application instance (e107 class) 432 * @return e107 433 */ 434 public static function app() 435 { 436 return e107::getInstance(); 437 } 438 439 /** 440 * Get dispatcher instance 441 * @return eDispatcher 442 */ 443 public function getDispatcher() 444 { 445 return $this->_dispatcher; 446 } 447 448 /** 449 * Set dispatcher 450 * @param eDispatcher $dispatcher 451 * @return eFront 452 */ 453 public function setDispatcher(eDispatcher $dispatcher) 454 { 455 $this->_dispatcher = $dispatcher; 456 return $this; 457 } 458 459 /** 460 * Get request instance 461 * @return eRequest 462 */ 463 public function getRequest() 464 { 465 return $this->_request; 466 } 467 468 /** 469 * Set request 470 * @param eRequest $request 471 * @return eFront 472 */ 473 public function setRequest(eRequest $request) 474 { 475 $this->_request = $request; 476 return $this; 477 } 478 479 /** 480 * Get response instance 481 * @return eResponse 482 */ 483 public function getResponse() 484 { 485 return $this->_response; 486 } 487 488 /** 489 * Set response 490 * @param eResponse $response 491 * @return eFront 492 */ 493 public function setResponse(eResponse $response) 494 { 495 $this->_response = $response; 496 return $this; 497 } 498 499 /** 500 * Get router instance 501 * @return eRouter 502 */ 503 public function getRouter() 504 { 505 return $this->_router; 506 } 507 508 /** 509 * Set router instance 510 * @return eFront 511 */ 512 public function setRouter(eRouter $router) 513 { 514 $this->_router = $router; 515 return $this; 516 } 517 518 /** 519 * Set/get legacy status of the current request 520 * @param boolean $status 521 * @return boolean 522 */ 523 public static function isLegacy($status = null) 524 { 525 if(null !== $status) 526 { 527 if(!empty($status[0]) && ($status[0] === '{')) 528 { 529 $status = e107::getParser()->replaceConstants($status); 530 } 531 self::$_legacy = $status; 532 } 533 return self::$_legacy; 534 } 535} 536 537/** 538 * e107 Dispatcher 539 * It decides how to dispatch the request. 540 */ 541class eDispatcher 542{ 543 protected static $_configObjects = array(); 544 545 public function dispatch(eRequest $request = null, eResponse $response = null) 546 { 547 $controllerName = $request->getControllerName(); 548 $moduleName = $request->getModuleName(); 549 $className = $this->isDispatchable($request, false); 550 551 // dispatch based on rule settings 552 if(!$className) 553 { 554 if($controllerName == 'index') // v2.x upgrade has not been run yet. 555 { 556 e107::getRedirect()->redirect(e_ADMIN."admin.php"); 557 } 558 559 throw new eException("Invalid controller '".$controllerName."'"); 560 } 561 562 $controller = new $className($request, $response); 563 if(!($controller instanceof eController)) 564 { 565 throw new eException("Controller $controller is not an instance of eController"); 566 } 567 568 $actionName = $request->getActionMethodName(); 569 570 ob_start(); 571 572 $controller->dispatch($actionName); 573 574 $content = ob_get_contents(); 575 ob_end_clean(); 576 577 $response->appendBody($content); 578 unset($controller); 579 } 580 581 /** 582 * Get path to the e_url handler 583 * @param string $module 584 * @param string $location plugin|core|override 585 * @param boolean $sc 586 * @return string path 587 */ 588 public static function getConfigPath($module, $location, $sc = false) 589 { 590 $tmp = explode('/', $location); 591 $custom = ''; 592 $location = $tmp[0]; 593 if(isset($tmp[1]) && !empty($tmp[1])) 594 { 595 $custom = $tmp[1].'_'; 596 } 597 unset($tmp); 598 if($module !== '*') $module .= '/'; 599 600 switch ($location) 601 { 602 case 'plugin': 603 //if($custom) $custom = 'url/'.$custom; 604 if(!defined('e_CURRENT_PLUGIN')) 605 { 606 define('e_CURRENT_PLUGIN', rtrim($module,'/')); // TODO Move to a better location. 607 } 608 return $sc ? '{e_PLUGIN}'.$module.'url/'.$custom.'url.php' : e_PLUGIN.$module.'url/'.$custom.'url.php'; 609 break; 610 611 case 'core': 612 if($module === '*') return $sc ? '{e_CORE}url/' : e_CORE.'url/'; 613 return $sc ? '{e_CORE}url/'.$module.$custom.'url.php' : e_CORE.'url/'.$module.$custom.'url.php'; 614 break; 615 616 case 'override': 617 if($module === '*') return $sc ? '{e_CORE}override/url/' : e_CORE.'override/url/' ; 618 return $sc ? '{e_CORE}override/url/'.$module.$custom.'url.php' : e_CORE.'override/url/'.$module.$custom.'url.php' ; 619 break; 620 621 default: 622 return null; 623 break; 624 } 625 } 626 627 /** 628 * Get path to url configuration subfolders 629 * @param string $module 630 * @param string $location plugin|core|override 631 * @param boolean $sc 632 * @return string path 633 */ 634 public static function getConfigLocationPath($module, $location, $sc = false) 635 { 636 switch ($location) 637 { 638 case 'plugin': 639 return $sc ? '{e_PLUGIN}'.$module.'/url/' : e_PLUGIN.$module.'/url/'; 640 break; 641 642 case 'core': 643 return $sc ? '{e_CORE}url/'.$module.'/' : e_CORE.'url/'.$module.'/'; 644 break; 645 646 case 'override': 647 return $sc ? '{e_CORE}override/url/'.$module.'/' : e_CORE.'override/url/'.$module.'/'; 648 break; 649 650 default: 651 return null; 652 break; 653 } 654 } 655 656 /** 657 * Get dispatch system path 658 * @param string $location plugin|core|override 659 * @param string $plugin required only when $location is plugin 660 * @param boolean $sc 661 * @return string path 662 */ 663 public static function getDispatchLocationPath($location, $plugin = null, $sc = false) 664 { 665 switch ($location) 666 { 667 case 'plugin': 668 if(!$plugin) return null; 669 return $sc ? '{e_PLUGIN}'.$plugin.'/controllers/' : e_PLUGIN.$plugin.'/controllers/'; 670 break; 671 672 case 'core': 673 return $sc ? '{e_CORE}controllers/' : e_CORE.'controllers/'; 674 break; 675 676 case 'override': 677 return $sc ? '{e_CORE}override/controllers/' : e_CORE.'override/controllers/'; 678 break; 679 680 default: 681 return null; 682 break; 683 } 684 } 685 686 /** 687 * Get full dispatch system path 688 * @param string $module 689 * @param string $location plugin|core|override 690 * @param boolean $sc 691 * @return string path 692 */ 693 public static function getDispatchPath($module, $location, $sc = false) 694 { 695 switch ($location) 696 { 697 case 'plugin': 698 return $sc ? '{e_PLUGIN}'.$module.'/controllers/' : e_PLUGIN.$module.'/controllers/'; 699 break; 700 701 case 'core': 702 return $sc ? '{e_CORE}controllers/'.$module.'/' : e_CORE.'controllers/'.$module.'/'; 703 break; 704 705 case 'override': 706 return $sc ? '{e_CORE}override/controllers/'.$module.'/' : e_CORE.'override/controllers/'.$module.'/'; 707 break; 708 709 default: 710 return null; 711 break; 712 } 713 } 714 715 /** 716 * Get include path to a given module/controller 717 * 718 * @param string $module valid module name 719 * @param string $controller controller name 720 * @param string $location core|plugin|override 721 * @param boolean $sc return relative (false) OR shortcode (true) path 722 * @return string controller path 723 */ 724 public static function getControllerPath($module, $controller, $location = null, $sc = false) 725 { 726 if(null === $location) $location = self::getDispatchLocation($module); 727 728 return ($location ? self::getDispatchPath($module, $location, $sc).$controller.'.php': null); 729 } 730 731 /** 732 * Get class name of a given module/controller 733 * 734 * @param string $module valid module name 735 * @param string $controllerName controller name 736 * @param string $location core|plugin|override 737 * @return string controller path 738 */ 739 public static function getControllerClass($module, $controllerName, $location = null) 740 { 741 if(null === $location) $location = self::getDispatchLocation($module); 742 743 return ($location ? $location.'_'.$module.'_'.$controllerName.'_controller' : null); 744 } 745 746 747 /** 748 * Get controller object 749 * 750 * @param eRequest $request 751 * @param boolean $checkOverride whether to check the override location 752 * @return eController null if not dispatchable 753 */ 754 public function getController(eRequest $request, $checkOverride = true) 755 { 756 $class_name = $this->isDispatchable($request, true, $checkOverride); 757 if(!$class_name) return null; 758 759 return new $class_name(); 760 } 761 762 /** 763 * Check if given module/controller is dispatchable 764 * @param string $module valid module name 765 * @param string $controllerName controller name 766 * @param string $location core|plugin|override 767 * @param boolean $checkOverride whether to check the override location 768 * @return string class name OR false if not dispatchable 769 */ 770 public function isDispatchableModule($module, $controllerName, $location, $checkOverride = false) 771 { 772 if($checkOverride || $location == 'override') 773 { 774 $path = self::getControllerPath($module, $controllerName, 'override', false); 775 776 $class_name = self::getControllerClass($module, $controllerName, 'override'); 777 if($class_name && !class_exists($class_name, false) && is_readable($path)) include_once($path); 778 779 if($class_name && class_exists($class_name, false)) return $class_name; 780 } 781 782 // fallback to original dispatch location if any 783 if($location === 'override') 784 { 785 // check for real location 786 if(($location = eDispatcher::getModuleRealLocation($module)) === null) return false; 787 } 788 789 if($location !== 'override') 790 { 791 $path = self::getControllerPath($module, $controllerName, $location, false); 792 793 $class_name = self::getControllerClass($module, $controllerName, $location); 794 if(!$class_name) return false; 795 796 if(!class_exists($class_name, false) && is_readable($path)) include_once($path); 797 798 if(class_exists($class_name, false)) return $class_name; 799 } 800 return false; 801 } 802 803 /** 804 * Automated version of self::isDispatchableModule() 805 * @param eRequest $request 806 * @param boolean $checkReflection deep check - proper subclassing, action 807 * @param boolean $checkOverride try override controller folder first 808 * @return mixed class name OR false if not dispatchable 809 */ 810 public function isDispatchable(eRequest $request, $checkReflection = false, $checkOverride = true) 811 { 812 $location = self::getDispatchLocation($request->getModuleName()); 813 814 $controllerName = $request->getControllerName(); 815 $moduleName = $request->getModuleName(); 816 $className = false; 817 818 // dispatch based on url_config preference value, if config location is override and there is no 819 // override controller, additional check against real controller location will be made 820 if($location) 821 { 822 $className = $this->isDispatchableModule($moduleName, $controllerName, $location, $checkOverride); 823 } 824 //else 825 //{ 826 # Disable plugin check for routes with no config info - prevent calling of non-installed plugins 827 # We may allow this for plugins which don't have plugin.xml in the future 828 // $className = $this->isDispatchableModule($moduleName, $controllerName, 'plugin', $checkOverride); 829 // if(!$className) 830 //$className = $this->isDispatchableModule($moduleName, $controllerName, 'core', $checkOverride); 831 //} 832 833 if(empty($className)) return false; 834 elseif(!$checkReflection) return $className; 835 836 $rfl = new ReflectionClass($className); 837 $method = $request->getActionMethodName(); 838 if($rfl->isSubclassOf('eController') && $rfl->hasMethod($method) && $rfl->getMethod($method)->isPublic() && !$rfl->getMethod($method)->isStatic()) 839 return $className; 840 841 return false; 842 } 843 844 /** 845 * Get class name of a given module config 846 * 847 * @param string $module valid module name 848 * @param string $location core|plugin|override[/custom] 849 * @return string controller path 850 */ 851 public static function getConfigClassName($module, $location) 852 { 853 $tmp = explode('/', $location); 854 $custom = ''; 855 $location = $tmp[0]; 856 if(isset($tmp[1]) && !empty($tmp[1])) 857 { 858 $custom = $tmp[1].'_'; 859 } 860 unset($tmp); 861 $module .= '_'; 862 863 // we need to prepend location to avoid namespace colisions 864 return $location.'_'.$module.$custom.'url'; 865 } 866 867 /** 868 * Get config object for a module 869 * 870 * @param string $module valid module name 871 * @param string $location core|plugin|override[/custom] 872 * @return eUrlConfig 873 */ 874 public static function getConfigObject($module, $location = null) 875 { 876 if(null === $location) 877 { 878 $location = self::getModuleConfigLocation($module); 879 if(!$location) return null; 880 } 881 $reg = $module.'/'.$location; 882 if(isset(self::$_configObjects[$reg])) return self::$_configObjects[$reg]; 883 $className = self::getConfigClassName($module, $location); 884 if(!class_exists($className, false)) 885 { 886 $path = self::getConfigPath($module, $location, false); 887 if(!is_readable($path)) return null; 888 include_once($path); 889 890 if(!class_exists($className, false)) return null; 891 } 892 893 /** @var eUrlConfig $obj */ 894 $obj = new $className(); 895 $obj->init(); 896 self::$_configObjects[$reg] = $obj; 897 $obj = null; 898 899 return self::$_configObjects[$reg]; 900 } 901 902 /** 903 * Auto discover module location from stored in core prefs data 904 * @param string $module 905 * @return mixed 906 */ 907 public static function getModuleConfigLocation($module) 908 { 909 //retrieve from config prefs 910 return e107::findPref('url_config/'.$module, ''); 911 } 912 913 /** 914 * Auto discover module location from stored in core prefs data 915 * @param string $module 916 * @return mixed|null|string 917 */ 918 public static function getDispatchLocation($module) 919 { 920 //retrieve from prefs 921 $location = self::getModuleConfigLocation($module); 922 if(!$location) return null; 923 924 if(($pos = strpos($location, '/'))) //can't be 0 925 { 926 return substr($location, 0, $pos); 927 } 928 return $location; 929 } 930 931 932 /** 933 * Auto discover module real location (and not currently set from url adminsitration) from stored in core prefs data 934 * @param string $module 935 */ 936 public static function getModuleRealLocation($module) 937 { 938 //retrieve from prefs 939 $searchArray = e107::findPref('url_modules'); 940 if(!$searchArray) return null; 941 942 $search = array('core', 'plugin', 'override'); 943 944 foreach ($search as $location) 945 { 946 $_searchArray = vartrue($searchArray[$location], array()); 947 if(in_array($module, $_searchArray)) return $location; 948 } 949 return null; 950 } 951} 952 953/** 954 * URL manager - parse and create URLs based on rules set 955 * Inspired by Yii Framework UrlManager <www.yiiframework.com> 956 */ 957class eRouter 958{ 959 /** 960 * Configuration array containing all available syste routes and route object configuration values 961 * @var array 962 */ 963 protected $_rules = array(); 964 965 /** 966 * List of all system wide available aliases 967 * This includes multi-lingual configurations as well 968 * @var array 969 */ 970 protected $_aliases = array(); 971 972 /** 973 * Cache for rule objects 974 * @var array 975 */ 976 protected $_parsedRules = array(); // array of rule objects 977 978 /** 979 * Global config values per rule set 980 * @var array 981 */ 982 protected $_globalConfig = array(); 983 984 /** 985 * Module name which is used for site main namespace 986 * Example mysite.com/news/News Item => converted to mysite.com/News Item 987 * NOTE: Could be moved to rules config 988 * 989 * @var string 990 */ 991 protected $_mainNsModule = ''; 992 993 /** 994 * Default URL suffix - to be added to end of all urls (e.g. '.html') 995 * This value can be overridden per rule item 996 * NOTE could be moved to rules config only 997 * @var string 998 */ 999 public $urlSuffix = ''; 1000 1001 /** 1002 * @var string GET variable name for route. Defaults to 'route'. 1003 */ 1004 public $routeVar = 'route'; 1005 1006 /** 1007 * @var string 1008 */ 1009 const FORMAT_GET = 'get'; 1010 1011 /** 1012 * @var string 1013 */ 1014 const FORMAT_PATH = 'path'; 1015 1016 /** 1017 * @var string 1018 */ 1019 private $_urlFormat = self::FORMAT_PATH; 1020 1021 /** 1022 * Not found route 1023 * @var string 1024 */ 1025 public $notFoundRoute = 'system/error/notfound'; 1026 1027 protected $_defaultAssembleOptions = array('full' => false, 'amp' => '&', 'equal' => '=', 'encode' => true); 1028 1029 /** 1030 * Not found URL - used when system route not found and 'url_error_redirect' core pref is true 1031 * TODO - user friendly URL ('/system/404') when system config is ready ('/system/404') 1032 * @var string 1033 */ 1034 public $notFoundUrl = 'system/error/404?type=routeError'; 1035 1036 1037 1038 1039 public function __construct() 1040 { 1041 $this->_init(); 1042 } 1043 1044 /** 1045 * Init object 1046 * @return void 1047 */ 1048 protected function _init() 1049 { 1050 // Gather all rules, add-on info, cache, module for main namespace etc 1051 $this->_loadConfig() 1052 ->setAliases(); 1053 // we need config first as setter does some checks if module can be set as main 1054 $this->setMainModule(e107::getPref('url_main_module', '')); 1055 } 1056 1057 /** 1058 * Set module for default namespace 1059 * @param string $module 1060 * @return eRouter 1061 */ 1062 public function setMainModule($module) 1063 { 1064 if(!$module || !$this->isModule($module) || !$this->getConfigValue($module, 'allowMain')) return $this; 1065 $this->_mainNsModule = $module; 1066 return $this; 1067 } 1068 1069 /** 1070 * Get main url namespace module 1071 * @return string 1072 */ 1073 public function getMainModule() 1074 { 1075 return $this->_mainNsModule; 1076 } 1077 1078 /** 1079 * Check if given module is the main module 1080 * @param string $module 1081 * @return boolean 1082 */ 1083 public function isMainModule($module) 1084 { 1085 return ($this->_mainNsModule === $module); 1086 } 1087 1088 1089 /** 1090 * @return string get|path 1091 */ 1092 public function getUrlFormat() 1093 { 1094 return $this->_urlFormat; 1095 } 1096 1097 1098 /** 1099 * Load config and url rules, if not available - build it on the fly 1100 * @return eRouter 1101 */ 1102 protected function _loadConfig() 1103 { 1104 if(!is_readable(e_CACHE_URL.'config.php')) $config = $this->buildGlobalConfig(); 1105 else $config = include(e_CACHE_URL.'config.php'); 1106 1107 if(!$config) $config = array(); 1108 1109 $rules = array(); 1110 1111 foreach ($config as $module => $c) 1112 { 1113 $rules[$module] = $c['rules']; 1114 unset($config[$module]['rules']); 1115 $config[$module] = $config[$module]['config']; 1116 } 1117 $this->_globalConfig = $config; 1118 $this->setRuleSets($rules); 1119 1120 return $this; 1121 } 1122 1123 public static function clearCache() 1124 { 1125 if(file_exists(e_CACHE_URL.'config.php')) 1126 { 1127 @unlink(e_CACHE_URL.'config.php'); 1128 } 1129 } 1130 1131 /** 1132 * Build unified config.php 1133 */ 1134 public function buildGlobalConfig($save = true) 1135 { 1136 $active = e107::getPref('url_config', array()); 1137 1138 $config = array(); 1139 foreach ($active as $module => $location) 1140 { 1141 $_config = array(); 1142 $obj = eDispatcher::getConfigObject($module, $location); 1143 $path = eDispatcher::getConfigPath($module, $location, true); 1144 1145 if(null !== $obj) 1146 { 1147 $_config = $obj->config(); 1148 $_config['config']['configPath'] = $path; 1149 $_config['config']['configClass'] = eDispatcher::getConfigClassName($module, $location); 1150 } 1151 if(!isset($_config['config'])) $_config['config'] = array(); 1152 1153 $_config['config']['location'] = $location; 1154 if(!isset($_config['config']['format']) || !in_array($_config['config']['format'], array(self::FORMAT_GET, self::FORMAT_PATH))) 1155 { 1156 $_config['config']['format'] = $this->getUrlFormat(); 1157 } 1158 1159 if(!isset($_config['rules'])) $_config['rules'] = array(); 1160 1161 foreach ($_config['rules'] as $pattern => $rule) 1162 { 1163 if(!is_array($rule)) 1164 { 1165 $_config['rules'][$pattern] = array($rule); 1166 } 1167 } 1168 1169 $config[$module] = $_config; 1170 } 1171 1172 if($save) 1173 { 1174 $fileContent = '<?php'."\n### Auto-generated - DO NOT EDIT ### \nreturn "; 1175 $fileContent .= trim(var_export($config, true)).';'; 1176 1177 file_put_contents(e_CACHE_URL.'config.php', $fileContent); 1178 } 1179 return $config; 1180 } 1181 1182 /** 1183 * Retrieve config array from a given system path 1184 * @param string $path 1185 * @param string $location core|plugin|override 1186 */ 1187 public static function adminReadConfigs($path, $location = null) 1188 { 1189 $file = e107::getFile(false); 1190 $ret = array(); 1191 1192 $file->mode = 'fname'; 1193 $files = $file->setFileInfo('fname') 1194 ->get_files($path, '^([a-z_]{1,}_)?url\.php$'); 1195 1196 1197 foreach ($files as $file) 1198 { 1199 if(null === $location) 1200 { 1201 $c = eRouter::file2config($file, $location); 1202 if($c) $ret[] = $c; 1203 continue; 1204 } 1205 $ret[] = eRouter::file2config($file, $location); 1206 } 1207 return $ret; 1208 } 1209 1210 /** 1211 * Convert filename to configuration string 1212 * @param string $filename 1213 * @param string $location core|plugin|override 1214 */ 1215 public static function file2config($filename, $location = '') 1216 { 1217 if($filename == 'url.php') return $location; 1218 if($location) $location .= '/'; 1219 return $location.substr($filename, 0, strrpos($filename, '_')); 1220 } 1221 1222 /** 1223 * Detect all available system url modules, used as a map on administration configuration path 1224 * and required (same structure) {@link from eDispatcher::adminBuildConfig()) 1225 * This is a very liberal detection, as it doesn't require config file. 1226 * It goes through both config and dispatch locations and registers directory tree as modules 1227 * The only exception are plugins - if plugin requires install (plugin.xml) and it is not installed, 1228 * it won't be registered 1229 * Another important thing is - core has always higher priority, as plugins are not allowed to 1230 * directly override core modules. At this moment, core modules could be overloaded only via override configs (e107_core/override/url/) 1231 * and controllers (e107_core/override/controllers) 1232 * This array is stored as url_modules core preference 1233 * 1234 * @param string $type possible values are all|plugin|core|override 1235 * @return array available system url modules stored as url_modules core preference 1236 */ 1237 public static function adminReadModules($type = 'all') 1238 { 1239 $f = e107::getFile(); 1240 $ret = array('core' => array(), 'plugin' => array(), 'override' => array()); 1241 1242 if($type == 'all' || $type = 'core') 1243 { 1244 $location = eDispatcher::getDispatchLocationPath('core'); 1245 // search for controllers first 1246 $ret['core'] = $f->get_dirs($location); 1247 1248 // merge with configs 1249 $configArray = $f->get_dirs(eDispatcher::getConfigPath('*', 'core')); 1250 foreach ($configArray as $config) 1251 { 1252 if(!in_array($config, $ret['core'])) 1253 { 1254 $ret['core'][] = $config; 1255 } 1256 } 1257 sort($ret['core']); 1258 } 1259 1260 if($type == 'all' || $type = 'plugin') 1261 { 1262 $plugins = $f->get_dirs(e_PLUGIN); 1263 foreach ($plugins as $plugin) 1264 { 1265 // DON'T ALLOW PLUGINS TO OVERRIDE CORE!!! 1266 // This will be possible in the future under some other, more controllable form 1267 if(in_array($plugin, $ret['core'])) continue; 1268 1269 $location = eDispatcher::getDispatchLocationPath('plugin', $plugin); 1270 $config = eDispatcher::getConfigPath($plugin, 'plugin'); 1271 1272 if(e107::isInstalled($plugin)) 1273 { 1274 if(is_dir($location) || is_readable($config)) 1275 { 1276 $ret['plugin'][] = $plugin; 1277 } 1278 continue; 1279 } 1280 1281 // Register only those who don't need install and may be dispatchable 1282 if((!is_readable(e_PLUGIN.$plugin.'/plugin.php') && !is_readable(e_PLUGIN.$plugin.'/plugin.xml'))) 1283 { 1284 if(is_dir($location) || is_readable($config)) 1285 { 1286 $ret['plugin'][] = $plugin; 1287 } 1288 } 1289 } 1290 sort($ret['plugin']); 1291 } 1292 1293 if($type == 'all' || $type = 'override') 1294 { 1295 // search for controllers first 1296 $location = eDispatcher::getDispatchLocationPath('override'); 1297 $ret['override'] = $f->get_dirs($location); 1298 1299 // merge with configs 1300 $configArray = $f->get_dirs(eDispatcher::getConfigPath('*', 'override')); 1301 foreach ($configArray as $config) 1302 { 1303 if(!in_array($config, $ret['override'])) 1304 { 1305 $ret['override'][] = $config; 1306 } 1307 } 1308 sort($ret['override']); 1309 1310 // remove not installed plugin locations, possible only for 'all' type 1311 if($type == 'all') 1312 { 1313 foreach ($ret['override'] as $i => $l) 1314 { 1315 // it's a plugin override, but not listed in current plugin array - remove 1316 if(in_array($l, $plugins) && !in_array($l, $ret['plugin'])) 1317 { 1318 unset($ret['override'][$i]); 1319 } 1320 } 1321 } 1322 } 1323 1324 return $ret; 1325 } 1326 1327 /** 1328 * Rebuild configuration array, stored as url_config core preference 1329 * More strict detection compared to {@link eDispatcher::adminReadModules()} 1330 * Current flat array containing config locations per module are rebuilt so that new 1331 * modules are registered, missing modules - removed. Additionally fallback to the default location 1332 * is done if current user defined location is not readable 1333 * @see eDispatcher::adminReadModules() 1334 * @param array current configuration array (url_config core preference like) 1335 * @param array available URL modules as detected by {@link eDispatcher::adminReadModules()} and stored as url_modules core preference value 1336 * @return array new url_config array 1337 */ 1338 public static function adminBuildConfig($current, $adminReadModules = null) 1339 { 1340 if(null === $adminReadModules) $adminReadModules = self::adminReadModules(); 1341 1342 $ret = array(); 1343 $all = array_unique(array_merge($adminReadModules['core'], $adminReadModules['plugin'], $adminReadModules['override'])); 1344 foreach ($all as $module) 1345 { 1346 if(isset($current[$module])) 1347 { 1348 // current contains custom (readable) config location e.g. news => core/rewrite 1349 if(strpos($current[$module], '/') !== false && is_readable(eDispatcher::getConfigPath($module, $current[$module]))) 1350 { 1351 $ret[$module] = $current[$module]; 1352 continue; 1353 } 1354 1355 // in all other cases additional re-check will be made - see below 1356 } 1357 1358 if(in_array($module, $adminReadModules['override'])) 1359 { 1360 // core check 1361 if(in_array($module, $adminReadModules['core'])) 1362 { 1363 $mustHave = is_readable(eDispatcher::getConfigPath($module, 'core')); 1364 $has = is_readable(eDispatcher::getConfigPath($module, 'override')); 1365 1366 // No matter if it must have, it has e_url config 1367 if($has) $ret[$module] = 'override'; 1368 // It must have but it doesn't have e_url config, fallback 1369 elseif($mustHave && !$has) $ret[$module] = 'core'; 1370 // Rest is always core as controller override is done on run time 1371 else $ret[$module] = 'core'; 1372 } 1373 // plugin check 1374 elseif(in_array($module, $adminReadModules['plugin'])) 1375 { 1376 $mustHave = is_readable(eDispatcher::getConfigPath($module, 'plugin')); 1377 $has = is_readable(eDispatcher::getConfigPath($module, 'override')); 1378 1379 // No matter if it must have, it has e_url config 1380 if($has) $ret[$module] = 'override'; 1381 // It must have but it doesn't have e_url config, fallback 1382 elseif($mustHave && !$has) $ret[$module] = 'plugin'; 1383 // Rest is always plugin as config is most important, controller override check is done on run time 1384 else $ret[$module] = 'plugin'; 1385 } 1386 // standalone override module 1387 else 1388 { 1389 $ret[$module] = 'override'; 1390 } 1391 1392 } 1393 // default core location 1394 elseif(in_array($module, $adminReadModules['core'])) 1395 { 1396 $ret[$module] = 'core'; 1397 } 1398 // default plugin location 1399 elseif(in_array($module, $adminReadModules['plugin'])) 1400 { 1401 $ret[$module] = 'plugin'; 1402 } 1403 } 1404 return $ret; 1405 } 1406 1407 /** 1408 * Detect available config locations (readable check), based on available url_modules {@link eDispatcher::adminReadModules()} core preference arrays 1409 * Used to rebuild url_locations core preference value 1410 * @see eDispatcher::adminBuildConfig() 1411 * @see eDispatcher::adminReadModules() 1412 * @param array $available {@link eDispatcher::adminReadModules()} stored as url_modules core preference 1413 * @return array available config locations, stored as url_locations core preference 1414 */ 1415 public static function adminBuildLocations($available = null) 1416 { 1417 $ret = array(); 1418 if(null === $available) $available = self::adminReadModules(); 1419 1420 $fl = e107::getFile(); 1421 1422 // Core 1423 foreach ($available['core'] as $module) 1424 { 1425 // Default module 1426 $ret[$module] = array('core'); 1427 1428 // read sub-locations 1429 $path = eDispatcher::getConfigLocationPath($module, 'core'); 1430 //$sub = $fl->get_dirs($path); 1431 $sub = eRouter::adminReadConfigs($path); 1432 1433 if($sub) 1434 { 1435 foreach ($sub as $moduleSub) 1436 { 1437 // auto-override: override available (controller or url config), check for config 1438 if(in_array($module, $available['override']) && is_readable(eDispatcher::getConfigPath($module, 'override/'.$moduleSub))) 1439 { 1440 $ret[$module][] = 'override/'.$moduleSub; 1441 } 1442 // no override available, register the core location 1443 elseif(is_readable(eDispatcher::getConfigPath($module, 'core/'.$moduleSub))) 1444 { 1445 $ret[$module][] = 'core/'.$moduleSub; 1446 } 1447 } 1448 } 1449 } 1450 1451 1452 // Plugins 1453 foreach ($available['plugin'] as $module) 1454 { 1455 // Default module 1456 $ret[$module] = array('plugin'); 1457 1458 // read sub-locations 1459 $path = eDispatcher::getConfigLocationPath($module, 'plugin'); 1460 //$sub = $fl->get_dirs($path); 1461 $sub = eRouter::adminReadConfigs($path); 1462 1463 if($sub) 1464 { 1465 foreach ($sub as $moduleSub) 1466 { 1467 // auto-override: override available (controller or url config), check for config 1468 if(in_array($module, $available['override']) && is_readable(eDispatcher::getConfigPath($module, 'override/'.$moduleSub))) 1469 { 1470 $ret[$module][] = 'override/'.$moduleSub; 1471 } 1472 // no override available, register the core location 1473 elseif(is_readable(eDispatcher::getConfigPath($module, 'plugin/'.$moduleSub))) 1474 { 1475 $ret[$module][] = 'plugin/'.$moduleSub; 1476 } 1477 } 1478 } 1479 } 1480 1481 // Go through all overrides, register those who don't belong to core & plugins as standalone core modules 1482 foreach ($available['override'] as $module) 1483 { 1484 // either it is a core/plugin module or e_url.php is not readable - continue 1485 if(in_array($module, $available['core']) || in_array($module, $available['plugin'])) 1486 { 1487 continue; 1488 } 1489 1490 // Default module 1491 $ret[$module] = array('override'); 1492 1493 // read sub-locations 1494 $path = eDispatcher::getConfigLocationPath($module, 'override'); 1495 //$sub = $fl->get_dirs($path); 1496 $sub = eRouter::adminReadConfigs($path); 1497 1498 if($sub) 1499 { 1500 foreach ($sub as $moduleSub) 1501 { 1502 if(is_readable(eDispatcher::getConfigPath($module, 'override/'.$moduleSub))) 1503 { 1504 $ret[$module][] = 'override/'.$moduleSub; 1505 } 1506 } 1507 } 1508 } 1509 1510 return $ret; 1511 } 1512 1513 /** 1514 * Match current aliases against currently available module and languages 1515 * @param array $currentAliases url_aliases core preference 1516 * @param array $currentConfig url_config core preference 1517 * @return array cleaned aliases 1518 */ 1519 public static function adminSyncAliases($currentAliases, $currentConfig) 1520 { 1521 if(empty($currentAliases)) return array(); 1522 1523 $modules = array_keys($currentConfig); 1524 1525 // remove non existing languages 1526 $lng = e107::getLanguage(); 1527 $lanList = $lng->installed(); 1528 1529 if(is_array($currentAliases)) 1530 { 1531 foreach ($currentAliases as $lanCode => $aliases) 1532 { 1533 $lanName = $lng->convert($lanCode); 1534 if(!$lanName || !in_array($lanName, $lanList)) 1535 { 1536 unset($currentAliases[$lanCode]); 1537 continue; 1538 } 1539 1540 // remove non-existing modules 1541 foreach ($aliases as $alias => $module) 1542 { 1543 if(!isset($currentConfig[$module])) unset($currentAliases[$lanCode][$alias]); 1544 } 1545 } 1546 } 1547 return $currentAliases; 1548 } 1549 1550 /** 1551 * Retrieve global configuration array for a single or all modules 1552 * @param string $module system module 1553 * @return array configuration 1554 */ 1555 public function getConfig($module = null) 1556 { 1557 if(null === $module) return $this->_globalConfig; 1558 1559 return isset($this->_globalConfig[$module]) ? $this->_globalConfig[$module] : array(); 1560 } 1561 1562 /** 1563 * Retrieve single value from a module global configuration array 1564 * @param string $module system module 1565 * @return array configuration 1566 */ 1567 public function getConfigValue($module, $key, $default = null) 1568 { 1569 return isset($this->_globalConfig[$module]) && isset($this->_globalConfig[$module][$key]) ? $this->_globalConfig[$module][$key] : $default; 1570 } 1571 1572 /** 1573 * Get system name of a module by its alias 1574 * Returns null if $alias is not an existing alias 1575 * @param string $alias 1576 * @param string $lan optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases) 1577 * @return string module 1578 */ 1579 public function getModuleFromAlias($alias, $lan = null) 1580 { 1581 if($lan) return e107::findPref('url_aliases/'.$lan.'/'.$alias, null); 1582 return (isset($this->_aliases[$alias]) ? $this->_aliases[$alias] : null); 1583 } 1584 1585 /** 1586 * Get alias name for a module 1587 * Returns null if module doesn't have an alias 1588 * @param string $module 1589 * @param string $lan optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases) 1590 * @return string alias 1591 */ 1592 public function getAliasFromModule($module, $lan = null) 1593 { 1594 if($lan) 1595 { 1596 $aliases = e107::findPref('url_aliases/'.$lan, array()); 1597 return (in_array($module, $aliases) ? array_search($module, $aliases) : null); 1598 } 1599 return (in_array($module, $this->_aliases) ? array_search($module, $this->_aliases) : null); 1600 } 1601 1602 /** 1603 * Check if alias exists 1604 * @param string $alias 1605 * @param string $lan optional language alias. Example $lan = 'bg' (search for Bulgarian aliases) 1606 * @return boolean 1607 */ 1608 public function isAlias($alias, $lan = null) 1609 { 1610 if($lan) 1611 { 1612 $aliases = e107::findPref('url_aliases/'.$lan, array()); 1613 return isset($aliases[$alias]); 1614 } 1615 return isset($this->_aliases[$alias]); 1616 } 1617 1618 /** 1619 * Check if there is an alias for provided module 1620 * @param string $module 1621 * @param string $lan optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases) 1622 * @return boolean 1623 */ 1624 public function hasAlias($module, $lan = null) 1625 { 1626 if($lan) 1627 { 1628 $aliases = e107::findPref('url_aliases/'.$lan, array()); 1629 return in_array($module, $aliases); 1630 } 1631 return in_array($module, $this->_aliases); 1632 } 1633 1634 /** 1635 * Get all available module aliases 1636 * @param string $lan optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases) 1637 * @return array 1638 */ 1639 public function getAliases($lanCode = null) 1640 { 1641 if($lanCode) 1642 { 1643 return e107::findPref('url_aliases/'.$lanCode, array()); 1644 } 1645 return $this->_aliases; 1646 } 1647 1648 /** 1649 * Set module aliases 1650 * @param array $aliases 1651 * @return eRouter 1652 */ 1653 public function setAliases($aliases = null) 1654 { 1655 if(null === $aliases) 1656 { 1657 $lanCode = e107::getLanguage()->convert(e_LANGUAGE); 1658 1659 $aliases = e107::findPref('url_aliases/'.$lanCode, array()); 1660 } 1661 $this->_aliases = $aliases; 1662 1663 return $this; 1664 } 1665 1666 /** 1667 * Check if provided module is present in the rules config 1668 * @param string module 1669 * @return boolean 1670 */ 1671 public function isModule($module) 1672 { 1673 return isset($this->_globalConfig[$module]); 1674 } 1675 1676 /** 1677 * Check if the passed value is valid module or module alias, returns system module 1678 * or null on failure 1679 * @param string $module 1680 * @param boolean $strict check for existence if true 1681 * @return string module 1682 */ 1683 public function retrieveModule($module, $strict = true) 1684 { 1685 if($this->isAlias($module)) 1686 $module = $this->getModuleFromAlias($module); 1687 1688 if($strict && (!$module || !$this->isModule($module))) 1689 return null; 1690 1691 return $module; 1692 } 1693 1694 /** 1695 * Set rule config for this instance 1696 * @param array $rules 1697 * @return void 1698 */ 1699 public function setRuleSets($rules) 1700 { 1701 $this->_rules = $rules; 1702 } 1703 1704 /** 1705 * Retrieve rule set for a module 1706 * @param string $module 1707 */ 1708 public function getRuleSet($module) 1709 { 1710 return (isset($this->_rules[$module]) ? $this->_rules[$module] : array()); 1711 } 1712 1713 /** 1714 * Get all rule sets 1715 */ 1716 public function getRuleSets() 1717 { 1718 return $this->_rules; 1719 } 1720 1721 /** 1722 * Retrive array of eUrlRule objects for given module 1723 */ 1724 public function getRules($module) 1725 { 1726 return $this->_processRules($module); 1727 } 1728 1729 /** 1730 * Process rule set array, create rule objects 1731 * TODO - rule cache 1732 * @param string $module 1733 * @return array processed rule set 1734 */ 1735 protected function _processRules($module) 1736 { 1737 if(!$this->isModule($module)) return array(); 1738 1739 if(!isset($this->_parsedRules[$module])) 1740 { 1741 $rules = $this->getRuleSet($module); 1742 $config = $this->getConfig($module); 1743 $this->_parsedRules[$module] = array(); 1744 $map = array('urlSuffix' => 'urlSuffix', 'legacy' => 'legacy', 'legacyQuery' => 'legacyQuery', 'mapVars' => 'mapVars', 'allowVars' => 'allowVars', 'matchValue' => 'matchValue'); 1745 foreach ($rules as $pattern => $set) 1746 { 1747 foreach ($map as $key => $value) 1748 { 1749 if(!isset($set[$value]) && isset($config[$key])) 1750 { 1751 $set[$value] = $config[$key]; 1752 } 1753 } 1754 $this->_parsedRules[$module][$pattern] = $this->createRule($set, $pattern); 1755 } 1756 } 1757 return $this->_parsedRules[$module]; 1758 } 1759 1760 /** 1761 * Create rule object 1762 * 1763 * @param string $route 1764 * @param string|array $pattern 1765 * @param boolean $cache 1766 * @return eUrlRule 1767 */ 1768 protected function createRule($route, $pattern = null, $cache = false) 1769 { 1770 return new eUrlRule($route, $pattern, $cache); 1771 } 1772 1773 1774 private function _debug($label,$val=null, $line=null) 1775 { 1776 if(!deftrue('e_DEBUG_SEF')) 1777 { 1778 return false; 1779 } 1780 1781 e107::getDebug()->log("<h3>SEF: ".$label . " <small>".basename(__FILE__)." (".$line.")</small></h3>".print_a($val,true)); 1782 } 1783 1784 /** 1785 * Route current request 1786 * @param eRequest $request 1787 * @return boolean 1788 */ 1789 public function route(eRequest $request, $checkOnly = false) 1790 { 1791 $request->routed = false; 1792 1793 if(isset($_GET[$this->routeVar])) 1794 { 1795 $rawPathInfo = $_GET[$this->routeVar]; 1796 unset($_GET[$this->routeVar]); 1797 $this->_urlFormat = self::FORMAT_GET; 1798 } 1799 else 1800 { 1801 $rawPathInfo = rawurldecode($request->getPathInfo()); 1802 //$this->_urlFormat = self::FORMAT_PATH; 1803 } 1804 1805 1806 1807 // Ignore social trackers when determining route. 1808 $get = eHelper::removeTrackers($_GET); 1809 1810 // Route to front page - index/index/index route 1811 if(!$rawPathInfo && (!$this->getMainModule() || empty($get))) 1812 { 1813 // front page settings will be detected and front page will be rendered 1814 $request->setRoute('index/index/index'); 1815 $request->addRouteHistory($rawPathInfo); 1816 $request->routed = true; 1817 return true; 1818 } 1819 1820 // max number of parts is actually 4 - module/controller/action/[additional/pathinfo/vars], here for reference only 1821 $parts = $rawPathInfo ? explode('/', $rawPathInfo, 4) : array(); 1822 1823 $this->_debug('parts',$parts, __LINE__); 1824 1825 // find module - check aliases 1826 $module = $this->retrieveModule($parts[0]); 1827 $mainSwitch = false; 1828 1829 // no module found, switch to Main module (pref) if available 1830 if(null === $module && $this->getMainModule() && $this->isModule($this->getMainModule())) 1831 { 1832 $module = $this->getMainModule(); 1833 $rawPathInfo = $module.'/'.$rawPathInfo; 1834 array_unshift($parts, $module); 1835 $mainSwitch = true; 1836 } 1837 1838 $request->routePathInfo = $rawPathInfo; 1839 1840 $this->_debug('module',$module, __LINE__); 1841 $this->_debug('rawPathInfo',$rawPathInfo, __LINE__); 1842 1843 1844 // valid module 1845 if(null !== $module) 1846 { 1847 // we have valid module 1848 $config = $this->getConfig($module); 1849 1850 $this->_debug('config',$module, __LINE__); 1851 1852 // set legacy state 1853 eFront::isLegacy(varset($config['legacy'])); 1854 1855 // Don't allow single entry if required by module config 1856 if(vartrue($config['noSingleEntry'])) 1857 { 1858 $request->routed = true; 1859 if(!eFront::isLegacy()) 1860 { 1861 $request->setRoute($this->notFoundRoute); 1862 return false; 1863 } 1864 // legacy entry point - include it later in the bootstrap, legacy query string will be set to current 1865 $request->addRouteHistory($rawPathInfo); 1866 return true; 1867 } 1868 1869 // URL format - the one set by current config overrides the auto-detection 1870 $format = isset($config['format']) && $config['format'] ? $config['format'] : $this->getUrlFormat(); 1871 1872 //remove leading module, unnecessary overhead while matching 1873 array_shift($parts); 1874 $rawPathInfo = $parts ? implode('/', $parts) : ''; 1875 $pathInfo = $this->removeUrlSuffix($rawPathInfo, $this->urlSuffix); 1876 1877 // retrieve rules if any and if needed 1878 $rules = $format == self::FORMAT_PATH ? $this->getRules($module) : array(); 1879 1880 // Further parsing may still be needed 1881 if(empty($rawPathInfo)) 1882 { 1883 $rawPathInfo = $pathInfo; 1884 } 1885 1886 // parse callback 1887 if(vartrue($config['selfParse'])) 1888 { 1889 // controller/action[/additional/parms] 1890 if(vartrue($config['urlSuffix'])) $rawPathInfo = $this->removeUrlSuffix($rawPathInfo, $config['urlSuffix']); 1891 $route = $this->configCallback($module, 'parse', array($rawPathInfo, $_GET, $request, $this, $config), $config['location']); 1892 } 1893 // default module route 1894 elseif($format == self::FORMAT_GET || !$rules) 1895 { 1896 $route = $pathInfo; 1897 } 1898 // rules available - try to match an Url Rule 1899 elseif($rules) 1900 { 1901 // $this->_debug('rules',$rules, __LINE__); 1902 1903 foreach ($rules as $rule) 1904 { 1905 $route = $rule->parseUrl($this, $request, $pathInfo, $rawPathInfo); 1906 1907 1908 1909 if($route !== false) 1910 { 1911 eFront::isLegacy($rule->legacy); // legacy include override 1912 1913 $this->_debug('rule->legacy',$rule->legacy, __LINE__); 1914 $this->_debug('rule->parseCallback',$rule->parseCallback, __LINE__); 1915 1916 if($rule->parseCallback) 1917 { 1918 $this->configCallback($module, $rule->parseCallback, array($request), $config['location']); 1919 } 1920 1921 // parse legacy query string if any 1922 $this->_debug('rule->legacyQuery',$rule->legacyQuery, __LINE__); 1923 1924 if(null !== $rule->legacyQuery) 1925 { 1926 $obj = eDispatcher::getConfigObject($module, $config['location']); 1927 // eUrlConfig::legacyQueryString set as legacy string by default in eUrlConfig::legacy() method 1928 $vars = new e_vars($request->getRequestParams()); 1929 $vars->module = $module; 1930 $vars->controller = $request->getController(); 1931 $vars->action = $request->getAction(); 1932 if($rule->allowVars) 1933 { 1934 foreach ($rule->allowVars as $key) 1935 { 1936 if(isset($_GET[$key]) && !$request->isRequestParam($key)) 1937 { 1938 // sanitize 1939 $vars->$key = preg_replace('/[^\d\w\-]/', '', $_GET[$key]); 1940 } 1941 } 1942 } 1943 $obj->legacyQueryString = e107::getParser()->simpleParse($rule->legacyQuery, $vars, '0'); 1944 1945 $this->_debug('obj->legacyQueryString',$obj->legacyQueryString, __LINE__); 1946 unset($vars, $obj); 1947 } 1948 break; 1949 } 1950 } 1951 } 1952 1953 // append module to be registered in the request object 1954 if(false !== $route) 1955 { 1956 // don't modify if true - request directly modified by config callback 1957 if(!$request->routed) 1958 { 1959 if(eFront::isLegacy()) $this->configCallback($module, 'legacy', array($route, $request), $config['location']); 1960 $route = $module.'/'.$route; 1961 } 1962 } 1963 // No route found, we didn't switched to main module auto-magically 1964 elseif(!$mainSwitch && vartrue($config['errorRoute'])) 1965 { 1966 $route = !$checkOnly ? $module.'/'.$config['errorRoute'] : false; 1967 } 1968 1969 } 1970 1971 // final fallback 1972 if(!vartrue($route)) 1973 { 1974 if($request->routed) 1975 { 1976 $route = $request->getRoute(); 1977 } 1978 1979 if(!$route) 1980 { 1981 $route = $this->notFoundRoute; 1982 eFront::isLegacy(''); // reset legacy - not found route isn't legacy call 1983 $request->routed = true; 1984 if($checkOnly) return false; 1985 ## Global redirect on error option 1986 if(e107::getPref('url_error_redirect', false) && $this->notFoundUrl) 1987 { 1988 $redirect = $this->assemble($this->notFoundUrl, '', 'encode=0&full=1'); 1989 //echo $redirect; exit; 1990 e107::getRedirect()->redirect($redirect, true, 404); 1991 } 1992 } 1993 } 1994 1995 $this->_debug('route',$route, __LINE__); 1996 1997 $request->setRoute($route); 1998 $request->addRouteHistory($route); 1999 $request->routed = true; 2000 return true; 2001 } 2002 2003 /** 2004 * And more BC 2005 * Checks and does some addtional logic if registered module is of type legacy 2006 * @param eRequest $request 2007 * @return void 2008 */ 2009 public function checkLegacy(eRequest $request) 2010 { 2011 $module = $request->getModule(); 2012 2013 // forward from controller to a legacy module - bad stuff 2014 if(!$request->isDispatched() && $this->getConfigValue($module, 'legacy')) 2015 { 2016 eFront::isLegacy($this->getConfigValue($module, 'legacy')); 2017 2018 $url = $this->assemble($request->getRoute(), $request->getRequestParams()); 2019 $request->setRequestInfo($url)->setPathInfo(null)->setRoute(null); 2020 2021 $_GET = $request->getRequestParams(); 2022 $_SERVER['QUERY_STRING'] = http_build_query($request->getRequestParams(), null, '&'); 2023 2024 // Infinite loop impossible, as dispatcher will break because of the registered legacy path 2025 $this->route($request); 2026 } 2027 } 2028 2029 /** 2030 * Convenient way to call config methods 2031 */ 2032 public function configCallback($module, $callBack, $params, $location) 2033 { 2034 if(null == $location) $location = eDispatcher::getModuleConfigLocation($module); 2035 if(!$module || !($obj = eDispatcher::getConfigObject($module, $location))) return false; 2036 2037 return call_user_func_array(array($obj, $callBack), $params); 2038 } 2039 2040 /** 2041 * Convert assembled url to shortcode 2042 * 2043 * @param string $route 2044 * @param array $params 2045 * @param array $options {@see eRouter::$_defaultAssembleOptions} 2046 */ 2047 public function assembleSc($route, $params = array(), $options = array()) 2048 { 2049 //if(is_string($options)) parse_str($options, $options); 2050 $url = $this->assemble($route, $params, $options); 2051 return e107::getParser()->createConstants($url, 'mix'); 2052 } 2053 2054 /** 2055 * Assemble system URL 2056 * Examples: 2057 * <?php 2058 * $router->assemble('/'); // index page URL e.g. / or /site_folder/ 2059 * $router->assemble('news/view/item?id=1'); // depends on current news config, possible return value is /news/1 2060 * $router->assemble('*', 'id=1'); // use current request info - /module/controller/action?id=1 2061 * $router->assemble('* /* /newaction'); // (NO EMPTY SPACES) change only current action - /module/controller/newaction 2062 * $newsItem = array('news_id' => 1, 'news_sef' => 'My Title', ...); // as retrieved from DB 2063 * $router->assemble('news/view/item', $newsItem); // All unused key=>values will be removed and NOT appended as GET vars 2064 * 2065 * @param string $route 2066 * @param array $params 2067 * @param array $options {@see eRouter::$_defaultAssembleOptions} 2068 */ 2069 public function assemble($route, $params = array(), $options = array()) 2070 { 2071 // TODO - url options 2072 $request = eFront::instance()->getRequest(); 2073 if(is_string($options)) parse_str($options, $options); 2074 $options = array_merge($this->_defaultAssembleOptions, $options); 2075 $base = ($options['full'] ? SITEURLBASE : '').$request->getBasePath(); 2076 2077 $anc = ''; 2078 2079 2080 if(is_string($params)) parse_str($params, $params); 2081 if(isset($params['#'])) 2082 { 2083 $anc = '#'.$params['#']; 2084 unset($params['#']); 2085 } 2086 2087 // Config independent - Deny parameter keys, useful for directly denying sensitive data e.g. password db fields 2088 if(isset($options['deny'])) 2089 { 2090 $list = array_map('trim', explode(',', $options['deny'])); 2091 foreach ($list as $value) 2092 { 2093 unset($params[$value]); 2094 } 2095 unset($list); 2096 } 2097 2098 // Config independent - allow parameter keys, useful to directly allow data (and not to rely on config allowVars) e.g. when retrieved from db 2099 if(isset($options['allow'])) 2100 { 2101 $list = array_map('trim', explode(',', $options['allow'])); 2102 $_params = $params; 2103 $params = array(); 2104 foreach ($list as $value) 2105 { 2106 if(isset($_params[$value])) $params[$value] = $_params[$value]; 2107 } 2108 unset($list, $_params); 2109 } 2110 2111 # Optional convenient masks for creating system URL's 2112 if($route === '/' || empty($route)) 2113 { 2114 if($params) 2115 { 2116 $params = $this->createPathInfo($params, $options); 2117 return $base.'?'.$params; 2118 } 2119 return $base; 2120 } 2121 elseif(strpos($route, '?') !== false) 2122 { 2123 $tmp = explode('?', $route, 2); 2124 $route = $tmp[0]; 2125 parse_str($tmp[1], $params); 2126 unset($tmp); 2127 } 2128 2129 if($route === '*') 2130 { 2131 $route = $route = explode('/', $request->getRoute()); 2132 } 2133 elseif(strpos($route, '*') !== false) 2134 { 2135 $route = explode('/', $route, 3); 2136 if($route[0] === '*') $route[0] = $request->getModule(); 2137 if(isset($route[1]) && $route[1] === '*') $route[1] = $request->getController(); 2138 } 2139 else 2140 { 2141 $route = explode('/', $route, 3); 2142 } 2143 2144 // we don't know anything about this route, just build it blind 2145 if(!$this->isModule($route[0])) 2146 { 2147 if($params) 2148 { 2149 $params = $this->createPathInfo($params, $options); 2150 return $base.implode('/', $route).'?'.$params; 2151 } 2152 return $base.implode('/', $route); 2153 } 2154 2155 # fill in index when needed - XXX not needed, may be removed soon 2156 switch (count($route)) 2157 { 2158 case 1: 2159 $route[1] = 'index'; 2160 $route[2] = 'index'; 2161 break; 2162 case 2: 2163 $route[2] = 'index'; 2164 break; 2165 } 2166 2167 # aliases 2168 $module = $route[0]; 2169 $config = $this->getConfig($module); 2170 2171 $alias = $this->hasAlias($module, vartrue($options['lan'], null)) ? $this->getAliasFromModule($module, vartrue($options['lan'], null)) : $module; 2172 $route[0] = $alias; 2173 if($options['encode']) $alias = rawurlencode($alias); 2174 2175 $format = isset($config['format']) && $config['format'] ? $config['format'] : self::FORMAT_GET; 2176 2177 $urlSuffix = ''; 2178 2179 // Fix base url for legacy links 2180 if(vartrue($config['noSingleEntry'])) $base = $options['full'] ? SITEURL : e_HTTP; 2181 elseif(self::FORMAT_GET !== $config['format']) 2182 { 2183 $urlSuffix = $this->urlSuffix; 2184 if(isset($config['urlSuffix'])) $urlSuffix = $config['urlSuffix']; 2185 } 2186 2187 // Create by config callback 2188 if(vartrue($config['selfCreate'])) 2189 { 2190 $tmp = $this->configCallback($module, 'create', array(array($route[1], $route[2]), $params, $options), $config['location']); 2191 2192 if(empty($tmp)) return '#not-found'; 2193 2194 if(is_array($tmp)) 2195 { 2196 $route = $tmp[0]; 2197 $params = $tmp[1]; 2198 2199 if($options['encode']) $route = array_map('rawurlencode', $route); 2200 $route = implode('/', $route); 2201 2202 if(!$route) 2203 { 2204 $urlSuffix = ''; 2205 if(!$this->isMainModule($module)) $route = $alias; 2206 } 2207 elseif (!$this->isMainModule($module)) 2208 { 2209 $route = $alias.'/'.$route; 2210 } 2211 2212 } 2213 else 2214 { 2215 // relative url returned 2216 return $base.$tmp.$anc; 2217 } 2218 unset($tmp); 2219 2220 if($format === self::FORMAT_GET) 2221 { 2222 $params[$this->routeVar] = $route; 2223 $route = ''; 2224 } 2225 2226 if($params) 2227 { 2228 $params = $this->createPathInfo($params, $options); 2229 return $base.$route.$urlSuffix.'?'.$params.$anc; 2230 } 2231 2232 return $base.$route.$urlSuffix.$anc; 2233 } 2234 2235 2236 // System URL create routine 2237 $rules = $this->getRules($module); 2238 if($format !== self::FORMAT_GET && !empty($rules)) 2239 { 2240 foreach ($rules as $k => $rule) 2241 { 2242 if (($url = $rule->createUrl($this, array($route[1], $route[2]), $params, $options)) !== false) 2243 { 2244 return $base.rtrim(($this->isMainModule($module) ? '' : $alias.'/').$url, '/').$anc; 2245 } 2246 } 2247 } 2248 2249 // default - module/controller/action 2250 if($this->isMainModule($module)) unset($route[0]); 2251 if($route[2] == 'index') 2252 { 2253 unset($route[2]); 2254 if($route[1] == 'index') unset($route[1]); 2255 } 2256 2257 # Modify params if required 2258 if($params) 2259 { 2260 if(varset($config['mapVars'])) 2261 { 2262 foreach ($config['mapVars'] as $srcKey => $dstKey) 2263 { 2264 if (isset($params[$srcKey])) 2265 { 2266 $params[$dstKey] = $params[$srcKey]; 2267 unset($params[$srcKey]); 2268 } 2269 } 2270 } 2271 2272 // false means - no vars are allowed, nothing to preserve here 2273 if(varset($config['allowVars']) === false) $params = array(); 2274 // default empty array value - try to guess what's allowed - mapVars is the best possible candidate 2275 elseif(empty($config['allowVars']) && !empty($config['mapVars'])) $params = array_unique(array_values($config['mapVars'])); 2276 // disallow everything but valid URL parameters 2277 if(!empty($config['allowVars'])) 2278 { 2279 $copy = $params; 2280 $params = array(); 2281 foreach ($config['allowVars'] as $key) 2282 { 2283 if(isset($copy[$key])) $params[$key] = $copy[$key]; 2284 } 2285 unset($copy); 2286 } 2287 2288 if($format === self::FORMAT_GET) 2289 { 2290 $urlSuffix = ''; 2291 $copy = $params; 2292 $params = array(); 2293 $params[$this->routeVar] = implode('/', $route); 2294 foreach ($copy as $key => $value) 2295 { 2296 $params[$key] = $value; 2297 } 2298 unset($copy); 2299 $route = array(); 2300 } 2301 $params = $this->createPathInfo($params, $options); 2302 $route = implode('/', $route); 2303 if(!$route || $route == $alias) $urlSuffix = ''; 2304 return $base.$route.$urlSuffix.'?'.$params.$anc; 2305 } 2306 $route = implode('/', $route); 2307 if(!$route || $route == $alias) $urlSuffix = ''; 2308 2309 2310 return $format === self::FORMAT_GET ? $base.'?'.$this->routeVar.'='.$route.$anc : $base.$route.$urlSuffix.$anc; 2311 } 2312 2313 /** 2314 * Alias of assemble() 2315 */ 2316 public function url($route, $params = array()) 2317 { 2318 return $this->assemble($route, $params); 2319 } 2320 2321 /** 2322 * Creates a path info based on the given parameters. 2323 * XXX - maybe we can switch to http_build_query(), should be able to do everything we need in a much better way 2324 * 2325 * @param array $params list of GET parameters 2326 * @param array $options rawurlencode, equal, encode and amp settings 2327 * @param string $key this is used internally for recursive calls 2328 * 2329 * @return string the created path info 2330 */ 2331 public function createPathInfo($params, $options, $key = null) 2332 { 2333 $pairs = array(); 2334 $equal = $options['equal']; 2335 $encode = $options['encode']; 2336 $ampersand = !$encode && $options['amp'] == '&' ? '&' : $options['amp']; 2337 foreach ($params as $k => $v) 2338 { 2339 if (null !== $key) $k = $key.'['.rawurlencode($k).']'; 2340 2341 if (is_array($v)) $pairs[] = $this->createPathInfo($v, $options, $k); 2342 else 2343 { 2344 if(null === $v) 2345 { 2346 if($encode) 2347 { 2348 $k = null !== $key ? $k : rawurlencode($k); 2349 } 2350 $pairs[] = $k; 2351 continue; 2352 } 2353 if($encode) 2354 { 2355 $k = null !== $key ? $k : rawurlencode($k); 2356 $v = rawurlencode($v); 2357 } 2358 $pairs[] = $k.$equal.$v; 2359 } 2360 } 2361 return implode($ampersand, $pairs); 2362 } 2363 2364 /** 2365 * Parses a path info into URL segments 2366 * Be sure to not use non-unique chars for equal and ampersand signs, or you'll break your URLs 2367 * 2368 * @param eRequest $request 2369 * @param string $pathInfo path info 2370 * @param string $equal 2371 * @param string $ampersand 2372 */ 2373 public function parsePathInfo($pathInfo, $equal = '/', $ampersand = '/') 2374 { 2375 if ('' === $pathInfo) return; 2376 2377 if ($equal != $ampersand) $pathInfo = str_replace($equal, $ampersand, $pathInfo); 2378 $segs = explode($ampersand, $pathInfo.$ampersand); 2379 2380 $segs = explode('/', $pathInfo); 2381 $ret = array(); 2382 2383 for ($i = 0, $n = count($segs); $i < $n - 1; $i += 2) 2384 { 2385 $key = $segs[$i]; 2386 if ('' === $key) continue; 2387 $value = $segs[$i + 1]; 2388 // array support 2389 if (($pos = strpos($key, '[')) !== false && ($pos2 = strpos($key, ']', $pos + 1)) !== false) 2390 { 2391 $name = substr($key, 0, $pos); 2392 // numerical array 2393 if ($pos2 === $pos + 1) 2394 $ret[$name][] = $value; 2395 // associative array 2396 else 2397 { 2398 $key = substr($key, $pos + 1, $pos2 - $pos - 1); 2399 $ret[$name][$key] = $value; 2400 } 2401 } 2402 else 2403 { 2404 $ret[$key] = $value; 2405 2406 } 2407 } 2408 return $ret; 2409 } 2410 2411 /** 2412 * Removes the URL suffix from path info. 2413 * @param string $pathInfo path info part in the URL 2414 * @param string $urlSuffix the URL suffix to be removed 2415 * 2416 * @return string path info with URL suffix removed. 2417 */ 2418 public function removeUrlSuffix($pathInfo, $urlSuffix) 2419 { 2420 if ('' !== $urlSuffix && substr($pathInfo, -strlen($urlSuffix)) === $urlSuffix) return substr($pathInfo, 0, -strlen($urlSuffix)); 2421 else return $pathInfo; 2422 } 2423} 2424 2425class eException extends Exception 2426{ 2427 2428} 2429 2430/** 2431 * Based on Yii Framework UrlRule handler <www.yiiframework.com> 2432 */ 2433class eUrlRule 2434{ 2435 /** 2436 * 2437 * For example, ".html" can be used so that the URL looks like pointing to a static HTML page. 2438 * Defaults to null, meaning using the value of {@link cl_shop_core_url::urlSuffix}. 2439 * 2440 * @var string the URL suffix used for this rule. 2441 */ 2442 public $urlSuffix; 2443 2444 /** 2445 * When this rule is used to parse the incoming request, the values declared in this property 2446 * will be injected into $_GET. 2447 * 2448 * @var array the default GET parameters (name=>value) that this rule provides. 2449 */ 2450 public $defaultParams = array(); 2451 2452 /** 2453 * @var string module/controller/action 2454 */ 2455 public $route; 2456 2457 /** 2458 * @var array the mapping from route param name to token name (e.g. _r1=><1>) 2459 */ 2460 public $references = array(); 2461 2462 /** 2463 * @var string the pattern used to match route 2464 */ 2465 public $routePattern; 2466 2467 /** 2468 * @var string regular expression used to parse a URL 2469 */ 2470 public $pattern; 2471 2472 /** 2473 * @var string template used to construct a URL 2474 */ 2475 public $template; 2476 2477 /** 2478 * @var array list of parameters (name=>regular expression) 2479 */ 2480 public $params = array(); 2481 2482 /** 2483 * @var boolean whether the URL allows additional parameters at the end of the path info. 2484 */ 2485 public $append; 2486 2487 /** 2488 * @var array list of SourceKey=>DestinationKey associations 2489 */ 2490 public $mapVars = array(); 2491 2492 /** 2493 * Numerical array of allowed parameter keys. If set, everything else will be wiped out from the passed parameter array 2494 * @var array 2495 */ 2496 public $allowVars = array(); 2497 2498 /** 2499 * Should be values matched vs route patterns when assembling URLs 2500 * Warning SLOW when true!!! 2501 * @var mixed true or 1 for preg_match (extremely slower), or 'empty' for only empty check (better) 2502 */ 2503 public $matchValue; 2504 2505 /** 2506 * Method member of module config object, to be called after successful request parsing 2507 * @var string 2508 */ 2509 public $parseCallback; 2510 2511 /** 2512 * Shortcode path to the old entry point e.g. '{e_BASE}news.php' 2513 * @var string 2514 */ 2515 public $legacy; 2516 2517 /** 2518 * Template used for automated recognition of legacy QueryString (parsed via simpleParser with values of retrieved requestParameters) 2519 * @var string 2520 */ 2521 public $legacyQuery; 2522 2523 /** 2524 * Core regex templates 2525 * Example usage - route <var:{number}> will result in 2526 * @var array 2527 */ 2528 public $regexTemplates = array( 2529 'az' => '[A-Za-z]+', // NOTE - it won't match non-latin word characters! 2530 'alphanum' => '[\w\pL]+', 2531 'sefsecure' => '[\w\pL\s\-+.,]+', 2532 'secure' => '[^\/\'"\\<%]+', 2533 'number' => '[\d]+', 2534 'username' => '[\w\pL.\-\s!,]+', // TODO - should equal to username pattern, sync it 2535 'azOptional' => '[A-Za-z]{0,}', 2536 'alphanumOptional' => '[\w\pL]{0,}', 2537 'sefsecureOptional' => '[\w\pL\s\-+.,]{0,}', 2538 'secureOptional' => '[^\/\'"\\<%]{0,}', 2539 'numberOptional' => '[\d]{0,}', 2540 'usernameOptional' => '[\w\pL.\-\s!,]{0,}', // TODO - should equal to username pattern, sync it 2541 ); 2542 2543 /** 2544 * User defined regex templates 2545 * @var array 2546 */ 2547 public $varTemplates = array(); 2548 2549 /** 2550 * All regex templates 2551 * @var e_var 2552 */ 2553 protected $_regexTemplates; 2554 2555 2556 /** 2557 * Constructor. 2558 * @param string $route the route of the URL (controller/action) 2559 * @param string $pattern the pattern for matching the URL 2560 */ 2561 public function __construct($route, $pattern, $fromCache = false) 2562 { 2563 if (is_array($route)) 2564 { 2565 if ($fromCache && !$pattern) 2566 { 2567 $this->setData($route); 2568 $this->_regexTemplates = new e_vars($this->regexTemplates); 2569 return; 2570 } 2571 2572 $this->setData($route); 2573 if($this->defaultParams && is_string($this->defaultParams)) 2574 { 2575 parse_str($this->defaultParams, $this->defaultParams); 2576 } 2577 $route = $this->route = $route[0]; 2578 } 2579 else $this->route = $route; 2580 2581 $tr2['/'] = $tr['/'] = '\\/'; 2582 2583 if (strpos($route, '<') !== false && preg_match_all('/<(\w+)>/', $route, $matches2)) 2584 { 2585 foreach ($matches2[1] as $name) $this->references[$name] = "<$name>"; 2586 } 2587 2588 if($this->varTemplates) 2589 { 2590 // don't override core regex templates 2591 $this->regexTemplates = array_merge($this->varTemplates, $this->regexTemplates); 2592 $this->varTemplates = array(); 2593 } 2594 $this->_regexTemplates = new e_vars($this->regexTemplates); 2595 2596 if (preg_match_all('/<(\w+):?(.*?)?>/', $pattern, $matches)) 2597 { 2598 $tokens = array_combine($matches[1], $matches[2]); 2599 $tp = e107::getParser(); 2600 foreach ($tokens as $name => $value) 2601 { 2602 if ($value === '') $value = '[^\/]+'; 2603 elseif($value[0] == '{') 2604 { 2605 $value = $tp->simpleParse($value, $this->_regexTemplates, '[^\/]+'); 2606 } 2607 $tr["<$name>"] = "(?P<$name>$value)"; 2608 if (isset($this->references[$name])) $tr2["<$name>"] = $tr["<$name>"]; 2609 else $this->params[$name] = $value; 2610 } 2611 } 2612 2613 $p = rtrim($pattern, '*'); 2614 $this->append = $p !== $pattern; 2615 $p = trim($p, '/'); 2616 $this->template = preg_replace('/<(\w+):?.*?>/', '<$1>', $p); 2617 $this->pattern = '/^'.strtr($this->template, $tr).'\/?'; 2618 if ($this->append) $this->pattern .= '/u'; 2619 else $this->pattern .= '$/u'; 2620 2621 if ($this->references !== array()) $this->routePattern = '/^'.strtr($this->route, $tr2).'$/u'; 2622 } 2623 2624 public function getData() 2625 { 2626 $vars = array_keys(get_class_vars(__CLASS__)); 2627 $data = array(); 2628 foreach ($vars as $prop) 2629 { 2630 $data[$prop] = $this->$prop; 2631 } 2632 return $data; 2633 } 2634 2635 protected function setData($data) 2636 { 2637 if (!is_array($data)) return; 2638 $vars = array_keys(get_class_vars(__CLASS__)); 2639 2640 foreach ($vars as $prop) 2641 { 2642 if (!isset($data[$prop])) continue; 2643 $this->$prop = $data[$prop]; 2644 } 2645 } 2646 2647 /** 2648 * Creates a URL based on this rule. 2649 * TODO - more clear logic and flexibility by building the query string 2650 * 2651 * @param eRouter $manager the router/manager 2652 * @param string $route the route 2653 * @param array $params list of parameters 2654 * @param array $options 2655 * @return mixed the constructed URL or false on error 2656 */ 2657 public function createUrl($manager, $route, $params, $options) 2658 { 2659 $case = 'i'; 2660 $ampersand = $options['amp']; 2661 $encode = vartrue($options['encode']); 2662 2663 if(is_array($route)) $route = implode('/', $route); 2664 2665 2666 2667 $tr = array(); 2668 if ($route !== $this->route) 2669 { 2670 if ($this->routePattern !== null && preg_match($this->routePattern.$case, $route, $matches)) 2671 { 2672 foreach ($this->references as $key => $name) $tr[$name] = $matches[$key]; 2673 } 2674 else return false; 2675 } 2676 2677 // map vars first 2678 foreach ($this->mapVars as $srcKey => $dstKey) 2679 { 2680 if (isset($params[$srcKey])/* && !isset($params[$dstKey])*/) 2681 { 2682 $params[$dstKey] = $params[$srcKey]; 2683 unset($params[$srcKey]); 2684 } 2685 } 2686 2687 // false means - no vars are allowed, preserve only route vars 2688 if($this->allowVars === false) $this->allowVars = array_keys($this->params); 2689 // empty array (default) - everything is allowed 2690 2691 // disallow everything but valid URL parameters 2692 if(!empty($this->allowVars)) 2693 { 2694 $copy = $params; 2695 $params = array(); 2696 $this->allowVars = array_unique(array_merge($this->allowVars, array_keys($this->params))); 2697 foreach ($this->allowVars as $key) 2698 { 2699 if(isset($copy[$key])) $params[$key] = $copy[$key]; 2700 } 2701 unset($copy); 2702 } 2703 2704 foreach ($this->defaultParams as $key => $value) 2705 { 2706 if (isset($params[$key])) 2707 { 2708 if ($params[$key] == $value) unset($params[$key]); 2709 else return false; 2710 } 2711 } 2712 2713 foreach ($this->params as $key => $value) if (!isset($params[$key])) return false; 2714 2715 if($this->matchValue) 2716 { 2717 2718 if('empty' !== $this->matchValue) 2719 { 2720 foreach($this->params as $key=>$value) 2721 { 2722 if(!preg_match('/'.$value.'/'.$case,$params[$key])) 2723 return false; 2724 } 2725 } 2726 else 2727 { 2728 foreach($this->params as $key=>$value) 2729 { 2730 if(empty($params[$key]) ) 2731 return false; 2732 } 2733 } 2734 } 2735 2736 $tp = e107::getParser(); 2737 $urlFormat = e107::getConfig()->get('url_sef_translate'); 2738 2739 foreach ($this->params as $key => $value) 2740 { 2741 // FIX - non-latin URLs proper encoded 2742 $tr["<$key>"] = rawurlencode($params[$key]); //todo transliterate non-latin 2743 // $tr["<$key>"] = eHelper::title2sef($tp->toASCII($params[$key]), $urlFormat); // enabled to test. 2744 unset($params[$key]); 2745 } 2746 2747 $suffix = $this->urlSuffix === null ? $manager->urlSuffix : $this->urlSuffix; 2748 2749 // XXX TODO Find better place for this check which will affect all types of SEF URL configurations. (@see news/sef_noid_url.php for duplicate) 2750 2751 2752 2753 2754 if($urlFormat == 'dashl' || $urlFormat == 'underscorel' || $urlFormat == 'plusl') // convert template to lowercase when using lowercase SEF URL format. 2755 { 2756 $this->template = strtolower($this->template); 2757 } 2758 2759 $url = strtr($this->template, $tr); 2760 2761 // Work-around fix for lowercase username 2762 if($urlFormat == 'dashl' && $this->route == 'profile/view') 2763 { 2764 $url = str_replace('%20','-', strtolower($url)); 2765 } 2766 2767 if(empty($params)) 2768 { 2769 return $url !== '' ? $url.$suffix : $url; 2770 } 2771 2772 // apppend not supported, maybe in the future...? 2773 if ($this->append) $url .= '/'.$manager->createPathInfo($params, '/', '/').$suffix; 2774 else 2775 { 2776 if ($url !== '') $url = $url.$suffix; 2777 2778 $options['equal'] = '='; 2779 $url .= '?'.$manager->createPathInfo($params, $options); 2780 } 2781 2782 2783 return rtrim($url, '/'); 2784 } 2785 2786 /** 2787 * Parases a URL based on this rule. 2788 * @param eRouter $manager the router/URL manager 2789 * @param eRequest $request the request object 2790 * @param string $pathInfo path info part of the URL 2791 * @param string $rawPathInfo path info that contains the potential URL suffix 2792 * @return mixed the route that consists of the controller ID and action ID or false on error 2793 */ 2794 public function parseUrl($manager, $request, $pathInfo, $rawPathInfo) 2795 { 2796 $case = 'i'; # 'i' = insensitive 2797 2798 if ($this->urlSuffix !== null) $pathInfo = $manager->removeUrlSuffix($rawPathInfo, $this->urlSuffix); 2799 2800 $pathInfo = rtrim($pathInfo, '/').'/'; 2801 // pathInfo is decoded, pattern could be encoded - required for proper url assemble (e.g. cyrillic chars) 2802 if (preg_match(rawurldecode($this->pattern).$case, $pathInfo, $matches)) 2803 { 2804 foreach ($this->defaultParams as $name => $value) 2805 { 2806 //if (!isset($_GET[$name])) $_REQUEST[$name] = $_GET[$name] = $value; 2807 if (!$request->isRequestParam($name)) $request->setRequestParam($name, $value); 2808 } 2809 $tr = array(); 2810 foreach ($matches as $key => $value) 2811 { 2812 if (isset($this->references[$key])) $tr[$this->references[$key]] = $value; 2813 elseif (isset($this->params[$key])) 2814 { 2815 //$_REQUEST[$key] = $_GET[$key] = $value; 2816 $request->setRequestParam($key, $value); 2817 } 2818 } 2819 2820 if ($pathInfo !== $matches[0]) # Additional GET params exist 2821 { 2822 $manager->parsePathInfo($request, ltrim(substr($pathInfo, strlen($matches[0])), '/')); 2823 } 2824 return (null !== $this->routePattern ? strtr($this->route, $tr) : $this->route); 2825 } 2826 else return false; 2827 } 2828 2829} 2830 2831abstract class eUrlConfig 2832{ 2833 /** 2834 * Registered by parse method legacy query string 2835 */ 2836 public $legacyQueryString = null; 2837 2838 /** 2839 * User defined initialization 2840 */ 2841 public function init() {} 2842 2843 /** 2844 * Retrieve module config options (including url rules if any) 2845 * Return array is called once and cached, so runtime changes are not an option 2846 * @return array 2847 */ 2848 abstract public function config(); 2849 2850 /** 2851 * Create URL callback, called only when config option selfParse is set to true 2852 * Expected return array format: 2853 * <code> 2854 * array( 2855 * array(part1, part2, part3), 2856 * array(parm1 => val1, parm2 => val2), 2857 * ); 2858 * </code> 2859 * @param array $route parts 2860 * @param array $params 2861 * @return array|string numerical of type (routeParts, GET Params)| string route or false on error 2862 */ 2863 public function create($route, $params = array(), $options = array()) {} 2864 2865 /** 2866 * Parse URL callback, called only when config option selfCreate is set to true 2867 * TODO - register variable eURLConfig::currentConfig while initializing the object, remove from method arguments 2868 * @param string $pathInfo 2869 * @param array $params request parameters 2870 * @param eRequest $request 2871 * @param eRouter $router 2872 * @param array $config 2873 * @return string route or false on error 2874 */ 2875 public function parse($pathInfo, $params = array(), eRequest $request = null, eRouter $router = null, $config = array()) 2876 { 2877 return false; 2878 } 2879 2880 /** 2881 * Legacy callback, used called when config option legacy is not empty 2882 * By default it sets legacy query string to $legacyQueryString value (normaly assigned inside of the parse method) 2883 * @param string $resolvedRoute 2884 * @param eRequest $request 2885 * @param string $callType 'route' - called once, when parsing the request, 'dispatch' - called inside the dispatch loop (in case of controller _forward) 2886 * @param void 2887 */ 2888 public function legacy($resolvedRoute, eRequest $request, $callType = 'route') 2889 { 2890 if($this->legacyQueryString !== null) 2891 { 2892 $request->setLegacyQstring($this->legacyQueryString); 2893 $request->setLegacyPage(); 2894 } 2895 } 2896 2897 /** 2898 * Developed mainly for legacy modules. 2899 * It should be manually triggered inside of old entry point. The idea is 2900 * to avoid multiple URL addresses having same content (bad SEO practice) 2901 * FIXME - under construction 2902 */ 2903 public function forward() {} 2904 2905 /** 2906 * Admin interface callback, returns array with all required from administration data 2907 * Return array structure: 2908 * <code> 2909 * <?php 2910 * return array( 2911 * 'labels' => array( 2912 * 'name' => 'Module name', 2913 * 'label' => 'Profile Label', 2914 * 'description' => 'Additional profile info, exmples etc.', 2915 * ), 2916 * 'form' => array(), // awaiting future development 2917 * 'callbacks' => array(), // awaiting future development 2918 * ); 2919 * </code> 2920 */ 2921 public function admin() { return array(); } 2922 2923 /** 2924 * Admin submit hook 2925 * FIXME - under construction 2926 */ 2927 public function submit() {} 2928 2929 /** 2930 * Admin interface help messages, labels and titles 2931 * FIXME - under construction 2932 */ 2933 public function help() {} 2934 2935 2936} 2937 2938/** 2939 * Controller base class, actions are extending it 2940 * 2941 */ 2942class eController 2943{ 2944 protected $_request; 2945 protected $_response; 2946 2947 public function __construct(eRequest $request, eResponse $response = null) 2948 { 2949 $this->setRequest($request) 2950 ->setResponse($response) 2951 ->init(); 2952 } 2953 2954 /** 2955 * Custom init, always called in the constructor, no matter what is the request dispatch status 2956 */ 2957 public function init() {} 2958 2959 /** 2960 * Custom shutdown, always called after the controller dispatch, no matter what is the request dispatch status 2961 */ 2962 public function shutdown() {} 2963 2964 /** 2965 * Pre-action callback, fired only if dispatch status is still true and action method is found 2966 */ 2967 public function preAction() {} 2968 2969 /** 2970 * Post-action callback, fired only if dispatch status is still true and action method is found 2971 */ 2972 public function postAction() {} 2973 2974 /** 2975 * @param eRequest $request 2976 * @return eController 2977 */ 2978 public function setRequest($request) 2979 { 2980 $this->_request = $request; 2981 return $this; 2982 } 2983 2984 /** 2985 * @return eRequest 2986 */ 2987 public function getRequest() 2988 { 2989 return $this->_request; 2990 } 2991 2992 /** 2993 * @param eResponse $response 2994 * @return eController 2995 */ 2996 public function setResponse($response) 2997 { 2998 $this->_response = $response; 2999 return $this; 3000 } 3001 3002 /** 3003 * @return eResponse 3004 */ 3005 public function getResponse() 3006 { 3007 return $this->_response; 3008 } 3009 3010 public function addBody($content) 3011 { 3012 $this->getResponse()->appendBody($content); 3013 return $this; 3014 } 3015 3016 public function addMetaDescription($description) 3017 { 3018 $this->getResponse()->addMetaDescription($description); 3019 return $this; 3020 } 3021 3022 /** 3023 * Add document title 3024 * @param string $title 3025 * @param boolean $meta auto-add it as meta-title 3026 * @return eResponse 3027 */ 3028 public function addTitle($title, $meta = true) 3029 { 3030 $this->getResponse()->appendTitle($title); 3031 if($meta) $this->addMetaTitle(strip_tags($title)); 3032 return $this; 3033 } 3034 3035 3036 public function addMetaTitle($title) 3037 { 3038 $this->getResponse()->addMetaTitle($title); 3039 return $this; 3040 } 3041 3042 public function dispatch($actionMethodName) 3043 { 3044 $request = $this->getRequest(); 3045 $content = ''; 3046 3047 // init() could modify the dispatch status 3048 if($request->isDispatched()) 3049 { 3050 if(method_exists($this, $actionMethodName)) 3051 { 3052 $this->preAction(); 3053 // TODO request userParams() to store private data - check for noPopulate param here 3054 if($request->isDispatched()) 3055 { 3056 $request->populateRequestParams(); 3057 3058 // allow return output 3059 $content = $this->$actionMethodName(); 3060 if(!empty($content)) $this->addBody($content); 3061 3062 if($request->isDispatched()) 3063 { 3064 $this->postAction(); 3065 } 3066 } 3067 } 3068 else 3069 { 3070 //TODO not found method by controller or default one 3071 $action = substr($actionMethodName, 6); 3072 throw new eException('Action "'.$action.'" does not exist'); 3073 } 3074 } 3075 $this->shutdown(); 3076 } 3077 3078 public function run(eRequest $request = null, eResponse $response = null) 3079 { 3080 if(null === $request) $request = $this->getRequest(); 3081 else $this->setRequest($request); 3082 3083 if(null === $response) $response = $this->getResponse(); 3084 else $this->setResponse($response); 3085 3086 $action = $request->getActionMethodName(); 3087 3088 $request->setDispatched(true); 3089 $this->dispatch($action); 3090 3091 return $this->getResponse(); 3092 } 3093 3094 protected function _redirect($url, $createURL = false, $code = null) 3095 { 3096 $redirect = e107::getRedirect(); 3097 if($createURL) 3098 { 3099 $url = eFront::instance()->getRouter()->assemble($url, '', 'encode=0'); 3100 } 3101 if(strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) 3102 { 3103 $url = $url[0] == '/' ? SITEURLBASE.$url : SITEURL.$url; 3104 } 3105 $redirect->redirect($url, true, $code); 3106 } 3107 3108 /** 3109 * System forward 3110 * @param string $route 3111 * @param array $params 3112 */ 3113 protected function _forward($route, $params = array()) 3114 { 3115 $request = $this->getRequest(); 3116 3117 if(is_string($params)) 3118 { 3119 parse_str($params, $params); 3120 } 3121 3122 $oldRoute = $request->getRoute(); 3123 $route = explode('/', trim($route, '/')); 3124 3125 switch (count($route)) { 3126 case 3: 3127 if($route[0] !== '*') $request->setModule($route[0]); 3128 if($route[1] !== '*') $request->setController($route[1]); 3129 $request->setAction($route[2]); 3130 break; 3131 3132 case 2: 3133 if($route[1] !== '*') $request->setController($route[0]); 3134 $request->setAction($route[1]); 3135 break; 3136 3137 case 1: 3138 $request->setAction($route[0]); 3139 break; 3140 3141 default: 3142 return; 3143 break; 3144 } 3145 3146 $request->addRouteHistory($oldRoute); 3147 3148 if(false !== $params) $request->setRequestParams($params); 3149 $request->setDispatched(false); 3150 } 3151 3152 /** 3153 * @param string $methodName 3154 * @param array $args 3155 * @return void 3156 * @throws eException 3157 */ 3158 public function __call($methodName, $args) 3159 { 3160 if ('action' == substr($methodName, 0, 6)) 3161 { 3162 $action = substr($methodName, 6); 3163 throw new eException('Action "'.$action.'" does not exist', 2404); 3164 } 3165 3166 throw new eException('Method "'.$methodName.'" does not exist', 3404); 3167 } 3168} 3169 3170/** 3171 * @package e107 3172 * @subpackage e107_handlers 3173 * @version $Id$ 3174 * 3175 * Base front-end controller 3176 */ 3177 3178class eControllerFront extends eController 3179{ 3180 /** 3181 * Plugin name - used to check if plugin is installed 3182 * Set this only if plugin requires installation 3183 * @var string 3184 */ 3185 protected $plugin = null; 3186 3187 /** 3188 * Default controller access 3189 * @var integer 3190 */ 3191 protected $userclass = e_UC_PUBLIC; 3192 3193 /** 3194 * Generic 404 page URL (redirect), SITEURL will be added 3195 * @var string 3196 */ 3197 protected $e404 = '404.html'; 3198 3199 /** 3200 * Generic 403 page URL (redirect), SITEURL will be added 3201 * @var string 3202 */ 3203 protected $e403 = '403.html'; 3204 3205 /** 3206 * Generic 404 route URL (forward) 3207 * @var string 3208 */ 3209 protected $e404route = 'index/not-found'; 3210 3211 /** 3212 * Generic 403 route URL (forward) 3213 * @var string 3214 */ 3215 protected $e403route = 'index/access-denied'; 3216 3217 /** 3218 * View renderer objects 3219 * @var array 3220 */ 3221 protected $_validator; 3222 3223 /** 3224 * Per action access 3225 * Format 'action' => userclass 3226 * @var array 3227 */ 3228 protected $access = array(); 3229 3230 /** 3231 * User input filter (_GET) 3232 * Format 'action' => array(var => validationArray) 3233 * @var array 3234 */ 3235 protected $filter = array(); 3236 3237 /** 3238 * Base constructor - set 404/403 locations 3239 */ 3240 public function __construct(eRequest $request, eResponse $response = null) 3241 { 3242 parent::__construct($request, $response); 3243 $this->_init(); 3244 } 3245 3246 /** 3247 * Base init, called after the public init() - handle access restrictions 3248 * The base init() method is able to change controller variables on the fly (e.g. access, filters, etc) 3249 */ 3250 final protected function _init() 3251 { 3252 // plugin check 3253 if(null !== $this->plugin) 3254 { 3255 if(!e107::isInstalled($this->plugin)) 3256 { 3257 $this->forward403(); 3258 return; 3259 } 3260 } 3261 3262 // global controller restriction 3263 if(!e107::getUser()->checkClass($this->userclass, false)) 3264 { 3265 $this->forward403(); 3266 return; 3267 } 3268 3269 // by action access 3270 if(!$this->checkActionPermissions()) exit; 3271 3272 // _GET input validation 3273 $this->validateInput(); 3274 3275 // Set Render mode to module-controller-action, override possible within the action 3276 $this->getResponse()->setRenderMod(str_replace('/', '-', $this->getRequest()->getRoute())); 3277 } 3278 3279 /** 3280 * Check persmission for current action 3281 * @return boolean 3282 */ 3283 protected function checkActionPermissions() 3284 { 3285 // per action restrictions 3286 $action = $this->getRequest()->getAction(); 3287 if(isset($this->access[$action]) && !e107::getUser()->checkClass($this->access[$action], false)) 3288 { 3289 $this->forward403(); 3290 return false; 3291 } 3292 return true; 3293 } 3294 3295 public function redirect404() 3296 { 3297 e107::getRedirect()->redirect(SITEURL.$this->e404); 3298 } 3299 3300 public function redirect403() 3301 { 3302 e107::getRedirect()->redirect(SITEURL.$this->e403); 3303 } 3304 3305 public function forward404() 3306 { 3307 $this->_forward($this->e404route); 3308 } 3309 3310 public function forward403() 3311 { 3312 $this->_forward($this->e403route); 3313 } 3314 3315 /** 3316 * Controller validator object 3317 * @return e_validator 3318 */ 3319 public function getValidator() 3320 { 3321 if(null === $this->_validator) 3322 { 3323 $this->_validator = new e_validator('controller'); 3324 } 3325 3326 return $this->_validator; 3327 } 3328 3329 /** 3330 * Register request parameters based on current $filter data (_GET only) 3331 * Additional security layer 3332 */ 3333 public function validateInput() 3334 { 3335 $validator = $this->getValidator(); 3336 $request = $this->getRequest(); 3337 if(empty($this->filter) || !isset($this->filter[$request->getAction()])) return; 3338 $validator->setRules($this->filter[$request->getAction()]) 3339 ->validate($_GET); 3340 3341 $validData = $validator->getValidData(); 3342 3343 foreach ($validData as $key => $value) 3344 { 3345 if(!$request->isRequestParam($key)) $request->setRequestParam($key, $value); 3346 } 3347 $validator->clearValidateMessages(); 3348 } 3349 3350 /** 3351 * System error message proxy 3352 * @param string $message 3353 * @param boolean $session 3354 */ 3355 public function messageError($message, $session = false) 3356 { 3357 return e107::getMessage()->addError($message, 'default', $session); 3358 } 3359 3360 /** 3361 * System success message proxy 3362 * @param string $message 3363 * @param boolean $session 3364 */ 3365 public function messageSuccess($message, $session = false) 3366 { 3367 return e107::getMessage()->addSuccess($message, 'default', $session); 3368 } 3369 3370 /** 3371 * System warning message proxy 3372 * @param string $message 3373 * @param boolean $session 3374 */ 3375 public function messageWarning($message, $session = false) 3376 { 3377 return e107::getMessage()->addWarning($message, 'default', $session); 3378 } 3379 3380 /** 3381 * System debug message proxy 3382 * @param string $message 3383 * @param boolean $session 3384 */ 3385 public function messageDebug($message, $session = false) 3386 { 3387 return e107::getMessage()->addDebug($message, 'default', $session); 3388 } 3389} 3390 3391 3392/** 3393 * Request handler 3394 * 3395 */ 3396class eRequest 3397{ 3398 /** 3399 * @var string 3400 */ 3401 protected $_module; 3402 3403 /** 3404 * @var string 3405 */ 3406 protected $_controller; 3407 3408 /** 3409 * @var string 3410 */ 3411 protected $_action; 3412 3413 /** 3414 * Request status 3415 * @var boolean 3416 */ 3417 protected $_dispatched = false; 3418 3419 /** 3420 * @var array 3421 */ 3422 protected $_requestParams = array(); 3423 3424 /** 3425 * @var string 3426 */ 3427 protected $_basePath; 3428 3429 /** 3430 * @var string 3431 */ 3432 protected $_pathInfo; 3433 3434 3435 /** 3436 * @var string 3437 */ 3438 protected $_requestInfo; 3439 3440 /** 3441 * Pathinfo string used for initial system routing 3442 */ 3443 public $routePathInfo; 3444 3445 /** 3446 * @var array 3447 */ 3448 protected $_routeHistory = array(); 3449 3450 /** 3451 * @var boolean if request is already routed - generally set by callbacks to notify router about route changes 3452 */ 3453 public $routed = false; 3454 3455 /** 3456 * Name of the bootstrap file 3457 * @var string 3458 */ 3459 public $singleEntry = 'index.php'; 3460 3461 /** 3462 * Request constructor 3463 */ 3464 public function __construct($route = null) 3465 { 3466 if(null !== $route) 3467 { 3468 $this->setRoute($route); 3469 $this->routed = true; 3470 } 3471 } 3472 3473 /** 3474 * Get system base path 3475 * @return string 3476 */ 3477 public function getBasePath() 3478 { 3479 if(null == $this->_basePath) 3480 { 3481 $this->_basePath = e_HTTP; 3482 if(!e107::getPref('url_disable_pathinfo')) $this->_basePath .= $this->singleEntry.'/'; 3483 } 3484 3485 return $this->_basePath; 3486 } 3487 3488 /** 3489 * Set system base path 3490 * @param string $basePath 3491 * @return eRequest 3492 */ 3493 public function setBasePath($basePath) 3494 { 3495 $this->_basePath = $basePath; 3496 return $this; 3497 } 3498 3499 /** 3500 * Get path info 3501 * If not set, it'll be auto-retrieved 3502 * @return string path info 3503 */ 3504 public function getPathInfo() 3505 { 3506 if(null == $this->_pathInfo) 3507 { 3508 if($this->getBasePath() == $this->getRequestInfo()) 3509 $this->_pathInfo = ''; // map to indexRoute 3510 3511 else 3512 $this->_pathInfo = substr($this->getRequestInfo(), strlen($this->getBasePath())); 3513 3514 if($this->_pathInfo && trim($this->_pathInfo, '/') == trim($this->singleEntry, '/')) $this->_pathInfo = ''; 3515 } 3516 3517 return $this->_pathInfo; 3518 } 3519 3520 /** 3521 * Override path info 3522 * @param string $pathInfo 3523 * @return eRequest 3524 */ 3525 public function setPathInfo($pathInfo) 3526 { 3527 $this->_pathInfo = $pathInfo; 3528 return $this; 3529 } 3530 3531 /** 3532 * @return string request info 3533 */ 3534 public function getRequestInfo() 3535 { 3536 if(null === $this->_requestInfo) 3537 { 3538 $this->_requestInfo = e_REQUEST_HTTP; 3539 } 3540 return $this->_requestInfo; 3541 } 3542 3543 3544 /** 3545 * Override request info 3546 * @param string $pathInfo 3547 * @return eRequest 3548 */ 3549 public function setRequestInfo($requestInfo) 3550 { 3551 $this->_requestInfo = $requestInfo; 3552 return $this; 3553 } 3554 3555 /** 3556 * Quick front page check 3557 */ 3558 public static function isFrontPage($entryScript = 'index.php', $currentPathInfo = e_REQUEST_HTTP) 3559 { 3560 $basePath = e_HTTP; 3561 if(!e107::getPref('url_disable_pathinfo')) $basePath .= $entryScript.'/'; 3562 3563 return ($basePath == $currentPathInfo); 3564 } 3565 3566 /** 3567 * Get current controller string 3568 * @return string 3569 */ 3570 public function getController() 3571 { 3572 return $this->_controller; 3573 } 3574 3575 /** 3576 * Get current controller name 3577 * Example: requested controller-name or 'controller name' -> converted to controller_name 3578 * @return string 3579 */ 3580 public function getControllerName() 3581 { 3582 return eHelper::underscore($this->_controller); 3583 } 3584 3585 /** 3586 * Set current controller name 3587 * Example: controller_name OR 'controller name' -> converted to controller-name 3588 * Always sanitized 3589 * @param string $controller 3590 * @return eRequest 3591 */ 3592 public function setController($controller) 3593 { 3594 $this->_controller = strtolower(eHelper::dasherize($this->sanitize($controller))); 3595 return $this; 3596 } 3597 3598 /** 3599 * Get current module string 3600 * @return string 3601 */ 3602 public function getModule() 3603 { 3604 return $this->_module; 3605 } 3606 3607 /** 3608 * Get current module name 3609 * Example: module-name OR 'module name' -> converted to module_name 3610 * @return string 3611 */ 3612 public function getModuleName() 3613 { 3614 return eHelper::underscore($this->_module); 3615 } 3616 3617 /** 3618 * Set current module name 3619 * Example: module_name OR 'module name' -> converted to module-name 3620 * Always sanitized 3621 * @param string $module 3622 * @return eRequest 3623 */ 3624 public function setModule($module) 3625 { 3626 $this->_module = strtolower(eHelper::dasherize($this->sanitize($module))); 3627 return $this; 3628 } 3629 3630 /** 3631 * Get current action string 3632 * @return string 3633 */ 3634 public function getAction() 3635 { 3636 return $this->_action; 3637 } 3638 3639 /** 3640 * Get current action name 3641 * Example: action-name OR 'action name' OR action_name -> converted to ActionName 3642 * @return string 3643 */ 3644 public function getActionName() 3645 { 3646 return eHelper::camelize($this->_action, true); 3647 } 3648 3649 /** 3650 * Get current action method name 3651 * Example: action-name OR 'action name' OR action_name -> converted to actionActionName 3652 * @return string 3653 */ 3654 public function getActionMethodName() 3655 { 3656 return 'action'.eHelper::camelize($this->_action, true); 3657 } 3658 3659 /** 3660 * Set current action name 3661 * Example: action_name OR 'action name' OR Action_Name OR 'Action Name' -> converted to ation-name 3662 * Always sanitized 3663 * @param string $action 3664 * @return eRequest 3665 */ 3666 public function setAction($action) 3667 { 3668 $this->_action = strtolower(eHelper::dasherize($this->sanitize($action))); 3669 return $this; 3670 } 3671 3672 /** 3673 * Get current route string/array -> module/controller/action 3674 * @param boolean $array 3675 * @return string|array route 3676 */ 3677 public function getRoute($array = false) 3678 { 3679 if(!$this->getModule()) 3680 { 3681 $route = array('index', 'index', 'index'); 3682 } 3683 else 3684 { 3685 $route = array( 3686 $this->getModule(), 3687 $this->getController() ? $this->getController() : 'index', 3688 $this->getAction() ? $this->getAction() : 'index', 3689 ); 3690 } 3691 return ($array ? $route : implode('/', $route)); 3692 } 3693 3694 /** 3695 * Set current route 3696 * @param string $route module/controller/action 3697 * @return eRequest 3698 */ 3699 public function setRoute($route) 3700 { 3701 if(null === $route) 3702 { 3703 $this->_module = null; 3704 $this->_controller = null; 3705 $this->_action = null; 3706 } 3707 return $this->initFromRoute($route); 3708 } 3709 3710 /** 3711 * System routing track, used in controllers forwarder 3712 * @param string $route 3713 * @return eRequest 3714 */ 3715 public function addRouteHistory($route) 3716 { 3717 $this->_routeHistory[] = $route; 3718 return $this; 3719 } 3720 3721 /** 3722 * Retrieve route from history track 3723 * Based on $source we can retrieve 3724 * - array of all history records 3725 * - 'first' route record 3726 * - 'last' route record 3727 * - history record by its index number 3728 * @param mixed $source 3729 * @return string|array 3730 */ 3731 public function getRouteHistory($source = null) 3732 { 3733 if(null === $source) return $this->_routeHistory; 3734 3735 if(!$this->_routeHistory) return null; 3736 elseif('last' === $source) 3737 { 3738 return $this->_routeHistory[count($this->_routeHistory) -1]; 3739 } 3740 elseif('first' === $source) 3741 { 3742 return $this->_routeHistory[0]; 3743 } 3744 elseif(is_int($source)) 3745 { 3746 return isset($this->_routeHistory[$source]) ? $this->_routeHistory[$source] : null; 3747 } 3748 return null; 3749 } 3750 3751 /** 3752 * Search route history for the given $route 3753 * 3754 * @param string $route 3755 * @return integer route index or false if not found 3756 */ 3757 public function findRouteHistory($route) 3758 { 3759 return array_search($route, $this->_routeHistory); 3760 } 3761 3762 /** 3763 * Populate module, controller and action from route string 3764 * @param string $route 3765 * @return array route data 3766 */ 3767 public function initFromRoute($route) 3768 { 3769 $route = trim($route, '/'); 3770 if(!$route) 3771 { 3772 $route = 'index/index/index'; 3773 } 3774 $parts = explode('/', $route); 3775 $this->setModule($parts[0]) 3776 ->setController(vartrue($parts[1], 'index')) 3777 ->setAction(vartrue($parts[2], 'index')); 3778 3779 return $this;//->getRoute(true); 3780 } 3781 3782 /** 3783 * Get request parameter 3784 * @param string $key 3785 * @param string $default value if key not set 3786 * @return mixed value 3787 */ 3788 public function getRequestParam($key, $default = null) 3789 { 3790 return (isset($this->_requestParams[$key]) ? $this->_requestParams[$key] : $default); 3791 } 3792 3793 /** 3794 * Check if request parameter exists 3795 * @param string $key 3796 * @return boolean 3797 */ 3798 public function isRequestParam($key) 3799 { 3800 return isset($this->_requestParams[$key]); 3801 } 3802 3803 /** 3804 * Get request parameters array 3805 * @return array value 3806 */ 3807 public function getRequestParams() 3808 { 3809 return $this->_requestParams; 3810 } 3811 3812 /** 3813 * Set request parameter 3814 * @param string $key 3815 * @param mixed $value 3816 * @return eRequest 3817 */ 3818 public function setRequestParam($key, $value) 3819 { 3820 $this->_requestParams[$key] = $value; 3821 return $this; 3822 } 3823 3824 /** 3825 * Set request parameters 3826 * @param array $params 3827 * @return eRequest 3828 */ 3829 public function setRequestParams($params) 3830 { 3831 $this->_requestParams = $params; 3832 return $this; 3833 } 3834 3835 /** 3836 * Populate current request parameters (_GET scope) 3837 * @return eRequest 3838 */ 3839 public function populateRequestParams() 3840 { 3841 $rp = $this->getRequestParams(); 3842 3843 foreach ($rp as $key => $value) 3844 { 3845 $_GET[$key] = $value; 3846 } 3847 return $this; 3848 } 3849 3850 /** 3851 * More BC 3852 * @param string $qstring 3853 * @return eRequest 3854 */ 3855 public function setLegacyQstring($qstring = null) 3856 { 3857 if(defined('e_QUERY')) return $this; 3858 3859 if(null === $qstring) 3860 { 3861 $qstring = self::getQueryString(); 3862 } 3863 3864 if(!defined('e_SELF')) 3865 { 3866 define("e_SELF", e_REQUEST_SELF); 3867 } 3868 3869 if(!defined('e_QUERY')) 3870 { 3871 define("e_QUERY", $qstring); 3872 } 3873 3874 $_SERVER['QUERY_STRING'] = e_QUERY; 3875 3876 if(strpos(e_QUERY,"=")!==false ) // Fix for legacyQuery using $_GET ie. ?x=y&z=1 etc. 3877 { 3878 parse_str(str_replace(array('&'), array('&'), e_QUERY),$tmp); 3879 foreach($tmp as $key=>$value) 3880 { 3881 $_GET[$key] = $value; 3882 } 3883 } 3884 3885 return $this; 3886 } 3887 3888 /** 3889 * And More BC :/ 3890 * @param string $page 3891 * @return eRequest 3892 */ 3893 public function setLegacyPage($page = null) 3894 { 3895 if(defined('e_PAGE')) return $this; 3896 if(null === $page) 3897 { 3898 $page = eFront::isLegacy(); 3899 } 3900 if(!$page) 3901 { 3902 define('e_PAGE', $this->singleEntry); 3903 } 3904 else define('e_PAGE', basename(str_replace(array('{', '}'), '/', $page))); 3905 return $this; 3906 } 3907 3908 /** 3909 * And More from the same - BC :/ 3910 * @return string 3911 */ 3912 public static function getQueryString() 3913 { 3914 $qstring = ''; 3915 if($_SERVER['QUERY_STRING']) 3916 { 3917 $qstring = str_replace(array('{', '}', '%7B', '%7b', '%7D', '%7d'), '', rawurldecode($_SERVER['QUERY_STRING'])); 3918 } 3919 $qstring = str_replace('&', '&', e107::getParser()->post_toForm($qstring)); 3920 return $qstring; 3921 } 3922 3923 /** 3924 * Basic sanitize method for module, controller and action input values 3925 * @param string $str string to be sanitized 3926 * @param string $pattern optional replace pattern 3927 * @param string $replace optional replace string, defaults to dash 3928 */ 3929 public function sanitize($str, $pattern='', $replace='-') 3930 { 3931 if (!$pattern) $pattern = '/[^\w\pL-]/u'; 3932 3933 return preg_replace($pattern, $replace, $str); 3934 } 3935 3936 /** 3937 * Set dispatched status of the request 3938 * @param boolean $mod 3939 * @return eRequest 3940 */ 3941 public function setDispatched($mod) 3942 { 3943 $this->_dispatched = $mod ? true : false; 3944 return $this; 3945 } 3946 3947 /** 3948 * Get dispatched status of the request 3949 * @return boolean 3950 */ 3951 public function isDispatched() 3952 { 3953 return $this->_dispatched; 3954 } 3955} 3956 3957class eResponse 3958{ 3959 protected $_body = array('default' => ''); 3960 protected $_title = array('default' => array()); 3961 protected $_e_PAGETITLE = array(); 3962 protected $_META_DESCRIPTION = array(); 3963 protected $_META_KEYWORDS = array(); 3964 protected $_render_mod = array('default' => 'default'); 3965 protected $_meta_title_separator = ' - '; 3966 protected $_meta_name_only = array('keywords', 'viewport', 'robots'); // Keep FB happy. 3967 protected $_meta_property_only = array('article:section', 'article:tag'); // Keep FB happy. 3968 protected $_meta = array(); 3969 protected $_meta_robot_types = array('noindex'=>'NoIndex', 'nofollow'=>'NoFollow','noarchive'=>'NoArchive','noimageindex'=>'NoImageIndex' ); 3970 protected $_title_separator = ' » '; 3971 protected $_content_type = 'html'; 3972 protected $_content_type_arr = array( 3973 'html' => 'text/html', 3974 'css' => 'text/css', 3975 'xml' => 'text/xml', 3976 'json' => 'application/json', 3977 'js' => 'application/javascript', 3978 'rss' => 'application/rss+xml', 3979 'soap' => 'application/soap+xml', 3980 ); 3981 3982 protected $_params = array( 3983 'render' => true, 3984 'meta' => false, 3985 'jsonNoTitle' => false, 3986 'jsonRender' => false, 3987 ); 3988 3989 public function getRobotTypes() 3990 { 3991 return $this->_meta_robot_types; 3992 } 3993 3994 public function getRobotDescriptions() 3995 { 3996 $_meta_robot_descriptions = array( 3997 'noindex' => LAN_ROBOTS_NOINDEX, 3998 'nofollow' => LAN_ROBOTS_NOFOLLOW, 3999 'noarchive' => LAN_ROBOTS_NOARCHIVE, 4000 'noimageindex' => LAN_ROBOTS_NOIMAGE ); 4001 4002 return $_meta_robot_descriptions; 4003 } 4004 4005 public function setParam($key, $value) 4006 { 4007 $this->_params[$key] = $value; 4008 return $this; 4009 } 4010 4011 public function setParams($params) 4012 { 4013 $this->_params = $params; 4014 return $this; 4015 } 4016 4017 public function getParam($key, $default = null) 4018 { 4019 return (isset($this->_params[$key]) ? $this->_params[$key] : $default); 4020 } 4021 4022 public function isParam($key) 4023 { 4024 return isset($this->_params[$key]); 4025 } 4026 4027 public function addContentType($typeName, $mediaType) 4028 { 4029 $this->_content_type_arr[$typeName] = $mediaType; 4030 return $this; 4031 } 4032 4033 public function getContentType() 4034 { 4035 return $this->_content_type; 4036 } 4037 4038 public function getContentMediaType($typeName) 4039 { 4040 if(isset($this->_content_type_arr[$typeName])) 4041 return $this->_content_type_arr[$typeName]; 4042 } 4043 4044 public function setContentType($typeName) 4045 { 4046 $this->_content_type = $typeName; 4047 } 4048 4049 /** 4050 * @return eResponse 4051 */ 4052 public function sendContentType() 4053 { 4054 $ctypeStr = $this->getContentMediaType($this->getContentType()); 4055 if($ctypeStr) 4056 { 4057 header('Content-type: '.$this->getContentMediaType($this->getContentType()).'; charset=utf-8', TRUE); 4058 } 4059 return $this; 4060 } 4061 4062 /** 4063 * @return eResponse 4064 */ 4065 public function addHeader($header, $override = false, $responseCode = null) 4066 { 4067 header($header, $override, $responseCode); 4068 return $this; 4069 } 4070 4071 /** 4072 * Append content 4073 * @param string $body 4074 * @param string $ns namespace 4075 * @return eResponse 4076 */ 4077 public function appendBody($body, $ns = 'default') 4078 { 4079 if(!isset($this->_body[$ns])) 4080 { 4081 $this->_body[$ns] = ''; 4082 } 4083 $this->_body[$ns] .= $body; 4084 4085 return $this; 4086 } 4087 4088 /** 4089 * Set content 4090 * @param string $body 4091 * @param string $ns namespace 4092 * @return eResponse 4093 */ 4094 public function setBody($body, $ns = 'default') 4095 { 4096 $this->_body[$ns] = $body; 4097 return $this; 4098 } 4099 4100 4101 /** 4102 * @param $name 4103 * @param $content 4104 * @return $this 4105 */ 4106 public function setMeta($name, $content) 4107 { 4108 foreach($this->_meta as $k=>$v) 4109 { 4110 if($v['name'] === $name) 4111 { 4112 $this->_meta[$k]['content'] = $content; 4113 } 4114 } 4115 4116 return $this; 4117 4118 } 4119 4120 4121 /** 4122 * Removes a Meta tag by name/property. 4123 * 4124 * @param string $name 4125 * 'name' or 'property' for the meta tag we want to remove. 4126 * 4127 * @return eResponse $this 4128 */ 4129 public function removeMeta($name) 4130 { 4131 foreach($this->_meta as $k=>$v) 4132 { 4133 // Meta tags like: <meta content="..." name="description" /> 4134 if(isset($v['name']) && $v['name'] === $name) 4135 { 4136 unset($this->_meta[$k]); 4137 continue; 4138 } 4139 4140 // Meta tags like: <meta content="..." property="og:title" /> 4141 if(isset($v['property']) && $v['property'] === $name) 4142 { 4143 unset($this->_meta[$k]); 4144 } 4145 } 4146 4147 return $this; 4148 } 4149 4150 4151 /** 4152 * Prepend content 4153 * @param string $body 4154 * @param string $ns namespace 4155 * @return eResponse 4156 */ 4157 function prependBody($body, $ns = 'default') 4158 { 4159 if(!isset($this->_body[$ns])) 4160 { 4161 $this->_body[$ns] = ''; 4162 } 4163 // $this->_body[$ns] = $content.$this->_body[$ns]; 4164 4165 return $this; 4166 } 4167 4168 /** 4169 * Get content 4170 * @param string $ns 4171 * @param boolean $reset 4172 * @return string 4173 */ 4174 public function getBody($ns = 'default', $reset = false) 4175 { 4176 if(!isset($this->_body[$ns])) 4177 { 4178 $this->_body[$ns] = ''; 4179 } 4180 $ret = $this->_body[$ns]; 4181 if($reset) unset($this->_body[$ns]); 4182 4183 return $ret; 4184 } 4185 4186 /** 4187 * @param string $title 4188 * @param string $ns 4189 * @return eResponse 4190 */ 4191 function setTitle($title, $ns = 'default') 4192 { 4193 4194 if(!is_string($ns) || empty($ns)) 4195 { 4196 $this->_title['default'] = array((string) $title); 4197 } 4198 else 4199 { 4200 $this->_title[$ns] = array((string) $title); 4201 } 4202 return $this; 4203 } 4204 4205 /** 4206 * @param string $title 4207 * @param string $ns 4208 * @return eResponse 4209 */ 4210 function appendTitle($title, $ns = 'default') 4211 { 4212 if(empty($title)) 4213 { 4214 return $this; 4215 } 4216 if(!is_string($ns) || empty($ns)) 4217 { 4218 $ns = 'default'; 4219 } 4220 elseif(!isset($this->_title[$ns])) 4221 { 4222 $this->_title[$ns] = array(); 4223 } 4224 $this->_title[$ns][] = (string) $title; 4225 return $this; 4226 } 4227 4228 /** 4229 * @param string $title 4230 * @param string $ns 4231 * @return eResponse 4232 */ 4233 function prependTitle($title, $ns = 'default') 4234 { 4235 if(empty($title)) 4236 { 4237 return $this; 4238 } 4239 if(!is_string($ns) || empty($ns)) 4240 { 4241 $ns = 'default'; 4242 } 4243 elseif(!isset($this->_title[$ns])) 4244 { 4245 $this->_title[$ns] = array(); 4246 } 4247 array_unshift($this->_title[$ns], $title); 4248 return $this; 4249 } 4250 4251 /** 4252 * Assemble title 4253 * @param string $ns 4254 * @param bool $reset 4255 * @return string 4256 */ 4257 function getTitle($ns = 'default', $reset = false) 4258 { 4259 if(!is_string($ns) || empty($ns)) 4260 { 4261 $ret = implode($this->_title_separator, $this->_title['default']); 4262 if($reset) 4263 $this->_title['default'] = ''; 4264 } 4265 elseif(isset($this->_title[$ns])) 4266 { 4267 $ret = implode($this->_title_separator, $this->_title[$ns]); 4268 if($reset) 4269 unset($this->_title[$ns]); 4270 } 4271 else 4272 { 4273 $ret = ''; 4274 } 4275 return $ret; 4276 } 4277 4278 /** 4279 * 4280 * @param string $render_mod 4281 * @param mixed $ns 4282 * @return eResponse 4283 */ 4284 function setRenderMod($render_mod, $ns = 'default') 4285 { 4286 $this->_render_mod[$ns] = $render_mod; 4287 return $this; 4288 } 4289 4290 /** 4291 * Retrieve render mod 4292 * @param mixed $ns 4293 * @return mixed 4294 */ 4295 function getRenderMod($ns = 'default') 4296 { 4297 if(!is_string($ns) || empty($ns)) 4298 { 4299 $ns = 'default'; 4300 } 4301 return vartrue($this->_render_mod[$ns], null); 4302 } 4303 4304 /** 4305 * Generic meta information 4306 * Example usage: 4307 * addMeta('og:title', 'My Title'); 4308 * addMeta(null, 30, array('http-equiv' => 'refresh')); 4309 * addMeta(null, null, array('http-equiv' => 'refresh', 'content' => 30)); // same as above 4310 * @param string $name 'name' attribute value, or null to avoid it 4311 * @param string $content 'content' attribute value, or null to avoid it 4312 * @param array $extended format 'attribute_name' => 'value' 4313 * @return eResponse 4314 */ 4315 public function addMeta($name = null, $content = null, $extended = array()) 4316 { 4317 if(empty($content)){ return $this; } // content is required, otherwise ignore. 4318 4319 //TODO need an option that allows subsequent entries to overwrite existing ones. 4320 //ie. 'description' and 'keywords' should never be duplicated, but overwritten by plugins and other non-pref-based meta data. 4321 4322 4323 4324 4325 $attr = array(); 4326 4327 if(null !== $name) 4328 { 4329 // $key = (substr($name,0,3) == 'og:') ? 'property' : 'name'; 4330 // $attr[$key] = $name; 4331 if(!in_array($name, $this->_meta_name_only)) 4332 { 4333 $attr['property'] = $name; // giving both should be valid and avoid issues with FB and others. 4334 } 4335 4336 if(!in_array($name, $this->_meta_property_only)) 4337 { 4338 $attr['name'] = $name; 4339 } 4340 } 4341 4342 4343 4344 if(null !== $content) $attr['content'] = $content; 4345 if(!empty($extended)) 4346 { 4347 if(!empty($attr)) $attr = array_merge($attr, $extended); 4348 else $attr = $extended; 4349 } 4350 4351 4352 if(!empty($attr)) 4353 { 4354 if($name === 'keywords') // prevent multiple keyword tags. 4355 { 4356 $this->_meta['keywords'] = $attr; 4357 } 4358 else 4359 { 4360 $this->_meta[] = $attr; 4361 } 4362 } 4363 4364 return $this; 4365 } 4366 4367 /** 4368 * Render meta tags, registered via addMeta() method 4369 * @return string 4370 */ 4371 public function renderMeta() 4372 { 4373 $attrData = ''; 4374 4375 e107::getEvent()->trigger('system_meta_pre', $this->_meta); 4376 4377 $pref = e107::getPref(); 4378 4379 if(!empty($pref['meta_keywords'][e_LANGUAGE])) // Always append (global) meta keywords to the end. 4380 { 4381 $tmp1 = (array) explode(",", $this->getMetaKeywords()); 4382 $tmp2 = (array) explode(",", $pref['meta_keywords'][e_LANGUAGE]); 4383 4384 $tmp3 = array_unique(array_merge($tmp1,$tmp2)); 4385 4386 $this->setMeta('keywords', implode(',',$tmp3)); 4387 } 4388 4389 4390 4391 e107::getDebug()->log($this->_meta); 4392 4393 4394 foreach ($this->_meta as $attr) 4395 { 4396 $attrData .= '<meta'; 4397 foreach ($attr as $p => $v) 4398 { 4399 $attrData .= ' '.preg_replace('/[^\w\-]/', '', $p).'="'.str_replace(array('"', '<'), '', $v).'"'; 4400 } 4401 $attrData .= ' />'."\n"; 4402 } 4403 4404 return $attrData; 4405 } 4406 4407 /** 4408 * Add meta title, description and keywords 4409 * 4410 * @param string $meta property name 4411 * @param string $content meta content 4412 * @return eResponse 4413 */ 4414 function addMetaData($meta, $content) 4415 { 4416 $meta = '_' . $meta; 4417 if(isset($this->$meta) && !empty($content)) 4418 { 4419 $content = str_replace(array('&', '"', "'"), array('&', '', ''), $content); 4420 $this->{$meta}[] = htmlspecialchars((string) $content, ENT_QUOTES, 'UTF-8'); 4421 } 4422 return $this; 4423 } 4424 4425 /** 4426 * Get meta title, description and keywords 4427 * 4428 * @param string $meta property name 4429 * @return string 4430 */ 4431 function getMetaData($meta, $separator = '') 4432 { 4433 $meta = '_' . $meta; 4434 if(isset($this->$meta) && !empty($this->$meta)) 4435 { 4436 return implode($separator, $this->$meta); 4437 } 4438 return ''; 4439 } 4440 4441 4442 4443 /** 4444 * Return an array of all meta data 4445 * @return array 4446 */ 4447 function getMeta() 4448 { 4449 return $this->_meta; 4450 } 4451 4452 4453 /** 4454 * @param string $title 4455 * @return eResponse 4456 */ 4457 function addMetaTitle($title) 4458 { 4459 return $this->addMetaData('e_PAGETITLE', $title); 4460 } 4461 4462 function getMetaTitle() 4463 { 4464 return $this->getMetaData('e_PAGETITLE', $this->_meta_title_separator); 4465 } 4466 4467 /** 4468 * @param string $description 4469 * @return eResponse 4470 */ 4471 function addMetaDescription($description) 4472 { 4473 return $this->addMetaData('META_DESCRIPTION', $description); 4474 } 4475 4476 function getMetaDescription() 4477 { 4478 return $this->getMetaData('META_DESCRIPTION'); 4479 } 4480 4481 /** 4482 * @param string $keywords 4483 * @return eResponse 4484 */ 4485 function addMetaKeywords($keywords) 4486 { 4487 return $this->addMetaData('META_KEYWORDS', $keywords); 4488 } 4489 4490 function getMetaKeywords() 4491 { 4492 return $this->getMetaData('META_KEYWORDS', ','); 4493 } 4494 4495 /** 4496 * Send e107 meta-data 4497 * @return eResponse 4498 */ 4499 function sendMeta() 4500 { 4501 //HEADERF already included or meta content already sent 4502 if(e_AJAX_REQUEST || defined('USER_AREA') || defined('e_PAGETITLE')) 4503 return $this; 4504 4505 if(!defined('e_PAGETITLE') && !empty($this->_e_PAGETITLE)) 4506 { 4507 define('e_PAGETITLE', $this->getMetaTitle()); 4508 } 4509 if(!defined('META_DESCRIPTION') && !empty($this->_META_DESCRIPTION)) 4510 { 4511 define('META_DESCRIPTION', $this->getMetaDescription()); 4512 } 4513 if(!defined('META_KEYWORDS') && !empty($this->_META_KEYWORDS)) 4514 { 4515 define('META_KEYWORDS', $this->getMetaKeywords()); 4516 } 4517 return $this; 4518 } 4519 4520 /** 4521 * Send Response Output - default method 4522 * TODO - ajax send, using js_manager 4523 * @param string $ns namespace/segment 4524 * @param bool $return 4525 * @param bool $render_message append system messages 4526 * @return null|string 4527 */ 4528 function send($ns = null, $return = true, $render_message = true) 4529 { 4530 $content = $this->getBody($ns, true); 4531 $render = $this->getParam('render'); 4532 $meta = $this->getParam('meta'); 4533 4534 $this->sendContentType(); 4535 4536 if($render_message) 4537 { 4538 $content = eMessage::getInstance()->render().$content; 4539 } 4540 4541 if($meta) 4542 { 4543 $this->sendMeta(); 4544 } 4545 4546 //render disabled by the controller 4547 if(!$this->getRenderMod($ns)) 4548 { 4549 $render = false; 4550 } 4551 4552 if($render) 4553 { 4554 $render = e107::getRender(); 4555 if($return) 4556 { 4557 return $render->tablerender($this->getTitle($ns, true), $content, $this->getRenderMod($ns), true); 4558 } 4559 else 4560 { 4561 $render->tablerender($this->getTitle($ns, true), $content, $this->getRenderMod($ns)); 4562 return ''; 4563 } 4564 } 4565 elseif($return) 4566 { 4567 return $content; 4568 } 4569 else 4570 { 4571 print $content; 4572 return ''; 4573 } 4574 } 4575 4576 /** 4577 * Send AJAX Json Response Output - default method 4578 * It's fully compatible with the core dialog.js 4579 * @param array $override override output associative array (header, body and footer keys) 4580 * @param string $ns namespace/segment 4581 * @param bool $render_message append system messages 4582 */ 4583 function sendJson($override = array(), $ns = null, $render_message = true) 4584 { 4585 if(!$ns) $ns = 'default'; 4586 4587 $content = $this->getBody($ns, true); 4588 // separate render parameter for json response, false by default 4589 $render = $this->getParam('jsonRender'); 4590 if($render_message) 4591 { 4592 $content = eMessage::getInstance()->render().$content; 4593 } 4594 4595 //render disabled by the controller 4596 if(!$this->getRenderMod($ns)) 4597 { 4598 $render = false; 4599 } 4600 4601 4602 $title = ''; 4603 if(!$this->getParam('jsonNoTitle')) 4604 { 4605 $titleArray = $this->_title; 4606 $title = isset($titleArray[$ns]) ? array_pop($titleArray[$ns]) : ''; 4607 } 4608 4609 if($render) 4610 { 4611 $render = e107::getRender(); 4612 $content = $render->tablerender($this->getTitle($ns, true), $content, $this->getRenderMod($ns), true); 4613 } 4614 4615 $jshelper = e107::getJshelper(); 4616 $override = array_merge(array( 4617 'header' => $title, 4618 'body' => $content, 4619 // 'footer' => $statusText, // FIXME $statusText has no value. 4620 ), $override); 4621 echo $jshelper->buildJsonResponse($override); 4622 $jshelper->sendJsonResponse(null); 4623 } 4624 4625 /** 4626 * JS manager 4627 * @return e_jsmanager 4628 */ 4629 function getJs() 4630 { 4631 return e107::getJs(); 4632 } 4633} 4634 4635/** 4636 * We move all generic helper functionallity here - a lot of candidates in e107 class 4637 * 4638 */ 4639class eHelper 4640{ 4641 protected static $_classRegEx = '#[^\w\s\-]#'; 4642 protected static $_idRegEx = '#[^\w\-]#'; 4643 protected static $_styleRegEx = '#[^\w\s\-\.;:!]#'; 4644 4645 public static function secureClassAttr($string) 4646 { 4647 return preg_replace(self::$_classRegEx, '', $string); 4648 } 4649 4650 public static function secureIdAttr($string) 4651 { 4652 $string = str_replace(array('/','_'),'-',$string); 4653 return preg_replace(self::$_idRegEx, '', $string); 4654 } 4655 4656 public static function secureStyleAttr($string) 4657 { 4658 return preg_replace(self::$_styleRegEx, '', $string); 4659 } 4660 4661 public static function buildAttr($safeArray) 4662 { 4663 return http_build_query($safeArray, null, '&'); 4664 } 4665 4666 public static function formatMetaTitle($title) 4667 { 4668 $title = trim(str_replace(array('"', "'"), '', strip_tags(e107::getParser()->toHTML($title, TRUE)))); 4669 return trim(preg_replace('/[\s,]+/', ' ', str_replace('_', ' ', $title))); 4670 } 4671 4672 public static function secureSef($sef) 4673 { 4674 return trim(preg_replace('/[^\w\pL\s\-+.,]+/u', '', strip_tags(e107::getParser()->toHTML($sef, TRUE)))); 4675 } 4676 4677 public static function formatMetaKeys($keywordString) 4678 { 4679 $keywordString = preg_replace('/[^\w\pL\s\-.,+]/u', '', strip_tags(e107::getParser()->toHTML($keywordString, TRUE))); 4680 return trim(preg_replace('/[\s]?,[\s]?/', ',', str_replace('_', ' ', $keywordString))); 4681 } 4682 4683 public static function formatMetaDescription($descrString) 4684 { 4685 $descrString = preg_replace('/[\r]*\n[\r]*/', ' ', trim(str_replace(array('"', "'"), '', strip_tags(e107::getParser()->toHTML($descrString, TRUE))))); 4686 return trim(preg_replace('/[\s]+/', ' ', str_replace('_', ' ', $descrString))); 4687 } 4688 4689 /** 4690 * Convert title to valid SEF URL string 4691 * Type ending with 'l' stands for 'to lowercase', ending with 'c' - 'to camel case' 4692 * @param string $title 4693 * @param string $type dashl|dashc|dash|underscorel|underscorec|underscore|plusl|plusc|plus|none 4694 * @return mixed|string 4695 */ 4696 public static function title2sef($title, $type = null) 4697 { 4698 /*$char_map = array( 4699 // Latin 4700 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'AE', 'Ç' => 'C', 4701 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', 4702 'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ő' => 'O', 4703 'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ű' => 'U', 'Ý' => 'Y', 'Þ' => 'TH', 4704 'ß' => 'ss', 4705 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'ae', 'ç' => 'c', 4706 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 4707 'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ő' => 'o', 4708 'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ű' => 'u', 'ý' => 'y', 'þ' => 'th', 4709 'ÿ' => 'y', 4710 // Latin symbols 4711 '©' => '(c)', 4712 // Greek 4713 'Α' => 'A', 'Β' => 'B', 'Γ' => 'G', 'Δ' => 'D', 'Ε' => 'E', 'Ζ' => 'Z', 'Η' => 'H', 'Θ' => '8', 4714 'Ι' => 'I', 'Κ' => 'K', 'Λ' => 'L', 'Μ' => 'M', 'Ν' => 'N', 'Ξ' => '3', 'Ο' => 'O', 'Π' => 'P', 4715 'Ρ' => 'R', 'Σ' => 'S', 'Τ' => 'T', 'Υ' => 'Y', 'Φ' => 'F', 'Χ' => 'X', 'Ψ' => 'PS', 'Ω' => 'W', 4716 'Ά' => 'A', 'Έ' => 'E', 'Ί' => 'I', 'Ό' => 'O', 'Ύ' => 'Y', 'Ή' => 'H', 'Ώ' => 'W', 'Ϊ' => 'I', 4717 'Ϋ' => 'Y', 4718 'α' => 'a', 'β' => 'b', 'γ' => 'g', 'δ' => 'd', 'ε' => 'e', 'ζ' => 'z', 'η' => 'h', 'θ' => '8', 4719 'ι' => 'i', 'κ' => 'k', 'λ' => 'l', 'μ' => 'm', 'ν' => 'n', 'ξ' => '3', 'ο' => 'o', 'π' => 'p', 4720 'ρ' => 'r', 'σ' => 's', 'τ' => 't', 'υ' => 'y', 'φ' => 'f', 'χ' => 'x', 'ψ' => 'ps', 'ω' => 'w', 4721 'ά' => 'a', 'έ' => 'e', 'ί' => 'i', 'ό' => 'o', 'ύ' => 'y', 'ή' => 'h', 'ώ' => 'w', 'ς' => 's', 4722 'ϊ' => 'i', 'ΰ' => 'y', 'ϋ' => 'y', 'ΐ' => 'i', 4723 // Turkish 4724 'Ş' => 'S', 'İ' => 'I', 'Ç' => 'C', 'Ü' => 'U', 'Ö' => 'O', 'Ğ' => 'G', 4725 'ş' => 's', 'ı' => 'i', 'ç' => 'c', 'ü' => 'u', 'ö' => 'o', 'ğ' => 'g', 4726 // Russian 4727 'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', 'Е' => 'E', 'Ё' => 'Yo', 'Ж' => 'Zh', 4728 'З' => 'Z', 'И' => 'I', 'Й' => 'J', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', 'О' => 'O', 4729 'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C', 4730 'Ч' => 'Ch', 'Ш' => 'Sh', 'Щ' => 'Sh', 'Ъ' => '', 'Ы' => 'Y', 'Ь' => '', 'Э' => 'E', 'Ю' => 'Yu', 4731 'Я' => 'Ya', 4732 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', 'ё' => 'yo', 'ж' => 'zh', 4733 'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o', 4734 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', 4735 'ч' => 'ch', 'ш' => 'sh', 'щ' => 'sh', 'ъ' => '', 'ы' => 'y', 'ь' => '', 'э' => 'e', 'ю' => 'yu', 4736 'я' => 'ya', 4737 // Ukrainian 4738 'Є' => 'Ye', 'І' => 'I', 'Ї' => 'Yi', 'Ґ' => 'G', 4739 'є' => 'ye', 'і' => 'i', 'ї' => 'yi', 'ґ' => 'g', 4740 // Czech 4741 'Č' => 'C', 'Ď' => 'D', 'Ě' => 'E', 'Ň' => 'N', 'Ř' => 'R', 'Š' => 'S', 'Ť' => 'T', 'Ů' => 'U', 4742 'Ž' => 'Z', 4743 'č' => 'c', 'ď' => 'd', 'ě' => 'e', 'ň' => 'n', 'ř' => 'r', 'š' => 's', 'ť' => 't', 'ů' => 'u', 4744 'ž' => 'z', 4745 // Polish 4746 'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'e', 'Ł' => 'L', 'Ń' => 'N', 'Ó' => 'o', 'Ś' => 'S', 'Ź' => 'Z', 4747 'Ż' => 'Z', 4748 'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z', 4749 'ż' => 'z', 4750 // Latvian 4751 'Ā' => 'A', 'Č' => 'C', 'Ē' => 'E', 'Ģ' => 'G', 'Ī' => 'i', 'Ķ' => 'k', 'Ļ' => 'L', 'Ņ' => 'N', 4752 'Š' => 'S', 'Ū' => 'u', 'Ž' => 'Z', 4753 'ā' => 'a', 'č' => 'c', 'ē' => 'e', 'ģ' => 'g', 'ī' => 'i', 'ķ' => 'k', 'ļ' => 'l', 'ņ' => 'n', 4754 'š' => 's', 'ū' => 'u', 'ž' => 'z' 4755 );*/ 4756 4757 $tp = e107::getParser(); 4758 4759 // issue #3245: strip all html and bbcode before processing 4760 $title = $tp->toText($title); 4761 4762 $title = $tp->toASCII($title); 4763 4764 $title = str_replace(array('/',' ',","),' ',$title); 4765 $title = str_replace(array("&","(",")"),'',$title); 4766 $title = preg_replace('/[^\w\d\pL\s.-]/u', '', strip_tags(e107::getParser()->toHTML($title, TRUE))); 4767 $title = trim(preg_replace('/[\s]+/', ' ', str_replace('_', ' ', $title))); 4768 $title = str_replace(array(' - ',' -','- ','--'),'-',$title); // cleanup to avoid --- 4769 4770 $words = str_word_count($title,1, '1234567890'); 4771 4772 $limited = array_slice($words, 0, 14); // Limit number of words to 14. - any more and it ain't friendly. 4773 4774 $title = implode(" ",$limited); 4775 4776 if(null === $type) 4777 { 4778 $type = e107::getPref('url_sef_translate'); 4779 } 4780 4781 switch ($type) 4782 { 4783 case 'dashl': //dasherize, to lower case 4784 return self::dasherize($tp->ustrtolower($title)); 4785 break; 4786 4787 case 'dashc': //dasherize, camel case 4788 return self::dasherize(self::camelize($title, true, ' ')); 4789 break; 4790 4791 case 'dash': //dasherize 4792 return self::dasherize($title); 4793 break; 4794 4795 case 'underscorel': ///underscore, to lower case 4796 return self::underscore($tp->ustrtolower($title)); 4797 break; 4798 4799 case 'underscorec': ///underscore, camel case 4800 return self::underscore(self::camelize($title, true, ' ')); 4801 break; 4802 4803 case 'underscore': ///underscore 4804 return self::underscore($title); 4805 break; 4806 4807 case 'plusl': ///plus separator, to lower case 4808 return str_replace(' ', '+', $tp->ustrtolower($title)); 4809 break; 4810 4811 case 'plusc': ///plus separator, to lower case 4812 return str_replace(' ', '+', self::camelize($title, true, ' ')); 4813 break; 4814 4815 case 'plus': ///plus separator 4816 return str_replace(' ', '+', $title); 4817 break; 4818 4819 case 'none': 4820 default: 4821 return $title; 4822 break; 4823 } 4824 } 4825 4826 /** 4827 * Return a memory value formatted helpfully 4828 * $dp overrides the number of decimal places displayed - realistically, only 0..3 are sensible 4829 * FIXME e107->parseMemorySize() START 4830 * - move here all e107 class ban/ip related methods 4831 * - out of (integer) range case? 4832 * 32 bit systems range: -2147483648 to 2147483647 4833 * 64 bit systems range: -9223372036854775808 9223372036854775807 4834 * {@link http://www.php.net/intval} 4835 * FIXME e107->parseMemorySize() END 4836 * 4837 * @param integer $size 4838 * @param integer $dp 4839 * @return string formatted size 4840 */ 4841 public static function parseMemorySize($size, $dp = 2) 4842 { 4843 if (!$size) { $size = 0; } 4844 if ($size < 4096) 4845 { // Fairly arbitrary limit below which we always return number of bytes 4846 return number_format($size, 0).CORE_LAN_B; 4847 } 4848 4849 $size = $size / 1024; 4850 $memunit = CORE_LAN_KB; 4851 4852 if ($size > 1024) 4853 { /* 1.002 mb, etc */ 4854 $size = $size / 1024; 4855 $memunit = CORE_LAN_MB; 4856 } 4857 if ($size > 1024) 4858 { /* show in GB if >1GB */ 4859 $size = $size / 1024; 4860 $memunit = CORE_LAN_GB; 4861 } 4862 if ($size > 1024) 4863 { /* show in TB if >1TB */ 4864 $size = $size / 1024; 4865 $memunit = CORE_LAN_TB; 4866 } 4867 return (number_format($size, $dp).$memunit); 4868 } 4869 4870 /** 4871 * Get the current memory usage of the code 4872 * If $separator argument is null, raw data (array) will be returned 4873 * 4874 * @param null|string $separator 4875 * @return string|array memory usage 4876 */ 4877 public static function getMemoryUsage($separator = '/') 4878 { 4879 $ret = array(); 4880 if(function_exists("memory_get_usage")) 4881 { 4882 $ret[] = eHelper::parseMemorySize(memory_get_usage()); 4883 // With PHP>=5.2.0, can show peak usage as well 4884 if (function_exists("memory_get_peak_usage")) $ret[] = eHelper::parseMemorySize(memory_get_peak_usage(TRUE)); 4885 } 4886 else 4887 { 4888 $ret[] = 'Unknown'; 4889 } 4890 4891 return (null !== $separator ? implode($separator, $ret) : $ret); 4892 } 4893 4894 public static function camelize($str, $all = false, $space = '') 4895 { 4896 // clever recursion o.O 4897 if($all) return self::camelize('-'.$str, false, $space); 4898 4899 $tmp = explode('-', str_replace(array('_', ' '), '-', e107::getParser()->ustrtolower($str))); 4900 return trim(implode($space, array_map('ucfirst', $tmp)), $space); 4901 } 4902 4903 public static function labelize($str, $space = ' ') 4904 { 4905 return self::camelize($str, true, ' '); 4906 } 4907 4908 public static function dasherize($str) 4909 { 4910 return str_replace(array('_', ' '), '-', $str); 4911 } 4912 4913 public static function underscore($str) 4914 { 4915 return str_replace(array('-', ' '), '_', $str); 4916 } 4917 4918 /** 4919 * Parse generic shortcode parameter string 4920 * Format expected: {SC=key=val&key1=val1...} 4921 * Escape strings: \& => & 4922 * 4923 * @param string $parmstr 4924 * @return array associative param array 4925 */ 4926 public static function scParams($parm) 4927 { 4928 if (!$parm) return array(); 4929 if (!is_array($parm)) 4930 { 4931 $parm = str_replace('\&', '%%__amp__%%', $parm); 4932 $parm = str_replace('&', '&', $parm); // clean when it comes from the DB 4933 parse_str($parm, $parm); 4934 foreach ($parm as $k => $v) 4935 { 4936 $parm[str_replace('%%__amp__%%', '&', $k)] = str_replace('%%__amp__%%', '\&', $v); 4937 } 4938 } 4939 4940 return $parm; 4941 } 4942 4943 /** 4944 * Parse shortcode parameter string of type 'dual parameters' - advanced, more complex and slower(!) case 4945 * Format expected: {SC=name|key=val&key1=val1...} 4946 * Escape strings: \| => | , \& => & and \& => & 4947 * Return array is formatted like this: 4948 * 1 => string|array (depends on $name2array value) containing first set of parameters; 4949 * 2 => array containing second set of parameters; 4950 * 3 => string containing second set of parameters; 4951 * 4952 * @param string $parmstr 4953 * @param boolean $first2array If true, first key (1) of the returned array will be parsed to array as well 4954 * @return array 4955 */ 4956 public static function scDualParams($parmstr, $first2array = false) 4957 { 4958 if (!$parmstr) return array(1 => '', 2 => array(), 3 => ''); 4959 if (is_array($parmstr)) return $parmstr; 4960 4961 $parmstr = str_replace('&', '&', $parmstr); // clean when it comes from the DB 4962 $parm = explode('|', str_replace(array('\|', '\&', '\&'), array('%%__pipe__%%', '%%__ampamp__%%', '%%__amp__%%'), $parmstr), 2); 4963 4964 $multi = str_replace('%%__pipe__%%', '|', $parm[0]); 4965 if ($first2array) 4966 { 4967 parse_str($multi, $multi); 4968 foreach ($multi as $k => $v) 4969 { 4970 $multi[str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&', '&'), $k)] = str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&', '&'), $v); 4971 } 4972 } 4973 4974 if (varset($parm[1])) 4975 { 4976 // second paramater as a string - allow to be further passed to shortcodes 4977 $parmstr = str_replace(array('%%__pipe__%%', '%%__ampamp__%%', '%%__amp__%%'), array('\|', '\&', '\&'), $parm[1]); 4978 parse_str(str_replace('%%__pipe__%%', '|', $parm[1]), $params); 4979 foreach ($params as $k => $v) 4980 { 4981 $params[str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&', '&'), $k)] = str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&', '&'), $v); 4982 } 4983 } 4984 else 4985 { 4986 $parmstr = ''; 4987 $params = array(); 4988 } 4989 4990 return array(1 => $multi, 2 => $params, 3 => $parmstr); 4991 } 4992 4993 4994 /** 4995 * Remove Social Media Trackers from a $_GET array based on key matches. 4996 * @param array $get 4997 * @return array 4998 */ 4999 public static function removeTrackers($get = array()) 5000 { 5001 $trackers = array('fbclid','utm_source','utm_medium','utm_content','utm_campaign','elan', 'msclkid', 'gclid'); 5002 5003 foreach($trackers as $val) 5004 { 5005 if(isset($get[$val])) 5006 { 5007 unset($get[$val]); 5008 } 5009 } 5010 5011 return $get; 5012 5013 } 5014 5015 5016} 5017