MAJ en Thelia 2.3.4

This commit is contained in:
2020-05-03 08:14:07 +02:00
parent 72ddf49e60
commit 35a800ca0e
328 changed files with 9560 additions and 14163 deletions

View File

@@ -12,6 +12,8 @@
namespace Thelia\Core\Archiver;
use Thelia\Core\Translation\Translator;
/**
* Class AbstractArchiver
* @author Jérôme Billiras <jbilliras@openstudio.fr>
@@ -28,10 +30,10 @@ abstract class AbstractArchiver implements ArchiverInterface
*/
protected $archivePath;
public function __construct()
public function __construct($checkIsAvailable = false)
{
if (!$this->isAvailable()) {
throw new Exception(
if ($checkIsAvailable && !$this->isAvailable()) {
throw new \Exception(
Translator::getInstance()->trans(
"The archiver :name is not available. Please install the php extension :extension first.",
[
@@ -40,7 +42,7 @@ abstract class AbstractArchiver implements ArchiverInterface
]
)
);
}
}
}
public function getArchivePath()

View File

@@ -15,8 +15,8 @@ namespace Thelia\Core\DependencyInjection\Compiler;
use Propel\Runtime\Propel;
use ReflectionException;
use ReflectionMethod;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Thelia\Core\Hook\BaseHook;
use Thelia\Core\Hook\HookDefinition;
@@ -26,8 +26,8 @@ use Thelia\Model\Base\IgnoredModuleHookQuery;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Hook;
use Thelia\Model\HookQuery;
use Thelia\Model\ModuleHookQuery;
use Thelia\Model\ModuleHook;
use Thelia\Model\ModuleHookQuery;
use Thelia\Model\ModuleQuery;
/**
@@ -61,11 +61,11 @@ class RegisterHookListenersPass implements CompilerPassInterface
}
}
protected function logAlertMessage($message)
protected function logAlertMessage($message, $failSafe = false)
{
Tlog::getInstance()->addAlert($message);
if ($this->debugEnabled) {
if (!$failSafe && $this->debugEnabled) {
throw new \InvalidArgumentException($message);
}
}
@@ -190,7 +190,6 @@ class RegisterHookListenersPass implements CompilerPassInterface
$moduleHook
->setHookActive(false)
->save();
} else {
//$moduleHook->setTemplates($attributes['templates']);
@@ -226,6 +225,7 @@ class RegisterHookListenersPass implements CompilerPassInterface
$hookId = 0;
/** @var ModuleHook $moduleHook */
foreach ($moduleHooks as $moduleHook) {
// check if class and method exists
if (!$container->hasDefinition($moduleHook->getClassname())) {
continue;
@@ -236,7 +236,8 @@ class RegisterHookListenersPass implements CompilerPassInterface
if (!$this->isValidHookMethod(
$container->getDefinition($moduleHook->getClassname())->getClass(),
$moduleHook->getMethod(),
$hook->getBlock()
$hook->getBlock(),
true
)
) {
$moduleHook->delete();
@@ -346,9 +347,7 @@ class RegisterHookListenersPass implements CompilerPassInterface
}
if (! $hook->getActivate()) {
$this->logAlertMessage(sprintf("Hook %s is not activated.", $hookName));
return null;
$this->logAlertMessage(sprintf("Hook %s is not activated.", $hookName), true);
}
return $hook;
@@ -360,10 +359,11 @@ class RegisterHookListenersPass implements CompilerPassInterface
* @param string $className the namespace of the class
* @param string $methodName the method name
* @param bool $block tell if the hook is a block or a function
* @param bool $failSafe
*
* @return bool
*/
protected function isValidHookMethod($className, $methodName, $block)
protected function isValidHookMethod($className, $methodName, $block, $failSafe = false)
{
try {
$method = new ReflectionMethod($className, $methodName);
@@ -380,7 +380,10 @@ class RegisterHookListenersPass implements CompilerPassInterface
return false;
}
} catch (ReflectionException $ex) {
$this->logAlertMessage(sprintf("Method %s does not exist in %s : %s", $methodName, $className, $ex));
$this->logAlertMessage(
sprintf("Method %s does not exist in %s : %s", $methodName, $className, $ex),
$failSafe
);
return false;
}

View File

@@ -17,6 +17,8 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Thelia\Model\Module;
use Thelia\Model\ModuleQuery;
/**
*
@@ -52,8 +54,9 @@ class RegisterRouterPass implements CompilerPassInterface
$chainRouter->addMethodCall("add", array(new Reference($id), $priority));
}
if (defined("THELIA_INSTALL_MODE") === false) {
$modules = \Thelia\Model\ModuleQuery::getActivated();
$modules = ModuleQuery::getActivated();
/** @var Module $module */
foreach ($modules as $module) {
$moduleBaseDir = $module->getBaseDir();
$routingConfigFilePath = $module->getAbsoluteBaseDir() . DS . "Config" . DS . "routing.xml";
@@ -76,7 +79,7 @@ class RegisterRouterPass implements CompilerPassInterface
$container->setDefinition("router.".$moduleBaseDir, $definition);
$chainRouter->addMethodCall("add", array(new Reference("router.".$moduleBaseDir), 150));
$chainRouter->addMethodCall("add", array(new Reference("router.".$moduleBaseDir), 150 + $module->getPosition()));
}
}
}

View File

@@ -30,7 +30,7 @@ class CartDuplicationEvent extends CartEvent
*/
public function getDuplicatedCart()
{
return parent::getCart();
return $this->getCart();
}
/**

View File

@@ -20,5 +20,4 @@ namespace Thelia\Core\Event\Country;
*/
class CountryToggleVisibilityEvent extends CountryEvent
{
}

View File

@@ -128,6 +128,4 @@ class ModuleHookCreateEvent extends ModuleHookEvent
return $this;
}
}

View File

@@ -10,36 +10,57 @@
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Core\Event\Loop;
use Thelia\Core\Template\Element\BaseLoop;
/**
* Class LoopExtendsBuildArrayEvent
*
* @package Thelia\Core\Event\Loop
* @author Julien Chanséaume <julien@thelia.net>
*/
class LoopExtendsBuildArrayEvent extends LoopExtendsEvent
{
/** @var array $array */
/**
* @var array Build array results
*/
protected $array;
/**
* LoopExtendsBuildArrayEvent constructor.
* @param array $array
* Class constructor
*
* @param \Thelia\Core\Template\Element\BaseLoop $loop Loop object
* @param array $array Build array base results
*/
public function __construct(BaseLoop $loop, array $array)
{
parent::__construct($loop);
$this->array = $array;
}
/**
* @return array
* Get build array results
*
* @return array Build array results
*/
public function getArray()
{
return $this->array;
}
/**
* Set build array results
*
* @param array $array
*
* @return $this Return $this, allow chaining
*/
public function setArray(array $array)
{
$this->array = $array;
return $this;
}
}

View File

@@ -25,17 +25,28 @@ class ModuleDeleteEvent extends ModuleEvent
protected $module_id;
protected $delete_data;
public function __construct($module_id)
/**
* @var bool
*/
protected $assume_delete;
public function __construct($module_id, $assume_delete = false)
{
parent::__construct();
$this->module_id = $module_id;
$this->assume_delete = $assume_delete;
}
/**
* @param int $module_id
* @return $this
*/
public function setModuleId($module_id)
{
$this->module_id = $module_id;
return $this;
}
/**
@@ -51,10 +62,33 @@ class ModuleDeleteEvent extends ModuleEvent
return $this->delete_data;
}
/**
* @param boolean $delete_data
* @return $this
*/
public function setDeleteData($delete_data)
{
$this->delete_data = $delete_data;
return $this;
}
/**
* @return boolean
*/
public function getAssumeDelete()
{
return $this->assume_delete;
}
/**
* @param boolean $assume_delete
* @return $this
*/
public function setAssumeDelete($assume_delete)
{
$this->assume_delete = $assume_delete;
return $this;
}
}

View File

@@ -34,13 +34,21 @@ class ModuleToggleActivationEvent extends ModuleEvent
*/
protected $recursive;
/**
* @var bool
*/
protected $assume_deactivate;
/**
* @param int $module_id
*/
public function __construct($module_id)
public function __construct($module_id, $assume_deactivate = false)
{
parent::__construct();
$this->module_id = $module_id;
$this->assume_deactivate = $assume_deactivate;
}
/**
@@ -73,7 +81,7 @@ class ModuleToggleActivationEvent extends ModuleEvent
/**
* @param boolean $noCheck
* @return $this;
* @return $this
*/
public function setNoCheck($noCheck)
{
@@ -92,11 +100,29 @@ class ModuleToggleActivationEvent extends ModuleEvent
/**
* @param boolean $recursive
* @return $this;
* @return $this
*/
public function setRecursive($recursive)
{
$this->recursive = $recursive;
return $this;
}
/**
* @return boolean
*/
public function getAssumeDeactivate()
{
return $this->assume_deactivate;
}
/**
* @param boolean $assume_deactivate
* @return $this
*/
public function setAssumeDeactivate($assume_deactivate)
{
$this->assume_deactivate = $assume_deactivate;
return $this;
}
}

View File

@@ -61,6 +61,9 @@ class OrderEvent extends ActionEvent
/** @var null|int */
protected $cartItemId = null;
/** @var null|string */
protected $transactionRef = null;
/**
* @var Response
*/
@@ -352,4 +355,24 @@ class OrderEvent extends ActionEvent
return $this;
}
/**
* @since 2.4.0
* @return null|string
*/
public function getTransactionRef()
{
return $this->transactionRef;
}
/**
* @since 2.4.0
* @param null|string $transactionRef
* @return $this
*/
public function setTransactionRef($transactionRef)
{
$this->transactionRef = $transactionRef;
return $this;
}
}

View File

