PageRenderTime 53ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/Dispatcher.php

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