PageRenderTime 72ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/e107_handlers/application.php

https://github.com/CasperGemini/e107
PHP | 4498 lines | 2653 code | 548 blank | 1297 comment | 437 complexity | 6bafe22ce7e315584503a185db6cb42e MD5 | raw file
Possible License(s): GPL-2.0
  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. * $URL$
  10. * $Id$
  11. */
  12. /**
  13. * @package e107
  14. * @subpackage e107_handlers
  15. * @version $Id$
  16. * @author SecretR
  17. *
  18. * e107 Single Entry Point handling
  19. *
  20. * Currently this file contains all classes required for single entry point functionallity
  21. * They will be separated in different files in a proper way (soon)
  22. */
  23. /**
  24. * e107 Front controller
  25. */
  26. class eFront
  27. {
  28. /**
  29. * Singleton instance
  30. * @var eFront
  31. */
  32. private static $_instance;
  33. /**
  34. * @var eDispatcher
  35. */
  36. protected $_dispatcher;
  37. /**
  38. * @var eRequest
  39. */
  40. protected $_request;
  41. /**
  42. * @var eRouter
  43. */
  44. protected $_router;
  45. /**
  46. * @var string path to file to include - the old deprecated way of delivering content
  47. */
  48. protected static $_legacy = '';
  49. /**
  50. * Constructor
  51. */
  52. private function __construct()
  53. {
  54. }
  55. /**
  56. * Cloning not allowed
  57. *
  58. */
  59. private function __clone()
  60. {
  61. }
  62. /**
  63. * Singleton implementation
  64. * @return eFront
  65. */
  66. public static function instance()
  67. {
  68. if(null == self::$_instance)
  69. {
  70. self::$_instance = new self();
  71. }
  72. return self::$_instance;
  73. }
  74. /**
  75. * Dispatch
  76. */
  77. public function dispatch(eRequest $request = null, eResponse $response = null, eDispatcher $dispatcher = null)
  78. {
  79. if(null === $request)
  80. {
  81. if(null === $this->getRequest())
  82. {
  83. $request = new eRequest();
  84. $this->setRequest($request);
  85. }
  86. else $request = $this->getRequest();
  87. }
  88. elseif(null === $this->getRequest()) $this->setRequest($request);
  89. if(null === $response)
  90. {
  91. if(null === $this->getResponse())
  92. {
  93. $response = new eResponse();
  94. $this->setResponse($response);
  95. }
  96. else $response = $this->getResponse();
  97. }
  98. elseif(null === $this->getRequest()) $this->setRequest($request);
  99. if(null === $dispatcher)
  100. {
  101. if(null === $this->getDispatcher())
  102. {
  103. $dispatcher = new eDispatcher();
  104. $this->setDispatcher($dispatcher);
  105. }
  106. else $dispatcher = $this->getDispatcher();
  107. }
  108. elseif(null === $this->getDispatcher()) $this->setDispatcher($dispatcher);
  109. // set dispatched status true, required for checkLegacy()
  110. $request->setDispatched(true);
  111. $router = $this->getRouter();
  112. // If current request not already routed outside the dispatch method, route it
  113. if(!$request->routed) $router->route($request);
  114. $c = 0;
  115. // dispatch loop
  116. do
  117. {
  118. $c++;
  119. if($c > 100)
  120. {
  121. throw new eException("Too much dispatch loops", 1);
  122. }
  123. // dispatched status true on first loop
  124. $router->checkLegacy($request);
  125. // dispatched by default - don't allow legacy to alter dispatch status
  126. $request->setDispatched(true);
  127. // legacy mod - return control to the bootstrap
  128. if(self::isLegacy())
  129. {
  130. return;
  131. }
  132. // for the good players - dispatch loop - no more BC!
  133. try
  134. {
  135. $dispatcher->dispatch($request, $response);
  136. }
  137. catch(eException $e)
  138. {
  139. echo $request->getRoute().' - '.$e->getMessage();
  140. exit;
  141. }
  142. } while (!$request->isDispatched());
  143. }
  144. /**
  145. * Init all objects required for request dispatching
  146. * @return eFront
  147. */
  148. public function init()
  149. {
  150. $request = new eRequest();
  151. $this->setRequest($request);
  152. $dispatcher = new eDispatcher();
  153. $this->setDispatcher($dispatcher);
  154. $router = new eRouter();
  155. $this->setRouter($router);
  156. $response = new eResponse();
  157. $this->setResponse($response);
  158. return $this;
  159. }
  160. /**
  161. * Dispatch
  162. * @param string|eRequest $route
  163. */
  164. public function run($route = null)
  165. {
  166. if($route)
  167. {
  168. if(is_object($route) && ($route instanceof eRequest)) $this->setRequest($route);
  169. elseif(null !== $route && null !== $this->getRequest()) $this->getRequest()->setRoute($route);
  170. }
  171. try
  172. {
  173. $this->dispatch();
  174. }
  175. catch(eException $e)
  176. {
  177. echo $e->getMessage();
  178. exit;
  179. }
  180. }
  181. /**
  182. * Application instance (e107 class)
  183. * @return e107
  184. */
  185. public static function app()
  186. {
  187. return e107::getInstance();
  188. }
  189. /**
  190. * Get dispatcher instance
  191. * @return eDispatcher
  192. */
  193. public function getDispatcher()
  194. {
  195. return $this->_dispatcher;
  196. }
  197. /**
  198. * Set dispatcher
  199. * @param eDispatcher $dispatcher
  200. * @return eFront
  201. */
  202. public function setDispatcher(eDispatcher $dispatcher)
  203. {
  204. $this->_dispatcher = $dispatcher;
  205. return $this;
  206. }
  207. /**
  208. * Get request instance
  209. * @return eRequest
  210. */
  211. public function getRequest()
  212. {
  213. return $this->_request;
  214. }
  215. /**
  216. * Set request
  217. * @param eRequest $request
  218. * @return eFront
  219. */
  220. public function setRequest(eRequest $request)
  221. {
  222. $this->_request = $request;
  223. return $this;
  224. }
  225. /**
  226. * Get response instance
  227. * @return eResponse
  228. */
  229. public function getResponse()
  230. {
  231. return $this->_response;
  232. }
  233. /**
  234. * Set response
  235. * @param eResponse $response
  236. * @return eFront
  237. */
  238. public function setResponse(eResponse $response)
  239. {
  240. $this->_response = $response;
  241. return $this;
  242. }
  243. /**
  244. * Get router instance
  245. * @return eRouter
  246. */
  247. public function getRouter()
  248. {
  249. return $this->_router;
  250. }
  251. /**
  252. * Set router instance
  253. * @return eFront
  254. */
  255. public function setRouter(eRouter $router)
  256. {
  257. $this->_router = $router;
  258. return $this;
  259. }
  260. /**
  261. * Set/get legacy status of the current request
  262. * @param boolean $status
  263. * @return boolean
  264. */
  265. public static function isLegacy($status = null)
  266. {
  267. if(null !== $status)
  268. {
  269. if($status[0] === '{')
  270. {
  271. $status = e107::getParser()->replaceConstants($status);
  272. }
  273. self::$_legacy = $status;
  274. }
  275. return self::$_legacy;
  276. }
  277. }
  278. /**
  279. * e107 Dispatcher
  280. * It decides how to dispatch the request.
  281. */
  282. class eDispatcher
  283. {
  284. protected static $_configObjects = array();
  285. public function dispatch(eRequest $request = null, eResponse $response = null)
  286. {
  287. $controllerName = $request->getControllerName();
  288. $moduleName = $request->getModuleName();
  289. $className = $this->isDispatchable($request, false);
  290. // dispatch based on rule settings
  291. if(!$className)
  292. {
  293. if($controllerName == 'index') // v2.x upgrade has not been run yet.
  294. {
  295. e107::getRedirect()->redirect(e_ADMIN."admin.php");
  296. }
  297. throw new eException("Invalid controller '".$controllerName."'");
  298. }
  299. $controller = new $className($request, $response);
  300. if(!($controller instanceof eController))
  301. {
  302. throw new eException("Controller $controller is not an instance of eController");
  303. }
  304. $actionName = $request->getActionMethodName();
  305. ob_start();
  306. $controller->dispatch($actionName);
  307. $content = ob_get_contents();
  308. ob_end_clean();
  309. $response->appendBody($content);
  310. unset($controller);
  311. }
  312. /**
  313. * Get path to the e_url handler
  314. * @param string $module
  315. * @param string $location plugin|core|override
  316. * @param boolean $sc
  317. * @return string path
  318. */
  319. public static function getConfigPath($module, $location, $sc = false)
  320. {
  321. $tmp = explode('/', $location);
  322. $custom = '';
  323. $location = $tmp[0];
  324. if(isset($tmp[1]) && !empty($tmp[1]))
  325. {
  326. $custom = $tmp[1].'_';
  327. }
  328. unset($tmp);
  329. if($module !== '*') $module .= '/';
  330. switch ($location)
  331. {
  332. case 'plugin':
  333. //if($custom) $custom = 'url/'.$custom;
  334. return $sc ? '{e_PLUGIN}'.$module.'url/'.$custom.'url.php' : e_PLUGIN.$module.'url/'.$custom.'url.php';
  335. break;
  336. case 'core':
  337. if($module === '*') return $sc ? '{e_CORE}url/' : e_CORE.'url/';
  338. return $sc ? '{e_CORE}url/'.$module.$custom.'url.php' : e_CORE.'url/'.$module.$custom.'url.php';
  339. break;
  340. case 'override':
  341. if($module === '*') return $sc ? '{e_CORE}override/url/' : e_CORE.'override/url/' ;
  342. return $sc ? '{e_CORE}override/url/'.$module.$custom.'url.php' : e_CORE.'override/url/'.$module.$custom.'url.php' ;
  343. break;
  344. default:
  345. return null;
  346. break;
  347. }
  348. }
  349. /**
  350. * Get path to url configuration subfolders
  351. * @param string $module
  352. * @param string $location plugin|core|override
  353. * @param boolean $sc
  354. * @return string path
  355. */
  356. public static function getConfigLocationPath($module, $location, $sc = false)
  357. {
  358. switch ($location)
  359. {
  360. case 'plugin':
  361. return $sc ? '{e_PLUGIN}'.$module.'/url/' : e_PLUGIN.$module.'/url/';
  362. break;
  363. case 'core':
  364. return $sc ? '{e_CORE}url/'.$module.'/' : e_CORE.'url/'.$module.'/';
  365. break;
  366. case 'override':
  367. return $sc ? '{e_CORE}override/url/'.$module.'/' : e_CORE.'override/url/'.$module.'/';
  368. break;
  369. default:
  370. return null;
  371. break;
  372. }
  373. }
  374. /**
  375. * Get dispatch system path
  376. * @param string $location plugin|core|override
  377. * @param string $plugin required only when $location is plugin
  378. * @param boolean $sc
  379. * @return string path
  380. */
  381. public static function getDispatchLocationPath($location, $plugin = null, $sc = false)
  382. {
  383. switch ($location)
  384. {
  385. case 'plugin':
  386. if(!$plugin) return null;
  387. return $sc ? '{e_PLUGIN}'.$plugin.'/controllers/' : e_PLUGIN.$plugin.'/controllers/';
  388. break;
  389. case 'core':
  390. return $sc ? '{e_CORE}controllers/' : e_CORE.'controllers/';
  391. break;
  392. case 'override':
  393. return $sc ? '{e_CORE}override/controllers/' : e_CORE.'override/controllers/';
  394. break;
  395. default:
  396. return null;
  397. break;
  398. }
  399. }
  400. /**
  401. * Get full dispatch system path
  402. * @param string $module
  403. * @param string $location plugin|core|override
  404. * @param boolean $sc
  405. * @return string path
  406. */
  407. public static function getDispatchPath($module, $location, $sc = false)
  408. {
  409. switch ($location)
  410. {
  411. case 'plugin':
  412. return $sc ? '{e_PLUGIN}'.$module.'/controllers/' : e_PLUGIN.$module.'/controllers/';
  413. break;
  414. case 'core':
  415. return $sc ? '{e_CORE}controllers/'.$module.'/' : e_CORE.'controllers/'.$module.'/';
  416. break;
  417. case 'override':
  418. return $sc ? '{e_CORE}override/controllers/'.$module.'/' : e_CORE.'override/controllers/'.$module.'/';
  419. break;
  420. default:
  421. return null;
  422. break;
  423. }
  424. }
  425. /**
  426. * Get include path to a given module/controller
  427. *
  428. * @param string $module valid module name
  429. * @param string $controller controller name
  430. * @param string $location core|plugin|override
  431. * @param boolean $sc return relative (false) OR shortcode (true) path
  432. * @return string controller path
  433. */
  434. public static function getControllerPath($module, $controller, $location = null, $sc = false)
  435. {
  436. if(null === $location) $location = self::getDispatchLocation($module);
  437. return ($location ? self::getDispatchPath($module, $location, $sc).$controller.'.php': null);
  438. }
  439. /**
  440. * Get class name of a given module/controller
  441. *
  442. * @param string $module valid module name
  443. * @param string $controllerName controller name
  444. * @param string $location core|plugin|override
  445. * @return string controller path
  446. */
  447. public static function getControllerClass($module, $controllerName, $location = null)
  448. {
  449. if(null === $location) $location = self::getDispatchLocation($module);
  450. return ($location ? $location.'_'.$module.'_'.$controllerName.'_controller' : null);
  451. }
  452. /**
  453. * Get controller object
  454. *
  455. * @param eRequest $request
  456. * @param boolean $checkOverride whether to check the override location
  457. * @return eController null if not dispatchable
  458. */
  459. public function getController(eRequest $request, $checkOverride = true)
  460. {
  461. $class_name = $this->isDispatchable($request, true, $checkOverride);
  462. if(!$class_name) return null;
  463. return new $class_name();
  464. }
  465. /**
  466. * Check if given module/controller is dispatchable
  467. * @param string $module valid module name
  468. * @param string $controllerName controller name
  469. * @param string $location core|plugin|override
  470. * @param boolean $checkOverride whether to check the override location
  471. * @return string class name OR false if not dispatchable
  472. */
  473. public function isDispatchableModule($module, $controllerName, $location, $checkOverride = false)
  474. {
  475. if($checkOverride || $location == 'override')
  476. {
  477. $path = self::getControllerPath($module, $controllerName, 'override', false);
  478. $class_name = self::getControllerClass($module, $controllerName, 'override');
  479. if($class_name && !class_exists($class_name, false) && is_readable($path)) include_once($path);
  480. if($class_name && class_exists($class_name, false)) return $class_name;
  481. }
  482. // fallback to original dispatch location if any
  483. if($location === 'override')
  484. {
  485. // check for real location
  486. if(($location = eDispatcher::getModuleRealLocation($module)) === null) return false;
  487. }
  488. if($location !== 'override')
  489. {
  490. $path = self::getControllerPath($module, $controllerName, $location, false);
  491. $class_name = self::getControllerClass($module, $controllerName, $location);
  492. if(!$class_name) return false;
  493. if(!class_exists($class_name, false) && is_readable($path)) include_once($path);
  494. if(class_exists($class_name, false)) return $class_name;
  495. }
  496. return false;
  497. }
  498. /**
  499. * Automated version of self::isDispatchableModule()
  500. * @param eRequest $request
  501. * @param boolean $checkReflection deep check - proper subclassing, action
  502. * @param boolean $checkOverride try override controller folder first
  503. * @return mixed class name OR false if not dispatchable
  504. */
  505. public function isDispatchable(eRequest $request, $checkReflection = false, $checkOverride = true)
  506. {
  507. $location = self::getDispatchLocation($request->getModuleName());
  508. $controllerName = $request->getControllerName();
  509. $moduleName = $request->getModuleName();
  510. $className = false;
  511. // dispatch based on url_config preference value, if config location is override and there is no
  512. // override controller, additional check against real controller location will be made
  513. if($location)
  514. {
  515. $className = $this->isDispatchableModule($moduleName, $controllerName, $location, $checkOverride);
  516. }
  517. //else
  518. //{
  519. # Disable plugin check for routes with no config info - prevent calling of non-installed plugins
  520. # We may allow this for plugins which don't have plugin.xml in the future
  521. // $className = $this->isDispatchableModule($moduleName, $controllerName, 'plugin', $checkOverride);
  522. // if(!$className)
  523. //$className = $this->isDispatchableModule($moduleName, $controllerName, 'core', $checkOverride);
  524. //}
  525. if(empty($className)) return false;
  526. elseif(!$checkReflection) return $className;
  527. $rfl = new ReflectionClass($className);
  528. $method = $request->getActionMethodName();
  529. if($rfl->isSubclassOf('eController') && $rfl->hasMethod($method) && $rfl->getMethod($method)->isPublic() && !$rfl->getMethod($method)->isStatic())
  530. return $className;
  531. return false;
  532. }
  533. /**
  534. * Get class name of a given module config
  535. *
  536. * @param string $module valid module name
  537. * @param string $location core|plugin|override[/custom]
  538. * @return string controller path
  539. */
  540. public static function getConfigClassName($module, $location)
  541. {
  542. $tmp = explode('/', $location);
  543. $custom = '';
  544. $location = $tmp[0];
  545. if(isset($tmp[1]) && !empty($tmp[1]))
  546. {
  547. $custom = $tmp[1].'_';
  548. }
  549. unset($tmp);
  550. $module .= '_';
  551. // we need to prepend location to avoid namespace colisions
  552. return $location.'_'.$module.$custom.'url';
  553. }
  554. /**
  555. * Get config object for a module
  556. *
  557. * @param string $module valid module name
  558. * @param string $location core|plugin|override[/custom]
  559. * @return eUrlConfig
  560. */
  561. public static function getConfigObject($module, $location = null)
  562. {
  563. if(null === $location)
  564. {
  565. $location = self::getModuleConfigLocation($module);
  566. if(!$location) return null;
  567. }
  568. $reg = $module.'/'.$location;
  569. if(isset(self::$_configObjects[$reg])) return self::$_configObjects[$reg];
  570. $className = self::getConfigClassName($module, $location);
  571. if(!class_exists($className, false))
  572. {
  573. $path = self::getConfigPath($module, $location, false);
  574. if(!is_readable($path)) return null;
  575. include_once($path);
  576. if(!class_exists($className, false)) return null;
  577. }
  578. $obj = new $className();
  579. $obj->init();
  580. self::$_configObjects[$reg] = $obj;
  581. $obj = null;
  582. return self::$_configObjects[$reg];
  583. }
  584. /**
  585. * Auto discover module location from stored in core prefs data
  586. * @param string $module
  587. */
  588. public static function getModuleConfigLocation($module)
  589. {
  590. //retrieve from config prefs
  591. return e107::findPref('url_config/'.$module, '');
  592. }
  593. /**
  594. * Auto discover module location from stored in core prefs data
  595. * @param string $module
  596. */
  597. public static function getDispatchLocation($module)
  598. {
  599. //retrieve from prefs
  600. $location = self::getModuleConfigLocation($module);
  601. if(!$location) return null;
  602. if(($pos = strpos($location, '/'))) //can't be 0
  603. {
  604. return substr($location, 0, $pos);
  605. }
  606. return $location;
  607. }
  608. /**
  609. * Auto discover module real location (and not currently set from url adminsitration) from stored in core prefs data
  610. * @param string $module
  611. */
  612. public static function getModuleRealLocation($module)
  613. {
  614. //retrieve from prefs
  615. $searchArray = e107::findPref('url_modules');
  616. if(!$searchArray) return null;
  617. $search = array('core', 'plugin', 'override');
  618. foreach ($search as $location)
  619. {
  620. $_searchArray = vartrue($searchArray[$location], array());
  621. if(in_array($module, $_searchArray)) return $location;
  622. }
  623. return null;
  624. }
  625. }
  626. /**
  627. * URL manager - parse and create URLs based on rules set
  628. * Inspired by Yii Framework UrlManager <www.yiiframework.com>
  629. */
  630. class eRouter
  631. {
  632. /**
  633. * Configuration array containing all available syste routes and route object configuration values
  634. * @var array
  635. */
  636. protected $_rules = array();
  637. /**
  638. * List of all system wide available aliases
  639. * This includes multi-lingual configurations as well
  640. * @var array
  641. */
  642. protected $_aliases = array();
  643. /**
  644. * Cache for rule objects
  645. * @var array
  646. */
  647. protected $_parsedRules = array(); // array of rule objects
  648. /**
  649. * Global config values per rule set
  650. * @var array
  651. */
  652. protected $_globalConfig = array();
  653. /**
  654. * Module name which is used for site main namespace
  655. * Example mysite.com/news/News Item => converted to mysite.com/News Item
  656. * NOTE: Could be moved to rules config
  657. *
  658. * @var string
  659. */
  660. protected $_mainNsModule = '';
  661. /**
  662. * Default URL suffix - to be added to end of all urls (e.g. '.html')
  663. * This value can be overridden per rule item
  664. * NOTE could be moved to rules config only
  665. * @var string
  666. */
  667. public $urlSuffix = '';
  668. /**
  669. * @var string GET variable name for route. Defaults to 'route'.
  670. */
  671. public $routeVar = 'route';
  672. /**
  673. * @var string
  674. */
  675. const FORMAT_GET = 'get';
  676. /**
  677. * @var string
  678. */
  679. const FORMAT_PATH = 'path';
  680. /**
  681. * @var string
  682. */
  683. private $_urlFormat = self::FORMAT_PATH;
  684. /**
  685. * Not found route
  686. * @var string
  687. */
  688. public $notFoundRoute = 'system/error/notfound';
  689. protected $_defaultAssembleOptions = array('full' => false, 'amp' => '&amp;', 'equal' => '=', 'encode' => true);
  690. /**
  691. * Not found URL - used when system route not found and 'url_error_redirect' core pref is true
  692. * TODO - user friendly URL ('/system/404') when system config is ready ('/system/404')
  693. * @var string
  694. */
  695. public $notFoundUrl = 'system/error/404?type=routeError';
  696. public function __construct()
  697. {
  698. $this->_init();
  699. }
  700. /**
  701. * Init object
  702. * @return void
  703. */
  704. protected function _init()
  705. {
  706. // Gather all rules, add-on info, cache, module for main namespace etc
  707. $this->_loadConfig()
  708. ->setAliases();
  709. // we need config first as setter does some checks if module can be set as main
  710. $this->setMainModule(e107::getPref('url_main_module', ''));
  711. }
  712. /**
  713. * Set module for default namespace
  714. * @param string $module
  715. * @return eRouter
  716. */
  717. public function setMainModule($module)
  718. {
  719. if(!$module || !$this->isModule($module) || !$this->getConfigValue($module, 'allowMain')) return $this;
  720. $this->_mainNsModule = $module;
  721. return $this;
  722. }
  723. /**
  724. * Get main url namespace module
  725. * @return string
  726. */
  727. public function getMainModule()
  728. {
  729. return $this->_mainNsModule;
  730. }
  731. /**
  732. * Check if given module is the main module
  733. * @param string $module
  734. * @return boolean
  735. */
  736. public function isMainModule($module)
  737. {
  738. return ($this->_mainNsModule === $module);
  739. }
  740. /**
  741. * @return string get|path
  742. */
  743. public function getUrlFormat()
  744. {
  745. return $this->_urlFormat;
  746. }
  747. /**
  748. * Load config and url rules, if not available - build it on the fly
  749. * @return eRouter
  750. */
  751. protected function _loadConfig()
  752. {
  753. if(!is_readable(e_CACHE_URL.'config.php')) $config = $this->buildGlobalConfig();
  754. else $config = include(e_CACHE_URL.'config.php');
  755. if(!$config) $config = array();
  756. $rules = array();
  757. foreach ($config as $module => $c)
  758. {
  759. $rules[$module] = $c['rules'];
  760. unset($config[$module]['rules']);
  761. $config[$module] = $config[$module]['config'];
  762. }
  763. $this->_globalConfig = $config;
  764. $this->setRuleSets($rules);
  765. return $this;
  766. }
  767. public static function clearCache()
  768. {
  769. if(file_exists(e_CACHE_URL.'config.php'))
  770. {
  771. @unlink(e_CACHE_URL.'config.php');
  772. }
  773. }
  774. /**
  775. * Build unified config.php
  776. */
  777. public function buildGlobalConfig($save = true)
  778. {
  779. $active = e107::getPref('url_config', array());
  780. $config = array();
  781. foreach ($active as $module => $location)
  782. {
  783. $_config = array();
  784. $obj = eDispatcher::getConfigObject($module, $location);
  785. $path = eDispatcher::getConfigPath($module, $location, true);
  786. if(null !== $obj)
  787. {
  788. $_config = $obj->config();
  789. $_config['config']['configPath'] = $path;
  790. $_config['config']['configClass'] = eDispatcher::getConfigClassName($module, $location);
  791. }
  792. if(!isset($_config['config'])) $_config['config'] = array();
  793. $_config['config']['location'] = $location;
  794. if(!isset($_config['config']['format']) || !in_array($_config['config']['format'], array(self::FORMAT_GET, self::FORMAT_PATH)))
  795. {
  796. $_config['config']['format'] = $this->getUrlFormat();
  797. }
  798. if(!isset($_config['rules'])) $_config['rules'] = array();
  799. foreach ($_config['rules'] as $pattern => $rule)
  800. {
  801. if(!is_array($rule))
  802. {
  803. $_config['rules'][$pattern] = array($rule);
  804. }
  805. }
  806. $config[$module] = $_config;
  807. }
  808. if($save)
  809. {
  810. $fileContent = '<?php'."\n### Auto-generated - DO NOT EDIT ### \nreturn ";
  811. $fileContent .= trim(var_export($config, true)).';';
  812. file_put_contents(e_CACHE_URL.'config.php', $fileContent);
  813. }
  814. return $config;
  815. }
  816. /**
  817. * Retrieve config array from a given system path
  818. * @param string $path
  819. * @param string $location core|plugin|override
  820. */
  821. public static function adminReadConfigs($path, $location = null)
  822. {
  823. $file = e107::getFile(false);
  824. $ret = array();
  825. $file->mode = 'fname';
  826. $files = $file->setFileInfo('fname')
  827. ->get_files($path, '^([a-z_]{1,}_)?url\.php$');
  828. foreach ($files as $file)
  829. {
  830. if(null === $location)
  831. {
  832. $c = eRouter::file2config($file, $location);
  833. if($c) $ret[] = $c;
  834. continue;
  835. }
  836. $ret[] = eRouter::file2config($file, $location);
  837. }
  838. return $ret;
  839. }
  840. /**
  841. * Convert filename to configuration string
  842. * @param string $filename
  843. * @param string $location core|plugin|override
  844. */
  845. public static function file2config($filename, $location = '')
  846. {
  847. if($filename == 'url.php') return $location;
  848. if($location) $location .= '/';
  849. return $location.substr($filename, 0, strrpos($filename, '_'));
  850. }
  851. /**
  852. * Detect all available system url modules, used as a map on administration configuration path
  853. * and required (same structure) {@link from eDispatcher::adminBuildConfig())
  854. * This is a very liberal detection, as it doesn't require config file.
  855. * It goes through both config and dispatch locations and registers directory tree as modules
  856. * The only exception are plugins - if plugin requires install (plugin.xml) and it is not installed,
  857. * it won't be registered
  858. * Another important thing is - core has always higher priority, as plugins are not allowed to
  859. * directly override core modules. At this moment, core modules could be overloaded only via override configs (e107_core/override/url/)
  860. * and controllers (e107_core/override/controllers)
  861. * This array is stored as url_modules core preference
  862. *
  863. * @param string $type possible values are all|plugin|core|override
  864. * @return array available system url modules stored as url_modules core preference
  865. */
  866. public static function adminReadModules($type = 'all')
  867. {
  868. $f = e107::getFile();
  869. $ret = array('core' => array(), 'plugin' => array(), 'override' => array());
  870. if($type == 'all' || $type = 'core')
  871. {
  872. $location = eDispatcher::getDispatchLocationPath('core');
  873. // search for controllers first
  874. $ret['core'] = $f->get_dirs($location);
  875. // merge with configs
  876. $configArray = $f->get_dirs(eDispatcher::getConfigPath('*', 'core'));
  877. foreach ($configArray as $config)
  878. {
  879. if(!in_array($config, $ret['core']))
  880. {
  881. $ret['core'][] = $config;
  882. }
  883. }
  884. sort($ret['core']);
  885. }
  886. if($type == 'all' || $type = 'plugin')
  887. {
  888. $plugins = $f->get_dirs(e_PLUGIN);
  889. foreach ($plugins as $plugin)
  890. {
  891. // DON'T ALLOW PLUGINS TO OVERRIDE CORE!!!
  892. // This will be possible in the future under some other, more controllable form
  893. if(in_array($plugin, $ret['core'])) continue;
  894. $location = eDispatcher::getDispatchLocationPath('plugin', $plugin);
  895. $config = eDispatcher::getConfigPath($plugin, 'plugin');
  896. if(e107::isInstalled($plugin))
  897. {
  898. if(is_dir($location) || is_readable($config))
  899. {
  900. $ret['plugin'][] = $plugin;
  901. }
  902. continue;
  903. }
  904. // Register only those who don't need install and may be dispatchable
  905. if((!is_readable(e_PLUGIN.$plugin.'/plugin.php') && !is_readable(e_PLUGIN.$plugin.'/plugin.xml')))
  906. {
  907. if(is_dir($location) || is_readable($config))
  908. {
  909. $ret['plugin'][] = $plugin;
  910. }
  911. }
  912. }
  913. sort($ret['plugin']);
  914. }
  915. if($type == 'all' || $type = 'override')
  916. {
  917. // search for controllers first
  918. $location = eDispatcher::getDispatchLocationPath('override');
  919. $ret['override'] = $f->get_dirs($location);
  920. // merge with configs
  921. $configArray = $f->get_dirs(eDispatcher::getConfigPath('*', 'override'));
  922. foreach ($configArray as $config)
  923. {
  924. if(!in_array($config, $ret['override']))
  925. {
  926. $ret['override'][] = $config;
  927. }
  928. }
  929. sort($ret['override']);
  930. // remove not installed plugin locations, possible only for 'all' type
  931. if($type == 'all')
  932. {
  933. foreach ($ret['override'] as $i => $l)
  934. {
  935. // it's a plugin override, but not listed in current plugin array - remove
  936. if(in_array($l, $plugins) && !in_array($l, $ret['plugin']))
  937. {
  938. unset($ret['override'][$i]);
  939. }
  940. }
  941. }
  942. }
  943. return $ret;
  944. }
  945. /**
  946. * Rebuild configuration array, stored as url_config core preference
  947. * More strict detection compared to {@link eDispatcher::adminReadModules()}
  948. * Current flat array containing config locations per module are rebuilt so that new
  949. * modules are registered, missing modules - removed. Additionally fallback to the default location
  950. * is done if current user defined location is not readable
  951. * @see eDispatcher::adminReadModules()
  952. * @param array current configuration array (url_config core preference like)
  953. * @param array available URL modules as detected by {@link eDispatcher::adminReadModules()} and stored as url_modules core preference value
  954. * @return array new url_config array
  955. */
  956. public static function adminBuildConfig($current, $adminReadModules = null)
  957. {
  958. if(null === $adminReadModules) $adminReadModules = self::adminReadModules();
  959. $ret = array();
  960. $all = array_unique(array_merge($adminReadModules['core'], $adminReadModules['plugin'], $adminReadModules['override']));
  961. foreach ($all as $module)
  962. {
  963. if(isset($current[$module]))
  964. {
  965. // current contains custom (readable) config location e.g. news => core/rewrite
  966. if(strpos($current[$module], '/') !== false && is_readable(eDispatcher::getConfigPath($module, $current[$module])))
  967. {
  968. $ret[$module] = $current[$module];
  969. continue;
  970. }
  971. // in all other cases additional re-check will be made - see below
  972. }
  973. if(in_array($module, $adminReadModules['override']))
  974. {
  975. // core check
  976. if(in_array($module, $adminReadModules['core']))
  977. {
  978. $mustHave = is_readable(eDispatcher::getConfigPath($module, 'core'));
  979. $has = is_readable(eDispatcher::getConfigPath($module, 'override'));
  980. // No matter if it must have, it has e_url config
  981. if($has) $ret[$module] = 'override';
  982. // It must have but it doesn't have e_url config, fallback
  983. elseif($mustHave && !$has) $ret[$module] = 'core';
  984. // Rest is always core as controller override is done on run time
  985. else $ret[$module] = 'core';
  986. }
  987. // plugin check
  988. elseif(in_array($module, $adminReadModules['plugin']))
  989. {
  990. $mustHave = is_readable(eDispatcher::getConfigPath($module, 'plugin'));
  991. $has = is_readable(eDispatcher::getConfigPath($module, 'override'));
  992. // No matter if it must have, it has e_url config
  993. if($has) $ret[$module] = 'override';
  994. // It must have but it doesn't have e_url config, fallback
  995. elseif($mustHave && !$has) $ret[$module] = 'plugin';
  996. // Rest is always plugin as config is most important, controller override check is done on run time
  997. else $ret[$module] = 'plugin';
  998. }
  999. // standalone override module
  1000. else
  1001. {
  1002. $ret[$module] = 'override';
  1003. }
  1004. }
  1005. // default core location
  1006. elseif(in_array($module, $adminReadModules['core']))
  1007. {
  1008. $ret[$module] = 'core';
  1009. }
  1010. // default plugin location
  1011. elseif(in_array($module, $adminReadModules['plugin']))
  1012. {
  1013. $ret[$module] = 'plugin';
  1014. }
  1015. }
  1016. return $ret;
  1017. }
  1018. /**
  1019. * Detect available config locations (readable check), based on available url_modules {@link eDispatcher::adminReadModules()} core preference arrays
  1020. * Used to rebuild url_locations core preference value
  1021. * @see eDispatcher::adminBuildConfig()
  1022. * @see eDispatcher::adminReadModules()
  1023. * @param array $available {@link eDispatcher::adminReadModules()} stored as url_modules core preference
  1024. * @return array available config locations, stored as url_locations core preference
  1025. */
  1026. public static function adminBuildLocations($available = null)
  1027. {
  1028. $ret = array();
  1029. if(null === $available) $available = self::adminReadModules();
  1030. $fl = e107::getFile();
  1031. // Core
  1032. foreach ($available['core'] as $module)
  1033. {
  1034. // Default module
  1035. $ret[$module] = array('core');
  1036. // read sub-locations
  1037. $path = eDispatcher::getConfigLocationPath($module, 'core');
  1038. //$sub = $fl->get_dirs($path);
  1039. $sub = eRouter::adminReadConfigs($path);
  1040. if($sub)
  1041. {
  1042. foreach ($sub as $moduleSub)
  1043. {
  1044. // auto-override: override available (controller or url config), check for config
  1045. if(in_array($module, $available['override']) && is_readable(eDispatcher::getConfigPath($module, 'override/'.$moduleSub)))
  1046. {
  1047. $ret[$module][] = 'override/'.$moduleSub;
  1048. }
  1049. // no override available, register the core location
  1050. elseif(is_readable(eDispatcher::getConfigPath($module, 'core/'.$moduleSub)))
  1051. {
  1052. $ret[$module][] = 'core/'.$moduleSub;
  1053. }
  1054. }
  1055. }
  1056. }
  1057. // Plugins
  1058. foreach ($available['plugin'] as $module)
  1059. {
  1060. // Default module
  1061. $ret[$module] = array('plugin');
  1062. // read sub-locations
  1063. $path = eDispatcher::getConfigLocationPath($module, 'plugin');
  1064. //$sub = $fl->get_dirs($path);
  1065. $sub = eRouter::adminReadConfigs($path);
  1066. if($sub)
  1067. {
  1068. foreach ($sub as $moduleSub)
  1069. {
  1070. // auto-override: override available (controller or url config), check for config
  1071. if(in_array($module, $available['override']) && is_readable(eDispatcher::getConfigPath($module, 'override/'.$moduleSub)))
  1072. {
  1073. $ret[$module][] = 'override/'.$moduleSub;
  1074. }
  1075. // no override available, register the core location
  1076. elseif(is_readable(eDispatcher::getConfigPath($module, 'plugin/'.$moduleSub)))
  1077. {
  1078. $ret[$module][] = 'plugin/'.$moduleSub;
  1079. }
  1080. }
  1081. }
  1082. }
  1083. // Go through all overrides, register those who don't belong to core & plugins as standalone core modules
  1084. foreach ($available['override'] as $module)
  1085. {
  1086. // either it is a core/plugin module or e_url.php is not readable - continue
  1087. if(in_array($module, $available['core']) || in_array($module, $available['plugin']))
  1088. {
  1089. continue;
  1090. }
  1091. // Default module
  1092. $ret[$module] = array('override');
  1093. // read sub-locations
  1094. $path = eDispatcher::getConfigLocationPath($module, 'override');
  1095. //$sub = $fl->get_dirs($path);
  1096. $sub = eRouter::adminReadConfigs($path);
  1097. if($sub)
  1098. {
  1099. foreach ($sub as $moduleSub)
  1100. {
  1101. if(is_readable(eDispatcher::getConfigPath($module, 'override/'.$moduleSub)))
  1102. {
  1103. $ret[$module][] = 'override/'.$moduleSub;
  1104. }
  1105. }
  1106. }
  1107. }
  1108. return $ret;
  1109. }
  1110. /**
  1111. * Match current aliases against currently available module and languages
  1112. * @param array $currentAliases url_aliases core preference
  1113. * @param array $currentConfig url_config core preference
  1114. * @return array cleaned aliases
  1115. */
  1116. public static function adminSyncAliases($currentAliases, $currentConfig)
  1117. {
  1118. if(empty($currentAliases)) return array();
  1119. $modules = array_keys($currentConfig);
  1120. // remove non existing languages
  1121. $lng = e107::getLanguage();
  1122. $lanList = $lng->installed();
  1123. if(is_array($currentAliases))
  1124. {
  1125. foreach ($currentAliases as $lanCode => $aliases)
  1126. {
  1127. $lanName = $lng->convert($lanCode);
  1128. if(!$lanName || !in_array($lanName, $lanList))
  1129. {
  1130. unset($currentAliases[$lanCode]);
  1131. continue;
  1132. }
  1133. // remove non-existing modules
  1134. foreach ($aliases as $alias => $module)
  1135. {
  1136. if(!isset($currentConfig[$module])) unset($currentAliases[$lanCode][$alias]);
  1137. }
  1138. }
  1139. }
  1140. return $currentAliases;
  1141. }
  1142. /**
  1143. * Retrieve global configuration array for a single or all modules
  1144. * @param string $module system module
  1145. * @return array configuration
  1146. */
  1147. public function getConfig($module = null)
  1148. {
  1149. if(null === $module) return $this->_globalConfig;
  1150. return isset($this->_globalConfig[$module]) ? $this->_globalConfig[$module] : array();
  1151. }
  1152. /**
  1153. * Retrieve single value from a module global configuration array
  1154. * @param string $module system module
  1155. * @return array configuration
  1156. */
  1157. public function getConfigValue($module, $key, $default = null)
  1158. {
  1159. return isset($this->_globalConfig[$module]) && isset($this->_globalConfig[$module][$key]) ? $this->_globalConfig[$module][$key] : $default;
  1160. }
  1161. /**
  1162. * Get system name of a module by its alias
  1163. * Returns null if $alias is not an existing alias
  1164. * @param string $alias
  1165. * @param string $lan optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases)
  1166. * @return string module
  1167. */
  1168. public function getModuleFromAlias($alias, $lan = null)
  1169. {
  1170. if($lan) return e107::findPref('url_aliases/'.$lan.'/'.$alias, null);
  1171. return (isset($this->_aliases[$alias]) ? $this->_aliases[$alias] : null);
  1172. }
  1173. /**
  1174. * Get alias name for a module
  1175. * Returns null if module doesn't have an alias
  1176. * @param string $module
  1177. * @param string $lan optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases)
  1178. * @return string alias
  1179. */
  1180. public function getAliasFromModule($module, $lan = null)
  1181. {
  1182. if($lan)
  1183. {
  1184. $aliases = e107::findPref('url_aliases/'.$lan, array());
  1185. return (in_array($module, $aliases) ? array_search($module, $aliases) : null);
  1186. }
  1187. return (in_array($module, $this->_aliases) ? array_search($module, $this->_aliases) : null);
  1188. }
  1189. /**
  1190. * Check if alias exists
  1191. * @param string $alias
  1192. * @param string $lan optional language alias. Example $lan = 'bg' (search for Bulgarian aliases)
  1193. * @return boolean
  1194. */
  1195. public function isAlias($alias, $lan = null)
  1196. {
  1197. if($lan)
  1198. {
  1199. $aliases = e107::findPref('url_aliases/'.$lan, array());
  1200. return isset($aliases[$alias]);
  1201. }
  1202. return isset($this->_aliases[$alias]);
  1203. }
  1204. /**
  1205. * Check if there is an alias for provided module
  1206. * @param string $module
  1207. * @param string $lan optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases)
  1208. * @return boolean
  1209. */
  1210. public function hasAlias($module, $lan = null)
  1211. {
  1212. if($lan)
  1213. {
  1214. $aliases = e107::findPref('url_aliases/'.$lan, array());
  1215. return in_array($module, $aliases);
  1216. }
  1217. return in_array($module, $this->_aliases);
  1218. }
  1219. /**
  1220. * Get all available module aliases
  1221. * @param string $lan optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases)
  1222. * @return array
  1223. */
  1224. public function getAliases($lanCode = null)
  1225. {
  1226. if($lan)
  1227. {
  1228. return e107::findPref('url_aliases/'.$lan, array());
  1229. }
  1230. return $this->_aliases;
  1231. }
  1232. /**
  1233. * Set module aliases
  1234. * @param array $aliases
  1235. * @return eRouter
  1236. */
  1237. public function setAliases($aliases = null)
  1238. {
  1239. if(null === $aliases)
  1240. {
  1241. $lanCode = e107::getLanguage()->convert(e_LANGUAGE);
  1242. $aliases = e107::findPref('url_aliases/'.$lanCode, array());
  1243. }
  1244. $this->_aliases = $aliases;
  1245. return $this;
  1246. }
  1247. /**
  1248. * Check if provided module is present in the rules config
  1249. * @param string module
  1250. * @return boolean
  1251. */
  1252. public function isModule($module)
  1253. {
  1254. return isset($this->_globalConfig[$module]);
  1255. }
  1256. /**
  1257. * Check if the passed value is valid module or module alias, returns system module
  1258. * or null on failure
  1259. * @param string $module
  1260. * @param boolean $strict check for existence if true
  1261. * @return string module
  1262. */
  1263. public function retrieveModule($module, $strict = true)
  1264. {
  1265. if($this->isAlias($module))
  1266. $module = $this->getModuleFromAlias($module);
  1267. if($strict && (!$module || !$this->isModule($module)))
  1268. return null;
  1269. return $module;
  1270. }
  1271. /**
  1272. * Set rule config for this instance
  1273. * @param array $rules
  1274. * @return void
  1275. */
  1276. public function setRuleSets($rules)
  1277. {
  1278. $this->_rules = $rules;
  1279. }
  1280. /**
  1281. * Retrieve rule set for a module
  1282. * @param string $module
  1283. */
  1284. public function getRuleSet($module)
  1285. {
  1286. return (isset($this->_rules[$module]) ? $this->_rules[$module] : array());
  1287. }
  1288. /**
  1289. * Get all rule sets
  1290. */
  1291. public function getRuleSets()
  1292. {
  1293. return $this->_rules;
  1294. }
  1295. /**
  1296. * Retrive array of eUrlRule objects for given module
  1297. */
  1298. public function getRules($module)
  1299. {
  1300. return $this->_processRules($module);
  1301. }
  1302. /**
  1303. * Process rule set array, create rule objects
  1304. * TODO - rule cache
  1305. * @param string $module
  1306. * @return array processed rule set
  1307. */
  1308. protected function _processRules($module)
  1309. {
  1310. if(!$this->isModule($module)) return array();
  1311. if(!isset($this->_parsedRules[$module]))
  1312. {
  1313. $rules = $this->getRuleSet($module);
  1314. $config = $this->getConfig($module);
  1315. $this->_parsedRules[$module] = array();
  1316. $map = array('urlSuffix' => 'urlSuffix', 'legacy' => 'legacy', 'legacyQuery' => 'legacyQuery', 'mapVars' => 'mapVars', 'allowVars' => 'allowVars', 'matchValue' => 'matchValue');
  1317. foreach ($rules as $pattern => $set)
  1318. {
  1319. foreach ($map as $key => $value)
  1320. {
  1321. if(!isset($set[$value]) && isset($config[$key]))
  1322. {
  1323. $set[$value] = $config[$key];
  1324. }
  1325. }
  1326. $this->_parsedRules[$module][$pattern] = $this->createRule($set, $pattern);
  1327. }
  1328. }
  1329. return $this->_parsedRules[$module];
  1330. }
  1331. /**
  1332. * Create rule object
  1333. *
  1334. * @param string $route
  1335. * @param string|array $pattern
  1336. * @param boolean $cache
  1337. * @return eUrlRule
  1338. */
  1339. protected function createRule($route, $pattern = null, $cache = false)
  1340. {
  1341. return new eUrlRule($route, $pattern, $cache);
  1342. }
  1343. /**
  1344. * Route current request
  1345. * @param eRequest $request
  1346. * @return boolean
  1347. */
  1348. public function route(eRequest $request, $checkOnly = false)
  1349. {
  1350. $request->routed = false;
  1351. if(isset($_GET[$this->routeVar]))
  1352. {
  1353. $rawPathInfo = $_GET[$this->routeVar];
  1354. unset($_GET[$this->routeVar]);
  1355. $this->_urlFormat = self::FORMAT_GET;
  1356. }
  1357. else
  1358. {
  1359. $rawPathInfo = rawurldecode($request->getPathInfo());
  1360. //$this->_urlFormat = self::FORMAT_PATH;
  1361. }
  1362. // Route to front page - index/index/index route
  1363. if(!$rawPathInfo && (!$this->getMainModule() || empty($_GET)))
  1364. {
  1365. // front page settings will be detected and front page will be rendered
  1366. $request->setRoute('index/index/index');
  1367. $request->addRouteHistory($rawPathInfo);
  1368. $request->routed = true;
  1369. return true;
  1370. }
  1371. // max number of parts is actually 4 - module/controller/action/[additional/pathinfo/vars], here for reference only
  1372. $parts = $rawPathInfo ? explode('/', $rawPathInfo, 4) : array();
  1373. // find module - check aliases
  1374. $module = $this->retrieveModule($parts[0]);
  1375. $mainSwitch = false;
  1376. // no module found, switch to Main module (pref) if available
  1377. if(null === $module && $this->getMainModule() && $this->isModule($this->getMainModule()))
  1378. {
  1379. $module = $this->getMainModule();
  1380. $rawPathInfo = $module.'/'.$rawPathInfo;
  1381. array_unshift($parts, $module);
  1382. $mainSwitch = true;
  1383. }
  1384. $request->routePathInfo = $rawPathInfo;
  1385. // valid module
  1386. if(null !== $module)
  1387. {
  1388. // we have valid module
  1389. $config = $this->getConfig($module);
  1390. // set legacy state
  1391. eFront::isLegacy(varset($config['legacy']));
  1392. // Don't allow single entry if required by module config
  1393. if(vartrue($config['noSingleEntry']))
  1394. {
  1395. $request->routed = true;
  1396. if(!eFront::isLegacy())
  1397. {
  1398. $request->setRoute($this->notFoundRoute);
  1399. return false;
  1400. }
  1401. // legacy entry point - include it later in the bootstrap, legacy query string will be set to current
  1402. $request->addRouteHistory($rawPathInfo);
  1403. return true;
  1404. }
  1405. // URL format - the one set by current config overrides the auto-detection
  1406. $format = isset($config['format']) && $config['format'] ? $config['format'] : $this->getUrlFormat();
  1407. //remove leading module, unnecessary overhead while matching
  1408. array_shift($parts);
  1409. $rawPathInfo = $parts ? implode('/', $parts) : '';
  1410. $pathInfo = $this->removeUrlSuffix($rawPathInfo, $this->urlSuffix);
  1411. // retrieve rules if any and if needed
  1412. $rules = $format == self::FORMAT_PATH ? $this->getRules($module) : array();
  1413. // Further parsing may still be needed
  1414. if(empty($rawPathInfo))
  1415. {
  1416. $rawPathInfo = $pathInfo;
  1417. }
  1418. // parse callback
  1419. if(vartrue($config['selfParse']))
  1420. {
  1421. // controller/action[/additional/parms]
  1422. if(vartrue($config['urlSuffix'])) $rawPathInfo = $this->removeUrlSuffix($rawPathInfo, $config['urlSuffix']);
  1423. $route = $this->configCallback($module, 'parse', array($rawPathInfo, $_GET, $request, $this, $config), $config['location']);
  1424. }
  1425. // default module route
  1426. elseif($format == self::FORMAT_GET || !$rules)
  1427. {
  1428. $route = $pathInfo;
  1429. }
  1430. // rules available - try to match an Url Rule
  1431. elseif($rules)
  1432. {
  1433. foreach ($rules as $rule)
  1434. {
  1435. $route = $rule->parseUrl($this, $request, $pathInfo, $rawPathInfo);
  1436. if($route !== false)
  1437. {
  1438. eFront::isLegacy($rule->legacy); // legacy include override
  1439. if($rule->parseCallback)
  1440. {
  1441. $this->configCallback($module, $rule->parseCallback, array($request), $config['location']);
  1442. }
  1443. // parse legacy query string if any
  1444. if(null !== $rule->legacyQuery)
  1445. {
  1446. $obj = eDispatcher::getConfigObject($module, $config['location']);
  1447. // eUrlConfig::legacyQueryString set as legacy string by default in eUrlConfig::legacy() method
  1448. $vars = new e_vars($request->getRequestParams());
  1449. $vars->module = $module;
  1450. $vars->controller = $request->getController();
  1451. $vars->action = $request->getAction();
  1452. if($rule->allowVars)
  1453. {
  1454. foreach ($rule->allowVars as $key)
  1455. {
  1456. if(isset($_GET[$key]) && !$request->isRequestParam($key))
  1457. {
  1458. // sanitize
  1459. $vars->$key = preg_replace('/[^\d\w\-]/', '', $_GET[$key]);
  1460. }
  1461. }
  1462. }
  1463. $obj->legacyQueryString = e107::getParser()->simpleParse($rule->legacyQuery, $vars, '0');
  1464. unset($vars, $obj);
  1465. }
  1466. break;
  1467. }
  1468. }
  1469. }
  1470. // append module to be registered in the request object
  1471. if(false !== $route)
  1472. {
  1473. // don't modify if true - request directly modified by config callback
  1474. if(!$request->routed)
  1475. {
  1476. if(eFront::isLegacy()) $this->configCallback($module, 'legacy', array($route, $request), $config['location']);
  1477. $route = $module.'/'.$route;
  1478. }
  1479. }
  1480. // No route found, we didn't switched to main module auto-magically
  1481. elseif(!$mainSwitch && vartrue($config['errorRoute']))
  1482. {
  1483. $route = !$checkOnly ? $module.'/'.$config['errorRoute'] : false;
  1484. }
  1485. }
  1486. // final fallback
  1487. if(!vartrue($route))
  1488. {
  1489. if($request->routed)
  1490. {
  1491. $route = $request->getRoute();
  1492. }
  1493. if(!$route)
  1494. {
  1495. $route = $this->notFoundRoute;
  1496. eFront::isLegacy(''); // reset legacy - not found route isn't legacy call
  1497. $request->routed = true;
  1498. if($checkOnly) return false;
  1499. ## Global redirect on error option
  1500. if(e107::getPref('url_error_redirect', false) && $this->notFoundUrl)
  1501. {
  1502. $redirect = $this->assemble($this->notFoundUrl, '', 'encode=0&full=1');
  1503. //echo $redirect; exit;
  1504. e107::getRedirect()->redirect($redirect, true, 404);
  1505. }
  1506. }
  1507. }
  1508. $request->setRoute($route);
  1509. $request->addRouteHistory($route);
  1510. $request->routed = true;
  1511. return true;
  1512. }
  1513. /**
  1514. * And more BC
  1515. * Checks and does some addtional logic if registered module is of type legacy
  1516. * @param eRequest $request
  1517. * @return void
  1518. */
  1519. public function checkLegacy(eRequest $request)
  1520. {
  1521. $module = $request->getModule();
  1522. // forward from controller to a legacy module - bad stuff
  1523. if(!$request->isDispatched() && $this->getConfigValue($module, 'legacy'))
  1524. {
  1525. eFront::isLegacy($this->getConfigValue($module, 'legacy'));
  1526. $url = $this->assemble($request->getRoute(), $request->getRequestParams());
  1527. $request->setRequestInfo($url)->setPathInfo(null)->setRoute(null);
  1528. $_GET = $request->getRequestParams();
  1529. $_SERVER['QUERY_STRING'] = http_build_query($request->getRequestParams(), null, '&');
  1530. // Infinite loop impossible, as dispatcher will break because of the registered legacy path
  1531. $this->route($request);
  1532. }
  1533. }
  1534. /**
  1535. * Convenient way to call config methods
  1536. */
  1537. public function configCallback($module, $callBack, $params, $location)
  1538. {
  1539. if(null == $location) $location = eDispatcher::getModuleConfigLocation($module);
  1540. if(!$module || !($obj = eDispatcher::getConfigObject($module, $location))) return false;
  1541. return call_user_func_array(array($obj, $callBack), $params);
  1542. }
  1543. /**
  1544. * Convert assembled url to shortcode
  1545. *
  1546. * @param string $route
  1547. * @param array $params
  1548. * @param array $options {@see eRouter::$_defaultAssembleOptions}
  1549. */
  1550. public function assembleSc($route, $params = array(), $options = array())
  1551. {
  1552. //if(is_string($options)) parse_str($options, $options);
  1553. $url = $this->assemble($route, $params, $options);
  1554. return e107::getParser()->createConstants($url, 'mix');
  1555. }
  1556. /**
  1557. * Assemble system URL
  1558. * Examples:
  1559. * <?php
  1560. * $router->assemble('/'); // index page URL e.g. / or /site_folder/
  1561. * $router->assemble('news/view/item?id=1'); // depends on current news config, possible return value is /news/1
  1562. * $router->assemble('*', 'id=1'); // use current request info - /module/controller/action?id=1
  1563. * $router->assemble('* /* /newaction'); // (NO EMPTY SPACES) change only current action - /module/controller/newaction
  1564. * $newsItem = array('news_id' => 1, 'news_sef' => 'My Title', ...); // as retrieved from DB
  1565. * $router->assemble('news/view/item', $newsItem); // All unused key=>values will be removed and NOT appended as GET vars
  1566. *
  1567. * @param string $route
  1568. * @param array $params
  1569. * @param array $options {@see eRouter::$_defaultAssembleOptions}
  1570. */
  1571. public function assemble($route, $params = array(), $options = array())
  1572. {
  1573. // TODO - url options
  1574. $request = eFront::instance()->getRequest();
  1575. if(is_string($options)) parse_str($options, $options);
  1576. $options = array_merge($this->_defaultAssembleOptions, $options);
  1577. $base = ($options['full'] ? SITEURLBASE : '').$request->getBasePath();
  1578. $anc = '';
  1579. if(is_string($params)) parse_str($params, $params);
  1580. if(isset($params['#']))
  1581. {
  1582. $anc = '#'.$params['#'];
  1583. usnet($params['#']);
  1584. }
  1585. // Config independent - Deny parameter keys, useful for directly denying sensitive data e.g. password db fields
  1586. if(isset($options['deny']))
  1587. {
  1588. $list = array_map('trim', explode(',', $options['deny']));
  1589. foreach ($list as $value)
  1590. {
  1591. unset($params[$value]);
  1592. }
  1593. unset($list);
  1594. }
  1595. // Config independent - allow parameter keys, useful to directly allow data (and not to rely on config allowVars) e.g. when retrieved from db
  1596. if(isset($options['allow']))
  1597. {
  1598. $list = array_map('trim', explode(',', $options['allow']));
  1599. $_params = $params;
  1600. $params = array();
  1601. foreach ($list as $value)
  1602. {
  1603. if(isset($_params[$value])) $params[$value] = $_params[$value];
  1604. }
  1605. unset($list, $_params);
  1606. }
  1607. # Optional convenient masks for creating system URL's
  1608. if($route === '/' || empty($route))
  1609. {
  1610. if($params)
  1611. {
  1612. $params = $this->createPathInfo($params, $options);
  1613. return $base.'?'.$params;
  1614. }
  1615. return $base;
  1616. }
  1617. elseif(strpos($route, '?') !== false)
  1618. {
  1619. $tmp = explode('?', $route, 2);
  1620. $route = $tmp[0];
  1621. parse_str($tmp[1], $params);
  1622. unset($tmp);
  1623. }
  1624. if($route === '*')
  1625. {
  1626. $route = $route = explode('/', $request->getRoute());
  1627. }
  1628. elseif(strpos($route, '*') !== false)
  1629. {
  1630. $route = explode('/', $route, 3);
  1631. if($route[0] === '*') $route[0] = $request->getModule();
  1632. if(isset($route[1]) && $route[1] === '*') $route[1] = $request->getController();
  1633. }
  1634. else
  1635. {
  1636. $route = explode('/', $route, 3);
  1637. }
  1638. // we don't know anything about this route, just build it blind
  1639. if(!$this->isModule($route[0]))
  1640. {
  1641. if($params)
  1642. {
  1643. $params = $this->createPathInfo($params, $options);
  1644. return $base.implode('/', $route).'?'.$params;
  1645. }
  1646. return $base.implode('/', $route);
  1647. }
  1648. # fill in index when needed - XXX not needed, may be removed soon
  1649. switch (count($route))
  1650. {
  1651. case 1:
  1652. $route[1] = 'index';
  1653. $route[2] = 'index';
  1654. break;
  1655. case 2:
  1656. $route[2] = 'index';
  1657. break;
  1658. }
  1659. # aliases
  1660. $module = $route[0];
  1661. $config = $this->getConfig($module);
  1662. $alias = $this->hasAlias($module, vartrue($options['lan'], null)) ? $this->getAliasFromModule($module, vartrue($options['lan'], null)) : $module;
  1663. $route[0] = $alias;
  1664. if($options['encode']) $alias = rawurlencode($alias);
  1665. $format = isset($config['format']) && $config['format'] ? $config['format'] : self::FORMAT_GET;
  1666. $urlSuffix = '';
  1667. // Fix base url for legacy links
  1668. if(vartrue($config['noSingleEntry'])) $base = $options['full'] ? SITEURL : e_HTTP;
  1669. elseif(self::FORMAT_GET !== $config['format'])
  1670. {
  1671. $urlSuffix = $this->urlSuffix;
  1672. if(isset($config['urlSuffix'])) $urlSuffix = $config['urlSuffix'];
  1673. }
  1674. // Create by config callback
  1675. if(vartrue($config['selfCreate']))
  1676. {
  1677. $tmp = $this->configCallback($module, 'create', array(array($route[1], $route[2]), $params, $options), $config['location']);
  1678. if(empty($tmp)) return '#not-found';
  1679. if(is_array($tmp))
  1680. {
  1681. $route = $tmp[0];
  1682. $params = $tmp[1];
  1683. if($options['encode']) $route = array_map('rawurlencode', $route);
  1684. $route = implode('/', $route);
  1685. if(!$route)
  1686. {
  1687. $urlSuffix = '';
  1688. if(!$this->isMainModule($module)) $route = $alias;
  1689. }
  1690. elseif (!$this->isMainModule($module))
  1691. {
  1692. $route = $alias.'/'.$route;
  1693. }
  1694. }
  1695. else
  1696. {
  1697. // relative url returned
  1698. return $base.$tmp.$anc;
  1699. }
  1700. unset($tmp);
  1701. if($format === self::FORMAT_GET)
  1702. {
  1703. $params[$this->routeVar] = $route;
  1704. $route = '';
  1705. }
  1706. if($params)
  1707. {
  1708. $params = $this->createPathInfo($params, $options);
  1709. return $base.$route.$urlSuffix.'?'.$params.$anc;
  1710. }
  1711. return $base.$route.$urlSuffix.$anc;
  1712. }
  1713. // System URL create routine
  1714. $rules = $this->getRules($module);
  1715. if($format !== self::FORMAT_GET && !empty($rules))
  1716. {
  1717. foreach ($rules as $k => $rule)
  1718. {
  1719. if (($url = $rule->createUrl($this, array($route[1], $route[2]), $params, $options)) !== false)
  1720. {
  1721. return $base.rtrim(($this->isMainModule($module) ? '' : $alias.'/').$url, '/').$anc;
  1722. }
  1723. }
  1724. }
  1725. // default - module/controller/action
  1726. if($this->isMainModule($module)) unset($route[0]);
  1727. if($route[2] == 'index')
  1728. {
  1729. unset($route[2]);
  1730. if($route[1] == 'index') unset($route[1]);
  1731. }
  1732. # Modify params if required
  1733. if($params)
  1734. {
  1735. if(varset($config['mapVars']))
  1736. {
  1737. foreach ($config['mapVars'] as $srcKey => $dstKey)
  1738. {
  1739. if (isset($params[$srcKey]))
  1740. {
  1741. $params[$dstKey] = $params[$srcKey];
  1742. unset($params[$srcKey]);
  1743. }
  1744. }
  1745. }
  1746. // false means - no vars are allowed, nothing to preserve here
  1747. if(varset($config['allowVars']) === false) $params = array();
  1748. // default empty array value - try to guess what's allowed - mapVars is the best possible candidate
  1749. elseif(empty($config['allowVars']) && !empty($config['mapVars'])) $params = array_unique(array_values($config['mapVars']));
  1750. // disallow everything but valid URL parameters
  1751. if(!empty($config['allowVars']))
  1752. {
  1753. $copy = $params;
  1754. $params = array();
  1755. foreach ($config['allowVars'] as $key)
  1756. {
  1757. if(isset($copy[$key])) $params[$key] = $copy[$key];
  1758. }
  1759. unset($copy);
  1760. }
  1761. if($format === self::FORMAT_GET)
  1762. {
  1763. $urlSuffix = '';
  1764. $copy = $params;
  1765. $params = array();
  1766. $params[$this->routeVar] = implode('/', $route);
  1767. foreach ($copy as $key => $value)
  1768. {
  1769. $params[$key] = $value;
  1770. }
  1771. unset($copy);
  1772. $route = array();
  1773. }
  1774. $params = $this->createPathInfo($params, $options);
  1775. $route = implode('/', $route);
  1776. if(!$route || $route == $alias) $urlSuffix = '';
  1777. return $base.$route.$urlSuffix.'?'.$params.$anc;
  1778. }
  1779. $route = implode('/', $route);
  1780. if(!$route || $route == $alias) $urlSuffix = '';
  1781. return $format === self::FORMAT_GET ? $base.'?'.$this->routeVar.'='.$route.$anc : $base.$route.$urlSuffix.$anc;
  1782. }
  1783. /**
  1784. * Alias of assemble()
  1785. */
  1786. public function url($route, $params = array())
  1787. {
  1788. return $this->assemble($route, $params);
  1789. }
  1790. /**
  1791. * Creates a path info based on the given parameters.
  1792. * XXX - maybe we can switch to http_build_query(), should be able to do everything we need in a much better way
  1793. *
  1794. * @param array $params list of GET parameters
  1795. * @param array $options rawurlencode, equal, encode and amp settings
  1796. * @param string $key this is used internally for recursive calls
  1797. *
  1798. * @return string the created path info
  1799. */
  1800. public function createPathInfo($params, $options, $key = null)
  1801. {
  1802. $pairs = array();
  1803. $equal = $options['equal'];
  1804. $encode = $options['encode'];
  1805. $ampersand = !$encode && $options['amp'] == '&amp;' ? '&' : $options['amp'];
  1806. foreach ($params as $k => $v)
  1807. {
  1808. if (null !== $key) $k = $key.'['.rawurlencode($k).']';
  1809. if (is_array($v)) $pairs[] = $this->createPathInfo($v, $options, $k);
  1810. else
  1811. {
  1812. if(null === $v)
  1813. {
  1814. if($encode)
  1815. {
  1816. $k = null !== $key ? $k : rawurlencode($k);
  1817. }
  1818. $pairs[] = $k;
  1819. continue;
  1820. }
  1821. if($encode)
  1822. {
  1823. $k = null !== $key ? $k : rawurlencode($k);
  1824. $v = rawurlencode($v);
  1825. }
  1826. $pairs[] = $k.$equal.$v;
  1827. }
  1828. }
  1829. return implode($ampersand, $pairs);
  1830. }
  1831. /**
  1832. * Parses a path info into URL segments
  1833. * Be sure to not use non-unique chars for equal and ampersand signs, or you'll break your URLs
  1834. *
  1835. * @param eRequest $request
  1836. * @param string $pathInfo path info
  1837. * @param string $equal
  1838. * @param string $ampersand
  1839. */
  1840. public function parsePathInfo($pathInfo, $equal = '/', $ampersand = '/')
  1841. {
  1842. if ('' === $pathInfo) return;
  1843. if ($equal != $ampersand) $pathInfo = str_replace($equal, $ampersand, $pathInfo);
  1844. $segs = explode($ampersand, $pathInfo.$ampersand);
  1845. $segs = explode('/', $pathInfo);
  1846. $ret = array();
  1847. for ($i = 0, $n = count($segs); $i < $n - 1; $i += 2)
  1848. {
  1849. $key = $segs[$i];
  1850. if ('' === $key) continue;
  1851. $value = $segs[$i + 1];
  1852. // array support
  1853. if (($pos = strpos($key, '[')) !== false && ($pos2 = strpos($key, ']', $pos + 1)) !== false)
  1854. {
  1855. $name = substr($key, 0, $pos);
  1856. // numerical array
  1857. if ($pos2 === $pos + 1)
  1858. $ret[$name][] = $value;
  1859. // associative array
  1860. else
  1861. {
  1862. $key = substr($key, $pos + 1, $pos2 - $pos - 1);
  1863. $ret[$name][$key] = $value;
  1864. }
  1865. }
  1866. else
  1867. {
  1868. $ret[$key] = $value;
  1869. }
  1870. }
  1871. return $ret;
  1872. }
  1873. /**
  1874. * Removes the URL suffix from path info.
  1875. * @param string $pathInfo path info part in the URL
  1876. * @param string $urlSuffix the URL suffix to be removed
  1877. *
  1878. * @return string path info with URL suffix removed.
  1879. */
  1880. public function removeUrlSuffix($pathInfo, $urlSuffix)
  1881. {
  1882. if ('' !== $urlSuffix && substr($pathInfo, -strlen($urlSuffix)) === $urlSuffix) return substr($pathInfo, 0, -strlen($urlSuffix));
  1883. else return $pathInfo;
  1884. }
  1885. }
  1886. class eException extends Exception
  1887. {
  1888. }
  1889. /**
  1890. * Based on Yii Framework UrlRule handler <www.yiiframework.com>
  1891. */
  1892. class eUrlRule
  1893. {
  1894. /**
  1895. *
  1896. * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
  1897. * Defaults to null, meaning using the value of {@link cl_shop_core_url::urlSuffix}.
  1898. *
  1899. * @var string the URL suffix used for this rule.
  1900. */
  1901. public $urlSuffix;
  1902. /**
  1903. * When this rule is used to parse the incoming request, the values declared in this property
  1904. * will be injected into $_GET.
  1905. *
  1906. * @var array the default GET parameters (name=>value) that this rule provides.
  1907. */
  1908. public $defaultParams = array();
  1909. /**
  1910. * @var string module/controller/action
  1911. */
  1912. public $route;
  1913. /**
  1914. * @var array the mapping from route param name to token name (e.g. _r1=><1>)
  1915. */
  1916. public $references = array();
  1917. /**
  1918. * @var string the pattern used to match route
  1919. */
  1920. public $routePattern;
  1921. /**
  1922. * @var string regular expression used to parse a URL
  1923. */
  1924. public $pattern;
  1925. /**
  1926. * @var string template used to construct a URL
  1927. */
  1928. public $template;
  1929. /**
  1930. * @var array list of parameters (name=>regular expression)
  1931. */
  1932. public $params = array();
  1933. /**
  1934. * @var boolean whether the URL allows additional parameters at the end of the path info.
  1935. */
  1936. public $append;
  1937. /**
  1938. * @var array list of SourceKey=>DestinationKey associations
  1939. */
  1940. public $mapVars = array();
  1941. /**
  1942. * Numerical array of allowed parameter keys. If set, everything else will be wiped out from the passed parameter array
  1943. * @var array
  1944. */
  1945. public $allowVars = array();
  1946. /**
  1947. * Should be values matched vs route patterns when assembling URLs
  1948. * Warning SLOW when true!!!
  1949. * @var mixed true or 1 for preg_match (extremely slower), or 'empty' for only empty check (better)
  1950. */
  1951. public $matchValue;
  1952. /**
  1953. * Method member of module config object, to be called after successful request parsing
  1954. * @var string
  1955. */
  1956. public $parseCallback;
  1957. /**
  1958. * Shortcode path to the old entry point e.g. '{e_BASE}news.php'
  1959. * @var string
  1960. */
  1961. public $legacy;
  1962. /**
  1963. * Template used for automated recognition of legacy QueryString (parsed via simpleParser with values of retrieved requestParameters)
  1964. * @var string
  1965. */
  1966. public $legacyQuery;
  1967. /**
  1968. * Core regex templates
  1969. * Example usage - route <var:{number}> will result in
  1970. * @var array
  1971. */
  1972. public $regexTemplates = array(
  1973. 'az' => '[A-Za-z]+', // NOTE - it won't match non-latin word characters!
  1974. 'alphanum' => '[\w\pL]+',
  1975. 'sefsecure' => '[\w\pL\s\-+.,]+',
  1976. 'secure' => '[^\/\'"\\<%]+',
  1977. 'number' => '[\d]+',
  1978. 'username' => '[\w\pL.\-\s!,]+', // TODO - should equal to username pattern, sync it
  1979. 'azOptional' => '[A-Za-z]{0,}',
  1980. 'alphanumOptional' => '[\w\pL]{0,}',
  1981. 'sefsecureOptional' => '[\w\pL\s\-+.,]{0,}',
  1982. 'secureOptional' => '[^\/\'"\\<%]{0,}',
  1983. 'numberOptional' => '[\d]{0,}',
  1984. 'usernameOptional' => '[\w\pL.\-\s!,]{0,}', // TODO - should equal to username pattern, sync it
  1985. );
  1986. /**
  1987. * User defined regex templates
  1988. * @var array
  1989. */
  1990. public $varTemplates = array();
  1991. /**
  1992. * All regex templates
  1993. * @var e_var
  1994. */
  1995. protected $_regexTemplates;
  1996. /**
  1997. * Constructor.
  1998. * @param string $route the route of the URL (controller/action)
  1999. * @param string $pattern the pattern for matching the URL
  2000. */
  2001. public function __construct($route, $pattern, $fromCache = false)
  2002. {
  2003. if (is_array($route))
  2004. {
  2005. if ($fromCache && !$pattern)
  2006. {
  2007. $this->setData($route);
  2008. $this->_regexTemplates = new e_vars($this->regexTemplates);
  2009. return;
  2010. }
  2011. $this->setData($route);
  2012. if($this->defaultParams && is_string($this->defaultParams))
  2013. {
  2014. parse_str($this->defaultParams, $this->defaultParams);
  2015. }
  2016. $route = $this->route = $route[0];
  2017. }
  2018. else $this->route = $route;
  2019. $tr2['/'] = $tr['/'] = '\\/';
  2020. if (strpos($route, '<') !== false && preg_match_all('/<(\w+)>/', $route, $matches2))
  2021. {
  2022. foreach ($matches2[1] as $name) $this->references[$name] = "<$name>";
  2023. }
  2024. if($this->varTemplates)
  2025. {
  2026. // don't override core regex templates
  2027. $this->regexTemplates = array_merge($this->varTemplates, $this->regexTemplates);
  2028. $this->varTemplates = array();
  2029. }
  2030. $this->_regexTemplates = new e_vars($this->regexTemplates);
  2031. if (preg_match_all('/<(\w+):?(.*?)?>/', $pattern, $matches))
  2032. {
  2033. $tokens = array_combine($matches[1], $matches[2]);
  2034. $tp = e107::getParser();
  2035. foreach ($tokens as $name => $value)
  2036. {
  2037. if ($value === '') $value = '[^\/]+';
  2038. elseif($value[0] == '{')
  2039. {
  2040. $value = $tp->simpleParse($value, $this->_regexTemplates, '[^\/]+');
  2041. }
  2042. $tr["<$name>"] = "(?P<$name>$value)";
  2043. if (isset($this->references[$name])) $tr2["<$name>"] = $tr["<$name>"];
  2044. else $this->params[$name] = $value;
  2045. }
  2046. }
  2047. $p = rtrim($pattern, '*');
  2048. $this->append = $p !== $pattern;
  2049. $p = trim($p, '/');
  2050. $this->template = preg_replace('/<(\w+):?.*?>/', '<$1>', $p);
  2051. $this->pattern = '/^'.strtr($this->template, $tr).'\/?';
  2052. if ($this->append) $this->pattern .= '/u';
  2053. else $this->pattern .= '$/u';
  2054. if ($this->references !== array()) $this->routePattern = '/^'.strtr($this->route, $tr2).'$/u';
  2055. }
  2056. public function getData()
  2057. {
  2058. $vars = array_keys(get_class_vars(__CLASS__));
  2059. $data = array();
  2060. foreach ($vars as $prop)
  2061. {
  2062. $data[$prop] = $this->$prop;
  2063. }
  2064. return $data;
  2065. }
  2066. protected function setData($data)
  2067. {
  2068. if (!is_array($data)) return;
  2069. $vars = array_keys(get_class_vars(__CLASS__));
  2070. foreach ($vars as $prop)
  2071. {
  2072. if (!isset($data[$prop])) continue;
  2073. $this->$prop = $data[$prop];
  2074. }
  2075. }
  2076. /**
  2077. * Creates a URL based on this rule.
  2078. * TODO - more clear logic and flexibility by building the query string
  2079. *
  2080. * @param eRouter $manager the router/manager
  2081. * @param string $route the route
  2082. * @param array $params list of parameters
  2083. * @param array $options
  2084. * @return mixed the constructed URL or false on error
  2085. */
  2086. public function createUrl($manager, $route, $params, $options)
  2087. {
  2088. $case = 'i';
  2089. $ampersand = $options['amp'];
  2090. $encode = vartrue($options['encode']);
  2091. if(is_array($route)) $route = implode('/', $route);
  2092. $tr = array();
  2093. if ($route !== $this->route)
  2094. {
  2095. if ($this->routePattern !== null && preg_match($this->routePattern.$case, $route, $matches))
  2096. {
  2097. foreach ($this->references as $key => $name) $tr[$name] = $matches[$key];
  2098. }
  2099. else return false;
  2100. }
  2101. // map vars first
  2102. foreach ($this->mapVars as $srcKey => $dstKey)
  2103. {
  2104. if (isset($params[$srcKey])/* && !isset($params[$dstKey])*/)
  2105. {
  2106. $params[$dstKey] = $params[$srcKey];
  2107. unset($params[$srcKey]);
  2108. }
  2109. }
  2110. // false means - no vars are allowed, preserve only route vars
  2111. if($this->allowVars === false) $this->allowVars = array_keys($this->params);
  2112. // empty array (default) - everything is allowed
  2113. // disallow everything but valid URL parameters
  2114. if(!empty($this->allowVars))
  2115. {
  2116. $copy = $params;
  2117. $params = array();
  2118. $this->allowVars = array_unique(array_merge($this->allowVars, array_keys($this->params)));
  2119. foreach ($this->allowVars as $key)
  2120. {
  2121. if(isset($copy[$key])) $params[$key] = $copy[$key];
  2122. }
  2123. unset($copy);
  2124. }
  2125. foreach ($this->defaultParams as $key => $value)
  2126. {
  2127. if (isset($params[$key]))
  2128. {
  2129. if ($params[$key] == $value) unset($params[$key]);
  2130. else return false;
  2131. }
  2132. }
  2133. foreach ($this->params as $key => $value) if (!isset($params[$key])) return false;
  2134. if($this->matchValue)
  2135. {
  2136. if('empty' !== $this->matchValue)
  2137. {
  2138. foreach($this->params as $key=>$value)
  2139. {
  2140. if(!preg_match('/'.$value.'/'.$case,$params[$key]))
  2141. return false;
  2142. }
  2143. }
  2144. else
  2145. {
  2146. foreach($this->params as $key=>$value)
  2147. {
  2148. if(empty($params[$key]) )
  2149. return false;
  2150. }
  2151. }
  2152. }
  2153. foreach ($this->params as $key => $value)
  2154. {
  2155. // FIX - non-latin URLs proper encoded
  2156. $tr["<$key>"] = rawurlencode($params[$key]);
  2157. unset($params[$key]);
  2158. }
  2159. $suffix = $this->urlSuffix === null ? $manager->urlSuffix : $this->urlSuffix;
  2160. // 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)
  2161. $urlFormat = e107::getConfig()->get('url_sef_translate');
  2162. if($urlFormat == 'dashl' || $urlFormat == 'underscorel' || $urlFormat == 'plusl') // convert template to lowercase when using lowercase SEF URL format.
  2163. {
  2164. $this->template = strtolower($this->template);
  2165. }
  2166. $url = strtr($this->template, $tr);
  2167. if(empty($params))
  2168. {
  2169. return $url !== '' ? $url.$suffix : $url;
  2170. }
  2171. // apppend not supported, maybe in the future...?
  2172. if ($this->append) $url .= '/'.$manager->createPathInfo($params, '/', '/').$suffix;
  2173. else
  2174. {
  2175. if ($url !== '') $url = $url.$suffix;
  2176. $options['equal'] = '=';
  2177. $url .= '?'.$manager->createPathInfo($params, $options);
  2178. }
  2179. return rtrim($url, '/');
  2180. }
  2181. /**
  2182. * Parases a URL based on this rule.
  2183. * @param eRouter $manager the router/URL manager
  2184. * @param eRequest $request the request object
  2185. * @param string $pathInfo path info part of the URL
  2186. * @param string $rawPathInfo path info that contains the potential URL suffix
  2187. * @return mixed the route that consists of the controller ID and action ID or false on error
  2188. */
  2189. public function parseUrl($manager, $request, $pathInfo, $rawPathInfo)
  2190. {
  2191. $case = 'i'; # 'i' = insensitive
  2192. if ($this->urlSuffix !== null) $pathInfo = $manager->removeUrlSuffix($rawPathInfo, $this->urlSuffix);
  2193. $pathInfo = rtrim($pathInfo, '/').'/';
  2194. // pathInfo is decoded, pattern could be encoded - required for proper url assemble (e.g. cyrillic chars)
  2195. if (preg_match(rawurldecode($this->pattern).$case, $pathInfo, $matches))
  2196. {
  2197. foreach ($this->defaultParams as $name => $value)
  2198. {
  2199. //if (!isset($_GET[$name])) $_REQUEST[$name] = $_GET[$name] = $value;
  2200. if (!$request->isRequestParam($name)) $request->setRequestParam($name, $value);
  2201. }
  2202. $tr = array();
  2203. foreach ($matches as $key => $value)
  2204. {
  2205. if (isset($this->references[$key])) $tr[$this->references[$key]] = $value;
  2206. elseif (isset($this->params[$key]))
  2207. {
  2208. //$_REQUEST[$key] = $_GET[$key] = $value;
  2209. $request->setRequestParam($key, $value);
  2210. }
  2211. }
  2212. if ($pathInfo !== $matches[0]) # Additional GET params exist
  2213. {
  2214. $manager->parsePathInfo($request, ltrim(substr($pathInfo, strlen($matches[0])), '/'));
  2215. }
  2216. return (null !== $this->routePattern ? strtr($this->route, $tr) : $this->route);
  2217. }
  2218. else return false;
  2219. }
  2220. }
  2221. abstract class eUrlConfig
  2222. {
  2223. /**
  2224. * Registered by parse method legacy query string
  2225. */
  2226. public $legacyQueryString = null;
  2227. /**
  2228. * User defined initialization
  2229. */
  2230. public function init() {}
  2231. /**
  2232. * Retrieve module config options (including url rules if any)
  2233. * Return array is called once and cached, so runtime changes are not an option
  2234. * @return array
  2235. */
  2236. abstract public function config();
  2237. /**
  2238. * Create URL callback, called only when config option selfParse is set to true
  2239. * Expected return array format:
  2240. * <code>
  2241. * array(
  2242. * array(part1, part2, part3),
  2243. * array(parm1 => val1, parm2 => val2),
  2244. * );
  2245. * </code>
  2246. * @param array $route parts
  2247. * @param array $params
  2248. * @return array|string numerical of type (routeParts, GET Params)| string route or false on error
  2249. */
  2250. public function create($route, $params = array(), $options = array()) {}
  2251. /**
  2252. * Parse URL callback, called only when config option selfCreate is set to true
  2253. * TODO - register variable eURLConfig::currentConfig while initializing the object, remove from method arguments
  2254. * @param string $pathInfo
  2255. * @param array $params request parameters
  2256. * @param eRequest $request
  2257. * @param eRouter $router
  2258. * @param array $config
  2259. * @return string route or false on error
  2260. */
  2261. public function parse($pathInfo, $params = array(), eRequest $request = null, eRouter $router = null, $config = array()) { return false; }
  2262. /**
  2263. * Legacy callback, used called when config option legacy is not empty
  2264. * By default it sets legacy query string to $legacyQueryString value (normaly assigned inside of the parse method)
  2265. * @param string $resolvedRoute
  2266. * @param eRequest $request
  2267. * @param string $callType 'route' - called once, when parsing the request, 'dispatch' - called inside the dispatch loop (in case of controller _forward)
  2268. * @param void
  2269. */
  2270. public function legacy($resolvedRoute, eRequest $request, $callType = 'route')
  2271. {
  2272. if($this->legacyQueryString !== null)
  2273. {
  2274. $request->setLegacyQstring($this->legacyQueryString);
  2275. $request->setLegacyPage();
  2276. }
  2277. }
  2278. /**
  2279. * Developed mainly for legacy modules.
  2280. * It should be manually triggered inside of old entry point. The idea is
  2281. * to avoid multiple URL addresses having same content (bad SEO practice)
  2282. * FIXME - under construction
  2283. */
  2284. public function forward() {}
  2285. /**
  2286. * Admin interface callback, returns array with all required from administration data
  2287. * Return array structure:
  2288. * <code>
  2289. * <?php
  2290. * return array(
  2291. * 'labels' => array(
  2292. * 'name' => 'Module name',
  2293. * 'label' => 'Profile Label',
  2294. * 'description' => 'Additional profile info, exmples etc.',
  2295. * ),
  2296. * 'form' => array(), // awaiting future development
  2297. * 'callbacks' => array(), // awaiting future development
  2298. * );
  2299. * </code>
  2300. */
  2301. public function admin() { return array(); }
  2302. /**
  2303. * Admin submit hook
  2304. * FIXME - under construction
  2305. */
  2306. public function submit() {}
  2307. /**
  2308. * Admin interface help messages, labels and titles
  2309. * FIXME - under construction
  2310. */
  2311. public function help() {}
  2312. }
  2313. /**
  2314. * Controller base class, actions are extending it
  2315. *
  2316. */
  2317. class eController
  2318. {
  2319. protected $_request;
  2320. protected $_response;
  2321. public function __construct(eRequest $request, eResponse $response = null)
  2322. {
  2323. $this->setRequest($request)
  2324. ->setResponse($response)
  2325. ->init();
  2326. }
  2327. /**
  2328. * Custom init, always called in the constructor, no matter what is the request dispatch status
  2329. */
  2330. public function init() {}
  2331. /**
  2332. * Custom shutdown, always called after the controller dispatch, no matter what is the request dispatch status
  2333. */
  2334. public function shutdown() {}
  2335. /**
  2336. * Pre-action callback, fired only if dispatch status is still true and action method is found
  2337. */
  2338. public function preAction() {}
  2339. /**
  2340. * Post-action callback, fired only if dispatch status is still true and action method is found
  2341. */
  2342. public function postAction() {}
  2343. /**
  2344. * @param eRequest $request
  2345. * @return eController
  2346. */
  2347. public function setRequest($request)
  2348. {
  2349. $this->_request = $request;
  2350. return $this;
  2351. }
  2352. /**
  2353. * @return eRequest
  2354. */
  2355. public function getRequest()
  2356. {
  2357. return $this->_request;
  2358. }
  2359. /**
  2360. * @param eResponse $response
  2361. * @return eController
  2362. */
  2363. public function setResponse($response)
  2364. {
  2365. $this->_response = $response;
  2366. return $this;
  2367. }
  2368. /**
  2369. * @return eResponse
  2370. */
  2371. public function getResponse()
  2372. {
  2373. return $this->_response;
  2374. }
  2375. public function addBody($content)
  2376. {
  2377. $this->getResponse()->appendBody($content);
  2378. return $this;
  2379. }
  2380. public function addMetaDescription($description)
  2381. {
  2382. $this->getResponse()->addMetaDescription($description);
  2383. return $this;
  2384. }
  2385. /**
  2386. * Add document title
  2387. * @param string $title
  2388. * @param boolean $meta auto-add it as meta-title
  2389. * @return eResponse
  2390. */
  2391. public function addTitle($title, $meta = true)
  2392. {
  2393. $this->getResponse()->appendTitle($title);
  2394. if($meta) $this->addMetaTitle(strip_tags($title));
  2395. return $this;
  2396. }
  2397. public function addMetaTitle($title)
  2398. {
  2399. $this->getResponse()->addMetaTitle($title);
  2400. return $this;
  2401. }
  2402. public function dispatch($actionMethodName)
  2403. {
  2404. $request = $this->getRequest();
  2405. $content = '';
  2406. // init() could modify the dispatch status
  2407. if($request->isDispatched())
  2408. {
  2409. if(method_exists($this, $actionMethodName))
  2410. {
  2411. $this->preAction();
  2412. // TODO request userParams() to store private data - check for noPopulate param here
  2413. if($request->isDispatched())
  2414. {
  2415. $request->populateRequestParams();
  2416. // allow return output
  2417. $content = $this->$actionMethodName();
  2418. if(!empty($content)) $this->addBody($content);
  2419. if($request->isDispatched())
  2420. {
  2421. $this->postAction();
  2422. }
  2423. }
  2424. }
  2425. else
  2426. {
  2427. //TODO not found method by controller or default one
  2428. $action = substr($actionMethodName, 6);
  2429. throw new eException('Action "'.$action.'" does not exist');
  2430. }
  2431. }
  2432. $this->shutdown();
  2433. }
  2434. public function run(eRequest $request = null, eResponse $response = null)
  2435. {
  2436. if(null === $request) $request = $this->getRequest();
  2437. else $this->setRequest($request);
  2438. if(null === $response) $response = $this->getResponse();
  2439. else $this->setResponse($response);
  2440. $action = $request->getActionMethodName();
  2441. $request->setDispatched(true);
  2442. $this->dispatch($action);
  2443. return $this->getResponse();
  2444. }
  2445. protected function _redirect($url, $createURL = false, $code = null)
  2446. {
  2447. $redirect = e107::getRedirect();
  2448. if($createURL)
  2449. {
  2450. $url = eFront::instance()->getRouter()->assemble($url, '', 'encode=0');
  2451. }
  2452. if(strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0)
  2453. {
  2454. $url = $url[0] == '/' ? SITEURLBASE.$url : SITEURL.$url;
  2455. }
  2456. $redirect->redirect($url, true, $code);
  2457. }
  2458. /**
  2459. * System forward
  2460. * @param string $route
  2461. * @param array $params
  2462. */
  2463. protected function _forward($route, $params = array())
  2464. {
  2465. $request = $this->getRequest();
  2466. if(is_string($params))
  2467. {
  2468. parse_str($params, $params);
  2469. }
  2470. $oldRoute = $request->getRoute();
  2471. $route = explode('/', trim($route, '/'));
  2472. switch (count($route)) {
  2473. case 3:
  2474. if($route[0] !== '*') $request->setModule($route[0]);
  2475. if($route[1] !== '*') $request->setController($route[1]);
  2476. $request->setAction($route[2]);
  2477. break;
  2478. case 2:
  2479. if($route[1] !== '*') $request->setController($route[0]);
  2480. $request->setAction($route[1]);
  2481. break;
  2482. case 1:
  2483. $request->setAction($route[0]);
  2484. break;
  2485. default:
  2486. return;
  2487. break;
  2488. }
  2489. $request->addRouteHistory($oldRoute);
  2490. if(false !== $params) $request->setRequestParams($params);
  2491. $request->setDispatched(false);
  2492. }
  2493. /**
  2494. * @param string $methodName
  2495. * @param array $args
  2496. * @return void
  2497. * @throws eException
  2498. */
  2499. public function __call($methodName, $args)
  2500. {
  2501. if ('action' == substr($methodName, 0, 6))
  2502. {
  2503. $action = substr($methodName, 6);
  2504. throw new eException('Action "'.$action.'" does not exist', 2404);
  2505. }
  2506. throw new eException('Method "'.$methodName.'" does not exist', 3404);
  2507. }
  2508. }
  2509. /**
  2510. * @package e107
  2511. * @subpackage e107_handlers
  2512. * @version $Id$
  2513. *
  2514. * Base front-end controller
  2515. */
  2516. class eControllerFront extends eController
  2517. {
  2518. /**
  2519. * Plugin name - used to check if plugin is installed
  2520. * Set this only if plugin requires installation
  2521. * @var string
  2522. */
  2523. protected $plugin = null;
  2524. /**
  2525. * Default controller access
  2526. * @var integer
  2527. */
  2528. protected $userclass = e_UC_PUBLIC;
  2529. /**
  2530. * Generic 404 page URL (redirect), SITEURL will be added
  2531. * @var string
  2532. */
  2533. protected $e404 = '404.html';
  2534. /**
  2535. * Generic 403 page URL (redirect), SITEURL will be added
  2536. * @var string
  2537. */
  2538. protected $e403 = '403.html';
  2539. /**
  2540. * Generic 404 route URL (forward)
  2541. * @var string
  2542. */
  2543. protected $e404route = 'index/not-found';
  2544. /**
  2545. * Generic 403 route URL (forward)
  2546. * @var string
  2547. */
  2548. protected $e403route = 'index/access-denied';
  2549. /**
  2550. * View renderer objects
  2551. * @var array
  2552. */
  2553. protected $_validator;
  2554. /**
  2555. * Per action access
  2556. * Format 'action' => userclass
  2557. * @var array
  2558. */
  2559. protected $access = array();
  2560. /**
  2561. * User input filter (_GET)
  2562. * Format 'action' => array(var => validationArray)
  2563. * @var array
  2564. */
  2565. protected $filter = array();
  2566. /**
  2567. * Base constructor - set 404/403 locations
  2568. */
  2569. public function __construct(eRequest $request, eResponse $response = null)
  2570. {
  2571. parent::__construct($request, $response);
  2572. $this->_init();
  2573. }
  2574. /**
  2575. * Base init, called after the public init() - handle access restrictions
  2576. * The base init() method is able to change controller variables on the fly (e.g. access, filters, etc)
  2577. */
  2578. final protected function _init()
  2579. {
  2580. // plugin check
  2581. if(null !== $this->plugin)
  2582. {
  2583. if(!e107::isInstalled($this->plugin))
  2584. {
  2585. $this->forward403();
  2586. return;
  2587. }
  2588. }
  2589. // global controller restriction
  2590. if(!e107::getUser()->checkClass($this->userclass, false))
  2591. {
  2592. $this->forward403();
  2593. return;
  2594. }
  2595. // by action access
  2596. if(!$this->checkActionPermissions()) exit;
  2597. // _GET input validation
  2598. $this->validateInput();
  2599. // Set Render mode to module-controller-action, override possible within the action
  2600. $this->getResponse()->setRenderMod(str_replace('/', '-', $this->getRequest()->getRoute()));
  2601. }
  2602. /**
  2603. * Check persmission for current action
  2604. * @return boolean
  2605. */
  2606. protected function checkActionPermissions()
  2607. {
  2608. // per action restrictions
  2609. $action = $this->getRequest()->getAction();
  2610. if(isset($this->access[$action]) && !e107::getUser()->checkClass($this->access[$action], false))
  2611. {
  2612. $this->forward403();
  2613. return false;
  2614. }
  2615. return true;
  2616. }
  2617. public function redirect404()
  2618. {
  2619. e107::getRedirect()->redirect(SITEURL.$this->e404);
  2620. }
  2621. public function redirect403()
  2622. {
  2623. e107::getRedirect()->redirect(SITEURL.$this->e403);
  2624. }
  2625. public function forward404()
  2626. {
  2627. $this->_forward($this->e404route);
  2628. }
  2629. public function forward403()
  2630. {
  2631. $this->_forward($this->e403route);
  2632. }
  2633. /**
  2634. * Controller validator object
  2635. * @return e_validator
  2636. */
  2637. public function getValidator()
  2638. {
  2639. if(null === $this->_validator)
  2640. {
  2641. $this->_validator = new e_validator('controller');
  2642. }
  2643. return $this->_validator;
  2644. }
  2645. /**
  2646. * Register request parameters based on current $filter data (_GET only)
  2647. * Additional security layer
  2648. */
  2649. public function validateInput()
  2650. {
  2651. $validator = $this->getValidator();
  2652. $request = $this->getRequest();
  2653. if(empty($this->filter) || !isset($this->filter[$request->getAction()])) return;
  2654. $validator->setRules($this->filter[$request->getAction()])
  2655. ->validate($_GET);
  2656. $validData = $validator->getValidData();
  2657. foreach ($validData as $key => $value)
  2658. {
  2659. if(!$request->isRequestParam($key)) $request->setRequestParam($key, $value);
  2660. }
  2661. $validator->clearValidateMessages();
  2662. }
  2663. /**
  2664. * System error message proxy
  2665. * @param string $message
  2666. * @param boolean $session
  2667. */
  2668. public function messageError($message, $session = false)
  2669. {
  2670. return e107::getMessage()->addError($message, 'default', $session);
  2671. }
  2672. /**
  2673. * System success message proxy
  2674. * @param string $message
  2675. * @param boolean $session
  2676. */
  2677. public function messageSuccess($message, $session = false)
  2678. {
  2679. return e107::getMessage()->addSuccess($message, 'default', $session);
  2680. }
  2681. /**
  2682. * System warning message proxy
  2683. * @param string $message
  2684. * @param boolean $session
  2685. */
  2686. public function messageWarning($message, $session = false)
  2687. {
  2688. return e107::getMessage()->addWarning($message, 'default', $session);
  2689. }
  2690. /**
  2691. * System debug message proxy
  2692. * @param string $message
  2693. * @param boolean $session
  2694. */
  2695. public function messageDebug($message, $session = false)
  2696. {
  2697. return e107::getMessage()->addDebug($message, 'default', $session);
  2698. }
  2699. }
  2700. /**
  2701. * Request handler
  2702. *
  2703. */
  2704. class eRequest
  2705. {
  2706. /**
  2707. * @var string
  2708. */
  2709. protected $_module;
  2710. /**
  2711. * @var string
  2712. */
  2713. protected $_controller;
  2714. /**
  2715. * @var string
  2716. */
  2717. protected $_action;
  2718. /**
  2719. * Request status
  2720. * @var boolean
  2721. */
  2722. protected $_dispatched = false;
  2723. /**
  2724. * @var array
  2725. */
  2726. protected $_requestParams = array();
  2727. /**
  2728. * @var string
  2729. */
  2730. protected $_basePath;
  2731. /**
  2732. * @var string
  2733. */
  2734. protected $_pathInfo;
  2735. /**
  2736. * @var string
  2737. */
  2738. protected $_requestInfo;
  2739. /**
  2740. * Pathinfo string used for initial system routing
  2741. */
  2742. public $routePathInfo;
  2743. /**
  2744. * @var array
  2745. */
  2746. protected $_routeHistory = array();
  2747. /**
  2748. * @var boolean if request is already routed - generally set by callbacks to notify router about route changes
  2749. */
  2750. public $routed = false;
  2751. /**
  2752. * Name of the bootstrap file
  2753. * @var string
  2754. */
  2755. public $singleEntry = 'index.php';
  2756. /**
  2757. * Request constructor
  2758. */
  2759. public function __construct($route = null)
  2760. {
  2761. if(null !== $route)
  2762. {
  2763. $this->setRoute($route);
  2764. $this->routed = true;
  2765. }
  2766. }
  2767. /**
  2768. * Get system base path
  2769. * @return string
  2770. */
  2771. public function getBasePath()
  2772. {
  2773. if(null == $this->_basePath)
  2774. {
  2775. $this->_basePath = e_HTTP;
  2776. if(!e107::getPref('url_disable_pathinfo')) $this->_basePath .= $this->singleEntry.'/';
  2777. }
  2778. return $this->_basePath;
  2779. }
  2780. /**
  2781. * Set system base path
  2782. * @param string $basePath
  2783. * @return eRequest
  2784. */
  2785. public function setBasePath($basePath)
  2786. {
  2787. $this->_basePath = $basePath;
  2788. return $this;
  2789. }
  2790. /**
  2791. * Get path info
  2792. * If not set, it'll be auto-retrieved
  2793. * @return string path info
  2794. */
  2795. public function getPathInfo()
  2796. {
  2797. if(null == $this->_pathInfo)
  2798. {
  2799. if($this->getBasePath() == $this->getRequestInfo())
  2800. $this->_pathInfo = ''; // map to indexRoute
  2801. else
  2802. $this->_pathInfo = substr($this->getRequestInfo(), strlen($this->getBasePath()));
  2803. if($this->_pathInfo && trim($this->_pathInfo, '/') == trim($this->singleEntry, '/')) $this->_pathInfo = '';
  2804. }
  2805. return $this->_pathInfo;
  2806. }
  2807. /**
  2808. * Override path info
  2809. * @param string $pathInfo
  2810. * @return eRequest
  2811. */
  2812. public function setPathInfo($pathInfo)
  2813. {
  2814. $this->_pathInfo = $pathInfo;
  2815. return $this;
  2816. }
  2817. /**
  2818. * @return string request info
  2819. */
  2820. public function getRequestInfo()
  2821. {
  2822. if(null === $this->_requestInfo)
  2823. {
  2824. $this->_requestInfo = e_REQUEST_HTTP;
  2825. }
  2826. return $this->_requestInfo;
  2827. }
  2828. /**
  2829. * Override request info
  2830. * @param string $pathInfo
  2831. * @return eRequest
  2832. */
  2833. public function setRequestInfo($requestInfo)
  2834. {
  2835. $this->_requestInfo = $requestInfo;
  2836. return $this;
  2837. }
  2838. /**
  2839. * Quick front page check
  2840. */
  2841. public static function isFrontPage($entryScript = 'index.php', $currentPathInfo = e_REQUEST_HTTP)
  2842. {
  2843. $basePath = e_HTTP;
  2844. if(!e107::getPref('url_disable_pathinfo')) $basePath .= $entryScript.'/';
  2845. return ($basePath == $currentPathInfo);
  2846. }
  2847. /**
  2848. * Get current controller string
  2849. * @return string
  2850. */
  2851. public function getController()
  2852. {
  2853. return $this->_controller;
  2854. }
  2855. /**
  2856. * Get current controller name
  2857. * Example: requested controller-name or 'controller name' -> converted to controller_name
  2858. * @return string
  2859. */
  2860. public function getControllerName()
  2861. {
  2862. return eHelper::underscore($this->_controller);
  2863. }
  2864. /**
  2865. * Set current controller name
  2866. * Example: controller_name OR 'controller name' -> converted to controller-name
  2867. * Always sanitized
  2868. * @param string $controller
  2869. * @return eRequest
  2870. */
  2871. public function setController($controller)
  2872. {
  2873. $this->_controller = strtolower(eHelper::dasherize($this->sanitize($controller)));
  2874. return $this;
  2875. }
  2876. /**
  2877. * Get current module string
  2878. * @return string
  2879. */
  2880. public function getModule()
  2881. {
  2882. return $this->_module;
  2883. }
  2884. /**
  2885. * Get current module name
  2886. * Example: module-name OR 'module name' -> converted to module_name
  2887. * @return string
  2888. */
  2889. public function getModuleName()
  2890. {
  2891. return eHelper::underscore($this->_module);
  2892. }
  2893. /**
  2894. * Set current module name
  2895. * Example: module_name OR 'module name' -> converted to module-name
  2896. * Always sanitized
  2897. * @param string $module
  2898. * @return eRequest
  2899. */
  2900. public function setModule($module)
  2901. {
  2902. $this->_module = strtolower(eHelper::dasherize($this->sanitize($module)));
  2903. return $this;
  2904. }
  2905. /**
  2906. * Get current action string
  2907. * @return string
  2908. */
  2909. public function getAction()
  2910. {
  2911. return $this->_action;
  2912. }
  2913. /**
  2914. * Get current action name
  2915. * Example: action-name OR 'action name' OR action_name -> converted to ActionName
  2916. * @return string
  2917. */
  2918. public function getActionName()
  2919. {
  2920. return eHelper::camelize($this->_action, true);
  2921. }
  2922. /**
  2923. * Get current action method name
  2924. * Example: action-name OR 'action name' OR action_name -> converted to actionActionName
  2925. * @return string
  2926. */
  2927. public function getActionMethodName()
  2928. {
  2929. return 'action'.eHelper::camelize($this->_action, true);
  2930. }
  2931. /**
  2932. * Set current action name
  2933. * Example: action_name OR 'action name' OR Action_Name OR 'Action Name' -> converted to ation-name
  2934. * Always sanitized
  2935. * @param string $action
  2936. * @return eRequest
  2937. */
  2938. public function setAction($action)
  2939. {
  2940. $this->_action = strtolower(eHelper::dasherize($this->sanitize($action)));
  2941. return $this;
  2942. }
  2943. /**
  2944. * Get current route string/array -> module/controller/action
  2945. * @param boolean $array
  2946. * @return string|array route
  2947. */
  2948. public function getRoute($array = false)
  2949. {
  2950. if(!$this->getModule())
  2951. {
  2952. $route = array('index', 'index', 'index');
  2953. }
  2954. else
  2955. {
  2956. $route = array(
  2957. $this->getModule(),
  2958. $this->getController() ? $this->getController() : 'index',
  2959. $this->getAction() ? $this->getAction() : 'index',
  2960. );
  2961. }
  2962. return ($array ? $route : implode('/', $route));
  2963. }
  2964. /**
  2965. * Set current route
  2966. * @param string $route module/controller/action
  2967. * @return eRequest
  2968. */
  2969. public function setRoute($route)
  2970. {
  2971. if(null === $route)
  2972. {
  2973. $this->_module = null;
  2974. $this->_controller = null;
  2975. $this->_action = null;
  2976. }
  2977. return $this->initFromRoute($route);
  2978. }
  2979. /**
  2980. * System routing track, used in controllers forwarder
  2981. * @param string $route
  2982. * @return eRequest
  2983. */
  2984. public function addRouteHistory($route)
  2985. {
  2986. $this->_routeHistory[] = $route;
  2987. return $this;
  2988. }
  2989. /**
  2990. * Retrieve route from history track
  2991. * Based on $source we can retrieve
  2992. * - array of all history records
  2993. * - 'first' route record
  2994. * - 'last' route record
  2995. * - history record by its index number
  2996. * @param mixed $source
  2997. * @return string|array
  2998. */
  2999. public function getRouteHistory($source = null)
  3000. {
  3001. if(null === $source) return $this->_routeHistory;
  3002. if(!$this->_routeHistory) return null;
  3003. elseif('last' === $source)
  3004. {
  3005. return $this->_routeHistory[count($this->_routeHistory) -1];
  3006. }
  3007. elseif('first' === $source)
  3008. {
  3009. return $this->_routeHistory[0];
  3010. }
  3011. elseif(is_int($source))
  3012. {
  3013. return isset($this->_routeHistory[$source]) ? $this->_routeHistory[$source] : null;
  3014. }
  3015. return null;
  3016. }
  3017. /**
  3018. * Search route history for the given $route
  3019. *
  3020. * @param string $route
  3021. * @return integer route index or false if not found
  3022. */
  3023. public function findRouteHistory($route)
  3024. {
  3025. return array_search($route, $this->_routeHistory);
  3026. }
  3027. /**
  3028. * Populate module, controller and action from route string
  3029. * @param string $route
  3030. * @return array route data
  3031. */
  3032. public function initFromRoute($route)
  3033. {
  3034. $route = trim($route, '/');
  3035. if(!$route)
  3036. {
  3037. $route = 'index/index/index';
  3038. }
  3039. $parts = explode('/', $route);
  3040. $this->setModule($parts[0])
  3041. ->setController(vartrue($parts[1], 'index'))
  3042. ->setAction(vartrue($parts[2], 'index'));
  3043. return $this;//->getRoute(true);
  3044. }
  3045. /**
  3046. * Get request parameter
  3047. * @param string $key
  3048. * @param string $default value if key not set
  3049. * @return mixed value
  3050. */
  3051. public function getRequestParam($key, $default = null)
  3052. {
  3053. return (isset($this->_requestParams[$key]) ? $this->_requestParams[$key] : $default);
  3054. }
  3055. /**
  3056. * Check if request parameter exists
  3057. * @param string $key
  3058. * @return boolean
  3059. */
  3060. public function isRequestParam($key)
  3061. {
  3062. return isset($this->_requestParams[$key]);
  3063. }
  3064. /**
  3065. * Get request parameters array
  3066. * @return array value
  3067. */
  3068. public function getRequestParams()
  3069. {
  3070. return $this->_requestParams;
  3071. }
  3072. /**
  3073. * Set request parameter
  3074. * @param string $key
  3075. * @param mixed $value
  3076. * @return eRequest
  3077. */
  3078. public function setRequestParam($key, $value)
  3079. {
  3080. $this->_requestParams[$key] = $value;
  3081. return $this;
  3082. }
  3083. /**
  3084. * Set request parameters
  3085. * @param array $params
  3086. * @return eRequest
  3087. */
  3088. public function setRequestParams($params)
  3089. {
  3090. $this->_requestParams = $params;
  3091. return $this;
  3092. }
  3093. /**
  3094. * Populate current request parameters (_GET scope)
  3095. * @return eRequest
  3096. */
  3097. public function populateRequestParams()
  3098. {
  3099. $rp = $this->getRequestParams();
  3100. foreach ($rp as $key => $value)
  3101. {
  3102. $_GET[$key] = $value;
  3103. }
  3104. return $this;
  3105. }
  3106. /**
  3107. * More BC
  3108. * @param string $qstring
  3109. * @return eRequest
  3110. */
  3111. public function setLegacyQstring($qstring = null)
  3112. {
  3113. if(defined('e_QUERY')) return $this;
  3114. if(null === $qstring)
  3115. {
  3116. $qstring = self::getQueryString();
  3117. }
  3118. define("e_SELF", e_REQUEST_SELF);
  3119. define("e_QUERY", $qstring);
  3120. $_SERVER['QUERY_STRING'] = e_QUERY;
  3121. if(strpos(e_QUERY,"=")!==false ) // Fix for legacyQuery using $_GET ie. ?x=y&z=1 etc.
  3122. {
  3123. parse_str(str_replace(array('&amp;'), array('&'), e_QUERY),$tmp);
  3124. foreach($tmp as $key=>$value)
  3125. {
  3126. $_GET[$key] = $value;
  3127. }
  3128. }
  3129. return $this;
  3130. }
  3131. /**
  3132. * And More BC :/
  3133. * @param string $page
  3134. * @return eRequest
  3135. */
  3136. public function setLegacyPage($page = null)
  3137. {
  3138. if(defined('e_PAGE')) return $this;
  3139. if(null === $page)
  3140. {
  3141. $page = eFront::isLegacy();
  3142. }
  3143. if(!$page)
  3144. {
  3145. define('e_PAGE', $this->singleEntry);
  3146. }
  3147. else define('e_PAGE', basename(str_replace(array('{', '}'), '/', $page)));
  3148. return $this;
  3149. }
  3150. /**
  3151. * And More from the same - BC :/
  3152. * @return string
  3153. */
  3154. public static function getQueryString()
  3155. {
  3156. $qstring = '';
  3157. if($_SERVER['QUERY_STRING'])
  3158. {
  3159. $qstring = str_replace(array('{', '}', '%7B', '%7b', '%7D', '%7d'), '', rawurldecode($_SERVER['QUERY_STRING']));
  3160. }
  3161. $qstring = str_replace('&', '&amp;', e107::getParser()->post_toForm($qstring));
  3162. return $qstring;
  3163. }
  3164. /**
  3165. * Basic sanitize method for module, controller and action input values
  3166. * @param string $str string to be sanitized
  3167. * @param string $pattern optional replace pattern
  3168. * @param string $replace optional replace string, defaults to dash
  3169. */
  3170. public function sanitize($str, $pattern='', $replace='-')
  3171. {
  3172. if (!$pattern) $pattern = '/[^\w\pL-]/u';
  3173. return preg_replace($pattern, $replace, $str);
  3174. }
  3175. /**
  3176. * Set dispatched status of the request
  3177. * @param boolean $mod
  3178. * @return eRequest
  3179. */
  3180. public function setDispatched($mod)
  3181. {
  3182. $this->_dispatched = $mod ? true : false;
  3183. return $this;
  3184. }
  3185. /**
  3186. * Get dispatched status of the request
  3187. * @return boolean
  3188. */
  3189. public function isDispatched()
  3190. {
  3191. return $this->_dispatched;
  3192. }
  3193. }
  3194. class eResponse
  3195. {
  3196. protected $_body = array('default' => '');
  3197. protected $_title = array('default' => array());
  3198. protected $_e_PAGETITLE = array();
  3199. protected $_META_DESCRIPTION = array();
  3200. protected $_META_KEYWORDS = array();
  3201. protected $_render_mod = array('default' => 'default');
  3202. protected $_meta_title_separator = ' - ';
  3203. protected $_meta = array();
  3204. protected $_title_separator = ' &raquo; ';
  3205. protected $_content_type = 'html';
  3206. protected $_content_type_arr = array(
  3207. 'html' => 'text/html',
  3208. 'css' => 'text/css',
  3209. 'xml' => 'text/xml',
  3210. 'json' => 'application/json',
  3211. 'js' => 'application/javascript',
  3212. 'rss' => 'application/rss+xml',
  3213. 'soap' => 'application/soap+xml',
  3214. );
  3215. protected $_params = array(
  3216. 'render' => true,
  3217. 'meta' => false,
  3218. 'jsonNoTitle' => false,
  3219. 'jsonRender' => false,
  3220. );
  3221. public function setParam($key, $value)
  3222. {
  3223. $this->_params[$key] = $value;
  3224. return $this;
  3225. }
  3226. public function setParams($params)
  3227. {
  3228. $this->_params = $params;
  3229. return $this;
  3230. }
  3231. public function getParam($key, $default = null)
  3232. {
  3233. return (isset($this->_params[$key]) ? $this->_params[$key] : $default);
  3234. }
  3235. public function isParam($key)
  3236. {
  3237. return isset($this->_params[$key]);
  3238. }
  3239. public function addContentType($typeName, $mediaType)
  3240. {
  3241. $this->_content_type_arr[$typeName] = $mediaType;
  3242. return $this;
  3243. }
  3244. public function getContentType()
  3245. {
  3246. return $this->_content_type;
  3247. }
  3248. public function getContentMediaType($typeName)
  3249. {
  3250. if(isset($this->_content_type_arr[$typeName]))
  3251. return $this->_content_type_arr[$typeName];
  3252. }
  3253. public function setContentType($typeName)
  3254. {
  3255. $this->_content_type = $typeName;
  3256. }
  3257. /**
  3258. * @return eResponse
  3259. */
  3260. public function sendContentType()
  3261. {
  3262. $ctypeStr = $this->getContentMediaType($this->getContentType());
  3263. if($ctypeStr)
  3264. {
  3265. header('Content-type: '.$this->getContentMediaType($this->getContentType()).'; charset=utf-8', TRUE);
  3266. }
  3267. return $this;
  3268. }
  3269. /**
  3270. * @return eResponse
  3271. */
  3272. public function addHeader($header, $override = false, $responseCode = null)
  3273. {
  3274. header($header, $override, $responseCode);
  3275. return $this;
  3276. }
  3277. /**
  3278. * Append content
  3279. * @param str $body
  3280. * @param str $ns namespace
  3281. * @return eResponse
  3282. */
  3283. public function appendBody($body, $ns = 'default')
  3284. {
  3285. if(!isset($this->_body[$ns]))
  3286. {
  3287. $this->_body[$ns] = '';
  3288. }
  3289. $this->_body[$ns] .= $body;
  3290. return $this;
  3291. }
  3292. /**
  3293. * Set content
  3294. * @param str $body
  3295. * @param str $ns namespace
  3296. * @return eResponse
  3297. */
  3298. public function setBody($body, $ns = 'default')
  3299. {
  3300. $this->_body[$ns] = $body;
  3301. return $this;
  3302. }
  3303. /**
  3304. * Prepend content
  3305. * @param str $body
  3306. * @param str $ns namespace
  3307. * @return eResponse
  3308. */
  3309. function prependBody($body, $ns = 'default')
  3310. {
  3311. if(!isset($this->_body[$ns]))
  3312. {
  3313. $this->_body[$ns] = '';
  3314. }
  3315. $this->_body[$ns] = $content.$this->_body[$ns];
  3316. return $this;
  3317. }
  3318. /**
  3319. * Get content
  3320. * @param str $ns
  3321. * @param boolean $reset
  3322. * @return string
  3323. */
  3324. public function getBody($ns = 'default', $reset = false)
  3325. {
  3326. if(!isset($this->_body[$ns]))
  3327. {
  3328. $this->_body[$ns] = '';
  3329. }
  3330. $ret = $this->_body[$ns];
  3331. if($reset) unset($this->_body[$ns]);
  3332. return $ret;
  3333. }
  3334. /**
  3335. * @param str $title
  3336. * @param str $ns
  3337. * @return eResponse
  3338. */
  3339. function setTitle($title, $ns = 'default')
  3340. {
  3341. if(!is_string($ns) || empty($ns))
  3342. {
  3343. $this->_title['default'] = array((string) $title);
  3344. }
  3345. else
  3346. {
  3347. $this->_title[$ns] = array((string) $title);
  3348. }
  3349. return $this;
  3350. }
  3351. /**
  3352. * @param str $title
  3353. * @param str $ns
  3354. * @return eResponse
  3355. */
  3356. function appendTitle($title, $ns = 'default')
  3357. {
  3358. if(empty($title))
  3359. {
  3360. return $this;
  3361. }
  3362. if(!is_string($ns) || empty($ns))
  3363. {
  3364. $ns = 'default';
  3365. }
  3366. elseif(!isset($this->_title[$ns]))
  3367. {
  3368. $this->_title[$ns] = array();
  3369. }
  3370. $this->_title[$ns][] = (string) $title;
  3371. return $this;
  3372. }
  3373. /**
  3374. * @param str $title
  3375. * @param str $ns
  3376. * @return eResponse
  3377. */
  3378. function prependTitle($title, $ns = 'default')
  3379. {
  3380. if(empty($title))
  3381. {
  3382. return $this;
  3383. }
  3384. if(!is_string($ns) || empty($ns))
  3385. {
  3386. $ns = 'default';
  3387. }
  3388. elseif(!isset($this->_title[$ns]))
  3389. {
  3390. $this->_title[$ns] = array();
  3391. }
  3392. array_unshift($this->_title[$ns], $title);
  3393. return $this;
  3394. }
  3395. /**
  3396. * Assemble title
  3397. * @param str $ns
  3398. * @param bool $reset
  3399. */
  3400. function getTitle($ns = 'default', $reset = false)
  3401. {
  3402. if(!is_string($ns) || empty($ns))
  3403. {
  3404. $ret = implode($this->_title_separator, $this->_title['default']);
  3405. if($reset)
  3406. $this->_title['default'] = '';
  3407. }
  3408. elseif(isset($this->_title[$ns]))
  3409. {
  3410. $ret = implode($this->_title_separator, $this->_title[$ns]);
  3411. if($reset)
  3412. unset($this->_title[$ns]);
  3413. }
  3414. else
  3415. {
  3416. $ret = '';
  3417. }
  3418. return $ret;
  3419. }
  3420. /**
  3421. *
  3422. * @param string $render_mod
  3423. * @param string $ns
  3424. * @return eResponse
  3425. */
  3426. function setRenderMod($render_mod, $ns = 'default')
  3427. {
  3428. if(!is_string($ns) || empty($ns))
  3429. {
  3430. return $this;
  3431. }
  3432. $this->_render_mod[$ns] = (string) $render_mod;
  3433. return $this;
  3434. }
  3435. /**
  3436. * Retrieve render mod
  3437. * @param string $ns
  3438. */
  3439. function getRenderMod($ns = 'default')
  3440. {
  3441. if(!is_string($ns) || empty($ns))
  3442. {
  3443. $ns = 'default';
  3444. }
  3445. return vartrue($this->_render_mod[$ns], null);
  3446. }
  3447. /**
  3448. * Generic meta information
  3449. * Example usage:
  3450. * addMeta('og:title', 'My Title');
  3451. * addMeta(null, 30, array('http-equiv' => 'refresh'));
  3452. * addMeta(null, null, array('http-equiv' => 'refresh', 'content' => 30)); // same as above
  3453. * @param string $name 'name' attribute value, or null to avoid it
  3454. * @param string $content 'content' attribute value, or null to avoid it
  3455. * @param array $extended format 'attribute_name' => 'value'
  3456. * @return eResponse
  3457. */
  3458. public function addMeta($name = null, $content = null, $extended = array())
  3459. {
  3460. if(empty($content)){ return $this; } // content is required, otherwise ignore.
  3461. //TODO need an option that allows subsequent entries to overwrite existing ones.
  3462. //ie. 'description' and 'keywords' should never be duplicated, but overwritten by plugins and other non-pref-based meta data.
  3463. $attr = array();
  3464. if(null !== $name)
  3465. {
  3466. $key = (substr($name,0,3) == 'og:') ? 'property' : 'name';
  3467. $attr[$key] = $name;
  3468. }
  3469. if(null !== $content) $attr['content'] = $content;
  3470. if(!empty($extended))
  3471. {
  3472. if(!empty($attr)) $attr = array_merge($attr, $extended);
  3473. else $attr = $extended;
  3474. }
  3475. if(!empty($attr)) $this->_meta[] = $attr;
  3476. return $this;
  3477. }
  3478. /**
  3479. * Render meta tags, registered via addMeta() method
  3480. * @return string
  3481. */
  3482. public function renderMeta()
  3483. {
  3484. $attrData = '';
  3485. foreach ($this->_meta as $attr)
  3486. {
  3487. $attrData .= '<meta';
  3488. foreach ($attr as $p => $v)
  3489. {
  3490. $attrData .= ' '.preg_replace('/[^\w\-]/', '', $p).'="'.str_replace(array('"', '<'), '', $v).'"';
  3491. }
  3492. $attrData .= ' />'."\n";
  3493. }
  3494. return $attrData;
  3495. }
  3496. /**
  3497. * Add meta title, description and keywords
  3498. *
  3499. * @param string $meta property name
  3500. * @param string $content meta content
  3501. * @return eResponse
  3502. */
  3503. function addMetaData($meta, $content)
  3504. {
  3505. $meta = '_' . $meta;
  3506. if(isset($this->$meta) && !empty($content))
  3507. {
  3508. $content = str_replace(array('&amp;', '"', "'"), array('&', '', ''), $content);
  3509. $this->{$meta}[] = htmlspecialchars((string) $content, ENT_QUOTES, 'UTF-8');
  3510. }
  3511. return $this;
  3512. }
  3513. /**
  3514. * Get meta title, description and keywords
  3515. *
  3516. * @param string $meta property name
  3517. * @return string
  3518. */
  3519. function getMetaData($meta, $separator = '')
  3520. {
  3521. $meta = '_' . $meta;
  3522. if(isset($this->$meta) && !empty($this->$meta))
  3523. {
  3524. return implode($separator, $this->$meta);
  3525. }
  3526. return '';
  3527. }
  3528. /**
  3529. * @param string $title
  3530. * @return eResponse
  3531. */
  3532. function addMetaTitle($title)
  3533. {
  3534. return $this->addMetaData('e_PAGETITLE', $title);
  3535. }
  3536. function getMetaTitle()
  3537. {
  3538. return $this->getMetaData('e_PAGETITLE', $this->_meta_title_separator);
  3539. }
  3540. /**
  3541. * @param string $description
  3542. * @return eResponse
  3543. */
  3544. function addMetaDescription($description)
  3545. {
  3546. return $this->addMetaData('META_DESCRIPTION', $description);
  3547. }
  3548. function getMetaDescription()
  3549. {
  3550. return $this->getMetaData('META_DESCRIPTION');
  3551. }
  3552. /**
  3553. * @param string $keywords
  3554. * @return eResponse
  3555. */
  3556. function addMetaKeywords($keywords)
  3557. {
  3558. return $this->addMetaData('META_KEYWORDS', $keywords);
  3559. }
  3560. function getMetaKeywords()
  3561. {
  3562. return $this->getMetaData('META_KEYWORDS', ',');
  3563. }
  3564. /**
  3565. * Send e107 meta-data
  3566. * @return eResponse
  3567. */
  3568. function sendMeta()
  3569. {
  3570. //HEADERF already included or meta content already sent
  3571. if(e_AJAX_REQUEST || defined('USER_AREA') || defined('e_PAGETITLE'))
  3572. return $this;
  3573. if(!defined('e_PAGETITLE') && !empty($this->_e_PAGETITLE))
  3574. {
  3575. define('e_PAGETITLE', $this->getMetaTitle());
  3576. }
  3577. if(!defined('META_DESCRIPTION') && !empty($this->_META_DESCRIPTION))
  3578. {
  3579. define('META_DESCRIPTION', $this->getMetaDescription());
  3580. }
  3581. if(!defined('META_KEYWORDS') && !empty($this->_META_KEYWORDS))
  3582. {
  3583. define('META_KEYWORDS', $this->getMetaKeywords());
  3584. }
  3585. return $this;
  3586. }
  3587. /**
  3588. * Send Response Output - default method
  3589. * TODO - ajax send, using js_manager
  3590. * @param string $ns namespace/segment
  3591. * @param bool $return
  3592. * @param bool $render_message append system messages
  3593. */
  3594. function send($ns = null, $return = true, $render_message = true)
  3595. {
  3596. $content = $this->getBody($ns, true);
  3597. $render = $this->getParam('render');
  3598. $meta = $this->getParam('meta');
  3599. $this->sendContentType();
  3600. if($render_message)
  3601. {
  3602. $content = eMessage::getInstance()->render().$content;
  3603. }
  3604. if($meta)
  3605. {
  3606. $this->sendMeta();
  3607. }
  3608. //render disabled by the controller
  3609. if(!$this->getRenderMod($ns))
  3610. {
  3611. $render = false;
  3612. }
  3613. if($render)
  3614. {
  3615. $render = e107::getRender();
  3616. if($return)
  3617. {
  3618. return $render->tablerender($this->getTitle($ns, true), $content, $this->getRenderMod($ns), true);
  3619. }
  3620. else
  3621. {
  3622. $render->tablerender($this->getTitle($ns, true), $content, $this->getRenderMod($ns));
  3623. return '';
  3624. }
  3625. }
  3626. elseif($return)
  3627. {
  3628. return $content;
  3629. }
  3630. else
  3631. {
  3632. print $content;
  3633. return '';
  3634. }
  3635. }
  3636. /**
  3637. * Send AJAX Json Response Output - default method
  3638. * It's fully compatible with the core dialog.js
  3639. * @param array $override override output associative array (header, body and footer keys)
  3640. * @param string $ns namespace/segment
  3641. * @param bool $render_message append system messages
  3642. */
  3643. function sendJson($override = array(), $ns = null, $render_message = true)
  3644. {
  3645. if(!$ns) $ns = 'default';
  3646. $content = $this->getBody($ns, true);
  3647. // separate render parameter for json response, false by default
  3648. $render = $this->getParam('jsonRender');
  3649. if($render_message)
  3650. {
  3651. $content = eMessage::getInstance()->render().$content;
  3652. }
  3653. //render disabled by the controller
  3654. if(!$this->getRenderMod($ns))
  3655. {
  3656. $render = false;
  3657. }
  3658. $title = '';
  3659. if(!$this->getParam('jsonNoTitle'))
  3660. {
  3661. $titleArray = $this->_title;
  3662. $title = isset($titleArray[$ns]) ? array_pop($titleArray[$ns]) : '';
  3663. }
  3664. if($render)
  3665. {
  3666. $render = e107::getRender();
  3667. $content = $render->tablerender($this->getTitle($ns, true), $content, $this->getRenderMod($ns), true);
  3668. }
  3669. $jshelper = e107::getJshelper();
  3670. $override = array_merge(array(
  3671. 'header' => $title,
  3672. 'body' => $content,
  3673. 'footer' => $statusText,
  3674. ), $override);
  3675. echo $jshelper->buildJsonResponse($override);
  3676. $jshelper->sendJsonResponse(null);
  3677. }
  3678. /**
  3679. * JS manager
  3680. * @return e_jsmanager
  3681. */
  3682. function getJs()
  3683. {
  3684. return e107::getJs();
  3685. }
  3686. }
  3687. /**
  3688. * We move all generic helper functionallity here - a lot of candidates in e107 class
  3689. *
  3690. */
  3691. class eHelper
  3692. {
  3693. protected static $_classRegEx = '#[^\w\s\-]#';
  3694. protected static $_idRegEx = '#[^\w\-]#';
  3695. protected static $_styleRegEx = '#[^\w\s\-\.;:!]#';
  3696. public static function secureClassAttr($string)
  3697. {
  3698. return preg_replace(self::$_classRegEx, '', $string);
  3699. }
  3700. public static function secureIdAttr($string)
  3701. {
  3702. return preg_replace(self::$_idRegEx, '', $string);
  3703. }
  3704. public static function secureStyleAttr($string)
  3705. {
  3706. return preg_replace(self::$_styleRegEx, '', $string);
  3707. }
  3708. public static function buildAttr($safeArray)
  3709. {
  3710. return http_build_query($safeArray, null, '&');
  3711. }
  3712. public static function formatMetaTitle($title)
  3713. {
  3714. $title = trim(str_replace(array('"', "'"), '', strip_tags(e107::getParser()->toHTML($title, TRUE))));
  3715. return trim(preg_replace('/[\s,]+/', ' ', str_replace('_', ' ', $title)));
  3716. }
  3717. public static function secureSef($sef)
  3718. {
  3719. return trim(preg_replace('/[^\w\pL\s\-+.,]+/u', '', strip_tags(e107::getParser()->toHTML($sef, TRUE))));
  3720. }
  3721. public static function formatMetaKeys($keywordString)
  3722. {
  3723. $keywordString = preg_replace('/[^\w\pL\s\-.,+]/u', '', strip_tags(e107::getParser()->toHTML($keywordString, TRUE)));
  3724. return trim(preg_replace('/[\s]?,[\s]?/', ',', str_replace('_', ' ', $keywordString)));
  3725. }
  3726. public static function formatMetaDescription($descrString)
  3727. {
  3728. $descrString = preg_replace('/[\r]*\n[\r]*/', ' ', trim(str_replace(array('"', "'"), '', strip_tags(e107::getParser()->toHTML($descrString, TRUE)))));
  3729. return trim(preg_replace('/[\s]+/', ' ', str_replace('_', ' ', $descrString)));
  3730. }
  3731. /**
  3732. * Convert title to valid SEF URL string
  3733. * Type ending with 'l' stands for 'to lowercase', ending with 'c' - 'to camel case'
  3734. * @param string $title
  3735. * @param string $type dashl|dashc|dash|underscorel|underscorec|underscore|plusl|plusc|plus|none
  3736. */
  3737. public static function title2sef($title, $type = null)
  3738. {
  3739. $title = preg_replace('/[^\w\pL\s.,]/u', '', strip_tags(e107::getParser()->toHTML($title, TRUE)));
  3740. $title = trim(preg_replace('/[\s]+/', ' ', str_replace('_', ' ', $title)));
  3741. if(null === $type)
  3742. {
  3743. $type = e107::getPref('url_sef_translate');
  3744. }
  3745. $tp = e107::getParser();
  3746. switch ($type)
  3747. {
  3748. case 'dashl': //dasherize, to lower case
  3749. return self::dasherize($tp->ustrtolower($title));
  3750. break;
  3751. case 'dashc': //dasherize, camel case
  3752. return self::dasherize(self::camelize($title, true, ' '));
  3753. break;
  3754. case 'dash': //dasherize
  3755. return self::dasherize($title);
  3756. break;
  3757. case 'underscorel': ///underscore, to lower case
  3758. return self::underscore($tp->ustrtolower($title));
  3759. break;
  3760. case 'underscorec': ///underscore, camel case
  3761. return self::underscore(self::camelize($title, true, ' '));
  3762. break;
  3763. case 'underscore': ///underscore
  3764. return self::underscore($title);
  3765. break;
  3766. case 'plusl': ///plus separator, to lower case
  3767. return str_replace(' ', '+', $tp->ustrtolower($title));
  3768. break;
  3769. case 'plusc': ///plus separator, to lower case
  3770. return str_replace(' ', '+', self::camelize($title, true, ' '));
  3771. break;
  3772. case 'plus': ///plus separator
  3773. return str_replace(' ', '+', $title);
  3774. break;
  3775. case 'none':
  3776. default:
  3777. return $title;
  3778. break;
  3779. }
  3780. }
  3781. /**
  3782. * Return a memory value formatted helpfully
  3783. * $dp overrides the number of decimal places displayed - realistically, only 0..3 are sensible
  3784. * FIXME e107->parseMemorySize() START
  3785. * - move here all e107 class ban/ip related methods
  3786. * - out of (integer) range case?
  3787. * 32 bit systems range: -2147483648 to 2147483647
  3788. * 64 bit systems range: -9223372036854775808 9223372036854775807
  3789. * {@link http://www.php.net/intval}
  3790. * FIXME e107->parseMemorySize() END
  3791. *
  3792. * @param integer $size
  3793. * @param integer $dp
  3794. * @return string formatted size
  3795. */
  3796. public static function parseMemorySize($size, $dp = 2)
  3797. {
  3798. if (!$size) { $size = 0; }
  3799. if ($size < 4096)
  3800. { // Fairly arbitrary limit below which we always return number of bytes
  3801. return number_format($size, 0).CORE_LAN_B;
  3802. }
  3803. $size = $size / 1024;
  3804. $memunit = CORE_LAN_KB;
  3805. if ($size > 1024)
  3806. { /* 1.002 mb, etc */
  3807. $size = $size / 1024;
  3808. $memunit = CORE_LAN_MB;
  3809. }
  3810. if ($size > 1024)
  3811. { /* show in GB if >1GB */
  3812. $size = $size / 1024;
  3813. $memunit = CORE_LAN_GB;
  3814. }
  3815. if ($size > 1024)
  3816. { /* show in TB if >1TB */
  3817. $size = $size / 1024;
  3818. $memunit = CORE_LAN_TB;
  3819. }
  3820. return (number_format($size, $dp).$memunit);
  3821. }
  3822. /**
  3823. * Get the current memory usage of the code
  3824. * If $separator argument is null, raw data (array) will be returned
  3825. *
  3826. * @param null|string $separator
  3827. * @return string|array memory usage
  3828. */
  3829. public static function getMemoryUsage($separator = '/')
  3830. {
  3831. $ret = array();
  3832. if(function_exists("memory_get_usage"))
  3833. {
  3834. $ret[] = eHelper::parseMemorySize(memory_get_usage());
  3835. // With PHP>=5.2.0, can show peak usage as well
  3836. if (function_exists("memory_get_peak_usage")) $ret[] = eHelper::parseMemorySize(memory_get_peak_usage(TRUE));
  3837. }
  3838. else
  3839. {
  3840. $ret[] = 'Unknown';
  3841. }
  3842. return (null !== $separator ? implode($separator, $ret) : $ret);
  3843. }
  3844. public static function camelize($str, $all = false, $space = '')
  3845. {
  3846. // clever recursion o.O
  3847. if($all) return self::camelize('-'.$str, false, $space);
  3848. $tmp = explode('-', str_replace(array('_', ' '), '-', e107::getParser()->ustrtolower($str)));
  3849. return trim(implode($space, array_map('ucfirst', $tmp)), $space);
  3850. }
  3851. public static function labelize($str, $space = ' ')
  3852. {
  3853. return self::camelize($str, true, ' ');
  3854. }
  3855. public static function dasherize($str)
  3856. {
  3857. return str_replace(array('_', ' '), '-', $str);
  3858. }
  3859. public static function underscore($str)
  3860. {
  3861. return str_replace(array('-', ' '), '_', $str);
  3862. }
  3863. /**
  3864. * Parse generic shortcode parameter string
  3865. * Format expected: {SC=key=val&key1=val1...}
  3866. * Escape strings: \& => &
  3867. *
  3868. * @param string $parmstr
  3869. * @return array associative param array
  3870. */
  3871. public static function scParams($parm)
  3872. {
  3873. if (!$parm) return array();
  3874. if (!is_array($parm))
  3875. {
  3876. $parm = str_replace('\&', '%%__amp__%%', $parm);
  3877. $parm = str_replace('&amp;', '&', $parm); // clean when it comes from the DB
  3878. parse_str($parm, $parm);
  3879. foreach ($parm as $k => $v)
  3880. {
  3881. $parm[str_replace('%%__amp__%%', '&', $k)] = str_replace('%%__amp__%%', '\&', $v);
  3882. }
  3883. }
  3884. return $parm;
  3885. }
  3886. /**
  3887. * Parse shortcode parameter string of type 'dual parameters' - advanced, more complex and slower(!) case
  3888. * Format expected: {SC=name|key=val&key1=val1...}
  3889. * Escape strings: \| => | , \& => & and \&amp; => &amp;
  3890. * Return array is formatted like this:
  3891. * 1 => string|array (depends on $name2array value) containing first set of parameters;
  3892. * 2 => array containing second set of parameters;
  3893. * 3 => string containing second set of parameters;
  3894. *
  3895. * @param string $parmstr
  3896. * @param boolean $first2array If true, first key (1) of the returned array will be parsed to array as well
  3897. * @return array
  3898. */
  3899. public static function scDualParams($parmstr, $first2array = false)
  3900. {
  3901. if (!$parmstr) return array(1 => '', 2 => array(), 3 => '');
  3902. if (is_array($parmstr)) return $parmstr;
  3903. $parmstr = str_replace('&amp;', '&', $parmstr); // clean when it comes from the DB
  3904. $parm = explode('|', str_replace(array('\|', '\&amp;', '\&'), array('%%__pipe__%%', '%%__ampamp__%%', '%%__amp__%%'), $parmstr), 2);
  3905. $multi = str_replace('%%__pipe__%%', '|', $parm[0]);
  3906. if ($first2array)
  3907. {
  3908. parse_str($multi, $multi);
  3909. foreach ($multi as $k => $v)
  3910. {
  3911. $multi[str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&amp;', '&'), $k)] = str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&amp;', '&'), $v);
  3912. }
  3913. }
  3914. if (varset($parm[1]))
  3915. {
  3916. // second paramater as a string - allow to be further passed to shortcodes
  3917. $parmstr = str_replace(array('%%__pipe__%%', '%%__ampamp__%%', '%%__amp__%%'), array('\|', '\&amp;', '\&'), $parm[1]);
  3918. parse_str(str_replace('%%__pipe__%%', '|', $parm[1]), $params);
  3919. foreach ($params as $k => $v)
  3920. {
  3921. $params[str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&amp;', '&'), $k)] = str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&amp;', '&'), $v);
  3922. }
  3923. }
  3924. else
  3925. {
  3926. $parmstr = '';
  3927. $params = array();
  3928. }
  3929. return array(1 => $multi, 2 => $params, 3 => $parmstr);
  3930. }
  3931. }