@@ -17,12 +17,23 @@ use Thelia\Model\Product;
class ProductCloneEvent extends ActionEvent
{
/** @var string */
protected $ref;
/** @var string */
protected $lang;
protected $originalProduct = array();
protected $clonedProduct = array();
/** @var Product */
protected $originalProduct;
/** @var Product */
protected $clonedProduct;
/** @var array */
protected $types = array('images', 'documents');
/**
* ProductCloneEvent constructor.
* @param string $ref
* @param string $lang the locale (such as fr_FR)
* @param $originalProduct
*/
public function __construct(
$ref,
$lang,
@@ -34,7 +45,7 @@ class ProductCloneEvent extends ActionEvent
}
/**
* @return mixed
* @return string
*/
public function getRef()
{
@@ -42,7 +53,7 @@ class ProductCloneEvent extends ActionEvent
}
/**
* @param mixed $ref
* @param string $ref
*/
public function setRef($ref)
{
@@ -50,7 +61,7 @@ class ProductCloneEvent extends ActionEvent
}
/**
* @return mixed
* @return string the locale (such as fr_FR)
*/
public function getLang()
{
@@ -58,7 +69,7 @@ class ProductCloneEvent extends ActionEvent
}
/**
* @param mixed $lang
* @param string $lang the locale (such as fr_FR)
*/
public function setLang($lang)
{

View File

@@ -14,34 +14,55 @@ namespace Thelia\Core\Event\ProductSaleElement;
class ProductSaleElementDeleteEvent extends ProductSaleElementEvent
{
/** @var int */
protected $product_sale_element_id;
/** @var int */
protected $currency_id;
/**
* ProductSaleElementDeleteEvent constructor.
* @param int $product_sale_element_id
* @param int $currency_id
*/
public function __construct($product_sale_element_id, $currency_id)
{
parent::__construct();
$this->product_sale_element_id = $product_sale_element_id;
$this->setCurrencyId($currency_id);
$this->currency_id = $currency_id;
}
/**
* @return int
*/
public function getProductSaleElementId()
{
return $this->product_sale_element_id;
}
/**
* @param int $product_sale_element_id
* @return $this
*/
public function setProductSaleElementId($product_sale_element_id)
{
$this->product_sale_element_id = $product_sale_element_id;
return $this;
}
/**
* @return int
*/
public function getCurrencyId()
{
return $this->currency_id;
}
/**
* @param int $currency_id
* @return $this
*/
public function setCurrencyId($currency_id)
{
$this->currency_id = $currency_id;

View File

@@ -20,5 +20,4 @@ namespace Thelia\Core\Event\State;
*/
class StateToggleVisibilityEvent extends StateEvent
{
}

View File

@@ -17,23 +17,36 @@ use Thelia\Model\Template;
class TemplateEvent extends ActionEvent
{
/**
* @var Template
*/
protected $template = null;
public function __construct(Template $template = null)
{
$this->template = $template;
}
/**
* @return bool
*/
public function hasTemplate()
{
return ! is_null($this->template);
}
/**
* @return Template
*/
public function getTemplate()
{
return $this->template;
}
/**
* @param Template $template
* @return $this
*/
public function setTemplate($template)
{
$this->template = $template;

View File

@@ -26,6 +26,10 @@ final class TheliaEvents
* sent at the beginning
*/
const BOOT = "thelia.boot";
/**
* Kernel View Check Handle
*/
const VIEW_CHECK = "thelia.view_check";
// -- END CORE EVENTS ---------------------------------------------------------
// -- ADDRESS EVENTS ---------------------------------------------------------
/**
@@ -115,6 +119,8 @@ final class TheliaEvents
const CATEGORY_REMOVE_CONTENT = "action.categoryRemoveContent";
const CATEGORY_UPDATE_SEO = "action.updateCategorySeo";
const VIEW_CATEGORY_ID_NOT_VISIBLE = "action.viewCategoryIdNotVisible";
// -- END CATEGORIES EVENTS -----------------------------------------------
@@ -137,6 +143,8 @@ final class TheliaEvents
const CONTENT_ADD_FOLDER = "action.contentAddFolder";
const CONTENT_REMOVE_FOLDER = "action.contentRemoveFolder";
const VIEW_CONTENT_ID_NOT_VISIBLE = "action.viewContentIdNotVisible";
// -- END CONTENT EVENTS ---------------------------------------------------------
@@ -234,6 +242,12 @@ final class TheliaEvents
* sent when a customer need a new password
*/
const LOST_PASSWORD = "action.lostPassword";
/**
* Send the account ccreation confirmation email
*/
const SEND_ACCOUNT_CONFIRMATION_EMAIL = "action.customer.sendAccountConfirmationEmail";
// -- END CUSTOMER EVENTS ---------------------------------------------------------
@@ -253,6 +267,8 @@ final class TheliaEvents
const FOLDER_TOGGLE_VISIBILITY = "action.toggleFolderVisibility";
const FOLDER_UPDATE_POSITION = "action.updateFolderPosition";
const FOLDER_UPDATE_SEO = "action.updateFolderSeo";
const VIEW_FOLDER_ID_NOT_VISIBLE = "action.viewFolderIdNotVisible";
// -- END FOLDER EVENTS ---------------------------------------------------------
@@ -299,6 +315,7 @@ final class TheliaEvents
const VIRTUAL_PRODUCT_ORDER_HANDLE = "action.virtualProduct.handleOrder";
const VIRTUAL_PRODUCT_ORDER_DOWNLOAD_RESPONSE = "action.virtualProduct.downloadResponse";
const VIEW_PRODUCT_ID_NOT_VISIBLE = "action.viewProductIdNotVisible";
// -- END PRODUCT EVENTS ---------------------------------------------------------
@@ -437,6 +454,7 @@ final class TheliaEvents
const ORDER_SEND_NOTIFICATION_EMAIL = "action.order.sendOrderNotificationEmail";
const ORDER_UPDATE_DELIVERY_REF = "action.order.updateDeliveryRef";
const ORDER_UPDATE_TRANSACTION_REF = "action.order.updateTransactionRef";
const ORDER_UPDATE_ADDRESS = "action.order.updateAddress";
const ORDER_PRODUCT_BEFORE_CREATE = "action.orderProduct.beforeCreate";
@@ -694,6 +712,7 @@ final class TheliaEvents
const TEMPLATE_CREATE = "action.createTemplate";
const TEMPLATE_UPDATE = "action.updateTemplate";
const TEMPLATE_DELETE = "action.deleteTemplate";
const TEMPLATE_DUPLICATE = "action.duplicateTemplate";
const TEMPLATE_ADD_ATTRIBUTE = "action.templateAddAttribute";
const TEMPLATE_DELETE_ATTRIBUTE = "action.templateDeleteAttribute";
@@ -912,6 +931,8 @@ final class TheliaEvents
const BEFORE_UPDATEBRAND = "action.before_updateBrand";
const AFTER_UPDATEBRAND = "action.after_updateBrand";
const VIEW_BRAND_ID_NOT_VISIBLE = "action.viewBrandIdNotVisible";
// -- Import ----------------------------------------------
const IMPORT_CHANGE_POSITION = 'import.change.position';
@@ -980,4 +1001,20 @@ final class TheliaEvents
const TRANSLATION_GET_STRINGS = 'action.translation.get_strings';
const TRANSLATION_WRITE_FILE = 'action.translation.write_file';
// -- ORDER STATUS EVENTS -----------------------------------------------
const BEFORE_CREATE_ORDER_STATUS = "action.before_createOrderStatus";
const ORDER_STATUS_CREATE = "action.createOrderStatus";
const AFTER_CREATE_ORDER_STATUS = "action.after_createOrderStatus";
const BEFORE_UPDATE_ORDER_STATUS = "action.before_updateOrderStatus";
const ORDER_STATUS_UPDATE = "action.updateOrderStatus";
const AFTER_UPDATE_ORDER_STATUS = "action.after_updateOrderStatus";
const BEFORE_DELETE_ORDER_STATUS = "action.before_deleteOrderStatus";
const ORDER_STATUS_DELETE = "action.deleteOrderStatus";
const AFTER_DELETE_ORDER_STATUS = "action.after_deleteOrderStatus";
const ORDER_STATUS_UPDATE_POSITION = "action.updateOrderStatusPosition";
// -- END ORDER STATUS EVENTS -----------------------------------------------
}

View File

@@ -22,6 +22,7 @@ use Thelia\Core\Security\Exception\AuthenticationException;
use Thelia\Core\Security\SecurityContext;
use Thelia\Core\Template\ParserInterface;
use Thelia\Core\TheliaKernelEvents;
use Thelia\Log\Tlog;
use Thelia\Model\ConfigQuery;
/**
@@ -89,6 +90,25 @@ class ErrorListener implements EventSubscriberInterface
}
}
public function logException(GetResponseForExceptionEvent $event)
{
// Log exception in the Thelia log
$exception = $event->getException();
$logMessage = '';
do {
$logMessage .=
($logMessage ? PHP_EOL . 'Caused by' : 'Uncaught exception')
. $event->getException()->getMessage()
. PHP_EOL
. "Stack Trace: " . $event->getException()->getTraceAsString()
;
} while (null !== $exception = $exception->getPrevious());
Tlog::getInstance()->error($logMessage);
}
public function authenticationException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
@@ -107,6 +127,7 @@ class ErrorListener implements EventSubscriberInterface
{
return array(
KernelEvents::EXCEPTION => [
["logException", 0],
["handleException", 0],
['authenticationException', 100]
],

View File

@@ -275,9 +275,11 @@ class RequestListener implements EventSubscriberInterface
) {
$request->getSession()->setCurrency($find);
$this->eventDispatcher->dispatch(TheliaEvents::CHANGE_DEFAULT_CURRENCY, new CurrencyChangeEvent($find, $request));
} else {
$defaultCurrency = Currency::getDefaultCurrency();
$request->getSession()->setCurrency($defaultCurrency);
$this->eventDispatcher->dispatch(TheliaEvents::CHANGE_DEFAULT_CURRENCY, new CurrencyChangeEvent($defaultCurrency, $request));
}
} else {
$request->getSession()->setCurrency(Currency::getDefaultCurrency());
}
}

View File

@@ -30,7 +30,6 @@ class ResponseListener implements EventSubscriberInterface
$session = $event->getRequest()->getSession();
if (null !== $id = $session->get("cart_use_cookie")) {
$response = $event->getResponse();
$cookieName = ConfigQuery::read("cart.cookie_name", 'thelia_cart');

View File

@@ -13,6 +13,7 @@
namespace Thelia\Core\EventListener;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -20,6 +21,8 @@ use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Router;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\ViewCheckEvent;
use Thelia\Core\HttpFoundation\Response;
use Thelia\Core\Template\Exception\ResourceNotFoundException;
use Thelia\Exception\OrderException;
@@ -35,23 +38,23 @@ use Thelia\Exception\OrderException;
class ViewListener implements EventSubscriberInterface
{
/**
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
/** @var ContainerInterface */
private $container;
/** @var EventDispatcherInterface */
protected $eventDispatcher;
/**
*
* @param ContainerInterface $container
* @param EventDispatcherInterface $eventDispatcher
*/
public function __construct(ContainerInterface $container)
public function __construct(ContainerInterface $container, EventDispatcherInterface $eventDispatcher)
{
$this->container = $container;
$this->eventDispatcher = $eventDispatcher;
}
/**
*
* Launch the parser defined on the constructor and get the result.
*
* The result is transform id needed into a Response object
@@ -66,7 +69,13 @@ class ViewListener implements EventSubscriberInterface
$request = $this->container->get('request_stack')->getCurrentRequest();
$response = null;
try {
$content = $parser->render($request->attributes->get('_view').".html");
$view = $request->attributes->get('_view');
$viewId = $request->attributes->get($view . '_id');
$this->eventDispatcher->dispatch(TheliaEvents::VIEW_CHECK, new ViewCheckEvent($view, $viewId));
$content = $parser->render($view . '.html');
if ($content instanceof Response) {
$response = $content;
@@ -98,15 +107,19 @@ class ViewListener implements EventSubscriberInterface
{
$request = $this->container->get('request_stack')->getCurrentRequest();
if (null === $request->attributes->get('_view')) {
if (null === $view = $request->attributes->get('_view')) {
$request->attributes->set('_view', $this->findView($request));
}
if (null === $request->attributes->get($view . '_id')) {
$request->attributes->set($view . '_id', $this->findViewId($request, $view));
}
}
public function findView(Request $request)
{
if (! $view = $request->query->get('view')) {
$view = "index";
$view = 'index';
if ($request->request->has('view')) {
$view = $request->request->get('view');
}
@@ -115,6 +128,17 @@ class ViewListener implements EventSubscriberInterface
return $view;
}
public function findViewId(Request $request, $view)
{
if (! $viewId = $request->query->get($view . '_id')) {
$viewId = 0;
if ($request->request->has($view . '_id')) {
$viewId = $request->request->get($view . '_id');
}
}
return $viewId;
}
/**
* {@inheritdoc}

View File

@@ -20,5 +20,4 @@ namespace Thelia\Core\Hook;
*/
class DefaultHook extends BaseHook
{
}

View File

@@ -12,6 +12,7 @@
namespace Thelia\Core\Routing;
use Propel\Runtime\ActiveQuery\Criteria;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\InvalidParameterException;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
@@ -26,7 +27,10 @@ use Thelia\Core\HttpFoundation\Request as TheliaRequest;
use Thelia\Core\HttpKernel\Exception\RedirectException;
use Thelia\Exception\UrlRewritingException;
use Thelia\Model\ConfigQuery;
use Thelia\Model\CustomerQuery;
use Thelia\Model\Lang;
use Thelia\Model\LangQuery;
use Thelia\Model\RewritingUrlQuery;
use Thelia\Rewriting\RewritingResolver;
use Thelia\Tools\URL;
@@ -177,6 +181,11 @@ class RewritingRouter implements RouterInterface, RequestMatcherInterface
if (null ==! $requestedLocale = $request->get('lang')) {
if (null !== $requestedLang = LangQuery::create()->findOneByLocale($requestedLocale)) {
if ($requestedLang->getLocale() != $rewrittenUrlData->locale) {
// Save one redirection if requested locale is disabled.
if (! $requestedLang->getActive()) {
$requestedLang = Lang::getDefaultLanguage();
}
$localizedUrl = $urlTool->retrieve(
$rewrittenUrlData->view,
$rewrittenUrlData->viewId,
@@ -188,9 +197,33 @@ class RewritingRouter implements RouterInterface, RequestMatcherInterface
}
}
// If the rewritten URL locale is disabled, redirect to the URL in the default language
if (null === $lang = LangQuery::create()
->filterByActive(true)
->filterByLocale($rewrittenUrlData->locale)
->findOne()) {
$lang = Lang::getDefaultLanguage();
$localizedUrl = $urlTool->retrieve(
$rewrittenUrlData->view,
$rewrittenUrlData->viewId,
$lang->getLocale()
)->toString();
$this->redirect($urlTool->absoluteUrl($localizedUrl), 301);
}
/* is the URL redirected ? */
if (null !== $rewrittenUrlData->redirectedToUrl) {
$this->redirect($urlTool->absoluteUrl($rewrittenUrlData->redirectedToUrl), 301);
$redirect = RewritingUrlQuery::create()
->filterByView($rewrittenUrlData->view)
->filterByViewId($rewrittenUrlData->viewId)
->filterByViewLocale($rewrittenUrlData->locale)
->filterByRedirected(null, Criteria::ISNULL)
->findOne()
;
$this->redirect($urlTool->absoluteUrl($redirect->getUrl()), 301);
}
/* define GET arguments in request */

View File

@@ -13,11 +13,14 @@
namespace Thelia\Core\Security\Authentication;
use Symfony\Component\HttpFoundation\Request;
use Thelia\Core\Security\Exception\CustomerNotConfirmedException;
use Thelia\Core\Security\UserProvider\UserProviderInterface;
use Thelia\Core\Security\Exception\WrongPasswordException;
use Thelia\Core\Security\Exception\UsernameNotFoundException;
use Symfony\Component\Validator\Exception\ValidatorException;
use Thelia\Form\BaseForm;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Customer;
class UsernamePasswordFormAuthenticator implements AuthenticatorInterface
{
@@ -78,6 +81,14 @@ class UsernamePasswordFormAuthenticator implements AuthenticatorInterface
if ($authOk !== true) {
throw new WrongPasswordException(sprintf("Wrong password for user '%s'.", $username));
}
if (ConfigQuery::isCustomerEmailConfirmationEnable() && $user instanceof Customer) {
// Customer email confirmation feature is available since Thelia 2.3.4
if ($user->getConfirmationToken() !== null && ! $user->getEnable()) {
throw (new CustomerNotConfirmedException())->setUser($user);
}
}
return $user;
}

View File

@@ -97,6 +97,8 @@ class AdminResources
const ORDER = "admin.order";
const ORDER_STATUS = "admin.configuration.order-status";
const PRODUCT = "admin.product";
const PROFILE = "admin.configuration.profile";

View File

@@ -114,7 +114,7 @@ class CSVSerializer extends AbstractSerializer
if ($this->headers !== null) {
// Create tmp file with header
$fd = fopen('php://temp', 'w+b');
fputcsv($fd, $this->headers);
fputcsv($fd, $this->headers, $this->delimiter, $this->enclosure);
// Copy file content into tmp file
$fileObject->rewind();
@@ -131,6 +131,8 @@ class CSVSerializer extends AbstractSerializer
// Remove last line feed
$fileObject->ftruncate($fileObject->getSize() - 1);
clearstatcache(true, $fileObject->getPathname());
}
public function unserialize(\SplFileObject $fileObject)

View File

@@ -147,7 +147,6 @@ class ParamInitMiddleware implements HttpKernelInterface
Tlog::getInstance()->warning("The domain URL for language ".$lang->getTitle()." (id ".$lang->getId().") is not defined.");
return Lang::getDefaultLanguage();
} else {
// one domain for all languages, the lang has to be set into session
return $lang;

View File

@@ -12,6 +12,7 @@
namespace Thelia\Core\Template\Element;
use Propel\Runtime\ActiveQuery\Criteria;
use Thelia\Core\Template\Loop\Argument\Argument;
use Propel\Runtime\ActiveQuery\ModelCriteria;
use Thelia\Model\Tools\ModelCriteriaTools;
@@ -54,8 +55,13 @@ abstract class BaseI18nLoop extends BaseLoop
*
* @return mixed the locale
*/
protected function configureI18nProcessing(ModelCriteria $search, $columns = array('TITLE', 'CHAPO', 'DESCRIPTION', 'POSTSCRIPTUM'), $foreignTable = null, $foreignKey = 'ID', $forceReturn = false)
{
protected function configureI18nProcessing(
ModelCriteria $search,
$columns = array('TITLE', 'CHAPO', 'DESCRIPTION', 'POSTSCRIPTUM'),
$foreignTable = null,
$foreignKey = 'ID',
$forceReturn = false
) {
/* manage translations */
$this->locale = ModelCriteriaTools::getI18n(
@@ -69,4 +75,34 @@ abstract class BaseI18nLoop extends BaseLoop
$this->getForceReturn()
);
}
/**
* Add the search clause for an I18N column, taking care of the back/front context, as default_locale_i18n is
* not defined in the backEnd I18N context.
*
* @param ModelCriteria $search
* @param string $columnName the column to search into, such as TITLE
* @param string $searchCriteria the search criteria, such as Criterial::LIKE, Criteria::EQUAL, etc.
* @param string $searchTerm the searched term
*/
public function addSearchInI18nColumn($search, $columnName, $searchCriteria, $searchTerm)
{
if (! $this->getBackendContext()) {
$search->where(
"CASE WHEN NOT ISNULL(`requested_locale_i18n`.ID)
THEN `requested_locale_i18n`.`$columnName`
ELSE `default_locale_i18n`.`$columnName`
END " . $searchCriteria . " ?",
$searchTerm,
\PDO::PARAM_STR
);
} else {
$search->where(
"`requested_locale_i18n`.`$columnName` $searchCriteria ?",
$searchTerm,
\PDO::PARAM_STR
);
}
}
}

22
core/lib/Thelia/Core/Template/Element/BaseLoop.php Normal file → Executable file
View File

@@ -86,6 +86,7 @@ abstract class BaseLoop
protected $translator = null;
private static $cacheLoopResult = [];
private static $cacheLoopPagination = [];
private static $cacheCount = [];
/** @var array cache of event to dispatch */
@@ -498,6 +499,10 @@ abstract class BaseLoop
$hash = $this->args->getHash();
if (($isCaching = $this->isCaching()) && isset(self::$cacheLoopResult[$hash])) {
if (isset(self::$cacheLoopPagination[$hash])) {
$pagination = self::$cacheLoopPagination[$hash];
}
return self::$cacheLoopResult[$hash];
}
@@ -533,9 +538,15 @@ abstract class BaseLoop
}
$parsedResults = $this->extendsParseResults($this->parseResults($loopResult));
$loopResult->finalizeRows();
if ($isCaching) {
self::$cacheLoopResult[$hash] = $parsedResults;
if ($pagination instanceof PropelModelPager) {
self::$cacheLoopPagination[$hash] = clone $pagination;
}
}
return $parsedResults;
@@ -708,10 +719,11 @@ abstract class BaseLoop
$eventName = $this->getDispatchEventName(TheliaEvents::LOOP_EXTENDS_BUILD_ARRAY);
if (null !== $eventName) {
$this->dispatcher->dispatch(
$eventName,
new LoopExtendsBuildArrayEvent($this, $search)
);
$event = new LoopExtendsBuildArrayEvent($this, $search);
$this->dispatcher->dispatch($eventName, $event);
$search = $event->getArray();
}
return $search;

View File

@@ -80,6 +80,20 @@ class LoopResult implements \Iterator, \JsonSerializable
$this->collection[] = $row;
}
/**
* Adjust the collection once all results have been added.
*/
public function finalizeRows()
{
// Fix rows LOOP_TOTAL if parseResults() did not added all resultsCollection items to the collection array
// see https://github.com/thelia/thelia/issues/2337
if (true === $this->countable && $this->getResultDataCollectionCount() !== $realCount = $this->getCount()) {
foreach ($this->collection as &$item) {
$item->set('LOOP_TOTAL', $realCount);
}
}
}
public function getCount()
{

View File

@@ -12,6 +12,8 @@
namespace Thelia\Core\Template\Element;
use Propel\Runtime\ActiveQuery\ModelCriteria;
/**
*
* @author Etienne Roudeix <eroudeix@openstudio.fr>
@@ -28,5 +30,11 @@ interface SearchLoopInterface
*/
public function getSearchIn();
/**
* @param ModelCriteria $search a query
* @param string $searchTerm the searched term
* @param array $searchIn available field to search in
* @param string $searchCriteria the search criteria, such as Criterial::LIKE, Criteria::EQUAL, etc.
*/
public function doSearch(&$search, $searchTerm, $searchIn, $searchCriteria);
}

View File

@@ -143,13 +143,12 @@ class Argument
}
/**
* This method is available from Thelia 2.2 version
*
* @param $name
* @param null $default
* @param bool $mandatory
* @param bool $empty
* @return Argument
* @since 2.2
*/
public static function createAnyListTypeArgument($name, $default = null, $mandatory = false, $empty = true)
{
@@ -176,4 +175,46 @@ class Argument
$empty
);
}
/**
* @param $name
* @param null $default
* @param bool $mandatory
* @param bool $empty
* @return Argument
* @since 2.4.0
*/
public static function createAlphaNumStringTypeArgument($name, $default = null, $mandatory = false, $empty = true)
{
return new Argument(
$name,
new TypeCollection(
new Type\AlphaNumStringType()
),
$default,
$mandatory,
$empty
);
}
/**
* @param $name
* @param null $default
* @param bool $mandatory
* @param bool $empty
* @return Argument
* @since 2.4.0
*/
public static function createAlphaNumStringListTypeArgument($name, $default = null, $mandatory = false, $empty = true)
{
return new Argument(
$name,
new TypeCollection(
new Type\AlphaNumStringListType()
),
$default,
$mandatory,
$empty
);
}
}

View File

@@ -18,6 +18,7 @@ use Thelia\Core\Template\Element\LoopResult;
use Thelia\Core\Template\Element\LoopResultRow;
use Thelia\Core\Template\Element\PropelSearchLoopInterface;
use Thelia\Core\Template\Element\SearchLoopInterface;
use Thelia\Core\Template\Element\StandardI18nFieldsSearchTrait;
use Thelia\Core\Template\Loop\Argument\Argument;
use Thelia\Core\Template\Loop\Argument\ArgumentCollection;
use Thelia\Model\BrandQuery;
@@ -46,6 +47,8 @@ use Thelia\Type\TypeCollection;
*/
class Brand extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLoopInterface
{
use StandardI18nFieldsSearchTrait;
protected $timestampable = true;
/**
@@ -90,28 +93,20 @@ class Brand extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLoo
*/
public function getSearchIn()
{
return [
"title"
];
return $this->getStandardI18nSearchFields();
}
/**
* @param BrandQuery $search
* @param string $searchTerm
* @param string $searchIn
* @param array $searchIn
* @param string $searchCriteria
*/
public function doSearch(&$search, $searchTerm, $searchIn, $searchCriteria)
{
$search->_and();
$search->where(
"CASE WHEN NOT ISNULL(`requested_locale_i18n`.ID)
THEN `requested_locale_i18n`.`TITLE`ELSE `default_locale_i18n`.`TITLE`
END ".$searchCriteria." ?",
$searchTerm,
\PDO::PARAM_STR
);
$this->addStandardI18nSearch($search, $searchTerm, $searchCriteria);
}
public function buildModelCriteria()
@@ -153,14 +148,7 @@ class Brand extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLoo
$title = $this->getTitle();
if (!is_null($title)) {
$search->where(
"CASE WHEN NOT ISNULL(`requested_locale_i18n`.ID)
THEN `requested_locale_i18n`.`TITLE`
ELSE `default_locale_i18n`.`TITLE`
END ".Criteria::LIKE." ?",
"%".$title."%",
\PDO::PARAM_STR
);
$this->addSearchInI18nColumn($search, 'TITLE', Criteria::LIKE, "%".$title."%");
}
$current = $this->getCurrent();

View File

@@ -18,6 +18,7 @@ use Thelia\Core\Template\Element\LoopResult;
use Thelia\Core\Template\Element\LoopResultRow;
use Thelia\Core\Template\Element\PropelSearchLoopInterface;
use Thelia\Core\Template\Element\SearchLoopInterface;
use Thelia\Core\Template\Element\StandardI18nFieldsSearchTrait;
use Thelia\Core\Template\Loop\Argument\ArgumentCollection;
use Thelia\Core\Template\Loop\Argument\Argument;
use Thelia\Model\CategoryQuery;
@@ -59,9 +60,13 @@ use Thelia\Model\Category as CategoryModel;
* @method bool|string getVisible()
* @method int[] getExclude()
* @method string[] getOrder()
* @method int[] getTemplateId()
* @method bool getProductCountVisibleOnly()
*/
class Category extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLoopInterface
{
use StandardI18nFieldsSearchTrait;
protected $timestampable = true;
protected $versionable = true;
@@ -82,11 +87,21 @@ class Category extends BaseI18nLoop implements PropelSearchLoopInterface, Search
Argument::createBooleanTypeArgument('with_prev_next_info', false),
Argument::createBooleanTypeArgument('need_count_child', false),
Argument::createBooleanTypeArgument('need_product_count', false),
Argument::createBooleanTypeArgument('product_count_visible_only', false),
Argument::createBooleanOrBothTypeArgument('visible', 1),
Argument::createIntListTypeArgument('template_id'),
new Argument(
'order',
new TypeCollection(
new Type\EnumListType(array('id', 'id_reverse', 'alpha', 'alpha_reverse', 'manual', 'manual_reverse', 'visible', 'visible_reverse', 'random'))
new Type\EnumListType([
'id', 'id_reverse',
'alpha', 'alpha_reverse',
'manual', 'manual_reverse',
'visible', 'visible_reverse',
'created', 'created_reverse',
'updated', 'updated_reverse',
'random'
])
),
'manual'
),
@@ -99,22 +114,20 @@ class Category extends BaseI18nLoop implements PropelSearchLoopInterface, Search
*/
public function getSearchIn()
{
return [
"title"
];
return $this->getStandardI18nSearchFields();
}
/**
* @param CategoryQuery $search
* @param string $searchTerm
* @param string $searchIn
* @param array $searchIn
* @param string $searchCriteria
*/
public function doSearch(&$search, $searchTerm, $searchIn, $searchCriteria)
{
$search->_and();
$search->where("CASE WHEN NOT ISNULL(`requested_locale_i18n`.ID) THEN `requested_locale_i18n`.`TITLE` ELSE `default_locale_i18n`.`TITLE` END ".$searchCriteria." ?", $searchTerm, \PDO::PARAM_STR);
$this->addStandardI18nSearch($search, $searchTerm, $searchCriteria);
}
public function buildModelCriteria()
@@ -132,8 +145,11 @@ class Category extends BaseI18nLoop implements PropelSearchLoopInterface, Search
$parent = $this->getParent();
if (!is_null($parent)) {
if (null !== $parent) {
$search->filterByParent($parent, Criteria::IN);
$positionOrderAllowed = true;
} else {
$positionOrderAllowed = false;
}
$excludeParent = $this->getExcludeParent();
@@ -190,6 +206,11 @@ class Category extends BaseI18nLoop implements PropelSearchLoopInterface, Search
->endUse()
;
}
$templateIdList = $this->getTemplateId();
if (!is_null($templateIdList)) {
$search->filterByDefaultTemplateId($templateIdList, Criteria::IN);
}
$orders = $this->getOrder();
@@ -219,6 +240,18 @@ class Category extends BaseI18nLoop implements PropelSearchLoopInterface, Search
case "visible_reverse":
$search->orderByVisible(Criteria::DESC);
break;
case "created":
$search->addAscendingOrderByColumn('created_at');
break;
case "created_reverse":
$search->addDescendingOrderByColumn('created_at');
break;
case "updated":
$search->addAscendingOrderByColumn('updated_at');
break;
case "updated_reverse":
$search->addDescendingOrderByColumn('updated_at');
break;
case "random":
$search->clearOrderByColumns();
$search->addAscendingOrderByColumn('RAND()');
@@ -264,7 +297,11 @@ class Category extends BaseI18nLoop implements PropelSearchLoopInterface, Search
}
if ($this->getNeedProductCount()) {
$loopResultRow->set("PRODUCT_COUNT", $category->countAllProducts());
if ($this->getProductCountVisibleOnly()) {
$loopResultRow->set("PRODUCT_COUNT", $category->countAllProductsVisibleOnly());
} else {
$loopResultRow->set("PRODUCT_COUNT", $category->countAllProducts());
}
}
$isBackendContext = $this->getBackendContext();

View File

@@ -49,32 +49,34 @@ class CategoryPath extends BaseI18nLoop implements ArraySearchLoopInterface
{
return new ArgumentCollection(
Argument::createIntTypeArgument('category', null, true),
Argument::createIntTypeArgument('depth'),
Argument::createIntTypeArgument('depth', PHP_INT_MAX),
Argument::createBooleanOrBothTypeArgument('visible', true, false)
);
}
public function buildArray()
{
$id = $this->getCategory();
$originalId = $currentId = $this->getCategory();
$visible = $this->getVisible();
$search = CategoryQuery::create();
$this->configureI18nProcessing($search, array('TITLE'));
$search->filterById($id);
if ($visible !== BooleanOrBothType::ANY) {
$search->filterByVisible($visible);
}
$depth = $this->getDepth();
$results = array();
$ids = array();
do {
$search = CategoryQuery::create();
$this->configureI18nProcessing($search, array('TITLE'));
$search->filterById($currentId);
if ($visible !== BooleanOrBothType::ANY) {
$search->filterByVisible($visible);
}
$category = $search->findOne();
if ($category != null) {
$results[] = array(
"ID" => $category->getId(),
@@ -82,29 +84,26 @@ class CategoryPath extends BaseI18nLoop implements ArraySearchLoopInterface
"URL" => $category->getUrl($this->locale),
"LOCALE" => $this->locale,
);
$parent = $category->getParent();
if ($parent > 0) {
$currentId = $category->getParent();
if ($currentId > 0) {
// Prevent circular refererences
if (in_array($parent, $ids)) {
throw new \LogicException(sprintf("Circular reference detected in category ID=%d hierarchy (category ID=%d appears more than one times in path)", $id, $parent));
}
$ids[] = $parent;
$search = CategoryQuery::create();
$this->configureI18nProcessing($search, array('TITLE'));
$search->filterById($parent);
if ($visible != BooleanOrBothType::ANY) {
$search->filterByVisible($visible);
if (in_array($currentId, $ids)) {
throw new \LogicException(
sprintf(
"Circular reference detected in category ID=%d hierarchy (category ID=%d appears more than one times in path)",
$originalId,
$currentId
)
);
}
$ids[] = $currentId;
}
}
} while ($category != null && $parent > 0);
} while ($category != null && $currentId > 0 && --$depth > 0);
// Reverse list and build the final result
return array_reverse($results);
}

View File

@@ -18,6 +18,7 @@ use Thelia\Core\Template\Element\LoopResult;
use Thelia\Core\Template\Element\LoopResultRow;
use Thelia\Core\Template\Element\PropelSearchLoopInterface;
use Thelia\Core\Template\Element\SearchLoopInterface;
use Thelia\Core\Template\Element\StandardI18nFieldsSearchTrait;
use Thelia\Core\Template\Loop\Argument\ArgumentCollection;
use Thelia\Core\Template\Loop\Argument\Argument;
use Thelia\Model\ContentFolderQuery;
@@ -54,6 +55,8 @@ use Thelia\Type\BooleanOrBothType;
*/
class Content extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLoopInterface
{
use StandardI18nFieldsSearchTrait;
protected $timestampable = true;
protected $versionable = true;
@@ -77,18 +80,15 @@ class Content extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
new TypeCollection(
new Type\EnumListType(
array(
'alpha',
'alpha-reverse',
'manual',
'manual_reverse',
'id', 'id_reverse',
'alpha', 'alpha-reverse', 'alpha_reverse',
'manual', 'manual_reverse',
'visible', 'visible_reverse',
'random',
'given_id',
'created',
'created_reverse',
'updated',
'updated_reverse',
'position',
'position_reverse'
'created', 'created_reverse',
'updated', 'updated_reverse',
'position', 'position_reverse'
)
)
),
@@ -104,22 +104,20 @@ class Content extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
*/
public function getSearchIn()
{
return [
"title"
];
return $this->getStandardI18nSearchFields();
}
/**
* @param ContentQuery $search
* @param string $searchTerm
* @param string $searchIn
* @param array $searchIn
* @param string $searchCriteria
*/
public function doSearch(&$search, $searchTerm, $searchIn, $searchCriteria)
{
$search->_and();
$search->where("CASE WHEN NOT ISNULL(`requested_locale_i18n`.ID) THEN `requested_locale_i18n`.`TITLE` ELSE `default_locale_i18n`.`TITLE` END ".$searchCriteria." ?", $searchTerm, \PDO::PARAM_STR);
$this->addStandardI18nSearch($search, $searchTerm, $searchCriteria);
}
public function buildModelCriteria()
@@ -140,8 +138,8 @@ class Content extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
// Select the contents which have $folderDefault as the default folder.
$search
->useContentFolderQuery('FolderSelect')
->filterByDefaultFolder(true)
->filterByFolderId($folderDefault, Criteria::IN)
->filterByDefaultFolder(true)
->filterByFolderId($folderDefault, Criteria::IN)
->endUse()
;
@@ -155,7 +153,7 @@ class Content extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
$search
->useContentFolderQuery('FolderSelect')
->filterByFolderId($allFolderIDs, Criteria::IN)
->filterByFolderId($allFolderIDs, Criteria::IN)
->endUse()
;
@@ -163,12 +161,18 @@ class Content extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
$manualOrderAllowed = (1 == $depth && 1 == count($folderIdList));
} else {
$search
->useContentFolderQuery('FolderSelect')
->filterByDefaultFolder(true)
->endUse()
->leftJoinContentFolder('FolderSelect')
->addJoinCondition('FolderSelect', '`FolderSelect`.DEFAULT_FOLDER = 1')
;
}
$search->withColumn(
'CAST(CASE WHEN ISNULL(`FolderSelect`.POSITION) THEN \'' . PHP_INT_MAX . '\' ELSE `FolderSelect`.POSITION END AS SIGNED)',
'position_delegate'
);
$search->withColumn('`FolderSelect`.FOLDER_ID', 'default_folder_id');
$search->withColumn('`FolderSelect`.DEFAULT_FOLDER', 'is_default_folder');
$current = $this->getCurrent();
if ($current === true) {
@@ -198,19 +202,39 @@ class Content extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
$title = $this->getTitle();
if (!is_null($title)) {
$search->where("CASE WHEN NOT ISNULL(`requested_locale_i18n`.ID) THEN `requested_locale_i18n`.`TITLE` ELSE `default_locale_i18n`.`TITLE` END ".Criteria::LIKE." ?", "%".$title."%", \PDO::PARAM_STR);
$this->addSearchInI18nColumn($search, 'TITLE', Criteria::LIKE, "%".$title."%");
}
$search->withColumn('`FolderSelect`.POSITION', 'position_delegate');
$exclude = $this->getExclude();
if (!is_null($exclude)) {
$search->filterById($exclude, Criteria::NOT_IN);
}
$exclude_folder = $this->getExcludeFolder();
if (!is_null($exclude_folder)) {
$search->filterByFolder(
FolderQuery::create()->filterById($exclude_folder, Criteria::IN)->find(),
Criteria::NOT_IN
);
}
$orders = $this->getOrder();
foreach ($orders as $order) {
switch ($order) {
case "id":
$search->orderById(Criteria::ASC);
break;
case "id_reverse":
$search->orderById(Criteria::DESC);
break;
case "alpha":
$search->addAscendingOrderByColumn('i18n_TITLE');
break;
case "alpha-reverse":
case "alpha_reverse":
$search->addDescendingOrderByColumn('i18n_TITLE');
break;
case "manual":
@@ -235,6 +259,12 @@ class Content extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
$search->orderBy($givenIdMatched, Criteria::DESC);
}
break;
case "visible":
$search->orderByVisible(Criteria::ASC);
break;
case "visible_reverse":
$search->orderByVisible(Criteria::DESC);
break;
case "random":
$search->clearOrderByColumns();
$search->addAscendingOrderByColumn('RAND()');
@@ -260,20 +290,7 @@ class Content extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
}
}
$exclude = $this->getExclude();
if (!is_null($exclude)) {
$search->filterById($exclude, Criteria::NOT_IN);
}
$exclude_folder = $this->getExcludeFolder();
if (!is_null($exclude_folder)) {
$search->filterByFolder(
FolderQuery::create()->filterById($exclude_folder, Criteria::IN)->find(),
Criteria::NOT_IN
);
}
$search->groupById();
return $search;
}
@@ -283,7 +300,13 @@ class Content extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
/** @var ContentModel $content */
foreach ($loopResult->getResultDataCollection() as $content) {
$loopResultRow = new LoopResultRow($content);
$defaultFolderId = $content->getDefaultFolderId();
if ((bool) $content->getVirtualColumn('is_default_folder')) {
$defaultFolderId = $content->getVirtualColumn('default_folder_id');
} else {
$defaultFolderId = $content->getDefaultFolderId();
}
$loopResultRow->set("ID", $content->getId())
->set("IS_TRANSLATED", $content->getVirtualColumn('IS_TRANSLATED'))
->set("LOCALE", $this->locale)

View File

@@ -118,6 +118,7 @@ class Country extends BaseI18nLoop implements PropelSearchLoopInterface
if (true === $withArea) {
$search
->distinct()
->joinCountryArea('with_area', Criteria::LEFT_JOIN)
->where('`with_area`.country_id ' . Criteria::ISNOTNULL);
} elseif (false === $withArea) {

View File

@@ -252,7 +252,9 @@ class Customer extends BaseLoop implements SearchLoopInterface, PropelSearchLoop
->set("RESELLER", $customer->getReseller())
->set("SPONSOR", $customer->getSponsor())
->set("DISCOUNT", $customer->getDiscount())
->set("NEWSLETTER", $customer->getVirtualColumn("is_registered_to_newsletter"));
->set("NEWSLETTER", $customer->getVirtualColumn("is_registered_to_newsletter"))
->set("CONFIRMATION_TOKEN", $customer->getConfirmationToken())
;
if ($this->getWithPrevNextInfo()) {
// Find previous and next category

View File

@@ -123,6 +123,13 @@ class Feature extends BaseI18nLoop implements PropelSearchLoopInterface
/** @var ProductModel $product */
foreach ($products as $product) {
if (!$this->getBackendContext()) {
$search
->useFeatureProductQuery()
->filterByProduct($product)
->endUse()
;
}
$tplId = $product->getTemplateId();
if (! is_null($tplId)) {

View File

@@ -22,6 +22,8 @@ use Thelia\Core\Template\Loop\Argument\Argument;
use Thelia\Model\FeatureAv;
use Thelia\Model\FeatureAvQuery;
use Thelia\Model\FeatureProductQuery;
use Thelia\Model\Map\FeatureAvTableMap;
use Thelia\Model\Map\FeatureProductTableMap;
use Thelia\Type\TypeCollection;
use Thelia\Type;
@@ -106,6 +108,24 @@ class FeatureAvailability extends BaseI18nLoop implements PropelSearchLoopInterf
}
}
// We do not consider here Free Text values, so be sure that the features values we will get
// are not free text ones, e.g. are not defined as free-text feature values in the
// feature_product table.
// We are doig here something like
// SELECT * FROM `feature_av`
// WHERE feature_av.FEATURE_ID IN ('7')
// AND feature_av.ID not in (
// select feature_av_id from feature_product
// where feature_id = `feature_av`.feature_id
// and feature_av_id = `feature_av`.id
// and free_text_value = 1
// )
$search->where(FeatureAvTableMap::ID . ' NOT IN (
SELECT '. FeatureProductTableMap::FEATURE_AV_ID . '
FROM ' .FeatureProductTableMap::TABLE_NAME. '
WHERE ' . FeatureProductTableMap::FREE_TEXT_VALUE . ' = 1
)');
return $search;
}
@@ -113,26 +133,19 @@ class FeatureAvailability extends BaseI18nLoop implements PropelSearchLoopInterf
{
/** @var FeatureAv $featureAv */
foreach ($loopResult->getResultDataCollection() as $featureAv) {
$isFreeText = FeatureProductQuery::create()
->filterByFeatureId($featureAv->getFeatureId())
->filterByFeatureAvId($featureAv->getId())
->findOneByFreeTextValue(true);
$loopResultRow = new LoopResultRow($featureAv);
$loopResultRow->set("ID", $featureAv->getId())
->set("IS_TRANSLATED", $featureAv->getVirtualColumn('IS_TRANSLATED'))
->set("LOCALE", $this->locale)
->set("FEATURE_ID", $featureAv->getFeatureId())
->set("TITLE", $featureAv->getVirtualColumn('i18n_TITLE'))
->set("CHAPO", $featureAv->getVirtualColumn('i18n_CHAPO'))
->set("DESCRIPTION", $featureAv->getVirtualColumn('i18n_DESCRIPTION'))
->set("POSTSCRIPTUM", $featureAv->getVirtualColumn('i18n_POSTSCRIPTUM'))
->set("POSITION", $featureAv->getPosition());
$this->addOutputFields($loopResultRow, $featureAv);
if ($isFreeText === null) {
$loopResultRow = new LoopResultRow($featureAv);
$loopResultRow->set("ID", $featureAv->getId())
->set("IS_TRANSLATED", $featureAv->getVirtualColumn('IS_TRANSLATED'))
->set("LOCALE", $this->locale)
->set("FEATURE_ID", $featureAv->getFeatureId())
->set("TITLE", $featureAv->getVirtualColumn('i18n_TITLE'))
->set("CHAPO", $featureAv->getVirtualColumn('i18n_CHAPO'))
->set("DESCRIPTION", $featureAv->getVirtualColumn('i18n_DESCRIPTION'))
->set("POSTSCRIPTUM", $featureAv->getVirtualColumn('i18n_POSTSCRIPTUM'))
->set("POSITION", $featureAv->getPosition());
$this->addOutputFields($loopResultRow, $featureAv);
$loopResult->addRow($loopResultRow);
}
$loopResult->addRow($loopResultRow);
}
return $loopResult;

View File

@@ -12,6 +12,7 @@
namespace Thelia\Core\Template\Loop;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Thelia\Core\Template\Element\ArraySearchLoopInterface;
use Thelia\Core\Template\Element\BaseLoop;
use Thelia\Core\Template\Element\LoopResult;
@@ -41,27 +42,25 @@ class Feed extends BaseLoop implements ArraySearchLoopInterface
public function buildArray()
{
$cachedir = THELIA_ROOT . 'cache/feeds';
/** @var AdapterInterface $cacheAdapter */
$cacheAdapter = $this->container->get('thelia.cache');
if (! is_dir($cachedir)) {
if (! mkdir($cachedir)) {
throw new \Exception(sprintf("Failed to create cache directory '%s'", $cachedir));
}
$cacheItem = $cacheAdapter->getItem('feed_' . md5($this->getUrl()));
if (!$cacheItem->isHit()) {
$feed = new \SimplePie();
$feed->set_feed_url($this->getUrl());
$feed->init();
$feed->handle_content_type();
$cacheItem->expiresAfter($this->getTimeout() * 60);
$cacheItem->set($feed->get_items());
$cacheAdapter->save($cacheItem);
}
$feed = new \SimplePie();
$feed->set_feed_url($this->getUrl());
$feed->set_cache_location(THELIA_ROOT . 'cache/feeds');
$feed->init();
$feed->handle_content_type();
$feed->set_timeout($this->getTimeout());
$items = $feed->get_items();
return $items;
return $cacheItem->get();
}
public function parseResults(LoopResult $loopResult)

View File

@@ -18,6 +18,7 @@ use Thelia\Core\Template\Element\LoopResult;
use Thelia\Core\Template\Element\LoopResultRow;
use Thelia\Core\Template\Element\PropelSearchLoopInterface;
use Thelia\Core\Template\Element\SearchLoopInterface;
use Thelia\Core\Template\Element\StandardI18nFieldsSearchTrait;
use Thelia\Core\Template\Loop\Argument\ArgumentCollection;
use Thelia\Core\Template\Loop\Argument\Argument;
use Thelia\Model\ContentQuery;
@@ -42,9 +43,14 @@ use Thelia\Type\BooleanOrBothType;
* @method string getTitle()
* @method string[] getOrder()
* @method bool getWithPrevNextInfo()
* @method bool getNeedCountChild()
* @method bool getNeedContentCount()
* @method bool getContentCountVisible()
*/
class Folder extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLoopInterface
{
use StandardI18nFieldsSearchTrait;
protected $timestampable = true;
protected $versionable = true;
@@ -61,27 +67,28 @@ class Folder extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLo
Argument::createBooleanTypeArgument('not_empty', 0),
Argument::createBooleanOrBothTypeArgument('visible', 1),
Argument::createAnyTypeArgument('title'),
Argument::createBooleanTypeArgument('need_count_child', true),
Argument::createBooleanTypeArgument('need_content_count', true),
new Argument(
'order',
new TypeCollection(
new Type\EnumListType(
[
'alpha',
'alpha_reverse',
'manual',
'manual_reverse',
'id', 'id_reverse',
'alpha', 'alpha_reverse',
'manual', 'manual_reverse',
'visible', 'visible_reverse',
'random',
'created',
'created_reverse',
'updated',
'updated_reverse'
'created', 'created_reverse',
'updated', 'updated_reverse'
]
)
),
'manual'
),
Argument::createIntListTypeArgument('exclude'),
Argument::createBooleanTypeArgument('with_prev_next_info', false)
Argument::createBooleanTypeArgument('with_prev_next_info', false),
Argument::createBooleanOrBothTypeArgument('content_count_visible', true)
);
}
@@ -90,36 +97,20 @@ class Folder extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLo
*/
public function getSearchIn()
{
return [
"title"
];
return $this->getStandardI18nSearchFields();
}
/**
* @param FolderQuery $search
* @param string $searchTerm
* @param string $searchIn
* @param array $searchIn
* @param string $searchCriteria
*/
public function doSearch(&$search, $searchTerm, $searchIn, $searchCriteria)
{
$search->_and();
$this->addTitleSearchWhereClause($search, $searchCriteria, $searchTerm);
}
/**
* @param FolderQuery $search
* @param string $criteria
* @param string $value
*/
protected function addTitleSearchWhereClause($search, $criteria, $value)
{
$search->where(
"CASE WHEN NOT ISNULL(`requested_locale_i18n`.ID) THEN `requested_locale_i18n`.`TITLE` ELSE `default_locale_i18n`.`TITLE` END ".$criteria." ?",
$value,
\PDO::PARAM_STR
);
$this->addStandardI18nSearch($search, $searchTerm, $searchCriteria);
}
public function buildModelCriteria()
@@ -140,7 +131,7 @@ class Folder extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLo
$parent = $this->getParent();
if (!is_null($parent)) {
if (null !== $parent) {
$search->filterByParent($parent);
}
@@ -171,7 +162,7 @@ class Folder extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLo
$title = $this->getTitle();
if (!is_null($title)) {
$this->addTitleSearchWhereClause($search, Criteria::LIKE, '%'.$title.'%');
$this->addSearchInI18nColumn($search, 'TITLE', Criteria::LIKE, "%".$title."%");
}
$visible = $this->getVisible();
@@ -184,6 +175,12 @@ class Folder extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLo
foreach ($orders as $order) {
switch ($order) {
case "id":
$search->orderById(Criteria::ASC);
break;
case "id_reverse":
$search->orderById(Criteria::DESC);
break;
case "alpha":
$search->addAscendingOrderByColumn('i18n_TITLE');
break;
@@ -196,6 +193,12 @@ class Folder extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLo
case "manual":
$search->orderByPosition(Criteria::ASC);
break;
case "visible":
$search->orderByVisible(Criteria::ASC);
break;
case "visible_reverse":
$search->orderByVisible(Criteria::DESC);
break;
case "random":
$search->clearOrderByColumns();
$search->addAscendingOrderByColumn('RAND()');
@@ -221,6 +224,15 @@ class Folder extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLo
public function parseResults(LoopResult $loopResult)
{
$needCountChild = $this->getNeedCountChild();
$needContentCount = $this->getNeedContentCount();
$contentCountVisiblility = $this->getContentCountVisible();
if ($contentCountVisiblility !== BooleanOrBothType::ANY) {
$contentCountVisiblility = $contentCountVisiblility ? 1 : 0;
}
/** @var \Thelia\Model\Folder $folder */
foreach ($loopResult->getResultDataCollection() as $folder) {
$loopResultRow = new LoopResultRow($folder);
@@ -239,11 +251,18 @@ class Folder extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLo
->set("META_TITLE", $folder->getVirtualColumn('i18n_META_TITLE'))
->set("META_DESCRIPTION", $folder->getVirtualColumn('i18n_META_DESCRIPTION'))
->set("META_KEYWORDS", $folder->getVirtualColumn('i18n_META_KEYWORDS'))
->set("CHILD_COUNT", $folder->countChild())
->set("CONTENT_COUNT", $folder->countAllContents())
->set("VISIBLE", $folder->getVisible() ? "1" : "0")
->set("POSITION", $folder->getPosition());
if ($needCountChild) {
$loopResultRow->set("CHILD_COUNT", $folder->countChild());
}
if ($needContentCount) {
$loopResultRow->set("CONTENT_COUNT", $folder->countAllContents($contentCountVisiblility));
}
$isBackendContext = $this->getBackendContext();
if ($this->getWithPrevNextInfo()) {
@@ -286,4 +305,4 @@ class Folder extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLo
return $loopResult;
}
}
}

View File

@@ -29,6 +29,7 @@ use Thelia\Type\BooleanOrBothType;
* {@inheritdoc}
* @method int getFolder()
* @method bool|string getVisible()
* @method int getDepth()
* @method string[] getOrder()
*/
class FolderPath extends BaseI18nLoop implements ArraySearchLoopInterface
@@ -40,30 +41,32 @@ class FolderPath extends BaseI18nLoop implements ArraySearchLoopInterface
{
return new ArgumentCollection(
Argument::createIntTypeArgument('folder', null, true),
Argument::createIntTypeArgument('depth'),
Argument::createIntTypeArgument('depth', PHP_INT_MAX),
Argument::createBooleanOrBothTypeArgument('visible', true, false)
);
}
public function buildArray()
{
$id = $this->getFolder();
$originalId = $currentId = $this->getFolder();
$visible = $this->getVisible();
$search = FolderQuery::create();
$this->configureI18nProcessing($search, array('TITLE'));
$search->filterById($id);
if ($visible !== BooleanOrBothType::ANY) {
$search->filterByVisible($visible);
}
$depth = $this->getDepth();
$results = array();
$ids = array();
do {
$search = FolderQuery::create();
$this->configureI18nProcessing($search, array('TITLE'));
$search->filterById($currentId);
if ($visible !== BooleanOrBothType::ANY) {
$search->filterByVisible($visible);
}
$folder = $search->findOne();
if ($folder != null) {
@@ -73,28 +76,25 @@ class FolderPath extends BaseI18nLoop implements ArraySearchLoopInterface
"URL" => $folder->getUrl($this->locale),
"LOCALE" => $this->locale,
);
$currentId = $folder->getParent();
$parent = $folder->getParent();
if ($parent > 0) {
if ($currentId > 0) {
// Prevent circular refererences
if (in_array($parent, $ids)) {
throw new \LogicException(sprintf("Circular reference detected in folder ID=%d hierarchy (folder ID=%d appears more than one times in path)", $id, $parent));
if (in_array($currentId, $ids)) {
throw new \LogicException(
sprintf(
"Circular reference detected in folder ID=%d hierarchy (folder ID=%d appears more than one times in path)",
$originalId,
$currentId
)
);
}
$ids[] = $parent;
$search = FolderQuery::create();
$this->configureI18nProcessing($search, array('TITLE'));
$search->filterById($parent);
if ($visible != BooleanOrBothType::ANY) {
$search->filterByVisible($visible);
}
$ids[] = $currentId;
}
}
} while ($folder != null && $parent > 0);
} while ($folder != null && $currentId > 0 && --$depth > 0);
// Reverse list and build the final result
return array_reverse($results);

View File

@@ -47,6 +47,8 @@ use Thelia\Type\TypeCollection;
* @method int[] getExclude()
* @method bool|string getActive()
* @method string[] getOrder()
* @method bool|string getMandatory()
* @method bool|string getHidden()
*/
class Module extends BaseI18nLoop implements PropelSearchLoopInterface
{
@@ -87,24 +89,26 @@ class Module extends BaseI18nLoop implements PropelSearchLoopInterface
'order',
new TypeCollection(
new Type\EnumListType([
'id',
'id_reverse',
'code',
'code_reverse',
'title',
'title_reverse',
'type',
'type_reverse',
'manual',
'manual_reverse',
'enabled',
'enabled_reverse'
'id',
'id_reverse',
'code',
'code_reverse',
'title',
'title_reverse',
'type',
'type_reverse',
'manual',
'manual_reverse',
'enabled',
'enabled_reverse'
])
),
'manual'
),
Argument::createIntListTypeArgument('exclude'),
Argument::createBooleanOrBothTypeArgument('active', Type\BooleanOrBothType::ANY)
Argument::createBooleanOrBothTypeArgument('active', Type\BooleanOrBothType::ANY),
Argument::createBooleanOrBothTypeArgument('hidden', Type\BooleanOrBothType::ANY),
Argument::createBooleanOrBothTypeArgument('mandatory', Type\BooleanOrBothType::ANY)
);
}
@@ -169,6 +173,18 @@ class Module extends BaseI18nLoop implements PropelSearchLoopInterface
$search->filterByActivate($active ? 1 : 0, Criteria::EQUAL);
}
$hidden = $this->getHidden();
if ($hidden !== Type\BooleanOrBothType::ANY) {
$search->filterByHidden($hidden ? 1 : 0, Criteria::EQUAL);
}
$mandatory = $this->getMandatory();
if ($mandatory !== Type\BooleanOrBothType::ANY) {
$search->filterByMandatory($mandatory ? 1 : 0, Criteria::EQUAL);
}
$orders = $this->getOrder();
foreach ($orders as $order) {
@@ -245,6 +261,8 @@ class Module extends BaseI18nLoop implements PropelSearchLoopInterface
->set("VERSION", $module->getVersion())
->set("CLASS", $module->getFullNamespace())
->set("POSITION", $module->getPosition())
->set("MANDATORY", $module->getMandatory())
->set("HIDDEN", $module->getHidden())
->set("EXISTS", $exists);
$hasConfigurationInterface = false;

View File

@@ -93,6 +93,7 @@ class OrderCoupon extends BaseLoop implements PropelSearchLoopInterface
$loopResultRow->set("ID", $orderCoupon->getId())
->set("CODE", $orderCoupon->getCode())
->set("DISCOUNT_AMOUNT", $orderCoupon->getAmount())
->set("TITLE", $orderCoupon->getTitle())
->set("SHORT_DESCRIPTION", $orderCoupon->getShortDescription())
->set("DESCRIPTION", $orderCoupon->getDescription())
@@ -104,6 +105,7 @@ class OrderCoupon extends BaseLoop implements PropelSearchLoopInterface
->set("FREE_SHIPPING_FOR_COUNTRIES_LIST", implode(',', $freeShippingForCountriesIds))
->set("FREE_SHIPPING_FOR_MODULES_LIST", implode(',', $freeShippingForModulesIds))
->set("PER_CUSTOMER_USAGE_COUNT", $orderCoupon->getPerCustomerUsageCount())
->set("IS_USAGE_CANCELED", $orderCoupon->getUsageCanceled())
;
$this->addOutputFields($loopResultRow, $orderCoupon);

View File

@@ -30,9 +30,11 @@ use Thelia\Model\OrderStatus as OrderStatusModel;
* Class OrderStatus
* @package Thelia\Core\Template\Loop
* @author Etienne Roudeix <eroudeix@openstudio.fr>
* @author Gilles Bourgeat <gbourgeat@gmail.com>
*
* @method int[] getId()
* @method string getCode()
* @method string[] getOrder()
*/
class OrderStatus extends BaseI18nLoop implements PropelSearchLoopInterface
{
@@ -45,7 +47,17 @@ class OrderStatus extends BaseI18nLoop implements PropelSearchLoopInterface
{
return new ArgumentCollection(
Argument::createIntListTypeArgument('id'),
Argument::createAnyTypeArgument('code')
Argument::createAnyTypeArgument('code'),
Argument::createEnumListTypeArgument(
'order',
[
'alpha',
'alpha_reverse',
'manual',
'manual_reverse'
],
'manual'
)
);
}
@@ -56,18 +68,33 @@ class OrderStatus extends BaseI18nLoop implements PropelSearchLoopInterface
/* manage translations */
$this->configureI18nProcessing($search);
$id = $this->getId();
if (null !== $id) {
if (null !== $id = $this->getId()) {
$search->filterById($id, Criteria::IN);
}
$code = $this->getCode();
if (null !== $code) {
if (null !== $code = $this->getCode()) {
$search->filterByCode($code, Criteria::EQUAL);
}
$orders = $this->getOrder();
foreach ($orders as $order) {
switch ($order) {
case "alpha":
$search->addAscendingOrderByColumn('i18n_TITLE');
break;
case "alpha_reverse":
$search->addDescendingOrderByColumn('i18n_TITLE');
break;
case "manual":
$search->orderByPosition(Criteria::ASC);
break;
case "manual_reverse":
$search->orderByPosition(Criteria::DESC);
break;
}
}
return $search;
}
@@ -80,6 +107,9 @@ class OrderStatus extends BaseI18nLoop implements PropelSearchLoopInterface
->set("IS_TRANSLATED", $orderStatus->getVirtualColumn('IS_TRANSLATED'))
->set("LOCALE", $this->locale)
->set("CODE", $orderStatus->getCode())
->set("COLOR", $orderStatus->getColor())
->set("POSITION", $orderStatus->getPosition())
->set("PROTECTED_STATUS", $orderStatus->getProtectedStatus())
->set("TITLE", $orderStatus->getVirtualColumn('i18n_TITLE'))
->set("CHAPO", $orderStatus->getVirtualColumn('i18n_CHAPO'))
->set("DESCRIPTION", $orderStatus->getVirtualColumn('i18n_DESCRIPTION'))

View File

@@ -19,6 +19,7 @@ use Thelia\Core\Template\Element\LoopResult;
use Thelia\Core\Template\Element\LoopResultRow;
use Thelia\Core\Template\Element\PropelSearchLoopInterface;
use Thelia\Core\Template\Element\SearchLoopInterface;
use Thelia\Core\Template\Element\StandardI18nFieldsSearchTrait;
use Thelia\Core\Template\Loop\Argument\Argument;
use Thelia\Core\Template\Loop\Argument\ArgumentCollection;
use Thelia\Exception\TaxEngineException;
@@ -26,6 +27,7 @@ use Thelia\Log\Tlog;
use Thelia\Model\CategoryQuery;
use Thelia\Model\ConfigQuery;
use Thelia\Model\CurrencyQuery;
use Thelia\Model\Currency as CurrencyModel;
use Thelia\Model\Map\ProductPriceTableMap;
use Thelia\Model\Map\ProductSaleElementsTableMap;
use Thelia\Model\Map\ProductTableMap;
@@ -75,12 +77,15 @@ use Thelia\Type\TypeCollection;
* @method int[] getFeatureAvailability()
* @method string[] getFeatureValues()
* @method string[] getAttributeNonStrictMatch()
* @method int[] getTemplateId()
*/
class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLoopInterface
{
protected $timestampable = true;
protected $versionable = true;
use StandardI18nFieldsSearchTrait;
/**
* @return ArgumentCollection
*/
@@ -111,6 +116,7 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
Argument::createBooleanOrBothTypeArgument('visible', 1),
Argument::createIntTypeArgument('currency'),
Argument::createAnyTypeArgument('title'),
Argument::createIntListTypeArgument('template_id'),
new Argument(
'order',
new TypeCollection(
@@ -124,6 +130,7 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
'updated', 'updated_reverse',
'ref', 'ref_reverse',
'visible', 'visible_reverse',
'position', 'position_reverse',
'promo',
'new',
'random',
@@ -172,10 +179,10 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
public function getSearchIn()
{
return [
"ref",
"title",
];
return array_merge(
[ 'ref' ],
$this->getStandardI18nSearchFields()
);
}
/**
@@ -187,6 +194,7 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
public function doSearch(&$search, $searchTerm, $searchIn, $searchCriteria)
{
$search->_and();
foreach ($searchIn as $index => $searchInElement) {
if ($index > 0) {
$search->_or();
@@ -195,18 +203,10 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
case "ref":
$search->filterByRef($searchTerm, $searchCriteria);
break;
case "title":
$search->where(
"CASE WHEN NOT ISNULL(`requested_locale_i18n`.ID)
THEN `requested_locale_i18n`.`TITLE`
ELSE `default_locale_i18n`.`TITLE`
END ".$searchCriteria." ?",
$searchTerm,
\PDO::PARAM_STR
);
break;
}
}
$this->addStandardI18nSearch($search, $searchTerm, $searchCriteria);
}
public function parseResults(LoopResult $loopResult)
@@ -232,7 +232,9 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
$price = $product->getVirtualColumn('price');
if ($securityContext->hasCustomerUser() && $securityContext->getCustomerUser()->getDiscount() > 0) {
if (!$this->getBackendContext()
&& $securityContext->hasCustomerUser()
&& $securityContext->getCustomerUser()->getDiscount() > 0) {
$price = $price * (1-($securityContext->getCustomerUser()->getDiscount()/100));
}
@@ -246,7 +248,9 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
}
$promoPrice = $product->getVirtualColumn('promo_price');
if ($securityContext->hasCustomerUser() && $securityContext->getCustomerUser()->getDiscount() > 0) {
if (!$this->getBackendContext()
&& $securityContext->hasCustomerUser()
&& $securityContext->getCustomerUser()->getDiscount() > 0) {
$promoPrice = $promoPrice * (1-($securityContext->getCustomerUser()->getDiscount()/100));
}
try {
@@ -258,7 +262,7 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
$taxedPromoPrice = null;
}
$default_category_id = $product->getDefaultCategoryId();
$defaultCategoryId = $this->getDefaultCategoryId($product);
$loopResultRow
->set("WEIGHT", $product->getVirtualColumn('weight'))
@@ -277,9 +281,12 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
->set("IS_NEW", $product->getVirtualColumn('is_new'))
->set("PRODUCT_SALE_ELEMENT", $product->getVirtualColumn('pse_id'))
->set("PSE_COUNT", $product->getVirtualColumn('pse_count'));
$this->associateValues($loopResultRow, $product, $defaultCategoryId);
$this->addOutputFields($loopResultRow, $product);
$loopResult->addRow($this->associateValues($loopResultRow, $product, $default_category_id));
$loopResult->addRow($loopResultRow);
}
return $loopResult;
@@ -311,8 +318,7 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
$taxedPrice = null;
}
// Find previous and next product, in the default category.
$default_category_id = $product->getDefaultCategoryId();
$defaultCategoryId = $this->getDefaultCategoryId($product);
$loopResultRow
->set("BEST_PRICE", $price)
@@ -321,7 +327,11 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
->set("IS_PROMO", $product->getVirtualColumn('main_product_is_promo'))
->set("IS_NEW", $product->getVirtualColumn('main_product_is_new'));
$loopResult->addRow($this->associateValues($loopResultRow, $product, $default_category_id));
$this->associateValues($loopResultRow, $product, $defaultCategoryId);
$this->addOutputFields($loopResultRow, $product);
$loopResult->addRow($loopResultRow);
}
return $loopResult;
@@ -330,10 +340,10 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
/**
* @param LoopResultRow $loopResultRow the current result row
* @param \Thelia\Model\Product $product
* @param $default_category_id
* @param $defaultCategoryId
* @return mixed
*/
private function associateValues($loopResultRow, $product, $default_category_id)
private function associateValues($loopResultRow, $product, $defaultCategoryId)
{
$display_initial_price = $product->getVirtualColumn('display_initial_price');
@@ -358,12 +368,12 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
->set("VIRTUAL", $product->getVirtual() ? "1" : "0")
->set("VISIBLE", $product->getVisible() ? "1" : "0")
->set("TEMPLATE", $product->getTemplateId())
->set("DEFAULT_CATEGORY", $default_category_id)
->set("DEFAULT_CATEGORY", $defaultCategoryId)
->set("TAX_RULE_ID", $product->getTaxRuleId())
->set("BRAND_ID", $product->getBrandId() ?: 0)
->set("SHOW_ORIGINAL_PRICE", $display_initial_price);
$this->findNextPrev($loopResultRow, $product, $default_category_id);
$this->findNextPrev($loopResultRow, $product, $defaultCategoryId);
return $loopResultRow;
}
@@ -371,23 +381,23 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
/**
* @param LoopResultRow $loopResultRow
* @param ProductModel $product
* @param int $defaultFolderId
* @param int $defaultCategoryId
*/
private function findNextPrev(LoopResultRow $loopResultRow, ProductModel $product, $defaultFolderId)
private function findNextPrev(LoopResultRow $loopResultRow, ProductModel $product, $defaultCategoryId)
{
if ($this->getWithPrevNextInfo()) {
$currentPosition = ProductCategoryQuery::create()
->filterByCategoryId($defaultFolderId)
->filterByCategoryId($defaultCategoryId)
->filterByProductId($product->getId())
->findOne()->getPosition();
// Find previous and next product
$previousQuery = ProductCategoryQuery::create()
->filterByCategoryId($defaultFolderId)
->filterByCategoryId($defaultCategoryId)
->filterByPosition($currentPosition, Criteria::LESS_THAN);
$nextQuery = ProductCategoryQuery::create()
->filterByCategoryId($defaultFolderId)
->filterByCategoryId($defaultCategoryId)
->filterByPosition($currentPosition, Criteria::GREATER_THAN);
if (!$this->getBackendContext()) {
@@ -500,7 +510,7 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
$currency = $this->getCurrentRequest()->getSession()->getCurrency();
}
$defaultCurrency = CurrencyQuery::create()->findOneByByDefault(1);
$defaultCurrency = CurrencyModel::getDefaultCurrency();
$defaultCurrencySuffix = '_default_currency';
$priceToCompareAsSQL = '';
@@ -571,7 +581,13 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
$title = $this->getTitle();
if (!is_null($title)) {
$search->where("CASE WHEN NOT ISNULL(`requested_locale_i18n`.ID) THEN `requested_locale_i18n`.`TITLE` ELSE `default_locale_i18n`.`TITLE` END ".Criteria::LIKE." ?", "%".$title."%", \PDO::PARAM_STR);
$this->addSearchInI18nColumn($search, 'TITLE', Criteria::LIKE, "%".$title."%");
}
$templateIdList = $this->getTemplateId();
if (!is_null($templateIdList)) {
$search->filterByTemplateId($templateIdList, Criteria::IN);
}
$manualOrderAllowed = false;
@@ -603,13 +619,17 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
$manualOrderAllowed = (1 == $depth && 1 == count($categoryIdList));
} else {
$search
->useProductCategoryQuery('CategorySelect')
->filterByDefaultCategory(true)
->endUse()
->leftJoinProductCategory('CategorySelect')
->addJoinCondition('CategorySelect', '`CategorySelect`.DEFAULT_CATEGORY = 1')
;
}
$search->withColumn('`CategorySelect`.POSITION', 'position_delegate');
$search->withColumn(
'CASE WHEN ISNULL(`CategorySelect`.POSITION) THEN ' . PHP_INT_MAX . ' ELSE CAST(`CategorySelect`.POSITION as SIGNED) END',
'position_delegate'
);
$search->withColumn('`CategorySelect`.CATEGORY_ID', 'default_category_id');
$search->withColumn('`CategorySelect`.DEFAULT_CATEGORY', 'is_default_category');
$current = $this->getCurrent();
@@ -1080,6 +1100,12 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
case "updated_reverse":
$search->addDescendingOrderByColumn('updated_at');
break;
case "position":
$search->addAscendingOrderByColumn('position_delegate');
break;
case "position_reverse":
$search->addDescendingOrderByColumn('position_delegate');
break;
case "given_id":
if (null === $id) {
throw new \InvalidArgumentException('Given_id order cannot be set without `id` argument');
@@ -1099,4 +1125,21 @@ class Product extends BaseI18nLoop implements PropelSearchLoopInterface, SearchL
return $search;
}
/**
* Get the default category id for a product
*
* @param \Thelia\Model\Product $product
* @return null|int
*/
protected function getDefaultCategoryId($product)
{
$defaultCategoryId = null;
if ((bool) $product->getVirtualColumn('is_default_category')) {
$defaultCategoryId = $product->getVirtualColumn('default_category_id');
} else {
$defaultCategoryId = $product->getDefaultCategoryId();
}
return $defaultCategoryId;
}
}

View File

@@ -22,6 +22,7 @@ use Thelia\Core\Template\Loop\Argument\Argument;
use Thelia\Core\Template\Loop\Argument\ArgumentCollection;
use Thelia\Exception\TaxEngineException;
use Thelia\Model\CurrencyQuery;
use Thelia\Model\Currency as CurrencyModel;
use Thelia\Model\Map\ProductSaleElementsTableMap;
use Thelia\Model\ProductSaleElementsQuery;
use Thelia\Type;
@@ -150,7 +151,7 @@ class ProductSaleElements extends BaseLoop implements PropelSearchLoopInterface,
$search->orderByQuantity(Criteria::DESC);
break;
case "min_price":
$search->addAscendingOrderByColumn('price_FINAL_PRICE', Criteria::ASC);
$search->addAscendingOrderByColumn('price_FINAL_PRICE');
break;
case "max_price":
$search->addDescendingOrderByColumn('price_FINAL_PRICE');
@@ -184,7 +185,7 @@ class ProductSaleElements extends BaseLoop implements PropelSearchLoopInterface,
$currency = $this->getCurrentRequest()->getSession()->getCurrency();
}
$defaultCurrency = CurrencyQuery::create()->findOneByByDefault(1);
$defaultCurrency = CurrencyModel::getDefaultCurrency();
$defaultCurrencySuffix = '_default_currency';
$search->joinProductPrice('price', Criteria::LEFT_JOIN)
@@ -275,11 +276,32 @@ class ProductSaleElements extends BaseLoop implements PropelSearchLoopInterface,
{
return [
"ref",
"ean_code"
];
}
/**
* @param ProductSaleElementsQuery $search
* @param $searchTerm
* @param $searchIn
* @param $searchCriteria
*/
public function doSearch(&$search, $searchTerm, $searchIn, $searchCriteria)
{
$search->filterByRef($searchTerm, $searchCriteria);
$search->_and();
foreach ($searchIn as $index => $searchInElement) {
if ($index > 0) {
$search->_or();
}
switch ($searchInElement) {
case "ref":
$search->filterByRef($searchTerm, $searchCriteria);
break;
case "ean_code":
$search->filterByEanCode($searchTerm, $searchCriteria);
break;
}
}
}
}

View File

@@ -18,6 +18,7 @@ use Thelia\Core\Template\Element\LoopResult;
use Thelia\Core\Template\Element\LoopResultRow;
use Thelia\Core\Template\Element\PropelSearchLoopInterface;
use Thelia\Core\Template\Element\SearchLoopInterface;
use Thelia\Core\Template\Element\StandardI18nFieldsSearchTrait;
use Thelia\Core\Template\Loop\Argument\Argument;
use Thelia\Core\Template\Loop\Argument\ArgumentCollection;
use Thelia\Model\SaleQuery;
@@ -42,6 +43,8 @@ use Thelia\Type\BooleanOrBothType;
*/
class Sale extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLoopInterface
{
use StandardI18nFieldsSearchTrait;
protected $timestampable = true;
/**
@@ -89,17 +92,34 @@ class Sale extends BaseI18nLoop implements PropelSearchLoopInterface, SearchLoop
*/
public function getSearchIn()
{
return [
"title"
];
return array_merge(
[ "sale_label" ],
$this->getStandardI18nSearchFields()
);
}
/**
* @param SaleQuery $search
* @param string $searchTerm
* @param array $searchIn
* @param string $searchCriteria
*/
public function doSearch(&$search, $searchTerm, $searchIn, $searchCriteria)
{
/** @var SaleQuery $search */
$search->_and();
$search->where("CASE WHEN NOT ISNULL(`requested_locale_i18n`.ID) THEN `requested_locale_i18n`.`TITLE` ELSE `default_locale_i18n`.`TITLE` END ".$searchCriteria." ?", $searchTerm, \PDO::PARAM_STR);
foreach ($searchIn as $index => $searchInElement) {
if ($index > 0) {
$search->_or();
}
switch ($searchInElement) {
case "sale_label":
$this->addSearchInI18nColumn($search, 'SALE_LABEL', $searchCriteria, $searchTerm);
break;
}
}
$this->addStandardI18nSearch($search, $searchTerm, $searchCriteria);
}
public function buildModelCriteria()

View File

@@ -133,6 +133,28 @@ class ParserContext implements \IteratorAggregate
{
$formErrorInformation = $this->getSession()->getFormErrorInformation();
// Get form field error details
$formFieldErrors = [];
/** @var Form $field */
foreach ($form->getForm()->getIterator() as $field) {
$errors = $field->getErrors();
if (count($errors) > 0) {
$formFieldErrors[$field->getName()] = [];
/** @var FormError $error */
foreach ($errors as $error) {
$formFieldErrors[$field->getName()][] = [
'message' => $error->getMessage(),
'template' => $error->getMessageTemplate(),
'parameters' => $error->getMessageParameters(),
'pluralization' => $error->getMessagePluralization()
];
}
}
}
$this->set(get_class($form) . ":" . $form->getType(), $form);
// Set form error information
@@ -142,7 +164,8 @@ class ParserContext implements \IteratorAggregate
'errorMessage' => $form->getErrorMessage(),
'method' => $this->requestStack->getCurrentRequest()->getMethod(),
'timestamp' => time(),
'validation_groups' => $form->getForm()->getConfig()->getOption('validation_groups')
'validation_groups' => $form->getForm()->getConfig()->getOption('validation_groups'),
'field_errors' => $formFieldErrors
];
$this->getSession()->setFormErrorInformation($formErrorInformation);
@@ -189,6 +212,26 @@ class ParserContext implements \IteratorAggregate
} catch (\Exception $ex) {
// Ignore the exception.
}
// Manually set the form fields error information, if validateForm() did not the job,
// which is the case when the user has been redirected.
foreach ($formInfo['field_errors'] as $fieldName => $errors) {
/** @var Form $field */
$field = $form->getForm()->get($fieldName);
if (null !== $field && count($field->getErrors()) == 0) {
foreach ($errors as $errorData) {
$error = new FormError(
$errorData['message'],
$errorData['template'],
$errorData['parameters'],
$errorData['pluralization']
);
$field->addError($error);
}
}
}
}
$form->setError($formInfo['hasError']);

View File

@@ -50,7 +50,7 @@ use Thelia\Model\ModuleQuery;
class Thelia extends Kernel
{
const THELIA_VERSION = '2.3.1';
const THELIA_VERSION = '2.3.4';
public function __construct($environment, $debug)
{
@@ -136,6 +136,15 @@ class Thelia extends Kernel
$canUpdate = true;
Tlog::getInstance()->addWarning("Remove sql_mode ONLY_FULL_GROUP_BY. Please configure your MySQL server.");
}
// remove STRICT_ALL_TABLES, the scheme has been fixed in version 2.4 of Thelia
if (version_compare(Thelia::THELIA_VERSION, '2.4', '<')) {
if (($key = array_search('STRICT_ALL_TABLES', $sessionSqlMode)) !== false) {
unset($sessionSqlMode[$key]);
$canUpdate = true;
Tlog::getInstance()->addWarning("Remove sql_mode STRICT_ALL_TABLES. Please configure your MySQL server or update your Thelia on version 2.4 or higher.");
}
}
}
}