PageRenderTime 63ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/module/Module.php

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