PageRenderTime 45ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/Dispatcher.php

https://github.com/MyBB-Services/PrestaShop
PHP | 774 lines | 526 code | 83 blank | 165 comment | 104 complexity | 2bfb328b2168a9eb12f5929ac7cf8e0c MD5 | raw file
  1. <?php
  2. /*
  3. * 2007-2013 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2013 PrestaShop SA
  23. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. /**
  27. * @since 1.5.0
  28. */
  29. class DispatcherCore
  30. {
  31. /**
  32. * List of available front controllers types
  33. */
  34. const FC_FRONT = 1;
  35. const FC_ADMIN = 2;
  36. const FC_MODULE = 3;
  37. /**
  38. * @var Dispatcher
  39. */
  40. public static $instance = null;
  41. /**
  42. * @var array List of default routes
  43. */
  44. public $default_routes = array(
  45. 'category_rule' => array(
  46. 'controller' => 'category',
  47. 'rule' => '{id}-{rewrite}',
  48. 'keywords' => array(
  49. 'id' => array('regexp' => '[0-9]+', 'param' => 'id_category'),
  50. 'rewrite' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  51. 'meta_keywords' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  52. 'meta_title' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  53. ),
  54. ),
  55. 'supplier_rule' => array(
  56. 'controller' => 'supplier',
  57. 'rule' => '{id}__{rewrite}',
  58. 'keywords' => array(
  59. 'id' => array('regexp' => '[0-9]+', 'param' => 'id_supplier'),
  60. 'rewrite' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  61. 'meta_keywords' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  62. 'meta_title' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  63. ),
  64. ),
  65. 'manufacturer_rule' => array(
  66. 'controller' => 'manufacturer',
  67. 'rule' => '{id}_{rewrite}',
  68. 'keywords' => array(
  69. 'id' => array('regexp' => '[0-9]+', 'param' => 'id_manufacturer'),
  70. 'rewrite' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  71. 'meta_keywords' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  72. 'meta_title' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  73. ),
  74. ),
  75. 'cms_rule' => array(
  76. 'controller' => 'cms',
  77. 'rule' => 'content/{id}-{rewrite}',
  78. 'keywords' => array(
  79. 'id' => array('regexp' => '[0-9]+', 'param' => 'id_cms'),
  80. 'rewrite' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  81. 'meta_keywords' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  82. 'meta_title' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  83. ),
  84. ),
  85. 'cms_category_rule' => array(
  86. 'controller' => 'cms',
  87. 'rule' => 'content/category/{id}-{rewrite}',
  88. 'keywords' => array(
  89. 'id' => array('regexp' => '[0-9]+', 'param' => 'id_cms_category'),
  90. 'rewrite' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  91. 'meta_keywords' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  92. 'meta_title' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  93. ),
  94. ),
  95. 'module' => array(
  96. 'controller' => null,
  97. 'rule' => 'module/{module}{/:controller}',
  98. 'keywords' => array(
  99. 'module' => array('regexp' => '[_a-zA-Z0-9_-]+', 'param' => 'module'),
  100. 'controller' => array('regexp' => '[_a-zA-Z0-9_-]+', 'param' => 'controller'),
  101. ),
  102. 'params' => array(
  103. 'fc' => 'module',
  104. ),
  105. ),
  106. 'product_rule' => array(
  107. 'controller' => 'product',
  108. 'rule' => '{category:/}{id}-{rewrite}{-:ean13}.html',
  109. 'keywords' => array(
  110. 'id' => array('regexp' => '[0-9]+', 'param' => 'id_product'),
  111. 'rewrite' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  112. 'ean13' => array('regexp' => '[0-9\pL]*'),
  113. 'category' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  114. 'categories' => array('regexp' => '[/_a-zA-Z0-9-\pL]*'),
  115. 'reference' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  116. 'meta_keywords' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  117. 'meta_title' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  118. 'manufacturer' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  119. 'supplier' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  120. 'price' => array('regexp' => '[0-9\.,]*'),
  121. 'tags' => array('regexp' => '[a-zA-Z0-9-\pL]*'),
  122. ),
  123. ),
  124. // Must be after the product and category rules in order to avoid conflict
  125. 'layered_rule' => array(
  126. 'controller' => 'category',
  127. 'rule' => '{id}-{rewrite}{/:selected_filters}',
  128. 'keywords' => array(
  129. 'id' => array('regexp' => '[0-9]+', 'param' => 'id_category'),
  130. /* Selected filters is used by the module blocklayered */
  131. 'selected_filters' => array('regexp' => '.*', 'param' => 'selected_filters'),
  132. 'rewrite' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  133. 'meta_keywords' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  134. 'meta_title' => array('regexp' => '[_a-zA-Z0-9-\pL]*'),
  135. ),
  136. ),
  137. );
  138. /**
  139. * @var bool If true, use routes to build URL (mod rewrite must be activated)
  140. */
  141. protected $use_routes = false;
  142. protected $multilang_activated = false;
  143. /**
  144. * @var array List of loaded routes
  145. */
  146. protected $routes = array();
  147. /**
  148. * @var string Current controller name
  149. */
  150. protected $controller;
  151. /**
  152. * @var string Current request uri
  153. */
  154. protected $request_uri;
  155. /**
  156. * @var array Store empty route (a route with an empty rule)
  157. */
  158. protected $empty_route;
  159. /**
  160. * @var string Set default controller, which will be used if http parameter 'controller' is empty
  161. */
  162. protected $default_controller = 'index';
  163. /**
  164. * @var string Controller to use if found controller doesn't exist
  165. */
  166. protected $controller_not_found = 'pagenotfound';
  167. /**
  168. * @var string Front controller to use
  169. */
  170. protected $front_controller = self::FC_FRONT;
  171. /**
  172. * Get current instance of dispatcher (singleton)
  173. *
  174. * @return Dispatcher
  175. */
  176. public static function getInstance()
  177. {
  178. if (!self::$instance)
  179. self::$instance = new Dispatcher();
  180. return self::$instance;
  181. }
  182. /**
  183. * Need to be instancied from getInstance() method
  184. */
  185. protected function __construct()
  186. {
  187. $this->use_routes = (bool)Configuration::get('PS_REWRITING_SETTINGS');
  188. // Select right front controller
  189. if (defined('_PS_ADMIN_DIR_'))
  190. {
  191. $this->front_controller = self::FC_ADMIN;
  192. $this->controller_not_found = 'adminnotfound';
  193. $this->default_controller = 'adminhome';
  194. }
  195. elseif (Tools::getValue('fc') == 'module')
  196. {
  197. $this->front_controller = self::FC_MODULE;
  198. $this->controller_not_found = 'pagenotfound';
  199. $this->default_controller = 'default';
  200. }
  201. else
  202. {
  203. $this->front_controller = self::FC_FRONT;
  204. $this->controller_not_found = 'pagenotfound';
  205. $this->default_controller = 'index';
  206. }
  207. $this->setRequestUri();
  208. // Switch language if needed (only on front)
  209. if (in_array($this->front_controller, array(self::FC_FRONT, self::FC_MODULE)))
  210. Tools::switchLanguage();
  211. if (Language::isMultiLanguageActivated())
  212. $this->multilang_activated = true;
  213. $this->loadRoutes();
  214. }
  215. /**
  216. * Find the controller and instantiate it
  217. */
  218. public function dispatch()
  219. {
  220. $controller_class = '';
  221. // Get current controller
  222. $this->getController();
  223. if (!$this->controller)
  224. $this->controller = $this->default_controller;
  225. // Dispatch with right front controller
  226. switch ($this->front_controller)
  227. {
  228. // Dispatch front office controller
  229. case self::FC_FRONT :
  230. $controllers = Dispatcher::getControllers(array(_PS_FRONT_CONTROLLER_DIR_, _PS_OVERRIDE_DIR_.'controllers/front/'));
  231. $controllers['index'] = 'IndexController';
  232. if (isset($controllers['auth']))
  233. $controllers['authentication'] = $controllers['auth'];
  234. if (isset($controllers['compare']))
  235. $controllers['productscomparison'] = $controllers['compare'];
  236. if (isset($controllers['contact']))
  237. $controllers['contactform'] = $controllers['contact'];
  238. if (!isset($controllers[strtolower($this->controller)]))
  239. $this->controller = $this->controller_not_found;
  240. $controller_class = $controllers[strtolower($this->controller)];
  241. $params_hook_action_dispatcher = array('controller_type' => self::FC_FRONT, 'controller_class' => $controller_class, 'is_module' => 0);
  242. break;
  243. // Dispatch module controller for front office
  244. case self::FC_MODULE :
  245. $module_name = Validate::isModuleName(Tools::getValue('module')) ? Tools::getValue('module') : '';
  246. $module = Module::getInstanceByName($module_name);
  247. $controller_class = 'PageNotFoundController';
  248. if (Validate::isLoadedObject($module) && $module->active)
  249. {
  250. $controllers = Dispatcher::getControllers(_PS_MODULE_DIR_.$module_name.'/controllers/front/');
  251. if (isset($controllers[strtolower($this->controller)]))
  252. {
  253. include_once(_PS_MODULE_DIR_.$module_name.'/controllers/front/'.$this->controller.'.php');
  254. $controller_class = $module_name.$this->controller.'ModuleFrontController';
  255. }
  256. }
  257. $params_hook_action_dispatcher = array('controller_type' => self::FC_FRONT, 'controller_class' => $controller_class, 'is_module' => 1);
  258. break;
  259. // Dispatch back office controller + module back office controller
  260. case self::FC_ADMIN :
  261. $tab = Tab::getInstanceFromClassName($this->controller);
  262. $retrocompatibility_admin_tab = null;
  263. if ($tab->module)
  264. {
  265. if (file_exists(_PS_MODULE_DIR_.$tab->module.'/'.$tab->class_name.'.php'))
  266. $retrocompatibility_admin_tab = _PS_MODULE_DIR_.$tab->module.'/'.$tab->class_name.'.php';
  267. else
  268. {
  269. $controllers = Dispatcher::getControllers(_PS_MODULE_DIR_.$tab->module.'/controllers/admin/');
  270. if (!isset($controllers[strtolower($this->controller)]))
  271. {
  272. $this->controller = $this->controller_not_found;
  273. $controller_class = 'AdminNotFoundController';
  274. }
  275. else
  276. {
  277. // Controllers in modules can be named AdminXXX.php or AdminXXXController.php
  278. include_once(_PS_MODULE_DIR_.$tab->module.'/controllers/admin/'.$controllers[strtolower($this->controller)].'.php');
  279. $controller_class = $controllers[strtolower($this->controller)].(strpos($controllers[strtolower($this->controller)], 'Controller') ? '' : 'Controller');
  280. }
  281. }
  282. $params_hook_action_dispatcher = array('controller_type' => self::FC_ADMIN, 'controller_class' => $controller_class, 'is_module' => 1);
  283. }
  284. else
  285. {
  286. $controllers = Dispatcher::getControllers(array(_PS_ADMIN_DIR_.'/tabs/', _PS_ADMIN_CONTROLLER_DIR_, _PS_OVERRIDE_DIR_.'controllers/admin/'));
  287. if (!isset($controllers[strtolower($this->controller)]))
  288. $this->controller = $this->controller_not_found;
  289. $controller_class = $controllers[strtolower($this->controller)];
  290. $params_hook_action_dispatcher = array('controller_type' => self::FC_ADMIN, 'controller_class' => $controller_class, 'is_module' => 0);
  291. if (file_exists(_PS_ADMIN_DIR_.'/tabs/'.$controller_class.'.php'))
  292. $retrocompatibility_admin_tab = _PS_ADMIN_DIR_.'/tabs/'.$controller_class.'.php';
  293. }
  294. // @retrocompatibility with admin/tabs/ old system
  295. if ($retrocompatibility_admin_tab)
  296. {
  297. include_once($retrocompatibility_admin_tab);
  298. include_once(_PS_ADMIN_DIR_.'/functions.php');
  299. runAdminTab($this->controller, !empty($_REQUEST['ajaxMode']));
  300. return;
  301. }
  302. break;
  303. default :
  304. throw new PrestaShopException('Bad front controller chosen');
  305. }
  306. // Instantiate controller
  307. try
  308. {
  309. // Loading controller
  310. $controller = Controller::getController($controller_class);
  311. // Execute hook dispatcher
  312. if (isset($params_hook_action_dispatcher))
  313. Hook::exec('actionDispatcher', $params_hook_action_dispatcher);
  314. // Running controller
  315. $controller->run();
  316. }
  317. catch (PrestaShopException $e)
  318. {
  319. $e->displayMessage();
  320. }
  321. }
  322. /**
  323. * Set request uri and iso lang
  324. */
  325. protected function setRequestUri()
  326. {
  327. // Get request uri (HTTP_X_REWRITE_URL is used by IIS)
  328. if (isset($_SERVER['REQUEST_URI']))
  329. $this->request_uri = $_SERVER['REQUEST_URI'];
  330. else if (isset($_SERVER['HTTP_X_REWRITE_URL']))
  331. $this->request_uri = $_SERVER['HTTP_X_REWRITE_URL'];
  332. $this->request_uri = rawurldecode($this->request_uri);
  333. $this->request_uri = preg_replace('#^'.preg_quote(Context::getContext()->shop->getBaseURI(), '#').'#i', '/', $this->request_uri);
  334. // If there are several languages, get language from uri
  335. if ($this->use_routes && Language::isMultiLanguageActivated())
  336. if (preg_match('#^/([a-z]{2})/#', $this->request_uri, $m))
  337. {
  338. $_GET['isolang'] = $m[1];
  339. $this->request_uri = substr($this->request_uri, 3);
  340. }
  341. }
  342. /**
  343. * Load default routes group by languages
  344. */
  345. protected function loadRoutes()
  346. {
  347. $context = Context::getContext();
  348. // Load custom routes from modules
  349. $modules_routes = Hook::exec('moduleRoutes', array(), null, true, false);
  350. if (is_array($modules_routes) && count($modules_routes))
  351. foreach($modules_routes as $module_route)
  352. foreach($module_route as $route => $route_details)
  353. if (array_key_exists('controller', $route_details) && array_key_exists('rule', $route_details)
  354. && array_key_exists('keywords', $route_details) && array_key_exists('params', $route_details))
  355. {
  356. if (!isset($this->default_routes[$route]))
  357. $this->default_routes[$route] = array();
  358. $this->default_routes[$route] = array_merge($this->default_routes[$route], $route_details);
  359. }
  360. // Set default routes
  361. foreach (Language::getLanguages() as $lang)
  362. foreach ($this->default_routes as $id => $route)
  363. $this->addRoute(
  364. $id,
  365. $route['rule'],
  366. $route['controller'],
  367. $lang['id_lang'],
  368. $route['keywords'],
  369. isset($route['params']) ? $route['params'] : array()
  370. );
  371. // Load the custom routes prior the defaults to avoid infinite loops
  372. if ($this->use_routes)
  373. {
  374. // Get iso lang
  375. $iso_lang = Tools::getValue('isolang');
  376. $id_lang = $context->language->id;
  377. if (!empty($iso_lang))
  378. $id_lang = Language::getIdByIso($iso_lang);
  379. // Load routes from meta table
  380. $sql = 'SELECT m.page, ml.url_rewrite, ml.id_lang
  381. FROM `'._DB_PREFIX_.'meta` m
  382. LEFT JOIN `'._DB_PREFIX_.'meta_lang` ml ON (m.id_meta = ml.id_meta'.Shop::addSqlRestrictionOnLang('ml').')
  383. ORDER BY LENGTH(ml.url_rewrite) DESC';
  384. if ($results = Db::getInstance()->executeS($sql))
  385. foreach ($results as $row)
  386. {
  387. if ($row['url_rewrite'])
  388. $this->addRoute($row['page'], $row['url_rewrite'], $row['page'], $row['id_lang']);
  389. }
  390. // Set default empty route if no empty route (that's weird I know)
  391. if (!$this->empty_route)
  392. $this->empty_route = array(
  393. 'routeID' => 'index',
  394. 'rule' => '',
  395. 'controller' => 'index',
  396. );
  397. // Load custom routes
  398. foreach ($this->default_routes as $route_id => $route_data)
  399. if ($custom_route = Configuration::get('PS_ROUTE_'.$route_id))
  400. foreach (Language::getLanguages() as $lang)
  401. $this->addRoute(
  402. $route_id,
  403. $custom_route,
  404. $route_data['controller'],
  405. $lang['id_lang'],
  406. $route_data['keywords'],
  407. isset($route_data['params']) ? $route_data['params'] : array()
  408. );
  409. }
  410. }
  411. /**
  412. *
  413. * @param string $route_id Name of the route (need to be uniq, a second route with same name will override the first)
  414. * @param string $rule Url rule
  415. * @param string $controller Controller to call if request uri match the rule
  416. * @param int $id_lang
  417. */
  418. public function addRoute($route_id, $rule, $controller, $id_lang = null, array $keywords = array(), array $params = array())
  419. {
  420. if (is_null($id_lang))
  421. $id_lang = Context::getContext()->language->id;
  422. $regexp = preg_quote($rule, '#');
  423. if ($keywords)
  424. {
  425. $transform_keywords = array();
  426. preg_match_all('#\\\{(([^{}]*)\\\:)?('.implode('|', array_keys($keywords)).')(\\\:([^{}]*))?\\\}#', $regexp, $m);
  427. for ($i = 0, $total = count($m[0]); $i < $total; $i++)
  428. {
  429. $prepend = $m[2][$i];
  430. $keyword = $m[3][$i];
  431. $append = $m[5][$i];
  432. $transform_keywords[$keyword] = array(
  433. 'required' => isset($keywords[$keyword]['param']),
  434. 'prepend' => stripslashes($prepend),
  435. 'append' => stripslashes($append),
  436. );
  437. $prepend_regexp = $append_regexp = '';
  438. if ($prepend || $append)
  439. {
  440. $prepend_regexp = '('.preg_quote($prepend);
  441. $append_regexp = preg_quote($append).')?';
  442. }
  443. if (isset($keywords[$keyword]['param']))
  444. $regexp = str_replace($m[0][$i], $prepend_regexp.'(?P<'.$keywords[$keyword]['param'].'>'.$keywords[$keyword]['regexp'].')'.$append_regexp, $regexp);
  445. else
  446. $regexp = str_replace($m[0][$i], $prepend_regexp.'('.$keywords[$keyword]['regexp'].')'.$append_regexp, $regexp);
  447. }
  448. $keywords = $transform_keywords;
  449. }
  450. $regexp = '#^/'.$regexp.'(\?.*)?$#u';
  451. if (!isset($this->routes[$id_lang]))
  452. $this->routes[$id_lang] = array();
  453. $this->routes[$id_lang][$route_id] = array(
  454. 'rule' => $rule,
  455. 'regexp' => $regexp,
  456. 'controller' => $controller,
  457. 'keywords' => $keywords,
  458. 'params' => $params,
  459. );
  460. }
  461. /**
  462. * Check if a route exists
  463. *
  464. * @param string $route_id
  465. * @param int $id_lang
  466. * @return bool
  467. */
  468. public function hasRoute($route_id, $id_lang = null)
  469. {
  470. if (is_null($id_lang))
  471. $id_lang = Context::getContext()->language->id;
  472. return isset($this->routes[$id_lang]) && isset($this->routes[$id_lang][$route_id]);
  473. }
  474. /**
  475. * Check if a keyword is written in a route rule
  476. *
  477. * @param string $route_id
  478. * @param int $id_lang
  479. * @param string $keyword
  480. * @return bool
  481. */
  482. public function hasKeyword($route_id, $id_lang, $keyword)
  483. {
  484. if (!isset($this->routes[$id_lang]) && !isset($this->routes[$id_lang][$route_id]))
  485. return false;
  486. return preg_match('#\{([^{}]*:)?'.preg_quote($keyword, '#').'(:[^{}]*)?\}#', $this->routes[$id_lang][$route_id]['rule']);
  487. }
  488. /**
  489. * Check if a route rule contain all required keywords of default route definition
  490. *
  491. * @param string $route_id
  492. * @param string $rule Rule to verify
  493. * @param array $errors List of missing keywords
  494. */
  495. public function validateRoute($route_id, $rule, &$errors = array())
  496. {
  497. $errors = array();
  498. if (!isset($this->default_routes[$route_id]))
  499. return false;
  500. foreach ($this->default_routes[$route_id]['keywords'] as $keyword => $data)
  501. if (isset($data['param']) && !preg_match('#\{([^{}]*:)?'.$keyword.'(:[^{}]*)?\}#', $rule))
  502. $errors[] = $keyword;
  503. return (count($errors)) ? false : true;
  504. }
  505. /**
  506. * Create an url from
  507. *
  508. * @param string $route_id Name the route
  509. * @param int $id_lang
  510. * @param array $params
  511. * @param bool $use_routes If false, don't use to create this url
  512. * @param string $anchor Optional anchor to add at the end of this url
  513. */
  514. public function createUrl($route_id, $id_lang = null, array $params = array(), $force_routes = false, $anchor = '')
  515. {
  516. if (!$id_lang)
  517. $id_lang = Context::getContext()->language->id;
  518. if (!isset($this->routes[$id_lang][$route_id]))
  519. {
  520. $query = http_build_query($params, '', '&');
  521. $index_link = $this->use_routes ? '' : 'index.php';
  522. return ($route_id == 'index') ? $index_link.(($query) ? '?'.$query : '') : 'index.php?controller='.$route_id.(($query) ? '&'.$query : '').$anchor;
  523. }
  524. $route = $this->routes[$id_lang][$route_id];
  525. // Check required fields
  526. $query_params = isset($route['params']) ? $route['params'] : array();
  527. foreach ($route['keywords'] as $key => $data)
  528. {
  529. if (!$data['required'])
  530. continue;
  531. if (!array_key_exists($key, $params))
  532. die('Dispatcher::createUrl() miss required parameter "'.$key.'" for route "'.$route_id.'"');
  533. if (isset($this->default_routes[$route_id]))
  534. $query_params[$this->default_routes[$route_id]['keywords'][$key]['param']] = $params[$key];
  535. }
  536. // Build an url which match a route
  537. if ($this->use_routes || $force_routes)
  538. {
  539. $url = $route['rule'];
  540. $add_param = array();
  541. foreach ($params as $key => $value)
  542. {
  543. if (!isset($route['keywords'][$key]))
  544. {
  545. if (!isset($this->default_routes[$route_id]['keywords'][$key]))
  546. $add_param[$key] = $value;
  547. }
  548. else
  549. {
  550. if ($params[$key])
  551. $replace = $route['keywords'][$key]['prepend'].$params[$key].$route['keywords'][$key]['append'];
  552. else
  553. $replace = '';
  554. $url = preg_replace('#\{([^{}]*:)?'.$key.'(:[^{}]*)?\}#', $replace, $url);
  555. }
  556. }
  557. $url = preg_replace('#\{([^{}]*:)?[a-z0-9_]+?(:[^{}]*)?\}#', '', $url);
  558. if (count($add_param))
  559. $url .= '?'.http_build_query($add_param, '', '&');
  560. }
  561. // Build a classic url index.php?controller=foo&...
  562. else
  563. {
  564. $add_params = array();
  565. foreach ($params as $key => $value)
  566. if (!isset($route['keywords'][$key]) && !isset($this->default_routes[$route_id]['keywords'][$key]))
  567. $add_params[$key] = $value;
  568. if (!empty($route['controller']))
  569. $query_params['controller'] = $route['controller'];
  570. $query = http_build_query(array_merge($add_params, $query_params), '', '&');
  571. if ($this->multilang_activated)
  572. $query .= (!empty($query) ? '&' : '').'id_lang='.(int)$id_lang;
  573. $url = 'index.php?'.$query;
  574. }
  575. return $url.$anchor;
  576. }
  577. /**
  578. * Retrieve the controller from url or request uri if routes are activated
  579. *
  580. * @return string
  581. */
  582. public function getController()
  583. {
  584. if (defined('_PS_ADMIN_DIR_'))
  585. $_GET['controllerUri'] = Tools::getvalue('controller');
  586. if ($this->controller)
  587. {
  588. $_GET['controller'] = $this->controller;
  589. return $this->controller;
  590. }
  591. $controller = Tools::getValue('controller');
  592. if (isset($controller) && is_string($controller) && preg_match('/^([0-9a-z_-]+)\?(.*)=(.*)$/Ui', $controller, $m))
  593. {
  594. $controller = $m[1];
  595. if (isset($_GET['controller']))
  596. $_GET[$m[2]] = $m[3];
  597. else if (isset($_POST['controller']))
  598. $_POST[$m[2]] = $m[3];
  599. }
  600. if (!Validate::isControllerName($controller))
  601. $controller = false;
  602. // Use routes ? (for url rewriting)
  603. if ($this->use_routes && !$controller && !defined('_PS_ADMIN_DIR_'))
  604. {
  605. if (!$this->request_uri)
  606. return strtolower($this->controller_not_found);
  607. $controller = $this->controller_not_found;
  608. // If the request_uri matches a static file, then there is no need to check the routes, we keep "controller_not_found" (a static file should not go through the dispatcher)
  609. if (!preg_match('/\.(gif|jpe?g|png|css|js|ico)$/i', $this->request_uri))
  610. {
  611. // Add empty route as last route to prevent this greedy regexp to match request uri before right time
  612. if ($this->empty_route)
  613. $this->addRoute($this->empty_route['routeID'], $this->empty_route['rule'], $this->empty_route['controller'], Context::getContext()->language->id);
  614. if (isset($this->routes[Context::getContext()->language->id]))
  615. foreach ($this->routes[Context::getContext()->language->id] as $route)
  616. if (preg_match($route['regexp'], $this->request_uri, $m))
  617. {
  618. // Route found ! Now fill $_GET with parameters of uri
  619. foreach ($m as $k => $v)
  620. if (!is_numeric($k))
  621. $_GET[$k] = $v;
  622. $controller = $route['controller'] ? $route['controller'] : $_GET['controller'];
  623. if (!empty($route['params']))
  624. foreach ($route['params'] as $k => $v)
  625. $_GET[$k] = $v;
  626. // A patch for module friendly urls
  627. if (preg_match('#module-([a-z0-9_-]+)-([a-z0-9]+)$#i', $controller, $m))
  628. {
  629. $_GET['module'] = $m[1];
  630. $_GET['fc'] = 'module';
  631. $controller = $m[2];
  632. }
  633. if (isset($_GET['fc']) && $_GET['fc'] == 'module')
  634. $this->front_controller = self::FC_MODULE;
  635. break;
  636. }
  637. }
  638. if ($controller == 'index' || $this->request_uri == '/index.php')
  639. $controller = $this->default_controller;
  640. $this->controller = $controller;
  641. }
  642. // Default mode, take controller from url
  643. else
  644. $this->controller = $controller;
  645. $this->controller = str_replace('-', '', $this->controller);
  646. $_GET['controller'] = $this->controller;
  647. return $this->controller;
  648. }
  649. /**
  650. * Get list of all available FO controllers
  651. *
  652. * @var mixed $dirs
  653. * @return array
  654. */
  655. public static function getControllers($dirs)
  656. {
  657. if (!is_array($dirs))
  658. $dirs = array($dirs);
  659. $controllers = array();
  660. foreach ($dirs as $dir)
  661. $controllers = array_merge($controllers, Dispatcher::getControllersInDirectory($dir));
  662. return $controllers;
  663. }
  664. /**
  665. * Get list of available controllers from the specified dir
  666. *
  667. * @param string dir directory to scan (recursively)
  668. * @return array
  669. */
  670. public static function getControllersInDirectory($dir)
  671. {
  672. if (!is_dir($dir))
  673. return array();
  674. $controllers = array();
  675. $controller_files = scandir($dir);
  676. foreach ($controller_files as $controller_filename)
  677. {
  678. if ($controller_filename[0] != '.')
  679. {
  680. if (!strpos($controller_filename, '.php') && is_dir($dir.$controller_filename))
  681. $controllers += Dispatcher::getControllersInDirectory($dir.$controller_filename.DIRECTORY_SEPARATOR);
  682. elseif ($controller_filename != 'index.php')
  683. {
  684. $key = str_replace(array('controller.php', '.php'), '', strtolower($controller_filename));
  685. $controllers[$key] = basename($controller_filename, '.php');
  686. }
  687. }
  688. }
  689. return $controllers;
  690. }
  691. }