· 5 years ago · Mar 30, 2020, 03:48 PM
1<?php
2/*
3* 2007-2017 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-2017 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
27abstract class ModuleCore
28{
29 /** @var int Module ID */
30 public $id = null;
31
32 /** @var float Version */
33 public $version;
34 public $database_version;
35
36 /**
37 * @since 1.5.0.1
38 * @var string Registered Version in database
39 */
40 public $registered_version;
41
42 /** @var array filled with known compliant PS versions */
43 public $ps_versions_compliancy = array();
44
45 /** @var array filled with modules needed for install */
46 public $dependencies = array();
47
48 /** @var string Unique name */
49 public $name;
50
51 /** @var string Human name */
52 public $displayName;
53
54 /** @var string A little description of the module */
55 public $description;
56
57 /** @var string author of the module */
58 public $author;
59
60 /** @var string URI author of the module */
61 public $author_uri = '';
62
63 /** @var string Module key provided by addons.prestashop.com */
64 public $module_key = '';
65
66 public $description_full;
67
68 public $additional_description;
69
70 public $compatibility;
71
72 public $nb_rates;
73
74 public $avg_rate;
75
76 public $badges;
77
78 /** @var int need_instance */
79 public $need_instance = 1;
80
81 /** @var string Admin tab corresponding to the module */
82 public $tab = null;
83
84 /** @var bool Status */
85 public $active = false;
86
87 /** @var bool Is the module certified by addons.prestashop.com */
88 public $trusted = false;
89
90 /** @var string Fill it if the module is installed but not yet set up */
91 public $warning;
92
93 public $enable_device = 7;
94
95 /** @var array to store the limited country */
96 public $limited_countries = array();
97
98 /** @var array names of the controllers */
99 public $controllers = array();
100
101 /** @var array used by AdminTab to determine which lang file to use (admin.php or module lang file) */
102 public static $classInModule = array();
103
104 /** @var array current language translations */
105 protected $_lang = array();
106
107 /** @var string Module web path (eg. '/shop/modules/modulename/') */
108 protected $_path = null;
109 /**
110 * @since 1.5.0.1
111 * @var string Module local path (eg. '/home/prestashop/modules/modulename/')
112 */
113 protected $local_path = null;
114
115 /** @var array Array filled with module errors */
116 protected $_errors = array();
117
118 /** @var array Array array filled with module success */
119 protected $_confirmations = array();
120
121 /** @var string Main table used for modules installed */
122 protected $table = 'module';
123
124 /** @var string Identifier of the main table */
125 protected $identifier = 'id_module';
126
127 /** @var array Array cache filled with modules informations */
128 protected static $modules_cache;
129
130 /** @var array Array cache filled with modules instances */
131 protected static $_INSTANCE = array();
132
133 /** @var bool Config xml generation mode */
134 protected static $_generate_config_xml_mode = false;
135
136 /** @var array Array filled with cache translations */
137 protected static $l_cache = array();
138
139 /** @var array Array filled with cache permissions (modules / employee profiles) */
140 protected static $cache_permissions = array();
141
142 /** @var Context */
143 protected $context;
144
145 /** @var Smarty_Data */
146 protected $smarty;
147
148 /** @var Smarty_Internal_Template|null */
149 protected $current_subtemplate = null;
150
151 protected static $update_translations_after_install = true;
152
153 protected static $_batch_mode = false;
154 protected static $_defered_clearCache = array();
155 protected static $_defered_func_call = array();
156
157 /** @var bool If true, allow push */
158 public $allow_push;
159
160 public $push_time_limit = 180;
161
162 /** @var bool Define if we will log modules performances for this session */
163 public static $_log_modules_perfs = null;
164 /** @var bool Random session for modules perfs logs*/
165 public static $_log_modules_perfs_session = null;
166
167 const CACHE_FILE_MODULES_LIST = '/config/xml/modules_list.xml';
168
169 const CACHE_FILE_TAB_MODULES_LIST = '/config/xml/tab_modules_list.xml';
170
171 const CACHE_FILE_ALL_COUNTRY_MODULES_LIST = '/config/xml/modules_native_addons.xml';
172 const CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST = '/config/xml/default_country_modules_list.xml';
173
174 const CACHE_FILE_CUSTOMER_MODULES_LIST = '/config/xml/customer_modules_list.xml';
175
176 const CACHE_FILE_MUST_HAVE_MODULES_LIST = '/config/xml/must_have_modules_list.xml';
177
178 const CACHE_FILE_TRUSTED_MODULES_LIST = '/config/xml/trusted_modules_list.xml';
179 const CACHE_FILE_UNTRUSTED_MODULES_LIST = '/config/xml/untrusted_modules_list.xml';
180
181 public static $hosted_modules_blacklist = array('autoupgrade');
182
183 /**
184 * Set the flag to indicate we are doing an import
185 *
186 * @param bool $value
187 */
188 public static function setBatchMode($value)
189 {
190 self::$_batch_mode = (bool)$value;
191 }
192
193 /**
194 * @return bool
195 */
196 public static function getBatchMode()
197 {
198 return self::$_batch_mode;
199 }
200
201 public static function processDeferedFuncCall()
202 {
203 self::setBatchMode(false);
204 foreach (self::$_defered_func_call as $func_call) {
205 call_user_func_array($func_call[0], $func_call[1]);
206 }
207
208 self::$_defered_func_call = array();
209 }
210
211 /**
212 * Clear the caches stored in $_defered_clearCache
213 *
214 */
215 public static function processDeferedClearCache()
216 {
217 self::setBatchMode(false);
218
219 foreach (self::$_defered_clearCache as $clearCache_array) {
220 self::_deferedClearCache($clearCache_array[0], $clearCache_array[1], $clearCache_array[2]);
221 }
222
223 self::$_defered_clearCache = array();
224 }
225
226 /**
227 * Constructor
228 *
229 * @param string $name Module unique name
230 * @param Context $context
231 */
232 public function __construct($name = null, Context $context = null)
233 {
234 if (isset($this->ps_versions_compliancy) && !isset($this->ps_versions_compliancy['min'])) {
235 $this->ps_versions_compliancy['min'] = '1.4.0.0';
236 }
237
238 if (isset($this->ps_versions_compliancy) && !isset($this->ps_versions_compliancy['max'])) {
239 $this->ps_versions_compliancy['max'] = _PS_VERSION_;
240 }
241
242 if (strlen($this->ps_versions_compliancy['min']) == 3) {
243 $this->ps_versions_compliancy['min'] .= '.0.0';
244 }
245
246 if (strlen($this->ps_versions_compliancy['max']) == 3) {
247 $this->ps_versions_compliancy['max'] .= '.999.999';
248 }
249
250 // Load context and smarty
251 $this->context = $context ? $context : Context::getContext();
252 if (is_object($this->context->smarty)) {
253 $this->smarty = $this->context->smarty->createData($this->context->smarty);
254 }
255
256 // If the module has no name we gave him its id as name
257 if ($this->name === null) {
258 $this->name = $this->id;
259 }
260
261 // If the module has the name we load the corresponding data from the cache
262 if ($this->name != null) {
263 // If cache is not generated, we generate it
264 if (self::$modules_cache == null && !is_array(self::$modules_cache)) {
265 $id_shop = (Validate::isLoadedObject($this->context->shop) ? $this->context->shop->id : Configuration::get('PS_SHOP_DEFAULT'));
266
267 self::$modules_cache = array();
268 // Join clause is done to check if the module is activated in current shop context
269 $result = Db::getInstance()->executeS('
270 SELECT m.`id_module`, m.`name`, (
271 SELECT id_module
272 FROM `'._DB_PREFIX_.'module_shop` ms
273 WHERE m.`id_module` = ms.`id_module`
274 AND ms.`id_shop` = '.(int)$id_shop.'
275 LIMIT 1
276 ) as mshop
277 FROM `'._DB_PREFIX_.'module` m');
278 foreach ($result as $row) {
279 self::$modules_cache[$row['name']] = $row;
280 self::$modules_cache[$row['name']]['active'] = ($row['mshop'] > 0) ? 1 : 0;
281 }
282 }
283
284 // We load configuration from the cache
285 if (isset(self::$modules_cache[$this->name])) {
286 if (isset(self::$modules_cache[$this->name]['id_module'])) {
287 $this->id = self::$modules_cache[$this->name]['id_module'];
288 }
289 foreach (self::$modules_cache[$this->name] as $key => $value) {
290 if (array_key_exists($key, $this)) {
291 $this->{$key} = $value;
292 }
293 }
294 $this->_path = __PS_BASE_URI__.'modules/'.$this->name.'/';
295 }
296 if (!$this->context->controller instanceof Controller) {
297 self::$modules_cache = null;
298 }
299 $this->local_path = _PS_MODULE_DIR_.$this->name.'/';
300 }
301 }
302
303 /**
304 * Insert module into datable
305 */
306 public function install()
307 {
308 Hook::exec('actionModuleInstallBefore', array('object' => $this));
309 // Check module name validation
310 if (!Validate::isModuleName($this->name)) {
311 $this->_errors[] = Tools::displayError('Unable to install the module (Module name is not valid).');
312 return false;
313 }
314
315 // Check PS version compliancy
316 if (!$this->checkCompliancy()) {
317 $this->_errors[] = Tools::displayError('The version of your module is not compliant with your PrestaShop version.');
318 return false;
319 }
320
321 // Check module dependencies
322 if (count($this->dependencies) > 0) {
323 foreach ($this->dependencies as $dependency) {
324 if (!Db::getInstance()->getRow('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE LOWER(`name`) = \''.pSQL(Tools::strtolower($dependency)).'\'')) {
325 $error = Tools::displayError('Before installing this module, you have to install this/these module(s) first:').'<br />';
326 foreach ($this->dependencies as $d) {
327 $error .= '- '.$d.'<br />';
328 }
329 $this->_errors[] = $error;
330 return false;
331 }
332 }
333 }
334
335 // Check if module is installed
336 $result = Module::isInstalled($this->name);
337 if ($result) {
338 $this->_errors[] = Tools::displayError('This module has already been installed.');
339 return false;
340 }
341
342 // Install overrides
343 try {
344 $this->installOverrides();
345 } catch (Exception $e) {
346 $this->_errors[] = sprintf(Tools::displayError('Unable to install override: %s'), $e->getMessage());
347 $this->uninstallOverrides();
348 return false;
349 }
350
351 if (!$this->installControllers()) {
352 return false;
353 }
354
355 // Install module and retrieve the installation id
356 $result = Db::getInstance()->insert($this->table, array('name' => $this->name, 'active' => 1, 'version' => $this->version));
357 if (!$result) {
358 $this->_errors[] = Tools::displayError('Technical error: PrestaShop could not install this module.');
359 return false;
360 }
361 $this->id = Db::getInstance()->Insert_ID();
362
363 Cache::clean('Module::isInstalled'.$this->name);
364
365 // Enable the module for current shops in context
366 $this->enable();
367
368 // Permissions management
369 Db::getInstance()->execute('
370 INSERT INTO `'._DB_PREFIX_.'module_access` (`id_profile`, `id_module`, `view`, `configure`, `uninstall`) (
371 SELECT id_profile, '.(int)$this->id.', 1, 1, 1
372 FROM '._DB_PREFIX_.'access a
373 WHERE id_tab = (
374 SELECT `id_tab` FROM '._DB_PREFIX_.'tab
375 WHERE class_name = \'AdminModules\' LIMIT 1)
376 AND a.`view` = 1)');
377
378 Db::getInstance()->execute('
379 INSERT INTO `'._DB_PREFIX_.'module_access` (`id_profile`, `id_module`, `view`, `configure`, `uninstall`) (
380 SELECT id_profile, '.(int)$this->id.', 1, 0, 0
381 FROM '._DB_PREFIX_.'access a
382 WHERE id_tab = (
383 SELECT `id_tab` FROM '._DB_PREFIX_.'tab
384 WHERE class_name = \'AdminModules\' LIMIT 1)
385 AND a.`view` = 0)');
386
387 // Adding Restrictions for client groups
388 Group::addRestrictionsForModule($this->id, Shop::getShops(true, null, true));
389 Hook::exec('actionModuleInstallAfter', array('object' => $this));
390
391 if (Module::$update_translations_after_install) {
392 $this->updateModuleTranslations();
393 }
394
395 return true;
396 }
397
398 public function checkCompliancy()
399 {
400 if (version_compare(_PS_VERSION_, $this->ps_versions_compliancy['min'], '<') || version_compare(_PS_VERSION_, $this->ps_versions_compliancy['max'], '>')) {
401 return false;
402 } else {
403 return true;
404 }
405 }
406
407 public static function updateTranslationsAfterInstall($update = true)
408 {
409 Module::$update_translations_after_install = (bool)$update;
410 }
411
412 public function updateModuleTranslations()
413 {
414 return Language::updateModulesTranslations(array($this->name));
415 }
416
417 /**
418 * Set errors, warning or success message of a module upgrade
419 *
420 * @param $upgrade_detail
421 */
422 protected function setUpgradeMessage($upgrade_detail)
423 {
424 // Store information if a module has been upgraded (memory optimization)
425 if ($upgrade_detail['available_upgrade']) {
426 if ($upgrade_detail['success']) {
427 $this->_confirmations[] = sprintf(Tools::displayError('Current version: %s'), $this->version);
428 $this->_confirmations[] = sprintf(Tools::displayError('%d file upgrade applied'), $upgrade_detail['number_upgraded']);
429 } else {
430 if (!$upgrade_detail['number_upgraded']) {
431 $this->_errors[] = Tools::displayError('No upgrade has been applied');
432 } else {
433 $this->_errors[] = sprintf(Tools::displayError('Upgraded from: %s to %s'), $upgrade_detail['upgraded_from'], $upgrade_detail['upgraded_to']);
434 $this->_errors[] = sprintf(Tools::displayError('%d upgrade left'), $upgrade_detail['number_upgrade_left']);
435 }
436
437 if (isset($upgrade_detail['duplicate']) && $upgrade_detail['duplicate']) {
438 $this->_errors[] = sprintf(Tools::displayError('Module %s cannot be upgraded this time: please refresh this page to update it.'), $this->name);
439 } else {
440 $this->_errors[] = Tools::displayError('To prevent any problem, this module has been turned off');
441 }
442 }
443 }
444 }
445
446 /**
447 * Init the upgrade module
448 *
449 * @param $module
450 * @return bool
451 */
452 public static function initUpgradeModule($module)
453 {
454 if (((int)$module->installed == 1) & (empty($module->database_version) === true)) {
455 Module::upgradeModuleVersion($module->name, $module->version);
456 $module->database_version = $module->version;
457 }
458
459 // Init cache upgrade details
460 self::$modules_cache[$module->name]['upgrade'] = array(
461 'success' => false, // bool to know if upgrade succeed or not
462 'available_upgrade' => 0, // Number of available module before any upgrade
463 'number_upgraded' => 0, // Number of upgrade done
464 'number_upgrade_left' => 0,
465 'upgrade_file_left' => array(), // List of the upgrade file left
466 'version_fail' => 0, // Version of the upgrade failure
467 'upgraded_from' => 0, // Version number before upgrading anything
468 'upgraded_to' => 0, // Last upgrade applied
469 );
470
471 // Need Upgrade will check and load upgrade file to the moduleCache upgrade case detail
472 $ret = $module->installed && Module::needUpgrade($module);
473 return $ret;
474 }
475
476 /**
477 * Run the upgrade for a given module name and version
478 *
479 * @return array
480 */
481 public function runUpgradeModule()
482 {
483 $upgrade = &self::$modules_cache[$this->name]['upgrade'];
484 foreach ($upgrade['upgrade_file_left'] as $num => $file_detail) {
485 foreach ($file_detail['upgrade_function'] as $item) {
486 if (function_exists($item)) {
487 $upgrade['success'] = false;
488 $upgrade['duplicate'] = true;
489 break 2;
490 }
491 }
492
493 include($file_detail['file']);
494
495 // Call the upgrade function if defined
496 $upgrade['success'] = false;
497 foreach ($file_detail['upgrade_function'] as $item) {
498 if (function_exists($item)) {
499 $upgrade['success'] = $item($this);
500 }
501 }
502
503 // Set detail when an upgrade succeed or failed
504 if ($upgrade['success']) {
505 $upgrade['number_upgraded'] += 1;
506 $upgrade['upgraded_to'] = $file_detail['version'];
507
508 unset($upgrade['upgrade_file_left'][$num]);
509 } else {
510 $upgrade['version_fail'] = $file_detail['version'];
511
512 // If any errors, the module is disabled
513 $this->disable();
514 break;
515 }
516 }
517
518 $upgrade['number_upgrade_left'] = count($upgrade['upgrade_file_left']);
519
520 // Update module version in DB with the last succeed upgrade
521 if ($upgrade['upgraded_to']) {
522 Module::upgradeModuleVersion($this->name, $upgrade['upgraded_to']);
523 }
524 $this->setUpgradeMessage($upgrade);
525 return $upgrade;
526 }
527
528 /**
529 * Upgrade the registered version to a new one
530 *
531 * @param $name
532 * @param $version
533 * @return bool
534 */
535 public static function upgradeModuleVersion($name, $version)
536 {
537 return Db::getInstance()->execute('
538 UPDATE `'._DB_PREFIX_.'module` m
539 SET m.version = \''.pSQL($version).'\'
540 WHERE m.name = \''.pSQL($name).'\'');
541 }
542
543 /**
544 * Check if a module need to be upgraded.
545 * This method modify the module_cache adding an upgrade list file
546 *
547 * @param $module
548 * @return bool
549 */
550 public static function needUpgrade($module)
551 {
552 self::$modules_cache[$module->name]['upgrade']['upgraded_from'] = $module->database_version;
553 // Check the version of the module with the registered one and look if any upgrade file exist
554 if (Tools::version_compare($module->version, $module->database_version, '>')) {
555 $old_version = $module->database_version;
556 $module = Module::getInstanceByName($module->name);
557 if ($module instanceof Module) {
558 return $module->loadUpgradeVersionList($module->name, $module->version, $old_version);
559 }
560 }
561 return null;
562 }
563
564 /**
565 * Load the available list of upgrade of a specified module
566 * with an associated version
567 *
568 * @param $module_name
569 * @param $module_version
570 * @param $registered_version
571 * @return bool to know directly if any files have been found
572 */
573 protected static function loadUpgradeVersionList($module_name, $module_version, $registered_version)
574 {
575 $list = array();
576
577 $upgrade_path = _PS_MODULE_DIR_.$module_name.'/upgrade/';
578
579 // Check if folder exist and it could be read
580 if (file_exists($upgrade_path) && ($files = scandir($upgrade_path))) {
581 // Read each file name
582 foreach ($files as $file) {
583 if (!in_array($file, array('.', '..', '.svn', 'index.php')) && preg_match('/\.php$/', $file)) {
584 $tab = explode('-', $file);
585
586 if (!isset($tab[1])) {
587 continue;
588 }
589
590 $file_version = basename($tab[1], '.php');
591 // Compare version, if minor than actual, we need to upgrade the module
592 if (count($tab) == 2 &&
593 (Tools::version_compare($file_version, $module_version, '<=') &&
594 Tools::version_compare($file_version, $registered_version, '>'))) {
595 $list[] = array(
596 'file' => $upgrade_path.$file,
597 'version' => $file_version,
598 'upgrade_function' => array(
599 'upgrade_module_'.str_replace('.', '_', $file_version),
600 'upgradeModule'.str_replace('.', '', $file_version))
601 );
602 }
603 }
604 }
605 }
606
607 // No files upgrade, then upgrade succeed
608 if (count($list) == 0) {
609 self::$modules_cache[$module_name]['upgrade']['success'] = true;
610 Module::upgradeModuleVersion($module_name, $module_version);
611 }
612
613 usort($list, 'ps_module_version_sort');
614
615 // Set the list to module cache
616 self::$modules_cache[$module_name]['upgrade']['upgrade_file_left'] = $list;
617 self::$modules_cache[$module_name]['upgrade']['available_upgrade'] = count($list);
618 return (bool)count($list);
619 }
620
621 /**
622 * Return the status of the upgraded module
623 *
624 * @param $module_name
625 * @return bool
626 */
627 public static function getUpgradeStatus($module_name)
628 {
629 return (isset(self::$modules_cache[$module_name]) &&
630 self::$modules_cache[$module_name]['upgrade']['success']);
631 }
632
633 /**
634 * Delete module from datable
635 *
636 * @return bool result
637 */
638 public function uninstall()
639 {
640 // Check module installation id validation
641 if (!Validate::isUnsignedId($this->id)) {
642 $this->_errors[] = Tools::displayError('The module is not installed.');
643 return false;
644 }
645
646 // Uninstall overrides
647 if (!$this->uninstallOverrides()) {
648 return false;
649 }
650
651 // Retrieve hooks used by the module
652 $sql = 'SELECT `id_hook` FROM `'._DB_PREFIX_.'hook_module` WHERE `id_module` = '.(int)$this->id;
653 $result = Db::getInstance()->executeS($sql);
654 foreach ($result as $row) {
655 $this->unregisterHook((int)$row['id_hook']);
656 $this->unregisterExceptions((int)$row['id_hook']);
657 }
658
659 foreach ($this->controllers as $controller) {
660 $page_name = 'module-'.$this->name.'-'.$controller;
661 $meta = Db::getInstance()->getValue('SELECT id_meta FROM `'._DB_PREFIX_.'meta` WHERE page="'.pSQL($page_name).'"');
662 if ((int)$meta > 0) {
663 Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'theme_meta` WHERE id_meta='.(int)$meta);
664 Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'meta_lang` WHERE id_meta='.(int)$meta);
665 Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'meta` WHERE id_meta='.(int)$meta);
666 }
667 }
668
669 // Disable the module for all shops
670 $this->disable(true);
671
672 // Delete permissions module access
673 Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'module_access` WHERE `id_module` = '.(int)$this->id);
674
675 // Remove restrictions for client groups
676 Group::truncateRestrictionsByModule($this->id);
677
678 // Uninstall the module
679 if (Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'module` WHERE `id_module` = '.(int)$this->id)) {
680 Cache::clean('Module::isInstalled'.$this->name);
681 Cache::clean('Module::getModuleIdByName_'.pSQL($this->name));
682 return true;
683 }
684
685 return false;
686 }
687
688 /**
689 * This function enable module $name. If an $name is an array,
690 * this will enable all of them
691 *
692 * @param array|string $name
693 * @return true if succeed
694 * @since 1.4.1
695 */
696 public static function enableByName($name)
697 {
698 // If $name is not an array, we set it as an array
699 if (!is_array($name)) {
700 $name = array($name);
701 }
702 $res = true;
703 // Enable each module
704 foreach ($name as $n) {
705 if (Validate::isModuleName($n)) {
706 $res &= Module::getInstanceByName($n)->enable();
707 }
708 }
709 return $res;
710 }
711
712 /**
713 * Activate current module.
714 *
715 * @param bool $force_all If true, enable module for all shop
716 */
717 public function enable($force_all = false)
718 {
719 // Retrieve all shops where the module is enabled
720 $list = Shop::getContextListShopID();
721 if (!$this->id || !is_array($list)) {
722 return false;
723 }
724 $sql = 'SELECT `id_shop` FROM `'._DB_PREFIX_.'module_shop`
725 WHERE `id_module` = '.(int)$this->id.
726 ((!$force_all) ? ' AND `id_shop` IN('.implode(', ', $list).')' : '');
727
728 // Store the results in an array
729 $items = array();
730 if ($results = Db::getInstance($sql)->executeS($sql)) {
731 foreach ($results as $row) {
732 $items[] = $row['id_shop'];
733 }
734 }
735
736 // Enable module in the shop where it is not enabled yet
737 foreach ($list as $id) {
738 if (!in_array($id, $items)) {
739 Db::getInstance()->insert('module_shop', array(
740 'id_module' => $this->id,
741 'id_shop' => $id,
742 ));
743 }
744 }
745
746 return true;
747 }
748
749 public function enableDevice($device)
750 {
751 Db::getInstance()->execute('
752 UPDATE '._DB_PREFIX_.'module_shop
753 SET enable_device = enable_device + '.(int)$device.'
754 WHERE (enable_device &~ '.(int)$device.' OR enable_device = 0) AND id_module='.(int)$this->id.
755 Shop::addSqlRestriction()
756 );
757
758 return true;
759 }
760
761 public function disableDevice($device)
762 {
763 Db::getInstance()->execute('
764 UPDATE '._DB_PREFIX_.'module_shop
765 SET enable_device = enable_device - '.(int)$device.'
766 WHERE enable_device & '.(int)$device.' AND id_module='.(int)$this->id.
767 Shop::addSqlRestriction()
768 );
769
770 return true;
771 }
772
773 /**
774 * This function disable module $name. If an $name is an array,
775 * this will disable all of them
776 *
777 * @param array|string $name
778 * @return true if succeed
779 * @since 1.4.1
780 */
781 public static function disableByName($name)
782 {
783 // If $name is not an array, we set it as an array
784 if (!is_array($name)) {
785 $name = array($name);
786 }
787 $res = true;
788 // Disable each module
789 foreach ($name as $n) {
790 if (Validate::isModuleName($n)) {
791 $res &= Module::getInstanceByName($n)->disable();
792 }
793 }
794 return $res;
795 }
796
797 /**
798 * Desactivate current module.
799 *
800 * @param bool $force_all If true, disable module for all shop
801 */
802 public function disable($force_all = false)
803 {
804 // Disable module for all shops
805 $sql = 'DELETE FROM `'._DB_PREFIX_.'module_shop` WHERE `id_module` = '.(int)$this->id.' '.((!$force_all) ? ' AND `id_shop` IN('.implode(', ', Shop::getContextListShopID()).')' : '');
806 Db::getInstance()->execute($sql);
807 }
808
809 /**
810 * Display flags in forms for translations
811 * @deprecated since 1.6.0.10
812 *
813 * @param array $languages All languages available
814 * @param int $default_language Default language id
815 * @param string $ids Multilingual div ids in form
816 * @param string $id Current div id]
817 * @param bool $return define the return way : false for a display, true for a return
818 * @param bool $use_vars_instead_of_ids use an js vars instead of ids seperate by "¤"
819 */
820 public function displayFlags($languages, $default_language, $ids, $id, $return = false, $use_vars_instead_of_ids = false)
821 {
822 if (count($languages) == 1) {
823 return false;
824 }
825
826 $output = '
827 <div class="displayed_flag">
828 <img src="../img/l/'.$default_language.'.jpg" class="pointer" id="language_current_'.$id.'" onclick="toggleLanguageFlags(this);" alt="" />
829 </div>
830 <div id="languages_'.$id.'" class="language_flags">
831 '.$this->l('Choose language:').'<br /><br />';
832 foreach ($languages as $language) {
833 if ($use_vars_instead_of_ids) {
834 $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'].'\');" /> ';
835 } else {
836 $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'].'\');" /> ';
837 }
838 }
839 $output .= '</div>';
840
841 if ($return) {
842 return $output;
843 }
844 echo $output;
845 }
846
847 /**
848 * Connect module to a hook
849 *
850 * @param string $hook_name Hook name
851 * @param array $shop_list List of shop linked to the hook (if null, link hook to all shops)
852 * @return bool result
853 */
854 public function registerHook($hook_name, $shop_list = null)
855 {
856 $return = true;
857 if (is_array($hook_name)) {
858 $hook_names = $hook_name;
859 } else {
860 $hook_names = array($hook_name);
861 }
862
863 foreach ($hook_names as $hook_name) {
864 // Check hook name validation and if module is installed
865 if (!Validate::isHookName($hook_name)) {
866 throw new PrestaShopException('Invalid hook name');
867 }
868 if (!isset($this->id) || !is_numeric($this->id)) {
869 return false;
870 }
871
872 // Retrocompatibility
873 $hook_name_bak = $hook_name;
874 if ($alias = Hook::getRetroHookName($hook_name)) {
875 $hook_name = $alias;
876 }
877
878 Hook::exec('actionModuleRegisterHookBefore', array('object' => $this, 'hook_name' => $hook_name));
879 // Get hook id
880 $id_hook = Hook::getIdByName($hook_name);
881
882 // If hook does not exist, we create it
883 if (!$id_hook) {
884 $new_hook = new Hook();
885 $new_hook->name = pSQL($hook_name);
886 $new_hook->title = pSQL($hook_name);
887 $new_hook->live_edit = (bool)preg_match('/^display/i', $new_hook->name);
888 $new_hook->position = (bool)$new_hook->live_edit;
889 $new_hook->add();
890 $id_hook = $new_hook->id;
891 if (!$id_hook) {
892 return false;
893 }
894 }
895
896 // If shop lists is null, we fill it with all shops
897 if (is_null($shop_list)) {
898 $shop_list = Shop::getCompleteListOfShopsID();
899 }
900
901 $shop_list_employee = Shop::getShops(true, null, true);
902
903 foreach ($shop_list as $shop_id) {
904 // Check if already register
905 $sql = 'SELECT hm.`id_module`
906 FROM `'._DB_PREFIX_.'hook_module` hm, `'._DB_PREFIX_.'hook` h
907 WHERE hm.`id_module` = '.(int)$this->id.' AND h.`id_hook` = '.$id_hook.'
908 AND h.`id_hook` = hm.`id_hook` AND `id_shop` = '.(int)$shop_id;
909 if (Db::getInstance()->getRow($sql)) {
910 continue;
911 }
912
913 // Get module position in hook
914 $sql = 'SELECT MAX(`position`) AS position
915 FROM `'._DB_PREFIX_.'hook_module`
916 WHERE `id_hook` = '.(int)$id_hook.' AND `id_shop` = '.(int)$shop_id;
917 if (!$position = Db::getInstance()->getValue($sql)) {
918 $position = 0;
919 }
920
921 // Register module in hook
922 $return &= Db::getInstance()->insert('hook_module', array(
923 'id_module' => (int)$this->id,
924 'id_hook' => (int)$id_hook,
925 'id_shop' => (int)$shop_id,
926 'position' => (int)($position + 1),
927 ));
928
929 if (!in_array($shop_id, $shop_list_employee)) {
930 $where = '`id_module` = '.(int)$this->id.' AND `id_shop` = '.(int)$shop_id;
931 $return &= Db::getInstance()->delete('module_shop', $where);
932 }
933 }
934
935 Hook::exec('actionModuleRegisterHookAfter', array('object' => $this, 'hook_name' => $hook_name));
936 }
937 return $return;
938 }
939
940 /**
941 * Unregister module from hook
942 *
943 * @param mixed $id_hook Hook id (can be a hook name since 1.5.0)
944 * @param array $shop_list List of shop
945 * @return bool result
946 */
947 public function unregisterHook($hook_id, $shop_list = null)
948 {
949 // Get hook id if a name is given as argument
950 if (!is_numeric($hook_id)) {
951 $hook_name = (string)$hook_id;
952 // Retrocompatibility
953 $hook_id = Hook::getIdByName($hook_name);
954 if (!$hook_id) {
955 return false;
956 }
957 } else {
958 $hook_name = Hook::getNameById((int)$hook_id);
959 }
960
961 Hook::exec('actionModuleUnRegisterHookBefore', array('object' => $this, 'hook_name' => $hook_name));
962
963 // Unregister module on hook by id
964 $sql = 'DELETE FROM `'._DB_PREFIX_.'hook_module`
965 WHERE `id_module` = '.(int)$this->id.' AND `id_hook` = '.(int)$hook_id
966 .(($shop_list) ? ' AND `id_shop` IN('.implode(', ', array_map('intval', $shop_list)).')' : '');
967 $result = Db::getInstance()->execute($sql);
968
969 // Clean modules position
970 $this->cleanPositions($hook_id, $shop_list);
971
972 Hook::exec('actionModuleUnRegisterHookAfter', array('object' => $this, 'hook_name' => $hook_name));
973
974 return $result;
975 }
976
977 /**
978 * Unregister exceptions linked to module
979 *
980 * @param int $id_hook Hook id
981 * @param array $shop_list List of shop
982 * @return bool result
983 */
984 public function unregisterExceptions($hook_id, $shop_list = null)
985 {
986 $sql = 'DELETE FROM `'._DB_PREFIX_.'hook_module_exceptions`
987 WHERE `id_module` = '.(int)$this->id.' AND `id_hook` = '.(int)$hook_id
988 .(($shop_list) ? ' AND `id_shop` IN('.implode(', ', array_map('intval', $shop_list)).')' : '');
989 return Db::getInstance()->execute($sql);
990 }
991
992 /**
993 * Add exceptions for module->Hook
994 *
995 * @param int $id_hook Hook id
996 * @param array $excepts List of file name
997 * @param array $shop_list List of shop
998 * @return bool result
999 */
1000 public function registerExceptions($id_hook, $excepts, $shop_list = null)
1001 {
1002 // If shop lists is null, we fill it with all shops
1003 if (is_null($shop_list)) {
1004 $shop_list = Shop::getContextListShopID();
1005 }
1006
1007 // Save modules exception for each shop
1008 foreach ($shop_list as $shop_id) {
1009 foreach ($excepts as $except) {
1010 if (!$except) {
1011 continue;
1012 }
1013 $insert_exception = array(
1014 'id_module' => (int)$this->id,
1015 'id_hook' => (int)$id_hook,
1016 'id_shop' => (int)$shop_id,
1017 'file_name' => pSQL($except),
1018 );
1019 $result = Db::getInstance()->insert('hook_module_exceptions', $insert_exception);
1020 if (!$result) {
1021 return false;
1022 }
1023 }
1024 }
1025 return true;
1026 }
1027
1028 /**
1029 * Edit exceptions for module->Hook
1030 *
1031 * @param int $hookID Hook id
1032 * @param array $excepts List of shopID and file name
1033 * @return bool result
1034 */
1035 public function editExceptions($id_hook, $excepts)
1036 {
1037 $result = true;
1038 foreach ($excepts as $shop_id => $except) {
1039 $shop_list = ($shop_id == 0) ? Shop::getContextListShopID() : array($shop_id);
1040 $this->unregisterExceptions($id_hook, $shop_list);
1041 $result &= $this->registerExceptions($id_hook, $except, $shop_list);
1042 }
1043
1044 return $result;
1045 }
1046
1047
1048 /**
1049 * This function is used to determine the module name
1050 * of an AdminTab which belongs to a module, in order to keep translation
1051 * related to a module in its directory (instead of $_LANGADM)
1052 *
1053 * @param mixed $current_class the
1054 * @return bool|string if the class belongs to a module, will return the module name. Otherwise, return false.
1055 */
1056 public static function getModuleNameFromClass($current_class)
1057 {
1058 // Module can now define AdminTab keeping the module translations method,
1059 // i.e. in modules/[module name]/[iso_code].php
1060 if (!isset(self::$classInModule[$current_class]) && class_exists($current_class)) {
1061 global $_MODULES;
1062 $_MODULE = array();
1063 $reflection_class = new ReflectionClass($current_class);
1064 $file_path = realpath($reflection_class->getFileName());
1065 $realpath_module_dir = realpath(_PS_MODULE_DIR_);
1066 if (substr(realpath($file_path), 0, strlen($realpath_module_dir)) == $realpath_module_dir) {
1067 // For controllers in module/controllers path
1068 if (basename(dirname(dirname($file_path))) == 'controllers') {
1069 self::$classInModule[$current_class] = basename(dirname(dirname(dirname($file_path))));
1070 } else {
1071 // For old AdminTab controllers
1072 self::$classInModule[$current_class] = substr(dirname($file_path), strlen($realpath_module_dir) + 1);
1073 }
1074
1075 $file = _PS_MODULE_DIR_.self::$classInModule[$current_class].'/'.Context::getContext()->language->iso_code.'.php';
1076 if (Tools::file_exists_cache($file) && include_once($file)) {
1077 $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
1078 }
1079 } else {
1080 self::$classInModule[$current_class] = false;
1081 }
1082 }
1083
1084 // return name of the module, or false
1085 return self::$classInModule[$current_class];
1086 }
1087
1088 /**
1089 * Return an instance of the specified module
1090 *
1091 * @param string $module_name Module name
1092 * @return Module
1093 */
1094 public static function getInstanceByName($module_name)
1095 {
1096 if (!Validate::isModuleName($module_name)) {
1097 if (_PS_MODE_DEV_) {
1098 die(Tools::displayError(Tools::safeOutput($module_name).' is not a valid module name.'));
1099 }
1100 return false;
1101 }
1102
1103 if (!isset(self::$_INSTANCE[$module_name])) {
1104 if (!Tools::file_exists_no_cache(_PS_MODULE_DIR_.$module_name.'/'.$module_name.'.php')) {
1105 return false;
1106 }
1107 return Module::coreLoadModule($module_name);
1108 }
1109 return self::$_INSTANCE[$module_name];
1110 }
1111
1112 protected static function coreLoadModule($module_name)
1113 {
1114 // Define if we will log modules performances for this session
1115 if (Module::$_log_modules_perfs === null) {
1116 $modulo = _PS_DEBUG_PROFILING_ ? 1 : Configuration::get('PS_log_modules_perfs_MODULO');
1117 Module::$_log_modules_perfs = ($modulo && mt_rand(0, $modulo - 1) == 0);
1118 if (Module::$_log_modules_perfs) {
1119 Module::$_log_modules_perfs_session = mt_rand();
1120 }
1121 }
1122
1123 // Store time and memory before and after hook call and save the result in the database
1124 if (Module::$_log_modules_perfs) {
1125 $time_start = microtime(true);
1126 $memory_start = memory_get_usage(true);
1127 }
1128
1129 include_once(_PS_MODULE_DIR_.$module_name.'/'.$module_name.'.php');
1130
1131 $r = false;
1132 if (Tools::file_exists_no_cache(_PS_OVERRIDE_DIR_.'modules/'.$module_name.'/'.$module_name.'.php')) {
1133 include_once(_PS_OVERRIDE_DIR_.'modules/'.$module_name.'/'.$module_name.'.php');
1134 $override = $module_name.'Override';
1135
1136 if (class_exists($override, false)) {
1137 $r = self::$_INSTANCE[$module_name] = Adapter_ServiceLocator::get($override);
1138 }
1139 }
1140
1141 if (!$r && class_exists($module_name, false)) {
1142 $r = self::$_INSTANCE[$module_name] = Adapter_ServiceLocator::get($module_name);
1143 }
1144
1145 if (Module::$_log_modules_perfs) {
1146 $time_end = microtime(true);
1147 $memory_end = memory_get_usage(true);
1148
1149 Db::getInstance()->execute('
1150 INSERT INTO '._DB_PREFIX_.'modules_perfs (session, module, method, time_start, time_end, memory_start, memory_end)
1151 VALUES ('.(int)Module::$_log_modules_perfs_session.', "'.pSQL($module_name).'", "__construct", "'.pSQL($time_start).'", "'.pSQL($time_end).'", '.(int)$memory_start.', '.(int)$memory_end.')');
1152 }
1153
1154 return $r;
1155 }
1156
1157 /**
1158 * Return an instance of the specified module
1159 *
1160 * @param int $id_module Module ID
1161 * @return Module instance
1162 */
1163 public static function getInstanceById($id_module)
1164 {
1165 static $id2name = null;
1166
1167 if (is_null($id2name)) {
1168 $id2name = array();
1169 $sql = 'SELECT `id_module`, `name` FROM `'._DB_PREFIX_.'module`';
1170 if ($results = Db::getInstance()->executeS($sql)) {
1171 foreach ($results as $row) {
1172 $id2name[$row['id_module']] = $row['name'];
1173 }
1174 }
1175 }
1176
1177 if (isset($id2name[$id_module])) {
1178 return Module::getInstanceByName($id2name[$id_module]);
1179 }
1180
1181 return false;
1182 }
1183
1184 public static function configXmlStringFormat($string)
1185 {
1186 return Tools::htmlentitiesDecodeUTF8($string);
1187 }
1188
1189
1190 public static function getModuleName($module)
1191 {
1192 $iso = substr(Context::getContext()->language->iso_code, 0, 2);
1193
1194 // Config file
1195 $config_file = _PS_MODULE_DIR_.$module.'/config_'.$iso.'.xml';
1196 // For "en" iso code, we keep the default config.xml name
1197 if ($iso == 'en' || !file_exists($config_file)) {
1198 $config_file = _PS_MODULE_DIR_.$module.'/config.xml';
1199 if (!file_exists($config_file)) {
1200 return 'Module '.ucfirst($module);
1201 }
1202 }
1203
1204 // Load config.xml
1205 libxml_use_internal_errors(true);
1206 $xml_module = @simplexml_load_file($config_file);
1207 if (!$xml_module) {
1208 return 'Module '.ucfirst($module);
1209 }
1210 foreach (libxml_get_errors() as $error) {
1211 libxml_clear_errors();
1212 return 'Module '.ucfirst($module);
1213 }
1214 libxml_clear_errors();
1215
1216 // Find translations
1217 global $_MODULES;
1218 $file = _PS_MODULE_DIR_.$module.'/'.Context::getContext()->language->iso_code.'.php';
1219 if (Tools::file_exists_cache($file) && include_once($file)) {
1220 if (isset($_MODULE) && is_array($_MODULE)) {
1221 $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
1222 }
1223 }
1224
1225 // Return Name
1226 return Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->displayName), (string)$xml_module->name);
1227 }
1228
1229 protected static function useTooMuchMemory()
1230 {
1231 $memory_limit = Tools::getMemoryLimit();
1232 if (function_exists('memory_get_usage') && $memory_limit != '-1') {
1233 $current_memory = memory_get_usage(true);
1234 $memory_threshold = (int)max($memory_limit * 0.15, Tools::isX86_64arch() ? 4194304 : 2097152);
1235 $memory_left = $memory_limit - $current_memory;
1236
1237 if ($memory_left <= $memory_threshold) {
1238 return true;
1239 }
1240 }
1241 return false;
1242 }
1243
1244 /**
1245 * Return available modules
1246 *
1247 * @param bool $use_config in order to use config.xml file in module dir
1248 * @return array Modules
1249 */
1250 public static function getModulesOnDisk($use_config = false, $logged_on_addons = false, $id_employee = false)
1251 {
1252 global $_MODULES;
1253
1254 // Init var
1255 $module_list = array();
1256 $module_name_list = array();
1257 $modules_name_to_cursor = array();
1258 $errors = array();
1259
1260 // Get modules directory list and memory limit
1261 $modules_dir = Module::getModulesDirOnDisk();
1262
1263 $modules_installed = array();
1264 $result = Db::getInstance()->executeS('
1265 SELECT m.name, m.version, mp.interest, module_shop.enable_device
1266 FROM `'._DB_PREFIX_.'module` m
1267 '.Shop::addSqlAssociation('module', 'm').'
1268 LEFT JOIN `'._DB_PREFIX_.'module_preference` mp ON (mp.`module` = m.`name` AND mp.`id_employee` = '.(int)$id_employee.')');
1269 foreach ($result as $row) {
1270 $modules_installed[$row['name']] = $row;
1271 }
1272
1273 foreach ($modules_dir as $module) {
1274 if (Module::useTooMuchMemory()) {
1275 $errors[] = Tools::displayError('All modules cannot be loaded due to memory limit restrictions, please increase your memory_limit value on your server configuration');
1276 break;
1277 }
1278
1279 $iso = substr(Context::getContext()->language->iso_code, 0, 2);
1280
1281 // Check if config.xml module file exists and if it's not outdated
1282
1283 if ($iso == 'en') {
1284 $config_file = _PS_MODULE_DIR_.$module.'/config.xml';
1285 } else {
1286 $config_file = _PS_MODULE_DIR_.$module.'/config_'.$iso.'.xml';
1287 }
1288
1289 $xml_exist = (file_exists($config_file));
1290 $need_new_config_file = $xml_exist ? (@filemtime($config_file) < @filemtime(_PS_MODULE_DIR_.$module.'/'.$module.'.php')) : true;
1291
1292 // If config.xml exists and that the use config flag is at true
1293 if ($use_config && $xml_exist && !$need_new_config_file) {
1294 // Load config.xml
1295 libxml_use_internal_errors(true);
1296 $xml_module = @simplexml_load_file($config_file);
1297 if (!$xml_module) {
1298 $errors[] = Tools::displayError(sprintf('%1s could not be loaded.', $config_file));
1299 break;
1300 }
1301 foreach (libxml_get_errors() as $error) {
1302 $errors[] = '['.$module.'] '.Tools::displayError('Error found in config file:').' '.htmlentities($error->message);
1303 }
1304 libxml_clear_errors();
1305
1306 // If no errors in Xml, no need instand and no need new config.xml file, we load only translations
1307 if (!count($errors) && (int)$xml_module->need_instance == 0) {
1308 $file = _PS_MODULE_DIR_.$module.'/'.Context::getContext()->language->iso_code.'.php';
1309 if (Tools::file_exists_cache($file) && include_once($file)) {
1310 if (isset($_MODULE) && is_array($_MODULE)) {
1311 $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
1312 }
1313 }
1314
1315 $item = new stdClass();
1316 $item->id = 0;
1317 $item->warning = '';
1318
1319 foreach ($xml_module as $k => $v) {
1320 $item->$k = (string)$v;
1321 }
1322
1323 $item->displayName = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->displayName), (string)$xml_module->name));
1324 $item->description = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->description), (string)$xml_module->name));
1325 $item->author = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->author), (string)$xml_module->name));
1326 $item->author_uri = (isset($xml_module->author_uri) && $xml_module->author_uri) ? stripslashes($xml_module->author_uri) : false;
1327
1328 if (isset($xml_module->confirmUninstall)) {
1329 $item->confirmUninstall = Translate::getModuleTranslation((string)$xml_module->name, html_entity_decode(Module::configXmlStringFormat($xml_module->confirmUninstall)), (string)$xml_module->name);
1330 }
1331
1332 $item->active = 0;
1333 $item->onclick_option = false;
1334 $item->trusted = Module::isModuleTrusted($item->name);
1335
1336 $module_list[] = $item;
1337
1338 $module_name_list[] = '\''.pSQL($item->name).'\'';
1339 $modules_name_to_cursor[Tools::strtolower(strval($item->name))] = $item;
1340 }
1341 }
1342
1343 // If use config flag is at false or config.xml does not exist OR need instance OR need a new config.xml file
1344 if (!$use_config || !$xml_exist || (isset($xml_module->need_instance) && (int)$xml_module->need_instance == 1) || $need_new_config_file) {
1345 // If class does not exists, we include the file
1346 if (!class_exists($module, false)) {
1347 // Get content from php file
1348 $file_path = _PS_MODULE_DIR_.$module.'/'.$module.'.php';
1349 $file = trim(file_get_contents(_PS_MODULE_DIR_.$module.'/'.$module.'.php'));
1350
1351 if (substr($file, 0, 5) == '<?php') {
1352 $file = substr($file, 5);
1353 }
1354
1355 if (substr($file, -2) == '?>') {
1356 $file = substr($file, 0, -2);
1357 }
1358
1359 // If (false) is a trick to not load the class with "eval".
1360 // This way require_once will works correctly
1361 if (eval('if (false){ '.$file."\n".' }') !== false) {
1362 require_once(_PS_MODULE_DIR_.$module.'/'.$module.'.php');
1363 } else {
1364 $errors[] = sprintf(Tools::displayError('%1$s (parse error in %2$s)'), $module, substr($file_path, strlen(_PS_ROOT_DIR_)));
1365 }
1366 }
1367
1368 // If class exists, we just instanciate it
1369 if (class_exists($module, false)) {
1370 $tmp_module = Adapter_ServiceLocator::get($module);
1371
1372 $item = new stdClass();
1373 $item->id = $tmp_module->id;
1374 $item->warning = $tmp_module->warning;
1375 $item->name = $tmp_module->name;
1376 $item->version = $tmp_module->version;
1377 $item->tab = $tmp_module->tab;
1378 $item->displayName = $tmp_module->displayName;
1379 $item->description = stripslashes($tmp_module->description);
1380 $item->author = $tmp_module->author;
1381 $item->author_uri = (isset($tmp_module->author_uri) && $tmp_module->author_uri) ? $tmp_module->author_uri : false;
1382 $item->limited_countries = $tmp_module->limited_countries;
1383 $item->parent_class = get_parent_class($module);
1384 $item->is_configurable = $tmp_module->is_configurable = method_exists($tmp_module, 'getContent') ? 1 : 0;
1385 $item->need_instance = isset($tmp_module->need_instance) ? $tmp_module->need_instance : 0;
1386 $item->active = $tmp_module->active;
1387 $item->trusted = Module::isModuleTrusted($tmp_module->name);
1388 $item->currencies = isset($tmp_module->currencies) ? $tmp_module->currencies : null;
1389 $item->currencies_mode = isset($tmp_module->currencies_mode) ? $tmp_module->currencies_mode : null;
1390 $item->confirmUninstall = isset($tmp_module->confirmUninstall) ? html_entity_decode($tmp_module->confirmUninstall) : null;
1391 $item->description_full = stripslashes($tmp_module->description_full);
1392 $item->additional_description = isset($tmp_module->additional_description) ? stripslashes($tmp_module->additional_description) : null;
1393 $item->compatibility = isset($tmp_module->compatibility) ? (array)$tmp_module->compatibility : null;
1394 $item->nb_rates = isset($tmp_module->nb_rates) ? (array)$tmp_module->nb_rates : null;
1395 $item->avg_rate = isset($tmp_module->avg_rate) ? (array)$tmp_module->avg_rate : null;
1396 $item->badges = isset($tmp_module->badges) ? (array)$tmp_module->badges : null;
1397 $item->url = isset($tmp_module->url) ? $tmp_module->url : null;
1398 $item->onclick_option = method_exists($module, 'onclickOption') ? true : false;
1399
1400 if ($item->onclick_option) {
1401 $href = Context::getContext()->link->getAdminLink('Module', true).'&module_name='.$tmp_module->name.'&tab_module='.$tmp_module->tab;
1402 $item->onclick_option_content = array();
1403 $option_tab = array('desactive', 'reset', 'configure', 'delete');
1404
1405 foreach ($option_tab as $opt) {
1406 $item->onclick_option_content[$opt] = $tmp_module->onclickOption($opt, $href);
1407 }
1408 }
1409
1410 $module_list[] = $item;
1411
1412 if (!$xml_exist || $need_new_config_file) {
1413 self::$_generate_config_xml_mode = true;
1414 $tmp_module->_generateConfigXml();
1415 self::$_generate_config_xml_mode = false;
1416 }
1417
1418 unset($tmp_module);
1419 } else {
1420 $errors[] = sprintf(Tools::displayError('%1$s (class missing in %2$s)'), $module, substr($file_path, strlen(_PS_ROOT_DIR_)));
1421 }
1422 }
1423 }
1424
1425 // Get modules information from database
1426 if (!empty($module_name_list)) {
1427 $list = Shop::getContextListShopID();
1428 $sql = 'SELECT m.id_module, m.name, (
1429 SELECT COUNT(*) FROM '._DB_PREFIX_.'module_shop ms WHERE m.id_module = ms.id_module AND ms.id_shop IN ('.implode(',', $list).')
1430 ) as total
1431 FROM '._DB_PREFIX_.'module m
1432 WHERE LOWER(m.name) IN ('.Tools::strtolower(implode(',', $module_name_list)).')';
1433 $results = Db::getInstance()->executeS($sql);
1434
1435 foreach ($results as $result) {
1436 if (isset($modules_name_to_cursor[Tools::strtolower($result['name'])])) {
1437 $module_cursor = $modules_name_to_cursor[Tools::strtolower($result['name'])];
1438 $module_cursor->id = (int)$result['id_module'];
1439 $module_cursor->active = ($result['total'] == count($list)) ? 1 : 0;
1440 }
1441 }
1442 }
1443
1444 // Get Default Country Modules and customer module
1445 $files_list = array(
1446 array('type' => 'addonsNative', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST, 'loggedOnAddons' => 0),
1447 array('type' => 'addonsMustHave', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_MUST_HAVE_MODULES_LIST, 'loggedOnAddons' => 0),
1448 array('type' => 'addonsBought', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_CUSTOMER_MODULES_LIST, 'loggedOnAddons' => 1),
1449 );
1450 foreach ($files_list as $f) {
1451 if (file_exists($f['file']) && ($f['loggedOnAddons'] == 0 || $logged_on_addons)) {
1452 if (Module::useTooMuchMemory()) {
1453 $errors[] = Tools::displayError('All modules cannot be loaded due to memory limit restrictions, please increase your memory_limit value on your server configuration');
1454 break;
1455 }
1456
1457 $file = $f['file'];
1458 $content = Tools::file_get_contents($file);
1459 $xml = @simplexml_load_string($content, null, LIBXML_NOCDATA);
1460
1461 if ($xml && isset($xml->module)) {
1462 foreach ($xml->module as $modaddons) {
1463 $flag_found = 0;
1464
1465 foreach ($module_list as $k => &$m) {
1466 if (Tools::strtolower($m->name) == Tools::strtolower($modaddons->name) && !isset($m->available_on_addons)) {
1467 $flag_found = 1;
1468 if ($m->version != $modaddons->version && version_compare($m->version, $modaddons->version) === -1) {
1469 $module_list[$k]->version_addons = $modaddons->version;
1470 }
1471 }
1472 }
1473
1474 if ($flag_found == 0) {
1475 $item = new stdClass();
1476 $item->id = 0;
1477 $item->warning = '';
1478 $item->type = strip_tags((string)$f['type']);
1479 $item->name = strip_tags((string)$modaddons->name);
1480 $item->version = strip_tags((string)$modaddons->version);
1481 $item->tab = strip_tags((string)$modaddons->tab);
1482 $item->displayName = strip_tags((string)$modaddons->displayName);
1483 $item->description = stripslashes(strip_tags((string)$modaddons->description));
1484 $item->description_full = stripslashes(strip_tags((string)$modaddons->description_full));
1485 $item->author = strip_tags((string)$modaddons->author);
1486 $item->limited_countries = array();
1487 $item->parent_class = '';
1488 $item->onclick_option = false;
1489 $item->is_configurable = 0;
1490 $item->need_instance = 0;
1491 $item->not_on_disk = 1;
1492 $item->available_on_addons = 1;
1493 $item->trusted = Module::isModuleTrusted($item->name);
1494 $item->active = 0;
1495 $item->description_full = stripslashes($modaddons->description_full);
1496 $item->additional_description = isset($modaddons->additional_description) ? stripslashes($modaddons->additional_description) : null;
1497 $item->compatibility = isset($modaddons->compatibility) ? (array)$modaddons->compatibility : null;
1498 $item->nb_rates = isset($modaddons->nb_rates) ? (array)$modaddons->nb_rates : null;
1499 $item->avg_rate = isset($modaddons->avg_rate) ? (array)$modaddons->avg_rate : null;
1500 $item->badges = isset($modaddons->badges) ? (array)$modaddons->badges : null;
1501 $item->url = isset($modaddons->url) ? $modaddons->url : null;
1502
1503 if (isset($modaddons->img)) {
1504 if (!file_exists(_PS_TMP_IMG_DIR_.md5((int)$modaddons->id.'-'.$modaddons->name).'.jpg')) {
1505 if (!file_put_contents(_PS_TMP_IMG_DIR_.md5((int)$modaddons->id.'-'.$modaddons->name).'.jpg', Tools::file_get_contents($modaddons->img))) {
1506 copy(_PS_IMG_DIR_.'404.gif', _PS_TMP_IMG_DIR_.md5((int)$modaddons->id.'-'.$modaddons->name).'.jpg');
1507 }
1508 }
1509
1510 if (file_exists(_PS_TMP_IMG_DIR_.md5((int)$modaddons->id.'-'.$modaddons->name).'.jpg')) {
1511 $item->image = '../img/tmp/'.md5((int)$modaddons->id.'-'.$modaddons->name).'.jpg';
1512 }
1513 }
1514
1515 if ($item->type == 'addonsMustHave') {
1516 $item->addons_buy_url = strip_tags((string)$modaddons->url);
1517 $prices = (array)$modaddons->price;
1518 $id_default_currency = Configuration::get('PS_CURRENCY_DEFAULT');
1519
1520 foreach ($prices as $currency => $price) {
1521 if ($id_currency = Currency::getIdByIsoCode($currency)) {
1522 $item->price = (float)$price;
1523 $item->id_currency = (int)$id_currency;
1524
1525 if ($id_default_currency == $id_currency) {
1526 break;
1527 }
1528 }
1529 }
1530 }
1531
1532 $module_list[$modaddons->id.'-'.$item->name] = $item;
1533 }
1534 }
1535 }
1536 }
1537 }
1538
1539 foreach ($module_list as $key => &$module) {
1540 if (defined('_PS_HOST_MODE_') && in_array($module->name, self::$hosted_modules_blacklist)) {
1541 unset($module_list[$key]);
1542 } elseif (isset($modules_installed[$module->name])) {
1543 $module->installed = true;
1544 $module->database_version = $modules_installed[$module->name]['version'];
1545 $module->interest = $modules_installed[$module->name]['interest'];
1546 $module->enable_device = $modules_installed[$module->name]['enable_device'];
1547 } else {
1548 $module->installed = false;
1549 $module->database_version = 0;
1550 $module->interest = 0;
1551 }
1552 }
1553
1554 usort($module_list, create_function('$a,$b', 'return strnatcasecmp($a->displayName, $b->displayName);'));
1555 if ($errors) {
1556 if (!isset(Context::getContext()->controller) && !Context::getContext()->controller->controller_name) {
1557 echo '<div class="alert error"><h3>'.Tools::displayError('The following module(s) could not be loaded').':</h3><ol>';
1558 foreach ($errors as $error) {
1559 echo '<li>'.$error.'</li>';
1560 }
1561 echo '</ol></div>';
1562 } else {
1563 foreach ($errors as $error) {
1564 Context::getContext()->controller->errors[] = $error;
1565 }
1566 }
1567 }
1568
1569 return $module_list;
1570 }
1571
1572 /**
1573 * Return modules directory list
1574 *
1575 * @return array Modules Directory List
1576 */
1577 public static function getModulesDirOnDisk()
1578 {
1579 $module_list = array();
1580 $modules = scandir(_PS_MODULE_DIR_);
1581 foreach ($modules as $name) {
1582 if (is_file(_PS_MODULE_DIR_.$name)) {
1583 continue;
1584 } elseif (is_dir(_PS_MODULE_DIR_.$name.DIRECTORY_SEPARATOR) && Tools::file_exists_cache(_PS_MODULE_DIR_.$name.'/'.$name.'.php')) {
1585 if (!Validate::isModuleName($name)) {
1586 throw new PrestaShopException(sprintf('Module %s is not a valid module name', $name));
1587 }
1588 $module_list[] = $name;
1589 }
1590 }
1591
1592 return $module_list;
1593 }
1594
1595
1596 /**
1597 * Return non native module
1598 *
1599 * @param int $position Take only positionnables modules
1600 * @return array Modules
1601 */
1602 public static function getNonNativeModuleList()
1603 {
1604 $db = Db::getInstance();
1605 $module_list_xml = _PS_ROOT_DIR_.self::CACHE_FILE_MODULES_LIST;
1606 $native_modules = @simplexml_load_file($module_list_xml);
1607 if ($native_modules) {
1608 $native_modules = $native_modules->modules;
1609 }
1610
1611 $arr_native_modules = array();
1612 if (is_array($native_modules)) {
1613 foreach ($native_modules as $native_modules_type) {
1614 if (in_array($native_modules_type['type'], array('native', 'partner'))) {
1615 $arr_native_modules[] = '""';
1616 foreach ($native_modules_type->module as $module) {
1617 $arr_native_modules[] = '"'.pSQL($module['name']).'"';
1618 }
1619 }
1620 }
1621 }
1622
1623 if ($arr_native_modules) {
1624 return $db->executeS('SELECT * FROM `'._DB_PREFIX_.'module` m WHERE `name` NOT IN ('.implode(',', $arr_native_modules).') ');
1625 }
1626 return false;
1627 }
1628
1629 public static function getNativeModuleList()
1630 {
1631 $module_list_xml = _PS_ROOT_DIR_.self::CACHE_FILE_MODULES_LIST;
1632 if (!file_exists($module_list_xml)) {
1633 return false;
1634 }
1635
1636 $native_modules = @simplexml_load_file($module_list_xml);
1637
1638 if ($native_modules) {
1639 $native_modules = $native_modules->modules;
1640 }
1641
1642 $modules = array();
1643 if (is_object($native_modules)) {
1644 foreach ($native_modules as $native_modules_type) {
1645 if (in_array($native_modules_type['type'], array('native', 'partner'))) {
1646 foreach ($native_modules_type->module as $module) {
1647 $modules[] = $module['name'];
1648 }
1649 }
1650 }
1651 }
1652 if ($modules) {
1653 return $modules;
1654 }
1655 return false;
1656 }
1657
1658 /**
1659 * Return installed modules
1660 *
1661 * @param int $position Take only positionnables modules
1662 * @return array Modules
1663 */
1664 public static function getModulesInstalled($position = 0)
1665 {
1666 $sql = 'SELECT m.* FROM `'._DB_PREFIX_.'module` m ';
1667 if ($position) {
1668 $sql .= 'LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON m.`id_module` = hm.`id_module`
1669 LEFT JOIN `'._DB_PREFIX_.'hook` k ON hm.`id_hook` = k.`id_hook`
1670 WHERE k.`position` = 1
1671 GROUP BY m.id_module';
1672 }
1673 return Db::getInstance()->executeS($sql);
1674 }
1675
1676 /**
1677 * Return if the module is provided by addons.prestashop.com or not
1678 *
1679 * @param string $name The module name (the folder name)
1680 * @param string $key The key provided by addons
1681 * @return int
1682 */
1683 final public static function isModuleTrusted($module_name)
1684 {
1685 static $trusted_modules_list_content = null;
1686 static $modules_list_content = null;
1687 static $default_country_modules_list_content = null;
1688 static $untrusted_modules_list_content = null;
1689
1690 $context = Context::getContext();
1691
1692 // If the xml file exist, isn't empty, isn't too old
1693 // and if the theme hadn't change
1694 // we use the file, otherwise we regenerate it
1695 if (!(file_exists(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST)
1696 && filesize(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST) > 0
1697 && ((time() - filemtime(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST)) < 86400)
1698 )) {
1699 self::generateTrustedXml();
1700 }
1701
1702 if ($trusted_modules_list_content === null) {
1703 $trusted_modules_list_content = Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST);
1704 if (strpos($trusted_modules_list_content, $context->theme->name) === false) {
1705 self::generateTrustedXml();
1706 }
1707 }
1708
1709 if ($modules_list_content === null) {
1710 $modules_list_content = Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_MODULES_LIST);
1711 }
1712
1713 if ($default_country_modules_list_content === null) {
1714 $default_country_modules_list_content = Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST);
1715 }
1716
1717 if ($untrusted_modules_list_content === null) {
1718 $untrusted_modules_list_content = Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_UNTRUSTED_MODULES_LIST);
1719 }
1720
1721 // If the module is trusted, which includes both partner modules and modules bought on Addons
1722
1723 if (stripos($trusted_modules_list_content, $module_name) !== false) {
1724 // If the module is not a partner, then return 1 (which means the module is "trusted")
1725 if (stripos($modules_list_content, '<module name="'.$module_name.'"/>') == false) {
1726 return 1;
1727 } elseif (stripos($default_country_modules_list_content, '<name><![CDATA['.$module_name.']]></name>') !== false) {
1728 // 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")
1729 return 1;
1730 }
1731 // The module seems to be trusted, but it does not seem to be dedicated to this country
1732 return 2;
1733 } elseif (stripos($untrusted_modules_list_content, $module_name) !== false) {
1734 // If the module is already in the untrusted list, then return 0 (untrusted)
1735 return 0;
1736 } else {
1737 // If the module isn't in one of the xml files
1738 // It might have been uploaded recenlty so we check
1739 // Addons API and clear XML files to be regenerated next time
1740 Tools::deleteFile(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST);
1741 Tools::deleteFile(_PS_ROOT_DIR_.self::CACHE_FILE_UNTRUSTED_MODULES_LIST);
1742
1743 return (int)Module::checkModuleFromAddonsApi($module_name);
1744 }
1745 }
1746
1747 /**
1748 * Generate XML files for trusted and untrusted modules
1749 *
1750 */
1751 final public static function generateTrustedXml()
1752 {
1753 $modules_on_disk = Module::getModulesDirOnDisk();
1754 $trusted = array();
1755 $untrusted = array();
1756
1757 $trusted_modules_xml = array(
1758 _PS_ROOT_DIR_.self::CACHE_FILE_ALL_COUNTRY_MODULES_LIST,
1759 _PS_ROOT_DIR_.self::CACHE_FILE_MUST_HAVE_MODULES_LIST,
1760 );
1761
1762 if (file_exists(_PS_ROOT_DIR_.self::CACHE_FILE_CUSTOMER_MODULES_LIST)) {
1763 $trusted_modules_xml[] = _PS_ROOT_DIR_.self::CACHE_FILE_CUSTOMER_MODULES_LIST;
1764 }
1765
1766 // Create 2 arrays with trusted and untrusted modules
1767 foreach ($trusted_modules_xml as $file) {
1768 $content = Tools::file_get_contents($file);
1769 $xml = @simplexml_load_string($content, null, LIBXML_NOCDATA);
1770
1771 if ($xml && isset($xml->module)) {
1772 foreach ($xml->module as $modaddons) {
1773 $trusted[] = Tools::strtolower((string)$modaddons->name);
1774 }
1775 }
1776 }
1777
1778 foreach (glob(_PS_ROOT_DIR_.'/config/xml/themes/*.xml') as $theme_xml) {
1779 if (file_exists($theme_xml)) {
1780 $content = Tools::file_get_contents($theme_xml);
1781 $xml = @simplexml_load_string($content, null, LIBXML_NOCDATA);
1782
1783 if ($xml) {
1784 foreach ($xml->modules->module as $modaddons) {
1785 if ((string)$modaddons['action'] == 'install') {
1786 $trusted[] = Tools::strtolower((string)$modaddons['name']);
1787 }
1788 }
1789 }
1790 }
1791 }
1792
1793 foreach ($modules_on_disk as $name) {
1794 if (!in_array($name, $trusted)) {
1795 if (Module::checkModuleFromAddonsApi($name)) {
1796 $trusted[] = Tools::strtolower($name);
1797 } else {
1798 $untrusted[] = Tools::strtolower($name);
1799 }
1800 }
1801 }
1802
1803 $context = Context::getContext();
1804 $theme = new Theme($context->shop->id_theme);
1805
1806 // Save the 2 arrays into XML files
1807 $trusted_xml = new SimpleXMLElement('<modules_list/>');
1808 $trusted_xml->addAttribute('theme', $theme->name);
1809 $modules = $trusted_xml->addChild('modules');
1810 $modules->addAttribute('type', 'trusted');
1811 foreach ($trusted as $key => $name) {
1812 $module = $modules->addChild('module');
1813 $module->addAttribute('name', $name);
1814 }
1815 $success = file_put_contents(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST, $trusted_xml->asXML());
1816
1817 $untrusted_xml = new SimpleXMLElement('<modules_list/>');
1818 $modules = $untrusted_xml->addChild('modules');
1819 $modules->addAttribute('type', 'untrusted');
1820 foreach ($untrusted as $key => $name) {
1821 $module = $modules->addChild('module');
1822 $module->addAttribute('name', $name);
1823 }
1824 $success &= file_put_contents(_PS_ROOT_DIR_.self::CACHE_FILE_UNTRUSTED_MODULES_LIST, $untrusted_xml->asXML());
1825
1826 if ($success) {
1827 return true;
1828 } else {
1829 Tools::displayError('Trusted and Untrusted XML have not been generated properly');
1830 }
1831 }
1832
1833 /**
1834 * Create the Addons API call from the module name only
1835 *
1836 * @param string $name Module dir name
1837 * @return bool Returns if the module is trusted by addons.prestashop.com
1838 */
1839 final public static function checkModuleFromAddonsApi($module_name)
1840 {
1841 $obj = Module::getInstanceByName($module_name);
1842
1843 if (!is_object($obj)) {
1844 return false;
1845 } elseif ($obj->module_key === '') {
1846 return false;
1847 } else {
1848 $params = array(
1849 'module_name' => $obj->name,
1850 'module_key' => $obj->module_key,
1851 );
1852 $xml = Tools::addonsRequest('check_module', $params);
1853 return (bool)(strpos($xml, 'success') !== false);
1854 }
1855 }
1856
1857 /**
1858 * Execute modules for specified hook
1859 *
1860 * @deprecated 1.5.3.0
1861 * @param string $hook_name Hook Name
1862 * @param array $hook_args Parameters for the functions
1863 * @return string modules output
1864 */
1865 public static function hookExec($hook_name, $hook_args = array(), $id_module = null)
1866 {
1867 Tools::displayAsDeprecated();
1868 return Hook::exec($hook_name, $hook_args, $id_module);
1869 }
1870
1871 /**
1872 * @deprecated 1.5.3.0
1873 * @return string
1874 * @throws PrestaShopException
1875 */
1876 public static function hookExecPayment()
1877 {
1878 Tools::displayAsDeprecated();
1879 return Hook::exec('displayPayment');
1880 }
1881
1882 public static function preCall($module_name)
1883 {
1884 return true;
1885 }
1886
1887 /**
1888 * @deprecated since 1.6.0.2
1889 */
1890 public static function getPaypalIgnore()
1891 {
1892 Tools::displayAsDeprecated();
1893 }
1894
1895 /**
1896 * Returns the list of the payment module associated to the current customer
1897 * @see PaymentModule::getInstalledPaymentModules() if you don't care about the context
1898 *
1899 * @return array module informations
1900 */
1901 public static function getPaymentModules()
1902 {
1903 $context = Context::getContext();
1904 if (isset($context->cart)) {
1905 $billing = new Address((int)$context->cart->id_address_invoice);
1906 }
1907
1908 $use_groups = Group::isFeatureActive();
1909
1910 $frontend = true;
1911 $groups = array();
1912 if (isset($context->employee)) {
1913 $frontend = false;
1914 } elseif (isset($context->customer) && $use_groups) {
1915 $groups = $context->customer->getGroups();
1916 if (!count($groups)) {
1917 $groups = array(Configuration::get('PS_UNIDENTIFIED_GROUP'));
1918 }
1919 }
1920
1921 $hook_payment = 'Payment';
1922 if (Db::getInstance()->getValue('SELECT `id_hook` FROM `'._DB_PREFIX_.'hook` WHERE `name` = \'displayPayment\'')) {
1923 $hook_payment = 'displayPayment';
1924 }
1925
1926 $list = Shop::getContextListShopID();
1927
1928 return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT DISTINCT m.`id_module`, h.`id_hook`, m.`name`, hm.`position`
1929 FROM `'._DB_PREFIX_.'module` m
1930 '.($frontend ? 'LEFT JOIN `'._DB_PREFIX_.'module_country` mc ON (m.`id_module` = mc.`id_module` AND mc.id_shop = '.(int)$context->shop->id.')' : '').'
1931 '.($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.')' : '').'
1932 '.($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.')' : '').'
1933 LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON hm.`id_module` = m.`id_module`
1934 LEFT JOIN `'._DB_PREFIX_.'hook` h ON hm.`id_hook` = h.`id_hook`
1935 WHERE h.`name` = \''.pSQL($hook_payment).'\'
1936 '.(isset($billing) && $frontend ? 'AND mc.id_country = '.(int)$billing->id_country : '').'
1937 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).'
1938 AND hm.id_shop IN('.implode(', ', $list).')
1939 '.((count($groups) && $frontend && $use_groups) ? 'AND (mg.`id_group` IN ('.implode(', ', $groups).'))' : '').'
1940 GROUP BY hm.id_hook, hm.id_module
1941 ORDER BY hm.`position`, m.`name` DESC');
1942 }
1943
1944 /**
1945 * @deprecated 1.5.0 Use Translate::getModuleTranslation()
1946 */
1947 public static function findTranslation($name, $string, $source)
1948 {
1949 return Translate::getModuleTranslation($name, $string, $source);
1950 }
1951
1952 /**
1953 * Get translation for a given module text
1954 *
1955 * Note: $specific parameter is mandatory for library files.
1956 * Otherwise, translation key will not match for Module library
1957 * when module is loaded with eval() Module::getModulesOnDisk()
1958 *
1959 * @param string $string String to translate
1960 * @param bool|string $specific filename to use in translation key
1961 * @return string Translation
1962 */
1963 public function l($string, $specific = false)
1964 {
1965 if (self::$_generate_config_xml_mode) {
1966 return $string;
1967 }
1968
1969 return Translate::getModuleTranslation($this, $string, ($specific) ? $specific : $this->name);
1970 }
1971
1972 /*
1973 * Reposition module
1974 *
1975 * @param bool $id_hook Hook ID
1976 * @param bool $way Up (0) or Down (1)
1977 * @param int $position
1978 */
1979 public function updatePosition($id_hook, $way, $position = null)
1980 {
1981 foreach (Shop::getContextListShopID() as $shop_id) {
1982 $sql = 'SELECT hm.`id_module`, hm.`position`, hm.`id_hook`
1983 FROM `'._DB_PREFIX_.'hook_module` hm
1984 WHERE hm.`id_hook` = '.(int)$id_hook.' AND hm.`id_shop` = '.$shop_id.'
1985 ORDER BY hm.`position` '.($way ? 'ASC' : 'DESC');
1986 if (!$res = Db::getInstance()->executeS($sql)) {
1987 continue;
1988 }
1989
1990 foreach ($res as $key => $values) {
1991 if ((int)$values[$this->identifier] == (int)$this->id) {
1992 $k = $key;
1993 break;
1994 }
1995 }
1996 if (!isset($k) || !isset($res[$k]) || !isset($res[$k + 1])) {
1997 return false;
1998 }
1999
2000 $from = $res[$k];
2001 $to = $res[$k + 1];
2002
2003 if (isset($position) && !empty($position)) {
2004 $to['position'] = (int)$position;
2005 }
2006
2007 $sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
2008 SET `position`= position '.($way ? '-1' : '+1').'
2009 WHERE position between '.(int)(min(array($from['position'], $to['position']))).' AND '.max(array($from['position'], $to['position'])).'
2010 AND `id_hook` = '.(int)$from['id_hook'].' AND `id_shop` = '.$shop_id;
2011 if (!Db::getInstance()->execute($sql)) {
2012 return false;
2013 }
2014
2015 $sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
2016 SET `position`='.(int)$to['position'].'
2017 WHERE `'.pSQL($this->identifier).'` = '.(int)$from[$this->identifier].'
2018 AND `id_hook` = '.(int)$to['id_hook'].' AND `id_shop` = '.$shop_id;
2019 if (!Db::getInstance()->execute($sql)) {
2020 return false;
2021 }
2022 }
2023 return true;
2024 }
2025
2026 /*
2027 * Reorder modules position
2028 *
2029 * @param bool $id_hook Hook ID
2030 * @param array $shop_list List of shop
2031 */
2032 public function cleanPositions($id_hook, $shop_list = null)
2033 {
2034 $sql = 'SELECT `id_module`, `id_shop`
2035 FROM `'._DB_PREFIX_.'hook_module`
2036 WHERE `id_hook` = '.(int)$id_hook.'
2037 '.((!is_null($shop_list) && $shop_list) ? ' AND `id_shop` IN('.implode(', ', array_map('intval', $shop_list)).')' : '').'
2038 ORDER BY `position`';
2039 $results = Db::getInstance()->executeS($sql);
2040 $position = array();
2041 foreach ($results as $row) {
2042 if (!isset($position[$row['id_shop']])) {
2043 $position[$row['id_shop']] = 1;
2044 }
2045
2046 $sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
2047 SET `position` = '.$position[$row['id_shop']].'
2048 WHERE `id_hook` = '.(int)$id_hook.'
2049 AND `id_module` = '.$row['id_module'].' AND `id_shop` = '.$row['id_shop'];
2050 Db::getInstance()->execute($sql);
2051 $position[$row['id_shop']]++;
2052 }
2053
2054 return true;
2055 }
2056
2057 /**
2058 * Helper displaying error message(s)
2059 * @param string|array $error
2060 * @return string
2061 */
2062 public function displayError($error)
2063 {
2064 $output = '
2065 <div class="bootstrap">
2066 <div class="module_error alert alert-danger" >
2067 <button type="button" class="close" data-dismiss="alert">×</button>';
2068
2069 if (is_array($error)) {
2070 $output .= '<ul>';
2071 foreach ($error as $msg) {
2072 $output .= '<li>'.$msg.'</li>';
2073 }
2074 $output .= '</ul>';
2075 } else {
2076 $output .= $error;
2077 }
2078
2079 // Close div openned previously
2080 $output .= '</div></div>';
2081
2082 $this->error = true;
2083 return $output;
2084 }
2085
2086 /**
2087 * Helper displaying warning message(s)
2088 * @param string|array $error
2089 * @return string
2090 */
2091 public function displayWarning($warning)
2092 {
2093 $output = '
2094 <div class="bootstrap">
2095 <div class="module_warning alert alert-warning" >
2096 <button type="button" class="close" data-dismiss="alert">×</button>';
2097
2098 if (is_array($warning)) {
2099 $output .= '<ul>';
2100 foreach ($warning as $msg) {
2101 $output .= '<li>'.$msg.'</li>';
2102 }
2103 $output .= '</ul>';
2104 } else {
2105 $output .= $warning;
2106 }
2107
2108 // Close div openned previously
2109 $output .= '</div></div>';
2110
2111 return $output;
2112 }
2113
2114 public function displayConfirmation($string)
2115 {
2116 $output = '
2117 <div class="bootstrap">
2118 <div class="module_confirmation conf confirm alert alert-success">
2119 <button type="button" class="close" data-dismiss="alert">×</button>
2120 '.$string.'
2121 </div>
2122 </div>';
2123 return $output;
2124 }
2125
2126 /*
2127 * Return exceptions for module in hook
2128 *
2129 * @param int $id_module Module ID
2130 * @param int $id_hook Hook ID
2131 * @return array Exceptions
2132 */
2133 public static function getExceptionsStatic($id_module, $id_hook, $dispatch = false)
2134 {
2135 $cache_id = 'exceptionsCache';
2136 if (!Cache::isStored($cache_id)) {
2137 $exceptions_cache = array();
2138 $sql = 'SELECT * FROM `'._DB_PREFIX_.'hook_module_exceptions`
2139 WHERE `id_shop` IN ('.implode(', ', Shop::getContextListShopID()).')';
2140 $db = Db::getInstance();
2141 $result = $db->executeS($sql, false);
2142 while ($row = $db->nextRow($result)) {
2143 if (!$row['file_name']) {
2144 continue;
2145 }
2146 $key = $row['id_hook'].'-'.$row['id_module'];
2147 if (!isset($exceptions_cache[$key])) {
2148 $exceptions_cache[$key] = array();
2149 }
2150 if (!isset($exceptions_cache[$key][$row['id_shop']])) {
2151 $exceptions_cache[$key][$row['id_shop']] = array();
2152 }
2153 $exceptions_cache[$key][$row['id_shop']][] = $row['file_name'];
2154 }
2155 Cache::store($cache_id, $exceptions_cache);
2156 } else {
2157 $exceptions_cache = Cache::retrieve($cache_id);
2158 }
2159
2160 $key = $id_hook.'-'.$id_module;
2161 $array_return = array();
2162 if ($dispatch) {
2163 foreach (Shop::getContextListShopID() as $shop_id) {
2164 if (isset($exceptions_cache[$key], $exceptions_cache[$key][$shop_id])) {
2165 $array_return[$shop_id] = $exceptions_cache[$key][$shop_id];
2166 }
2167 }
2168 } else {
2169 foreach (Shop::getContextListShopID() as $shop_id) {
2170 if (isset($exceptions_cache[$key], $exceptions_cache[$key][$shop_id])) {
2171 foreach ($exceptions_cache[$key][$shop_id] as $file) {
2172 if (!in_array($file, $array_return)) {
2173 $array_return[] = $file;
2174 }
2175 }
2176 }
2177 }
2178 }
2179 return $array_return;
2180 }
2181 /*
2182 * Return exceptions for module in hook
2183 *
2184 * @param int $id_hook Hook ID
2185 * @return array Exceptions
2186 */
2187 public function getExceptions($id_hook, $dispatch = false)
2188 {
2189 return Module::getExceptionsStatic($this->id, $id_hook, $dispatch);
2190 }
2191
2192 public static function isInstalled($module_name)
2193 {
2194 if (!Cache::isStored('Module::isInstalled'.$module_name)) {
2195 $id_module = Module::getModuleIdByName($module_name);
2196 Cache::store('Module::isInstalled'.$module_name, (bool)$id_module);
2197 return (bool)$id_module;
2198 }
2199 return Cache::retrieve('Module::isInstalled'.$module_name);
2200 }
2201
2202 public function isEnabledForShopContext()
2203 {
2204 return (bool)Db::getInstance()->getValue('
2205 SELECT COUNT(*) n
2206 FROM `'._DB_PREFIX_.'module_shop`
2207 WHERE id_module='.(int)$this->id.' AND id_shop IN ('.implode(',', array_map('intval', Shop::getContextListShopID())).')
2208 GROUP BY id_module
2209 HAVING n='.(int)count(Shop::getContextListShopID())
2210 );
2211 }
2212
2213 public static function isEnabled($module_name)
2214 {
2215 if (!Cache::isStored('Module::isEnabled'.$module_name)) {
2216 $active = false;
2217 $id_module = Module::getModuleIdByName($module_name);
2218 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)) {
2219 $active = true;
2220 }
2221 Cache::store('Module::isEnabled'.$module_name, (bool)$active);
2222 return (bool)$active;
2223 }
2224 return Cache::retrieve('Module::isEnabled'.$module_name);
2225 }
2226
2227 public function isRegisteredInHook($hook)
2228 {
2229 if (!$this->id) {
2230 return false;
2231 }
2232
2233 $sql = 'SELECT COUNT(*)
2234 FROM `'._DB_PREFIX_.'hook_module` hm
2235 LEFT JOIN `'._DB_PREFIX_.'hook` h ON (h.`id_hook` = hm.`id_hook`)
2236 WHERE h.`name` = \''.pSQL($hook).'\' AND hm.`id_module` = '.(int)$this->id;
2237 return Db::getInstance()->getValue($sql);
2238 }
2239
2240 /*
2241 ** Template management (display, overload, cache)
2242 */
2243 protected static function _isTemplateOverloadedStatic($module_name, $template)
2244 {
2245 if (Tools::file_exists_cache(_PS_THEME_DIR_.'modules/'.$module_name.'/'.$template)) {
2246 return _PS_THEME_DIR_.'modules/'.$module_name.'/'.$template;
2247 } elseif (Tools::file_exists_cache(_PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/hook/'.$template)) {
2248 return _PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/hook/'.$template;
2249 } elseif (Tools::file_exists_cache(_PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/front/'.$template)) {
2250 return _PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/front/'.$template;
2251 } elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/views/templates/hook/'.$template)) {
2252 return false;
2253 } elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/views/templates/front/'.$template)) {
2254 return false;
2255 } elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/'.$template)) {
2256 return false;
2257 }
2258 return null;
2259 }
2260
2261 protected function _isTemplateOverloaded($template)
2262 {
2263 return Module::_isTemplateOverloadedStatic($this->name, $template);
2264 }
2265
2266 protected function getCacheId($name = null)
2267 {
2268 $cache_array = array();
2269 $cache_array[] = $name !== null ? $name : $this->name;
2270 if (Configuration::get('PS_SSL_ENABLED')) {
2271 $cache_array[] = (int)Tools::usingSecureMode();
2272 }
2273 if (Shop::isFeatureActive()) {
2274 $cache_array[] = (int)$this->context->shop->id;
2275 }
2276 if (Group::isFeatureActive() && isset($this->context->customer)) {
2277 $cache_array[] = (int)Group::getCurrent()->id;
2278 $cache_array[] = implode('_', Customer::getGroupsStatic($this->context->customer->id));
2279 }
2280 if (Language::isMultiLanguageActivated()) {
2281 $cache_array[] = (int)$this->context->language->id;
2282 }
2283 if (Currency::isMultiCurrencyActivated()) {
2284 $cache_array[] = (int)$this->context->currency->id;
2285 }
2286 $cache_array[] = (int)$this->context->country->id;
2287 return implode('|', $cache_array);
2288 }
2289
2290 public function display($file, $template, $cache_id = null, $compile_id = null)
2291 {
2292 if (($overloaded = Module::_isTemplateOverloadedStatic(basename($file, '.php'), $template)) === null) {
2293 return Tools::displayError('No template found for module').' '.basename($file, '.php');
2294 } else {
2295 if (Tools::getIsset('live_edit') || Tools::getIsset('live_configurator_token')) {
2296 $cache_id = null;
2297 }
2298
2299 $this->smarty->assign(array(
2300 'module_dir' => __PS_BASE_URI__.'modules/'.basename($file, '.php').'/',
2301 'module_template_dir' => ($overloaded ? _THEME_DIR_ : __PS_BASE_URI__).'modules/'.basename($file, '.php').'/',
2302 'allow_push' => $this->allow_push
2303 ));
2304
2305 if ($cache_id !== null) {
2306 Tools::enableCache();
2307 }
2308
2309 $result = $this->getCurrentSubTemplate($template, $cache_id, $compile_id)->fetch();
2310
2311 if ($cache_id !== null) {
2312 Tools::restoreCacheSettings();
2313 }
2314
2315 $this->resetCurrentSubTemplate($template, $cache_id, $compile_id);
2316
2317 return $result;
2318 }
2319 }
2320
2321 /**
2322 * @param string $template
2323 * @param string|null $cache_id
2324 * @param string|null $compile_id
2325 * @return Smarty_Internal_Template
2326 */
2327 protected function getCurrentSubTemplate($template, $cache_id = null, $compile_id = null)
2328 {
2329 if (!isset($this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id])) {
2330 $this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id] = $this->context->smarty->createTemplate(
2331 $this->getTemplatePath($template),
2332 $cache_id,
2333 $compile_id,
2334 $this->smarty
2335 );
2336 }
2337 return $this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id];
2338 }
2339
2340 protected function resetCurrentSubTemplate($template, $cache_id, $compile_id)
2341 {
2342 $this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id] = null;
2343 }
2344
2345 /**
2346 * Get realpath of a template of current module (check if template is overriden too)
2347 *
2348 * @since 1.5.0
2349 * @param string $template
2350 * @return string
2351 */
2352 public function getTemplatePath($template)
2353 {
2354 $overloaded = $this->_isTemplateOverloaded($template);
2355 if ($overloaded === null) {
2356 return null;
2357 }
2358
2359 if ($overloaded) {
2360 return $overloaded;
2361 } elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$this->name.'/views/templates/hook/'.$template)) {
2362 return _PS_MODULE_DIR_.$this->name.'/views/templates/hook/'.$template;
2363 } elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$this->name.'/views/templates/front/'.$template)) {
2364 return _PS_MODULE_DIR_.$this->name.'/views/templates/front/'.$template;
2365 } elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$this->name.'/'.$template)) {
2366 return _PS_MODULE_DIR_.$this->name.'/'.$template;
2367 } else {
2368 return null;
2369 }
2370 }
2371
2372 protected function _getApplicableTemplateDir($template)
2373 {
2374 return $this->_isTemplateOverloaded($template) ? _PS_THEME_DIR_ : _PS_MODULE_DIR_.$this->name.'/';
2375 }
2376
2377 public function isCached($template, $cache_id = null, $compile_id = null)
2378 {
2379 if (Tools::getIsset('live_edit') || Tools::getIsset('live_configurator_token')) {
2380 return false;
2381 }
2382 Tools::enableCache();
2383 $new_tpl = $this->getTemplatePath($template);
2384 $is_cached = $this->getCurrentSubTemplate($template, $cache_id, $compile_id)->isCached($new_tpl, $cache_id, $compile_id);
2385 Tools::restoreCacheSettings();
2386 return $is_cached;
2387 }
2388
2389
2390 /*
2391 * Clear template cache
2392 *
2393 * @param string $template Template name
2394 * @param int null $cache_id
2395 * @param int null $compile_id
2396 * @return int Number of template cleared
2397 */
2398 protected function _clearCache($template, $cache_id = null, $compile_id = null)
2399 {
2400 static $ps_smarty_clear_cache = null;
2401 if ($ps_smarty_clear_cache === null) {
2402 $ps_smarty_clear_cache = Configuration::get('PS_SMARTY_CLEAR_CACHE');
2403 }
2404
2405 if (self::$_batch_mode) {
2406 if ($ps_smarty_clear_cache == 'never') {
2407 return 0;
2408 }
2409
2410 if ($cache_id === null) {
2411 $cache_id = $this->name;
2412 }
2413
2414 $key = $template.'-'.$cache_id.'-'.$compile_id;
2415 if (!isset(self::$_defered_clearCache[$key])) {
2416 self::$_defered_clearCache[$key] = array($this->getTemplatePath($template), $cache_id, $compile_id);
2417 }
2418 } else {
2419 if ($ps_smarty_clear_cache == 'never') {
2420 return 0;
2421 }
2422
2423 if ($cache_id === null) {
2424 $cache_id = $this->name;
2425 }
2426
2427 Tools::enableCache();
2428 $number_of_template_cleared = Tools::clearCache(Context::getContext()->smarty, $this->getTemplatePath($template), $cache_id, $compile_id);
2429 Tools::restoreCacheSettings();
2430
2431 return $number_of_template_cleared;
2432 }
2433 }
2434
2435 /*
2436 * Clear defered template cache
2437 *
2438 * @param string $template_path Template path
2439 * @param int null $cache_id
2440 * @param int null $compile_id
2441 * @return int Number of template cleared
2442 */
2443 public static function _deferedClearCache($template_path, $cache_id, $compile_id)
2444 {
2445 Tools::enableCache();
2446 $number_of_template_cleared = Tools::clearCache(Context::getContext()->smarty, $template_path, $cache_id, $compile_id);
2447 Tools::restoreCacheSettings();
2448
2449 return $number_of_template_cleared;
2450 }
2451
2452 protected function _generateConfigXml()
2453 {
2454 $author_uri = '';
2455 if (isset($this->author_uri) && $this->author_uri) {
2456 $author_uri = '<author_uri><![CDATA['.Tools::htmlentitiesUTF8($this->author_uri).']]></author_uri>';
2457 }
2458
2459 $xml = '<?xml version="1.0" encoding="UTF-8" ?>
2460<module>
2461 <name>'.$this->name.'</name>
2462 <displayName><![CDATA['.str_replace('&', '&', Tools::htmlentitiesUTF8($this->displayName)).']]></displayName>
2463 <version><![CDATA['.$this->version.']]></version>
2464 <description><![CDATA['.str_replace('&', '&', Tools::htmlentitiesUTF8($this->description)).']]></description>
2465 <author><![CDATA['.str_replace('&', '&', Tools::htmlentitiesUTF8($this->author)).']]></author>'
2466 .$author_uri.'
2467 <tab><![CDATA['.Tools::htmlentitiesUTF8($this->tab).']]></tab>'.(isset($this->confirmUninstall) ? "\n\t".'<confirmUninstall><![CDATA['.$this->confirmUninstall.']]></confirmUninstall>' : '').'
2468 <is_configurable>'.(isset($this->is_configurable) ? (int)$this->is_configurable : 0).'</is_configurable>
2469 <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>' : '').'
2470</module>';
2471 if (is_writable(_PS_MODULE_DIR_.$this->name.'/')) {
2472 $iso = substr(Context::getContext()->language->iso_code, 0, 2);
2473 $file = _PS_MODULE_DIR_.$this->name.'/'.($iso == 'en' ? 'config.xml' : 'config_'.$iso.'.xml');
2474 if (!@file_put_contents($file, $xml)) {
2475 if (!is_writable($file)) {
2476 @unlink($file);
2477 @file_put_contents($file, $xml);
2478 }
2479 }
2480 @chmod($file, 0664);
2481 }
2482 }
2483
2484 /**
2485 * Check if the module is transplantable on the hook in parameter
2486 * @param string $hook_name
2487 * @return bool if module can be transplanted on hook
2488 */
2489 public function isHookableOn($hook_name)
2490 {
2491 $retro_hook_name = Hook::getRetroHookName($hook_name);
2492 return (is_callable(array($this, 'hook'.ucfirst($hook_name))) || is_callable(array($this, 'hook'.ucfirst($retro_hook_name))));
2493 }
2494
2495 /**
2496 * Check employee permission for module
2497 * @param array $variable (action)
2498 * @param object $employee
2499 * @return bool if module can be transplanted on hook
2500 */
2501 public function getPermission($variable, $employee = null)
2502 {
2503 return Module::getPermissionStatic($this->id, $variable, $employee);
2504 }
2505
2506 /**
2507 * Check employee permission for module (static method)
2508 * @param int $id_module
2509 * @param array $variable (action)
2510 * @param object $employee
2511 * @return bool if module can be transplanted on hook
2512 */
2513 public static function getPermissionStatic($id_module, $variable, $employee = null)
2514 {
2515 if (!in_array($variable, array('view', 'configure', 'uninstall'))) {
2516 return false;
2517 }
2518
2519 if (!$employee) {
2520 $employee = Context::getContext()->employee;
2521 }
2522
2523 if ($employee->id_profile == _PS_ADMIN_PROFILE_) {
2524 return true;
2525 }
2526
2527 if (!isset(self::$cache_permissions[$employee->id_profile])) {
2528 self::$cache_permissions[$employee->id_profile] = array();
2529 $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);
2530 foreach ($result as $row) {
2531 self::$cache_permissions[$employee->id_profile][$row['id_module']]['view'] = $row['view'];
2532 self::$cache_permissions[$employee->id_profile][$row['id_module']]['configure'] = $row['configure'];
2533 self::$cache_permissions[$employee->id_profile][$row['id_module']]['uninstall'] = $row['uninstall'];
2534 }
2535 }
2536
2537 if (!isset(self::$cache_permissions[$employee->id_profile][$id_module])) {
2538 throw new PrestaShopException('No access reference in table module_access for id_module '.$id_module.'.');
2539 }
2540
2541 return (bool)self::$cache_permissions[$employee->id_profile][$id_module][$variable];
2542 }
2543
2544 /**
2545 * Get Unauthorized modules for a client group
2546 *
2547 * @param int $group_id
2548 * @return array|null
2549 */
2550 public static function getAuthorizedModules($group_id)
2551 {
2552 return Db::getInstance()->executeS('
2553 SELECT m.`id_module`, m.`name` FROM `'._DB_PREFIX_.'module_group` mg
2554 LEFT JOIN `'._DB_PREFIX_.'module` m ON (m.`id_module` = mg.`id_module`)
2555 WHERE mg.`id_group` = '.(int)$group_id);
2556 }
2557
2558 /**
2559 * Get ID module by name
2560 *
2561 * @param string $name
2562 * @return int Module ID
2563 */
2564 public static function getModuleIdByName($name)
2565 {
2566 $cache_id = 'Module::getModuleIdByName_'.pSQL($name);
2567 if (!Cache::isStored($cache_id)) {
2568 $result = (int)Db::getInstance()->getValue('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = "'.pSQL($name).'"');
2569 Cache::store($cache_id, $result);
2570 return $result;
2571 }
2572 return Cache::retrieve($cache_id);
2573 }
2574
2575 /**
2576 * Get module errors
2577 *
2578 * @since 1.5.0
2579 * @return array errors
2580 */
2581 public function getErrors()
2582 {
2583 return $this->_errors;
2584 }
2585
2586 /**
2587 * Get module messages confirmation
2588 *
2589 * @since 1.5.0
2590 * @return array conf
2591 */
2592 public function getConfirmations()
2593 {
2594 return $this->_confirmations;
2595 }
2596
2597 /**
2598 * Get local path for module
2599 *
2600 * @since 1.5.0
2601 * @return string
2602 */
2603 public function getLocalPath()
2604 {
2605 return $this->local_path;
2606 }
2607
2608 /**
2609 * Get uri path for module
2610 *
2611 * @since 1.5.0
2612 * @return string
2613 */
2614 public function getPathUri()
2615 {
2616 return $this->_path;
2617 }
2618
2619 /*
2620 * Return module position for a given hook
2621 *
2622 * @param bool $id_hook Hook ID
2623 * @return int position
2624 */
2625 public function getPosition($id_hook)
2626 {
2627 if (isset(Hook::$preloadModulesFromHooks)) {
2628 if (isset(Hook::$preloadModulesFromHooks[$id_hook])) {
2629 if (isset(Hook::$preloadModulesFromHooks[$id_hook]['module_position'][$this->id])) {
2630 return Hook::$preloadModulesFromHooks[$id_hook]['module_position'][$this->id];
2631 } else {
2632 return 0;
2633 }
2634 }
2635 }
2636 $result = Db::getInstance()->getRow('
2637 SELECT `position`
2638 FROM `'._DB_PREFIX_.'hook_module`
2639 WHERE `id_hook` = '.(int)$id_hook.'
2640 AND `id_module` = '.(int)$this->id.'
2641 AND `id_shop` = '.(int)Context::getContext()->shop->id);
2642
2643 return $result['position'];
2644 }
2645
2646 /**
2647 * add a warning message to display at the top of the admin page
2648 *
2649 * @param string $msg
2650 */
2651 public function adminDisplayWarning($msg)
2652 {
2653 if (!($this->context->controller instanceof AdminController)) {
2654 return false;
2655 }
2656 $this->context->controller->warnings[] = $msg;
2657 }
2658
2659 /**
2660 * add a info message to display at the top of the admin page
2661 *
2662 * @param string $msg
2663 */
2664 protected function adminDisplayInformation($msg)
2665 {
2666 if (!($this->context->controller instanceof AdminController)) {
2667 return false;
2668 }
2669 $this->context->controller->informations[] = $msg;
2670 }
2671
2672 /**
2673 * Install module's controllers using public property $controllers
2674 * @return bool
2675 */
2676 protected function installControllers()
2677 {
2678 $themes = Theme::getThemes();
2679 $theme_meta_value = array();
2680 foreach ($this->controllers as $controller) {
2681 $page = 'module-'.$this->name.'-'.$controller;
2682 $result = Db::getInstance()->getValue('SELECT * FROM '._DB_PREFIX_.'meta WHERE page="'.pSQL($page).'"');
2683 if ((int)$result > 0) {
2684 continue;
2685 }
2686
2687 $meta = new Meta();
2688 $meta->page = $page;
2689 $meta->configurable = 1;
2690 $meta->save();
2691 if ((int)$meta->id > 0) {
2692 foreach ($themes as $theme) {
2693 /** @var Theme $theme */
2694 $theme_meta_value[] = array(
2695 'id_theme' => $theme->id,
2696 'id_meta' => $meta->id,
2697 'left_column' => (int)$theme->default_left_column,
2698 'right_column' => (int)$theme->default_right_column
2699 );
2700 }
2701 } else {
2702 $this->_errors[] = sprintf(Tools::displayError('Unable to install controller: %s'), $controller);
2703 }
2704 }
2705 if (count($theme_meta_value) > 0) {
2706 return Db::getInstance()->insert('theme_meta', $theme_meta_value);
2707 }
2708
2709 return true;
2710 }
2711
2712 /**
2713 * Install overrides files for the module
2714 *
2715 * @return bool
2716 */
2717 public function installOverrides()
2718 {
2719 if (!is_dir($this->getLocalPath().'override')) {
2720 return true;
2721 }
2722
2723 $result = true;
2724 foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file) {
2725 $class = basename($file, '.php');
2726 if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core') || Module::getModuleIdByName($class)) {
2727 $result &= $this->addOverride($class);
2728 }
2729 }
2730
2731 return $result;
2732 }
2733
2734 /**
2735 * Uninstall overrides files for the module
2736 *
2737 * @return bool
2738 */
2739 public function uninstallOverrides()
2740 {
2741 if (!is_dir($this->getLocalPath().'override')) {
2742 return true;
2743 }
2744
2745 $result = true;
2746 foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file) {
2747 $class = basename($file, '.php');
2748 if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core') || Module::getModuleIdByName($class)) {
2749 $result &= $this->removeOverride($class);
2750 }
2751 }
2752
2753 return $result;
2754 }
2755
2756 /**
2757 * Add all methods in a module override to the override class
2758 *
2759 * @param string $classname
2760 * @return bool
2761 */
2762 public function addOverride($classname)
2763 {
2764 $orig_path = $path = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core');
2765 if (!$path) {
2766 $path = 'modules'.DIRECTORY_SEPARATOR.$classname.DIRECTORY_SEPARATOR.$classname.'.php';
2767 }
2768 $path_override = $this->getLocalPath().'override'.DIRECTORY_SEPARATOR.$path;
2769
2770 if (!file_exists($path_override)) {
2771 return false;
2772 } else {
2773 file_put_contents($path_override, preg_replace('#(\r\n|\r)#ism', "\n", file_get_contents($path_override)));
2774 }
2775
2776 $pattern_escape_com = '#(^\s*?\/\/.*?\n|\/\*(?!\n\s+\* module:.*?\* date:.*?\* version:.*?\*\/).*?\*\/)#ism';
2777 // Check if there is already an override file, if not, we just need to copy the file
2778 if ($file = PrestaShopAutoload::getInstance()->getClassPath($classname)) {
2779 // Check if override file is writable
2780 $override_path = _PS_ROOT_DIR_.'/'.$file;
2781
2782 if ((!file_exists($override_path) && !is_writable(dirname($override_path))) || (file_exists($override_path) && !is_writable($override_path))) {
2783 throw new Exception(sprintf(Tools::displayError('file (%s) not writable'), $override_path));
2784 }
2785
2786 // 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
2787 do {
2788 $uniq = uniqid();
2789 } while (class_exists($classname.'OverrideOriginal_remove', false));
2790
2791 // Make a reflection of the override class and the module override class
2792 $override_file = file($override_path);
2793 $override_file = array_diff($override_file, array("\n"));
2794 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)));
2795 $override_class = new ReflectionClass($classname.'OverrideOriginal'.$uniq);
2796
2797 $module_file = file($path_override);
2798 $module_file = array_diff($module_file, array("\n"));
2799 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)));
2800 $module_class = new ReflectionClass($classname.'Override'.$uniq);
2801
2802 // Check if none of the methods already exists in the override class
2803 foreach ($module_class->getMethods() as $method) {
2804 if ($override_class->hasMethod($method->getName())) {
2805 $method_override = $override_class->getMethod($method->getName());
2806 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)) {
2807 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]));
2808 }
2809 throw new Exception(sprintf(Tools::displayError('The method %1$s in the class %2$s is already overridden.'), $method->getName(), $classname));
2810 }
2811
2812 $module_file = preg_replace('/((:?public|private|protected)\s+(static\s+)?function\s+(?:\b'.$method->getName().'\b))/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1", $module_file);
2813 if ($module_file === null) {
2814 throw new Exception(sprintf(Tools::displayError('Failed to override method %1$s in class %2$s.'), $method->getName(), $classname));
2815 }
2816 }
2817
2818 // Check if none of the properties already exists in the override class
2819 foreach ($module_class->getProperties() as $property) {
2820 if ($override_class->hasProperty($property->getName())) {
2821 throw new Exception(sprintf(Tools::displayError('The property %1$s in the class %2$s is already defined.'), $property->getName(), $classname));
2822 }
2823
2824 $module_file = preg_replace('/((?:public|private|protected)\s)\s*(static\s)?\s*(\$\b'.$property->getName().'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2$3", $module_file);
2825 if ($module_file === null) {
2826 throw new Exception(sprintf(Tools::displayError('Failed to override property %1$s in class %2$s.'), $property->getName(), $classname));
2827 }
2828 }
2829
2830 foreach ($module_class->getConstants() as $constant => $value) {
2831 if ($override_class->hasConstant($constant)) {
2832 throw new Exception(sprintf(Tools::displayError('The constant %1$s in the class %2$s is already defined.'), $constant, $classname));
2833 }
2834
2835 $module_file = preg_replace('/(const\s)\s*(\b'.$constant.'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2", $module_file);
2836 if ($module_file === null) {
2837 throw new Exception(sprintf(Tools::displayError('Failed to override constant %1$s in class %2$s.'), $constant, $classname));
2838 }
2839 }
2840
2841 // Insert the methods from module override in override
2842 $copy_from = array_slice($module_file, $module_class->getStartLine() + 1, $module_class->getEndLine() - $module_class->getStartLine() - 2);
2843 array_splice($override_file, $override_class->getEndLine() - 1, 0, $copy_from);
2844 $code = implode('', $override_file);
2845
2846 file_put_contents($override_path, preg_replace($pattern_escape_com, '', $code));
2847 } else {
2848 $override_src = $path_override;
2849
2850 $override_dest = _PS_ROOT_DIR_.DIRECTORY_SEPARATOR.'override'.DIRECTORY_SEPARATOR.$path;
2851 $dir_name = dirname($override_dest);
2852
2853 if (!$orig_path && !is_dir($dir_name)) {
2854 $oldumask = umask(0000);
2855 @mkdir($dir_name, 0777);
2856 umask($oldumask);
2857 }
2858
2859 if (!is_writable($dir_name)) {
2860 throw new Exception(sprintf(Tools::displayError('directory (%s) not writable'), $dir_name));
2861 }
2862 $module_file = file($override_src);
2863 $module_file = array_diff($module_file, array("\n"));
2864
2865 if ($orig_path) {
2866 do {
2867 $uniq = uniqid();
2868 } while (class_exists($classname.'OverrideOriginal_remove', false));
2869 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)));
2870 $module_class = new ReflectionClass($classname.'Override'.$uniq);
2871
2872 // For each method found in the override, prepend a comment with the module name and version
2873 foreach ($module_class->getMethods() as $method) {
2874 $module_file = preg_replace('/((:?public|private|protected)\s+(static\s+)?function\s+(?:\b'.$method->getName().'\b))/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1", $module_file);
2875 if ($module_file === null) {
2876 throw new Exception(sprintf(Tools::displayError('Failed to override method %1$s in class %2$s.'), $method->getName(), $classname));
2877 }
2878 }
2879
2880 // Same loop for properties
2881 foreach ($module_class->getProperties() as $property) {
2882 $module_file = preg_replace('/((?:public|private|protected)\s)\s*(static\s)?\s*(\$\b'.$property->getName().'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2$3", $module_file);
2883 if ($module_file === null) {
2884 throw new Exception(sprintf(Tools::displayError('Failed to override property %1$s in class %2$s.'), $property->getName(), $classname));
2885 }
2886 }
2887
2888 // Same loop for constants
2889 foreach ($module_class->getConstants() as $constant => $value) {
2890 $module_file = preg_replace('/(const\s)\s*(\b'.$constant.'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2", $module_file);
2891 if ($module_file === null) {
2892 throw new Exception(sprintf(Tools::displayError('Failed to override constant %1$s in class %2$s.'), $constant, $classname));
2893 }
2894 }
2895 }
2896
2897 file_put_contents($override_dest, preg_replace($pattern_escape_com, '', $module_file));
2898
2899 // Re-generate the class index
2900 Tools::generateIndex();
2901 }
2902 return true;
2903 }
2904
2905 /**
2906 * Remove all methods in a module override from the override class
2907 *
2908 * @param string $classname
2909 * @return bool
2910 */
2911 public function removeOverride($classname)
2912 {
2913 $orig_path = $path = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core');
2914
2915 if ($orig_path && !$file = PrestaShopAutoload::getInstance()->getClassPath($classname)) {
2916 return true;
2917 } elseif (!$orig_path && Module::getModuleIdByName($classname)) {
2918 $path = 'modules'.DIRECTORY_SEPARATOR.$classname.DIRECTORY_SEPARATOR.$classname.'.php';
2919 }
2920
2921 // Check if override file is writable
2922 if ($orig_path) {
2923 $override_path = _PS_ROOT_DIR_.'/'.$file;
2924 } else {
2925 $override_path = _PS_OVERRIDE_DIR_.$path;
2926 }
2927
2928 if (!is_file($override_path) || !is_writable($override_path)) {
2929 return false;
2930 }
2931
2932 file_put_contents($override_path, preg_replace('#(\r\n|\r)#ism', "\n", file_get_contents($override_path)));
2933
2934 if ($orig_path) {
2935 // 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
2936 do {
2937 $uniq = uniqid();
2938 } while (class_exists($classname.'OverrideOriginal_remove', false));
2939
2940 // Make a reflection of the override class and the module override class
2941 $override_file = file($override_path);
2942
2943 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)));
2944 $override_class = new ReflectionClass($classname.'OverrideOriginal_remove'.$uniq);
2945
2946 $module_file = file($this->getLocalPath().'override/'.$path);
2947 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)));
2948 $module_class = new ReflectionClass($classname.'Override_remove'.$uniq);
2949
2950 // Remove methods from override file
2951 foreach ($module_class->getMethods() as $method) {
2952 if (!$override_class->hasMethod($method->getName())) {
2953 continue;
2954 }
2955
2956 $method = $override_class->getMethod($method->getName());
2957 $length = $method->getEndLine() - $method->getStartLine() + 1;
2958
2959 $module_method = $module_class->getMethod($method->getName());
2960 $module_length = $module_method->getEndLine() - $module_method->getStartLine() + 1;
2961
2962 $override_file_orig = $override_file;
2963
2964 $orig_content = preg_replace('/\s/', '', implode('', array_splice($override_file, $method->getStartLine() - 1, $length, array_pad(array(), $length, '#--remove--#'))));
2965 $module_content = preg_replace('/\s/', '', implode('', array_splice($module_file, $module_method->getStartLine() - 1, $length, array_pad(array(), $length, '#--remove--#'))));
2966
2967 $replace = true;
2968 if (preg_match('/\* module: ('.$this->name.')/ism', $override_file[$method->getStartLine() - 5])) {
2969 $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--#';
2970 $replace = false;
2971 }
2972
2973 if (md5($module_content) != md5($orig_content) && $replace) {
2974 $override_file = $override_file_orig;
2975 }
2976 }
2977
2978 // Remove properties from override file
2979 foreach ($module_class->getProperties() as $property) {
2980 if (!$override_class->hasProperty($property->getName())) {
2981 continue;
2982 }
2983
2984 // Replace the declaration line by #--remove--#
2985 foreach ($override_file as $line_number => &$line_content) {
2986 if (preg_match('/(public|private|protected)\s+(static\s+)?(\$)?'.$property->getName().'/i', $line_content)) {
2987 if (preg_match('/\* module: ('.$this->name.')/ism', $override_file[$line_number - 4])) {
2988 $override_file[$line_number - 5] = $override_file[$line_number - 4] = $override_file[$line_number - 3] = $override_file[$line_number - 2] = $override_file[$line_number - 1] = '#--remove--#';
2989 }
2990 $line_content = '#--remove--#';
2991 break;
2992 }
2993 }
2994 }
2995
2996 // Remove properties from override file
2997 foreach ($module_class->getConstants() as $constant => $value) {
2998 if (!$override_class->hasConstant($constant)) {
2999 continue;
3000 }
3001
3002 // Replace the declaration line by #--remove--#
3003 foreach ($override_file as $line_number => &$line_content) {
3004 if (preg_match('/(const)\s+(static\s+)?(\$)?'.$constant.'/i', $line_content)) {
3005 if (preg_match('/\* module: ('.$this->name.')/ism', $override_file[$line_number - 4])) {
3006 $override_file[$line_number - 5] = $override_file[$line_number - 4] = $override_file[$line_number - 3] = $override_file[$line_number - 2] = $override_file[$line_number - 1] = '#--remove--#';
3007 }
3008 $line_content = '#--remove--#';
3009 break;
3010 }
3011 }
3012 }
3013
3014 $count = count($override_file);
3015 for ($i = 0; $i < $count; ++$i) {
3016 if (preg_match('/(^\s*\/\/.*)/i', $override_file[$i])) {
3017 $override_file[$i] = '#--remove--#';
3018 } elseif (preg_match('/(^\s*\/\*)/i', $override_file[$i])) {
3019 if (!preg_match('/(^\s*\* module:)/i', $override_file[$i + 1])
3020 && !preg_match('/(^\s*\* date:)/i', $override_file[$i + 2])
3021 && !preg_match('/(^\s*\* version:)/i', $override_file[$i + 3])
3022 && !preg_match('/(^\s*\*\/)/i', $override_file[$i + 4])) {
3023 for (; $override_file[$i] && !preg_match('/(.*?\*\/)/i', $override_file[$i]); ++$i) {
3024 $override_file[$i] = '#--remove--#';
3025 }
3026 $override_file[$i] = '#--remove--#';
3027 }
3028 }
3029 }
3030
3031 // Rewrite nice code
3032 $code = '';
3033 foreach ($override_file as $line) {
3034 if ($line == '#--remove--#') {
3035 continue;
3036 }
3037
3038 $code .= $line;
3039 }
3040
3041 $to_delete = preg_match('/<\?(?:php)?\s+(?:abstract|interface)?\s*?class\s+'.$classname.'\s+extends\s+'.$classname.'Core\s*?[{]\s*?[}]/ism', $code);
3042 }
3043
3044 if (!isset($to_delete) || $to_delete) {
3045 unlink($override_path);
3046 } else {
3047 file_put_contents($override_path, $code);
3048 }
3049
3050 // Re-generate the class index
3051 Tools::generateIndex();
3052
3053 return true;
3054 }
3055
3056 /**
3057 * Return the hooks list where this module can be hooked.
3058 *
3059 * @return array Hooks list.
3060 */
3061 public function getPossibleHooksList()
3062 {
3063 $hooks_list = Hook::getHooks();
3064 $possible_hooks_list = array();
3065 foreach ($hooks_list as &$current_hook) {
3066 $hook_name = $current_hook['name'];
3067 $retro_hook_name = Hook::getRetroHookName($hook_name);
3068
3069 if (is_callable(array($this, 'hook'.ucfirst($hook_name))) || is_callable(array($this, 'hook'.ucfirst($retro_hook_name)))) {
3070 $possible_hooks_list[] = array(
3071 'id_hook' => $current_hook['id_hook'],
3072 'name' => $hook_name,
3073 'title' => $current_hook['title'],
3074 );
3075 }
3076 }
3077
3078 return $possible_hooks_list;
3079 }
3080}
3081
3082function ps_module_version_sort($a, $b)
3083{
3084 return version_compare($a['version'], $b['version']);
3085}