PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/Dispatcher.php

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