PageRenderTime 58ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/classes/Dispatcher.php

https://github.com/wonxshelond/PrestaShop
PHP | 772 lines | 525 code | 82 blank | 165 comment | 102 complexity | 0d672066e4f2e838d7ca0aa1166fe05c 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. $this->use_routes = false;
  195. }
  196. elseif (Tools::getValue('fc') == 'module')
  197. {
  198. $this->front_controller = self::FC_MODULE;
  199. $this->controller_not_found = 'pagenotfound';
  200. $this->default_controller = 'default';
  201. }
  202. else
  203. {
  204. $this->front_controller = self::FC_FRONT;
  205. $this->controller_not_found = 'pagenotfound';
  206. $this->default_controller = 'index';
  207. }
  208. $this->setRequestUri();
  209. // Switch language if needed (only on front)
  210. if (in_array($this->front_controller, array(self::FC_FRONT, self::FC_MODULE)))
  211. Tools::switchLanguage();
  212. if (Language::isMultiLanguageActivated())
  213. $this->multilang_activated = true;
  214. $this->loadRoutes();
  215. }
  216. /**
  217. * Find the controller and instantiate it
  218. */
  219. public function dispatch()
  220. {
  221. $controller_class = '';
  222. // Get current controller
  223. $this->getController();
  224. if (!$this->controller)
  225. $this->controller = $this->default_controller;
  226. // Dispatch with right front controller
  227. switch ($this->front_controller)
  228. {
  229. // Dispatch front office controller
  230. case self::FC_FRONT :
  231. $controllers = Dispatcher::getControllers(array(_PS_FRONT_CONTROLLER_DIR_, _PS_OVERRIDE_DIR_.'controllers/front/'));
  232. $controllers['index'] = 'IndexController';
  233. if (isset($controllers['auth']))
  234. $controllers['authentication'] = $controllers['auth'];
  235. if (isset($controllers['compare']))
  236. $controllers['productscomparison'] = $controllers['compare'];
  237. if (isset($controllers['contact']))
  238. $controllers['contactform'] = $controllers['contact'];
  239. if (!isset($controllers[strtolower($this->controller)]))
  240. $this->controller = $this->controller_not_found;
  241. $controller_class = $controllers[strtolower($this->controller)];
  242. $params_hook_action_dispatcher = array('controller_type' => self::FC_FRONT, 'controller_class' => $controller_class, 'is_module' => 0);
  243. break;
  244. // Dispatch module controller for front office
  245. case self::FC_MODULE :
  246. $module_name = Validate::isModuleName(Tools::getValue('module')) ? Tools::getValue('module') : '';
  247. $module = Module::getInstanceByName($module_name);
  248. $controller_class = 'PageNotFoundController';
  249. if (Validate::isLoadedObject($module) && $module->active)
  250. {
  251. $controllers = Dispatcher::getControllers(_PS_MODULE_DIR_.$module_name.'/controllers/front/');
  252. if (isset($controllers[strtolower($this->controller)]))
  253. {
  254. include_once(_PS_MODULE_DIR_.$module_name.'/controllers/front/'.$this->controller.'.php');
  255. $controller_class = $module_name.$this->controller.'ModuleFrontController';
  256. }
  257. }
  258. $params_hook_action_dispatcher = array('controller_type' => self::FC_FRONT, 'controller_class' => $controller_class, 'is_module' => 1);
  259. break;
  260. // Dispatch back office controller + module back office controller
  261. case self::FC_ADMIN :
  262. $tab = Tab::getInstanceFromClassName($this->controller);
  263. $retrocompatibility_admin_tab = null;
  264. if ($tab->module)
  265. {
  266. if (file_exists(_PS_MODULE_DIR_.$tab->module.'/'.$tab->class_name.'.php'))
  267. $retrocompatibility_admin_tab = _PS_MODULE_DIR_.$tab->module.'/'.$tab->class_name.'.php';
  268. else
  269. {
  270. $controllers = Dispatcher::getControllers(_PS_MODULE_DIR_.$tab->module.'/controllers/admin/');
  271. if (!isset($controllers[strtolower($this->controller)]))
  272. {
  273. $this->controller = $this->controller_not_found;
  274. $controller_class = 'AdminNotFoundController';
  275. }
  276. else
  277. {
  278. // Controllers in modules can be named AdminXXX.php or AdminXXXController.php
  279. include_once(_PS_MODULE_DIR_.$tab->module.'/controllers/admin/'.$controllers[strtolower($this->controller)].'.php');
  280. $controller_class = $controllers[strtolower($this->controller)].(strpos($controllers[strtolower($this->controller)], 'Controller') ? '' : 'Controller');
  281. }
  282. }
  283. $params_hook_action_dispatcher = array('controller_type' => self::FC_ADMIN, 'controller_class' => $controller_class, 'is_module' => 1);
  284. }
  285. else
  286. {
  287. $controllers = Dispatcher::getControllers(array(_PS_ADMIN_DIR_.'/tabs/', _PS_ADMIN_CONTROLLER_DIR_, _PS_OVERRIDE_DIR_.'controllers/admin/'));
  288. if (!isset($controllers[strtolower($this->controller)]))
  289. $this->controller = $this->controller_not_found;
  290. $controller_class = $controllers[strtolower($this->controller)];
  291. $params_hook_action_dispatcher = array('controller_type' => self::FC_ADMIN, 'controller_class' => $controller_class, 'is_module' => 0);
  292. if (file_exists(_PS_ADMIN_DIR_.'/tabs/'.$controller_class.'.php'))
  293. $retrocompatibility_admin_tab = _PS_ADMIN_DIR_.'/tabs/'.$controller_class.'.php';
  294. }
  295. // @retrocompatibility with admin/tabs/ old system
  296. if ($retrocompatibility_admin_tab)
  297. {
  298. include_once($retrocompatibility_admin_tab);
  299. include_once(_PS_ADMIN_DIR_.'/functions.php');
  300. runAdminTab($this->controller, !empty($_REQUEST['ajaxMode']));
  301. return;
  302. }
  303. break;
  304. default :
  305. throw new PrestaShopException('Bad front controller chosen');
  306. }
  307. // Instantiate controller
  308. try
  309. {
  310. // Loading controller
  311. $controller = Controller::getController($controller_class);
  312. // Execute hook dispatcher
  313. if (isset($params_hook_action_dispatcher))
  314. Hook::exec('actionDispatcher', $params_hook_action_dispatcher);
  315. // Running controller
  316. $controller->run();
  317. }
  318. catch (PrestaShopException $e)
  319. {
  320. $e->displayMessage();
  321. }
  322. }
  323. /**
  324. * Set request uri and iso lang
  325. */
  326. protected function setRequestUri()
  327. {
  328. // Get request uri (HTTP_X_REWRITE_URL is used by IIS)
  329. if (isset($_SERVER['REQUEST_URI']))
  330. $this->request_uri = $_SERVER['REQUEST_URI'];
  331. else if (isset($_SERVER['HTTP_X_REWRITE_URL']))
  332. $this->request_uri = $_SERVER['HTTP_X_REWRITE_URL'];
  333. $this->request_uri = rawurldecode($this->request_uri);
  334. $this->request_uri = preg_replace('#^'.preg_quote(Context::getContext()->shop->getBaseURI(), '#').'#i', '/', $this->request_uri);
  335. // If there are several languages, get language from uri
  336. if ($this->use_routes && Language::isMultiLanguageActivated())
  337. if (preg_match('#^/([a-z]{2})/#', $this->request_uri, $m))
  338. {
  339. $_GET['isolang'] = $m[1];
  340. $this->request_uri = substr($this->request_uri, 3);
  341. }
  342. }
  343. /**
  344. * Load default routes group by languages
  345. */
  346. protected function loadRoutes()
  347. {
  348. $context = Context::getContext();
  349. // Load custom routes from modules
  350. $modules_routes = Hook::exec('moduleRoutes', array(), null, true, false);
  351. if (is_array($modules_routes) && count($modules_routes))
  352. foreach($modules_routes as $module_route)
  353. foreach($module_route as $route => $route_details)
  354. if (array_key_exists('controller', $route_details) && array_key_exists('rule', $route_details)
  355. && array_key_exists('keywords', $route_details) && array_key_exists('params', $route_details))
  356. {
  357. if (!isset($this->default_routes[$route]))
  358. $this->default_routes[$route] = array();
  359. $this->default_routes[$route] = array_merge($this->default_routes[$route], $route_details);
  360. }
  361. // Set default routes
  362. foreach (Language::getLanguages() as $lang)
  363. foreach ($this->default_routes as $id => $route)
  364. $this->addRoute(
  365. $id,
  366. $route['rule'],
  367. $route['controller'],
  368. $lang['id_lang'],
  369. $route['keywords'],
  370. isset($route['params']) ? $route['params'] : array()
  371. );
  372. // Load the custom routes prior the defaults to avoid infinite loops
  373. if ($this->use_routes)
  374. {
  375. // Get iso lang
  376. $iso_lang = Tools::getValue('isolang');
  377. $id_lang = $context->language->id;
  378. if (!empty($iso_lang))
  379. $id_lang = Language::getIdByIso($iso_lang);
  380. // Load routes from meta table
  381. $sql = 'SELECT m.page, ml.url_rewrite, ml.id_lang
  382. FROM `'._DB_PREFIX_.'meta` m
  383. LEFT JOIN `'._DB_PREFIX_.'meta_lang` ml ON (m.id_meta = ml.id_meta'.Shop::addSqlRestrictionOnLang('ml').')
  384. ORDER BY LENGTH(ml.url_rewrite) DESC';
  385. if ($results = Db::getInstance()->executeS($sql))
  386. foreach ($results as $row)
  387. {
  388. if ($row['url_rewrite'])
  389. $this->addRoute($row['page'], $row['url_rewrite'], $row['page'], $row['id_lang']);
  390. }
  391. // Set default empty route if no empty route (that's weird I know)
  392. if (!$this->empty_route)
  393. $this->empty_route = array(
  394. 'routeID' => 'index',
  395. 'rule' => '',
  396. 'controller' => 'index',
  397. );
  398. // Load custom routes
  399. foreach ($this->default_routes as $route_id => $route_data)
  400. if ($custom_route = Configuration::get('PS_ROUTE_'.$route_id))
  401. foreach (Language::getLanguages() as $lang)
  402. $this->addRoute(
  403. $route_id,
  404. $custom_route,
  405. $route_data['controller'],
  406. $lang['id_lang'],
  407. $route_data['keywords'],
  408. isset($route_data['params']) ? $route_data['params'] : array()
  409. );
  410. }
  411. }
  412. /**
  413. *
  414. * @param string $route_id Name of the route (need to be uniq, a second route with same name will override the first)
  415. * @param string $rule Url rule
  416. * @param string $controller Controller to call if request uri match the rule
  417. * @param int $id_lang
  418. */
  419. public function addRoute($route_id, $rule, $controller, $id_lang = null, array $keywords = array(), array $params = array())
  420. {
  421. if (is_null($id_lang))
  422. $id_lang = Context::getContext()->language->id;
  423. $regexp = preg_quote($rule, '#');
  424. if ($keywords)
  425. {
  426. $transform_keywords = array();
  427. preg_match_all('#\\\{(([^{}]*)\\\:)?('.implode('|', array_keys($keywords)).')(\\\:([^{}]*))?\\\}#', $regexp, $m);
  428. for ($i = 0, $total = count($m[0]); $i < $total; $i++)
  429. {
  430. $prepend = $m[2][$i];
  431. $keyword = $m[3][$i];
  432. $append = $m[5][$i];
  433. $transform_keywords[$keyword] = array(
  434. 'required' => isset($keywords[$keyword]['param']),
  435. 'prepend' => stripslashes($prepend),
  436. 'append' => stripslashes($append),
  437. );
  438. $prepend_regexp = $append_regexp = '';
  439. if ($prepend || $append)
  440. {
  441. $prepend_regexp = '('.preg_quote($prepend);
  442. $append_regexp = preg_quote($append).')?';
  443. }
  444. if (isset($keywords[$keyword]['param']))
  445. $regexp = str_replace($m[0][$i], $prepend_regexp.'(?P<'.$keywords[$keyword]['param'].'>'.$keywords[$keyword]['regexp'].')'.$append_regexp, $regexp);
  446. else
  447. $regexp = str_replace($m[0][$i], $prepend_regexp.'('.$keywords[$keyword]['regexp'].')'.$append_regexp, $regexp);
  448. }
  449. $keywords = $transform_keywords;
  450. }
  451. $regexp = '#^/'.$regexp.'(\?.*)?$#u';
  452. if (!isset($this->routes[$id_lang]))
  453. $this->routes[$id_lang] = array();
  454. $this->routes[$id_lang][$route_id] = array(
  455. 'rule' => $rule,
  456. 'regexp' => $regexp,
  457. 'controller' => $controller,
  458. 'keywords' => $keywords,
  459. 'params' => $params,
  460. );
  461. }
  462. /**
  463. * Check if a route exists
  464. *
  465. * @param string $route_id
  466. * @param int $id_lang
  467. * @return bool
  468. */
  469. public function hasRoute($route_id, $id_lang = null)
  470. {
  471. if (is_null($id_lang))
  472. $id_lang = Context::getContext()->language->id;
  473. return isset($this->routes[$id_lang]) && isset($this->routes[$id_lang][$route_id]);
  474. }
  475. /**
  476. * Check if a keyword is written in a route rule
  477. *
  478. * @param string $route_id
  479. * @param int $id_lang
  480. * @param string $keyword
  481. * @return bool
  482. */
  483. public function hasKeyword($route_id, $id_lang, $keyword)
  484. {
  485. if (!isset($this->routes[$id_lang]) && !isset($this->routes[$id_lang][$route_id]))
  486. return false;
  487. return preg_match('#\{([^{}]*:)?'.preg_quote($keyword, '#').'(:[^{}]*)?\}#', $this->routes[$id_lang][$route_id]['rule']);
  488. }
  489. /**
  490. * Check if a route rule contain all required keywords of default route definition
  491. *
  492. * @param string $route_id
  493. * @param string $rule Rule to verify
  494. * @param array $errors List of missing keywords
  495. */
  496. public function validateRoute($route_id, $rule, &$errors = array())
  497. {
  498. $errors = array();
  499. if (!isset($this->default_routes[$route_id]))
  500. return false;
  501. foreach ($this->default_routes[$route_id]['keywords'] as $keyword => $data)
  502. if (isset($data['param']) && !preg_match('#\{([^{}]*:)?'.$keyword.'(:[^{}]*)?\}#', $rule))
  503. $errors[] = $keyword;
  504. return (count($errors)) ? false : true;
  505. }
  506. /**
  507. * Create an url from
  508. *
  509. * @param string $route_id Name the route
  510. * @param int $id_lang
  511. * @param array $params
  512. * @param bool $use_routes If false, don't use to create this url
  513. * @param string $anchor Optional anchor to add at the end of this url
  514. */
  515. public function createUrl($route_id, $id_lang = null, array $params = array(), $force_routes = false, $anchor = '')
  516. {
  517. if (!$id_lang)
  518. $id_lang = Context::getContext()->language->id;
  519. if (!isset($this->routes[$id_lang][$route_id]))
  520. {
  521. $query = http_build_query($params, '', '&');
  522. $index_link = $this->use_routes ? '' : 'index.php';
  523. return ($route_id == 'index') ? $index_link.(($query) ? '?'.$query : '') : 'index.php?controller='.$route_id.(($query) ? '&'.$query : '').$anchor;
  524. }
  525. $route = $this->routes[$id_lang][$route_id];
  526. // Check required fields
  527. $query_params = isset($route['params']) ? $route['params'] : array();
  528. foreach ($route['keywords'] as $key => $data)
  529. {
  530. if (!$data['required'])
  531. continue;
  532. if (!array_key_exists($key, $params))
  533. die('Dispatcher::createUrl() miss required parameter "'.$key.'" for route "'.$route_id.'"');
  534. if (isset($this->default_routes[$route_id]))
  535. $query_params[$this->default_routes[$route_id]['keywords'][$key]['param']] = $params[$key];
  536. }
  537. // Build an url which match a route
  538. if ($this->use_routes || $force_routes)
  539. {
  540. $url = $route['rule'];
  541. $add_param = array();
  542. foreach ($params as $key => $value)
  543. {
  544. if (!isset($route['keywords'][$key]))
  545. {
  546. if (!isset($this->default_routes[$route_id]['keywords'][$key]))
  547. $add_param[$key] = $value;
  548. }
  549. else
  550. {
  551. if ($params[$key])
  552. $replace = $route['keywords'][$key]['prepend'].$params[$key].$route['keywords'][$key]['append'];
  553. else
  554. $replace = '';
  555. $url = preg_replace('#\{([^{}]*:)?'.$key.'(:[^{}]*)?\}#', $replace, $url);
  556. }
  557. }
  558. $url = preg_replace('#\{([^{}]*:)?[a-z0-9_]+?(:[^{}]*)?\}#', '', $url);
  559. if (count($add_param))
  560. $url .= '?'.http_build_query($add_param, '', '&');
  561. }
  562. // Build a classic url index.php?controller=foo&...
  563. else
  564. {
  565. $add_params = array();
  566. foreach ($params as $key => $value)
  567. if (!isset($route['keywords'][$key]) && !isset($this->default_routes[$route_id]['keywords'][$key]))
  568. $add_params[$key] = $value;
  569. if (!empty($route['controller']))
  570. $query_params['controller'] = $route['controller'];
  571. $query = http_build_query(array_merge($add_params, $query_params), '', '&');
  572. if ($this->multilang_activated)
  573. $query .= (!empty($query) ? '&' : '').'id_lang='.(int)$id_lang;
  574. $url = 'index.php?'.$query;
  575. }
  576. return $url.$anchor;
  577. }
  578. /**
  579. * Retrieve the controller from url or request uri if routes are activated
  580. *
  581. * @return string
  582. */
  583. public function getController()
  584. {
  585. if ($this->controller)
  586. {
  587. $_GET['controller'] = $this->controller;
  588. return $this->controller;
  589. }
  590. $controller = Tools::getValue('controller');
  591. if (isset($controller) && is_string($controller) && preg_match('/^([0-9a-z_-]+)\?(.*)=(.*)$/Ui', $controller, $m))
  592. {
  593. $controller = $m[1];
  594. if (isset($_GET['controller']))
  595. $_GET[$m[2]] = $m[3];
  596. else if (isset($_POST['controller']))
  597. $_POST[$m[2]] = $m[3];
  598. }
  599. if (!Validate::isControllerName($controller))
  600. $controller = false;
  601. // Use routes ? (for url rewriting)
  602. if ($this->use_routes && !$controller)
  603. {
  604. if (!$this->request_uri)
  605. return strtolower($this->controller_not_found);
  606. $controller = $this->controller_not_found;
  607. // 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)
  608. if (!preg_match('/\.(gif|jpe?g|png|css|js|ico)$/i', $this->request_uri))
  609. {
  610. // Add empty route as last route to prevent this greedy regexp to match request uri before right time
  611. if ($this->empty_route)
  612. $this->addRoute($this->empty_route['routeID'], $this->empty_route['rule'], $this->empty_route['controller'], Context::getContext()->language->id);
  613. if (isset($this->routes[Context::getContext()->language->id]))
  614. foreach ($this->routes[Context::getContext()->language->id] as $route)
  615. if (preg_match($route['regexp'], $this->request_uri, $m))
  616. {
  617. // Route found ! Now fill $_GET with parameters of uri
  618. foreach ($m as $k => $v)
  619. if (!is_numeric($k))
  620. $_GET[$k] = $v;
  621. $controller = $route['controller'] ? $route['controller'] : $_GET['controller'];
  622. if (!empty($route['params']))
  623. foreach ($route['params'] as $k => $v)
  624. $_GET[$k] = $v;
  625. // A patch for module friendly urls
  626. if (preg_match('#module-([a-z0-9_-]+)-([a-z0-9]+)$#i', $controller, $m))
  627. {
  628. $_GET['module'] = $m[1];
  629. $_GET['fc'] = 'module';
  630. $controller = $m[2];
  631. }
  632. if (isset($_GET['fc']) && $_GET['fc'] == 'module')
  633. $this->front_controller = self::FC_MODULE;
  634. break;
  635. }
  636. }
  637. if ($controller == 'index' || $this->request_uri == '/index.php')
  638. $controller = $this->default_controller;
  639. $this->controller = $controller;
  640. }
  641. // Default mode, take controller from url
  642. else
  643. $this->controller = $controller;
  644. $this->controller = str_replace('-', '', $this->controller);
  645. $_GET['controller'] = $this->controller;
  646. return $this->controller;
  647. }
  648. /**
  649. * Get list of all available FO controllers
  650. *
  651. * @var mixed $dirs
  652. * @return array
  653. */
  654. public static function getControllers($dirs)
  655. {
  656. if (!is_array($dirs))
  657. $dirs = array($dirs);
  658. $controllers = array();
  659. foreach ($dirs as $dir)
  660. $controllers = array_merge($controllers, Dispatcher::getControllersInDirectory($dir));
  661. return $controllers;
  662. }
  663. /**
  664. * Get list of available controllers from the specified dir
  665. *
  666. * @param string dir directory to scan (recursively)
  667. * @return array
  668. */
  669. public static function getControllersInDirectory($dir)
  670. {
  671. if (!is_dir($dir))
  672. return array();
  673. $controllers = array();
  674. $controller_files = scandir($dir);
  675. foreach ($controller_files as $controller_filename)
  676. {
  677. if ($controller_filename[0] != '.')
  678. {
  679. if (!strpos($controller_filename, '.php') && is_dir($dir.$controller_filename))
  680. $controllers += Dispatcher::getControllersInDirectory($dir.$controller_filename.DIRECTORY_SEPARATOR);
  681. elseif ($controller_filename != 'index.php')
  682. {
  683. $key = str_replace(array('controller.php', '.php'), '', strtolower($controller_filename));
  684. $controllers[$key] = basename($controller_filename, '.php');
  685. }
  686. }
  687. }
  688. return $controllers;
  689. }
  690. }