<?php

class Hook extends HookCore
{
    public static function exec($hook_name, $hook_args = array(), $id_module = null, $array_return = false, $check_exceptions = true)
    {
        // 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 = '';
        $callableHooks = array('displayLeftColumn', 'displayRightColumn', 'displayFooter', 'displayTop', 'displayHome');
        $isDesignerHook = isset($hook_args) && isset($hook_args['designer_hook']); // Designer layoutPosition only
        $source_hook_name = $hook_name;
        if (isset($hook_args) && isset($hook_args['blockId']))  // both Prestashop hooks and Designer layoutPosition
            $context->smarty->assign('blockId', $hook_args['blockId']);		

        foreach ($module_list as $array)
        {
            // Check errors
            if ($id_module && $id_module != $array['id_module'])
                continue;
            if (!($moduleInstance = Module::getInstanceByName($array['module'])))
                continue;

            // Check permissions
            if ($check_exceptions)
            {
                $exceptions = $moduleInstance->getExceptions($array['id_hook']);
                if (in_array(Dispatcher::getInstance()->getController(), $exceptions))
                    continue;
                if (Validate::isLoadedObject($context->employee) && !$moduleInstance->getPermission('view', $context->employee))
                    continue;
            }

            $hook_callable = false;
            $hook_retro_callable = false;
            // Check whether Designer hook is
            if ($isDesignerHook)
            {
                foreach ($callableHooks as $name)
                {
                    $hook_name = $name;
                    $retro_hook_name = Hook::getRetroHookName($hook_name);
                    $hook_callable = is_callable(array($moduleInstance, 'hook'.$hook_name));
                    $hook_retro_callable = is_callable(array($moduleInstance, 'hook'.$retro_hook_name));
                    // modules in layoutPosition Designer hook can be called from $callableHooks (displayLeftColumn etc.)
                    // so we save callable hook name as $hook_name variable and write its output to layoutPosition hook
                    if ($hook_callable || $hook_retro_callable)
                        break; // goes to Module::preCall line
                }
            }
            else
            {
                // Check which / if method is callable as usual
                $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;
                
                // Call hook method
                if ($hook_callable)
                    $display = $moduleInstance->{'hook'.$hook_name}($hook_args);
                else if ($hook_retro_callable)
                    $display = $moduleInstance->{'hook'.$retro_hook_name}($hook_args);
                
                // Designer edit - $array['live_edit'] defines if module can be moved
                // displayTop and displayFooter hooks can be moved too in Designer but it has live_edit = 0 in DB.hook table
                if (!$array_return && Tools::getValue('theme_name') && ($array['live_edit'] || $hook_name == 'displayTop' || $hook_name == 'displayFooter'))
                    $display = self::wrapDesignerEdit($display, $moduleInstance, $array['id_hook']);
                
                // 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')))
                {
                    // We should used in Live Edit mode native PrestaShop hooks only not Designer ones
                    // That's why we remember $source_hook_name and check it
                    if ($isDesignerHook && !in_array($source_hook_name, $callableHooks))
                    {
                        $output .= $display;
                    }
                    else
                    {
                        $live_edit = true;
                        $output .= self::wrapLiveEdit($display, $moduleInstance, $array['id_hook']);
                    }
                }
                else if ($array_return)
                    $output[$moduleInstance->name] = $display;
                else
                    $output .= $display;
            }
        }
        
        if ($array_return)
            return $output;
        else 
        {
            return ($live_edit ? '<script type="text/javascript">hooks_list.push(\''.$hook_name.'\'); </script>
                <div id="'.$hook_name.'" class="dndHook" style="min-height:50px">' : '').$output.($live_edit ? '</div>' : '');// Return html string
        }	
    }

    /*
     * Adds Id to modules output
    */
    private static function wrapDesignerEdit($display, $moduleInstance, $id_hook)
    {
        $moduleId = (int)$id_hook.'_module_'.(int)$moduleInstance->id.'_moduleName_'.str_replace('_', '-', Tools::safeOutput($moduleInstance->name));
        if (empty($display) && ($moduleInstance->name == 'blocklayered')) // HACK: blocklayered module position for a while
            return '<div data-block-id="hook_'.$moduleId.'">&nbsp;</div>';
        return preg_replace('~<(.*?id=[\'"])(.*?)([\'"])~',
                            '<$1$2$3 data-block-id="hook_'.$moduleId.'$3', $display, 1);
    }

    /**
     * Register new hook for Designer. Method similar to Module.registerHook()
     *
     * @@param string $hook_name Hook name
     * @@return boolean result
     */
    public static function registerDesignerHook($hook_name)
    {
        // Check hook name validation and if module is installed
        if (!Validate::isHookName($hook_name))
            throw new PrestaShopException('Invalid hook name');

        // Retrocompatibility
        if ($alias = Hook::getRetroHookName($hook_name))
            $hook_name = $alias;

        // Get hook id
        $id_hook = Hook::getIdByName($hook_name);

        // 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 = 1;
            $new_hook->add();
            $id_hook = $new_hook->id;
            if (!$id_hook) 
                return false;
        }
        return $id_hook;
    }
}