PageRenderTime 59ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/classes/module/Module.php

https://github.com/netplayer/PrestaShop
PHP | 2548 lines | 1695 code | 326 blank | 527 comment | 290 complexity | 5eb4dc1c29c712c3c240f7d33393bb64 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * 2007-2014 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2014 PrestaShop SA
  23. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. abstract class ModuleCore
  27. {
  28. /** @var integer Module ID */
  29. public $id = null;
  30. /** @var float Version */
  31. public $version;
  32. public $database_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();
  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 string Module key provided by addons.prestashop.com */
  51. public $module_key = '';
  52. public $description_full;
  53. public $additional_description;
  54. public $compatibility;
  55. public $nb_rates;
  56. public $avg_rate;
  57. public $badges;
  58. /** @var int need_instance */
  59. public $need_instance = 1;
  60. /** @var string Admin tab corresponding to the module */
  61. public $tab = null;
  62. /** @var boolean Status */
  63. public $active = false;
  64. /** @var boolean Is the module certified by addons.prestashop.com */
  65. public $trusted = false;
  66. /** @var string Fill it if the module is installed but not yet set up */
  67. public $warning;
  68. public $enable_device = 7;
  69. /** @var array to store the limited country */
  70. public $limited_countries = array();
  71. /** @var array names of the controllers */
  72. public $controllers = array();
  73. /** @var array used by AdminTab to determine which lang file to use (admin.php or module lang file) */
  74. public static $classInModule = array();
  75. /** @var array current language translations */
  76. protected $_lang = array();
  77. /** @var string Module web path (eg. '/shop/modules/modulename/') */
  78. protected $_path = null;
  79. /**
  80. * @since 1.5.0.1
  81. * @var string Module local path (eg. '/home/prestashop/modules/modulename/')
  82. */
  83. protected $local_path = null;
  84. /** @var protected array filled with module errors */
  85. protected $_errors = array();
  86. /** @var protected array filled with module success */
  87. protected $_confirmations = array();
  88. /** @var protected string main table used for modules installed */
  89. protected $table = 'module';
  90. /** @var protected string identifier of the main table */
  91. protected $identifier = 'id_module';
  92. /** @var protected array cache filled with modules informations */
  93. protected static $modules_cache;
  94. /** @var protected array cache filled with modules instances */
  95. protected static $_INSTANCE = array();
  96. /** @var protected boolean filled with config xml generation mode */
  97. protected static $_generate_config_xml_mode = false;
  98. /** @var protected array filled with cache translations */
  99. protected static $l_cache = array();
  100. /** @var protected array filled with cache permissions (modules / employee profiles) */
  101. protected static $cache_permissions = array();
  102. /** @var Context */
  103. protected $context;
  104. /** @var Smarty_Data */
  105. protected $smarty;
  106. /** @var currentSmartySubTemplate */
  107. protected $current_subtemplate = null;
  108. protected static $update_translations_after_install = true;
  109. /** @var allow push */
  110. public $allow_push;
  111. public $push_time_limit = 180;
  112. const CACHE_FILE_MODULES_LIST = '/config/xml/modules_list.xml';
  113. const CACHE_FILE_TAB_MODULES_LIST = '/config/xml/tab_modules_list.xml';
  114. const CACHE_FILE_ALL_COUNTRY_MODULES_LIST = '/config/xml/modules_native_addons.xml';
  115. const CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST = '/config/xml/default_country_modules_list.xml';
  116. const CACHE_FILE_CUSTOMER_MODULES_LIST = '/config/xml/customer_modules_list.xml';
  117. const CACHE_FILE_MUST_HAVE_MODULES_LIST = '/config/xml/must_have_modules_list.xml';
  118. const CACHE_FILE_TRUSTED_MODULES_LIST = '/config/xml/trusted_modules_list.xml';
  119. const CACHE_FILE_UNTRUSTED_MODULES_LIST = '/config/xml/untrusted_modules_list.xml';
  120. public static $hosted_modules_blacklist = array('autoupgrade');
  121. /**
  122. * Constructor
  123. *
  124. * @param string $name Module unique name
  125. * @param Context $context
  126. */
  127. public function __construct($name = null, Context $context = null)
  128. {
  129. if (isset($this->ps_versions_compliancy) && !isset($this->ps_versions_compliancy['min']))
  130. $this->ps_versions_compliancy['min'] = '1.4.0.0';
  131. if (isset($this->ps_versions_compliancy) && !isset($this->ps_versions_compliancy['max']))
  132. $this->ps_versions_compliancy['max'] = _PS_VERSION_;
  133. if (strlen($this->ps_versions_compliancy['min']) == 3)
  134. $this->ps_versions_compliancy['min'] .= '.0.0';
  135. if (strlen($this->ps_versions_compliancy['max']) == 3)
  136. $this->ps_versions_compliancy['max'] .= '.999.999';
  137. // Load context and smarty
  138. $this->context = $context ? $context : Context::getContext();
  139. if (is_object($this->context->smarty))
  140. $this->smarty = $this->context->smarty->createData($this->context->smarty);
  141. // If the module has no name we gave him its id as name
  142. if ($this->name == null)
  143. $this->name = $this->id;
  144. // If the module has the name we load the corresponding data from the cache
  145. if ($this->name != null)
  146. {
  147. // If cache is not generated, we generate it
  148. if (self::$modules_cache == null && !is_array(self::$modules_cache))
  149. {
  150. $id_shop = (Validate::isLoadedObject($this->context->shop) ? $this->context->shop->id : 1);
  151. self::$modules_cache = array();
  152. // Join clause is done to check if the module is activated in current shop context
  153. $result = Db::getInstance()->executeS('
  154. SELECT m.`id_module`, m.`name`, (
  155. SELECT id_module
  156. FROM `'._DB_PREFIX_.'module_shop` ms
  157. WHERE m.`id_module` = ms.`id_module`
  158. AND ms.`id_shop` = '.(int)$id_shop.'
  159. LIMIT 1
  160. ) as mshop
  161. FROM `'._DB_PREFIX_.'module` m');
  162. foreach ($result as $row)
  163. {
  164. self::$modules_cache[$row['name']] = $row;
  165. self::$modules_cache[$row['name']]['active'] = ($row['mshop'] > 0) ? 1 : 0;
  166. }
  167. }
  168. // We load configuration from the cache
  169. if (isset(self::$modules_cache[$this->name]))
  170. {
  171. if (isset(self::$modules_cache[$this->name]['id_module']))
  172. $this->id = self::$modules_cache[$this->name]['id_module'];
  173. foreach (self::$modules_cache[$this->name] as $key => $value)
  174. if (array_key_exists($key, $this))
  175. $this->{$key} = $value;
  176. $this->_path = __PS_BASE_URI__.'modules/'.$this->name.'/';
  177. }
  178. $this->local_path = _PS_MODULE_DIR_.$this->name.'/';
  179. }
  180. }
  181. /**
  182. * Insert module into datable
  183. */
  184. public function install()
  185. {
  186. Hook::exec('actionModuleInstallBefore', array('object' => $this));
  187. // Check module name validation
  188. if (!Validate::isModuleName($this->name))
  189. {
  190. $this->_errors[] = $this->l('Unable to install the module (Module name is not valid).');
  191. return false;
  192. }
  193. // Check PS version compliancy
  194. if (!$this->checkCompliancy())
  195. {
  196. $this->_errors[] = $this->l('The version of your module is not compliant with your PrestaShop version.');
  197. return false;
  198. }
  199. // Check module dependencies
  200. if (count($this->dependencies) > 0)
  201. foreach ($this->dependencies as $dependency)
  202. if (!Db::getInstance()->getRow('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($dependency).'\''))
  203. {
  204. $error = $this->l('Before installing this module, you have to install this/these module(s) first:').'<br />';
  205. foreach ($this->dependencies as $d)
  206. $error .= '- '.$d.'<br />';
  207. $this->_errors[] = $error;
  208. return false;
  209. }
  210. // Check if module is installed
  211. $result = Module::isInstalled($this->name);
  212. if ($result)
  213. {
  214. $this->_errors[] = $this->l('This module has already been installed.');
  215. return false;
  216. }
  217. // Install overrides
  218. try {
  219. $this->installOverrides();
  220. } catch (Exception $e) {
  221. $this->_errors[] = sprintf(Tools::displayError('Unable to install override: %s'), $e->getMessage());
  222. $this->uninstallOverrides();
  223. return false;
  224. }
  225. if (!$this->installControllers())
  226. return false;
  227. // Install module and retrieve the installation id
  228. $result = Db::getInstance()->insert($this->table, array('name' => $this->name, 'active' => 1, 'version' => $this->version));
  229. if (!$result)
  230. {
  231. $this->_errors[] = $this->l('Technical error: PrestaShop could not installed this module.');
  232. return false;
  233. }
  234. $this->id = Db::getInstance()->Insert_ID();
  235. Cache::clean('Module::isInstalled'.$this->name);
  236. // Enable the module for current shops in context
  237. $this->enable();
  238. // Permissions management
  239. Db::getInstance()->execute('
  240. INSERT INTO `'._DB_PREFIX_.'module_access` (`id_profile`, `id_module`, `view`, `configure`) (
  241. SELECT id_profile, '.(int)$this->id.', 1, 1
  242. FROM '._DB_PREFIX_.'access a
  243. WHERE id_tab = (
  244. SELECT `id_tab` FROM '._DB_PREFIX_.'tab
  245. WHERE class_name = \'AdminModules\' LIMIT 1)
  246. AND a.`view` = 1)');
  247. Db::getInstance()->execute('
  248. INSERT INTO `'._DB_PREFIX_.'module_access` (`id_profile`, `id_module`, `view`, `configure`) (
  249. SELECT id_profile, '.(int)$this->id.', 1, 0
  250. FROM '._DB_PREFIX_.'access a
  251. WHERE id_tab = (
  252. SELECT `id_tab` FROM '._DB_PREFIX_.'tab
  253. WHERE class_name = \'AdminModules\' LIMIT 1)
  254. AND a.`view` = 0)');
  255. // Adding Restrictions for client groups
  256. Group::addRestrictionsForModule($this->id, Shop::getShops(true, null, true));
  257. Hook::exec('actionModuleInstallAfter', array('object' => $this));
  258. if (Module::$update_translations_after_install)
  259. $this->updateModuleTranslations();
  260. return true;
  261. }
  262. public function checkCompliancy()
  263. {
  264. if (version_compare(_PS_VERSION_, $this->ps_versions_compliancy['min'], '<') || version_compare(_PS_VERSION_, $this->ps_versions_compliancy['max'], '>'))
  265. return false;
  266. else
  267. return true;
  268. }
  269. public static function updateTranslationsAfterInstall($update = true)
  270. {
  271. Module::$update_translations_after_install = (bool)$update;
  272. }
  273. public function updateModuleTranslations()
  274. {
  275. return Language::updateModulesTranslations(array($this->name));
  276. }
  277. /**
  278. * Set errors, warning or success message of a module upgrade
  279. *
  280. * @param $upgrade_detail
  281. */
  282. protected function setUpgradeMessage($upgrade_detail)
  283. {
  284. // Store information if a module has been upgraded (memory optimization)
  285. if ($upgrade_detail['available_upgrade'])
  286. {
  287. if ($upgrade_detail['success'])
  288. {
  289. $this->_confirmations[] = sprintf($this->l('Current version: %s'), $this->version);
  290. $this->_confirmations[] = $upgrade_detail['number_upgraded'].' '.$this->l('file upgrade applied');
  291. }
  292. else
  293. {
  294. if (!$upgrade_detail['number_upgraded'])
  295. $this->_errors[] = $this->l('No upgrade has been applied');
  296. else
  297. {
  298. $this->_errors[] = sprintf($this->l('Upgraded from: %S to %s'), $upgrade_detail['upgraded_from'], $upgrade_detail['upgraded_to']);
  299. $this->_errors[] = $upgrade_detail['number_upgrade_left'].' '.$this->l('upgrade left');
  300. }
  301. if (isset($upgrade_detail['duplicate']) && $upgrade_detail['duplicate'])
  302. $this->_errors[] = sprintf(Tools::displayError('Module %s cannot be upgraded this time: please refresh this page to update it.'), $this->name);
  303. else
  304. $this->_errors[] = $this->l('To prevent any problem, this module has been turned off');
  305. }
  306. }
  307. }
  308. /**
  309. * Init the upgrade module
  310. *
  311. * @static
  312. * @param $module_name
  313. * @param $module_version
  314. * @return bool
  315. */
  316. public static function initUpgradeModule($module)
  317. {
  318. if (((int)$module->installed == 1) & (empty($module->database_version) === true))
  319. {
  320. Module::upgradeModuleVersion($module->name, $module->version);
  321. $module->database_version = $module->version;
  322. }
  323. // Init cache upgrade details
  324. self::$modules_cache[$module->name]['upgrade'] = array(
  325. 'success' => false, // bool to know if upgrade succeed or not
  326. 'available_upgrade' => 0, // Number of available module before any upgrade
  327. 'number_upgraded' => 0, // Number of upgrade done
  328. 'number_upgrade_left' => 0,
  329. 'upgrade_file_left' => array(), // List of the upgrade file left
  330. 'version_fail' => 0, // Version of the upgrade failure
  331. 'upgraded_from' => 0, // Version number before upgrading anything
  332. 'upgraded_to' => 0, // Last upgrade applied
  333. );
  334. // Need Upgrade will check and load upgrade file to the moduleCache upgrade case detail
  335. $ret = $module->installed && Module::needUpgrade($module);
  336. return $ret;
  337. }
  338. /**
  339. * Run the upgrade for a given module name and version
  340. *
  341. * @return array
  342. */
  343. public function runUpgradeModule()
  344. {
  345. $upgrade = &self::$modules_cache[$this->name]['upgrade'];
  346. foreach ($upgrade['upgrade_file_left'] as $num => $file_detail)
  347. {
  348. if (function_exists($file_detail['upgrade_function']))
  349. {
  350. $upgrade['success'] = false;
  351. $upgrade['duplicate'] = true;
  352. break;
  353. }
  354. include($file_detail['file']);
  355. // Call the upgrade function if defined
  356. $upgrade['success'] = false;
  357. if (function_exists($file_detail['upgrade_function']))
  358. $upgrade['success'] = $file_detail['upgrade_function']($this);
  359. // Set detail when an upgrade succeed or failed
  360. if ($upgrade['success'])
  361. {
  362. $upgrade['number_upgraded'] += 1;
  363. $upgrade['upgraded_to'] = $file_detail['version'];
  364. unset($upgrade['upgrade_file_left'][$num]);
  365. }
  366. else
  367. {
  368. $upgrade['version_fail'] = $file_detail['version'];
  369. // If any errors, the module is disabled
  370. $this->disable();
  371. break;
  372. }
  373. }
  374. $upgrade['number_upgrade_left'] = count($upgrade['upgrade_file_left']);
  375. // Update module version in DB with the last succeed upgrade
  376. if ($upgrade['upgraded_to'])
  377. Module::upgradeModuleVersion($this->name, $upgrade['upgraded_to']);
  378. $this->setUpgradeMessage($upgrade);
  379. return $upgrade;
  380. }
  381. /**
  382. * Upgrade the registered version to a new one
  383. *
  384. * @static
  385. * @param $name
  386. * @param $version
  387. * @return bool
  388. */
  389. public static function upgradeModuleVersion($name, $version)
  390. {
  391. return Db::getInstance()->execute('
  392. UPDATE `'._DB_PREFIX_.'module` m
  393. SET m.version = \''.bqSQL($version).'\'
  394. WHERE m.name = \''.bqSQL($name).'\'');
  395. }
  396. /**
  397. * Check if a module need to be upgraded.
  398. * This method modify the module_cache adding an upgrade list file
  399. *
  400. * @static
  401. * @param $module_name
  402. * @param $module_version
  403. * @return bool
  404. */
  405. public static function needUpgrade($module)
  406. {
  407. self::$modules_cache[$module->name]['upgrade']['upgraded_from'] = $module->database_version;
  408. // Check the version of the module with the registered one and look if any upgrade file exist
  409. if (Tools::version_compare($module->version, $module->database_version, '>'))
  410. {
  411. $old_version = $module->database_version;
  412. $module = Module::getInstanceByName($module->name);
  413. if ($module instanceof Module)
  414. return $module->loadUpgradeVersionList($module->name, $module->version, $old_version);
  415. }
  416. return null;
  417. }
  418. /**
  419. * Load the available list of upgrade of a specified module
  420. * with an associated version
  421. *
  422. * @static
  423. * @param $module_name
  424. * @param $module_version
  425. * @param $registered_version
  426. * @return bool to know directly if any files have been found
  427. */
  428. protected static function loadUpgradeVersionList($module_name, $module_version, $registered_version)
  429. {
  430. $list = array();
  431. $upgrade_path = _PS_MODULE_DIR_.$module_name.'/upgrade/';
  432. // Check if folder exist and it could be read
  433. if (file_exists($upgrade_path) && ($files = scandir($upgrade_path)))
  434. {
  435. // Read each file name
  436. foreach ($files as $file)
  437. if (!in_array($file, array('.', '..', '.svn', 'index.php')) && preg_match('/\.php$/', $file))
  438. {
  439. $tab = explode('-', $file);
  440. if (!isset($tab[1]))
  441. continue;
  442. $file_version = basename($tab[1], '.php');
  443. // Compare version, if minor than actual, we need to upgrade the module
  444. if (count($tab) == 2 &&
  445. (Tools::version_compare($file_version, $module_version, '<=') &&
  446. Tools::version_compare($file_version, $registered_version, '>')))
  447. {
  448. $list[] = array(
  449. 'file' => $upgrade_path.$file,
  450. 'version' => $file_version,
  451. 'upgrade_function' => 'upgrade_module_'.str_replace('.', '_', $file_version));
  452. }
  453. }
  454. }
  455. // No files upgrade, then upgrade succeed
  456. if (count($list) == 0)
  457. {
  458. self::$modules_cache[$module_name]['upgrade']['success'] = true;
  459. Module::upgradeModuleVersion($module_name, $module_version);
  460. }
  461. usort($list, 'ps_module_version_sort');
  462. // Set the list to module cache
  463. self::$modules_cache[$module_name]['upgrade']['upgrade_file_left'] = $list;
  464. self::$modules_cache[$module_name]['upgrade']['available_upgrade'] = count($list);
  465. return (bool)count($list);
  466. }
  467. /**
  468. * Return the status of the upgraded module
  469. *
  470. * @static
  471. * @param $module_name
  472. * @return bool
  473. */
  474. public static function getUpgradeStatus($module_name)
  475. {
  476. return (isset(self::$modules_cache[$module_name]) &&
  477. self::$modules_cache[$module_name]['upgrade']['success']);
  478. }
  479. /**
  480. * Delete module from datable
  481. *
  482. * @return boolean result
  483. */
  484. public function uninstall()
  485. {
  486. // Check module installation id validation
  487. if (!Validate::isUnsignedId($this->id))
  488. {
  489. $this->_errors[] = $this->l('The module is not installed.');
  490. return false;
  491. }
  492. // Uninstall overrides
  493. if (!$this->uninstallOverrides())
  494. return false;
  495. // Retrieve hooks used by the module
  496. $sql = 'SELECT `id_hook` FROM `'._DB_PREFIX_.'hook_module` WHERE `id_module` = '.(int)$this->id;
  497. $result = Db::getInstance()->executeS($sql);
  498. foreach ($result as $row)
  499. {
  500. $this->unregisterHook((int)$row['id_hook']);
  501. $this->unregisterExceptions((int)$row['id_hook']);
  502. }
  503. foreach ($this->controllers as $controller)
  504. {
  505. $page_name = 'module-'.$this->name.'-'.$controller;
  506. $meta = Db::getInstance()->getValue('SELECT id_meta FROM `'._DB_PREFIX_.'meta` WHERE page="'.pSQL($page_name).'"');
  507. if ((int)$meta > 0)
  508. {
  509. Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'theme_meta` WHERE id_meta='.(int)$meta);
  510. Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'meta_lang` WHERE id_meta='.(int)$meta);
  511. Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'meta` WHERE id_meta='.(int)$meta);
  512. }
  513. }
  514. // Disable the module for all shops
  515. $this->disable(true);
  516. // Delete permissions module access
  517. Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'module_access` WHERE `id_module` = '.(int)$this->id);
  518. // Remove restrictions for client groups
  519. Group::truncateRestrictionsByModule($this->id);
  520. // Uninstall the module
  521. if (Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'module` WHERE `id_module` = '.(int)$this->id))
  522. {
  523. Cache::clean('Module::isInstalled'.$this->name);
  524. return true;
  525. }
  526. return false;
  527. }
  528. /**
  529. * This function enable module $name. If an $name is an array,
  530. * this will enable all of them
  531. *
  532. * @param array|string $name
  533. * @return true if succeed
  534. * @since 1.4.1
  535. */
  536. public static function enableByName($name)
  537. {
  538. // If $name is not an array, we set it as an array
  539. if (!is_array($name))
  540. $name = array($name);
  541. $res = true;
  542. // Enable each module
  543. foreach ($name as $n)
  544. if (Validate::isModuleName($n))
  545. $res &= Module::getInstanceByName($n)->enable();
  546. return $res;
  547. }
  548. /**
  549. * Activate current module.
  550. *
  551. * @param bool $forceAll If true, enable module for all shop
  552. */
  553. public function enable($forceAll = false)
  554. {
  555. // Retrieve all shops where the module is enabled
  556. $list = Shop::getContextListShopID();
  557. if (!$this->id || !is_array($list))
  558. return false;
  559. $sql = 'SELECT `id_shop` FROM `'._DB_PREFIX_.'module_shop`
  560. WHERE `id_module` = '.(int)$this->id.
  561. ((!$forceAll) ? ' AND `id_shop` IN('.implode(', ', $list).')' : '');
  562. // Store the results in an array
  563. $items = array();
  564. if ($results = Db::getInstance($sql)->executeS($sql))
  565. foreach ($results as $row)
  566. $items[] = $row['id_shop'];
  567. // Enable module in the shop where it is not enabled yet
  568. foreach ($list as $id)
  569. if (!in_array($id, $items))
  570. Db::getInstance()->insert('module_shop', array(
  571. 'id_module' => $this->id,
  572. 'id_shop' => $id,
  573. ));
  574. return true;
  575. }
  576. public function enableDevice($device)
  577. {
  578. Db::getInstance()->execute('
  579. UPDATE '._DB_PREFIX_.'module_shop
  580. SET enable_device = enable_device + '.(int)$device.'
  581. WHERE enable_device &~ '.(int)$device.' AND id_module='.(int)$this->id.
  582. Shop::addSqlRestriction()
  583. );
  584. return true;
  585. }
  586. public function disableDevice($device)
  587. {
  588. Db::getInstance()->execute('
  589. UPDATE '._DB_PREFIX_.'module_shop
  590. SET enable_device = enable_device - '.(int)$device.'
  591. WHERE enable_device & '.(int)$device.' AND id_module='.(int)$this->id.
  592. Shop::addSqlRestriction()
  593. );
  594. return true;
  595. }
  596. /**
  597. * This function disable module $name. If an $name is an array,
  598. * this will disable all of them
  599. *
  600. * @param array|string $name
  601. * @return true if succeed
  602. * @since 1.4.1
  603. */
  604. public static function disableByName($name)
  605. {
  606. // If $name is not an array, we set it as an array
  607. if (!is_array($name))
  608. $name = array($name);
  609. $res = true;
  610. // Disable each module
  611. foreach ($name as $n)
  612. if (Validate::isModuleName($n))
  613. $res &= Module::getInstanceByName($n)->disable();
  614. return $res;
  615. }
  616. /**
  617. * Desactivate current module.
  618. *
  619. * @param bool $forceAll If true, disable module for all shop
  620. */
  621. public function disable($forceAll = false)
  622. {
  623. // Disable module for all shops
  624. $sql = 'DELETE FROM `'._DB_PREFIX_.'module_shop` WHERE `id_module` = '.(int)$this->id.' '.((!$forceAll) ? ' AND `id_shop` IN('.implode(', ', Shop::getContextListShopID()).')' : '');
  625. Db::getInstance()->execute($sql);
  626. }
  627. /**
  628. * Display flags in forms for translations
  629. *
  630. * @param array $languages All languages available
  631. * @param integer $default_language Default language id
  632. * @param string $ids Multilingual div ids in form
  633. * @param string $id Current div id]
  634. * @param boolean $return define the return way : false for a display, true for a return
  635. * @param boolean $use_vars_instead_of_ids use an js vars instead of ids seperate by "造"
  636. */
  637. public function displayFlags($languages, $default_language, $ids, $id, $return = false, $use_vars_instead_of_ids = false)
  638. {
  639. if (count($languages) == 1)
  640. return false;
  641. $output = '
  642. <div class="displayed_flag">
  643. <img src="../img/l/'.$default_language.'.jpg" class="pointer" id="language_current_'.$id.'" onclick="toggleLanguageFlags(this);" alt="" />
  644. </div>
  645. <div id="languages_'.$id.'" class="language_flags">
  646. '.$this->l('Choose language:').'<br /><br />';
  647. foreach ($languages as $language)
  648. if ($use_vars_instead_of_ids)
  649. $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'].'\');" /> ';
  650. else
  651. $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'].'\');" /> ';
  652. $output .= '</div>';
  653. if ($return)
  654. return $output;
  655. echo $output;
  656. }
  657. /**
  658. * Connect module to a hook
  659. *
  660. * @param string $hook_name Hook name
  661. * @param array $shop_list List of shop linked to the hook (if null, link hook to all shops)
  662. * @return boolean result
  663. */
  664. public function registerHook($hook_name, $shop_list = null)
  665. {
  666. $return = true;
  667. if (is_array($hook_name))
  668. $hook_names = $hook_name;
  669. else
  670. $hook_names = array($hook_name);
  671. foreach ($hook_names as $hook_name)
  672. {
  673. // Check hook name validation and if module is installed
  674. if (!Validate::isHookName($hook_name))
  675. throw new PrestaShopException('Invalid hook name');
  676. if (!isset($this->id) || !is_numeric($this->id))
  677. return false;
  678. // Retrocompatibility
  679. $hook_name_bak = $hook_name;
  680. if ($alias = Hook::getRetroHookName($hook_name))
  681. $hook_name = $alias;
  682. Hook::exec('actionModuleRegisterHookBefore', array('object' => $this, 'hook_name' => $hook_name));
  683. // Get hook id
  684. $id_hook = Hook::getIdByName($hook_name);
  685. $live_edit = Hook::getLiveEditById((int)Hook::getIdByName($hook_name_bak));
  686. // If hook does not exist, we create it
  687. if (!$id_hook)
  688. {
  689. $new_hook = new Hook();
  690. $new_hook->name = pSQL($hook_name);
  691. $new_hook->title = pSQL($hook_name);
  692. $new_hook->live_edit = (bool)preg_match('/^display/i', $new_hook->name);
  693. $new_hook->position = (bool)$new_hook->live_edit;
  694. $new_hook->add();
  695. $id_hook = $new_hook->id;
  696. if (!$id_hook)
  697. return false;
  698. }
  699. // If shop lists is null, we fill it with all shops
  700. if (is_null($shop_list))
  701. $shop_list = Shop::getShops(true, null, true);
  702. foreach ($shop_list as $shop_id)
  703. {
  704. // Check if already register
  705. $sql = 'SELECT hm.`id_module`
  706. FROM `'._DB_PREFIX_.'hook_module` hm, `'._DB_PREFIX_.'hook` h
  707. WHERE hm.`id_module` = '.(int)($this->id).' AND h.`id_hook` = '.$id_hook.'
  708. AND h.`id_hook` = hm.`id_hook` AND `id_shop` = '.(int)$shop_id;
  709. if (Db::getInstance()->getRow($sql))
  710. continue;
  711. // Get module position in hook
  712. $sql = 'SELECT MAX(`position`) AS position
  713. FROM `'._DB_PREFIX_.'hook_module`
  714. WHERE `id_hook` = '.(int)$id_hook.' AND `id_shop` = '.(int)$shop_id;
  715. if (!$position = Db::getInstance()->getValue($sql))
  716. $position = 0;
  717. // Register module in hook
  718. $return &= Db::getInstance()->insert('hook_module', array(
  719. 'id_module' => (int)$this->id,
  720. 'id_hook' => (int)$id_hook,
  721. 'id_shop' => (int)$shop_id,
  722. 'position' => (int)($position + 1),
  723. ));
  724. }
  725. Hook::exec('actionModuleRegisterHookAfter', array('object' => $this, 'hook_name' => $hook_name));
  726. }
  727. return $return;
  728. }
  729. /**
  730. * Unregister module from hook
  731. *
  732. * @param mixed $id_hook Hook id (can be a hook name since 1.5.0)
  733. * @param array $shop_list List of shop
  734. * @return boolean result
  735. */
  736. public function unregisterHook($hook_id, $shop_list = null)
  737. {
  738. // Get hook id if a name is given as argument
  739. if (!is_numeric($hook_id))
  740. {
  741. $hook_name = (int)$hook_id;
  742. // Retrocompatibility
  743. $hook_id = Hook::getIdByName($hook_id);
  744. if (!$hook_id)
  745. return false;
  746. }
  747. else
  748. $hook_name = Hook::getNameById((int)$hook_id);
  749. Hook::exec('actionModuleUnRegisterHookBefore', array('object' => $this, 'hook_name' => $hook_name));
  750. // Unregister module on hook by id
  751. $sql = 'DELETE FROM `'._DB_PREFIX_.'hook_module`
  752. WHERE `id_module` = '.(int)$this->id.' AND `id_hook` = '.(int)$hook_id
  753. .(($shop_list) ? ' AND `id_shop` IN('.implode(', ', array_map('intval', $shop_list)).')' : '');
  754. $result = Db::getInstance()->execute($sql);
  755. // Clean modules position
  756. $this->cleanPositions($hook_id, $shop_list);
  757. Hook::exec('actionModuleUnRegisterHookAfter', array('object' => $this, 'hook_name' => $hook_name));
  758. return $result;
  759. }
  760. /**
  761. * Unregister exceptions linked to module
  762. *
  763. * @param int $id_hook Hook id
  764. * @param array $shop_list List of shop
  765. * @return boolean result
  766. */
  767. public function unregisterExceptions($hook_id, $shop_list = null)
  768. {
  769. $sql = 'DELETE FROM `'._DB_PREFIX_.'hook_module_exceptions`
  770. WHERE `id_module` = '.(int)$this->id.' AND `id_hook` = '.(int)$hook_id
  771. .(($shop_list) ? ' AND `id_shop` IN('.implode(', ', array_map('intval', $shop_list)).')' : '');
  772. return Db::getInstance()->execute($sql);
  773. }
  774. /**
  775. * Add exceptions for module->Hook
  776. *
  777. * @param int $id_hook Hook id
  778. * @param array $excepts List of file name
  779. * @param array $shop_list List of shop
  780. * @return boolean result
  781. */
  782. public function registerExceptions($id_hook, $excepts, $shop_list = null)
  783. {
  784. // If shop lists is null, we fill it with all shops
  785. if (is_null($shop_list))
  786. $shop_list = Shop::getContextListShopID();
  787. // Save modules exception for each shop
  788. foreach ($shop_list as $shop_id)
  789. {
  790. foreach ($excepts as $except)
  791. {
  792. if (!$except)
  793. continue;
  794. $insertException = array(
  795. 'id_module' => (int)$this->id,
  796. 'id_hook' => (int)$id_hook,
  797. 'id_shop' => (int)$shop_id,
  798. 'file_name' => pSQL($except),
  799. );
  800. $result = Db::getInstance()->insert('hook_module_exceptions', $insertException);
  801. if (!$result)
  802. return false;
  803. }
  804. }
  805. return true;
  806. }
  807. /**
  808. * Edit exceptions for module->Hook
  809. *
  810. * @param int $hookID Hook id
  811. * @param array $excepts List of shopID and file name
  812. * @return boolean result
  813. */
  814. public function editExceptions($id_hook, $excepts)
  815. {
  816. $result = true;
  817. foreach ($excepts as $shop_id => $except)
  818. {
  819. $shop_list = ($shop_id == 0) ? Shop::getContextListShopID() : array($shop_id);
  820. $this->unregisterExceptions($id_hook, $shop_list);
  821. $result &= $this->registerExceptions($id_hook, $except, $shop_list);
  822. }
  823. return $result;
  824. }
  825. /**
  826. * This function is used to determine the module name
  827. * of an AdminTab which belongs to a module, in order to keep translation
  828. * related to a module in its directory (instead of $_LANGADM)
  829. *
  830. * @param mixed $currentClass the
  831. * @return boolean|string if the class belongs to a module, will return the module name. Otherwise, return false.
  832. */
  833. public static function getModuleNameFromClass($currentClass)
  834. {
  835. // Module can now define AdminTab keeping the module translations method,
  836. // i.e. in modules/[module name]/[iso_code].php
  837. if (!isset(self::$classInModule[$currentClass]) && class_exists($currentClass))
  838. {
  839. global $_MODULES;
  840. $_MODULE = array();
  841. $reflectionClass = new ReflectionClass($currentClass);
  842. $filePath = realpath($reflectionClass->getFileName());
  843. $realpathModuleDir = realpath(_PS_MODULE_DIR_);
  844. if (substr(realpath($filePath), 0, strlen($realpathModuleDir)) == $realpathModuleDir)
  845. {
  846. // For controllers in module/controllers path
  847. if (basename(dirname(dirname($filePath))) == 'controllers')
  848. self::$classInModule[$currentClass] = basename(dirname(dirname(dirname($filePath))));
  849. // For old AdminTab controllers
  850. else
  851. self::$classInModule[$currentClass] = substr(dirname($filePath), strlen($realpathModuleDir) + 1);
  852. $file = _PS_MODULE_DIR_.self::$classInModule[$currentClass].'/'.Context::getContext()->language->iso_code.'.php';
  853. if (Tools::file_exists_cache($file) && include_once($file))
  854. $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
  855. }
  856. else
  857. self::$classInModule[$currentClass] = false;
  858. }
  859. // return name of the module, or false
  860. return self::$classInModule[$currentClass];
  861. }
  862. /**
  863. * Return an instance of the specified module
  864. *
  865. * @param string $module_name Module name
  866. * @return Module
  867. */
  868. public static function getInstanceByName($module_name)
  869. {
  870. if (!Validate::isModuleName($module_name))
  871. {
  872. if (_PS_MODE_DEV_)
  873. die(Tools::displayError(Tools::safeOutput($module_name).' is not a valid module name.'));
  874. return false;
  875. }
  876. if (!isset(self::$_INSTANCE[$module_name]))
  877. {
  878. if (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/'.$module_name.'.php'))
  879. {
  880. include_once(_PS_MODULE_DIR_.$module_name.'/'.$module_name.'.php');
  881. if (class_exists($module_name, false))
  882. return self::$_INSTANCE[$module_name] = new $module_name;
  883. }
  884. return false;
  885. }
  886. return self::$_INSTANCE[$module_name];
  887. }
  888. /**
  889. * Return an instance of the specified module
  890. *
  891. * @param integer $id_module Module ID
  892. * @return Module instance
  893. */
  894. public static function getInstanceById($id_module)
  895. {
  896. static $id2name = null;
  897. if (is_null($id2name))
  898. {
  899. $id2name = array();
  900. $sql = 'SELECT `id_module`, `name` FROM `'._DB_PREFIX_.'module`';
  901. if ($results = Db::getInstance()->executeS($sql))
  902. foreach ($results as $row)
  903. $id2name[$row['id_module']] = $row['name'];
  904. }
  905. if (isset($id2name[$id_module]))
  906. return Module::getInstanceByName($id2name[$id_module]);
  907. return false;
  908. }
  909. public static function configXmlStringFormat($string)
  910. {
  911. return Tools::htmlentitiesDecodeUTF8($string);
  912. }
  913. public static function getModuleName($module)
  914. {
  915. $iso = substr(Context::getContext()->language->iso_code, 0, 2);
  916. // Config file
  917. $configFile = _PS_MODULE_DIR_.$module.'/config_'.$iso.'.xml';
  918. // For "en" iso code, we keep the default config.xml name
  919. if ($iso == 'en' || !file_exists($configFile))
  920. {
  921. $configFile = _PS_MODULE_DIR_.$module.'/config.xml';
  922. if (!file_exists($configFile))
  923. return 'Module '.ucfirst($module);
  924. }
  925. // Load config.xml
  926. libxml_use_internal_errors(true);
  927. $xml_module = simplexml_load_file($configFile);
  928. foreach (libxml_get_errors() as $error)
  929. {
  930. libxml_clear_errors();
  931. return 'Module '.ucfirst($module);
  932. }
  933. libxml_clear_errors();
  934. // Find translations
  935. global $_MODULES;
  936. $file = _PS_MODULE_DIR_.$module.'/'.Context::getContext()->language->iso_code.'.php';
  937. if (Tools::file_exists_cache($file) && include_once($file))
  938. if (isset($_MODULE) && is_array($_MODULE))
  939. $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
  940. // Return Name
  941. return Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->displayName), (string)$xml_module->name);
  942. }
  943. protected static function useTooMuchMemory()
  944. {
  945. $memory_limit = Tools::getMemoryLimit();
  946. if (function_exists('memory_get_usage') && $memory_limit != '-1')
  947. {
  948. $current_memory = memory_get_usage(true);
  949. $memory_threshold = (int)max($memory_limit * 0.15, Tools::isX86_64arch() ? 4194304 : 2097152);
  950. $memory_left = $memory_limit - $current_memory;
  951. if ($memory_left <= $memory_threshold)
  952. return true;
  953. }
  954. return false;
  955. }
  956. /**
  957. * Return available modules
  958. *
  959. * @param boolean $useConfig in order to use config.xml file in module dir
  960. * @return array Modules
  961. */
  962. public static function getModulesOnDisk($useConfig = false, $loggedOnAddons = false, $id_employee = false)
  963. {
  964. global $_MODULES;
  965. // Init var
  966. $module_list = array();
  967. $module_name_list = array();
  968. $modulesNameToCursor = array();
  969. $errors = array();
  970. // Get modules directory list and memory limit
  971. $modules_dir = Module::getModulesDirOnDisk();
  972. $modules_installed = array();
  973. $result = Db::getInstance()->executeS('
  974. SELECT m.name, m.version, mp.interest, module_shop.enable_device
  975. FROM `'._DB_PREFIX_.'module` m
  976. '.Shop::addSqlAssociation('module', 'm').'
  977. LEFT JOIN `'._DB_PREFIX_.'module_preference` mp ON (mp.`module` = m.`name` AND mp.`id_employee` = '.(int)$id_employee.')');
  978. foreach ($result as $row)
  979. $modules_installed[$row['name']] = $row;
  980. foreach ($modules_dir as $module)
  981. {
  982. if (Module::useTooMuchMemory())
  983. {
  984. $errors[] = Tools::displayError('All modules cannot be loaded due to memory limit restrictions, please increase your memory_limit value on your server configuration');
  985. break;
  986. }
  987. $iso = substr(Context::getContext()->language->iso_code, 0, 2);
  988. // Check if config.xml module file exists and if it's not outdated
  989. if ($iso == 'en')
  990. $configFile = _PS_MODULE_DIR_.$module.'/config.xml';
  991. else
  992. $configFile = _PS_MODULE_DIR_.$module.'/config_'.$iso.'.xml';
  993. $xml_exist = (file_exists($configFile));
  994. $needNewConfigFile = $xml_exist ? (@filemtime($configFile) < @filemtime(_PS_MODULE_DIR_.$module.'/'.$module.'.php')) : true;
  995. // If config.xml exists and that the use config flag is at true
  996. if ($useConfig && $xml_exist && !$needNewConfigFile)
  997. {
  998. // Load config.xml
  999. libxml_use_internal_errors(true);
  1000. $xml_module = simplexml_load_file($configFile);
  1001. foreach (libxml_get_errors() as $error)
  1002. $errors[] = '['.$module.'] '.Tools::displayError('Error found in config file:').' '.htmlentities($error->message);
  1003. libxml_clear_errors();
  1004. // If no errors in Xml, no need instand and no need new config.xml file, we load only translations
  1005. if (!count($errors) && (int)$xml_module->need_instance == 0)
  1006. {
  1007. $file = _PS_MODULE_DIR_.$module.'/'.Context::getContext()->language->iso_code.'.php';
  1008. if (Tools::file_exists_cache($file) && include_once($file))
  1009. if (isset($_MODULE) && is_array($_MODULE))
  1010. $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
  1011. $item = new stdClass();
  1012. $item->id = 0;
  1013. $item->warning = '';
  1014. foreach ($xml_module as $k => $v)
  1015. $item->$k = (string)$v;
  1016. $item->displayName = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->displayName), (string)$xml_module->name));
  1017. $item->description = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->description), (string)$xml_module->name));
  1018. $item->author = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->author), (string)$xml_module->name));
  1019. if (isset($xml_module->confirmUninstall))
  1020. $item->confirmUninstall = Translate::getModuleTranslation((string)$xml_module->name, html_entity_decode(Module::configXmlStringFormat($xml_module->confirmUninstall)), (string)$xml_module->name);
  1021. $item->active = 0;
  1022. $item->onclick_option = false;
  1023. $item->trusted = Module::isModuleTrusted($item->name);
  1024. $module_list[] = $item;
  1025. $module_name_list[] = '\''.pSQL($item->name).'\'';
  1026. $modulesNameToCursor[strval($item->name)] = $item;
  1027. }
  1028. }
  1029. // If use config flag is at false or config.xml does not exist OR need instance OR need a new config.xml file
  1030. if (!$useConfig || !$xml_exist || (isset($xml_module->need_instance) && (int)$xml_module->need_instance == 1) || $needNewConfigFile)
  1031. {
  1032. // If class does not exists, we include the file
  1033. if (!class_exists($module, false))
  1034. {
  1035. // Get content from php file
  1036. $filepath = _PS_MODULE_DIR_.$module.'/'.$module.'.php';
  1037. $file = trim(file_get_contents(_PS_MODULE_DIR_.$module.'/'.$module.'.php'));
  1038. if (substr($file, 0, 5) == '<?php')
  1039. $file = substr($file, 5);
  1040. if (substr($file, -2) == '?>')
  1041. $file = substr($file, 0, -2);
  1042. // If (false) is a trick to not load the class with "eval".
  1043. // This way require_once will works correctly
  1044. if (eval('if (false){ '.$file.' }') !== false)
  1045. require_once( _PS_MODULE_DIR_.$module.'/'.$module.'.php' );
  1046. else
  1047. $errors[] = sprintf(Tools::displayError('%1$s (parse error in %2$s)'), $module, substr($filepath, strlen(_PS_ROOT_DIR_)));
  1048. }
  1049. // If class exists, we just instanciate it
  1050. if (class_exists($module, false))
  1051. {
  1052. $tmp_module = new $module;
  1053. $item = new stdClass();
  1054. $item->id = $tmp_module->id;
  1055. $item->warning = $tmp_module->warning;
  1056. $item->name = $tmp_module->name;
  1057. $item->version = $tmp_module->version;
  1058. $item->tab = $tmp_module->tab;
  1059. $item->displayName = $tmp_module->displayName;
  1060. $item->description = stripslashes($tmp_module->description);
  1061. $item->author = $tmp_module->author;
  1062. $item->limited_countries = $tmp_module->limited_countries;
  1063. $item->parent_class = get_parent_class($module);
  1064. $item->is_configurable = $tmp_module->is_configurable = method_exists($tmp_module, 'getContent') ? 1 : 0;
  1065. $item->need_instance = isset($tmp_module->need_instance) ? $tmp_module->need_instance : 0;
  1066. $item->active = $tmp_module->active;
  1067. $item->trusted = Module::isModuleTrusted($tmp_module->name);
  1068. $item->currencies = isset($tmp_module->currencies) ? $tmp_module->currencies : null;
  1069. $item->currencies_mode = isset($tmp_module->currencies_mode) ? $tmp_module->currencies_mode : null;
  1070. $item->confirmUninstall = isset($tmp_module->confirmUninstall) ? html_entity_decode($tmp_module->confirmUninstall) : null;
  1071. $item->description_full = stripslashes($tmp_module->description_full);
  1072. $item->additional_description = isset($tmp_module->additional_description) ? stripslashes($tmp_module->additional_description) : null;
  1073. $item->compatibility = isset($tmp_module->compatibility) ? (array)$tmp_module->compatibility : null;
  1074. $item->nb_rates = isset($tmp_module->nb_rates) ? (array)$tmp_module->nb_rates : null;
  1075. $item->avg_rate = isset($tmp_module->avg_rate) ? (array)$tmp_module->avg_rate : null;
  1076. $item->badges = isset($tmp_module->badges) ? (array)$tmp_module->badges : null;
  1077. $item->url = isset($tmp_module->url) ? $tmp_module->url : null;
  1078. $item->onclick_option = method_exists($module, 'onclickOption') ? true : false;
  1079. if ($item->onclick_option)
  1080. {
  1081. $href = Context::getContext()->link->getAdminLink('Module', true).'&module_name='.$tmp_module->name.'&tab_module='.$tmp_module->tab;
  1082. $item->onclick_option_content = array();
  1083. $option_tab = array('desactive', 'reset', 'configure', 'delete');
  1084. foreach ($option_tab as $opt)
  1085. $item->onclick_option_content[$opt] = $tmp_module->onclickOption($opt, $href);
  1086. }
  1087. $module_list[] = $item;
  1088. if (!$xml_exist || $needNewConfigFile)
  1089. {
  1090. self::$_generate_config_xml_mode = true;
  1091. $tmp_module->_generateConfigXml();
  1092. self::$_generate_config_xml_mode = false;
  1093. }
  1094. unset($tmp_module);
  1095. }
  1096. else
  1097. $errors[] = sprintf(Tools::displayError('%1$s (class missing in %2$s)'), $module, substr($filepath, strlen(_PS_ROOT_DIR_)));
  1098. }
  1099. }
  1100. // Get modules information from database
  1101. if (!empty($module_name_list))
  1102. {
  1103. $list = Shop::getContextListShopID();
  1104. $sql = 'SELECT m.id_module, m.name, (
  1105. SELECT COUNT(*) FROM '._DB_PREFIX_.'module_shop ms WHERE m.id_module = ms.id_module AND ms.id_shop IN ('.implode(',', $list).')
  1106. ) as total
  1107. FROM '._DB_PREFIX_.'module m
  1108. WHERE m.name IN ('.implode(',', $module_name_list).')';
  1109. $results = Db::getInstance()->executeS($sql);
  1110. foreach ($results as $result)
  1111. {
  1112. $moduleCursor = $modulesNameToCursor[$result['name']];
  1113. $moduleCursor->id = $result['id_module'];
  1114. $moduleCursor->active = ($result['total'] == count($list)) ? 1 : 0;
  1115. }
  1116. }
  1117. // Get Default Country Modules and customer module
  1118. $files_list = array(
  1119. array('type' => 'addonsNative', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST, 'loggedOnAddons' => 0),
  1120. array('type' => 'addonsBought', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_CUSTOMER_MODULES_LIST, 'loggedOnAddons' => 1),
  1121. array('type' => 'addonsMustHave', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_MUST_HAVE_MODULES_LIST, 'loggedOnAddons' => 0),
  1122. );
  1123. foreach ($files_list as $f)
  1124. if (file_exists($f['file']) && ($f['loggedOnAddons'] == 0 || $loggedOnAddons))
  1125. {
  1126. if (Module::useTooMuchMemory())
  1127. {
  1128. $errors[] = Tools::displayError('All modules cannot be loaded due to memory limit restrictions, please increase your memory_limit value on your server configuration');
  1129. break;
  1130. }
  1131. $file = $f['file'];
  1132. $content = Tools::file_get_contents($file);
  1133. $xml = @simplexml_load_string($content, null, LIBXML_NOCDATA);
  1134. if ($xml && isset($xml->module))
  1135. foreach ($xml->module as $modaddons)
  1136. {
  1137. $flag_found = 0;
  1138. foreach ($module_list as $k => &$m)
  1139. if ($m->name == $modaddons->name && !isset($m->available_on_addons))
  1140. {
  1141. $flag_found = 1;
  1142. if ($m->version != $modaddons->version && version_compare($m->version, $modaddons->version) === -1)
  1143. $module_list[$k]->version_addons = $modaddons->version;
  1144. }
  1145. if ($flag_found == 0)
  1146. {
  1147. $item = new stdClass();
  1148. $item->id = 0;
  1149. $item->warning = '';
  1150. $item->type = strip_tags((string)$f['type']);
  1151. $item->name = strip_tags((string)$modaddons->name);
  1152. $item->version = strip_tags((string)$modaddons->version);
  1153. $item->tab = strip_tags((string)$modaddons->tab);
  1154. $item->displayName = strip_tags((string)$modaddons->displayName);
  1155. $item->description = stripslashes(strip_tags((string)$modaddons->description));
  1156. $item->description_full = stripslashes(strip_tags((string)$modaddons->description_full));
  1157. $item->author = strip_tags((string)$modaddons->author);
  1158. $item->limited_countries = array();
  1159. $item->parent_class = '';
  1160. $item->onclick_option = false;
  1161. $item->is_configurable = 0;
  1162. $item->need_instance = 0;
  1163. $item->not_on_disk = 1;
  1164. $item->available_on_addons = 1;
  1165. $item->trusted = Module::isModuleTrusted($item->name);
  1166. $item->active = 0;
  1167. $item->description_full = stripslashes($modaddons->description_full);
  1168. $item->additional_description = isset($modaddons->additional_description) ? stripslashes($modaddons->additional_description) : null;
  1169. $item->compatibility = isset($modaddons->compatibility) ? (array)$modaddons->compatibility : null;
  1170. $item->nb_rates = isset($modaddons->nb_rates) ? (array)$modaddons->nb_rates : null;
  1171. $item->avg_rate = isset($modaddons->avg_rate) ? (array)$modaddons->avg_rate : null;
  1172. $item->badges = isset($modaddons->badges) ? (array)$modaddons->badges : null;
  1173. $item->url = isset($modaddons->url) ? $modaddons->url : null;
  1174. if (isset($modaddons->img))
  1175. {
  1176. if (!file_exists(_PS_TMP_IMG_DIR_.md5($modaddons->name).'.jpg'))
  1177. if (!file_put_contents(_PS_TMP_IMG_DIR_.md5($modaddons->name).'.jpg', Tools::file_get_contents($modaddons->img)))
  1178. copy(_PS_IMG_DIR_.'404.gif', _PS_TMP_IMG_DIR_.md5($modaddons->name).'.jpg');
  1179. if (file_exists(_PS_TMP_IMG_DIR_.md5($modaddons->name).'.jpg'))
  1180. $item->image = '../img/tmp/'.md5($modaddons->name).'.jpg';
  1181. }
  1182. if ($item->type == 'addonsMustHave')
  1183. {
  1184. $item->addons_buy_url = strip_tags((string)$modaddons->url);
  1185. $prices = (array)$modaddons->price;
  1186. $id_default_currency = Configuration::get('PS_CURRENCY_DEFAULT');
  1187. foreach ($prices as $currency => $price)
  1188. if ($id_currency = Currency::getIdByIsoCode($currency))
  1189. {
  1190. $item->price = (float)$price;
  1191. $item->id_currency = (int)$id_currency;
  1192. if ($id_default_currency == $id_currency)
  1193. break;
  1194. }
  1195. }
  1196. $module_list[] = $item;
  1197. }
  1198. }
  1199. }
  1200. foreach ($module_list as $key => &$module)
  1201. if (defined('_PS_HOST_MODE_') && in_array($module->name, self::$hosted_modules_blacklist))
  1202. unset($module_list[$key]);
  1203. elseif (isset($modules_installed[$module->name]))
  1204. {
  1205. $module->installed = true;
  1206. $module->database_version = $modules_installed[$module->name]['version'];
  1207. $module->interest = $modules_installed[$module->name]['interest'];
  1208. $module->enable_device = $modules_installed[$module->name]['enable_device'];
  1209. }
  1210. else
  1211. {
  1212. $module->installed = false;
  1213. $module->database_version = 0;
  1214. $module->interest = 0;
  1215. }
  1216. usort($module_list, create_function('$a,$b', 'return strnatcasecmp($a->displayName, $b->displayName);'));
  1217. if ($errors)
  1218. {
  1219. if (!isset(Context::getContext()->controller) && !Context::getContext()->controller->controller_name)
  1220. {
  1221. echo '<div class="alert error"><h3>'.Tools::displayError('The following module(s) could not be loaded').':</h3><ol>';
  1222. foreach ($errors as $error)
  1223. echo '<li>'.$error.'</li>';
  1224. echo '</ol></div>';
  1225. }
  1226. else
  1227. foreach ($errors as $error)
  1228. Context::getContext()->controller->errors[] = $error;
  1229. }
  1230. return $module_list;
  1231. }
  1232. /**
  1233. * Return modules directory list
  1234. *
  1235. * @return array Modules Directory List
  1236. */
  1237. public static function getModulesDirOnDisk()
  1238. {
  1239. $module_list = array();
  1240. $modules = scandir(_PS_MODULE_DIR_);
  1241. foreach ($modules as $name)
  1242. {
  1243. if (is_file(_PS_MODULE_DIR_.$name))
  1244. continue;
  1245. elseif (is_dir(_PS_MODULE_DIR_.$name.DIRECTORY_SEPARATOR) && Tools::file_exists_cache(_PS_MODULE_DIR_.$name.'/'.$name.'.php'))
  1246. {
  1247. if (!Validate::isModuleName($name))
  1248. throw new PrestaShopException(sprintf('Module %s is not a valid module name', $name));
  1249. $module_list[] = $name;
  1250. }
  1251. }
  1252. return $module_list;
  1253. }
  1254. /**
  1255. * Return non native module
  1256. *
  1257. * @param int $position Take only positionnables modules
  1258. * @return array Modules
  1259. */
  1260. public static function getNonNativeModuleList()
  1261. {
  1262. $db = Db::getInstance();
  1263. $module_list_xml = _PS_ROOT_DIR_.self::CACHE_FILE_MODULES_LIST;
  1264. $native_modules = simplexml_load_file($module_list_xml);
  1265. $native_modules = $native_modules->modules;
  1266. foreach ($native_modules as $native_modules_type)
  1267. if (in_array($native_modules_type['type'], array('native', 'partner')))
  1268. {
  1269. $arr_native_modules[] = '""';
  1270. foreach ($native_modules_type->module as $module)
  1271. $arr_native_modules[] = '"'.pSQL($module['name']).'"';
  1272. }
  1273. return $db->executeS('SELECT * FROM `'._DB_PREFIX_.'module` m WHERE `name` NOT IN ('.implode(',', $arr_native_modules).') ');
  1274. }
  1275. public static function getNativeModuleList()
  1276. {
  1277. $module_list_xml = _PS_ROOT_DIR_.self::CACHE_FILE_MODULES_LIST;
  1278. if (!file_exists($module_list_xml))
  1279. return false;
  1280. $native_modules = simplexml_load_file($module_list_xml);
  1281. $native_modules = $native_modules->modules;
  1282. $modules = array();
  1283. foreach ($native_modules as $native_modules_type)
  1284. if (in_array($native_modules_type['type'], array('native', 'partner')))
  1285. {
  1286. foreach ($native_modules_type->module as $module)
  1287. $modules[] = $module['name'];
  1288. }
  1289. return $modules;
  1290. }
  1291. /**
  1292. * Return installed modules
  1293. *
  1294. * @param int $position Take only positionnables modules
  1295. * @return array Modules
  1296. */
  1297. public static function getModulesInstalled($position = 0)
  1298. {
  1299. $sql = 'SELECT m.* FROM `'._DB_PREFIX_.'module` m ';
  1300. if ($position)
  1301. $sql .= 'LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON m.`id_module` = hm.`id_module`
  1302. LEFT JOIN `'._DB_PREFIX_.'hook` k ON hm.`id_hook` = k.`id_hook`
  1303. WHERE k.`position` = 1
  1304. GROUP BY m.id_module';
  1305. return Db::getInstance()->executeS($sql);
  1306. }
  1307. /**
  1308. * Return if the module is provided by addons.prestashop.com or not
  1309. *
  1310. * @param string $name The module name (the folder name)
  1311. * @param string $key The key provided by addons
  1312. * @return boolean
  1313. */
  1314. public static function isModuleTrusted($module_name)
  1315. {
  1316. $context = Context::getContext();
  1317. $theme = new Theme($context->shop->id_theme);
  1318. // If the xml file exist, isn't empty, isn't too old
  1319. // and if the theme hadn't change
  1320. // we use the file, otherwise we regenerate it
  1321. if (!(file_exists(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST)
  1322. && filesize(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST) > 0
  1323. && ((time() - filemtime(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST)) < 86400)
  1324. && strstr(Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST), $theme->name)))
  1325. self::generateTrustedXml();
  1326. if (strstr(Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST), $module_name))
  1327. return true;
  1328. elseif (strstr(Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_UNTRUSTED_MODULES_LIST), $module_name))
  1329. return false;
  1330. else
  1331. {
  1332. // If the module isn't in one of the xml files
  1333. // It might have been uploaded recenlty so we check
  1334. // Addons API and clear XML files to be regenerated next time
  1335. Tools::deleteFile(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST);
  1336. Tools::deleteFile(_PS_ROOT_DIR_.self::CACHE_FILE_UNTRUSTED_MODULES_LIST);
  1337. return Module::checkModuleFromAddonsApi($module_name);
  1338. }
  1339. }
  1340. /**
  1341. * Generate XML files for trusted and untrusted modules
  1342. *
  1343. */
  1344. public static function generateTrustedXml()
  1345. {
  1346. $modules_on_disk = Module::getModulesDirOnDisk();
  1347. $trusted = array();
  1348. $untrusted = array();
  1349. $trusted_modules_xml = array(
  1350. _PS_ROOT_DIR_.self::CACHE_FILE_ALL_COUNTRY_MODULES_LIST,
  1351. _PS_ROOT_DIR_.self::CACHE_FILE_MUST_HAVE_MODULES_LIST,
  1352. );
  1353. // Create 2 arrays with trusted and untrusted modules
  1354. foreach ($trusted_modules_xml as $file)
  1355. {
  1356. $content = Tools::file_get_contents($file);
  1357. $xml = @simplexml_load_string($content, null, LIBXML_NOCDATA);
  1358. if ($xml && isset($xml->module))
  1359. foreach ($xml->module as $modaddons)
  1360. $trusted[] = (string)$modaddons->name;
  1361. }
  1362. foreach (glob(_PS_ROOT_DIR_.'/config/xml/themes/*.xml') as $theme_xml)
  1363. if(file_exists($theme_xml))
  1364. {
  1365. $content = Tools::file_get_contents($theme_xml);
  1366. $xml = @simplexml_load_string($content, null, LIBXML_NOCDATA);
  1367. foreach ($xml->modules->module as $modaddons)
  1368. if((string)$modaddons['action'] == 'install')
  1369. $trusted[] = (string)$modaddons['name'];
  1370. }
  1371. foreach ($modules_on_disk as $name)
  1372. {
  1373. if (!in_array($name, $trusted))
  1374. {
  1375. if (Module::checkModuleFromAddonsApi($name))
  1376. $trusted[] = $name;
  1377. else
  1378. $untrusted[] = $name;
  1379. }
  1380. }
  1381. $context = Context::getContext();
  1382. $theme = new Theme($context->shop->id_theme);
  1383. // Save the 2 arrays into XML files
  1384. $trusted_xml = new SimpleXMLElement('<modules_list/>');
  1385. $trusted_xml->addAttribute('theme', $theme->name);
  1386. $modules = $trusted_xml->addChild('modules');
  1387. $modules->addAttribute('type', 'trusted');
  1388. foreach ($trusted as $key => $name)
  1389. {
  1390. $module = $modules->addChild('module');
  1391. $module->addAttribute('name', $name);
  1392. }
  1393. $success = file_put_contents( _PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST, $trusted_xml->asXML());
  1394. $untrusted_xml = new SimpleXMLElement('<modules_list/>');
  1395. $modules = $untrusted_xml->addChild('modules');
  1396. $modules->addAttribute('type', 'untrusted');
  1397. foreach ($untrusted as $key => $name)
  1398. {
  1399. $module = $modules->addChild('module');
  1400. $module->addAttribute('name', $name);
  1401. }
  1402. $success &= file_put_contents( _PS_ROOT_DIR_.self::CACHE_FILE_UNTRUSTED_MODULES_LIST, $untrusted_xml->asXML());
  1403. if ($success)
  1404. return true;
  1405. else
  1406. Tools::displayError('Trusted and Untrusted XML have not been generated properly');
  1407. }
  1408. /**
  1409. * Create the Addons API call from the module name only
  1410. *
  1411. * @param string $name Module dir name
  1412. * @return boolean Returns if the module is trusted by addons.prestashop.com
  1413. */
  1414. public static function checkModuleFromAddonsApi($module_name)
  1415. {
  1416. $obj = Module::getInstanceByName($module_name);
  1417. if (!is_object($obj))
  1418. return false;
  1419. elseif ($obj->module_key === '')
  1420. return false;
  1421. else
  1422. {
  1423. $params = array(
  1424. 'module_name' => $obj->name,
  1425. 'module_key' => $obj->module_key,
  1426. );
  1427. $xml = Tools::addonsRequest('check_module', $params);
  1428. return (stristr($xml, 'success'));
  1429. }
  1430. }
  1431. /**
  1432. * Execute modules for specified hook
  1433. *
  1434. * @param string $hook_name Hook Name
  1435. * @param array $hookArgs Parameters for the functions
  1436. * @return string modules output
  1437. */
  1438. public static function hookExec($hook_name, $hookArgs = array(), $id_module = null)
  1439. {
  1440. Tools::displayAsDeprecated();
  1441. return Hook::exec($hook_name, $hookArgs, $id_module);
  1442. }
  1443. public static function hookExecPayment()
  1444. {
  1445. Tools::displayAsDeprecated();
  1446. return Hook::exec('displayPayment');
  1447. }
  1448. public static function preCall($module_name)
  1449. {
  1450. return true;
  1451. }
  1452. /*
  1453. @deprecated since 1.6.0.2
  1454. */
  1455. public static function getPaypalIgnore()
  1456. {
  1457. Tools::displayAsDeprecated();
  1458. }
  1459. /**
  1460. * Returns the list of the payment module associated to the current customer
  1461. * @see PaymentModule::getInstalledPaymentModules() if you don't care about the context
  1462. *
  1463. * @return array module informations
  1464. */
  1465. public static function getPaymentModules()
  1466. {
  1467. $context = Context::getContext();
  1468. if (isset($context->cart))
  1469. $billing = new Address((int)$context->cart->id_address_invoice);
  1470. $use_groups = Group::isFeatureActive();
  1471. $frontend = true;
  1472. $groups = array();
  1473. if (isset($context->employee))
  1474. $frontend = false;
  1475. elseif (isset($context->customer) && $use_groups)
  1476. {
  1477. $groups = $context->customer->getGroups();
  1478. if (!count($groups))
  1479. $groups = array(Configuration::get('PS_UNIDENTIFIED_GROUP'));
  1480. }
  1481. $hookPayment = 'Payment';
  1482. if (Db::getInstance()->getValue('SELECT `id_hook` FROM `'._DB_PREFIX_.'hook` WHERE `name` = \'displayPayment\''))
  1483. $hookPayment = 'displayPayment';
  1484. $list = Shop::getContextListShopID();
  1485. return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT DISTINCT m.`id_module`, h.`id_hook`, m.`name`, hm.`position`
  1486. FROM `'._DB_PREFIX_.'module` m
  1487. '.($frontend ? 'LEFT JOIN `'._DB_PREFIX_.'module_country` mc ON (m.`id_module` = mc.`id_module` AND mc.id_shop = '.(int)$context->shop->id.')' : '').'
  1488. '.($frontend && $use_groups ? 'INNER JOIN `'._DB_PREFIX_.'module_group` mg ON (m.`id_module` = mg.`id_module` AND mg.id_shop = '.(int)$context->shop->id.')' : '').'
  1489. '.($frontend && isset($context->customer) && $use_groups ? 'INNER JOIN `'._DB_PREFIX_.'customer_group` cg on (cg.`id_group` = mg.`id_group`AND cg.`id_customer` = '.(int)$context->customer->id.')' : '').'
  1490. LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON hm.`id_module` = m.`id_module`
  1491. LEFT JOIN `'._DB_PREFIX_.'hook` h ON hm.`id_hook` = h.`id_hook`
  1492. WHERE h.`name` = \''.pSQL($hookPayment).'\'
  1493. '.(isset($billing) && $frontend ? 'AND mc.id_country = '.(int)$billing->id_country : '').'
  1494. 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).'
  1495. AND hm.id_shop IN('.implode(', ', $list).')
  1496. '.((count($groups) && $frontend && $use_groups) ? 'AND (mg.`id_group` IN ('.implode(', ', $groups).'))' : '').'
  1497. GROUP BY hm.id_hook, hm.id_module
  1498. ORDER BY hm.`position`, m.`name` DESC');
  1499. }
  1500. /**
  1501. * @deprecated 1.5.0 Use Translate::getModuleTranslation()
  1502. */
  1503. public static function findTranslation($name, $string, $source)
  1504. {
  1505. return Translate::getModuleTranslation($name, $string, $source);
  1506. }
  1507. /**
  1508. * Get translation for a given module text
  1509. *
  1510. * Note: $specific parameter is mandatory for library files.
  1511. * Otherwise, translation key will not match for Module library
  1512. * when module is loaded with eval() Module::getModulesOnDisk()
  1513. *
  1514. * @param string $string String to translate
  1515. * @param boolean|string $specific filename to use in translation key
  1516. * @return string Translation
  1517. */
  1518. public function l($string, $specific = false)
  1519. {
  1520. if (self::$_generate_config_xml_mode)
  1521. return $string;
  1522. return Translate::getModuleTranslation($this, $string, ($specific) ? $specific : $this->name);
  1523. }
  1524. /*
  1525. * Reposition module
  1526. *
  1527. * @param boolean $id_hook Hook ID
  1528. * @param boolean $way Up (0) or Down (1)
  1529. * @param int $position
  1530. */
  1531. public function updatePosition($id_hook, $way, $position = null)
  1532. {
  1533. foreach (Shop::getContextListShopID() as $shop_id)
  1534. {
  1535. $sql = 'SELECT hm.`id_module`, hm.`position`, hm.`id_hook`
  1536. FROM `'._DB_PREFIX_.'hook_module` hm
  1537. WHERE hm.`id_hook` = '.(int)$id_hook.' AND hm.`id_shop` = '.$shop_id.'
  1538. ORDER BY hm.`position` '.($way ? 'ASC' : 'DESC');
  1539. if (!$res = Db::getInstance()->executeS($sql))
  1540. continue;
  1541. foreach ($res as $key => $values)
  1542. if ((int)$values[$this->identifier] == (int)$this->id)
  1543. {
  1544. $k = $key;
  1545. break;
  1546. }
  1547. if (!isset($k) || !isset($res[$k]) || !isset($res[$k + 1]))
  1548. return false;
  1549. $from = $res[$k];
  1550. $to = $res[$k + 1];
  1551. if (isset($position) && !empty($position))
  1552. $to['position'] = (int)$position;
  1553. $sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
  1554. SET `position`= position '.($way ? '-1' : '+1').'
  1555. WHERE position between '.(int)(min(array($from['position'], $to['position']))).' AND '.max(array($from['position'], $to['position'])).'
  1556. AND `id_hook` = '.(int)$from['id_hook'].' AND `id_shop` = '.$shop_id;
  1557. if (!Db::getInstance()->execute($sql))
  1558. return false;
  1559. $sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
  1560. SET `position`='.(int)$to['position'].'
  1561. WHERE `'.pSQL($this->identifier).'` = '.(int)$from[$this->identifier].'
  1562. AND `id_hook` = '.(int)$to['id_hook'].' AND `id_shop` = '.$shop_id;
  1563. if (!Db::getInstance()->execute($sql))
  1564. return false;
  1565. }
  1566. return true;
  1567. }
  1568. /*
  1569. * Reorder modules position
  1570. *
  1571. * @param boolean $id_hook Hook ID
  1572. * @param array $shop_list List of shop
  1573. */
  1574. public function cleanPositions($id_hook, $shop_list = null)
  1575. {
  1576. $sql = 'SELECT `id_module`, `id_shop`
  1577. FROM `'._DB_PREFIX_.'hook_module`
  1578. WHERE `id_hook` = '.(int)$id_hook.'
  1579. '.((!is_null($shop_list) && $shop_list) ? ' AND `id_shop` IN('.implode(', ', array_map('intval', $shop_list)).')' : '').'
  1580. ORDER BY `position`';
  1581. $results = Db::getInstance()->executeS($sql);
  1582. $position = array();
  1583. foreach ($results as $row)
  1584. {
  1585. if (!isset($position[$row['id_shop']]))
  1586. $position[$row['id_shop']] = 1;
  1587. $sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
  1588. SET `position` = '.$position[$row['id_shop']].'
  1589. WHERE `id_hook` = '.(int)$id_hook.'
  1590. AND `id_module` = '.$row['id_module'].' AND `id_shop` = '.$row['id_shop'];
  1591. Db::getInstance()->execute($sql);
  1592. $position[$row['id_shop']]++;
  1593. }
  1594. return true;
  1595. }
  1596. public function displayError($error)
  1597. {
  1598. $output = '
  1599. <div class="bootstrap">
  1600. <div class="module_error alert alert-danger" >
  1601. <button type="button" class="close" data-dismiss="alert">&times;</button>
  1602. '.$error.'
  1603. </div>
  1604. </div>';
  1605. $this->error = true;
  1606. return $output;
  1607. }
  1608. public function displayConfirmation($string)
  1609. {
  1610. $output = '
  1611. <div class="bootstrap">
  1612. <div class="module_confirmation conf confirm alert alert-success">
  1613. <button type="button" class="close" data-dismiss="alert">&times;</button>
  1614. '.$string.'
  1615. </div>
  1616. </div>';
  1617. return $output;
  1618. }
  1619. /*
  1620. * Return exceptions for module in hook
  1621. *
  1622. * @param int $id_hook Hook ID
  1623. * @return array Exceptions
  1624. */
  1625. public function getExceptions($id_hook, $dispatch = false)
  1626. {
  1627. $cache_id = 'exceptionsCache';
  1628. if (!Cache::isStored($cache_id))
  1629. {
  1630. $exceptionsCache = array();
  1631. $sql = 'SELECT * FROM `'._DB_PREFIX_.'hook_module_exceptions`
  1632. WHERE `id_shop` IN ('.implode(', ', Shop::getContextListShopID()).')';
  1633. $db = Db::getInstance();
  1634. $result = $db->executeS($sql, false);
  1635. while ($row = $db->nextRow($result))
  1636. {
  1637. if (!$row['file_name'])
  1638. continue;
  1639. $key = $row['id_hook'].'-'.$row['id_module'];
  1640. if (!isset($exceptionsCache[$key]))
  1641. $exceptionsCache[$key] = array();
  1642. if (!isset($exceptionsCache[$key][$row['id_shop']]))
  1643. $exceptionsCache[$key][$row['id_shop']] = array();
  1644. $exceptionsCache[$key][$row['id_shop']][] = $row['file_name'];
  1645. }
  1646. Cache::store($cache_id, $exceptionsCache);
  1647. }
  1648. else
  1649. $exceptionsCache = Cache::retrieve($cache_id);
  1650. $key = $id_hook.'-'.$this->id;
  1651. $array_return = array();
  1652. if ($dispatch)
  1653. {
  1654. foreach (Shop::getContextListShopID() as $shop_id)
  1655. if (isset($exceptionsCache[$key], $exceptionsCache[$key][$shop_id]))
  1656. $array_return[$shop_id] = $exceptionsCache[$key][$shop_id];
  1657. }
  1658. else
  1659. {
  1660. foreach (Shop::getContextListShopID() as $shop_id)
  1661. if (isset($exceptionsCache[$key], $exceptionsCache[$key][$shop_id]))
  1662. foreach ($exceptionsCache[$key][$shop_id] as $file)
  1663. if (!in_array($file, $array_return))
  1664. $array_return[] = $file;
  1665. }
  1666. return $array_return;
  1667. }
  1668. public static function isInstalled($module_name)
  1669. {
  1670. if (!Cache::isStored('Module::isInstalled'.$module_name))
  1671. {
  1672. $id_module = Module::getModuleIdByName($module_name);
  1673. Cache::store('Module::isInstalled'.$module_name, (bool)$id_module);
  1674. }
  1675. return Cache::retrieve('Module::isInstalled'.$module_name);
  1676. }
  1677. public function isEnabledForShopContext()
  1678. {
  1679. return (bool)Db::getInstance()->getValue('
  1680. SELECT COUNT(*) n
  1681. FROM `'._DB_PREFIX_.'module_shop`
  1682. WHERE id_module='.(int)$this->id.' AND id_shop IN ('.implode(',', array_map('intval', Shop::getContextListShopID())).')
  1683. GROUP BY id_module
  1684. HAVING n='.(int)count(Shop::getContextListShopID())
  1685. );
  1686. }
  1687. public static function isEnabled($module_name)
  1688. {
  1689. if (!Cache::isStored('Module::isEnabled'.$module_name))
  1690. {
  1691. $active = false;
  1692. $id_module = Module::getModuleIdByName($module_name);
  1693. 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))
  1694. $active = true;
  1695. Cache::store('Module::isEnabled'.$module_name, (bool)$active);
  1696. }
  1697. return Cache::retrieve('Module::isEnabled'.$module_name);
  1698. }
  1699. public function isRegisteredInHook($hook)
  1700. {
  1701. if (!$this->id)
  1702. return false;
  1703. $sql = 'SELECT COUNT(*)
  1704. FROM `'._DB_PREFIX_.'hook_module` hm
  1705. LEFT JOIN `'._DB_PREFIX_.'hook` h ON (h.`id_hook` = hm.`id_hook`)
  1706. WHERE h.`name` = \''.pSQL($hook).'\' AND hm.`id_module` = '.(int)$this->id;
  1707. return Db::getInstance()->getValue($sql);
  1708. }
  1709. /*
  1710. ** Template management (display, overload, cache)
  1711. */
  1712. protected static function _isTemplateOverloadedStatic($module_name, $template)
  1713. {
  1714. if (Tools::file_exists_cache(_PS_THEME_DIR_.'modules/'.$module_name.'/'.$template))
  1715. return _PS_THEME_DIR_.'modules/'.$module_name.'/'.$template;
  1716. elseif (Tools::file_exists_cache(_PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/hook/'.$template))
  1717. return _PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/hook/'.$template;
  1718. elseif (Tools::file_exists_cache(_PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/front/'.$template))
  1719. return _PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/front/'.$template;
  1720. elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/views/templates/hook/'.$template))
  1721. return false;
  1722. elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/views/templates/front/'.$template))
  1723. return false;
  1724. elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/'.$template))
  1725. return false;
  1726. return null;
  1727. }
  1728. protected function _isTemplateOverloaded($template)
  1729. {
  1730. return Module::_isTemplateOverloadedStatic($this->name, $template);
  1731. }
  1732. protected function getCacheId($name = null)
  1733. {
  1734. $cache_array = array();
  1735. $cache_array[] = $name !== null ? $name : $this->name;
  1736. if (Configuration::get('PS_SSL_ENABLED'))
  1737. $cache_array[] = (int)Tools::usingSecureMode();
  1738. if (Shop::isFeatureActive())
  1739. $cache_array[] = (int)$this->context->shop->id;
  1740. if (Group::isFeatureActive())
  1741. $cache_array[] = (int)Group::getCurrent()->id;
  1742. if (Language::isMultiLanguageActivated())
  1743. $cache_array[] = (int)$this->context->language->id;
  1744. if (Currency::isMultiCurrencyActivated())
  1745. $cache_array[] = (int)$this->context->currency->id;
  1746. $cache_array[] = (int)$this->context->country->id;
  1747. return implode('|', $cache_array);
  1748. }
  1749. public function display($file, $template, $cacheId = null, $compileId = null)
  1750. {
  1751. if (($overloaded = Module::_isTemplateOverloadedStatic(basename($file, '.php'), $template)) === null)
  1752. return Tools::displayError('No template found for module').' '.basename($file, '.php');
  1753. else
  1754. {
  1755. if (Tools::getIsset('live_edit'))
  1756. $cacheId = null;
  1757. $this->smarty->assign(array(
  1758. 'module_dir' => __PS_BASE_URI__.'modules/'.basename($file, '.php').'/',
  1759. 'module_template_dir' => ($overloaded ? _THEME_DIR_ : __PS_BASE_URI__).'modules/'.basename($file, '.php').'/',
  1760. 'allow_push' => $this->allow_push
  1761. ));
  1762. if ($cacheId !== null)
  1763. Tools::enableCache();
  1764. $result = $this->getCurrentSubTemplate($template, $cacheId, $compileId)->fetch();
  1765. if ($cacheId !== null)
  1766. Tools::restoreCacheSettings();
  1767. $this->resetCurrentSubTemplate($template, $cacheId, $compileId);
  1768. return $result;
  1769. }
  1770. }
  1771. protected function getCurrentSubTemplate($template, $cache_id = null, $compile_id = null)
  1772. {
  1773. if (!isset($this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id]))
  1774. {
  1775. $this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id] = $this->context->smarty->createTemplate(
  1776. $this->getTemplatePath($template),
  1777. $cache_id,
  1778. $compile_id,
  1779. $this->smarty
  1780. );
  1781. }
  1782. return $this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id];
  1783. }
  1784. protected function resetCurrentSubTemplate($template, $cache_id, $compile_id)
  1785. {
  1786. $this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id] = null;
  1787. }
  1788. /**
  1789. * Get realpath of a template of current module (check if template is overriden too)
  1790. *
  1791. * @since 1.5.0
  1792. * @param string $template
  1793. * @return string
  1794. */
  1795. public function getTemplatePath($template)
  1796. {
  1797. $overloaded = $this->_isTemplateOverloaded($template);
  1798. if ($overloaded === null)
  1799. return null;
  1800. if ($overloaded)
  1801. return $overloaded;
  1802. elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$this->name.'/views/templates/hook/'.$template))
  1803. return _PS_MODULE_DIR_.$this->name.'/views/templates/hook/'.$template;
  1804. elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$this->name.'/views/templates/front/'.$template))
  1805. return _PS_MODULE_DIR_.$this->name.'/views/templates/front/'.$template;
  1806. elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$this->name.'/'.$template))
  1807. return _PS_MODULE_DIR_.$this->name.'/'.$template;
  1808. else
  1809. return null;
  1810. }
  1811. protected function _getApplicableTemplateDir($template)
  1812. {
  1813. return $this->_isTemplateOverloaded($template) ? _PS_THEME_DIR_ : _PS_MODULE_DIR_.$this->name.'/';
  1814. }
  1815. public function isCached($template, $cacheId = null, $compileId = null)
  1816. {
  1817. if (Tools::getIsset('live_edit'))
  1818. return false;
  1819. Tools::enableCache();
  1820. $is_cached = $this->getCurrentSubTemplate($this->getTemplatePath($template), $cacheId, $compileId)->isCached($this->getTemplatePath($template), $cacheId, $compileId);
  1821. Tools::restoreCacheSettings();
  1822. return $is_cached;
  1823. }
  1824. /*
  1825. * Clear template cache
  1826. *
  1827. * @param string $template Template name
  1828. * @param int null $cache_id
  1829. * @param int null $compile_id
  1830. * @return int Number of template cleared
  1831. */
  1832. protected function _clearCache($template, $cache_id = null, $compile_id = null)
  1833. {
  1834. Tools::enableCache();
  1835. if ($cache_id === null)
  1836. $cache_id = $this->name;
  1837. $number_of_template_cleared = Tools::clearCache(Context::getContext()->smarty, $this->getTemplatePath($template), $cache_id, $compile_id);
  1838. Tools::restoreCacheSettings();
  1839. return $number_of_template_cleared;
  1840. }
  1841. protected function _generateConfigXml()
  1842. {
  1843. $xml = '<?xml version="1.0" encoding="UTF-8" ?>
  1844. <module>
  1845. <name>'.$this->name.'</name>
  1846. <displayName><![CDATA['.Tools::htmlentitiesUTF8($this->displayName).']]></displayName>
  1847. <version><![CDATA['.$this->version.']]></version>
  1848. <description><![CDATA['.Tools::htmlentitiesUTF8($this->description).']]></description>
  1849. <author><![CDATA['.Tools::htmlentitiesUTF8($this->author).']]></author>
  1850. <tab><![CDATA['.Tools::htmlentitiesUTF8($this->tab).']]></tab>'.(isset($this->confirmUninstall) ? "\n\t".'<confirmUninstall><![CDATA['.$this->confirmUninstall.']]></confirmUninstall>' : '').'
  1851. <is_configurable>'.(isset($this->is_configurable) ? (int)$this->is_configurable : 0).'</is_configurable>
  1852. <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>' : '').'
  1853. </module>';
  1854. if (is_writable(_PS_MODULE_DIR_.$this->name.'/'))
  1855. {
  1856. $iso = substr(Context::getContext()->language->iso_code, 0, 2);
  1857. $file = _PS_MODULE_DIR_.$this->name.'/'.($iso == 'en' ? 'config.xml' : 'config_'.$iso.'.xml');
  1858. if (!@file_put_contents($file, $xml))
  1859. if (!is_writable($file))
  1860. {
  1861. @unlink($file);
  1862. @file_put_contents($file, $xml);
  1863. }
  1864. @chmod($file, 0664);
  1865. }
  1866. }
  1867. /**
  1868. * Check if the module is transplantable on the hook in parameter
  1869. * @param string $hook_name
  1870. * @return bool if module can be transplanted on hook
  1871. */
  1872. public function isHookableOn($hook_name)
  1873. {
  1874. $retro_hook_name = Hook::getRetroHookName($hook_name);
  1875. return (is_callable(array($this, 'hook'.ucfirst($hook_name))) || is_callable(array($this, 'hook'.ucfirst($retro_hook_name))));
  1876. }
  1877. /**
  1878. * Check employee permission for module
  1879. * @param array $variable (action)
  1880. * @param object $employee
  1881. * @return bool if module can be transplanted on hook
  1882. */
  1883. public function getPermission($variable, $employee = null)
  1884. {
  1885. return Module::getPermissionStatic($this->id, $variable, $employee);
  1886. }
  1887. /**
  1888. * Check employee permission for module (static method)
  1889. * @param integer $id_module
  1890. * @param array $variable (action)
  1891. * @param object $employee
  1892. * @return bool if module can be transplanted on hook
  1893. */
  1894. public static function getPermissionStatic($id_module, $variable, $employee = null)
  1895. {
  1896. if (!in_array($variable, array('view', 'configure')))
  1897. return false;
  1898. if (!$employee)
  1899. $employee = Context::getContext()->employee;
  1900. if ($employee->id_profile == _PS_ADMIN_PROFILE_)
  1901. return true;
  1902. if (!isset(self::$cache_permissions[$employee->id_profile]))
  1903. {
  1904. self::$cache_permissions[$employee->id_profile] = array();
  1905. $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);
  1906. foreach ($result as $row)
  1907. {
  1908. self::$cache_permissions[$employee->id_profile][$row['id_module']]['view'] = $row['view'];
  1909. self::$cache_permissions[$employee->id_profile][$row['id_module']]['configure'] = $row['configure'];
  1910. }
  1911. }
  1912. if (!isset(self::$cache_permissions[$employee->id_profile][$id_module]))
  1913. throw new PrestaShopException('No access reference in table module_access for id_module '.$id_module.'.');
  1914. return (bool)self::$cache_permissions[$employee->id_profile][$id_module][$variable];
  1915. }
  1916. /**
  1917. * Get Unauthorized modules for a client group
  1918. * @param integer group_id
  1919. */
  1920. public static function getAuthorizedModules($group_id)
  1921. {
  1922. return Db::getInstance()->executeS('
  1923. SELECT m.`id_module`, m.`name` FROM `'._DB_PREFIX_.'module_group` mg
  1924. LEFT JOIN `'._DB_PREFIX_.'module` m ON (m.`id_module` = mg.`id_module`)
  1925. WHERE mg.`id_group` = '.(int)$group_id);
  1926. }
  1927. /**
  1928. * Get id module by name
  1929. * @param string name
  1930. * @return integer id
  1931. */
  1932. public static function getModuleIdByName($name)
  1933. {
  1934. $cache_id = 'Module::getModuleIdByName_'.pSQL($name);
  1935. if (!Cache::isStored($cache_id))
  1936. {
  1937. $result = (int)Db::getInstance()->getValue('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = "'.pSQL($name).'"');
  1938. Cache::store($cache_id, $result);
  1939. }
  1940. return Cache::retrieve($cache_id);
  1941. }
  1942. /**
  1943. * Get module errors
  1944. *
  1945. * @since 1.5.0
  1946. * @return array errors
  1947. */
  1948. public function getErrors()
  1949. {
  1950. return $this->_errors;
  1951. }
  1952. /**
  1953. * Get module messages confirmation
  1954. *
  1955. * @since 1.5.0
  1956. * @return array conf
  1957. */
  1958. public function getConfirmations()
  1959. {
  1960. return $this->_confirmations;
  1961. }
  1962. /**
  1963. * Get local path for module
  1964. *
  1965. * @since 1.5.0
  1966. * @return string
  1967. */
  1968. public function getLocalPath()
  1969. {
  1970. return $this->local_path;
  1971. }
  1972. /**
  1973. * Get uri path for module
  1974. *
  1975. * @since 1.5.0
  1976. * @return string
  1977. */
  1978. public function getPathUri()
  1979. {
  1980. return $this->_path;
  1981. }
  1982. /*
  1983. * Return module position for a given hook
  1984. *
  1985. * @param boolean $id_hook Hook ID
  1986. * @return integer position
  1987. */
  1988. public function getPosition($id_hook)
  1989. {
  1990. if (isset(Hook::$preloadModulesFromHooks))
  1991. if (isset(Hook::$preloadModulesFromHooks[$id_hook]))
  1992. if (isset(Hook::$preloadModulesFromHooks[$id_hook]['module_position'][$this->id]))
  1993. return Hook::$preloadModulesFromHooks[$id_hook]['module_position'][$this->id];
  1994. else
  1995. return 0;
  1996. $result = Db::getInstance()->getRow('
  1997. SELECT `position`
  1998. FROM `'._DB_PREFIX_.'hook_module`
  1999. WHERE `id_hook` = '.(int)$id_hook.'
  2000. AND `id_module` = '.(int)$this->id.'
  2001. AND `id_shop` = '.(int)Context::getContext()->shop->id);
  2002. return $result['position'];
  2003. }
  2004. /**
  2005. * add a warning message to display at the top of the admin page
  2006. *
  2007. * @param string $msg
  2008. */
  2009. public function adminDisplayWarning($msg)
  2010. {
  2011. if (!($this->context->controller instanceof AdminController))
  2012. return false;
  2013. $this->context->controller->warnings[] = $msg;
  2014. }
  2015. /**
  2016. * add a info message to display at the top of the admin page
  2017. *
  2018. * @param string $msg
  2019. */
  2020. protected function adminDisplayInformation($msg)
  2021. {
  2022. if (!($this->context->controller instanceof AdminController))
  2023. return false;
  2024. $this->context->controller->informations[] = $msg;
  2025. }
  2026. /**
  2027. * Install module's controllers using public property $controllers
  2028. * @return bool
  2029. */
  2030. private function installControllers()
  2031. {
  2032. $themes = Theme::getThemes();
  2033. $theme_meta_value = array();
  2034. foreach ($this->controllers as $controller)
  2035. {
  2036. $page = 'module-'.$this->name.'-'.$controller;
  2037. $result = Db::getInstance()->getValue('SELECT * FROM '._DB_PREFIX_.'meta WHERE page="'.pSQL($page).'"');
  2038. if ((int)$result > 0)
  2039. continue;
  2040. $meta = New Meta();
  2041. $meta->page = $page;
  2042. $meta->configurable = 0;
  2043. $meta->save();
  2044. if ((int)$meta->id > 0)
  2045. {
  2046. foreach ($themes as $theme)
  2047. {
  2048. $theme_meta_value[] = array(
  2049. 'id_theme' => $theme->id,
  2050. 'id_meta' => $meta->id,
  2051. 'left_column' => (int)$theme->default_left_column,
  2052. 'right_column' => (int)$theme->default_right_column
  2053. );
  2054. }
  2055. }
  2056. else
  2057. $this->_errors[] = sprintf(Tools::displayError('Unable to install controller: %s'), $controller);
  2058. }
  2059. if (count($theme_meta_value) > 0)
  2060. return Db::getInstance()->insert('theme_meta', $theme_meta_value);
  2061. return true;
  2062. }
  2063. /**
  2064. * Install overrides files for the module
  2065. *
  2066. * @return bool
  2067. */
  2068. public function installOverrides()
  2069. {
  2070. if (!is_dir($this->getLocalPath().'override'))
  2071. return true;
  2072. $result = true;
  2073. foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file)
  2074. {
  2075. $class = basename($file, '.php');
  2076. if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core'))
  2077. $result &= $this->addOverride($class);
  2078. }
  2079. return $result;
  2080. }
  2081. /**
  2082. * Uninstall overrides files for the module
  2083. *
  2084. * @return bool
  2085. */
  2086. public function uninstallOverrides()
  2087. {
  2088. if (!is_dir($this->getLocalPath().'override'))
  2089. return true;
  2090. $result = true;
  2091. foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file)
  2092. {
  2093. $class = basename($file, '.php');
  2094. if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core'))
  2095. $result &= $this->removeOverride($class);
  2096. }
  2097. return $result;
  2098. }
  2099. /**
  2100. * Add all methods in a module override to the override class
  2101. *
  2102. * @param string $classname
  2103. * @return bool
  2104. */
  2105. public function addOverride($classname)
  2106. {
  2107. $path = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core');
  2108. // Check if there is already an override file, if not, we just need to copy the file
  2109. if (PrestaShopAutoload::getInstance()->getClassPath($classname))
  2110. {
  2111. // Check if override file is writable
  2112. $override_path = _PS_ROOT_DIR_.'/'.PrestaShopAutoload::getInstance()->getClassPath($classname);
  2113. if ((!file_exists($override_path) && !is_writable(dirname($override_path))) || (file_exists($override_path) && !is_writable($override_path)))
  2114. throw new Exception(sprintf(Tools::displayError('file (%s) not writable'), $override_path));
  2115. // Get a uniq id for the class, because you can override a class (or remove the override) twice in the same session and we need to avoid redeclaration
  2116. do $uniq = uniqid();
  2117. while (class_exists($classname.'OverrideOriginal_remove', false));
  2118. // Make a reflection of the override class and the module override class
  2119. $override_file = file($override_path);
  2120. eval(preg_replace(array('#^\s*<\?(?:php)?\s#', '#class\s+'.$classname.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i'), array(' ', 'class '.$classname.'OverrideOriginal'.$uniq), implode('', $override_file)));
  2121. $override_class = new ReflectionClass($classname.'OverrideOriginal'.$uniq);
  2122. $module_file = file($this->getLocalPath().'override'.DIRECTORY_SEPARATOR.$path);
  2123. eval(preg_replace(array('#^\s*<\?(?:php)?\s#', '#class\s+'.$classname.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array(' ', 'class '.$classname.'Override'.$uniq), implode('', $module_file)));
  2124. $module_class = new ReflectionClass($classname.'Override'.$uniq);
  2125. // Check if none of the methods already exists in the override class
  2126. foreach ($module_class->getMethods() as $method)
  2127. if ($override_class->hasMethod($method->getName()))
  2128. throw new Exception(sprintf(Tools::displayError('The method %1$s in the class %2$s is already overridden.'), $method->getName(), $classname));
  2129. // Check if none of the properties already exists in the override class
  2130. foreach ($module_class->getProperties() as $property)
  2131. if ($override_class->hasProperty($property->getName()))
  2132. throw new Exception(sprintf(Tools::displayError('The property %1$s in the class %2$s is already defined.'), $property->getName(), $classname));
  2133. // Insert the methods from module override in override
  2134. $copy_from = array_slice($module_file, $module_class->getStartLine() + 1, $module_class->getEndLine() - $module_class->getStartLine() - 2);
  2135. array_splice($override_file, $override_class->getEndLine() - 1, 0, $copy_from);
  2136. $code = implode('', $override_file);
  2137. file_put_contents($override_path, $code);
  2138. }
  2139. else
  2140. {
  2141. $override_src = $this->getLocalPath().'override'.DIRECTORY_SEPARATOR.$path;
  2142. $override_dest = _PS_ROOT_DIR_.DIRECTORY_SEPARATOR.'override'.DIRECTORY_SEPARATOR.$path;
  2143. if (!is_writable(dirname($override_dest)))
  2144. throw new Exception(sprintf(Tools::displayError('directory (%s) not writable'), dirname($override_dest)));
  2145. copy($override_src, $override_dest);
  2146. // Re-generate the class index
  2147. Tools::generateIndex();
  2148. }
  2149. return true;
  2150. }
  2151. /**
  2152. * Remove all methods in a module override from the override class
  2153. *
  2154. * @param string $classname
  2155. * @return bool
  2156. */
  2157. public function removeOverride($classname)
  2158. {
  2159. $path = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core');
  2160. if (!PrestaShopAutoload::getInstance()->getClassPath($classname))
  2161. return true;
  2162. // Check if override file is writable
  2163. $override_path = _PS_ROOT_DIR_.'/'.PrestaShopAutoload::getInstance()->getClassPath($classname);
  2164. if (!is_writable($override_path))
  2165. return false;
  2166. // Get a uniq id for the class, because you can override a class (or remove the override) twice in the same session and we need to avoid redeclaration
  2167. do $uniq = uniqid();
  2168. while (class_exists($classname.'OverrideOriginal_remove', false));
  2169. // Make a reflection of the override class and the module override class
  2170. $override_file = file($override_path);
  2171. eval(preg_replace(array('#^\s*<\?(?:php)?\s#', '#class\s+'.$classname.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i'), array(' ', 'class '.$classname.'OverrideOriginal_remove'.$uniq), implode('', $override_file)));
  2172. $override_class = new ReflectionClass($classname.'OverrideOriginal_remove'.$uniq);
  2173. $module_file = file($this->getLocalPath().'override/'.$path);
  2174. eval(preg_replace(array('#^\s*<\?(?:php)?\s#', '#class\s+'.$classname.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array(' ', 'class '.$classname.'Override_remove'.$uniq), implode('', $module_file)));
  2175. $module_class = new ReflectionClass($classname.'Override_remove'.$uniq);
  2176. // Remove methods from override file
  2177. $override_file = file($override_path);
  2178. foreach ($module_class->getMethods() as $method)
  2179. {
  2180. if (!$override_class->hasMethod($method->getName()))
  2181. continue;
  2182. $method = $override_class->getMethod($method->getName());
  2183. $length = $method->getEndLine() - $method->getStartLine() + 1;
  2184. $module_method = $module_class->getMethod($method->getName());
  2185. $module_length = $module_method->getEndLine() - $module_method->getStartLine() + 1;
  2186. $override_file_orig = $override_file;
  2187. $orig_content = preg_replace("/\s/", '', implode('', array_splice($override_file, $method->getStartLine() - 1, $length, array_pad(array(), $length, '#--remove--#'))));
  2188. $module_content = preg_replace("/\s/", '', implode('', array_splice($module_file, $module_method->getStartLine() - 1, $length, array_pad(array(), $length, '#--remove--#'))));
  2189. if (md5($module_content) != md5($orig_content))
  2190. $override_file = $override_file_orig;
  2191. }
  2192. // Remove properties from override file
  2193. foreach ($module_class->getProperties() as $property)
  2194. {
  2195. if (!$override_class->hasProperty($property->getName()))
  2196. continue;
  2197. // Remplacer la ligne de declaration par "remove"
  2198. foreach ($override_file as $line_number => &$line_content)
  2199. if (preg_match('/(public|private|protected|const)\s+(static\s+)?(\$)?'.$property->getName().'/i', $line_content))
  2200. {
  2201. $line_content = '#--remove--#';
  2202. break;
  2203. }
  2204. }
  2205. // Rewrite nice code
  2206. $code = '';
  2207. foreach ($override_file as $line)
  2208. {
  2209. if ($line == '#--remove--#')
  2210. continue;
  2211. $code .= $line;
  2212. }
  2213. file_put_contents($override_path, $code);
  2214. // Re-generate the class index
  2215. Tools::generateIndex();
  2216. return true;
  2217. }
  2218. }
  2219. function ps_module_version_sort($a, $b)
  2220. {
  2221. return version_compare($a['version'], $b['version']);
  2222. }