diff --git a/local/modules/TheliaSmarty/Config/config.xml b/local/modules/TheliaSmarty/Config/config.xml
index fa3efdf6..11f008fb 100644
--- a/local/modules/TheliaSmarty/Config/config.xml
+++ b/local/modules/TheliaSmarty/Config/config.xml
@@ -114,6 +114,7 @@
+
@@ -139,6 +140,13 @@
+
+
+
+
+ %kernel.debug%
+
+
diff --git a/local/modules/TheliaSmarty/Config/module.xml b/local/modules/TheliaSmarty/Config/module.xml
index 1a24d8f7..b6708542 100644
--- a/local/modules/TheliaSmarty/Config/module.xml
+++ b/local/modules/TheliaSmarty/Config/module.xml
@@ -1,5 +1,7 @@
-
+
TheliaSmarty\TheliaSmarty
Smarty template engine integration
@@ -7,12 +9,20 @@
Intégration du moteur de template Smarty
- 2.3.1
-
- Manuel Raynaud
- manu@raynaud.io
-
+
+ en_US
+ fr_FR
+
+ 2.3.4
+
+
+ Manuel Raynaud
+ manu@raynaud.io
+
+
classic
2.2.0
alpha
+ 1
+ 1
diff --git a/local/modules/TheliaSmarty/Template/Plugins/AdminUtilities.php b/local/modules/TheliaSmarty/Template/Plugins/AdminUtilities.php
index 35f03530..ed7b5125 100644
--- a/local/modules/TheliaSmarty/Template/Plugins/AdminUtilities.php
+++ b/local/modules/TheliaSmarty/Template/Plugins/AdminUtilities.php
@@ -53,6 +53,17 @@ class AdminUtilities extends AbstractSmartyPlugin
return $data;
}
+
+ public function optionOffsetGenerator($params, &$smarty)
+ {
+ $label = $this->getParam($params, 'label', null);
+
+ if (null !== $level = $this->getParam($params, [ 'l', 'level'], null)) {
+ $label = str_repeat(' ', 4 * $level) . $label;
+ }
+
+ return $label;
+ }
public function generatePositionChangeBlock($params, &$smarty)
{
@@ -155,6 +166,7 @@ class AdminUtilities extends AbstractSmartyPlugin
return array(
new SmartyPluginDescriptor('function', 'admin_sortable_header', $this, 'generateSortableColumnHeader'),
new SmartyPluginDescriptor('function', 'admin_position_block', $this, 'generatePositionChangeBlock'),
+ new SmartyPluginDescriptor('function', 'option_offset', $this, 'optionOffsetGenerator'),
);
}
}
diff --git a/local/modules/TheliaSmarty/Template/Plugins/DataAccessFunctions.php b/local/modules/TheliaSmarty/Template/Plugins/DataAccessFunctions.php
index d8ee4545..0dd8fc90 100644
--- a/local/modules/TheliaSmarty/Template/Plugins/DataAccessFunctions.php
+++ b/local/modules/TheliaSmarty/Template/Plugins/DataAccessFunctions.php
@@ -20,6 +20,8 @@ use Thelia\Core\HttpFoundation\Request;
use Thelia\Core\HttpFoundation\Session\Session;
use Thelia\Core\Security\SecurityContext;
use Thelia\Core\Template\ParserContext;
+use Thelia\Coupon\CouponManager;
+use Thelia\Coupon\Type\CouponInterface;
use Thelia\Log\Tlog;
use Thelia\Model\Base\BrandQuery;
use Thelia\Model\Cart;
@@ -33,6 +35,7 @@ use Thelia\Model\FolderQuery;
use Thelia\Model\MetaDataQuery;
use Thelia\Model\ModuleConfigQuery;
use Thelia\Model\ModuleQuery;
+use Thelia\Model\Order;
use Thelia\Model\OrderQuery;
use Thelia\Model\ProductQuery;
use Thelia\Model\Tools\ModelCriteriaTools;
@@ -40,6 +43,8 @@ use Thelia\TaxEngine\TaxEngine;
use Thelia\Tools\DateTimeFormat;
use TheliaSmarty\Template\AbstractSmartyPlugin;
use TheliaSmarty\Template\SmartyPluginDescriptor;
+use Thelia\Core\Event\Image\ImageEvent;
+use Thelia\Core\Event\TheliaEvents;
/**
* Implementation of data access to main Thelia objects (users, cart, etc.)
@@ -64,6 +69,9 @@ class DataAccessFunctions extends AbstractSmartyPlugin
/** @var TaxEngine */
protected $taxEngine;
+ /** @var CouponManager */
+ protected $couponManager;
+
private static $dataAccessCache = array();
public function __construct(
@@ -71,13 +79,15 @@ class DataAccessFunctions extends AbstractSmartyPlugin
SecurityContext $securityContext,
TaxEngine $taxEngine,
ParserContext $parserContext,
- EventDispatcherInterface $dispatcher
+ EventDispatcherInterface $dispatcher,
+ CouponManager $couponManager
) {
$this->securityContext = $securityContext;
$this->parserContext = $parserContext;
$this->requestStack = $requestStack;
$this->dispatcher = $dispatcher;
$this->taxEngine = $taxEngine;
+ $this->couponManager = $couponManager;
}
/**
@@ -306,6 +316,14 @@ class DataAccessFunctions extends AbstractSmartyPlugin
self::$dataAccessCache['currentCountry'] = $taxCountry;
}
+ /** @var State $taxState */
+ if (array_key_exists('currentState', self::$dataAccessCache)) {
+ $taxState = self::$dataAccessCache['currentState'];
+ } else {
+ $taxState = $this->taxEngine->getDeliveryState();
+ self::$dataAccessCache['currentState'] = $taxState;
+ }
+
/** @var Cart $cart */
$cart = $this->getSession()->getSessionCart($this->dispatcher);
@@ -325,17 +343,17 @@ class DataAccessFunctions extends AbstractSmartyPlugin
break;
case "total_price":
case "total_price_with_discount":
- $result = $cart->getTotalAmount();
+ $result = $cart->getTotalAmount(true);
break;
case "total_price_without_discount":
$result = $cart->getTotalAmount(false);
break;
case "total_taxed_price":
case "total_taxed_price_with_discount":
- $result = $cart->getTaxedAmount($taxCountry);
+ $result = $cart->getTaxedAmount($taxCountry, true, $taxState);
break;
case "total_taxed_price_without_discount":
- $result = $cart->getTaxedAmount($taxCountry, false);
+ $result = $cart->getTaxedAmount($taxCountry, false, $taxState);
break;
case "is_virtual":
case "contains_virtual_product":
@@ -353,6 +371,31 @@ class DataAccessFunctions extends AbstractSmartyPlugin
return $result;
}
+ public function couponDataAccess($params, &$smarty)
+ {
+ /** @var Order $order */
+ $order = $this->getSession()->getOrder();
+ $attribute = $this->getNormalizedParam($params, array('attribute', 'attrib', 'attr'));
+
+ switch ($attribute) {
+ case 'has_coupons':
+ return count($this->couponManager->getCouponsKept()) > 0;
+ case 'coupon_count':
+ return count($this->couponManager->getCouponsKept());
+ case 'coupon_list':
+ $orderCoupons = [];
+ /** @var CouponInterface $coupon */
+ foreach($this->couponManager->getCouponsKept() as $coupon) {
+ $orderCoupons[] = $coupon->getCode();
+ }
+ return $orderCoupons;
+ case 'is_delivery_free':
+ return $this->couponManager->isCouponRemovingPostage($order);
+ }
+
+ throw new \InvalidArgumentException(sprintf("%s has no '%s' attribute", 'Order', $attribute));
+ }
+
/**
* Provides access to an attribute of the current order
*
@@ -362,6 +405,7 @@ class DataAccessFunctions extends AbstractSmartyPlugin
*/
public function orderDataAccess($params, &$smarty)
{
+ /** @var Order $order */
$order = $this->getSession()->getOrder();
$attribute = $this->getNormalizedParam($params, array('attribute', 'attrib', 'attr'));
switch ($attribute) {
@@ -743,6 +787,134 @@ class DataAccessFunctions extends AbstractSmartyPlugin
return $return;
}
+
+
+
+ /**
+ * Provides access to the uploaded store-related images (such as logo or favicon)
+ *
+ * @param array $params
+ * @param string $content
+ * @param \Smarty_Internal_Template $template
+ * @param boolean $repeat
+ * @return string|null
+ */
+ public function storeMediaDataAccess($params, $content, \Smarty_Internal_Template $template, &$repeat)
+ {
+ $type = $this->getParam($params, 'type', null);
+ $allowedTypes = ['favicon', 'logo', 'banner'];
+
+
+ if ($type !== null && in_array($type, $allowedTypes)) {
+ switch ($type) {
+ case 'favicon':
+ $configKey = 'favicon_file';
+ $defaultImageName = 'favicon.png';
+ break;
+ case 'logo':
+ $configKey = 'logo_file';
+ $defaultImageName = 'logo.png';
+ break;
+ case 'banner':
+ $configKey = 'banner_file';
+ $defaultImageName = 'banner.jpg';
+ break;
+ }
+
+ $uploadDir = ConfigQuery::read('images_library_path');
+
+ if ($uploadDir === null) {
+ $uploadDir = THELIA_LOCAL_DIR . 'media' . DS . 'images';
+ } else {
+ $uploadDir = THELIA_ROOT . $uploadDir;
+ }
+
+ $uploadDir .= DS . 'store';
+
+
+ $imageFileName = ConfigQuery::read($configKey);
+
+ $skipImageTransform = false;
+
+ // If we couldn't find the image path in the config table or if it doesn't exist, we take the default image provided.
+ if ($imageFileName == null) {
+ $imageSourcePath = $uploadDir . DS . $defaultImageName;
+ } else {
+ $imageSourcePath = $uploadDir . DS . $imageFileName;
+
+ if (!file_exists($imageSourcePath)) {
+ Tlog::getInstance()->error(sprintf('Source image file %s does not exists.', $imageSourcePath));
+ $imageSourcePath = $uploadDir . DS . $defaultImageName;
+ }
+
+ if ($type == 'favicon') {
+ $extension = pathinfo($imageSourcePath, PATHINFO_EXTENSION);
+ if ($extension == 'ico') {
+ $mime_type = 'image/x-icon';
+
+ // If the media is a .ico favicon file, we skip the image transformations,
+ // as transformations on .ico file are not supported by Thelia.
+ $skipImageTransform = true;
+ } else {
+ $mime_type = 'image/png';
+ }
+
+ $template->assign('MEDIA_MIME_TYPE', $mime_type);
+ }
+ }
+
+ $event = new ImageEvent();
+ $event->setSourceFilepath($imageSourcePath)
+ ->setCacheSubdirectory('store');
+
+
+ if (!$skipImageTransform) {
+ switch ($this->getParam($params, 'resize_mode', null)) {
+ case 'crop':
+ $resize_mode = \Thelia\Action\Image::EXACT_RATIO_WITH_CROP;
+ break;
+
+ case 'borders':
+ $resize_mode = \Thelia\Action\Image::EXACT_RATIO_WITH_BORDERS;
+ break;
+
+ case 'none':
+ default:
+ $resize_mode = \Thelia\Action\Image::KEEP_IMAGE_RATIO;
+ }
+
+ // Prepare transformations
+ $width = $this->getParam($params, 'width', null);
+ $height = $this->getParam($params, 'height', null);
+ $rotation = $this->getParam($params, 'rotation', null);
+
+ if (!is_null($width)) {
+ $event->setWidth($width);
+ }
+ if (!is_null($height)) {
+ $event->setHeight($height);
+ }
+ $event->setResizeMode($resize_mode);
+ if (!is_null($rotation)) {
+ $event->setRotation($rotation);
+ }
+ }
+
+ $this->dispatcher->dispatch(TheliaEvents::IMAGE_PROCESS, $event);
+
+ $template->assign('MEDIA_URL', $event->getFileUrl());
+ }
+
+ if (isset($content)) {
+ return $content;
+ }
+
+ return null;
+ }
+
+
+
+
/**
* @inheritdoc
*/
@@ -765,6 +937,9 @@ class DataAccessFunctions extends AbstractSmartyPlugin
new SmartyPluginDescriptor('function', 'stats', $this, 'statsAccess'),
new SmartyPluginDescriptor('function', 'meta', $this, 'metaAccess'),
new SmartyPluginDescriptor('function', 'module_config', $this, 'moduleConfigDataAccess'),
+ new SmartyPluginDescriptor('function', 'coupon', $this, 'couponDataAccess'),
+
+ new SmartyPluginDescriptor('block', 'local_media', $this, 'storeMediaDataAccess'),
);
}
diff --git a/local/modules/TheliaSmarty/Template/Plugins/Form.php b/local/modules/TheliaSmarty/Template/Plugins/Form.php
index 6a48aff9..335b79a4 100644
--- a/local/modules/TheliaSmarty/Template/Plugins/Form.php
+++ b/local/modules/TheliaSmarty/Template/Plugins/Form.php
@@ -185,10 +185,10 @@ class Form extends AbstractSmartyPlugin
/** @var FormErrorIterator $errors */
$errors = $fieldVars["errors"];
-
- $template->assign("error", $errors->count() ? true : false);
-
- $this->assignFieldErrorVars($template, $errors);
+ if ($errors) {
+ $template->assign("error", $errors->count() ? true : false);
+ $this->assignFieldErrorVars($template, $errors);
+ }
$attr = array();
diff --git a/local/modules/TheliaSmarty/Template/Plugins/TheliaLoop.php b/local/modules/TheliaSmarty/Template/Plugins/TheliaLoop.php
index 30abd618..f2bb5ad8 100644
--- a/local/modules/TheliaSmarty/Template/Plugins/TheliaLoop.php
+++ b/local/modules/TheliaSmarty/Template/Plugins/TheliaLoop.php
@@ -151,7 +151,9 @@ class TheliaLoop extends AbstractSmartyPlugin
self::$pagination[$name] = null;
- $loopResults = $loop->exec(self::$pagination[$name]);
+ // We have to clone the result, as exec() returns a cached LoopResult object, which may cause side effects
+ // if loops with the same argument set are nested (see https://github.com/thelia/thelia/issues/2213)
+ $loopResults = clone($loop->exec(self::$pagination[$name]));
$loopResults->rewind();
diff --git a/local/modules/TheliaSmarty/Template/Plugins/UrlGenerator.php b/local/modules/TheliaSmarty/Template/Plugins/UrlGenerator.php
index ff422a28..988a7202 100644
--- a/local/modules/TheliaSmarty/Template/Plugins/UrlGenerator.php
+++ b/local/modules/TheliaSmarty/Template/Plugins/UrlGenerator.php
@@ -22,6 +22,9 @@ use TheliaSmarty\Template\AbstractSmartyPlugin;
use Thelia\Tools\TokenProvider;
use Thelia\Tools\URL;
use Thelia\Core\HttpFoundation\Request;
+use Thelia\Model\RewritingUrlQuery;
+use Thelia\Model\LangQuery;
+use Thelia\Model\ConfigQuery;
class UrlGenerator extends AbstractSmartyPlugin
{
@@ -119,6 +122,35 @@ class UrlGenerator extends AbstractSmartyPlugin
$this->getArgsFromParam($params, array_merge(['noamp', 'path', 'file', 'target'], $excludeParams)),
$mode
);
+
+ $request = $this->getRequest();
+ $requestedLangCodeOrLocale = $params["lang"];
+ $view = $request->attributes->get('_view', null);
+ $viewId = $view === null ? null : $request->query->get($view . '_id', null);
+
+ if (null !== $requestedLangCodeOrLocale) {
+ if (strlen($requestedLangCodeOrLocale) > 2) {
+ $lang = LangQuery::create()->findOneByLocale($requestedLangCodeOrLocale);
+ } else {
+ $lang = LangQuery::create()->findOneByCode($requestedLangCodeOrLocale);
+ }
+
+ if (ConfigQuery::isMultiDomainActivated()) {
+ $urlRewrite = RewritingUrlQuery::create()
+ ->filterByView($view)
+ ->filterByViewId($viewId)
+ ->filterByViewLocale($lang->getLocale())
+ ->findOneByRedirected(null)
+ ;
+
+ $path = '';
+ if (null != $urlRewrite) {
+ $path = "/".$urlRewrite->getUrl();
+ }
+ $url = rtrim($lang->getUrl(), "/").$request->getBaseUrl().$path;
+ }
+
+ }
}
return $this->applyNoAmpAndTarget($params, $url);
}
diff --git a/local/modules/TheliaSmarty/Template/SmartyParser.php b/local/modules/TheliaSmarty/Template/SmartyParser.php
index 422d29b6..679dbbff 100644
--- a/local/modules/TheliaSmarty/Template/SmartyParser.php
+++ b/local/modules/TheliaSmarty/Template/SmartyParser.php
@@ -16,6 +16,7 @@ use \Smarty;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
+use Thelia\Core\HttpFoundation\Session\Session;
use Thelia\Core\Template\ParserInterface;
use Thelia\Core\Template\Exception\ResourceNotFoundException;
use Thelia\Core\Template\ParserContext;
@@ -25,6 +26,7 @@ use Imagine\Exception\InvalidArgumentException;
use Thelia\Core\Translation\Translator;
use Thelia\Log\Tlog;
use Thelia\Model\ConfigQuery;
+use Thelia\Model\Lang;
/**
*
@@ -58,6 +60,12 @@ class SmartyParser extends Smarty implements ParserInterface
/** @var int */
protected $status = 200;
+ /** @var string */
+ protected $env;
+
+ /** @var bool */
+ protected $debug;
+
/**
* @param RequestStack $requestStack
* @param EventDispatcherInterface $dispatcher
@@ -80,15 +88,17 @@ class SmartyParser extends Smarty implements ParserInterface
$this->dispatcher = $dispatcher;
$this->parserContext = $parserContext;
$this->templateHelper = $templateHelper;
+ $this->env = $env;
+ $this->debug = $debug;
// Configure basic Smarty parameters
- $compile_dir = THELIA_ROOT . 'cache'. DS . $env . DS . 'smarty'.DS.'compile';
+ $compile_dir = THELIA_ROOT . 'cache'. DS . $env . DS . 'smarty' . DS . 'compile';
if (! is_dir($compile_dir)) {
@mkdir($compile_dir, 0777, true);
}
- $cache_dir = THELIA_ROOT . 'cache'. DS . $env . DS . 'smarty'.DS.'cache';
+ $cache_dir = THELIA_ROOT . 'cache'. DS . $env . DS . 'smarty' . DS . 'cache';
if (! is_dir($cache_dir)) {
@mkdir($cache_dir, 0777, true);
}
@@ -392,6 +402,25 @@ class SmartyParser extends Smarty implements ParserInterface
throw new ResourceNotFoundException(Translator::getInstance()->trans("Template file %file cannot be found.", array('%file' => $realTemplateName)));
}
+ // Prepare common template variables
+ /** @var Session $session */
+ $session = $this->getRequest()->getSession();
+
+ $lang = $session ? $session->getLang() : Lang::getDefaultLanguage();
+
+ $parameters = array_merge($parameters, [
+ 'locale' => $lang->getLocale(),
+ 'lang_code' => $lang->getCode(),
+ 'lang_id' => $lang->getId(),
+ 'current_url' => $this->getRequest()->getUri(),
+ 'app' => (object) [
+ 'environment' => $this->env,
+ 'request' => $this->getRequest(),
+ 'session' => $session,
+ 'debug' => $this->debug
+ ]
+ ]);
+
return $this->internalRenderer('file', $realTemplateName, $parameters, $compressOutput);
}