* @copyright 2007-2015 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)) { $result = Db::getInstance()->getValue(' SELECT `name` FROM `'._DB_PREFIX_.'hook` WHERE `id_hook` = '.(int)$hook_id); Cache::store($cache_id, $result); return $result; } 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)) { $result = Db::getInstance()->getValue(' SELECT `live_edit` FROM `'._DB_PREFIX_.'hook` WHERE `id_hook` = '.(int)$hook_id); Cache::store($cache_id, $result); return $result; } 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 $hook_alias; } return Cache::retrieve($cache_id); } /** * Return backward compatibility 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_module` hm STRAIGHT_JOIN `'._DB_PREFIX_.'hook` h ON (h.id_hook = hm.id_hook AND hm.id_shop = '.(int)Context::getContext()->shop->id.') STRAIGHT_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 $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).(count($groups) ? ' AND mg.`id_group` IN ('.implode(', ', $groups).')' : '')); } elseif (count($groups)) { $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 backward compatibility 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 * @param bool $array_return If specified, module output will be set by name in an array * @param bool $check_exceptions Check permission exceptions * @param bool $use_push Force change to be refreshed on Dashboard widgets * @param int $id_shop If specified, hook will be execute the shop with this ID * * @throws PrestaShopException * * @return string/array 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) { if (defined('PS_INSTALLATION_IN_PROGRESS')) { return; } 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 = $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'], Hook::$native_module)) { continue; } // Check permissions if ($check_exceptions) { $exceptions = Module::getExceptionsStatic($array['id_module'], $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; } //Backward compatibility 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) && !Module::getPermissionStatic($array['id_module'], 'view', $context->employee)) { continue; } } if (!($moduleInstance = Module::getInstanceByName($array['module']))) { 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 = Hook::coreCallHook($moduleInstance, 'hook'.$hook_name, $hook_args); } elseif ($hook_retro_callable) { $display = Hook::coreCallHook($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']); } elseif ($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 ? '
' : '').$output.($live_edit ? '
' : ''); }// Return html string } public static function coreCallHook($module, $method, $params) { // Define if we will log modules performances for this session if (Module::$_log_modules_perfs === null) { $modulo = _PS_DEBUG_PROFILING_ ? 1 : Configuration::get('PS_log_modules_perfs_MODULO'); Module::$_log_modules_perfs = ($modulo && mt_rand(0, $modulo - 1) == 0); if (Module::$_log_modules_perfs) { Module::$_log_modules_perfs_session = mt_rand(); } } // Immediately return the result if we do not log performances if (!Module::$_log_modules_perfs) { return $module->{$method}($params); } // Store time and memory before and after hook call and save the result in the database $time_start = microtime(true); $memory_start = memory_get_usage(true); // Call hook $r = $module->{$method}($params); $time_end = microtime(true); $memory_end = memory_get_usage(true); Db::getInstance()->execute(' INSERT INTO '._DB_PREFIX_.'modules_perfs (session, module, method, time_start, time_end, memory_start, memory_end) VALUES ('.(int)Module::$_log_modules_perfs_session.', "'.pSQL($module->name).'", "'.pSQL($method).'", "'.pSQL($time_start).'", "'.pSQL($time_end).'", '.(int)$memory_start.', '.(int)$memory_end.')'); return $r; } public static function wrapLiveEdit($display, $moduleInstance, $id_hook) { return '
' .Tools::safeOutput($moduleInstance->displayName).' '.$display.'
'; } /** * @deprecated 1.5.0 */ public static function updateOrderStatus($new_order_status_id, $id_order) { Tools::displayAsDeprecated(); $order = new Order((int)$id_order); $new_os = new OrderState((int)$new_order_status_id, $order->id_lang); $return = ((int)$new_os->id == Configuration::get('PS_OS_PAYMENT')) ? Hook::exec('paymentConfirm', array('id_order' => (int)($order->id))) : true; $return = Hook::exec('updateOrderStatus', array('newOrderStatus' => $new_os, 'id_order' => (int)($order->id))) && $return; return $return; } /** * @deprecated 1.5.0 */ public static function postUpdateOrderStatus($new_order_status_id, $id_order) { Tools::displayAsDeprecated(); $order = new Order((int)$id_order); $new_os = new OrderState((int)$new_order_status_id, $order->id_lang); $return = Hook::exec('postUpdateOrderStatus', array('newOrderStatus' => $new_os, 'id_order' => (int)($order->id))); return $return; } /** * @deprecated 1.5.0 */ public static function orderConfirmation($id_order) { Tools::displayAsDeprecated(); if (Validate::isUnsignedId($id_order)) { $params = array(); $order = new Order((int)$id_order); $currency = new Currency((int)$order->id_currency); if (Validate::isLoadedObject($order)) { $cart = new Cart((int)$order->id_cart); $params['total_to_pay'] = $cart->getOrderTotal(); $params['currency'] = $currency->sign; $params['objOrder'] = $order; $params['currencyObj'] = $currency; return Hook::exec('orderConfirmation', $params); } } return false; } /** * @deprecated 1.5.0 */ public static function paymentReturn($id_order, $id_module) { Tools::displayAsDeprecated(); if (Validate::isUnsignedId($id_order) && Validate::isUnsignedId($id_module)) { $params = array(); $order = new Order((int)($id_order)); $currency = new Currency((int)($order->id_currency)); if (Validate::isLoadedObject($order)) { $cart = new Cart((int)$order->id_cart); $params['total_to_pay'] = $cart->getOrderTotal(); $params['currency'] = $currency->sign; $params['objOrder'] = $order; $params['currencyObj'] = $currency; return Hook::exec('paymentReturn', $params, (int)($id_module)); } } return false; } /** * @deprecated 1.5.0 */ public static function PDFInvoice($pdf, $id_order) { Tools::displayAsDeprecated(); if (!is_object($pdf) || !Validate::isUnsignedId($id_order)) { return false; } return Hook::exec('PDFInvoice', array('pdf' => $pdf, 'id_order' => $id_order)); } /** * @deprecated 1.5.0 */ public static function backBeforePayment($module) { Tools::displayAsDeprecated(); if ($module) { return Hook::exec('backBeforePayment', array('module' => strval($module))); } } /** * @deprecated 1.5.0 */ public static function updateCarrier($id_carrier, $carrier) { Tools::displayAsDeprecated(); if (!Validate::isUnsignedId($id_carrier) || !is_object($carrier)) { return false; } return Hook::exec('updateCarrier', array('id_carrier' => $id_carrier, 'carrier' => $carrier)); } /** * Preload hook modules cache * * @deprecated 1.5.0 use Hook::getHookModuleList() instead * * @return bool preload_needed */ public static function preloadHookModulesCache() { Tools::displayAsDeprecated(); if (!is_null(self::$_hook_modules_cache)) { return false; } self::$_hook_modules_cache = Hook::getHookModuleList(); return true; } /** * Return hook ID from name * * @param string $hook_name Hook name * @return int Hook ID * * @deprecated since 1.5.0 use Hook::getIdByName() instead */ public static function get($hook_name) { Tools::displayAsDeprecated(); if (!Validate::isHookName($hook_name)) { die(Tools::displayError()); } $result = Db::getInstance()->getRow(' SELECT `id_hook`, `name` FROM `'._DB_PREFIX_.'hook` WHERE `name` = \''.pSQL($hook_name).'\''); return ($result ? $result['id_hook'] : false); } /** * Called when quantity of a product is updated. * * @deprecated 1.5.3.0 * * @param Cart $cart * @param Order $order * @param Customer $customer * @param Currency $currency * @param $orderStatus * * @throws PrestaShopException * * @return string */ public static function newOrder($cart, $order, $customer, $currency, $order_status) { Tools::displayAsDeprecated(); return Hook::exec('newOrder', array( 'cart' => $cart, 'order' => $order, 'customer' => $customer, 'currency' => $currency, 'orderStatus' => $order_status)); } /** * @deprecated 1.5.0 */ public static function updateQuantity($product, $order = null) { Tools::displayAsDeprecated(); return Hook::exec('updateQuantity', array('product' => $product, 'order' => $order)); } /** * @deprecated 1.5.0 */ public static function productFooter($product, $category) { Tools::displayAsDeprecated(); return Hook::exec('productFooter', array('product' => $product, 'category' => $category)); } /** * @deprecated 1.5.0 */ public static function productOutOfStock($product) { Tools::displayAsDeprecated(); return Hook::exec('productOutOfStock', array('product' => $product)); } /** * @deprecated 1.5.0 */ public static function addProduct($product) { Tools::displayAsDeprecated(); return Hook::exec('addProduct', array('product' => $product)); } /** * @deprecated 1.5.0 */ public static function updateProduct($product) { Tools::displayAsDeprecated(); return Hook::exec('updateProduct', array('product' => $product)); } /** * @deprecated 1.5.0 */ public static function deleteProduct($product) { Tools::displayAsDeprecated(); return Hook::exec('deleteProduct', array('product' => $product)); } /** * @deprecated 1.5.0 */ public static function updateProductAttribute($id_product_attribute) { Tools::displayAsDeprecated(); return Hook::exec('updateProductAttribute', array('id_product_attribute' => $id_product_attribute)); } }