File: /home/assibfaf/public_html/prox-classes/Hook.php
<?php
/**
* @package Proxim
* @author Davison Pro <davis@davisonpro.dev | https://davisonpro.dev>
* @copyright 2019 Proxim
* @version 1.5.0
* @since File available since Release 1.0.0
*/
namespace Proxim;
use Db;
use Exception;
use Proxim\Adapter\SymfonyContainer;
use Proxim\Database\DbQuery;
use Proxim\Cache\Cache;
use Proxim\Module\Module;
use Proxim\ObjectModel;
use Proxim\Site\Site;
/**
* Class Hook.
*/
class Hook 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 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' => 'hook_id',
'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'),
),
);
const MODULE_LIST_BY_HOOK_KEY = 'hook_module_exec_list_';
public function add($autodate = true, $null_values = false)
{
Cache::clean('hook_idsbyname');
return parent::add($autodate, $null_values);
}
public static function normalizeHookName($hookName)
{
if (strtolower($hookName) == 'displayheader') {
return 'displayHeader';
}
return $hookName;
}
public static function isDisplayHookName($hook_name)
{
$hook_name = strtolower(self::normalizeHookName($hook_name));
return strpos($hook_name, 'display') === 0;
}
public static function registerHook($module_instance, $hook_name)
{
$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 Exception('Invalid hook name');
}
if (!isset($module_instance->id) || !is_numeric($module_instance->id)) {
return false;
}
Hook::exec('actionModuleRegisterHookBefore', array('object' => $module_instance, 'hook_name' => $hook_name));
// Get hook id
$hook_id = Hook::getIdByName($hook_name);
// If hook does not exist, we create it
if (!$hook_id) {
$new_hook = new Hook();
$new_hook->name = pSQL($hook_name);
$new_hook->title = pSQL($hook_name);
$new_hook->position = 1;
$new_hook->add();
$hook_id = $new_hook->id;
if (!$hook_id) {
return false;
}
}
// Get module position in hook
$sql = 'SELECT MAX(`position`) AS position FROM ' . Db::prefix('hook_module') . ' WHERE `hook_id` = ' . (int) $hook_id;
if (!$position = Db::getInstance()->getValue($sql)) {
$position = 0;
}
// Register module in hook
$return &= Db::getInstance()->insert('hook_module', array(
'module_id' => (int) $module_instance->id,
'hook_id' => (int) $hook_id,
'position' => (int) ($position + 1),
));
Hook::exec('actionModuleRegisterHookAfter', array('object' => $module_instance, 'hook_name' => $hook_name));
}
return $return;
}
public static function unregisterHook($module_instance, $hook_name, $shop_list = null)
{
if (is_numeric($hook_name)) {
// $hook_name passed it the hook_id
$hook_id = $hook_name;
$hook_name = Hook::getNameById((int) $hook_id);
} else {
$hook_id = Hook::getIdByName($hook_name);
}
if (!$hook_id) {
return false;
}
Hook::exec('actionModuleUnRegisterHookBefore', array('object' => $module_instance, 'hook_name' => $hook_name));
// Unregister module on hook by id
$result = Db::getInstance()->execute(
'DELETE FROM ' . Db::prefix('hook_module') . ' WHERE `module_id` = ' . (int) $module_instance->id . ' AND `hook_id` = ' . (int) $hook_id
);
Hook::exec('actionModuleUnRegisterHookAfter', array('object' => $module_instance, 'hook_name' => $hook_name));
return $result;
}
/**
* 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 `hook_id`, `name`
FROM ' . Db::prefix('hook') . '
UNION
SELECT `hook_id`, 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['hook_id'];
}
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 `hook_id` = ' . (int) $hook_id
);
Cache::store($cache_id, $result);
return $result;
}
return Cache::retrieve($cache_id);
}
/**
* 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)
{
$application = Application::getInstance();
$site = $application->container->site;
$cache_id = self::MODULE_LIST_BY_HOOK_KEY . (isset($site->id) ? '_' . $site->id : '');
if (!Cache::isStored($cache_id)) {
// SQL Request
$sql = new DbQuery();
$sql->select('h.`name` as hook, m.`module_id`, h.`hook_id`, m.`name` as module');
$sql->from('module', 'm');
$sql->innerJoin('hook_module', 'hm', 'hm.`module_id` = m.`module_id`');
$sql->innerJoin('hook', 'h', 'hm.`hook_id` = h.`hook_id`');
if ($site && Validate::isLoadedObject($site->id)) {
$sql->where('hm.`site_id` = ' . (int) $site->id);
}
$sql->groupBy('hm.hook_id, hm.module_id');
$sql->orderBy('hm.`position`');
$list = array();
if ($result = Db::getInstance(PROX_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(
'hook_id' => $row['hook_id'],
'module' => $row['module'],
'module_id' => $row['module_id'],
);
}
}
if ($hook_name != 'displayPayment' && $hook_name != 'displayPaymentEU' && $hook_name != 'paymentOptions' && $hook_name != 'displayBackOfficeHeader') {
Cache::store($cache_id, $list);
}
} else {
$list = Cache::retrieve($cache_id);
}
// If hook_name is given, just get list of modules for this hook
if ($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['module_id'];
}
return count($return) > 0 ? $return : false;
} else {
return $list;
}
}
/**
* Get the list of hook aliases.
*
* @since 1.7.1.0
*
* @return array
*/
private static function getHookAliasesList()
{
$cacheId = 'hook_aliases';
if (!Cache::isStored($cacheId)) {
$hookAliasList = Db::getInstance()->executeS('SELECT * FROM '. Db::prefix('hook_alias'));
$hookAliases = array();
if ($hookAliasList) {
foreach ($hookAliasList as $ha) {
if (!isset($hookAliases[$ha['name']])) {
$hookAliases[$ha['name']] = array();
}
$hookAliases[$ha['name']][] = $ha['alias'];
}
}
Cache::store($cacheId, $hookAliases);
return $hookAliases;
}
return Cache::retrieve($cacheId);
}
/**
* Return backward compatibility hook names.
*
* @since 1.7.1.0
*
* @param $hookName
*
* @return array
*/
private static function getHookAliasesFor($hookName)
{
$cacheId = 'hook_aliases_' . $hookName;
if (!Cache::isStored($cacheId)) {
$aliasesList = Hook::getHookAliasesList();
if (isset($aliasesList[$hookName])) {
Cache::store($cacheId, $aliasesList[$hookName]);
return $aliasesList[$hookName];
}
$retroName = array_keys(array_filter($aliasesList, function ($elem) use ($hookName) {
return in_array($hookName, $elem);
}));
if (empty($retroName)) {
Cache::store($cacheId, array());
return array();
}
Cache::store($cacheId, $retroName);
return $retroName;
}
return Cache::retrieve($cacheId);
}
/**
* Check if a hook or one of its old names is callable on a module.
*
* @since 1.7.1.0
*
* @param $module
* @param $hookName
*
* @return bool
*/
private static function isHookCallableOn($module, $hookName)
{
$aliases = Hook::getHookAliasesFor($hookName);
$aliases[] = $hookName;
return array_reduce($aliases, function ($prev, $curr) use ($module) {
return $prev || is_callable(array($module, 'hook' . $curr));
}, false);
}
/**
* Call a hook (or one of its old name) on a module.
*
* @since 1.7.1.0
*
* @param $module
* @param $hookName
* @param $hookArgs
*
* @return string
*/
private static function callHookOn($module, $hookName, $hookArgs)
{
if (is_callable(array($module, 'hook' . $hookName))) {
return Hook::coreCallHook($module, 'hook' . $hookName, $hookArgs);
}
foreach (Hook::getHookAliasesFor($hookName) as $hook) {
if (is_callable(array($module, 'hook' . $hook))) {
return Hook::coreCallHook($module, 'hook' . $hook, $hookArgs);
}
}
return '';
}
/**
* Execute modules for specified hook.
*
* @param string $hook_name Hook Name
* @param array $hook_args Parameters for the functions
* @param int $module_id Execute hook for this module only
* @param bool $array_return If specified, module output will be set by name in an array
* @param int $site_id If specified, hook will be execute the site with this ID
* @param bool $chain If specified, hook will chain the return of hook module
*
* @throws Exception
*
* @return string/array modules output
*/
public static function exec(
$hook_name,
$hook_args = array(),
$module_id = null,
$array_return = false,
$site_id = null,
$chain = false
) {
$hookRegistry = self::getHookRegistry();
$isRegistryEnabled = null !== $hookRegistry;
if ($isRegistryEnabled) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
$hookRegistry->selectHook($hook_name, $hook_args, $backtrace[0]['file'], $backtrace[0]['line']);
}
// $chain & $array_return are incompatible so if chained is set to true, we disable the array_return option
if (true === $chain) {
$array_return = false;
}
// Check arguments validity
if (($module_id && !is_numeric($module_id)) || !Validate::isHookName($hook_name)) {
throw new Exception('Invalid module_id or hook_name');
}
// If no modules associated to hook_name we stop the function
if (!$module_list = Hook::getHookModuleExecList($hook_name)) {
if ($isRegistryEnabled) {
$hookRegistry->collect();
}
if ($array_return) {
return array();
} else {
return '';
}
}
// Check if hook exists
if (!$hook_id = Hook::getIdByName($hook_name)) {
if ($isRegistryEnabled) {
$hookRegistry->collect();
}
if ($array_return) {
return array();
} else {
return false;
}
}
// Store list of executed hooks on this page
Hook::$executed_hooks[$hook_id] = $hook_name;
$application = Application::getInstance();
if (!isset($hook_args['cookie']) || !$hook_args['cookie']) {
$hook_args['cookie'] = $application->container->cookie;
}
// Look on modules list
$altern = 0;
if ($array_return) {
$output = array();
} else {
$output = '';
}
$different_site = false;
if ($site_id !== null && Validate::isUnsignedId($site_id) && $site_id != $application->container->site->id) {
$old_site = clone $application->site;
$site = new Site((int) $site_id);
if (Validate::isLoadedObject($site)) {
$application->site = $site;
$different_site = true;
}
}
foreach ($module_list as $key => $array) {
// Check errors
if ($module_id && $module_id != $array['module_id']) {
continue;
}
if (!($moduleInstance = Module::getInstanceByName($array['module']))) {
continue;
}
if ($isRegistryEnabled) {
$hookRegistry->hookedByModule($moduleInstance);
}
if (Hook::isHookCallableOn($moduleInstance, $hook_name)) {
$hook_args['altern'] = ++$altern;
if (0 !== $key && true === $chain) {
$hook_args = $output;
}
$display = Hook::callHookOn($moduleInstance, $hook_name, $hook_args);
if ($array_return) {
$output[$moduleInstance->name] = $display;
} else {
if (true === $chain) {
$output = $display;
} else {
$output .= $display;
}
}
if ($isRegistryEnabled) {
$hookRegistry->hookedByCallback($moduleInstance, $hook_args);
}
}
}
if ($different_site) {
$application->site = $old_site;
}
if (true === $chain) {
if (isset($output['cookie'])) {
unset($output['cookie']);
}
if (isset($output['cart'])) {
unset($output['cart']);
}
}
if ($isRegistryEnabled) {
$hookRegistry->hookWasCalled();
$hookRegistry->collect();
}
return $output;
}
public static function coreCallHook($module, $method, $params)
{
return $module->{$method}($params);
}
public static function coreRenderWidget($module, $hook_name, $params)
{
return $module->renderWidget($hook_name, $params);
}
/**
* @return \HookRegistry|null
*/
private static function getHookRegistry()
{
$sfContainer = SymfonyContainer::getInstance();
if (null !== $sfContainer && 'dev' === $sfContainer->getParameter('kernel.environment')) {
return $sfContainer->get('proxim.hooks_registry');
}
return null;
}
}