PageRenderTime 48ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/module/Module.php

https://bitbucket.org/marcenuc/prestashop
PHP | 1979 lines | 1252 code | 242 blank | 485 comment | 213 complexity | e11ec9f8c5d7a467106eea3dc6116a81 MD5 | raw file
Possible License(s): LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * 2007-2012 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-2012 PrestaShop SA
  23. * @version Release: $Revision: 7436 $
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. * International Registered Trademark & Property of PrestaShop SA
  26. */
  27. abstract class ModuleCore
  28. {
  29. /** @var integer Module ID */
  30. public $id = null;
  31. /** @var float Version */
  32. public $version;
  33. /**
  34. * @since 1.5.0.1
  35. * @var string Registered Version in database
  36. */
  37. public $registered_version;
  38. /** @var array filled with known compliant PS versions */
  39. public $ps_versions_compliancy = array('min' => '1.4', 'max' => '1.6');
  40. /** @var array filled with modules needed for install */
  41. public $dependencies = array();
  42. /** @var string Unique name */
  43. public $name;
  44. /** @var string Human name */
  45. public $displayName;
  46. /** @var string A little description of the module */
  47. public $description;
  48. /** @var string author of the module */
  49. public $author;
  50. /** @var int need_instance */
  51. public $need_instance = 1;
  52. /** @var string Admin tab correponding to the module */
  53. public $tab = null;
  54. /** @var boolean Status */
  55. public $active = false;
  56. /** @var string Fill it if the module is installed but not yet set up */
  57. public $warning;
  58. /** @var array to store the limited country */
  59. public $limited_countries = array();
  60. /** @var array used by AdminTab to determine which lang file to use (admin.php or module lang file) */
  61. public static $classInModule = array();
  62. /** @var array current language translations */
  63. protected $_lang = array();
  64. /** @var string Module web path (eg. '/shop/modules/modulename/') */
  65. protected $_path = null;
  66. /**
  67. * @since 1.5.0.1
  68. * @var string Module local path (eg. '/home/prestashop/modules/modulename/')
  69. */
  70. protected $local_path = null;
  71. /** @var protected array filled with module errors */
  72. protected $_errors = array();
  73. /** @var protected array filled with module success */
  74. protected $_confirmations = array();
  75. /** @var protected string main table used for modules installed */
  76. protected $table = 'module';
  77. /** @var protected string identifier of the main table */
  78. protected $identifier = 'id_module';
  79. /** @var protected array cache filled with modules informations */
  80. protected static $modules_cache;
  81. /** @var protected array cache filled with modules instances */
  82. protected static $_INSTANCE = array();
  83. /** @var protected boolean filled with config xml generation mode */
  84. protected static $_generate_config_xml_mode = false;
  85. /** @var protected array filled with cache translations */
  86. protected static $l_cache = array();
  87. /** @var protected array filled with cache permissions (modules / employee profiles) */
  88. protected static $cache_permissions = array();
  89. /** @var Context */
  90. protected $context;
  91. /** @var Smarty_Data */
  92. protected $smarty;
  93. const CACHE_FILE_MODULES_LIST = '/config/xml/modules_list.xml';
  94. const CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST = '/config/xml/default_country_modules_list.xml';
  95. const CACHE_FILE_CUSTOMER_MODULES_LIST = '/config/xml/customer_modules_list.xml';
  96. const CACHE_FILE_MUST_HAVE_MODULES_LIST = '/config/xml/must_have_modules_list.xml';
  97. /**
  98. * Constructor
  99. *
  100. * @param string $name Module unique name
  101. * @param Context $context
  102. */
  103. public function __construct($name = null, Context $context = null)
  104. {
  105. // Load context and smarty
  106. $this->context = $context ? $context : Context::getContext();
  107. $this->smarty = $this->context->smarty->createData($this->context->smarty);
  108. // If the module has no name we gave him its id as name
  109. if ($this->name == null)
  110. $this->name = $this->id;
  111. // If the module has the name we load the corresponding data from the cache
  112. if ($this->name != null)
  113. {
  114. // If cache is not generated, we generate it
  115. if (self::$modules_cache == null && !is_array(self::$modules_cache))
  116. {
  117. // Join clause is done to check if the module is activated in current shop context
  118. $sql_limit_shop = 'SELECT COUNT(*) FROM `'._DB_PREFIX_.'module_shop` ms WHERE m.`id_module` = ms.`id_module` AND ms.`id_shop` = '.(int)Context::getContext()->shop->id;
  119. $sql = 'SELECT m.`id_module`, m.`name`, ('.$sql_limit_shop.') as total FROM `'._DB_PREFIX_.'module` m';
  120. // Result is cached
  121. self::$modules_cache = array();
  122. $result = Db::getInstance()->executeS($sql);
  123. foreach ($result as $row)
  124. {
  125. self::$modules_cache[$row['name']] = $row;
  126. self::$modules_cache[$row['name']]['active'] = ($row['total'] > 0) ? 1 : 0;
  127. }
  128. }
  129. // We load configuration from the cache
  130. if (isset(self::$modules_cache[$this->name]))
  131. {
  132. if (isset(self::$modules_cache[$this->name]['id_module']))
  133. $this->id = self::$modules_cache[$this->name]['id_module'];
  134. foreach (self::$modules_cache[$this->name] as $key => $value)
  135. if (key_exists($key, $this))
  136. $this->{$key} = $value;
  137. $this->_path = __PS_BASE_URI__.'modules/'.$this->name.'/';
  138. }
  139. $this->local_path = _PS_MODULE_DIR_.$this->name.'/';
  140. }
  141. }
  142. /**
  143. * Insert module into datable
  144. */
  145. public function install()
  146. {
  147. // Check module name validation
  148. if (!Validate::isModuleName($this->name))
  149. die(Tools::displayError());
  150. // Check PS version compliancy
  151. if (version_compare(_PS_VERSION_, $this->ps_versions_compliancy['min']) < 0 || version_compare(_PS_VERSION_, $this->ps_versions_compliancy['max']) >= 0)
  152. {
  153. $this->_errors[] = $this->l('The version of your module is not compliant with your PrestaShop version.');
  154. return false;
  155. }
  156. // Check module dependencies
  157. if (count($this->dependencies) > 0)
  158. foreach ($this->dependencies as $dependency)
  159. if (!Db::getInstance()->getRow('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($dependency).'\''))
  160. {
  161. $error = $this->l('Before installing this module, you have to installed these/this module(s) first :').'<br />';
  162. foreach ($this->dependencies as $d)
  163. $error .= '- '.$d.'<br />';
  164. $this->_errors[] = $error;
  165. return false;
  166. }
  167. // Check if module is installed
  168. $result = Db::getInstance()->getRow('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($this->name).'\'');
  169. if ($result)
  170. {
  171. $this->_errors[] = $this->l('This module has already been installed.');
  172. return false;
  173. }
  174. // Install overrides
  175. try {
  176. $this->installOverrides();
  177. } catch (Exception $e) {
  178. $this->_errors[] = sprintf(Tools::displayError('Unable to install override: %s'), $e->getMessage());
  179. //$this->uninstallOverrides(); remove this line because if module a install an override, then module b install same override, this line will remove override of module a (if you find a bug related to this line please don't forget what i say before)
  180. return false;
  181. }
  182. // Install module and retrieve the installation id
  183. $result = Db::getInstance()->insert($this->table, array('name' => $this->name, 'active' => 1, 'version' => $this->version));
  184. if (!$result)
  185. {
  186. $this->_errors[] = $this->l('Technical error : PrestaShop could not installed this module.');
  187. return false;
  188. }
  189. $this->id = Db::getInstance()->Insert_ID();
  190. Cache::clean('Module::isInstalled'.$this->name);
  191. // Enable the module for current shops in context
  192. $this->enable();
  193. // Permissions management
  194. Db::getInstance()->execute('
  195. INSERT INTO `'._DB_PREFIX_.'module_access` (`id_profile`, `id_module`, `view`, `configure`) (
  196. SELECT id_profile, '.(int)$this->id.', 1, 1
  197. FROM '._DB_PREFIX_.'access a
  198. WHERE id_tab = (
  199. SELECT `id_tab` FROM '._DB_PREFIX_.'tab
  200. WHERE class_name = \'AdminModules\' LIMIT 1)
  201. AND a.`view` = 1)');
  202. Db::getInstance()->execute('
  203. INSERT INTO `'._DB_PREFIX_.'module_access` (`id_profile`, `id_module`, `view`, `configure`) (
  204. SELECT id_profile, '.(int)$this->id.', 1, 0
  205. FROM '._DB_PREFIX_.'access a
  206. WHERE id_tab = (
  207. SELECT `id_tab` FROM '._DB_PREFIX_.'tab
  208. WHERE class_name = \'AdminModules\' LIMIT 1)
  209. AND a.`view` = 0)');
  210. // Adding Restrictions for client groups
  211. Group::addRestrictionsForModule($this->id, Shop::getShops(true, null, true));
  212. return true;
  213. }
  214. /**
  215. * Set errors, warning or success message of a module upgrade
  216. *
  217. * @param $upgrade_detail
  218. */
  219. protected function setUpgradeMessage($upgrade_detail)
  220. {
  221. // Store information if a module has been upgraded (memory optimization)
  222. if ($upgrade_detail['available_upgrade'])
  223. {
  224. if ($upgrade_detail['success'])
  225. {
  226. $this->_confirmations[] = $this->l('Current version: ').$this->version;
  227. $this->_confirmations[] = $upgrade_detail['number_upgraded'].' '.$this->l('file upgrade applied');
  228. }
  229. else
  230. {
  231. if (!$upgrade_detail['number_upgraded'])
  232. $this->_errors[] = $this->l('None upgrades have been applied');
  233. else
  234. {
  235. $this->_errors[] = $this->l('Upgraded from: ').$upgrade_detail['upgraded_from'].$this->l(' to ').
  236. $upgrade_detail['upgraded_to'];
  237. $this->_errors[] = $upgrade_detail['number_upgrade_left'].' '.$this->l('upgrade left');
  238. }
  239. $this->_errors[] = $this->l('To prevent any problem, this module has been turned off');
  240. }
  241. }
  242. }
  243. /**
  244. * Init the upgrade module
  245. *
  246. * @static
  247. * @param $module_name
  248. * @param $module_version
  249. * @return bool
  250. */
  251. public static function initUpgradeModule($module)
  252. {
  253. // Init cache upgrade details
  254. self::$modules_cache[$module->name]['upgrade'] = array(
  255. 'success' => false, // bool to know if upgrade succeed or not
  256. 'available_upgrade' => 0, // Number of available module before any upgrade
  257. 'number_upgraded' => 0, // Number of upgrade done
  258. 'number_upgrade_left' => 0,
  259. 'upgrade_file_left' => array(), // List of the upgrade file left
  260. 'version_fail' => 0, // Version of the upgrade failure
  261. 'upgraded_from' => 0, // Version number before upgrading anything
  262. 'upgraded_to' => 0, // Last upgrade applied
  263. );
  264. // Need Upgrade will check and load upgrade file to the moduleCache upgrade case detail
  265. $ret = $module->installed && Module::needUpgrade($module);
  266. return $ret;
  267. }
  268. /**
  269. * Run the upgrade for a given module name and version
  270. *
  271. * @return array
  272. */
  273. public function runUpgradeModule()
  274. {
  275. $upgrade = &self::$modules_cache[$this->name]['upgrade'];
  276. foreach ($upgrade['upgrade_file_left'] as $num => $file_detail)
  277. {
  278. // Default variable required in the included upgrade file need to be set by default there:
  279. // upgrade_version, success_upgrade
  280. $upgrade_result = false;
  281. include($file_detail['file']);
  282. // Call the upgrade function if defined
  283. if (function_exists($file_detail['upgrade_function']))
  284. $upgrade_result = $file_detail['upgrade_function']($this);
  285. $upgrade['success'] = $upgrade_result;
  286. // Set detail when an upgrade succeed or failed
  287. if ($upgrade_result)
  288. {
  289. $upgrade['number_upgraded'] += 1;
  290. $upgrade['upgraded_to'] = $file_detail['version'];
  291. unset($upgrade['upgrade_file_left'][$num]);
  292. }
  293. else
  294. {
  295. $upgrade['version_fail'] = $file_detail['version'];
  296. // If any errors, the module is disabled
  297. $this->disable();
  298. break;
  299. }
  300. }
  301. $upgrade['number_upgrade_left'] = count($upgrade['upgrade_file_left']);
  302. // Update module version in DB with the last succeed upgrade
  303. if ($upgrade['upgraded_to'])
  304. Module::upgradeModuleVersion($this->name, $upgrade['upgraded_to']);
  305. $this->setUpgradeMessage($upgrade);
  306. return $upgrade;
  307. }
  308. /**
  309. * Upgrade the registered version to a new one
  310. *
  311. * @static
  312. * @param $name
  313. * @param $version
  314. * @return bool
  315. */
  316. public static function upgradeModuleVersion($name, $version)
  317. {
  318. return Db::getInstance()->execute('
  319. UPDATE `'._DB_PREFIX_.'module` m
  320. SET m.version = \''.bqSQL($version).'\'
  321. WHERE m.name = \''.bqSQL($name).'\'');
  322. }
  323. /**
  324. * Check if a module need to be upgraded.
  325. * This method modify the module_cache adding an upgrade list file
  326. *
  327. * @static
  328. * @param $module_name
  329. * @param $module_version
  330. * @return bool
  331. */
  332. public static function needUpgrade($module)
  333. {
  334. self::$modules_cache[$module->name]['upgrade']['upgraded_from'] = $module->database_version;
  335. // Check the version of the module with the registered one and look if any upgrade file exist
  336. return Tools::version_compare($module->version, $module->database_version, '>')
  337. && Module::loadUpgradeVersionList($module->name, $module->version, $module->database_version);
  338. }
  339. /**
  340. * Load the available list of upgrade of a specified module
  341. * with an associated version
  342. *
  343. * @static
  344. * @param $module_name
  345. * @param $module_version
  346. * @param $registered_version
  347. * @return bool to know directly if any files have been found
  348. */
  349. protected static function loadUpgradeVersionList($module_name, $module_version, $registered_version)
  350. {
  351. $list = array();
  352. $upgrade_path = _PS_MODULE_DIR_.$module_name.'/upgrade/';
  353. // Check if folder exist and it could be read
  354. if (file_exists($upgrade_path) && ($files = scandir($upgrade_path)))
  355. {
  356. // Read each file name
  357. foreach ($files as $file)
  358. if (!in_array($file, array('.', '..', '.svn', 'index.php')))
  359. {
  360. $tab = explode('-', $file);
  361. $file_version = basename($tab[1], '.php');
  362. // Compare version, if minor than actual, we need to upgrade the module
  363. if (count($tab) == 2 &&
  364. (Tools::version_compare($file_version, $module_version, '<=') &&
  365. Tools::version_compare($file_version, $registered_version, '>')))
  366. {
  367. $list[] = array(
  368. 'file' => $upgrade_path.$file,
  369. 'version' => $file_version,
  370. 'upgrade_function' => 'upgrade_module_'.str_replace('.', '_', $file_version));
  371. }
  372. }
  373. }
  374. // No files upgrade, then upgrade succeed
  375. if (count($list) == 0)
  376. {
  377. self::$modules_cache[$module_name]['upgrade']['success'] = true;
  378. Module::upgradeModuleVersion($module_name, $module_version);
  379. }
  380. usort($list, 'ps_module_version_sort');
  381. // Set the list to module cache
  382. self::$modules_cache[$module_name]['upgrade']['upgrade_file_left'] = $list;
  383. self::$modules_cache[$module_name]['upgrade']['available_upgrade'] = count($list);
  384. return (bool)count($list);
  385. }
  386. /**
  387. * Return the status of the upgraded module
  388. *
  389. * @static
  390. * @param $module_name
  391. * @return bool
  392. */
  393. public static function getUpgradeStatus($module_name)
  394. {
  395. return (isset(self::$modules_cache[$module_name]) &&
  396. self::$modules_cache[$module_name]['upgrade']['success']);
  397. }
  398. /**
  399. * Delete module from datable
  400. *
  401. * @return boolean result
  402. */
  403. public function uninstall()
  404. {
  405. // Check module installation id validation
  406. if (!Validate::isUnsignedId($this->id))
  407. {
  408. $this->_errors[] = $this->l('The module is not installed.');
  409. return false;
  410. }
  411. // Uninstall overrides
  412. if (!$this->uninstallOverrides())
  413. return false;
  414. // Retrieve hooks used by the module
  415. $sql = 'SELECT `id_hook` FROM `'._DB_PREFIX_.'hook_module` WHERE `id_module` = '.(int)$this->id;
  416. $result = Db::getInstance()->executeS($sql);
  417. foreach ($result as $row)
  418. {
  419. $sql = 'DELETE FROM `'._DB_PREFIX_.'hook_module` WHERE `id_module` = '.(int)$this->id.' AND `id_hook` = '.(int)$row['id_hook'];
  420. Db::getInstance()->execute($sql);
  421. $this->cleanPositions($row['id_hook']);
  422. }
  423. // Disable the module for all shops
  424. $this->disable(true);
  425. // Delete permissions module access
  426. Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'module_access` WHERE `id_module` = '.(int)$this->id);
  427. // Remove restrictions for client groups
  428. Group::truncateRestrictionsByModule($this->id);
  429. // Uninstall the module
  430. if (Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'module` WHERE `id_module` = '.(int)$this->id))
  431. {
  432. Cache::clean('Module::isInstalled'.$this->name);
  433. return true;
  434. }
  435. return false;
  436. }
  437. /**
  438. * This function enable module $name. If an $name is an array,
  439. * this will enable all of them
  440. *
  441. * @param array|string $name
  442. * @return true if succeed
  443. * @since 1.4.1
  444. */
  445. public static function enableByName($name)
  446. {
  447. // If $name is not an array, we set it as an array
  448. if (!is_array($name))
  449. $name = array($name);
  450. // Enable each module
  451. foreach ($name as $k => $v)
  452. Module::getInstanceByName($name)->enable();
  453. }
  454. /**
  455. * Activate current module.
  456. *
  457. * @param bool $forceAll If true, enable module for all shop
  458. */
  459. public function enable($forceAll = false)
  460. {
  461. // Retrieve all shops where the module is enabled
  462. $list = Shop::getContextListShopID();
  463. $sql = 'SELECT `id_shop` FROM `'._DB_PREFIX_.'module_shop`
  464. WHERE `id_module` = '.$this->id.
  465. ((!$forceAll) ? ' AND `id_shop` IN('.implode(', ', $list).')' : '');
  466. // Store the results in an array
  467. $items = array();
  468. if ($results = Db::getInstance($sql)->executeS($sql))
  469. foreach ($results as $row)
  470. $items[] = $row['id_shop'];
  471. // Enable module in the shop where it is not enabled yet
  472. foreach ($list as $id)
  473. if (!in_array($id, $items))
  474. Db::getInstance()->insert('module_shop', array(
  475. 'id_module' => $this->id,
  476. 'id_shop' => $id,
  477. ));
  478. return true;
  479. }
  480. /**
  481. * This function disable module $name. If an $name is an array,
  482. * this will disable all of them
  483. *
  484. * @param array|string $name
  485. * @return true if succeed
  486. * @since 1.4.1
  487. */
  488. public static function disableByName($name)
  489. {
  490. // If $name is not an array, we set it as an array
  491. if (!is_array($name))
  492. $name = array($name);
  493. // Disable each module
  494. foreach ($name as $k => $v)
  495. Module::getInstanceByName($name)->disable();
  496. return true;
  497. }
  498. /**
  499. * Desactivate current module.
  500. *
  501. * @param bool $forceAll If true, disable module for all shop
  502. */
  503. public function disable($forceAll = false)
  504. {
  505. // Disable module for all shops
  506. $sql = 'DELETE FROM `'._DB_PREFIX_.'module_shop` WHERE `id_module` = '.(int)$this->id.' '.((!$forceAll) ? ' AND `id_shop` IN('.implode(', ', Shop::getContextListShopID()).')' : '');
  507. Db::getInstance()->execute($sql);
  508. }
  509. /**
  510. * Display flags in forms for translations
  511. *
  512. * @param array $languages All languages available
  513. * @param integer $default_language Default language id
  514. * @param string $ids Multilingual div ids in form
  515. * @param string $id Current div id]
  516. * @param boolean $return define the return way : false for a display, true for a return
  517. * @param boolean $use_vars_instead_of_ids use an js vars instead of ids seperate by "¤"
  518. */
  519. public function displayFlags($languages, $default_language, $ids, $id, $return = false, $use_vars_instead_of_ids = false)
  520. {
  521. if (count($languages) == 1)
  522. return false;
  523. $output = '
  524. <div class="displayed_flag">
  525. <img src="../img/l/'.$default_language.'.jpg" class="pointer" id="language_current_'.$id.'" onclick="toggleLanguageFlags(this);" alt="" />
  526. </div>
  527. <div id="languages_'.$id.'" class="language_flags">
  528. '.$this->l('Choose language:').'<br /><br />';
  529. foreach ($languages as $language)
  530. if ($use_vars_instead_of_ids)
  531. $output .= '<img src="../img/l/'.(int)$language['id_lang'].'.jpg" class="pointer" alt="'.$language['name'].'" title="'.$language['name'].'" onclick="changeLanguage(\''.$id.'\', '.$ids.', '.$language['id_lang'].', \''.$language['iso_code'].'\');" /> ';
  532. else
  533. $output .= '<img src="../img/l/'.(int)$language['id_lang'].'.jpg" class="pointer" alt="'.$language['name'].'" title="'.$language['name'].'" onclick="changeLanguage(\''.$id.'\', \''.$ids.'\', '.$language['id_lang'].', \''.$language['iso_code'].'\');" /> ';
  534. $output .= '</div>';
  535. if ($return)
  536. return $output;
  537. echo $output;
  538. }
  539. /**
  540. * Connect module to a hook
  541. *
  542. * @param string $hook_name Hook name
  543. * @param array $shop_list List of shop linked to the hook (if null, link hook to all shops)
  544. * @return boolean result
  545. */
  546. public function registerHook($hook_name, $shop_list = null)
  547. {
  548. // Check hook name validation and if module is installed
  549. if (!Validate::isHookName($hook_name))
  550. throw new PrestaShopException('Invalid hook name');
  551. if (!isset($this->id) || !is_numeric($this->id))
  552. return false;
  553. // Retrocompatibility
  554. if ($alias = Hook::getRetroHookName($hook_name))
  555. $hook_name = $alias;
  556. // Get hook id
  557. $id_hook = Hook::getIdByName($hook_name);
  558. // If hook does not exist, we create it
  559. if (!$id_hook)
  560. {
  561. $new_hook = new Hook();
  562. $new_hook->name = pSQL($hook_name);
  563. $new_hook->title = pSQL($hook_name);
  564. $new_hook->add();
  565. $id_hook = $new_hook->id;
  566. if (!$id_hook)
  567. return false;
  568. }
  569. // If shop lists is null, we fill it with all shops
  570. if (is_null($shop_list))
  571. $shop_list = Shop::getShops(true, null, true);
  572. $return = true;
  573. foreach ($shop_list as $shop_id)
  574. {
  575. // Check if already register
  576. $sql = 'SELECT hm.`id_module`
  577. FROM `'._DB_PREFIX_.'hook_module` hm, `'._DB_PREFIX_.'hook` h
  578. WHERE hm.`id_module` = '.(int)($this->id).' AND h.`id_hook` = '.$id_hook.'
  579. AND h.`id_hook` = hm.`id_hook` AND `id_shop` = '.(int)$shop_id;
  580. if (Db::getInstance()->getRow($sql))
  581. continue;
  582. // Get module position in hook
  583. $sql = 'SELECT MAX(`position`) AS position
  584. FROM `'._DB_PREFIX_.'hook_module`
  585. WHERE `id_hook` = '.(int)$id_hook.' AND `id_shop` = '.(int)$shop_id;
  586. if (!$position = Db::getInstance()->getValue($sql))
  587. $position = 0;
  588. // Register module in hook
  589. $return &= Db::getInstance()->insert('hook_module', array(
  590. 'id_module' => (int)$this->id,
  591. 'id_hook' => (int)$id_hook,
  592. 'id_shop' => (int)$shop_id,
  593. 'position' => (int)($position + 1),
  594. ));
  595. }
  596. return $return;
  597. }
  598. /**
  599. * Unregister module from hook
  600. *
  601. * @param mixed $id_hook Hook id (can be a hook name since 1.5.0)
  602. * @param array $shop_list List of shop
  603. * @return boolean result
  604. */
  605. public function unregisterHook($hook_id, $shop_list = null)
  606. {
  607. // Get hook id if a name is given as argument
  608. if (!is_numeric($hook_id))
  609. {
  610. // Retrocompatibility
  611. $hook_id = Hook::getIdByName($hook_id);
  612. if (!$hook_id)
  613. return false;
  614. }
  615. // Unregister module on hook by id
  616. $sql = 'DELETE FROM `'._DB_PREFIX_.'hook_module`
  617. WHERE `id_module` = '.(int)$this->id.' AND `id_hook` = '.(int)$hook_id
  618. .(($shop_list) ? ' AND `id_shop` IN('.implode(', ', $shop_list).')' : '');
  619. $result = Db::getInstance()->execute($sql);
  620. // Clean modules position
  621. $this->cleanPositions($hook_id, $shop_list);
  622. return $result;
  623. }
  624. /**
  625. * Unregister exceptions linked to module
  626. *
  627. * @param int $id_hook Hook id
  628. * @param array $shop_list List of shop
  629. * @return boolean result
  630. */
  631. public function unregisterExceptions($hook_id, $shop_list = null)
  632. {
  633. $sql = 'DELETE FROM `'._DB_PREFIX_.'hook_module_exceptions`
  634. WHERE `id_module` = '.(int)$this->id.' AND `id_hook` = '.(int)$hook_id
  635. .(($shop_list) ? ' AND `id_shop` IN('.implode(', ', $shop_list).')' : '');
  636. return Db::getInstance()->execute($sql);
  637. }
  638. /**
  639. * Add exceptions for module->Hook
  640. *
  641. * @param int $id_hook Hook id
  642. * @param array $excepts List of file name
  643. * @param array $shop_list List of shop
  644. * @return boolean result
  645. */
  646. public function registerExceptions($id_hook, $excepts, $shop_list = null)
  647. {
  648. // If shop lists is null, we fill it with all shops
  649. if (is_null($shop_list))
  650. $shop_list = Shop::getContextListShopID();
  651. // Save modules exception for each shop
  652. foreach ($shop_list as $shop_id)
  653. {
  654. foreach ($excepts as $except)
  655. {
  656. if (!$except)
  657. continue;
  658. $insertException = array(
  659. 'id_module' => (int)$this->id,
  660. 'id_hook' => (int)$id_hook,
  661. 'id_shop' => (int)$shop_id,
  662. 'file_name' => pSQL($except),
  663. );
  664. $result = Db::getInstance()->insert('hook_module_exceptions', $insertException);
  665. if (!$result)
  666. return false;
  667. }
  668. }
  669. return true;
  670. }
  671. /**
  672. * Edit exceptions for module->Hook
  673. *
  674. * @param int $hookID Hook id
  675. * @param array $excepts List of shopID and file name
  676. * @return boolean result
  677. */
  678. public function editExceptions($id_hook, $excepts)
  679. {
  680. $result = true;
  681. foreach ($excepts as $shop_id => $except)
  682. {
  683. $shop_list = ($shop_id == 0) ? Shop::getContextListShopID() : array($shop_id);
  684. $this->unregisterExceptions($id_hook, $shop_list);
  685. $result &= $this->registerExceptions($id_hook, $except, $shop_list);
  686. }
  687. return $result;
  688. }
  689. /**
  690. * This function is used to determine the module name
  691. * of an AdminTab which belongs to a module, in order to keep translation
  692. * related to a module in its directory (instead of $_LANGADM)
  693. *
  694. * @param mixed $currentClass the
  695. * @return boolean|string if the class belongs to a module, will return the module name. Otherwise, return false.
  696. */
  697. public static function getModuleNameFromClass($currentClass)
  698. {
  699. // Module can now define AdminTab keeping the module translations method,
  700. // i.e. in modules/[module name]/[iso_code].php
  701. if (!isset(self::$classInModule[$currentClass]) && class_exists($currentClass))
  702. {
  703. global $_MODULES;
  704. $_MODULE = array();
  705. $reflectionClass = new ReflectionClass($currentClass);
  706. $filePath = realpath($reflectionClass->getFileName());
  707. $realpathModuleDir = realpath(_PS_MODULE_DIR_);
  708. if (substr(realpath($filePath), 0, strlen($realpathModuleDir)) == $realpathModuleDir)
  709. {
  710. // For controllers in module/controllers path
  711. if (basename(dirname(dirname($filePath))) == 'controllers')
  712. self::$classInModule[$currentClass] = basename(dirname(dirname(dirname($filePath))));
  713. // For old AdminTab controllers
  714. else
  715. self::$classInModule[$currentClass] = substr(dirname($filePath), strlen($realpathModuleDir) + 1);
  716. $file = _PS_MODULE_DIR_.self::$classInModule[$currentClass].'/'.Context::getContext()->language->iso_code.'.php';
  717. if (Tools::file_exists_cache($file) && include_once($file))
  718. $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
  719. }
  720. else
  721. self::$classInModule[$currentClass] = false;
  722. }
  723. // return name of the module, or false
  724. return self::$classInModule[$currentClass];
  725. }
  726. /**
  727. * Return an instance of the specified module
  728. *
  729. * @param string $module_name Module name
  730. * @return Module
  731. */
  732. public static function getInstanceByName($module_name)
  733. {
  734. if (!Validate::isModuleName($module_name))
  735. die(Tools::displayError());
  736. if (!isset(self::$_INSTANCE[$module_name]))
  737. {
  738. if (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/'.$module_name.'.php'))
  739. {
  740. include_once(_PS_MODULE_DIR_.$module_name.'/'.$module_name.'.php');
  741. if (class_exists($module_name, false))
  742. return self::$_INSTANCE[$module_name] = new $module_name;
  743. }
  744. return false;
  745. }
  746. return self::$_INSTANCE[$module_name];
  747. }
  748. /**
  749. * Return an instance of the specified module
  750. *
  751. * @param integer $id_module Module ID
  752. * @return Module instance
  753. */
  754. public static function getInstanceById($id_module)
  755. {
  756. static $id2name = null;
  757. if (is_null($id2name))
  758. {
  759. $id2name = array();
  760. $sql = 'SELECT `id_module`, `name` FROM `'._DB_PREFIX_.'module`';
  761. if ($results = Db::getInstance()->executeS($sql))
  762. foreach ($results as $row)
  763. $id2name[$row['id_module']] = $row['name'];
  764. }
  765. if (isset($id2name[$id_module]))
  766. return Module::getInstanceByName($id2name[$id_module]);
  767. return false;
  768. }
  769. public static function configXmlStringFormat($string)
  770. {
  771. return str_replace('\'', '\\\'', Tools::htmlentitiesDecodeUTF8($string));
  772. }
  773. public static function getModuleName($module)
  774. {
  775. // Config file
  776. $configFile = _PS_MODULE_DIR_.$module.'/config.xml';
  777. if (!file_exists($configFile))
  778. return 'Module '.ucfirst($module);
  779. // Load config.xml
  780. libxml_use_internal_errors(true);
  781. $xml_module = simplexml_load_file($configFile);
  782. foreach (libxml_get_errors() as $error)
  783. {
  784. libxml_clear_errors();
  785. return 'Module '.ucfirst($module);
  786. }
  787. libxml_clear_errors();
  788. // Find translations
  789. global $_MODULES;
  790. $file = _PS_MODULE_DIR_.$module.'/'.Context::getContext()->language->iso_code.'.php';
  791. if (Tools::file_exists_cache($file) && include_once($file))
  792. if (isset($_MODULE) && is_array($_MODULE))
  793. $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
  794. // Return Name
  795. return Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->displayName), (string)$xml_module->name);
  796. }
  797. /**
  798. * Return available modules
  799. *
  800. * @param boolean $useConfig in order to use config.xml file in module dir
  801. * @return array Modules
  802. */
  803. public static function getModulesOnDisk($useConfig = false, $loggedOnAddons = false, $id_employee = false)
  804. {
  805. global $_MODULES;
  806. // Init var
  807. $module_list = array();
  808. $module_name_list = array();
  809. $modulesNameToCursor = array();
  810. $errors = array();
  811. // Get modules directory list and memory limit
  812. $modules_dir = Module::getModulesDirOnDisk();
  813. $memory_limit = Tools::getMemoryLimit();
  814. $modules_installed = array();
  815. $result = Db::getInstance()->executeS('
  816. SELECT name, version, interest
  817. FROM `'._DB_PREFIX_.'module`
  818. LEFT JOIN `'._DB_PREFIX_.'module_preference` ON (`module` = `name` AND `id_employee` = '.(int)$id_employee.')');
  819. foreach ($result as $row)
  820. $modules_installed[$row['name']] = $row;
  821. foreach ($modules_dir as $module)
  822. {
  823. // Memory usage checking
  824. if (function_exists('memory_get_usage') && $memory_limit != '-1')
  825. {
  826. $current_memory = memory_get_usage(true);
  827. // memory_threshold in MB
  828. $memory_threshold = (Tools::isX86_64arch() ? 3 : 1.5);
  829. if (($memory_limit - $current_memory) <= ($memory_threshold * 1024 * 1024))
  830. {
  831. $errors[] = Tools::displayError('All modules cannot be loaded due to memory limit restrictions, please increase your memory_limit value on your server configuration');
  832. break;
  833. }
  834. }
  835. // Check if config.xml module file exists and if it's not outdated
  836. $configFile = _PS_MODULE_DIR_.$module.'/config.xml';
  837. $xml_exist = file_exists($configFile);
  838. if ($xml_exist)
  839. $needNewConfigFile = (filemtime($configFile) < filemtime(_PS_MODULE_DIR_.$module.'/'.$module.'.php'));
  840. else
  841. $needNewConfigFile = true;
  842. // If config.xml exists and that the use config flag is at true
  843. if ($useConfig && $xml_exist)
  844. {
  845. // Load config.xml
  846. libxml_use_internal_errors(true);
  847. $xml_module = simplexml_load_file($configFile);
  848. foreach (libxml_get_errors() as $error)
  849. $errors[] = '['.$module.'] '.Tools::displayError('Error found in config file:').' '.htmlentities($error->message);
  850. libxml_clear_errors();
  851. // If no errors in Xml, no need instand and no need new config.xml file, we load only translations
  852. if (!count($errors) && (int)$xml_module->need_instance == 0 && !$needNewConfigFile)
  853. {
  854. $file = _PS_MODULE_DIR_.$module.'/'.Context::getContext()->language->iso_code.'.php';
  855. if (Tools::file_exists_cache($file) && include_once($file))
  856. if (isset($_MODULE) && is_array($_MODULE))
  857. $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
  858. $item = new stdClass();
  859. $item->id = 0;
  860. $item->warning = '';
  861. foreach ($xml_module as $k => $v)
  862. $item->$k = (string)$v;
  863. $item->displayName = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->displayName), (string)$xml_module->name));
  864. $item->description = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->description), (string)$xml_module->name));
  865. $item->author = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->author), (string)$xml_module->name));
  866. if (isset($xml_module->confirmUninstall))
  867. $item->confirmUninstall = Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->confirmUninstall), (string)$xml_module->name);
  868. $item->active = 0;
  869. $item->onclick_option = false;
  870. $module_list[] = $item;
  871. $module_name_list[] = '\''.pSQL($item->name).'\'';
  872. $modulesNameToCursor[strval($item->name)] = $item;
  873. }
  874. }
  875. // If use config flag is at false or config.xml does not exist OR need instance OR need a new config.xml file
  876. if (!$useConfig || !$xml_exist || (isset($xml_module->need_instance) && (int)$xml_module->need_instance == 1) || $needNewConfigFile)
  877. {
  878. // If class does not exists, we include the file
  879. if (!class_exists($module, false))
  880. {
  881. // Get content from php file
  882. $filepath = _PS_MODULE_DIR_.$module.'/'.$module.'.php';
  883. $file = trim(file_get_contents(_PS_MODULE_DIR_.$module.'/'.$module.'.php'));
  884. if (substr($file, 0, 5) == '<?php')
  885. $file = substr($file, 5);
  886. if (substr($file, -2) == '?>')
  887. $file = substr($file, 0, -2);
  888. // If (false) is a trick to not load the class with "eval".
  889. // This way require_once will works correctly
  890. if (eval('if (false){ '.$file.' }') !== false)
  891. require_once( _PS_MODULE_DIR_.$module.'/'.$module.'.php' );
  892. else
  893. $errors[] = sprintf(Tools::displayError('%1$s (parse error in %2$s)'), $module, substr($filepath, strlen(_PS_ROOT_DIR_)));
  894. }
  895. // If class exists, we just instanciate it
  896. if (class_exists($module, false))
  897. {
  898. $tmp_module = new $module;
  899. $item = new stdClass();
  900. $item->id = $tmp_module->id;
  901. $item->warning = $tmp_module->warning;
  902. $item->name = $tmp_module->name;
  903. $item->version = $tmp_module->version;
  904. $item->tab = $tmp_module->tab;
  905. $item->displayName = $tmp_module->displayName;
  906. $item->description = stripslashes($tmp_module->description);
  907. $item->author = $tmp_module->author;
  908. $item->limited_countries = $tmp_module->limited_countries;
  909. $item->parent_class = get_parent_class($module);
  910. $item->is_configurable = $tmp_module->is_configurable = method_exists($tmp_module, 'getContent') ? 1 : 0;
  911. $item->need_instance = isset($tmp_module->need_instance) ? $tmp_module->need_instance : 0;
  912. $item->active = $tmp_module->active;
  913. $item->currencies = isset($tmp_module->currencies) ? $tmp_module->currencies : null;
  914. $item->currencies_mode = isset($tmp_module->currencies_mode) ? $tmp_module->currencies_mode : null;
  915. // Method pointer to get dynamically the onclick content
  916. $item->onclick_option = method_exists($module, 'onclickOption') ? true : false;
  917. $module_list[] = $item;
  918. if (!$xml_exist || $needNewConfigFile)
  919. {
  920. self::$_generate_config_xml_mode = true;
  921. $tmp_module->_generateConfigXml();
  922. self::$_generate_config_xml_mode = false;
  923. }
  924. unset($tmp_module);
  925. }
  926. else
  927. $errors[] = sprintf(Tools::displayError('%1$s (class missing in %2$s)'), $module, substr($filepath, strlen(_PS_ROOT_DIR_)));
  928. }
  929. }
  930. // Get modules information from database
  931. if (!empty($module_name_list))
  932. {
  933. $list = Shop::getContextListShopID();
  934. $sql = 'SELECT m.id_module, m.name, (
  935. SELECT COUNT(*) FROM '._DB_PREFIX_.'module_shop ms WHERE m.id_module = ms.id_module AND ms.id_shop IN ('.implode(',', $list).')
  936. ) as total
  937. FROM '._DB_PREFIX_.'module m
  938. WHERE m.name IN ('.implode(',', $module_name_list).')';
  939. $results = Db::getInstance()->executeS($sql);
  940. foreach ($results as $result)
  941. {
  942. $moduleCursor = $modulesNameToCursor[$result['name']];
  943. $moduleCursor->id = $result['id_module'];
  944. $moduleCursor->active = ($result['total'] == count($list)) ? 1 : 0;
  945. }
  946. }
  947. // Get Default Country Modules and customer module
  948. $files_list = array(
  949. array('type' => 'addonsNative', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST, 'loggedOnAddons' => 0),
  950. array('type' => 'addonsBought', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_CUSTOMER_MODULES_LIST, 'loggedOnAddons' => 1),
  951. array('type' => 'addonsMustHave', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_MUST_HAVE_MODULES_LIST, 'loggedOnAddons' => 0),
  952. );
  953. foreach ($files_list as $f)
  954. if (file_exists($f['file']) && ($f['loggedOnAddons'] == 0 || $loggedOnAddons))
  955. {
  956. $file = $f['file'];
  957. $content = Tools::file_get_contents($file);
  958. $xml = @simplexml_load_string($content, null, LIBXML_NOCDATA);
  959. if ($xml && isset($xml->module))
  960. foreach ($xml->module as $modaddons)
  961. {
  962. $flag_found = 0;
  963. foreach ($module_list as $k => $m)
  964. if ($m->name == $modaddons->name && !isset($m->available_on_addons))
  965. {
  966. $flag_found = 1;
  967. if ($m->version != $modaddons->version && version_compare($m->version, $modaddons->version) === -1)
  968. $module_list[$k]->version_addons = $modaddons->version;
  969. }
  970. if ($flag_found == 0)
  971. {
  972. $item = new stdClass();
  973. $item->id = 0;
  974. $item->warning = '';
  975. $item->type = strip_tags((string)$f['type']);
  976. $item->name = strip_tags((string)$modaddons->name);
  977. $item->version = strip_tags((string)$modaddons->version);
  978. $item->tab = strip_tags((string)$modaddons->tab);
  979. $item->displayName = strip_tags((string)$modaddons->displayName).' (Addons)';
  980. $item->description = stripslashes(strip_tags((string)$modaddons->description));
  981. $item->author = strip_tags((string)$modaddons->author);
  982. $item->limited_countries = array();
  983. $item->parent_class = '';
  984. $item->onclick_option = false;
  985. $item->is_configurable = 0;
  986. $item->need_instance = 0;
  987. $item->not_on_disk = 1;
  988. $item->available_on_addons = 1;
  989. $item->active = 0;
  990. if (isset($modaddons->img))
  991. {
  992. if (!file_exists('../img/tmp/'.md5($modaddons->name).'.jpg'))
  993. if (!@copy($modaddons->img, '../img/tmp/'.md5($modaddons->name).'.jpg'))
  994. @copy('../img/404.gif', '../img/tmp/'.md5($modaddons->name).'.jpg');
  995. if (file_exists('../img/tmp/'.md5($modaddons->name).'.jpg'))
  996. $item->image = '../img/tmp/'.md5($modaddons->name).'.jpg';
  997. }
  998. if ($item->type == 'addonsMustHave')
  999. {
  1000. $item->addons_buy_url = strip_tags((string)$modaddons->url);
  1001. $prices = (array)$modaddons->price;
  1002. $id_default_currency = Configuration::get('PS_CURRENCY_DEFAULT');
  1003. foreach ($prices as $currency => $price)
  1004. if ($id_currency = Currency::getIdByIsoCode($currency))
  1005. if ($id_default_currency == $id_currency)
  1006. {
  1007. $item->price = (float)$price;
  1008. $item->id_currency = (int)$id_currency;
  1009. }
  1010. }
  1011. $module_list[] = $item;
  1012. }
  1013. }
  1014. }
  1015. foreach ($module_list as &$module)
  1016. if (isset($modules_installed[$module->name]))
  1017. {
  1018. $module->installed = true;
  1019. $module->database_version = $modules_installed[$module->name]['version'];
  1020. $module->interest = $modules_installed[$module->name]['interest'];
  1021. }
  1022. else
  1023. {
  1024. $module->installed = false;
  1025. $module->database_version = 0;
  1026. $module->interest = 0;
  1027. }
  1028. usort($module_list, create_function('$a,$b', '
  1029. if ($a->displayName == $b->displayName)
  1030. return 0;
  1031. return ($a->displayName < $b->displayName) ? -1 : 1;
  1032. '));
  1033. if ($errors)
  1034. {
  1035. echo '<div class="alert error"><h3>'.Tools::displayError('The following module(s) could not be loaded').':</h3><ol>';
  1036. foreach ($errors as $error)
  1037. echo '<li>'.$error.'</li>';
  1038. echo '</ol></div>';
  1039. }
  1040. return $module_list;
  1041. }
  1042. /**
  1043. * Return modules directory list
  1044. *
  1045. * @return array Modules Directory List
  1046. */
  1047. public static function getModulesDirOnDisk()
  1048. {
  1049. $module_list = array();
  1050. $modules = scandir(_PS_MODULE_DIR_);
  1051. foreach ($modules as $name)
  1052. {
  1053. if (is_dir(_PS_MODULE_DIR_.$name) && Tools::file_exists_cache(_PS_MODULE_DIR_.$name.'/'.$name.'.php'))
  1054. {
  1055. if (!Validate::isModuleName($name))
  1056. throw new PrestaShopException(sprintf('Module %s is not a valid module name', $name));
  1057. $module_list[] = $name;
  1058. }
  1059. }
  1060. return $module_list;
  1061. }
  1062. /**
  1063. * Return non native module
  1064. *
  1065. * @param int $position Take only positionnables modules
  1066. * @return array Modules
  1067. */
  1068. public static function getNonNativeModuleList()
  1069. {
  1070. $db = Db::getInstance();
  1071. $module_list_xml = _PS_ROOT_DIR_.self::CACHE_FILE_MODULES_LIST;
  1072. $native_modules = simplexml_load_file($module_list_xml);
  1073. $native_modules = $native_modules->modules;
  1074. foreach ($native_modules as $native_modules_type)
  1075. if (in_array($native_modules_type['type'], array('native', 'partner')))
  1076. {
  1077. $arr_native_modules[] = '""';
  1078. foreach ($native_modules_type->module as $module)
  1079. $arr_native_modules[] = '"'.pSQL($module['name']).'"';
  1080. }
  1081. return $db->executeS('SELECT * FROM `'._DB_PREFIX_.'module` m WHERE `name` NOT IN ('.implode(',', $arr_native_modules).') ');
  1082. }
  1083. /**
  1084. * Return installed modules
  1085. *
  1086. * @param int $position Take only positionnables modules
  1087. * @return array Modules
  1088. */
  1089. public static function getModulesInstalled($position = 0)
  1090. {
  1091. $sql = 'SELECT m.* FROM `'._DB_PREFIX_.'module` m ';
  1092. if ($position)
  1093. $sql .= 'LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON m.`id_module` = hm.`id_module`
  1094. LEFT JOIN `'._DB_PREFIX_.'hook` k ON hm.`id_hook` = k.`id_hook`
  1095. WHERE k.`position` = 1
  1096. GROUP BY m.id_module';
  1097. return Db::getInstance()->executeS($sql);
  1098. }
  1099. /**
  1100. * Execute modules for specified hook
  1101. *
  1102. * @param string $hook_name Hook Name
  1103. * @param array $hookArgs Parameters for the functions
  1104. * @return string modules output
  1105. */
  1106. public static function hookExec($hook_name, $hookArgs = array(), $id_module = null)
  1107. {
  1108. Tools::displayAsDeprecated();
  1109. return Hook::exec($hook_name, $hookArgs, $id_module);
  1110. }
  1111. public static function hookExecPayment()
  1112. {
  1113. Tools::displayAsDeprecated();
  1114. return Hook::exec('displayPayment');
  1115. }
  1116. public static function preCall($module_name)
  1117. {
  1118. return true;
  1119. }
  1120. /**
  1121. * Returns the list of the payment module associated to the current customer
  1122. * @see PaymentModule::getInstalledPaymentModules() if you don't care about the context
  1123. *
  1124. * @return array module informations
  1125. */
  1126. public static function getPaymentModules()
  1127. {
  1128. $context = Context::getContext();
  1129. if (isset($context->cart))
  1130. $billing = new Address((int)$context->cart->id_address_invoice);
  1131. $frontend = true;
  1132. $groups = array();
  1133. if (isset($context->employee))
  1134. $frontend = false;
  1135. elseif (isset($context->customer))
  1136. {
  1137. $groups = $context->customer->getGroups();
  1138. if (empty($groups))
  1139. $groups = array(Configuration::get('PS_UNIDENTIFIED_GROUP'));
  1140. }
  1141. $hookPayment = 'Payment';
  1142. if (Db::getInstance()->getValue('SELECT `id_hook` FROM `'._DB_PREFIX_.'hook` WHERE `name` = \'displayPayment\''))
  1143. $hookPayment = 'displayPayment';
  1144. $paypal_condition = '';
  1145. $iso_code = Country::getIsoById((int)Configuration::get('PS_COUNTRY_DEFAULT'));
  1146. $paypal_countries = array('ES', 'FR', 'PL', 'IT');
  1147. if (Context::getContext()->getMobileDevice() && Context::getContext()->shop->getTheme() == 'default' && in_array($iso_code, $paypal_countries))
  1148. $paypal_condition = ' AND m.`name` = \'paypal\'';
  1149. $list = Shop::getContextListShopID();
  1150. return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT DISTINCT m.`id_module`, h.`id_hook`, m.`name`, hm.`position`
  1151. FROM `'._DB_PREFIX_.'module` m
  1152. '.($frontend ? 'LEFT JOIN `'._DB_PREFIX_.'module_country` mc ON (m.`id_module` = mc.`id_module` AND mc.id_shop = '.(int)$context->shop->id.')' : '').'
  1153. '.($frontend ? 'INNER JOIN `'._DB_PREFIX_.'module_group` mg ON (m.`id_module` = mg.`id_module` AND mg.id_shop = '.(int)$context->shop->id.')' : '').'
  1154. '.($frontend && isset($context->customer) ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg on (cg.`id_group` = mg.`id_group`AND cg.`id_customer` = '.(int)$context->customer->id.')' : '').'
  1155. LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON hm.`id_module` = m.`id_module`
  1156. LEFT JOIN `'._DB_PREFIX_.'hook` h ON hm.`id_hook` = h.`id_hook`
  1157. WHERE h.`name` = \''.pSQL($hookPayment).'\'
  1158. '.(isset($billing) && $frontend ? 'AND mc.id_country = '.(int)$billing->id_country : '').'
  1159. AND (SELECT COUNT(*) FROM '._DB_PREFIX_.'module_shop ms WHERE ms.id_module = m.id_module AND ms.id_shop IN('.implode(', ', $list).')) = '.count($list).'
  1160. AND hm.id_shop IN('.implode(', ', $list).')
  1161. '.(count($groups) && $frontend ? 'AND (mg.`id_group` IN('.implode(', ', $groups).'))' : '').$paypal_condition.'
  1162. GROUP BY hm.id_hook, hm.id_module
  1163. ORDER BY hm.`position`, m.`name` DESC');
  1164. }
  1165. /**
  1166. * @deprecated 1.5.0 Use Translate::getModuleTranslation()
  1167. */
  1168. public static function findTranslation($name, $string, $source)
  1169. {
  1170. return Translate::getModuleTranslation($name, $string, $source);
  1171. }
  1172. /**
  1173. * Get translation for a given module text
  1174. *
  1175. * Note: $specific parameter is mandatory for library files.
  1176. * Otherwise, translation key will not match for Module library
  1177. * when module is loaded with eval() Module::getModulesOnDisk()
  1178. *
  1179. * @param string $string String to translate
  1180. * @param boolean|string $specific filename to use in translation key
  1181. * @return string Translation
  1182. */
  1183. public function l($string, $specific = false, $id_lang = null)
  1184. {
  1185. if (self::$_generate_config_xml_mode)
  1186. return $string;
  1187. return Translate::getModuleTranslation($this, $string, ($specific) ? $specific : $this->name);
  1188. }
  1189. /*
  1190. * Reposition module
  1191. *
  1192. * @param boolean $id_hook Hook ID
  1193. * @param boolean $way Up (1) or Down (0)
  1194. * @param int $position
  1195. */
  1196. public function updatePosition($id_hook, $way, $position = null)
  1197. {
  1198. foreach (Shop::getContextListShopID() as $shop_id)
  1199. {
  1200. $sql = 'SELECT hm.`id_module`, hm.`position`, hm.`id_hook`
  1201. FROM `'._DB_PREFIX_.'hook_module` hm
  1202. WHERE hm.`id_hook` = '.(int)$id_hook.' AND hm.`id_shop` = '.$shop_id.'
  1203. ORDER BY hm.`position` '.($way ? 'ASC' : 'DESC');
  1204. if (!$res = Db::getInstance()->executeS($sql))
  1205. continue;
  1206. foreach ($res as $key => $values)
  1207. if ((int)$values[$this->identifier] == (int)$this->id)
  1208. {
  1209. $k = $key;
  1210. break;
  1211. }
  1212. if (!isset($k) || !isset($res[$k]) || !isset($res[$k + 1]))
  1213. return false;
  1214. $from = $res[$k];
  1215. $to = $res[$k + 1];
  1216. if (isset($position) && !empty($position))
  1217. $to['position'] = (int)$position;
  1218. $sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
  1219. SET `position`= position '.($way ? '-1' : '+1').'
  1220. WHERE position between '.(int)(min(array($from['position'], $to['position']))).' AND '.max(array($from['position'], $to['position'])).'
  1221. AND `id_hook` = '.(int)$from['id_hook'].' AND `id_shop` = '.$shop_id;
  1222. if (!Db::getInstance()->execute($sql))
  1223. return false;
  1224. $sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
  1225. SET `position`='.(int)$to['position'].'
  1226. WHERE `'.pSQL($this->identifier).'` = '.(int)$from[$this->identifier].'
  1227. AND `id_hook` = '.(int)$to['id_hook'].' AND `id_shop` = '.$shop_id;
  1228. if (!Db::getInstance()->execute($sql))
  1229. return false;
  1230. }
  1231. return true;
  1232. }
  1233. /*
  1234. * Reorder modules position
  1235. *
  1236. * @param boolean $id_hook Hook ID
  1237. * @param array $shop_list List of shop
  1238. */
  1239. public function cleanPositions($id_hook, $shop_list = null)
  1240. {
  1241. $sql = 'SELECT `id_module`, `id_shop`
  1242. FROM `'._DB_PREFIX_.'hook_module`
  1243. WHERE `id_hook` = '.(int)$id_hook.'
  1244. '.((!is_null($shop_list) && $shop_list) ? ' AND `id_shop` IN('.implode(', ', $shop_list).')' : '').'
  1245. ORDER BY `position`';
  1246. $results = Db::getInstance()->executeS($sql);
  1247. $position = array();
  1248. foreach ($results as $row)
  1249. {
  1250. if (!isset($position[$row['id_shop']]))
  1251. $position[$row['id_shop']] = 1;
  1252. $sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
  1253. SET `position` = '.$position[$row['id_shop']].'
  1254. WHERE `id_hook` = '.(int)$id_hook.'
  1255. AND `id_module` = '.$row['id_module'].' AND `id_shop` = '.$row['id_shop'];
  1256. Db::getInstance()->execute($sql);
  1257. $position[$row['id_shop']]++;
  1258. }
  1259. return true;
  1260. }
  1261. public function displayError($error)
  1262. {
  1263. $output = '
  1264. <div class="module_error alert error">
  1265. '.$error.'
  1266. </div>';
  1267. $this->error = true;
  1268. return $output;
  1269. }
  1270. public function displayConfirmation($string)
  1271. {
  1272. $output = '
  1273. <div class="module_confirmation conf confirm">
  1274. '.$string.'
  1275. </div>';
  1276. return $output;
  1277. }
  1278. /*
  1279. * Return exceptions for module in hook
  1280. *
  1281. * @param int $id_hook Hook ID
  1282. * @return array Exceptions
  1283. */
  1284. protected static $exceptionsCache = null;
  1285. public function getExceptions($hookID, $dispatch = false)
  1286. {
  1287. if (self::$exceptionsCache === null)
  1288. {
  1289. self::$exceptionsCache = array();
  1290. $sql = 'SELECT * FROM `'._DB_PREFIX_.'hook_module_exceptions`
  1291. WHERE `id_shop` IN ('.implode(', ', Shop::getContextListShopID()).')';
  1292. $result = Db::getInstance()->executeS($sql);
  1293. foreach ($result as $row)
  1294. {
  1295. if (!$row['file_name'])
  1296. continue;
  1297. $key = $row['id_hook'].'-'.$row['id_module'];
  1298. if (!isset(self::$exceptionsCache[$key]))
  1299. self::$exceptionsCache[$key] = array();
  1300. if (!isset(self::$exceptionsCache[$key][$row['id_shop']]))
  1301. self::$exceptionsCache[$key][$row['id_shop']] = array();
  1302. self::$exceptionsCache[$key][$row['id_shop']][] = $row['file_name'];
  1303. }
  1304. }
  1305. $key = $hookID.'-'.$this->id;
  1306. if (!$dispatch)
  1307. {
  1308. $files = array();
  1309. foreach (Shop::getContextListShopID() as $shop_id)
  1310. if (isset(self::$exceptionsCache[$key], self::$exceptionsCache[$key][$shop_id]))
  1311. foreach (self::$exceptionsCache[$key][$shop_id] as $file)
  1312. if (!in_array($file, $files))
  1313. $files[] = $file;
  1314. return $files;
  1315. }
  1316. else
  1317. {
  1318. $list = array();
  1319. foreach (Shop::getContextListShopID() as $shop_id)
  1320. if (isset(self::$exceptionsCache[$key], self::$exceptionsCache[$key][$shop_id]))
  1321. $list[$shop_id] = self::$exceptionsCache[$key][$shop_id];
  1322. return $list;
  1323. }
  1324. }
  1325. public static function isInstalled($module_name)
  1326. {
  1327. if (!Cache::isStored('Module::isInstalled'.$module_name))
  1328. {
  1329. $id_module = Db::getInstance()->getValue('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($module_name).'\'');
  1330. Cache::store('Module::isInstalled'.$module_name, (bool)$id_module);
  1331. }
  1332. return Cache::retrieve('Module::isInstalled'.$module_name);
  1333. }
  1334. public static function isEnabled($module_name)
  1335. {
  1336. if (!Cache::isStored('Module::isEnabled'.$module_name))
  1337. {
  1338. $active = false;
  1339. $id_module = Db::getInstance()->getValue('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($module_name).'\'');
  1340. if (Db::getInstance()->getValue('SELECT `id_module` FROM `'._DB_PREFIX_.'module_shop` WHERE `id_module` = '.(int)$id_module.' AND `id_shop` = '.(int)Context::getContext()->shop->id))
  1341. $active = true;
  1342. Cache::store('Module::isEnabled'.$module_name, (bool)$active);
  1343. }
  1344. return Cache::retrieve('Module::isEnabled'.$module_name);
  1345. }
  1346. public function isRegisteredInHook($hook)
  1347. {
  1348. if (!$this->id)
  1349. return false;
  1350. $sql = 'SELECT COUNT(*)
  1351. FROM `'._DB_PREFIX_.'hook_module` hm
  1352. LEFT JOIN `'._DB_PREFIX_.'hook` h ON (h.`id_hook` = hm.`id_hook`)
  1353. WHERE h.`name` = \''.pSQL($hook).'\' AND hm.`id_module` = '.(int)$this->id;
  1354. return Db::getInstance()->getValue($sql);
  1355. }
  1356. /*
  1357. ** Template management (display, overload, cache)
  1358. */
  1359. protected static function _isTemplateOverloadedStatic($module_name, $template)
  1360. {
  1361. if (Tools::file_exists_cache(_PS_THEME_DIR_.'modules/'.$module_name.'/'.$template))
  1362. return true;
  1363. elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/views/templates/hook/'.$template))
  1364. return false;
  1365. elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/'.$template))
  1366. return false;
  1367. return null;
  1368. }
  1369. protected function _isTemplateOverloaded($template)
  1370. {
  1371. return Module::_isTemplateOverloadedStatic($this->name, $template);
  1372. }
  1373. public function display($file, $template, $cacheId = null, $compileId = null)
  1374. {
  1375. if (($overloaded = Module::_isTemplateOverloadedStatic(basename($file, '.php'), $template)) === null)
  1376. $result = Tools::displayError('No template found for module').' '.basename($file, '.php');
  1377. else
  1378. {
  1379. $this->smarty->assign(array(
  1380. 'module_dir' => __PS_BASE_URI__.'modules/'.basename($file, '.php').'/',
  1381. 'module_template_dir' => ($overloaded ? _THEME_DIR_ : __PS_BASE_URI__).'modules/'.basename($file, '.php').'/'
  1382. ));
  1383. $smarty_subtemplate = $this->context->smarty->createTemplate(
  1384. $this->getTemplatePath($template),
  1385. $cacheId,
  1386. $compileId,
  1387. $this->smarty
  1388. );
  1389. $result = $smarty_subtemplate->fetch();
  1390. }
  1391. return $result;
  1392. }
  1393. /**
  1394. * Get realpath of a template of current module (check if template is overriden too)
  1395. *
  1396. * @since 1.5.0
  1397. * @param string $template
  1398. * @return string
  1399. */
  1400. public function getTemplatePath($template)
  1401. {
  1402. $overloaded = $this->_isTemplateOverloaded($template);
  1403. if ($overloaded === null)
  1404. return null;
  1405. if ($overloaded)
  1406. return _PS_THEME_DIR_.'modules/'.$this->name.'/'.$template;
  1407. else if (file_exists(_PS_MODULE_DIR_.$this->name.'/views/templates/hook/'.$template))
  1408. return _PS_MODULE_DIR_.$this->name.'/views/templates/hook/'.$template;
  1409. else
  1410. return _PS_MODULE_DIR_.$this->name.'/'.$template;
  1411. }
  1412. protected function _getApplicableTemplateDir($template)
  1413. {
  1414. return $this->_isTemplateOverloaded($template) ? _PS_THEME_DIR_ : _PS_MODULE_DIR_.$this->name.'/';
  1415. }
  1416. public function isCached($template, $cacheId = null, $compileId = null)
  1417. {
  1418. $context = Context::getContext();
  1419. return $context->smarty->isCached($this->getTemplatePath($template), $cacheId, $compileId);
  1420. }
  1421. protected function _clearCache($template, $cache_id = null, $compile_id = null)
  1422. {
  1423. Tools::enableCache();
  1424. Tools::clearCache(Context::getContext()->smarty, $template, $cache_id, $compile_id);
  1425. Tools::restoreCacheSettings();
  1426. }
  1427. protected function _generateConfigXml()
  1428. {
  1429. $xml = '<?xml version="1.0" encoding="UTF-8" ?>
  1430. <module>
  1431. <name>'.$this->name.'</name>
  1432. <displayName><![CDATA['.Tools::htmlentitiesUTF8($this->displayName).']]></displayName>
  1433. <version><![CDATA['.$this->version.']]></version>
  1434. <description><![CDATA['.Tools::htmlentitiesUTF8($this->description).']]></description>
  1435. <author><![CDATA['.Tools::htmlentitiesUTF8($this->author).']]></author>
  1436. <tab><![CDATA['.Tools::htmlentitiesUTF8($this->tab).']]></tab>'.(isset($this->confirmUninstall) ? "\n\t".'<confirmUninstall>'.$this->confirmUninstall.'</confirmUninstall>' : '').'
  1437. <is_configurable>'.(isset($this->is_configurable) ? (int)$this->is_configurable : 0).'</is_configurable>
  1438. <need_instance>'.(int)$this->need_instance.'</need_instance>'.(isset($this->limited_countries) ? "\n\t".'<limited_countries>'.(count($this->limited_countries) == 1 ? $this->limited_countries[0] : '').'</limited_countries>' : '').'
  1439. </module>';
  1440. if (is_writable(_PS_MODULE_DIR_.$this->name.'/'))
  1441. file_put_contents(_PS_MODULE_DIR_.$this->name.'/config.xml', $xml);
  1442. }
  1443. /**
  1444. * Check if the module is transplantable on the hook in parameter
  1445. * @param string $hook_name
  1446. * @return bool if module can be transplanted on hook
  1447. */
  1448. public function isHookableOn($hook_name)
  1449. {
  1450. $retro_hook_name = Hook::getRetroHookName($hook_name);
  1451. return (is_callable(array($this, 'hook'.ucfirst($hook_name))) || is_callable(array($this, 'hook'.ucfirst($retro_hook_name))));
  1452. }
  1453. /**
  1454. * Check employee permission for module
  1455. * @param array $variable (action)
  1456. * @param object $employee
  1457. * @return bool if module can be transplanted on hook
  1458. */
  1459. public function getPermission($variable, $employee = null)
  1460. {
  1461. return Module::getPermissionStatic($this->id, $variable, $employee);
  1462. }
  1463. /**
  1464. * Check employee permission for module (static method)
  1465. * @param integer $id_module
  1466. * @param array $variable (action)
  1467. * @param object $employee
  1468. * @return bool if module can be transplanted on hook
  1469. */
  1470. public static function getPermissionStatic($id_module, $variable, $employee = null)
  1471. {
  1472. if (!in_array($variable, array('view', 'configure')))
  1473. return false;
  1474. if (!$employee)
  1475. $employee = Context::getContext()->employee;
  1476. if ($employee->id_profile == _PS_ADMIN_PROFILE_)
  1477. return true;
  1478. if (!isset(self::$cache_permissions[$employee->id_profile]))
  1479. {
  1480. self::$cache_permissions[$employee->id_profile] = array();
  1481. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT `id_module`, `view`, `configure` FROM `'._DB_PREFIX_.'module_access` WHERE `id_profile` = '.(int)$employee->id_profile);
  1482. foreach ($result as $row)
  1483. {
  1484. self::$cache_permissions[$employee->id_profile][$row['id_module']]['view'] = $row['view'];
  1485. self::$cache_permissions[$employee->id_profile][$row['id_module']]['configure'] = $row['configure'];
  1486. }
  1487. }
  1488. if (!isset(self::$cache_permissions[$employee->id_profile][$id_module]))
  1489. throw new PrestaShopException('No access reference in table module_access for id_module '.$id_module.'.');
  1490. return (bool)self::$cache_permissions[$employee->id_profile][$id_module][$variable];
  1491. }
  1492. /**
  1493. * Get Unauthorized modules for a client group
  1494. * @param integer group_id
  1495. */
  1496. public static function getAuthorizedModules($group_id)
  1497. {
  1498. return Db::getInstance()->executeS('
  1499. SELECT m.`id_module`, m.`name` FROM `'._DB_PREFIX_.'module_group` mg
  1500. LEFT JOIN `'._DB_PREFIX_.'module` m ON (m.`id_module` = mg.`id_module`)
  1501. WHERE mg.`id_group` = '.(int)$group_id);
  1502. }
  1503. /**
  1504. * Get id module by name
  1505. * @param string name
  1506. * @return integer id
  1507. */
  1508. public static function getModuleIdByName($name)
  1509. {
  1510. return Db::getInstance()->getValue('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = "'.pSQL($name).'"');
  1511. }
  1512. /**
  1513. * Get module errors
  1514. *
  1515. * @since 1.5.0
  1516. * @return array errors
  1517. */
  1518. public function getErrors()
  1519. {
  1520. return $this->_errors;
  1521. }
  1522. /**
  1523. * Get module messages confirmation
  1524. *
  1525. * @since 1.5.0
  1526. * @return array conf
  1527. */
  1528. public function getConfirmations()
  1529. {
  1530. return $this->_confirmations;
  1531. }
  1532. /**
  1533. * Get local path for module
  1534. *
  1535. * @since 1.5.0
  1536. * @return string
  1537. */
  1538. public function getLocalPath()
  1539. {
  1540. return $this->local_path;
  1541. }
  1542. /**
  1543. * Get uri path for module
  1544. *
  1545. * @since 1.5.0
  1546. * @return string
  1547. */
  1548. public function getPathUri()
  1549. {
  1550. return $this->_path;
  1551. }
  1552. /*
  1553. * Return module position for a given hook
  1554. *
  1555. * @param boolean $id_hook Hook ID
  1556. * @return integer position
  1557. */
  1558. public function getPosition($id_hook)
  1559. {
  1560. if (isset(Hook::$preloadModulesFromHooks))
  1561. if (isset(Hook::$preloadModulesFromHooks[$id_hook]))
  1562. if (isset(Hook::$preloadModulesFromHooks[$id_hook]['module_position'][$this->id]))
  1563. return Hook::$preloadModulesFromHooks[$id_hook]['module_position'][$this->id];
  1564. else
  1565. return 0;
  1566. $result = Db::getInstance()->getRow('
  1567. SELECT `position`
  1568. FROM `'._DB_PREFIX_.'hook_module`
  1569. WHERE `id_hook` = '.(int)$id_hook.'
  1570. AND `id_module` = '.(int)$this->id.'
  1571. AND `id_shop` = '.(int)Context::getContext()->shop->id);
  1572. return $result['position'];
  1573. }
  1574. /**
  1575. * add a warning message to display at the top of the admin page
  1576. *
  1577. * @param string $msg
  1578. */
  1579. public function adminDisplayWarning($msg)
  1580. {
  1581. if (!($this->context->controller instanceof AdminController))
  1582. return false;
  1583. $this->context->controller->warnings[] = $msg;
  1584. }
  1585. /**
  1586. * add a info message to display at the top of the admin page
  1587. *
  1588. * @param string $msg
  1589. */
  1590. protected function adminDisplayInformation($msg)
  1591. {
  1592. if (!($this->context->controller instanceof AdminController))
  1593. return false;
  1594. $this->context->controller->informations[] = $msg;
  1595. }
  1596. /**
  1597. * Install overrides files for the module
  1598. *
  1599. * @return bool
  1600. */
  1601. public function installOverrides()
  1602. {
  1603. if (!is_dir($this->getLocalPath().'override'))
  1604. return true;
  1605. $result = true;
  1606. foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file)
  1607. {
  1608. $class = basename($file, '.php');
  1609. if (Autoload::getInstance()->getClassPath($class.'Core'))
  1610. $result &= $this->addOverride($class);
  1611. }
  1612. return $result;
  1613. }
  1614. /**
  1615. * Uninstall overrides files for the module
  1616. *
  1617. * @return bool
  1618. */
  1619. public function uninstallOverrides()
  1620. {
  1621. if (!is_dir($this->getLocalPath().'override'))
  1622. return true;
  1623. $result = true;
  1624. foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file)
  1625. {
  1626. $class = basename($file, '.php');
  1627. if (Autoload::getInstance()->getClassPath($class.'Core'))
  1628. $result &= $this->removeOverride($class);
  1629. }
  1630. return $result;
  1631. }
  1632. /**
  1633. * Add all methods in a module override to the override class
  1634. *
  1635. * @param string $classname
  1636. * @return bool
  1637. */
  1638. public function addOverride($classname)
  1639. {
  1640. $path = Autoload::getInstance()->getClassPath($classname.'Core');
  1641. // Check if there is already an override file, if not, we just need to copy the file
  1642. if (!($classpath = Autoload::getInstance()->getClassPath($classname)))
  1643. {
  1644. $override_src = $this->getLocalPath().'override'.DIRECTORY_SEPARATOR.$path;
  1645. $override_dest = _PS_ROOT_DIR_.DIRECTORY_SEPARATOR.'override'.DIRECTORY_SEPARATOR.$path;
  1646. if (!is_writable(dirname($override_dest)))
  1647. throw new Exception(sprintf(Tools::displayError('directory (%s) not writable'), dirname($override_dest)));
  1648. copy($override_src, $override_dest);
  1649. return true;
  1650. }
  1651. // Check if override file is writable
  1652. $override_path = _PS_ROOT_DIR_.'/'.Autoload::getInstance()->getClassPath($classname);
  1653. if (!is_writable($override_path))
  1654. throw new Exception(sprintf(Tools::displayError('file (%s) not writable'), $override_path));
  1655. // Make a reflection of the override class and the module override class
  1656. $override_file = file($override_path);
  1657. eval(preg_replace(array('#^\s*<\?php#', '#class\s+'.$classname.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i'), array('', 'class '.$classname.'OverrideOriginal'), implode('', $override_file)));
  1658. $override_class = new ReflectionClass($classname.'OverrideOriginal');
  1659. $module_file = file($this->getLocalPath().'override'.DIRECTORY_SEPARATOR.$path);
  1660. eval(preg_replace(array('#^\s*<\?php#', '#class\s+'.$classname.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array('', 'class '.$classname.'Override'), implode('', $module_file)));
  1661. $module_class = new ReflectionClass($classname.'Override');
  1662. // Check if none of the methods already exists in the override class
  1663. foreach ($module_class->getMethods() as $method)
  1664. if ($override_class->hasMethod($method->getName()))
  1665. throw new Exception(sprintf(Tools::displayError('The method %1$s in the class %2$s is already overriden.'), $method->getName(), $classname));
  1666. // Check if none of the properties already exists in the override class
  1667. foreach ($module_class->getProperties() as $property)
  1668. if ($override_class->hasProperty($property->getName()))
  1669. throw new Exception(sprintf(Tools::displayError('The property %1$s in the class %2$s is already defined.'), $property->getName(), $classname));
  1670. // Insert the methods from module override in override
  1671. $copy_from = array_slice($module_file, $module_class->getStartLine() + 1, $module_class->getEndLine() - $module_class->getStartLine() - 2);
  1672. array_splice($override_file, $override_class->getEndLine() - 1, 0, $copy_from);
  1673. $code = implode('', $override_file);
  1674. file_put_contents($override_path, $code);
  1675. return true;
  1676. }
  1677. /**
  1678. * Remove all methods in a module override from the override class
  1679. *
  1680. * @param string $classname
  1681. * @return bool
  1682. */
  1683. public function removeOverride($classname)
  1684. {
  1685. $path = Autoload::getInstance()->getClassPath($classname.'Core');
  1686. if (!Autoload::getInstance()->getClassPath($classname))
  1687. return true;
  1688. // Check if override file is writable
  1689. $override_path = _PS_ROOT_DIR_.'/'.Autoload::getInstance()->getClassPath($classname);
  1690. if (!is_writable($override_path))
  1691. return false;
  1692. // Make a reflection of the override class and the module override class
  1693. $override_file = file($override_path);
  1694. eval(preg_replace(array('#^\s*<\?php#', '#class\s+'.$classname.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i'), array('', 'class '.$classname.'OverrideOriginal_remove'), implode('', $override_file)));
  1695. $override_class = new ReflectionClass($classname.'OverrideOriginal_remove');
  1696. $module_file = file($this->getLocalPath().'override/'.$path);
  1697. eval(preg_replace(array('#^\s*<\?php#', '#class\s+'.$classname.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array('', 'class '.$classname.'Override_remove'), implode('', $module_file)));
  1698. $module_class = new ReflectionClass($classname.'Override_remove');
  1699. // Remove methods from override file
  1700. $override_file = file($override_path);
  1701. foreach ($module_class->getMethods() as $method)
  1702. {
  1703. if (!$override_class->hasMethod($method->getName()))
  1704. continue;
  1705. $method = $override_class->getMethod($method->getName());
  1706. $length = $method->getEndLine() - $method->getStartLine() + 1;
  1707. array_splice($override_file, $method->getStartLine() - 1, $length, array_pad(array(), $length, '#--remove--#'));
  1708. }
  1709. // Remove properties from override file
  1710. foreach ($module_class->getProperties() as $property)
  1711. {
  1712. if (!$override_class->hasProperty($property->getName()))
  1713. continue;
  1714. // Remplacer la ligne de d?Šclaration par "remove"
  1715. foreach ($override_file as $line_number => &$line_content)
  1716. if (preg_match('/(public|private|protected)\s+(static\s+)?\$'.$property->getName().'/i', $line_content))
  1717. {
  1718. $line_content = '#--remove--#';
  1719. break;
  1720. }
  1721. }
  1722. // Rewrite nice code
  1723. $code = '';
  1724. foreach ($override_file as $line)
  1725. {
  1726. if ($line == '#--remove--#')
  1727. continue;
  1728. $code .= $line;
  1729. }
  1730. file_put_contents($override_path, $code);
  1731. return true;
  1732. }
  1733. }
  1734. function ps_module_version_sort($a, $b)
  1735. {
  1736. return version_compare($a['version'], $b['version']);
  1737. }