* @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 */ class HookCore extends ObjectModel { /** * @var string Hook name identifier */ public $name; /** * @var string Hook title (displayed in BO) */ public $title; /** * @var string Hook description */ public $description; /** * @var bool */ public $position = false; /** * @var bool Is this hook usable with live edit ? */ public $live_edit = false; /** * @var array List of executed hooks on this page */ public static $executed_hooks = array(); public static $native_module; /** * @see ObjectModel::$definition */ public static $definition = array( 'table' => 'hook', 'primary' => 'id_hook', 'fields' => array( 'name' => array('type' => self::TYPE_STRING, 'validate' => 'isHookName', 'required' => true, 'size' => 64), 'title' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName'), 'description' => array('type' => self::TYPE_HTML, 'validate' => 'isCleanHtml'), 'position' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'live_edit' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), ), ); /** * @deprecated 1.5.0 */ protected static $_hook_modules_cache = null; /** * @deprecated 1.5.0 */ protected static $_hook_modules_cache_exec = null; public function add($autodate = true, $null_values = false) { Cache::clean('hook_idsbyname'); return parent::add($autodate, $null_values); } /** * Return Hooks List * * @param bool $position * @return array Hooks List */ public static function getHooks($position = false) { return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' SELECT * FROM `'._DB_PREFIX_.'hook` h '.($position ? 'WHERE h.`position` = 1' : '').' ORDER BY `name`' ); } /** * Return hook ID from name * * @param string $hook_name Hook name * @return int Hook ID */ public static function getIdByName($hook_name) { $hook_name = strtolower($hook_name); if (!Validate::isHookName($hook_name)) return false; $cache_id = 'hook_idsbyname'; if (!Cache::isStored($cache_id)) { // Get all hook ID by name and alias $hook_ids = array(); $db = Db::getInstance(); $result = $db->ExecuteS(' SELECT `id_hook`, `name` FROM `'._DB_PREFIX_.'hook` UNION SELECT `id_hook`, ha.`alias` as name FROM `'._DB_PREFIX_.'hook_alias` ha INNER JOIN `'._DB_PREFIX_.'hook` h ON ha.name = h.name', false); while ($row = $db->nextRow($result)) $hook_ids[strtolower($row['name'])] = $row['id_hook']; Cache::store($cache_id, $hook_ids); } else $hook_ids = Cache::retrieve($cache_id); return (isset($hook_ids[$hook_name]) ? $hook_ids[$hook_name] : false); } /** * Return hook ID from name */ public static function getNameById($hook_id) { $cache_id = 'hook_namebyid_'.$hook_id; if (!Cache::isStored($cache_id)) Cache::store($cache_id, Db::getInstance()->getValue(' SELECT `name` FROM `'._DB_PREFIX_.'hook` WHERE `id_hook` = '.(int)$hook_id) ); return Cache::retrieve($cache_id); } /** * Return hook live edit bool from ID */ public static function getLiveEditById($hook_id) { $cache_id = 'hook_live_editbyid_'.$hook_id; if (!Cache::isStored($cache_id)) Cache::store($cache_id, Db::getInstance()->getValue(' SELECT `live_edit` FROM `'._DB_PREFIX_.'hook` WHERE `id_hook` = '.(int)$hook_id) ); return Cache::retrieve($cache_id); } /** * Get list of hook alias * * @since 1.5.0 * @return array */ public static function getHookAliasList() { $cache_id = 'hook_alias'; if (!Cache::isStored($cache_id)) { $hook_alias_list = Db::getInstance()->executeS('SELECT * FROM `'._DB_PREFIX_.'hook_alias`'); $hook_alias = array(); if ($hook_alias_list) foreach ($hook_alias_list as $ha) $hook_alias[strtolower($ha['alias'])] = $ha['name']; Cache::store($cache_id, $hook_alias); } return Cache::retrieve($cache_id); } /** * Return retrocompatible hook name * * @since 1.5.0 * @param string $hook_name Hook name * @return int Hook ID */ public static function getRetroHookName($hook_name) { $alias_list = Hook::getHookAliasList(); if (isset($alias_list[strtolower($hook_name)])) return $alias_list[strtolower($hook_name)]; $retro_hook_name = array_search($hook_name, $alias_list); if ($retro_hook_name === false) return ''; return $retro_hook_name; } /** * Get list of all registered hooks with modules * * @since 1.5.0 * @return array */ public static function getHookModuleList() { $cache_id = 'hook_module_list'; if (!Cache::isStored($cache_id)) { $results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' SELECT h.id_hook, h.name as h_name, title, description, h.position, live_edit, hm.position as hm_position, m.id_module, m.name, active FROM `'._DB_PREFIX_.'hook` h INNER JOIN `'._DB_PREFIX_.'hook_module` hm ON (h.id_hook = hm.id_hook AND hm.id_shop = '.(int)Context::getContext()->shop->id.') INNER JOIN `'._DB_PREFIX_.'module` as m ON (m.id_module = hm.id_module) ORDER BY hm.position'); $list = array(); foreach ($results as $result) { if (!isset($list[$result['id_hook']])) $list[$result['id_hook']] = array(); $list[$result['id_hook']][$result['id_module']] = array( 'id_hook' => $result['id_hook'], 'title' => $result['title'], 'description' => $result['description'], 'hm.position' => $result['position'], 'live_edit' => $result['live_edit'], 'm.position' => $result['hm_position'], 'id_module' => $result['id_module'], 'name' => $result['name'], 'active' => $result['active'], ); } Cache::store($cache_id, $list); // @todo remove this in 1.6, we keep it in 1.5 for retrocompatibility Hook::$_hook_modules_cache = $list; } return Cache::retrieve($cache_id); } /** * Return Hooks List * * @since 1.5.0 * @param int $id_hook * @param int $id_module * @return array Modules List */ public static function getModulesFromHook($id_hook, $id_module = null) { $hm_list = Hook::getHookModuleList(); $module_list = (isset($hm_list[$id_hook])) ? $hm_list[$id_hook] : array(); if ($id_module) return (isset($module_list[$id_module])) ? array($module_list[$id_module]) : array(); return $module_list; } /** * Get list of modules we can execute per hook * * @since 1.5.0 * @param string $hook_name Get list of modules for this hook if given * @return array */ public static function getHookModuleExecList($hook_name = null) { $context = Context::getContext(); $cache_id = 'hook_module_exec_list_'.(isset($context->shop->id) ? '_'.$context->shop->id : '' ).((isset($context->customer)) ? '_'.$context->customer->id : ''); if (!Cache::isStored($cache_id) || $hook_name == 'displayPayment' || $hook_name == 'displayBackOfficeHeader') { $frontend = true; $groups = array(); $use_groups = Group::isFeatureActive(); if (isset($context->employee)) $frontend = false; else { // Get groups list if ($use_groups) { if (isset($context->customer) && $context->customer->isLogged()) $groups = $context->customer->getGroups(); elseif (isset($context->customer) && $context->customer->isLogged(true)) $groups = array((int)Configuration::get('PS_GUEST_GROUP')); else $groups = array((int)Configuration::get('PS_UNIDENTIFIED_GROUP')); } } // SQL Request $sql = new DbQuery(); $sql->select('h.`name` as hook, m.`id_module`, h.`id_hook`, m.`name` as module, h.`live_edit`'); $sql->from('module', 'm'); if ($hook_name != 'displayBackOfficeHeader') { $sql->join(Shop::addSqlAssociation('module', 'm', true, 'module_shop.enable_device & '.(int)Context::getContext()->getDevice())); $sql->innerJoin('module_shop', 'ms', 'ms.`id_module` = m.`id_module`'); } $sql->innerJoin('hook_module', 'hm', 'hm.`id_module` = m.`id_module`'); $sql->innerJoin('hook', 'h', 'hm.`id_hook` = h.`id_hook`'); if ($hook_name != 'displayPayment') $sql->where('h.name != "displayPayment"'); // For payment modules, we check that they are available in the contextual country elseif ($frontend) { if (Validate::isLoadedObject($context->country)) $sql->where('(h.name = "displayPayment" AND (SELECT id_country FROM '._DB_PREFIX_.'module_country mc WHERE mc.id_module = m.id_module AND id_country = '.(int)$context->country->id.' AND id_shop = '.(int)$context->shop->id.' LIMIT 1) = '.(int)$context->country->id.')'); if (Validate::isLoadedObject($context->currency)) $sql->where('(h.name = "displayPayment" AND (SELECT id_currency FROM '._DB_PREFIX_.'module_currency mcr WHERE mcr.id_module = m.id_module AND id_currency IN ('.(int)$context->currency->id.', -1, -2) LIMIT 1) IN ('.(int)$context->currency->id.', -1, -2))'); } if (Validate::isLoadedObject($context->shop)) $sql->where('hm.id_shop = '.(int)$context->shop->id); if ($frontend) { if ($use_groups) { $sql->leftJoin('module_group', 'mg', 'mg.`id_module` = m.`id_module`'); if (Validate::isLoadedObject($context->shop)) $sql->where('mg.id_shop = '.((int)$context->shop->id).' AND mg.`id_group` IN ('.implode(', ', $groups).')'); else $sql->where('mg.`id_group` IN ('.implode(', ', $groups).')'); } } $sql->groupBy('hm.id_hook, hm.id_module'); $sql->orderBy('hm.`position`'); $list = array(); if ($result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql)) foreach ($result as $row) { $row['hook'] = strtolower($row['hook']); if (!isset($list[$row['hook']])) $list[$row['hook']] = array(); $list[$row['hook']][] = array( 'id_hook' => $row['id_hook'], 'module' => $row['module'], 'id_module' => $row['id_module'], 'live_edit' => $row['live_edit'], ); } if ($hook_name != 'displayPayment' && $hook_name != 'displayBackOfficeHeader') { Cache::store($cache_id, $list); // @todo remove this in 1.6, we keep it in 1.5 for retrocompatibility self::$_hook_modules_cache_exec = $list; } } else $list = Cache::retrieve($cache_id); // If hook_name is given, just get list of modules for this hook if ($hook_name) { $retro_hook_name = strtolower(Hook::getRetroHookName($hook_name)); $hook_name = strtolower($hook_name); $return = array(); $inserted_modules = array(); if (isset($list[$hook_name])) $return = $list[$hook_name]; foreach ($return as $module) $inserted_modules[] = $module['id_module']; if (isset($list[$retro_hook_name])) foreach ($list[$retro_hook_name] as $retro_module_call) if (!in_array($retro_module_call['id_module'], $inserted_modules)) $return[] = $retro_module_call; return (count($return) > 0 ? $return : false); } else return $list; } /** * Execute modules for specified hook * * @param string $hook_name Hook Name * @param array $hook_args Parameters for the functions * @param int $id_module Execute hook for this module only * @return string modules output */ public static function exec($hook_name, $hook_args = array(), $id_module = null, $array_return = false, $check_exceptions = true, $use_push = false, $id_shop = null) { static $disable_non_native_modules = null; if ($disable_non_native_modules === null) $disable_non_native_modules = (bool)Configuration::get('PS_DISABLE_NON_NATIVE_MODULE'); // Check arguments validity if (($id_module && !is_numeric($id_module)) || !Validate::isHookName($hook_name)) throw new PrestaShopException('Invalid id_module or hook_name'); // If no modules associated to hook_name or recompatible hook name, we stop the function if (!$module_list = Hook::getHookModuleExecList($hook_name)) return ''; // Check if hook exists if (!$id_hook = Hook::getIdByName($hook_name)) return false; // Store list of executed hooks on this page Hook::$executed_hooks[$id_hook] = $hook_name; $live_edit = false; $context = Context::getContext(); if (!isset($hook_args['cookie']) || !$hook_args['cookie']) $hook_args['cookie'] = $context->cookie; if (!isset($hook_args['cart']) || !$hook_args['cart']) $hook_args['cart'] = $context->cart; $retro_hook_name = Hook::getRetroHookName($hook_name); // Look on modules list $altern = 0; $output = ''; if ($disable_non_native_modules && !isset(Hook::$native_module)) Hook::$native_module = Module::getNativeModuleList(); $different_shop = false; if ($id_shop !== null && Validate::isUnsignedId($id_shop) && $id_shop != $context->shop->getContextShopID()) { $old_context_shop_id = $context->shop->getContextShopID(); $old_context = $context->shop->getContext(); $old_shop = clone $context->shop; $shop = new Shop((int)$id_shop); if (Validate::isLoadedObject($shop)) { $context->shop = $shop; $context->shop->setContext(Shop::CONTEXT_SHOP, $shop->id); $different_shop = true; } } foreach ($module_list as $array) { // Check errors if ($id_module && $id_module != $array['id_module']) continue; if ((bool)$disable_non_native_modules && Hook::$native_module && count(Hook::$native_module) && !in_array($array['module'], self::$native_module)) continue; if (!($moduleInstance = Module::getInstanceByName($array['module']))) continue; // Check permissions if ($check_exceptions) { $exceptions = $moduleInstance->getExceptions($array['id_hook']); $controller = Dispatcher::getInstance()->getController(); $controller_obj = Context::getContext()->controller; //check if current controller is a module controller if (isset($controller_obj->module) && Validate::isLoadedObject($controller_obj->module)) $controller = 'module-'.$controller_obj->module->name.'-'.$controller; if (in_array($controller, $exceptions)) continue; //retro compat of controller names $matching_name = array( 'authentication' => 'auth', 'productscomparison' => 'compare' ); if (isset($matching_name[$controller]) && in_array($matching_name[$controller], $exceptions)) continue; if (Validate::isLoadedObject($context->employee) && !$moduleInstance->getPermission('view', $context->employee)) continue; } if ($use_push && !$moduleInstance->allow_push) continue; // Check which / if method is callable $hook_callable = is_callable(array($moduleInstance, 'hook'.$hook_name)); $hook_retro_callable = is_callable(array($moduleInstance, 'hook'.$retro_hook_name)); if (($hook_callable || $hook_retro_callable) && Module::preCall($moduleInstance->name)) { $hook_args['altern'] = ++$altern; if ($use_push && isset($moduleInstance->push_filename) && file_exists($moduleInstance->push_filename)) Tools::waitUntilFileIsModified($moduleInstance->push_filename, $moduleInstance->push_time_limit); // Call hook method if ($hook_callable) $display = $moduleInstance->{'hook'.$hook_name}($hook_args); elseif ($hook_retro_callable) $display = $moduleInstance->{'hook'.$retro_hook_name}($hook_args); // Live edit if (!$array_return && $array['live_edit'] && Tools::isSubmit('live_edit') && Tools::getValue('ad') && Tools::getValue('liveToken') == Tools::getAdminToken('AdminModulesPositions'.(int)Tab::getIdFromClassName('AdminModulesPositions').(int)Tools::getValue('id_employee'))) { $live_edit = true; $output .= self::wrapLiveEdit($display, $moduleInstance, $array['id_hook']); } else if ($array_return) $output[$moduleInstance->name] = $display; else $output .= $display; } } if ($different_shop) { $context->shop = $old_shop; $context->shop->setContext($old_context, $shop->id); } if ($array_return) return $output; else return ($live_edit ? '