* @copyright 2007-2014 PrestaShop SA
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
abstract class ModuleCore
{
/** @var integer Module ID */
public $id = null;
/** @var float Version */
public $version;
public $database_version;
/**
* @since 1.5.0.1
* @var string Registered Version in database
*/
public $registered_version;
/** @var array filled with known compliant PS versions */
public $ps_versions_compliancy = array();
/** @var array filled with modules needed for install */
public $dependencies = array();
/** @var string Unique name */
public $name;
/** @var string Human name */
public $displayName;
/** @var string A little description of the module */
public $description;
/** @var string author of the module */
public $author;
/** @var string Module key provided by addons.prestashop.com */
public $module_key = '';
public $description_full;
public $additional_description;
public $compatibility;
public $nb_rates;
public $avg_rate;
public $badges;
/** @var int need_instance */
public $need_instance = 1;
/** @var string Admin tab corresponding to the module */
public $tab = null;
/** @var boolean Status */
public $active = false;
/** @var boolean Is the module certified by addons.prestashop.com */
public $trusted = false;
/** @var string Fill it if the module is installed but not yet set up */
public $warning;
public $enable_device = 7;
/** @var array to store the limited country */
public $limited_countries = array();
/** @var array names of the controllers */
public $controllers = array();
/** @var array used by AdminTab to determine which lang file to use (admin.php or module lang file) */
public static $classInModule = array();
/** @var array current language translations */
protected $_lang = array();
/** @var string Module web path (eg. '/shop/modules/modulename/') */
protected $_path = null;
/**
* @since 1.5.0.1
* @var string Module local path (eg. '/home/prestashop/modules/modulename/')
*/
protected $local_path = null;
/** @var protected array filled with module errors */
protected $_errors = array();
/** @var protected array filled with module success */
protected $_confirmations = array();
/** @var protected string main table used for modules installed */
protected $table = 'module';
/** @var protected string identifier of the main table */
protected $identifier = 'id_module';
/** @var protected array cache filled with modules informations */
protected static $modules_cache;
/** @var protected array cache filled with modules instances */
protected static $_INSTANCE = array();
/** @var protected boolean filled with config xml generation mode */
protected static $_generate_config_xml_mode = false;
/** @var protected array filled with cache translations */
protected static $l_cache = array();
/** @var protected array filled with cache permissions (modules / employee profiles) */
protected static $cache_permissions = array();
/** @var Context */
protected $context;
/** @var Smarty_Data */
protected $smarty;
/** @var currentSmartySubTemplate */
protected $current_subtemplate = null;
protected static $update_translations_after_install = true;
/** @var allow push */
public $allow_push;
public $push_time_limit = 180;
const CACHE_FILE_MODULES_LIST = '/config/xml/modules_list.xml';
const CACHE_FILE_TAB_MODULES_LIST = '/config/xml/tab_modules_list.xml';
const CACHE_FILE_ALL_COUNTRY_MODULES_LIST = '/config/xml/modules_native_addons.xml';
const CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST = '/config/xml/default_country_modules_list.xml';
const CACHE_FILE_CUSTOMER_MODULES_LIST = '/config/xml/customer_modules_list.xml';
const CACHE_FILE_MUST_HAVE_MODULES_LIST = '/config/xml/must_have_modules_list.xml';
const CACHE_FILE_TRUSTED_MODULES_LIST = '/config/xml/trusted_modules_list.xml';
const CACHE_FILE_UNTRUSTED_MODULES_LIST = '/config/xml/untrusted_modules_list.xml';
public static $hosted_modules_blacklist = array('autoupgrade');
/**
* Constructor
*
* @param string $name Module unique name
* @param Context $context
*/
public function __construct($name = null, Context $context = null)
{
if (isset($this->ps_versions_compliancy) && !isset($this->ps_versions_compliancy['min']))
$this->ps_versions_compliancy['min'] = '1.4.0.0';
if (isset($this->ps_versions_compliancy) && !isset($this->ps_versions_compliancy['max']))
$this->ps_versions_compliancy['max'] = _PS_VERSION_;
if (strlen($this->ps_versions_compliancy['min']) == 3)
$this->ps_versions_compliancy['min'] .= '.0.0';
if (strlen($this->ps_versions_compliancy['max']) == 3)
$this->ps_versions_compliancy['max'] .= '.999.999';
// Load context and smarty
$this->context = $context ? $context : Context::getContext();
if (is_object($this->context->smarty))
$this->smarty = $this->context->smarty->createData($this->context->smarty);
// If the module has no name we gave him its id as name
if ($this->name == null)
$this->name = $this->id;
// If the module has the name we load the corresponding data from the cache
if ($this->name != null)
{
// If cache is not generated, we generate it
if (self::$modules_cache == null && !is_array(self::$modules_cache))
{
$id_shop = (Validate::isLoadedObject($this->context->shop) ? $this->context->shop->id : 1);
self::$modules_cache = array();
// Join clause is done to check if the module is activated in current shop context
$result = Db::getInstance()->executeS('
SELECT m.`id_module`, m.`name`, (
SELECT id_module
FROM `'._DB_PREFIX_.'module_shop` ms
WHERE m.`id_module` = ms.`id_module`
AND ms.`id_shop` = '.(int)$id_shop.'
LIMIT 1
) as mshop
FROM `'._DB_PREFIX_.'module` m');
foreach ($result as $row)
{
self::$modules_cache[$row['name']] = $row;
self::$modules_cache[$row['name']]['active'] = ($row['mshop'] > 0) ? 1 : 0;
}
}
// We load configuration from the cache
if (isset(self::$modules_cache[$this->name]))
{
if (isset(self::$modules_cache[$this->name]['id_module']))
$this->id = self::$modules_cache[$this->name]['id_module'];
foreach (self::$modules_cache[$this->name] as $key => $value)
if (array_key_exists($key, $this))
$this->{$key} = $value;
$this->_path = __PS_BASE_URI__.'modules/'.$this->name.'/';
}
$this->local_path = _PS_MODULE_DIR_.$this->name.'/';
}
}
/**
* Insert module into datable
*/
public function install()
{
Hook::exec('actionModuleInstallBefore', array('object' => $this));
// Check module name validation
if (!Validate::isModuleName($this->name))
{
$this->_errors[] = $this->l('Unable to install the module (Module name is not valid).');
return false;
}
// Check PS version compliancy
if (!$this->checkCompliancy())
{
$this->_errors[] = $this->l('The version of your module is not compliant with your PrestaShop version.');
return false;
}
// Check module dependencies
if (count($this->dependencies) > 0)
foreach ($this->dependencies as $dependency)
if (!Db::getInstance()->getRow('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($dependency).'\''))
{
$error = $this->l('Before installing this module, you have to install this/these module(s) first:').'
';
foreach ($this->dependencies as $d)
$error .= '- '.$d.'
';
$this->_errors[] = $error;
return false;
}
// Check if module is installed
$result = Module::isInstalled($this->name);
if ($result)
{
$this->_errors[] = $this->l('This module has already been installed.');
return false;
}
// Install overrides
try {
$this->installOverrides();
} catch (Exception $e) {
$this->_errors[] = sprintf(Tools::displayError('Unable to install override: %s'), $e->getMessage());
$this->uninstallOverrides();
return false;
}
if (!$this->installControllers())
return false;
// Install module and retrieve the installation id
$result = Db::getInstance()->insert($this->table, array('name' => $this->name, 'active' => 1, 'version' => $this->version));
if (!$result)
{
$this->_errors[] = $this->l('Technical error: PrestaShop could not installed this module.');
return false;
}
$this->id = Db::getInstance()->Insert_ID();
Cache::clean('Module::isInstalled'.$this->name);
// Enable the module for current shops in context
$this->enable();
// Permissions management
Db::getInstance()->execute('
INSERT INTO `'._DB_PREFIX_.'module_access` (`id_profile`, `id_module`, `view`, `configure`) (
SELECT id_profile, '.(int)$this->id.', 1, 1
FROM '._DB_PREFIX_.'access a
WHERE id_tab = (
SELECT `id_tab` FROM '._DB_PREFIX_.'tab
WHERE class_name = \'AdminModules\' LIMIT 1)
AND a.`view` = 1)');
Db::getInstance()->execute('
INSERT INTO `'._DB_PREFIX_.'module_access` (`id_profile`, `id_module`, `view`, `configure`) (
SELECT id_profile, '.(int)$this->id.', 1, 0
FROM '._DB_PREFIX_.'access a
WHERE id_tab = (
SELECT `id_tab` FROM '._DB_PREFIX_.'tab
WHERE class_name = \'AdminModules\' LIMIT 1)
AND a.`view` = 0)');
// Adding Restrictions for client groups
Group::addRestrictionsForModule($this->id, Shop::getShops(true, null, true));
Hook::exec('actionModuleInstallAfter', array('object' => $this));
if (Module::$update_translations_after_install)
$this->updateModuleTranslations();
return true;
}
public function checkCompliancy()
{
if (version_compare(_PS_VERSION_, $this->ps_versions_compliancy['min'], '<') || version_compare(_PS_VERSION_, $this->ps_versions_compliancy['max'], '>'))
return false;
else
return true;
}
public static function updateTranslationsAfterInstall($update = true)
{
Module::$update_translations_after_install = (bool)$update;
}
public function updateModuleTranslations()
{
return Language::updateModulesTranslations(array($this->name));
}
/**
* Set errors, warning or success message of a module upgrade
*
* @param $upgrade_detail
*/
protected function setUpgradeMessage($upgrade_detail)
{
// Store information if a module has been upgraded (memory optimization)
if ($upgrade_detail['available_upgrade'])
{
if ($upgrade_detail['success'])
{
$this->_confirmations[] = sprintf($this->l('Current version: %s'), $this->version);
$this->_confirmations[] = $upgrade_detail['number_upgraded'].' '.$this->l('file upgrade applied');
}
else
{
if (!$upgrade_detail['number_upgraded'])
$this->_errors[] = $this->l('No upgrade has been applied');
else
{
$this->_errors[] = sprintf($this->l('Upgraded from: %S to %s'), $upgrade_detail['upgraded_from'], $upgrade_detail['upgraded_to']);
$this->_errors[] = $upgrade_detail['number_upgrade_left'].' '.$this->l('upgrade left');
}
if (isset($upgrade_detail['duplicate']) && $upgrade_detail['duplicate'])
$this->_errors[] = sprintf(Tools::displayError('Module %s cannot be upgraded this time: please refresh this page to update it.'), $this->name);
else
$this->_errors[] = $this->l('To prevent any problem, this module has been turned off');
}
}
}
/**
* Init the upgrade module
*
* @static
* @param $module_name
* @param $module_version
* @return bool
*/
public static function initUpgradeModule($module)
{
if (((int)$module->installed == 1) & (empty($module->database_version) === true))
{
Module::upgradeModuleVersion($module->name, $module->version);
$module->database_version = $module->version;
}
// Init cache upgrade details
self::$modules_cache[$module->name]['upgrade'] = array(
'success' => false, // bool to know if upgrade succeed or not
'available_upgrade' => 0, // Number of available module before any upgrade
'number_upgraded' => 0, // Number of upgrade done
'number_upgrade_left' => 0,
'upgrade_file_left' => array(), // List of the upgrade file left
'version_fail' => 0, // Version of the upgrade failure
'upgraded_from' => 0, // Version number before upgrading anything
'upgraded_to' => 0, // Last upgrade applied
);
// Need Upgrade will check and load upgrade file to the moduleCache upgrade case detail
$ret = $module->installed && Module::needUpgrade($module);
return $ret;
}
/**
* Run the upgrade for a given module name and version
*
* @return array
*/
public function runUpgradeModule()
{
$upgrade = &self::$modules_cache[$this->name]['upgrade'];
foreach ($upgrade['upgrade_file_left'] as $num => $file_detail)
{
if (function_exists($file_detail['upgrade_function']))
{
$upgrade['success'] = false;
$upgrade['duplicate'] = true;
break;
}
include($file_detail['file']);
// Call the upgrade function if defined
$upgrade['success'] = false;
if (function_exists($file_detail['upgrade_function']))
$upgrade['success'] = $file_detail['upgrade_function']($this);
// Set detail when an upgrade succeed or failed
if ($upgrade['success'])
{
$upgrade['number_upgraded'] += 1;
$upgrade['upgraded_to'] = $file_detail['version'];
unset($upgrade['upgrade_file_left'][$num]);
}
else
{
$upgrade['version_fail'] = $file_detail['version'];
// If any errors, the module is disabled
$this->disable();
break;
}
}
$upgrade['number_upgrade_left'] = count($upgrade['upgrade_file_left']);
// Update module version in DB with the last succeed upgrade
if ($upgrade['upgraded_to'])
Module::upgradeModuleVersion($this->name, $upgrade['upgraded_to']);
$this->setUpgradeMessage($upgrade);
return $upgrade;
}
/**
* Upgrade the registered version to a new one
*
* @static
* @param $name
* @param $version
* @return bool
*/
public static function upgradeModuleVersion($name, $version)
{
return Db::getInstance()->execute('
UPDATE `'._DB_PREFIX_.'module` m
SET m.version = \''.bqSQL($version).'\'
WHERE m.name = \''.bqSQL($name).'\'');
}
/**
* Check if a module need to be upgraded.
* This method modify the module_cache adding an upgrade list file
*
* @static
* @param $module_name
* @param $module_version
* @return bool
*/
public static function needUpgrade($module)
{
self::$modules_cache[$module->name]['upgrade']['upgraded_from'] = $module->database_version;
// Check the version of the module with the registered one and look if any upgrade file exist
if (Tools::version_compare($module->version, $module->database_version, '>'))
{
$old_version = $module->database_version;
$module = Module::getInstanceByName($module->name);
if ($module instanceof Module)
return $module->loadUpgradeVersionList($module->name, $module->version, $old_version);
}
return null;
}
/**
* Load the available list of upgrade of a specified module
* with an associated version
*
* @static
* @param $module_name
* @param $module_version
* @param $registered_version
* @return bool to know directly if any files have been found
*/
protected static function loadUpgradeVersionList($module_name, $module_version, $registered_version)
{
$list = array();
$upgrade_path = _PS_MODULE_DIR_.$module_name.'/upgrade/';
// Check if folder exist and it could be read
if (file_exists($upgrade_path) && ($files = scandir($upgrade_path)))
{
// Read each file name
foreach ($files as $file)
if (!in_array($file, array('.', '..', '.svn', 'index.php')) && preg_match('/\.php$/', $file))
{
$tab = explode('-', $file);
if (!isset($tab[1]))
continue;
$file_version = basename($tab[1], '.php');
// Compare version, if minor than actual, we need to upgrade the module
if (count($tab) == 2 &&
(Tools::version_compare($file_version, $module_version, '<=') &&
Tools::version_compare($file_version, $registered_version, '>')))
{
$list[] = array(
'file' => $upgrade_path.$file,
'version' => $file_version,
'upgrade_function' => 'upgrade_module_'.str_replace('.', '_', $file_version));
}
}
}
// No files upgrade, then upgrade succeed
if (count($list) == 0)
{
self::$modules_cache[$module_name]['upgrade']['success'] = true;
Module::upgradeModuleVersion($module_name, $module_version);
}
usort($list, 'ps_module_version_sort');
// Set the list to module cache
self::$modules_cache[$module_name]['upgrade']['upgrade_file_left'] = $list;
self::$modules_cache[$module_name]['upgrade']['available_upgrade'] = count($list);
return (bool)count($list);
}
/**
* Return the status of the upgraded module
*
* @static
* @param $module_name
* @return bool
*/
public static function getUpgradeStatus($module_name)
{
return (isset(self::$modules_cache[$module_name]) &&
self::$modules_cache[$module_name]['upgrade']['success']);
}
/**
* Delete module from datable
*
* @return boolean result
*/
public function uninstall()
{
// Check module installation id validation
if (!Validate::isUnsignedId($this->id))
{
$this->_errors[] = $this->l('The module is not installed.');
return false;
}
// Uninstall overrides
if (!$this->uninstallOverrides())
return false;
// Retrieve hooks used by the module
$sql = 'SELECT `id_hook` FROM `'._DB_PREFIX_.'hook_module` WHERE `id_module` = '.(int)$this->id;
$result = Db::getInstance()->executeS($sql);
foreach ($result as $row)
{
$this->unregisterHook((int)$row['id_hook']);
$this->unregisterExceptions((int)$row['id_hook']);
}
foreach ($this->controllers as $controller)
{
$page_name = 'module-'.$this->name.'-'.$controller;
$meta = Db::getInstance()->getValue('SELECT id_meta FROM `'._DB_PREFIX_.'meta` WHERE page="'.pSQL($page_name).'"');
if ((int)$meta > 0)
{
Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'theme_meta` WHERE id_meta='.(int)$meta);
Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'meta_lang` WHERE id_meta='.(int)$meta);
Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'meta` WHERE id_meta='.(int)$meta);
}
}
// Disable the module for all shops
$this->disable(true);
// Delete permissions module access
Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'module_access` WHERE `id_module` = '.(int)$this->id);
// Remove restrictions for client groups
Group::truncateRestrictionsByModule($this->id);
// Uninstall the module
if (Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'module` WHERE `id_module` = '.(int)$this->id))
{
Cache::clean('Module::isInstalled'.$this->name);
Cache::clean('Module::getModuleIdByName_'.pSQL($this->name));
return true;
}
return false;
}
/**
* This function enable module $name. If an $name is an array,
* this will enable all of them
*
* @param array|string $name
* @return true if succeed
* @since 1.4.1
*/
public static function enableByName($name)
{
// If $name is not an array, we set it as an array
if (!is_array($name))
$name = array($name);
$res = true;
// Enable each module
foreach ($name as $n)
if (Validate::isModuleName($n))
$res &= Module::getInstanceByName($n)->enable();
return $res;
}
/**
* Activate current module.
*
* @param bool $forceAll If true, enable module for all shop
*/
public function enable($forceAll = false)
{
// Retrieve all shops where the module is enabled
$list = Shop::getContextListShopID();
if (!$this->id || !is_array($list))
return false;
$sql = 'SELECT `id_shop` FROM `'._DB_PREFIX_.'module_shop`
WHERE `id_module` = '.(int)$this->id.
((!$forceAll) ? ' AND `id_shop` IN('.implode(', ', $list).')' : '');
// Store the results in an array
$items = array();
if ($results = Db::getInstance($sql)->executeS($sql))
foreach ($results as $row)
$items[] = $row['id_shop'];
// Enable module in the shop where it is not enabled yet
foreach ($list as $id)
if (!in_array($id, $items))
Db::getInstance()->insert('module_shop', array(
'id_module' => $this->id,
'id_shop' => $id,
));
return true;
}
public function enableDevice($device)
{
Db::getInstance()->execute('
UPDATE '._DB_PREFIX_.'module_shop
SET enable_device = enable_device + '.(int)$device.'
WHERE enable_device &~ '.(int)$device.' AND id_module='.(int)$this->id.
Shop::addSqlRestriction()
);
return true;
}
public function disableDevice($device)
{
Db::getInstance()->execute('
UPDATE '._DB_PREFIX_.'module_shop
SET enable_device = enable_device - '.(int)$device.'
WHERE enable_device & '.(int)$device.' AND id_module='.(int)$this->id.
Shop::addSqlRestriction()
);
return true;
}
/**
* This function disable module $name. If an $name is an array,
* this will disable all of them
*
* @param array|string $name
* @return true if succeed
* @since 1.4.1
*/
public static function disableByName($name)
{
// If $name is not an array, we set it as an array
if (!is_array($name))
$name = array($name);
$res = true;
// Disable each module
foreach ($name as $n)
if (Validate::isModuleName($n))
$res &= Module::getInstanceByName($n)->disable();
return $res;
}
/**
* Desactivate current module.
*
* @param bool $forceAll If true, disable module for all shop
*/
public function disable($forceAll = false)
{
// Disable module for all shops
$sql = 'DELETE FROM `'._DB_PREFIX_.'module_shop` WHERE `id_module` = '.(int)$this->id.' '.((!$forceAll) ? ' AND `id_shop` IN('.implode(', ', Shop::getContextListShopID()).')' : '');
Db::getInstance()->execute($sql);
}
/**
* Display flags in forms for translations
*
* @param array $languages All languages available
* @param integer $default_language Default language id
* @param string $ids Multilingual div ids in form
* @param string $id Current div id]
* @param boolean $return define the return way : false for a display, true for a return
* @param boolean $use_vars_instead_of_ids use an js vars instead of ids seperate by "ยค"
*/
public function displayFlags($languages, $default_language, $ids, $id, $return = false, $use_vars_instead_of_ids = false)
{
if (count($languages) == 1)
return false;
$output = '
'.$this->l('Choose language:').'
';
foreach ($languages as $language)
if ($use_vars_instead_of_ids)
$output .= '
';
else
$output .= '
';
$output .= '
';
if ($return)
return $output;
echo $output;
}
/**
* Connect module to a hook
*
* @param string $hook_name Hook name
* @param array $shop_list List of shop linked to the hook (if null, link hook to all shops)
* @return boolean result
*/
public function registerHook($hook_name, $shop_list = null)
{
$return = true;
if (is_array($hook_name))
$hook_names = $hook_name;
else
$hook_names = array($hook_name);
foreach ($hook_names as $hook_name)
{
// Check hook name validation and if module is installed
if (!Validate::isHookName($hook_name))
throw new PrestaShopException('Invalid hook name');
if (!isset($this->id) || !is_numeric($this->id))
return false;
// Retrocompatibility
$hook_name_bak = $hook_name;
if ($alias = Hook::getRetroHookName($hook_name))
$hook_name = $alias;
Hook::exec('actionModuleRegisterHookBefore', array('object' => $this, 'hook_name' => $hook_name));
// Get hook id
$id_hook = Hook::getIdByName($hook_name);
$live_edit = Hook::getLiveEditById((int)Hook::getIdByName($hook_name_bak));
// If hook does not exist, we create it
if (!$id_hook)
{
$new_hook = new Hook();
$new_hook->name = pSQL($hook_name);
$new_hook->title = pSQL($hook_name);
$new_hook->live_edit = (bool)preg_match('/^display/i', $new_hook->name);
$new_hook->position = (bool)$new_hook->live_edit;
$new_hook->add();
$id_hook = $new_hook->id;
if (!$id_hook)
return false;
}
// If shop lists is null, we fill it with all shops
if (is_null($shop_list))
$shop_list = Shop::getShops(true, null, true);
foreach ($shop_list as $shop_id)
{
// Check if already register
$sql = 'SELECT hm.`id_module`
FROM `'._DB_PREFIX_.'hook_module` hm, `'._DB_PREFIX_.'hook` h
WHERE hm.`id_module` = '.(int)($this->id).' AND h.`id_hook` = '.$id_hook.'
AND h.`id_hook` = hm.`id_hook` AND `id_shop` = '.(int)$shop_id;
if (Db::getInstance()->getRow($sql))
continue;
// Get module position in hook
$sql = 'SELECT MAX(`position`) AS position
FROM `'._DB_PREFIX_.'hook_module`
WHERE `id_hook` = '.(int)$id_hook.' AND `id_shop` = '.(int)$shop_id;
if (!$position = Db::getInstance()->getValue($sql))
$position = 0;
// Register module in hook
$return &= Db::getInstance()->insert('hook_module', array(
'id_module' => (int)$this->id,
'id_hook' => (int)$id_hook,
'id_shop' => (int)$shop_id,
'position' => (int)($position + 1),
));
}
Hook::exec('actionModuleRegisterHookAfter', array('object' => $this, 'hook_name' => $hook_name));
}
return $return;
}
/**
* Unregister module from hook
*
* @param mixed $id_hook Hook id (can be a hook name since 1.5.0)
* @param array $shop_list List of shop
* @return boolean result
*/
public function unregisterHook($hook_id, $shop_list = null)
{
// Get hook id if a name is given as argument
if (!is_numeric($hook_id))
{
$hook_name = (int)$hook_id;
// Retrocompatibility
$hook_id = Hook::getIdByName($hook_id);
if (!$hook_id)
return false;
}
else
$hook_name = Hook::getNameById((int)$hook_id);
Hook::exec('actionModuleUnRegisterHookBefore', array('object' => $this, 'hook_name' => $hook_name));
// Unregister module on hook by id
$sql = 'DELETE FROM `'._DB_PREFIX_.'hook_module`
WHERE `id_module` = '.(int)$this->id.' AND `id_hook` = '.(int)$hook_id
.(($shop_list) ? ' AND `id_shop` IN('.implode(', ', array_map('intval', $shop_list)).')' : '');
$result = Db::getInstance()->execute($sql);
// Clean modules position
$this->cleanPositions($hook_id, $shop_list);
Hook::exec('actionModuleUnRegisterHookAfter', array('object' => $this, 'hook_name' => $hook_name));
return $result;
}
/**
* Unregister exceptions linked to module
*
* @param int $id_hook Hook id
* @param array $shop_list List of shop
* @return boolean result
*/
public function unregisterExceptions($hook_id, $shop_list = null)
{
$sql = 'DELETE FROM `'._DB_PREFIX_.'hook_module_exceptions`
WHERE `id_module` = '.(int)$this->id.' AND `id_hook` = '.(int)$hook_id
.(($shop_list) ? ' AND `id_shop` IN('.implode(', ', array_map('intval', $shop_list)).')' : '');
return Db::getInstance()->execute($sql);
}
/**
* Add exceptions for module->Hook
*
* @param int $id_hook Hook id
* @param array $excepts List of file name
* @param array $shop_list List of shop
* @return boolean result
*/
public function registerExceptions($id_hook, $excepts, $shop_list = null)
{
// If shop lists is null, we fill it with all shops
if (is_null($shop_list))
$shop_list = Shop::getContextListShopID();
// Save modules exception for each shop
foreach ($shop_list as $shop_id)
{
foreach ($excepts as $except)
{
if (!$except)
continue;
$insertException = array(
'id_module' => (int)$this->id,
'id_hook' => (int)$id_hook,
'id_shop' => (int)$shop_id,
'file_name' => pSQL($except),
);
$result = Db::getInstance()->insert('hook_module_exceptions', $insertException);
if (!$result)
return false;
}
}
return true;
}
/**
* Edit exceptions for module->Hook
*
* @param int $hookID Hook id
* @param array $excepts List of shopID and file name
* @return boolean result
*/
public function editExceptions($id_hook, $excepts)
{
$result = true;
foreach ($excepts as $shop_id => $except)
{
$shop_list = ($shop_id == 0) ? Shop::getContextListShopID() : array($shop_id);
$this->unregisterExceptions($id_hook, $shop_list);
$result &= $this->registerExceptions($id_hook, $except, $shop_list);
}
return $result;
}
/**
* This function is used to determine the module name
* of an AdminTab which belongs to a module, in order to keep translation
* related to a module in its directory (instead of $_LANGADM)
*
* @param mixed $currentClass the
* @return boolean|string if the class belongs to a module, will return the module name. Otherwise, return false.
*/
public static function getModuleNameFromClass($currentClass)
{
// Module can now define AdminTab keeping the module translations method,
// i.e. in modules/[module name]/[iso_code].php
if (!isset(self::$classInModule[$currentClass]) && class_exists($currentClass))
{
global $_MODULES;
$_MODULE = array();
$reflectionClass = new ReflectionClass($currentClass);
$filePath = realpath($reflectionClass->getFileName());
$realpathModuleDir = realpath(_PS_MODULE_DIR_);
if (substr(realpath($filePath), 0, strlen($realpathModuleDir)) == $realpathModuleDir)
{
// For controllers in module/controllers path
if (basename(dirname(dirname($filePath))) == 'controllers')
self::$classInModule[$currentClass] = basename(dirname(dirname(dirname($filePath))));
// For old AdminTab controllers
else
self::$classInModule[$currentClass] = substr(dirname($filePath), strlen($realpathModuleDir) + 1);
$file = _PS_MODULE_DIR_.self::$classInModule[$currentClass].'/'.Context::getContext()->language->iso_code.'.php';
if (Tools::file_exists_cache($file) && include_once($file))
$_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
}
else
self::$classInModule[$currentClass] = false;
}
// return name of the module, or false
return self::$classInModule[$currentClass];
}
/**
* Return an instance of the specified module
*
* @param string $module_name Module name
* @return Module
*/
public static function getInstanceByName($module_name)
{
if (!Validate::isModuleName($module_name))
{
if (_PS_MODE_DEV_)
die(Tools::displayError(Tools::safeOutput($module_name).' is not a valid module name.'));
return false;
}
if (!isset(self::$_INSTANCE[$module_name]))
{
if (Tools::file_exists_no_cache(_PS_MODULE_DIR_.$module_name.'/'.$module_name.'.php'))
{
include_once(_PS_MODULE_DIR_.$module_name.'/'.$module_name.'.php');
if (class_exists($module_name, false))
return self::$_INSTANCE[$module_name] = new $module_name;
}
return false;
}
return self::$_INSTANCE[$module_name];
}
/**
* Return an instance of the specified module
*
* @param integer $id_module Module ID
* @return Module instance
*/
public static function getInstanceById($id_module)
{
static $id2name = null;
if (is_null($id2name))
{
$id2name = array();
$sql = 'SELECT `id_module`, `name` FROM `'._DB_PREFIX_.'module`';
if ($results = Db::getInstance()->executeS($sql))
foreach ($results as $row)
$id2name[$row['id_module']] = $row['name'];
}
if (isset($id2name[$id_module]))
return Module::getInstanceByName($id2name[$id_module]);
return false;
}
public static function configXmlStringFormat($string)
{
return Tools::htmlentitiesDecodeUTF8($string);
}
public static function getModuleName($module)
{
$iso = substr(Context::getContext()->language->iso_code, 0, 2);
// Config file
$configFile = _PS_MODULE_DIR_.$module.'/config_'.$iso.'.xml';
// For "en" iso code, we keep the default config.xml name
if ($iso == 'en' || !file_exists($configFile))
{
$configFile = _PS_MODULE_DIR_.$module.'/config.xml';
if (!file_exists($configFile))
return 'Module '.ucfirst($module);
}
// Load config.xml
libxml_use_internal_errors(true);
$xml_module = simplexml_load_file($configFile);
foreach (libxml_get_errors() as $error)
{
libxml_clear_errors();
return 'Module '.ucfirst($module);
}
libxml_clear_errors();
// Find translations
global $_MODULES;
$file = _PS_MODULE_DIR_.$module.'/'.Context::getContext()->language->iso_code.'.php';
if (Tools::file_exists_cache($file) && include_once($file))
if (isset($_MODULE) && is_array($_MODULE))
$_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
// Return Name
return Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->displayName), (string)$xml_module->name);
}
protected static function useTooMuchMemory()
{
$memory_limit = Tools::getMemoryLimit();
if (function_exists('memory_get_usage') && $memory_limit != '-1')
{
$current_memory = memory_get_usage(true);
$memory_threshold = (int)max($memory_limit * 0.15, Tools::isX86_64arch() ? 4194304 : 2097152);
$memory_left = $memory_limit - $current_memory;
if ($memory_left <= $memory_threshold)
return true;
}
return false;
}
/**
* Return available modules
*
* @param boolean $useConfig in order to use config.xml file in module dir
* @return array Modules
*/
public static function getModulesOnDisk($useConfig = false, $loggedOnAddons = false, $id_employee = false)
{
global $_MODULES;
// Init var
$module_list = array();
$module_name_list = array();
$modulesNameToCursor = array();
$errors = array();
// Get modules directory list and memory limit
$modules_dir = Module::getModulesDirOnDisk();
$modules_installed = array();
$result = Db::getInstance()->executeS('
SELECT m.name, m.version, mp.interest, module_shop.enable_device
FROM `'._DB_PREFIX_.'module` m
'.Shop::addSqlAssociation('module', 'm').'
LEFT JOIN `'._DB_PREFIX_.'module_preference` mp ON (mp.`module` = m.`name` AND mp.`id_employee` = '.(int)$id_employee.')');
foreach ($result as $row)
$modules_installed[$row['name']] = $row;
foreach ($modules_dir as $module)
{
if (Module::useTooMuchMemory())
{
$errors[] = Tools::displayError('All modules cannot be loaded due to memory limit restrictions, please increase your memory_limit value on your server configuration');
break;
}
$iso = substr(Context::getContext()->language->iso_code, 0, 2);
// Check if config.xml module file exists and if it's not outdated
if ($iso == 'en')
$configFile = _PS_MODULE_DIR_.$module.'/config.xml';
else
$configFile = _PS_MODULE_DIR_.$module.'/config_'.$iso.'.xml';
$xml_exist = (file_exists($configFile));
$needNewConfigFile = $xml_exist ? (@filemtime($configFile) < @filemtime(_PS_MODULE_DIR_.$module.'/'.$module.'.php')) : true;
// If config.xml exists and that the use config flag is at true
if ($useConfig && $xml_exist && !$needNewConfigFile)
{
// Load config.xml
libxml_use_internal_errors(true);
$xml_module = simplexml_load_file($configFile);
foreach (libxml_get_errors() as $error)
$errors[] = '['.$module.'] '.Tools::displayError('Error found in config file:').' '.htmlentities($error->message);
libxml_clear_errors();
// If no errors in Xml, no need instand and no need new config.xml file, we load only translations
if (!count($errors) && (int)$xml_module->need_instance == 0)
{
$file = _PS_MODULE_DIR_.$module.'/'.Context::getContext()->language->iso_code.'.php';
if (Tools::file_exists_cache($file) && include_once($file))
if (isset($_MODULE) && is_array($_MODULE))
$_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE;
$item = new stdClass();
$item->id = 0;
$item->warning = '';
foreach ($xml_module as $k => $v)
$item->$k = (string)$v;
$item->displayName = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->displayName), (string)$xml_module->name));
$item->description = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->description), (string)$xml_module->name));
$item->author = stripslashes(Translate::getModuleTranslation((string)$xml_module->name, Module::configXmlStringFormat($xml_module->author), (string)$xml_module->name));
if (isset($xml_module->confirmUninstall))
$item->confirmUninstall = Translate::getModuleTranslation((string)$xml_module->name, html_entity_decode(Module::configXmlStringFormat($xml_module->confirmUninstall)), (string)$xml_module->name);
$item->active = 0;
$item->onclick_option = false;
$item->trusted = Module::isModuleTrusted($item->name);
$module_list[] = $item;
$module_name_list[] = '\''.pSQL($item->name).'\'';
$modulesNameToCursor[strval($item->name)] = $item;
}
}
// If use config flag is at false or config.xml does not exist OR need instance OR need a new config.xml file
if (!$useConfig || !$xml_exist || (isset($xml_module->need_instance) && (int)$xml_module->need_instance == 1) || $needNewConfigFile)
{
// If class does not exists, we include the file
if (!class_exists($module, false))
{
// Get content from php file
$filepath = _PS_MODULE_DIR_.$module.'/'.$module.'.php';
$file = trim(file_get_contents(_PS_MODULE_DIR_.$module.'/'.$module.'.php'));
if (substr($file, 0, 5) == '')
$file = substr($file, 0, -2);
// If (false) is a trick to not load the class with "eval".
// This way require_once will works correctly
if (eval('if (false){ '.$file.' }') !== false)
require_once( _PS_MODULE_DIR_.$module.'/'.$module.'.php' );
else
$errors[] = sprintf(Tools::displayError('%1$s (parse error in %2$s)'), $module, substr($filepath, strlen(_PS_ROOT_DIR_)));
}
// If class exists, we just instanciate it
if (class_exists($module, false))
{
$tmp_module = new $module;
$item = new stdClass();
$item->id = $tmp_module->id;
$item->warning = $tmp_module->warning;
$item->name = $tmp_module->name;
$item->version = $tmp_module->version;
$item->tab = $tmp_module->tab;
$item->displayName = $tmp_module->displayName;
$item->description = stripslashes($tmp_module->description);
$item->author = $tmp_module->author;
$item->limited_countries = $tmp_module->limited_countries;
$item->parent_class = get_parent_class($module);
$item->is_configurable = $tmp_module->is_configurable = method_exists($tmp_module, 'getContent') ? 1 : 0;
$item->need_instance = isset($tmp_module->need_instance) ? $tmp_module->need_instance : 0;
$item->active = $tmp_module->active;
$item->trusted = Module::isModuleTrusted($tmp_module->name);
$item->currencies = isset($tmp_module->currencies) ? $tmp_module->currencies : null;
$item->currencies_mode = isset($tmp_module->currencies_mode) ? $tmp_module->currencies_mode : null;
$item->confirmUninstall = isset($tmp_module->confirmUninstall) ? html_entity_decode($tmp_module->confirmUninstall) : null;
$item->description_full = stripslashes($tmp_module->description_full);
$item->additional_description = isset($tmp_module->additional_description) ? stripslashes($tmp_module->additional_description) : null;
$item->compatibility = isset($tmp_module->compatibility) ? (array)$tmp_module->compatibility : null;
$item->nb_rates = isset($tmp_module->nb_rates) ? (array)$tmp_module->nb_rates : null;
$item->avg_rate = isset($tmp_module->avg_rate) ? (array)$tmp_module->avg_rate : null;
$item->badges = isset($tmp_module->badges) ? (array)$tmp_module->badges : null;
$item->url = isset($tmp_module->url) ? $tmp_module->url : null;
$item->onclick_option = method_exists($module, 'onclickOption') ? true : false;
if ($item->onclick_option)
{
$href = Context::getContext()->link->getAdminLink('Module', true).'&module_name='.$tmp_module->name.'&tab_module='.$tmp_module->tab;
$item->onclick_option_content = array();
$option_tab = array('desactive', 'reset', 'configure', 'delete');
foreach ($option_tab as $opt)
$item->onclick_option_content[$opt] = $tmp_module->onclickOption($opt, $href);
}
$module_list[] = $item;
if (!$xml_exist || $needNewConfigFile)
{
self::$_generate_config_xml_mode = true;
$tmp_module->_generateConfigXml();
self::$_generate_config_xml_mode = false;
}
unset($tmp_module);
}
else
$errors[] = sprintf(Tools::displayError('%1$s (class missing in %2$s)'), $module, substr($filepath, strlen(_PS_ROOT_DIR_)));
}
}
// Get modules information from database
if (!empty($module_name_list))
{
$list = Shop::getContextListShopID();
$sql = 'SELECT m.id_module, m.name, (
SELECT COUNT(*) FROM '._DB_PREFIX_.'module_shop ms WHERE m.id_module = ms.id_module AND ms.id_shop IN ('.implode(',', $list).')
) as total
FROM '._DB_PREFIX_.'module m
WHERE m.name IN ('.implode(',', $module_name_list).')';
$results = Db::getInstance()->executeS($sql);
foreach ($results as $result)
{
$moduleCursor = $modulesNameToCursor[$result['name']];
$moduleCursor->id = $result['id_module'];
$moduleCursor->active = ($result['total'] == count($list)) ? 1 : 0;
}
}
// Get Default Country Modules and customer module
$files_list = array(
array('type' => 'addonsNative', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST, 'loggedOnAddons' => 0),
array('type' => 'addonsBought', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_CUSTOMER_MODULES_LIST, 'loggedOnAddons' => 1),
array('type' => 'addonsMustHave', 'file' => _PS_ROOT_DIR_.self::CACHE_FILE_MUST_HAVE_MODULES_LIST, 'loggedOnAddons' => 0),
);
foreach ($files_list as $f)
if (file_exists($f['file']) && ($f['loggedOnAddons'] == 0 || $loggedOnAddons))
{
if (Module::useTooMuchMemory())
{
$errors[] = Tools::displayError('All modules cannot be loaded due to memory limit restrictions, please increase your memory_limit value on your server configuration');
break;
}
$file = $f['file'];
$content = Tools::file_get_contents($file);
$xml = @simplexml_load_string($content, null, LIBXML_NOCDATA);
if ($xml && isset($xml->module))
foreach ($xml->module as $modaddons)
{
$flag_found = 0;
foreach ($module_list as $k => &$m)
if ($m->name == $modaddons->name && !isset($m->available_on_addons))
{
$flag_found = 1;
if ($m->version != $modaddons->version && version_compare($m->version, $modaddons->version) === -1)
$module_list[$k]->version_addons = $modaddons->version;
}
if ($flag_found == 0)
{
$item = new stdClass();
$item->id = 0;
$item->warning = '';
$item->type = strip_tags((string)$f['type']);
$item->name = strip_tags((string)$modaddons->name);
$item->version = strip_tags((string)$modaddons->version);
$item->tab = strip_tags((string)$modaddons->tab);
$item->displayName = strip_tags((string)$modaddons->displayName);
$item->description = stripslashes(strip_tags((string)$modaddons->description));
$item->description_full = stripslashes(strip_tags((string)$modaddons->description_full));
$item->author = strip_tags((string)$modaddons->author);
$item->limited_countries = array();
$item->parent_class = '';
$item->onclick_option = false;
$item->is_configurable = 0;
$item->need_instance = 0;
$item->not_on_disk = 1;
$item->available_on_addons = 1;
$item->trusted = Module::isModuleTrusted($item->name);
$item->active = 0;
$item->description_full = stripslashes($modaddons->description_full);
$item->additional_description = isset($modaddons->additional_description) ? stripslashes($modaddons->additional_description) : null;
$item->compatibility = isset($modaddons->compatibility) ? (array)$modaddons->compatibility : null;
$item->nb_rates = isset($modaddons->nb_rates) ? (array)$modaddons->nb_rates : null;
$item->avg_rate = isset($modaddons->avg_rate) ? (array)$modaddons->avg_rate : null;
$item->badges = isset($modaddons->badges) ? (array)$modaddons->badges : null;
$item->url = isset($modaddons->url) ? $modaddons->url : null;
if (isset($modaddons->img))
{
if (!file_exists(_PS_TMP_IMG_DIR_.md5($modaddons->name).'.jpg'))
if (!file_put_contents(_PS_TMP_IMG_DIR_.md5($modaddons->name).'.jpg', Tools::file_get_contents($modaddons->img)))
copy(_PS_IMG_DIR_.'404.gif', _PS_TMP_IMG_DIR_.md5($modaddons->name).'.jpg');
if (file_exists(_PS_TMP_IMG_DIR_.md5($modaddons->name).'.jpg'))
$item->image = '../img/tmp/'.md5($modaddons->name).'.jpg';
}
if ($item->type == 'addonsMustHave')
{
$item->addons_buy_url = strip_tags((string)$modaddons->url);
$prices = (array)$modaddons->price;
$id_default_currency = Configuration::get('PS_CURRENCY_DEFAULT');
foreach ($prices as $currency => $price)
if ($id_currency = Currency::getIdByIsoCode($currency))
{
$item->price = (float)$price;
$item->id_currency = (int)$id_currency;
if ($id_default_currency == $id_currency)
break;
}
}
$module_list[] = $item;
}
}
}
foreach ($module_list as $key => &$module)
if (defined('_PS_HOST_MODE_') && in_array($module->name, self::$hosted_modules_blacklist))
unset($module_list[$key]);
elseif (isset($modules_installed[$module->name]))
{
$module->installed = true;
$module->database_version = $modules_installed[$module->name]['version'];
$module->interest = $modules_installed[$module->name]['interest'];
$module->enable_device = $modules_installed[$module->name]['enable_device'];
}
else
{
$module->installed = false;
$module->database_version = 0;
$module->interest = 0;
}
usort($module_list, create_function('$a,$b', 'return strnatcasecmp($a->displayName, $b->displayName);'));
if ($errors)
{
if (!isset(Context::getContext()->controller) && !Context::getContext()->controller->controller_name)
{
echo ''.Tools::displayError('The following module(s) could not be loaded').':
';
foreach ($errors as $error)
echo '- '.$error.'
';
echo '
';
}
else
foreach ($errors as $error)
Context::getContext()->controller->errors[] = $error;
}
return $module_list;
}
/**
* Return modules directory list
*
* @return array Modules Directory List
*/
public static function getModulesDirOnDisk()
{
$module_list = array();
$modules = scandir(_PS_MODULE_DIR_);
foreach ($modules as $name)
{
if (is_file(_PS_MODULE_DIR_.$name))
continue;
elseif (is_dir(_PS_MODULE_DIR_.$name.DIRECTORY_SEPARATOR) && Tools::file_exists_cache(_PS_MODULE_DIR_.$name.'/'.$name.'.php'))
{
if (!Validate::isModuleName($name))
throw new PrestaShopException(sprintf('Module %s is not a valid module name', $name));
$module_list[] = $name;
}
}
return $module_list;
}
/**
* Return non native module
*
* @param int $position Take only positionnables modules
* @return array Modules
*/
public static function getNonNativeModuleList()
{
$db = Db::getInstance();
$module_list_xml = _PS_ROOT_DIR_.self::CACHE_FILE_MODULES_LIST;
$native_modules = simplexml_load_file($module_list_xml);
$native_modules = $native_modules->modules;
foreach ($native_modules as $native_modules_type)
if (in_array($native_modules_type['type'], array('native', 'partner')))
{
$arr_native_modules[] = '""';
foreach ($native_modules_type->module as $module)
$arr_native_modules[] = '"'.pSQL($module['name']).'"';
}
return $db->executeS('SELECT * FROM `'._DB_PREFIX_.'module` m WHERE `name` NOT IN ('.implode(',', $arr_native_modules).') ');
}
public static function getNativeModuleList()
{
$module_list_xml = _PS_ROOT_DIR_.self::CACHE_FILE_MODULES_LIST;
if (!file_exists($module_list_xml))
return false;
$native_modules = simplexml_load_file($module_list_xml);
$native_modules = $native_modules->modules;
$modules = array();
foreach ($native_modules as $native_modules_type)
if (in_array($native_modules_type['type'], array('native', 'partner')))
{
foreach ($native_modules_type->module as $module)
$modules[] = $module['name'];
}
return $modules;
}
/**
* Return installed modules
*
* @param int $position Take only positionnables modules
* @return array Modules
*/
public static function getModulesInstalled($position = 0)
{
$sql = 'SELECT m.* FROM `'._DB_PREFIX_.'module` m ';
if ($position)
$sql .= 'LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON m.`id_module` = hm.`id_module`
LEFT JOIN `'._DB_PREFIX_.'hook` k ON hm.`id_hook` = k.`id_hook`
WHERE k.`position` = 1
GROUP BY m.id_module';
return Db::getInstance()->executeS($sql);
}
/**
* Return if the module is provided by addons.prestashop.com or not
*
* @param string $name The module name (the folder name)
* @param string $key The key provided by addons
* @return integer
*/
public static function isModuleTrusted($module_name)
{
$context = Context::getContext();
$theme = new Theme($context->shop->id_theme);
// If the xml file exist, isn't empty, isn't too old
// and if the theme hadn't change
// we use the file, otherwise we regenerate it
if (!(file_exists(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST)
&& filesize(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST) > 0
&& ((time() - filemtime(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST)) < 86400)
&& strpos(Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST), $theme->name) !== false))
self::generateTrustedXml();
// If the module is trusted, which includes both partner modules and modules bought on Addons
if (strpos(Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST), $module_name) !== false)
{
// If the module is not a partner, then return 1 (which means the module is "trusted")
if (strpos(Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_MODULES_LIST), '')== false)
return 1;
// 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")
elseif (strpos(Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_DEFAULT_COUNTRY_MODULES_LIST), '') !== false)
return 1;
// The module seems to be trusted, but it does not seem to be dedicated to this country
return 2;
}
// If the module is already in the untrusted list, then return 0 (untrusted)
elseif (strpos(Tools::file_get_contents(_PS_ROOT_DIR_.self::CACHE_FILE_UNTRUSTED_MODULES_LIST), $module_name) !== false)
return 0;
else
{
// If the module isn't in one of the xml files
// It might have been uploaded recenlty so we check
// Addons API and clear XML files to be regenerated next time
Tools::deleteFile(_PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST);
Tools::deleteFile(_PS_ROOT_DIR_.self::CACHE_FILE_UNTRUSTED_MODULES_LIST);
return (int)Module::checkModuleFromAddonsApi($module_name);
}
}
/**
* Generate XML files for trusted and untrusted modules
*
*/
public static function generateTrustedXml()
{
$modules_on_disk = Module::getModulesDirOnDisk();
$trusted = array();
$untrusted = array();
$trusted_modules_xml = array(
_PS_ROOT_DIR_.self::CACHE_FILE_ALL_COUNTRY_MODULES_LIST,
_PS_ROOT_DIR_.self::CACHE_FILE_MUST_HAVE_MODULES_LIST,
);
// Create 2 arrays with trusted and untrusted modules
foreach ($trusted_modules_xml as $file)
{
$content = Tools::file_get_contents($file);
$xml = @simplexml_load_string($content, null, LIBXML_NOCDATA);
if ($xml && isset($xml->module))
foreach ($xml->module as $modaddons)
$trusted[] = (string)$modaddons->name;
}
foreach (glob(_PS_ROOT_DIR_.'/config/xml/themes/*.xml') as $theme_xml)
if(file_exists($theme_xml))
{
$content = Tools::file_get_contents($theme_xml);
$xml = @simplexml_load_string($content, null, LIBXML_NOCDATA);
foreach ($xml->modules->module as $modaddons)
if((string)$modaddons['action'] == 'install')
$trusted[] = (string)$modaddons['name'];
}
foreach ($modules_on_disk as $name)
{
if (!in_array($name, $trusted))
{
if (Module::checkModuleFromAddonsApi($name))
$trusted[] = $name;
else
$untrusted[] = $name;
}
}
$context = Context::getContext();
$theme = new Theme($context->shop->id_theme);
// Save the 2 arrays into XML files
$trusted_xml = new SimpleXMLElement('');
$trusted_xml->addAttribute('theme', $theme->name);
$modules = $trusted_xml->addChild('modules');
$modules->addAttribute('type', 'trusted');
foreach ($trusted as $key => $name)
{
$module = $modules->addChild('module');
$module->addAttribute('name', $name);
}
$success = file_put_contents( _PS_ROOT_DIR_.self::CACHE_FILE_TRUSTED_MODULES_LIST, $trusted_xml->asXML());
$untrusted_xml = new SimpleXMLElement('');
$modules = $untrusted_xml->addChild('modules');
$modules->addAttribute('type', 'untrusted');
foreach ($untrusted as $key => $name)
{
$module = $modules->addChild('module');
$module->addAttribute('name', $name);
}
$success &= file_put_contents( _PS_ROOT_DIR_.self::CACHE_FILE_UNTRUSTED_MODULES_LIST, $untrusted_xml->asXML());
if ($success)
return true;
else
Tools::displayError('Trusted and Untrusted XML have not been generated properly');
}
/**
* Create the Addons API call from the module name only
*
* @param string $name Module dir name
* @return boolean Returns if the module is trusted by addons.prestashop.com
*/
public static function checkModuleFromAddonsApi($module_name)
{
$obj = Module::getInstanceByName($module_name);
if (!is_object($obj))
return false;
elseif ($obj->module_key === '')
return false;
else
{
$params = array(
'module_name' => $obj->name,
'module_key' => $obj->module_key,
);
$xml = Tools::addonsRequest('check_module', $params);
return (stristr($xml, 'success'));
}
}
/**
* Execute modules for specified hook
*
* @param string $hook_name Hook Name
* @param array $hookArgs Parameters for the functions
* @return string modules output
*/
public static function hookExec($hook_name, $hookArgs = array(), $id_module = null)
{
Tools::displayAsDeprecated();
return Hook::exec($hook_name, $hookArgs, $id_module);
}
public static function hookExecPayment()
{
Tools::displayAsDeprecated();
return Hook::exec('displayPayment');
}
public static function preCall($module_name)
{
return true;
}
/*
@deprecated since 1.6.0.2
*/
public static function getPaypalIgnore()
{
Tools::displayAsDeprecated();
}
/**
* Returns the list of the payment module associated to the current customer
* @see PaymentModule::getInstalledPaymentModules() if you don't care about the context
*
* @return array module informations
*/
public static function getPaymentModules()
{
$context = Context::getContext();
if (isset($context->cart))
$billing = new Address((int)$context->cart->id_address_invoice);
$use_groups = Group::isFeatureActive();
$frontend = true;
$groups = array();
if (isset($context->employee))
$frontend = false;
elseif (isset($context->customer) && $use_groups)
{
$groups = $context->customer->getGroups();
if (!count($groups))
$groups = array(Configuration::get('PS_UNIDENTIFIED_GROUP'));
}
$hookPayment = 'Payment';
if (Db::getInstance()->getValue('SELECT `id_hook` FROM `'._DB_PREFIX_.'hook` WHERE `name` = \'displayPayment\''))
$hookPayment = 'displayPayment';
$list = Shop::getContextListShopID();
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT DISTINCT m.`id_module`, h.`id_hook`, m.`name`, hm.`position`
FROM `'._DB_PREFIX_.'module` m
'.($frontend ? 'LEFT JOIN `'._DB_PREFIX_.'module_country` mc ON (m.`id_module` = mc.`id_module` AND mc.id_shop = '.(int)$context->shop->id.')' : '').'
'.($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.')' : '').'
'.($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.')' : '').'
LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON hm.`id_module` = m.`id_module`
LEFT JOIN `'._DB_PREFIX_.'hook` h ON hm.`id_hook` = h.`id_hook`
WHERE h.`name` = \''.pSQL($hookPayment).'\'
'.(isset($billing) && $frontend ? 'AND mc.id_country = '.(int)$billing->id_country : '').'
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).'
AND hm.id_shop IN('.implode(', ', $list).')
'.((count($groups) && $frontend && $use_groups) ? 'AND (mg.`id_group` IN ('.implode(', ', $groups).'))' : '').'
GROUP BY hm.id_hook, hm.id_module
ORDER BY hm.`position`, m.`name` DESC');
}
/**
* @deprecated 1.5.0 Use Translate::getModuleTranslation()
*/
public static function findTranslation($name, $string, $source)
{
return Translate::getModuleTranslation($name, $string, $source);
}
/**
* Get translation for a given module text
*
* Note: $specific parameter is mandatory for library files.
* Otherwise, translation key will not match for Module library
* when module is loaded with eval() Module::getModulesOnDisk()
*
* @param string $string String to translate
* @param boolean|string $specific filename to use in translation key
* @return string Translation
*/
public function l($string, $specific = false)
{
if (self::$_generate_config_xml_mode)
return $string;
return Translate::getModuleTranslation($this, $string, ($specific) ? $specific : $this->name);
}
/*
* Reposition module
*
* @param boolean $id_hook Hook ID
* @param boolean $way Up (0) or Down (1)
* @param int $position
*/
public function updatePosition($id_hook, $way, $position = null)
{
foreach (Shop::getContextListShopID() as $shop_id)
{
$sql = 'SELECT hm.`id_module`, hm.`position`, hm.`id_hook`
FROM `'._DB_PREFIX_.'hook_module` hm
WHERE hm.`id_hook` = '.(int)$id_hook.' AND hm.`id_shop` = '.$shop_id.'
ORDER BY hm.`position` '.($way ? 'ASC' : 'DESC');
if (!$res = Db::getInstance()->executeS($sql))
continue;
foreach ($res as $key => $values)
if ((int)$values[$this->identifier] == (int)$this->id)
{
$k = $key;
break;
}
if (!isset($k) || !isset($res[$k]) || !isset($res[$k + 1]))
return false;
$from = $res[$k];
$to = $res[$k + 1];
if (isset($position) && !empty($position))
$to['position'] = (int)$position;
$sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
SET `position`= position '.($way ? '-1' : '+1').'
WHERE position between '.(int)(min(array($from['position'], $to['position']))).' AND '.max(array($from['position'], $to['position'])).'
AND `id_hook` = '.(int)$from['id_hook'].' AND `id_shop` = '.$shop_id;
if (!Db::getInstance()->execute($sql))
return false;
$sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
SET `position`='.(int)$to['position'].'
WHERE `'.pSQL($this->identifier).'` = '.(int)$from[$this->identifier].'
AND `id_hook` = '.(int)$to['id_hook'].' AND `id_shop` = '.$shop_id;
if (!Db::getInstance()->execute($sql))
return false;
}
return true;
}
/*
* Reorder modules position
*
* @param boolean $id_hook Hook ID
* @param array $shop_list List of shop
*/
public function cleanPositions($id_hook, $shop_list = null)
{
$sql = 'SELECT `id_module`, `id_shop`
FROM `'._DB_PREFIX_.'hook_module`
WHERE `id_hook` = '.(int)$id_hook.'
'.((!is_null($shop_list) && $shop_list) ? ' AND `id_shop` IN('.implode(', ', array_map('intval', $shop_list)).')' : '').'
ORDER BY `position`';
$results = Db::getInstance()->executeS($sql);
$position = array();
foreach ($results as $row)
{
if (!isset($position[$row['id_shop']]))
$position[$row['id_shop']] = 1;
$sql = 'UPDATE `'._DB_PREFIX_.'hook_module`
SET `position` = '.$position[$row['id_shop']].'
WHERE `id_hook` = '.(int)$id_hook.'
AND `id_module` = '.$row['id_module'].' AND `id_shop` = '.$row['id_shop'];
Db::getInstance()->execute($sql);
$position[$row['id_shop']]++;
}
return true;
}
public function displayError($error)
{
$output = '
';
$this->error = true;
return $output;
}
public function displayConfirmation($string)
{
$output = '
';
return $output;
}
/*
* Return exceptions for module in hook
*
* @param int $id_hook Hook ID
* @return array Exceptions
*/
public function getExceptions($id_hook, $dispatch = false)
{
$cache_id = 'exceptionsCache';
if (!Cache::isStored($cache_id))
{
$exceptionsCache = array();
$sql = 'SELECT * FROM `'._DB_PREFIX_.'hook_module_exceptions`
WHERE `id_shop` IN ('.implode(', ', Shop::getContextListShopID()).')';
$db = Db::getInstance();
$result = $db->executeS($sql, false);
while ($row = $db->nextRow($result))
{
if (!$row['file_name'])
continue;
$key = $row['id_hook'].'-'.$row['id_module'];
if (!isset($exceptionsCache[$key]))
$exceptionsCache[$key] = array();
if (!isset($exceptionsCache[$key][$row['id_shop']]))
$exceptionsCache[$key][$row['id_shop']] = array();
$exceptionsCache[$key][$row['id_shop']][] = $row['file_name'];
}
Cache::store($cache_id, $exceptionsCache);
}
else
$exceptionsCache = Cache::retrieve($cache_id);
$key = $id_hook.'-'.$this->id;
$array_return = array();
if ($dispatch)
{
foreach (Shop::getContextListShopID() as $shop_id)
if (isset($exceptionsCache[$key], $exceptionsCache[$key][$shop_id]))
$array_return[$shop_id] = $exceptionsCache[$key][$shop_id];
}
else
{
foreach (Shop::getContextListShopID() as $shop_id)
if (isset($exceptionsCache[$key], $exceptionsCache[$key][$shop_id]))
foreach ($exceptionsCache[$key][$shop_id] as $file)
if (!in_array($file, $array_return))
$array_return[] = $file;
}
return $array_return;
}
public static function isInstalled($module_name)
{
if (!Cache::isStored('Module::isInstalled'.$module_name))
{
$id_module = Module::getModuleIdByName($module_name);
Cache::store('Module::isInstalled'.$module_name, (bool)$id_module);
}
return Cache::retrieve('Module::isInstalled'.$module_name);
}
public function isEnabledForShopContext()
{
return (bool)Db::getInstance()->getValue('
SELECT COUNT(*) n
FROM `'._DB_PREFIX_.'module_shop`
WHERE id_module='.(int)$this->id.' AND id_shop IN ('.implode(',', array_map('intval', Shop::getContextListShopID())).')
GROUP BY id_module
HAVING n='.(int)count(Shop::getContextListShopID())
);
}
public static function isEnabled($module_name)
{
if (!Cache::isStored('Module::isEnabled'.$module_name))
{
$active = false;
$id_module = Module::getModuleIdByName($module_name);
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))
$active = true;
Cache::store('Module::isEnabled'.$module_name, (bool)$active);
}
return Cache::retrieve('Module::isEnabled'.$module_name);
}
public function isRegisteredInHook($hook)
{
if (!$this->id)
return false;
$sql = 'SELECT COUNT(*)
FROM `'._DB_PREFIX_.'hook_module` hm
LEFT JOIN `'._DB_PREFIX_.'hook` h ON (h.`id_hook` = hm.`id_hook`)
WHERE h.`name` = \''.pSQL($hook).'\' AND hm.`id_module` = '.(int)$this->id;
return Db::getInstance()->getValue($sql);
}
/*
** Template management (display, overload, cache)
*/
protected static function _isTemplateOverloadedStatic($module_name, $template)
{
if (Tools::file_exists_cache(_PS_THEME_DIR_.'modules/'.$module_name.'/'.$template))
return _PS_THEME_DIR_.'modules/'.$module_name.'/'.$template;
elseif (Tools::file_exists_cache(_PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/hook/'.$template))
return _PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/hook/'.$template;
elseif (Tools::file_exists_cache(_PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/front/'.$template))
return _PS_THEME_DIR_.'modules/'.$module_name.'/views/templates/front/'.$template;
elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/views/templates/hook/'.$template))
return false;
elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/views/templates/front/'.$template))
return false;
elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$module_name.'/'.$template))
return false;
return null;
}
protected function _isTemplateOverloaded($template)
{
return Module::_isTemplateOverloadedStatic($this->name, $template);
}
protected function getCacheId($name = null)
{
$cache_array = array();
$cache_array[] = $name !== null ? $name : $this->name;
if (Configuration::get('PS_SSL_ENABLED'))
$cache_array[] = (int)Tools::usingSecureMode();
if (Shop::isFeatureActive())
$cache_array[] = (int)$this->context->shop->id;
if (Group::isFeatureActive())
$cache_array[] = (int)Group::getCurrent()->id;
if (Language::isMultiLanguageActivated())
$cache_array[] = (int)$this->context->language->id;
if (Currency::isMultiCurrencyActivated())
$cache_array[] = (int)$this->context->currency->id;
$cache_array[] = (int)$this->context->country->id;
return implode('|', $cache_array);
}
public function display($file, $template, $cacheId = null, $compileId = null)
{
if (($overloaded = Module::_isTemplateOverloadedStatic(basename($file, '.php'), $template)) === null)
return Tools::displayError('No template found for module').' '.basename($file, '.php');
else
{
if (Tools::getIsset('live_edit'))
$cacheId = null;
$this->smarty->assign(array(
'module_dir' => __PS_BASE_URI__.'modules/'.basename($file, '.php').'/',
'module_template_dir' => ($overloaded ? _THEME_DIR_ : __PS_BASE_URI__).'modules/'.basename($file, '.php').'/',
'allow_push' => $this->allow_push
));
if ($cacheId !== null)
Tools::enableCache();
$result = $this->getCurrentSubTemplate($template, $cacheId, $compileId)->fetch();
if ($cacheId !== null)
Tools::restoreCacheSettings();
$this->resetCurrentSubTemplate($template, $cacheId, $compileId);
return $result;
}
}
protected function getCurrentSubTemplate($template, $cache_id = null, $compile_id = null)
{
if (!isset($this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id]))
{
$this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id] = $this->context->smarty->createTemplate(
$this->getTemplatePath($template),
$cache_id,
$compile_id,
$this->smarty
);
}
return $this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id];
}
protected function resetCurrentSubTemplate($template, $cache_id, $compile_id)
{
$this->current_subtemplate[$template.'_'.$cache_id.'_'.$compile_id] = null;
}
/**
* Get realpath of a template of current module (check if template is overriden too)
*
* @since 1.5.0
* @param string $template
* @return string
*/
public function getTemplatePath($template)
{
$overloaded = $this->_isTemplateOverloaded($template);
if ($overloaded === null)
return null;
if ($overloaded)
return $overloaded;
elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$this->name.'/views/templates/hook/'.$template))
return _PS_MODULE_DIR_.$this->name.'/views/templates/hook/'.$template;
elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$this->name.'/views/templates/front/'.$template))
return _PS_MODULE_DIR_.$this->name.'/views/templates/front/'.$template;
elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$this->name.'/'.$template))
return _PS_MODULE_DIR_.$this->name.'/'.$template;
else
return null;
}
protected function _getApplicableTemplateDir($template)
{
return $this->_isTemplateOverloaded($template) ? _PS_THEME_DIR_ : _PS_MODULE_DIR_.$this->name.'/';
}
public function isCached($template, $cacheId = null, $compileId = null)
{
if (Tools::getIsset('live_edit'))
return false;
Tools::enableCache();
$is_cached = $this->getCurrentSubTemplate($this->getTemplatePath($template), $cacheId, $compileId)->isCached($this->getTemplatePath($template), $cacheId, $compileId);
Tools::restoreCacheSettings();
return $is_cached;
}
/*
* Clear template cache
*
* @param string $template Template name
* @param int null $cache_id
* @param int null $compile_id
* @return int Number of template cleared
*/
protected function _clearCache($template, $cache_id = null, $compile_id = null)
{
Tools::enableCache();
if ($cache_id === null)
$cache_id = $this->name;
$number_of_template_cleared = Tools::clearCache(Context::getContext()->smarty, $this->getTemplatePath($template), $cache_id, $compile_id);
Tools::restoreCacheSettings();
return $number_of_template_cleared;
}
protected function _generateConfigXml()
{
$xml = '
'.$this->name.'
displayName).']]>
version.']]>
description).']]>
author).']]>
tab).']]>'.(isset($this->confirmUninstall) ? "\n\t".'confirmUninstall.']]>' : '').'
'.(isset($this->is_configurable) ? (int)$this->is_configurable : 0).'
'.(int)$this->need_instance.''.(isset($this->limited_countries) ? "\n\t".''.(count($this->limited_countries) == 1 ? $this->limited_countries[0] : '').'' : '').'
';
if (is_writable(_PS_MODULE_DIR_.$this->name.'/'))
{
$iso = substr(Context::getContext()->language->iso_code, 0, 2);
$file = _PS_MODULE_DIR_.$this->name.'/'.($iso == 'en' ? 'config.xml' : 'config_'.$iso.'.xml');
if (!@file_put_contents($file, $xml))
if (!is_writable($file))
{
@unlink($file);
@file_put_contents($file, $xml);
}
@chmod($file, 0664);
}
}
/**
* Check if the module is transplantable on the hook in parameter
* @param string $hook_name
* @return bool if module can be transplanted on hook
*/
public function isHookableOn($hook_name)
{
$retro_hook_name = Hook::getRetroHookName($hook_name);
return (is_callable(array($this, 'hook'.ucfirst($hook_name))) || is_callable(array($this, 'hook'.ucfirst($retro_hook_name))));
}
/**
* Check employee permission for module
* @param array $variable (action)
* @param object $employee
* @return bool if module can be transplanted on hook
*/
public function getPermission($variable, $employee = null)
{
return Module::getPermissionStatic($this->id, $variable, $employee);
}
/**
* Check employee permission for module (static method)
* @param integer $id_module
* @param array $variable (action)
* @param object $employee
* @return bool if module can be transplanted on hook
*/
public static function getPermissionStatic($id_module, $variable, $employee = null)
{
if (!in_array($variable, array('view', 'configure')))
return false;
if (!$employee)
$employee = Context::getContext()->employee;
if ($employee->id_profile == _PS_ADMIN_PROFILE_)
return true;
if (!isset(self::$cache_permissions[$employee->id_profile]))
{
self::$cache_permissions[$employee->id_profile] = array();
$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT `id_module`, `view`, `configure` FROM `'._DB_PREFIX_.'module_access` WHERE `id_profile` = '.(int)$employee->id_profile);
foreach ($result as $row)
{
self::$cache_permissions[$employee->id_profile][$row['id_module']]['view'] = $row['view'];
self::$cache_permissions[$employee->id_profile][$row['id_module']]['configure'] = $row['configure'];
}
}
if (!isset(self::$cache_permissions[$employee->id_profile][$id_module]))
throw new PrestaShopException('No access reference in table module_access for id_module '.$id_module.'.');
return (bool)self::$cache_permissions[$employee->id_profile][$id_module][$variable];
}
/**
* Get Unauthorized modules for a client group
* @param integer group_id
*/
public static function getAuthorizedModules($group_id)
{
return Db::getInstance()->executeS('
SELECT m.`id_module`, m.`name` FROM `'._DB_PREFIX_.'module_group` mg
LEFT JOIN `'._DB_PREFIX_.'module` m ON (m.`id_module` = mg.`id_module`)
WHERE mg.`id_group` = '.(int)$group_id);
}
/**
* Get id module by name
* @param string name
* @return integer id
*/
public static function getModuleIdByName($name)
{
$cache_id = 'Module::getModuleIdByName_'.pSQL($name);
if (!Cache::isStored($cache_id))
{
$result = (int)Db::getInstance()->getValue('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = "'.pSQL($name).'"');
Cache::store($cache_id, $result);
}
return Cache::retrieve($cache_id);
}
/**
* Get module errors
*
* @since 1.5.0
* @return array errors
*/
public function getErrors()
{
return $this->_errors;
}
/**
* Get module messages confirmation
*
* @since 1.5.0
* @return array conf
*/
public function getConfirmations()
{
return $this->_confirmations;
}
/**
* Get local path for module
*
* @since 1.5.0
* @return string
*/
public function getLocalPath()
{
return $this->local_path;
}
/**
* Get uri path for module
*
* @since 1.5.0
* @return string
*/
public function getPathUri()
{
return $this->_path;
}
/*
* Return module position for a given hook
*
* @param boolean $id_hook Hook ID
* @return integer position
*/
public function getPosition($id_hook)
{
if (isset(Hook::$preloadModulesFromHooks))
if (isset(Hook::$preloadModulesFromHooks[$id_hook]))
if (isset(Hook::$preloadModulesFromHooks[$id_hook]['module_position'][$this->id]))
return Hook::$preloadModulesFromHooks[$id_hook]['module_position'][$this->id];
else
return 0;
$result = Db::getInstance()->getRow('
SELECT `position`
FROM `'._DB_PREFIX_.'hook_module`
WHERE `id_hook` = '.(int)$id_hook.'
AND `id_module` = '.(int)$this->id.'
AND `id_shop` = '.(int)Context::getContext()->shop->id);
return $result['position'];
}
/**
* add a warning message to display at the top of the admin page
*
* @param string $msg
*/
public function adminDisplayWarning($msg)
{
if (!($this->context->controller instanceof AdminController))
return false;
$this->context->controller->warnings[] = $msg;
}
/**
* add a info message to display at the top of the admin page
*
* @param string $msg
*/
protected function adminDisplayInformation($msg)
{
if (!($this->context->controller instanceof AdminController))
return false;
$this->context->controller->informations[] = $msg;
}
/**
* Install module's controllers using public property $controllers
* @return bool
*/
private function installControllers()
{
$themes = Theme::getThemes();
$theme_meta_value = array();
foreach ($this->controllers as $controller)
{
$page = 'module-'.$this->name.'-'.$controller;
$result = Db::getInstance()->getValue('SELECT * FROM '._DB_PREFIX_.'meta WHERE page="'.pSQL($page).'"');
if ((int)$result > 0)
continue;
$meta = New Meta();
$meta->page = $page;
$meta->configurable = 0;
$meta->save();
if ((int)$meta->id > 0)
{
foreach ($themes as $theme)
{
$theme_meta_value[] = array(
'id_theme' => $theme->id,
'id_meta' => $meta->id,
'left_column' => (int)$theme->default_left_column,
'right_column' => (int)$theme->default_right_column
);
}
}
else
$this->_errors[] = sprintf(Tools::displayError('Unable to install controller: %s'), $controller);
}
if (count($theme_meta_value) > 0)
return Db::getInstance()->insert('theme_meta', $theme_meta_value);
return true;
}
/**
* Install overrides files for the module
*
* @return bool
*/
public function installOverrides()
{
if (!is_dir($this->getLocalPath().'override'))
return true;
$result = true;
foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file)
{
$class = basename($file, '.php');
if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core'))
$result &= $this->addOverride($class);
}
return $result;
}
/**
* Uninstall overrides files for the module
*
* @return bool
*/
public function uninstallOverrides()
{
if (!is_dir($this->getLocalPath().'override'))
return true;
$result = true;
foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file)
{
$class = basename($file, '.php');
if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core'))
$result &= $this->removeOverride($class);
}
return $result;
}
/**
* Add all methods in a module override to the override class
*
* @param string $classname
* @return bool
*/
public function addOverride($classname)
{
$path = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core');
// Check if there is already an override file, if not, we just need to copy the file
if (PrestaShopAutoload::getInstance()->getClassPath($classname))
{
// Check if override file is writable
$override_path = _PS_ROOT_DIR_.'/'.PrestaShopAutoload::getInstance()->getClassPath($classname);
if ((!file_exists($override_path) && !is_writable(dirname($override_path))) || (file_exists($override_path) && !is_writable($override_path)))
throw new Exception(sprintf(Tools::displayError('file (%s) not writable'), $override_path));
// 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
do $uniq = uniqid();
while (class_exists($classname.'OverrideOriginal_remove', false));
// Make a reflection of the override class and the module override class
$override_file = file($override_path);
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)));
$override_class = new ReflectionClass($classname.'OverrideOriginal'.$uniq);
$module_file = file($this->getLocalPath().'override'.DIRECTORY_SEPARATOR.$path);
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)));
$module_class = new ReflectionClass($classname.'Override'.$uniq);
// Check if none of the methods already exists in the override class
foreach ($module_class->getMethods() as $method)
if ($override_class->hasMethod($method->getName()))
throw new Exception(sprintf(Tools::displayError('The method %1$s in the class %2$s is already overridden.'), $method->getName(), $classname));
// Check if none of the properties already exists in the override class
foreach ($module_class->getProperties() as $property)
if ($override_class->hasProperty($property->getName()))
throw new Exception(sprintf(Tools::displayError('The property %1$s in the class %2$s is already defined.'), $property->getName(), $classname));
// Insert the methods from module override in override
$copy_from = array_slice($module_file, $module_class->getStartLine() + 1, $module_class->getEndLine() - $module_class->getStartLine() - 2);
array_splice($override_file, $override_class->getEndLine() - 1, 0, $copy_from);
$code = implode('', $override_file);
file_put_contents($override_path, $code);
}
else
{
$override_src = $this->getLocalPath().'override'.DIRECTORY_SEPARATOR.$path;
$override_dest = _PS_ROOT_DIR_.DIRECTORY_SEPARATOR.'override'.DIRECTORY_SEPARATOR.$path;
if (!is_writable(dirname($override_dest)))
throw new Exception(sprintf(Tools::displayError('directory (%s) not writable'), dirname($override_dest)));
copy($override_src, $override_dest);
// Re-generate the class index
Tools::generateIndex();
}
return true;
}
/**
* Remove all methods in a module override from the override class
*
* @param string $classname
* @return bool
*/
public function removeOverride($classname)
{
$path = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core');
if (!PrestaShopAutoload::getInstance()->getClassPath($classname))
return true;
// Check if override file is writable
$override_path = _PS_ROOT_DIR_.'/'.PrestaShopAutoload::getInstance()->getClassPath($classname);
if (!is_writable($override_path))
return false;
// 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
do $uniq = uniqid();
while (class_exists($classname.'OverrideOriginal_remove', false));
// Make a reflection of the override class and the module override class
$override_file = file($override_path);
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)));
$override_class = new ReflectionClass($classname.'OverrideOriginal_remove'.$uniq);
$module_file = file($this->getLocalPath().'override/'.$path);
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)));
$module_class = new ReflectionClass($classname.'Override_remove'.$uniq);
// Remove methods from override file
$override_file = file($override_path);
foreach ($module_class->getMethods() as $method)
{
if (!$override_class->hasMethod($method->getName()))
continue;
$method = $override_class->getMethod($method->getName());
$length = $method->getEndLine() - $method->getStartLine() + 1;
$module_method = $module_class->getMethod($method->getName());
$module_length = $module_method->getEndLine() - $module_method->getStartLine() + 1;
$override_file_orig = $override_file;
$orig_content = preg_replace("/\s/", '', implode('', array_splice($override_file, $method->getStartLine() - 1, $length, array_pad(array(), $length, '#--remove--#'))));
$module_content = preg_replace("/\s/", '', implode('', array_splice($module_file, $module_method->getStartLine() - 1, $length, array_pad(array(), $length, '#--remove--#'))));
if (md5($module_content) != md5($orig_content))
$override_file = $override_file_orig;
}
// Remove properties from override file
foreach ($module_class->getProperties() as $property)
{
if (!$override_class->hasProperty($property->getName()))
continue;
// Remplacer la ligne de declaration par "remove"
foreach ($override_file as $line_number => &$line_content)
if (preg_match('/(public|private|protected|const)\s+(static\s+)?(\$)?'.$property->getName().'/i', $line_content))
{
$line_content = '#--remove--#';
break;
}
}
// Rewrite nice code
$code = '';
foreach ($override_file as $line)
{
if ($line == '#--remove--#')
continue;
$code .= $line;
}
file_put_contents($override_path, $code);
// Re-generate the class index
Tools::generateIndex();
return true;
}
}
function ps_module_version_sort($a, $b)
{
return version_compare($a['version'], $b['version']);
}