477 lines
16 KiB
PHP
477 lines
16 KiB
PHP
<?php
|
|
/*************************************************************************************/
|
|
/* This file is part of the Thelia package. */
|
|
/* */
|
|
/* Copyright (c) OpenStudio */
|
|
/* email : dev@thelia.net */
|
|
/* web : http://www.thelia.net */
|
|
/* */
|
|
/* For the full copyright and license information, please view the LICENSE.txt */
|
|
/* file that was distributed with this source code. */
|
|
/*************************************************************************************/
|
|
|
|
namespace TheliaSmarty\Template\Plugins;
|
|
|
|
use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
|
|
use Thelia\Core\Event\Hook\HookRenderBlockEvent;
|
|
use Thelia\Core\Event\Hook\HookRenderEvent;
|
|
use Thelia\Core\Hook\Fragment;
|
|
use Thelia\Core\Hook\FragmentBag;
|
|
use TheliaSmarty\Template\Plugins\Module;
|
|
use TheliaSmarty\Template\AbstractSmartyPlugin;
|
|
use TheliaSmarty\Template\SmartyParser;
|
|
use TheliaSmarty\Template\SmartyPluginDescriptor;
|
|
use Thelia\Core\Template\TemplateDefinition;
|
|
use Thelia\Core\Translation\Translator;
|
|
use Thelia\Model\ModuleQuery;
|
|
|
|
/**
|
|
* Plugin for smarty defining blocks and functions for using Hooks.
|
|
*
|
|
* Class Hook
|
|
* @package Thelia\Core\Template\Smarty\Plugins
|
|
* @author Julien Chanséaume <jchanseaume@openstudio.fr>
|
|
*/
|
|
class Hook extends AbstractSmartyPlugin
|
|
{
|
|
private $dispatcher;
|
|
|
|
/** @var Translator */
|
|
protected $translator;
|
|
|
|
/** @var Module */
|
|
protected $smartyPluginModule = null;
|
|
|
|
/** @var array */
|
|
protected $hookResults = array();
|
|
|
|
/** @var array */
|
|
protected $varstack = array();
|
|
|
|
/** @var bool debug */
|
|
protected $debug = false;
|
|
|
|
public function __construct($debug, ContainerAwareEventDispatcher $dispatcher)
|
|
{
|
|
$this->debug = $debug;
|
|
$this->dispatcher = $dispatcher;
|
|
$this->translator = $dispatcher->getContainer()->get("thelia.translator");
|
|
$this->hookResults = array();
|
|
}
|
|
|
|
/**
|
|
* Generates the content of the hook
|
|
*
|
|
* {hook name="hook_code" var1="value1" var2="value2" ... }
|
|
*
|
|
* This function create an event, feed it with the custom variables passed to the function (var1, var2, ...) and
|
|
* dispatch it to the hooks that respond to it.
|
|
*
|
|
* The name of the event is `hook.{context}.{hook_code}` where :
|
|
* * context : the id of the context of the smarty render : 1: frontoffice, 2: backoffice, 3: email, 4: pdf
|
|
* * hook_code : the code of the hook
|
|
*
|
|
* The event collects all the fragments of text rendered in each modules functions that listen to this event.
|
|
* Finally, this fragments are concatenated and injected in the template
|
|
*
|
|
* @param array $params the params passed in the smarty function
|
|
* @param \TheliaSmarty\Template\SmartyParser $smarty the smarty parser
|
|
*
|
|
* @return string the contents generated by modules
|
|
*/
|
|
public function processHookFunction($params, &$smarty)
|
|
{
|
|
$hookName = $this->getParam($params, 'name');
|
|
$module = intval($this->getParam($params, 'module', 0));
|
|
$moduleCode = $this->getParam($params, 'modulecode', "");
|
|
|
|
$type = $smarty->getTemplateDefinition()->getType();
|
|
|
|
$event = new HookRenderEvent($hookName, $params);
|
|
|
|
$event->setArguments($this->getArgumentsFromParams($params));
|
|
|
|
$eventName = sprintf('hook.%s.%s', $type, $hookName);
|
|
|
|
// this is a hook specific to a module
|
|
if (0 === $module && "" !== $moduleCode) {
|
|
if (null !== $mod = ModuleQuery::create()->findOneByCode($moduleCode)) {
|
|
$module = $mod->getId();
|
|
}
|
|
}
|
|
if (0 !== $module) {
|
|
$eventName .= '.' . $module;
|
|
}
|
|
|
|
$this->getDispatcher()->dispatch($eventName, $event);
|
|
|
|
$content = trim($event->dump());
|
|
|
|
if ($this->debug && $smarty->getRequest()->get('SHOW_HOOK')) {
|
|
$content = self::showHook($hookName, $params) . $content;
|
|
}
|
|
|
|
$this->hookResults[$hookName] = $content;
|
|
|
|
// support for compatibility with module_include
|
|
if ($type === TemplateDefinition::BACK_OFFICE) {
|
|
$content .= $this->moduleIncludeCompat($params, $smarty);
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Call the plugin function module_include for backward compatibility.
|
|
*
|
|
* @param array $params the params passed in the smarty function
|
|
* @param \TheliaSmarty\Template\SmartyParser $smarty the smarty parser
|
|
*
|
|
* @return string the contents generated by module_include function
|
|
*/
|
|
protected function moduleIncludeCompat($params, &$smarty)
|
|
{
|
|
$plugin = $this->getSmartyPluginModule();
|
|
$params = array(
|
|
"location" => $this->getParam($params, 'location', null),
|
|
"module" => $this->getParam($params, 'modulecode', null),
|
|
"countvar" => $this->getParam($params, 'countvar', null)
|
|
);
|
|
|
|
return $plugin->theliaModule($params, $smarty);
|
|
}
|
|
|
|
/**
|
|
* get the smarty plugin Module
|
|
*
|
|
* @return Module the smarty plugin Module
|
|
*/
|
|
protected function getSmartyPluginModule()
|
|
{
|
|
if (null === $this->smartyPluginModule) {
|
|
$this->smartyPluginModule = $this->dispatcher->getContainer()->get("smarty.plugin.module");
|
|
}
|
|
|
|
return $this->smartyPluginModule;
|
|
}
|
|
|
|
protected function showHook($hookName, $params)
|
|
{
|
|
$content = '<div style="background-color: #C82D26; color: #fff; border-color: #000000; border: solid;">' . $hookName;
|
|
|
|
foreach ($params as $name => $value) {
|
|
if ($name !== 'location' && $name !== "name") {
|
|
$type = '';
|
|
if (is_object($value)) {
|
|
$value = get_class($value);
|
|
$type = 'object';
|
|
} elseif (is_array($value)) {
|
|
$value = implode(',', $value);
|
|
$type = 'array';
|
|
} elseif (is_int($value)) {
|
|
$type = 'float';
|
|
} elseif (is_int($value)) {
|
|
$type = 'int';
|
|
} elseif (is_string($value)) {
|
|
$value = (strlen($value) > 30) ? substr($value, 0, 30) . '...' : $value;
|
|
$type = 'string';
|
|
}
|
|
|
|
if ($type !== '') {
|
|
$type = '<span style="background-color: #FF7D00; color: #fff">' . $type . '</span> ';
|
|
}
|
|
|
|
$content .= '<span style="background-color: #008000; color: #fff; margin-left: 6px;">' . $name . ' = ' . $type . $value . '</span>';
|
|
}
|
|
}
|
|
|
|
return $content . '</div>';
|
|
}
|
|
|
|
/**
|
|
* Process the content of the hook block.
|
|
*
|
|
* {hookblock name="hook_code" var1="value1" var2="value2" ... }
|
|
*
|
|
* This function create an event, feed it with the custom variables passed to the function (var1, var2, ...) and
|
|
* dispatch it to the hooks that respond to it.
|
|
*
|
|
* The name of the event is `hook.{context}.{hook_code}` where :
|
|
* * context : the id of the context of the smarty render : 1: frontoffice, 2: backoffice, 3: email, 4: pdf
|
|
* * hook_code : the code of the hook
|
|
*
|
|
* The event collects all the fragments generated by modules that listen to this event and add it to a fragmentBag.
|
|
* This fragmentBag is not used directly. This is the forhook block that iterates over the fragmentBag to inject
|
|
* data in the template.
|
|
*
|
|
* @param array $params
|
|
* @param string $content
|
|
* @param \TheliaSmarty\Template\SmartyParser $smarty
|
|
* @param bool $repeat
|
|
*
|
|
* @return string the generated content
|
|
*/
|
|
public function processHookBlock($params, $content, $smarty, &$repeat)
|
|
{
|
|
$hookName = $this->getParam($params, 'name');
|
|
$module = intval($this->getParam($params, 'module', 0));
|
|
// explicit definition of variable that can be returned
|
|
$fields = preg_replace(
|
|
'|[^a-zA-Z0-9,\-_]|',
|
|
'',
|
|
$this->getParam($params, 'fields', '')
|
|
);
|
|
$fields = ('' !== $fields) ? explode(",", $fields) : [];
|
|
|
|
if (!$repeat) {
|
|
if ($this->debug && $smarty->getRequest()->get('SHOW_HOOK')) {
|
|
$content = self::showHook($hookName, $params) . $content;
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
$type = $smarty->getTemplateDefinition()->getType();
|
|
|
|
$event = new HookRenderBlockEvent($hookName, $params, $fields);
|
|
|
|
$event->setArguments($this->getArgumentsFromParams($params));
|
|
|
|
$eventName = sprintf('hook.%s.%s', $type, $hookName);
|
|
|
|
// this is a hook specific to a module
|
|
if (0 !== $module) {
|
|
$eventName .= '.' . $module;
|
|
}
|
|
|
|
$this->getDispatcher()->dispatch($eventName, $event);
|
|
|
|
// save results so we can use it in forHook block
|
|
$this->hookResults[$hookName] = $event->get();
|
|
}
|
|
|
|
/**
|
|
* Process a {forhook rel="hookname"} ... {/forhook}
|
|
*
|
|
* The forhook iterates over the results return by a hookblock :
|
|
*
|
|
* {hookblock name="product.additional"}
|
|
* {forhook rel="product.additional"}
|
|
* <div id="{$id}">
|
|
* <h2>{$title}</h2>
|
|
* <p>{$content}</p>
|
|
* </div>
|
|
* {/forhook}
|
|
* {/hookblock}
|
|
*
|
|
* @param array $params
|
|
* @param string $content
|
|
* @param \TheliaSmarty\Template\SmartyParser $smarty
|
|
* @param bool $repeat
|
|
*
|
|
* @throws \InvalidArgumentException
|
|
* @return string the generated content
|
|
*/
|
|
public function processForHookBlock($params, $content, $smarty, &$repeat)
|
|
{
|
|
$rel = $this->getParam($params, 'rel');
|
|
if (null == $rel) {
|
|
throw new \InvalidArgumentException(
|
|
$this->translator->trans("Missing 'rel' parameter in forHook arguments")
|
|
);
|
|
}
|
|
|
|
/** @var FragmentBag $fragments */
|
|
$fragments = null;
|
|
|
|
// first call
|
|
if ($content === null) {
|
|
if (!array_key_exists($rel, $this->hookResults)) {
|
|
throw new \InvalidArgumentException(
|
|
$this->translator->trans("Related hook name '%name' is not defined.", ['%name' => $rel])
|
|
);
|
|
}
|
|
|
|
$fragments = $this->hookResults[$rel];
|
|
$fragments->rewind();
|
|
|
|
if ($fragments->isEmpty()) {
|
|
$repeat = false;
|
|
}
|
|
} else {
|
|
$fragments = $this->hookResults[$rel];
|
|
$fragments->next();
|
|
}
|
|
|
|
if ($fragments->valid()) {
|
|
/** @var Fragment $fragment */
|
|
$fragment = $fragments->current();
|
|
|
|
// On first iteration, save variables that may be overwritten by this hook
|
|
if (!isset($this->varstack[$rel])) {
|
|
$saved_vars = array();
|
|
|
|
$varlist = $fragment->getVars();
|
|
|
|
foreach ($varlist as $var) {
|
|
$saved_vars[$var] = $smarty->getTemplateVars($var);
|
|
}
|
|
|
|
$this->varstack[$rel] = $saved_vars;
|
|
}
|
|
|
|
foreach ($fragment->getVarVal() as $var => $val) {
|
|
$smarty->assign($var, $val);
|
|
}
|
|
// continue iteration
|
|
$repeat = true;
|
|
}
|
|
|
|
// end
|
|
if (!$repeat) {
|
|
// Restore previous variables values before terminating
|
|
if (isset($this->varstack[$rel])) {
|
|
foreach ($this->varstack[$rel] as $var => $value) {
|
|
$smarty->assign($var, $value);
|
|
}
|
|
|
|
unset($this->varstack[$rel]);
|
|
}
|
|
}
|
|
|
|
if ($content !== null) {
|
|
if ($fragments->isEmpty()) {
|
|
$content = "";
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Process {elsehook rel="hookname"} ... {/elsehook} block
|
|
*
|
|
* @param array $params hook parameters
|
|
* @param string $content hook text content
|
|
* @param \Smarty_Internal_Template $template the Smarty object
|
|
* @param boolean $repeat repeat indicator (see Smarty doc.)
|
|
*
|
|
* @return string the hook output
|
|
*/
|
|
public function elseHook(
|
|
$params,
|
|
$content,
|
|
/** @noinspection PhpUnusedParameterInspection */ $template,
|
|
&$repeat
|
|
) {
|
|
// When encountering close tag, check if hook has results.
|
|
if ($repeat === false) {
|
|
return $this->checkEmptyHook($params) ? $content : '';
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Process {ifhook rel="hookname"} ... {/ifhook} block
|
|
*
|
|
* @param array $params hook parameters
|
|
* @param string $content hook text content
|
|
* @param \Smarty_Internal_Template $template the Smarty object
|
|
* @param boolean $repeat repeat indicator (see Smarty doc.)
|
|
*
|
|
* @return string the hook output
|
|
*/
|
|
public function ifHook($params, $content, /** @noinspection PhpUnusedParameterInspection */ $template, &$repeat)
|
|
{
|
|
// When encountering close tag, check if hook has results.
|
|
if ($repeat === false) {
|
|
return $this->checkEmptyHook($params) ? '' : $content;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Check if a hook has returned results. The hook should have been executed before, or an
|
|
* InvalidArgumentException is thrown
|
|
*
|
|
* @param array $params
|
|
*
|
|
* @return boolean true if the hook is empty
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
protected function checkEmptyHook($params)
|
|
{
|
|
$hookName = $this->getParam($params, 'rel');
|
|
|
|
if (null == $hookName) {
|
|
throw new \InvalidArgumentException(
|
|
$this->translator->trans("Missing 'rel' parameter in ifhook/elsehook arguments")
|
|
);
|
|
}
|
|
|
|
if (!isset($this->hookResults[$hookName])) {
|
|
throw new \InvalidArgumentException(
|
|
$this->translator->trans("Related hook name '%name' is not defined.", ['%name' => $hookName])
|
|
);
|
|
}
|
|
|
|
return (is_string($this->hookResults[$hookName]) && '' === $this->hookResults[$hookName]
|
|
|| !is_string($this->hookResults[$hookName]) && $this->hookResults[$hookName]->isEmpty()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Clean the params of the params passed to the hook function or block to feed the arguments of the event
|
|
* with relevant arguments.
|
|
*
|
|
* @param $params
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getArgumentsFromParams($params)
|
|
{
|
|
$args = array();
|
|
$excludes = array("name", "before", "separator", "after", "fields");
|
|
|
|
if (is_array($params)) {
|
|
foreach ($params as $key => $value) {
|
|
if (!in_array($key, $excludes)) {
|
|
$args[$key] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $args;
|
|
}
|
|
|
|
/**
|
|
* Define the various smarty plugins handled by this class
|
|
*
|
|
* @return array an array of smarty plugin descriptors
|
|
*/
|
|
public function getPluginDescriptors()
|
|
{
|
|
return array(
|
|
new SmartyPluginDescriptor('function', 'hook', $this, 'processHookFunction'),
|
|
new SmartyPluginDescriptor('block', 'hookblock', $this, 'processHookBlock'),
|
|
new SmartyPluginDescriptor('block', 'forhook', $this, 'processForHookBlock'),
|
|
new SmartyPluginDescriptor('block', 'elsehook', $this, 'elseHook'),
|
|
new SmartyPluginDescriptor('block', 'ifhook', $this, 'ifHook'),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Return the event dispatcher,
|
|
*
|
|
* @return \Symfony\Component\EventDispatcher\EventDispatcher
|
|
*/
|
|
public function getDispatcher()
|
|
{
|
|
return $this->dispatcher;
|
|
}
|
|
}
|