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); }