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

@@ -20,13 +20,16 @@ A repository containing all thelia modules is available at this address : https:
Requirements
------------
* php 5.4
* php 5.5
* Required extensions :
* PDO_Mysql
* mcrypt
* openssl
* intl
* gd
* curl
* calendar
* dom
* fileinfo
* safe_mode off
* memory_limit at least 128M, preferably 256.
* post_max_size 20M

1
core/Thelia Normal file → Executable file
View File

@@ -1,4 +1,3 @@
#!/usr/bin/env php
<?php
if (php_sapi_name() != 'cli') {

View File

@@ -55,7 +55,8 @@
"smarty/smarty": "3.1.20",
"ramsey/array_column": "~1.1",
"propel/propel": "dev-thelia-2.3",
"commerceguys/addressing": "0.8.*"
"commerceguys/addressing": "0.8.*",
"symfony/cache": "~3.1.0"
},
"require-dev": {
"fzaninotto/faker": "1.5.*",

View File

@@ -14,6 +14,7 @@ namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Thelia\Core\Event\Brand\BrandCreateEvent;
use Thelia\Core\Event\Brand\BrandDeleteEvent;
use Thelia\Core\Event\Brand\BrandToggleVisibilityEvent;
@@ -21,6 +22,7 @@ use Thelia\Core\Event\Brand\BrandUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Core\Event\ViewCheckEvent;
use Thelia\Model\Brand as BrandModel;
use Thelia\Model\BrandQuery;
@@ -121,6 +123,36 @@ class Brand extends BaseAction implements EventSubscriberInterface
$this->genericUpdatePosition(BrandQuery::create(), $event, $dispatcher);
}
/**
* Check if is a brand view and if brand_id is visible
*
* @param ViewCheckEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function viewCheck(ViewCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->getView() == 'brand') {
$brand = BrandQuery::create()
->filterById($event->getViewId())
->filterByVisible(1)
->count();
if ($brand == 0) {
$dispatcher->dispatch(TheliaEvents::VIEW_BRAND_ID_NOT_VISIBLE, $event);
}
}
}
/**
* @param ViewCheckEvent $event
* @throws NotFoundHttpException
*/
public function viewBrandIdNotVisible(ViewCheckEvent $event)
{
throw new NotFoundHttpException();
}
/**
* {@inheritdoc}
*/
@@ -135,6 +167,9 @@ class Brand extends BaseAction implements EventSubscriberInterface
TheliaEvents::BRAND_UPDATE_POSITION => array('updatePosition', 128),
TheliaEvents::BRAND_TOGGLE_VISIBILITY => array('toggleVisibility', 128),
TheliaEvents::VIEW_CHECK => array('viewCheck', 128),
TheliaEvents::VIEW_BRAND_ID_NOT_VISIBLE => array('viewBrandIdNotVisible', 128),
);
}
}

View File

@@ -12,6 +12,7 @@
namespace Thelia\Action;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Filesystem\Filesystem;
use Thelia\Core\Event\Cache\CacheEvent;
@@ -24,8 +25,23 @@ use Thelia\Core\Event\TheliaEvents;
*/
class Cache extends BaseAction implements EventSubscriberInterface
{
/** @var AdapterInterface */
protected $adapter;
/**
* CacheListener constructor.
* @param AdapterInterface $adapter
*/
public function __construct(AdapterInterface $adapter)
{
$this->adapter = $adapter;
}
public function cacheClear(CacheEvent $event)
{
// clear cache on thelia.cache service
$this->adapter->clear();
$dir = $event->getDir();
$fs = new Filesystem();

View File

@@ -22,6 +22,7 @@ use Thelia\Core\Event\Cart\CartRestoreEvent;
use Thelia\Core\Event\Cart\CartEvent;
use Thelia\Core\Event\Currency\CurrencyChangeEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Exception\TheliaProcessException;
use Thelia\Model\Base\CustomerQuery;
use Thelia\Model\Base\ProductSaleElementsQuery;
use Thelia\Model\Currency as CurrencyModel;
@@ -100,9 +101,11 @@ class Cart extends BaseAction implements EventSubscriberInterface
$productId = $event->getProduct();
// Search for an identical item in the cart
$dispatcher->dispatch(TheliaEvents::CART_FINDITEM, $event);
$findItemEvent = clone $event;
$cartItem = $event->getCartItem();
$dispatcher->dispatch(TheliaEvents::CART_FINDITEM, $findItemEvent);
$cartItem = $findItemEvent->getCartItem();
if ($cartItem === null || $newness) {
$productSaleElements = ProductSaleElementsQuery::create()->findPk($productSaleElementsId);
@@ -111,6 +114,9 @@ class Cart extends BaseAction implements EventSubscriberInterface
$productPrices = $productSaleElements->getPricesByCurrency($currency, $discount);
$cartItem = $this->doAddItem($dispatcher, $cart, $productId, $productSaleElements, $quantity, $productPrices);
} else {
// We did no find any PSE... Something is wrong with the DB, just throw an exception.
throw new TheliaProcessException("This item cannot be added to the cart: no matching product sale element was found.");
}
} elseif ($append && $cartItem !== null) {
$cartItem->addQuantity($quantity)->save();
@@ -133,6 +139,10 @@ class Cart extends BaseAction implements EventSubscriberInterface
->filterByCartId($cart->getId())
->filterById($cartItemId)
->delete();
// Force an update of the Cart object to provide
// to other listeners an updated CartItem collection.
$cart->clearCartItems();
}
}
@@ -301,7 +311,9 @@ class Cart extends BaseAction implements EventSubscriberInterface
*/
public function findCartItem(CartEvent $event)
{
if (null !== $foundItem = CartItemQuery::create()
// Do not try to find a cartItem if one exists in the event, as previous event handlers
// mays have put it in th event.
if (null === $event->getCartItem() && null !== $foundItem = CartItemQuery::create()
->filterByCartId($event->getCart()->getId())
->filterByProductId($event->getProduct())
->filterByProductSaleElementsId($event->getProductSaleElementsId())

View File

@@ -15,6 +15,7 @@ namespace Thelia\Action;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Thelia\Core\Event\File\FileDeleteEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Model\CategoryDocumentQuery;
@@ -29,6 +30,7 @@ use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\Category\CategoryToggleVisibilityEvent;
use Thelia\Core\Event\Category\CategoryAddContentEvent;
use Thelia\Core\Event\Category\CategoryDeleteContentEvent;
use Thelia\Core\Event\ViewCheckEvent;
use Thelia\Model\CategoryAssociatedContent;
use Thelia\Model\CategoryAssociatedContentQuery;
use Thelia\Model\Map\CategoryTableMap;
@@ -212,6 +214,36 @@ class Category extends BaseAction implements EventSubscriberInterface
}
}
/**
* Check if is a category view and if category_id is visible
*
* @param ViewCheckEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function viewCheck(ViewCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->getView() == 'category') {
$category = CategoryQuery::create()
->filterById($event->getViewId())
->filterByVisible(1)
->count();
if ($category == 0) {
$dispatcher->dispatch(TheliaEvents::VIEW_CATEGORY_ID_NOT_VISIBLE, $event);
}
}
}
/**
* @param ViewCheckEvent $event
* @throws NotFoundHttpException
*/
public function viewcategoryIdNotVisible(ViewCheckEvent $event)
{
throw new NotFoundHttpException();
}
/**
* {@inheritDoc}
*/
@@ -229,6 +261,8 @@ class Category extends BaseAction implements EventSubscriberInterface
TheliaEvents::CATEGORY_ADD_CONTENT => array("addContent", 128),
TheliaEvents::CATEGORY_REMOVE_CONTENT => array("removeContent", 128),
TheliaEvents::VIEW_CHECK => array('viewCheck', 128),
TheliaEvents::VIEW_CATEGORY_ID_NOT_VISIBLE => array('viewcategoryIdNotVisible', 128),
);
}
}

View File

@@ -16,6 +16,7 @@ use Propel\Runtime\Exception\PropelException;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Thelia\Core\Event\Content\ContentAddFolderEvent;
use Thelia\Core\Event\Content\ContentCreateEvent;
use Thelia\Core\Event\Content\ContentDeleteEvent;
@@ -26,6 +27,7 @@ use Thelia\Core\Event\File\FileDeleteEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Core\Event\ViewCheckEvent;
use Thelia\Model\ContentDocumentQuery;
use Thelia\Model\ContentFolder;
use Thelia\Model\ContentFolderQuery;
@@ -207,13 +209,43 @@ class Content extends BaseAction implements EventSubscriberInterface
}
}
/**
* Check if is a content view and if content_id is visible
*
* @param ViewCheckEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function viewCheck(ViewCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->getView() == 'content') {
$content = ContentQuery::create()
->filterById($event->getViewId())
->filterByVisible(1)
->count();
if ($content == 0) {
$dispatcher->dispatch(TheliaEvents::VIEW_CONTENT_ID_NOT_VISIBLE, $event);
}
}
}
/**
* @param ViewCheckEvent $event
* @throws NotFoundHttpException
*/
public function viewContentIdNotVisible(ViewCheckEvent $event)
{
throw new NotFoundHttpException();
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::CONTENT_CREATE => array('create', 128),
TheliaEvents::CONTENT_CREATE => array('create', 128),
TheliaEvents::CONTENT_UPDATE => array('update', 128),
TheliaEvents::CONTENT_DELETE => array('delete', 128),
TheliaEvents::CONTENT_TOGGLE_VISIBILITY => array('toggleVisibility', 128),
@@ -223,6 +255,9 @@ class Content extends BaseAction implements EventSubscriberInterface
TheliaEvents::CONTENT_ADD_FOLDER => array('addFolder', 128),
TheliaEvents::CONTENT_REMOVE_FOLDER => array('removeFolder', 128),
TheliaEvents::VIEW_CHECK => array('viewCheck', 128),
TheliaEvents::VIEW_CONTENT_ID_NOT_VISIBLE => array('viewContentIdNotVisible', 128),
);
}
}

View File

@@ -38,6 +38,8 @@ use Thelia\Model\Map\OrderCouponTableMap;
use Thelia\Model\OrderCoupon;
use Thelia\Model\OrderCouponCountry;
use Thelia\Model\OrderCouponModule;
use Thelia\Model\OrderCouponQuery;
use Thelia\Model\OrderStatusQuery;
/**
* Process Coupon Events
@@ -322,7 +324,7 @@ class Coupon extends BaseAction implements EventSubscriberInterface
$orderCoupon->setOrder($event->getOrder())
->setCode($couponModel->getCode())
->setType($couponModel->getType())
->setAmount($couponModel->getAmount())
->setAmount($couponCode->exec())
->setTitle($couponModel->getTitle())
->setShortDescription($couponModel->getShortDescription())
@@ -378,6 +380,58 @@ class Coupon extends BaseAction implements EventSubscriberInterface
$dispatcher->dispatch(TheliaEvents::COUPON_CLEAR_ALL);
}
/**
* Cancels order coupons usage when order is canceled or refunded,
* or use canceled coupons again if the order is no longer canceled or refunded
*
* @param OrderEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
public function orderStatusChange(OrderEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
// The order has been canceled or refunded ?
if ($event->getOrder()->isCancelled() || $event->getOrder()->isRefunded()) {
// Cancel usage of all coupons for this order
$usedCoupons = OrderCouponQuery::create()
->filterByUsageCanceled(false)
->findByOrderId($event->getOrder()->getId());
$customerId = $event->getOrder()->getCustomerId();
/** @var OrderCoupon $usedCoupon */
foreach ($usedCoupons as $usedCoupon) {
if (null !== $couponModel = CouponQuery::create()->findOneByCode($usedCoupon->getCode())) {
// If the coupon still exists, restore one usage to the usage count.
$this->couponManager->incrementQuantity($couponModel, $customerId);
}
// Mark coupon usage as canceled in the OrderCoupon table
$usedCoupon->setUsageCanceled(true)->save();
}
} else {
// Mark canceled coupons for this order as used again
$usedCoupons = OrderCouponQuery::create()
->filterByUsageCanceled(true)
->findByOrderId($event->getOrder()->getId());
$customerId = $event->getOrder()->getCustomerId();
/** @var OrderCoupon $usedCoupon */
foreach ($usedCoupons as $usedCoupon) {
if (null !== $couponModel = CouponQuery::create()->findOneByCode($usedCoupon->getCode())) {
// If the coupon still exists, mark the coupon as used
$this->couponManager->decrementQuantity($couponModel, $customerId);
}
// The coupon is no longer canceled
$usedCoupon->setUsageCanceled(false)->save();
}
}
}
/**
* {@inheritdoc}
*/
@@ -392,9 +446,11 @@ class Coupon extends BaseAction implements EventSubscriberInterface
TheliaEvents::COUPON_CONDITION_UPDATE => array("updateCondition", 128),
TheliaEvents::ORDER_SET_POSTAGE => array("testFreePostage", 132),
TheliaEvents::ORDER_BEFORE_PAYMENT => array("afterOrder", 128),
TheliaEvents::ORDER_UPDATE_STATUS => array("orderStatusChange", 10),
TheliaEvents::CART_ADDITEM => array("updateOrderDiscount", 10),
TheliaEvents::CART_UPDATEITEM => array("updateOrderDiscount", 10),
TheliaEvents::CART_DELETEITEM => array("updateOrderDiscount", 10),
TheliaEvents::CUSTOMER_LOGIN => array("updateOrderDiscount", 10)
);
}

View File

@@ -24,6 +24,7 @@ use Thelia\Core\Security\SecurityContext;
use Thelia\Core\Translation\Translator;
use Thelia\Exception\CustomerException;
use Thelia\Mailer\MailerFactory;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Customer as CustomerModel;
use Thelia\Model\CustomerQuery;
use Thelia\Tools\Password;
@@ -59,7 +60,29 @@ class Customer extends BaseAction implements EventSubscriberInterface
$this->createOrUpdateCustomer($customer, $event, $dispatcher);
if ($event->getNotifyCustomerOfAccountCreation()) {
$this->mailer->sendEmailToCustomer('customer_account_created', $customer, [ 'password' => $plainPassword ]);
$this->mailer->sendEmailToCustomer(
'customer_account_created',
$customer,
[ 'password' => $plainPassword ]
);
}
$dispatcher->dispatch(
TheliaEvents::SEND_ACCOUNT_CONFIRMATION_EMAIL,
new CustomerEvent($customer)
);
}
public function customerConfirmationEmail(CustomerEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$customer = $event->getCustomer();
if (ConfigQuery::isCustomerEmailConfirmationEnable() && $customer->getConfirmationToken() !== null && $customer !== null) {
$this->mailer->sendEmailToCustomer(
'customer_confirmation',
$customer,
['customer_id' => $customer->getId()]
);
}
}
@@ -209,7 +232,8 @@ class Customer extends BaseAction implements EventSubscriberInterface
TheliaEvents::CUSTOMER_LOGOUT => array('logout', 128),
TheliaEvents::CUSTOMER_LOGIN => array('login', 128),
TheliaEvents::CUSTOMER_DELETEACCOUNT => array('delete', 128),
TheliaEvents::LOST_PASSWORD => array('lostPassword', 128)
TheliaEvents::LOST_PASSWORD => array('lostPassword', 128),
TheliaEvents::SEND_ACCOUNT_CONFIRMATION_EMAIL => array('customerConfirmationEmail', 128)
);
}
}

View File

@@ -15,6 +15,7 @@ namespace Thelia\Action;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Thelia\Core\Event\File\FileDeleteEvent;
use Thelia\Core\Event\Folder\FolderCreateEvent;
use Thelia\Core\Event\Folder\FolderDeleteEvent;
@@ -23,6 +24,7 @@ use Thelia\Core\Event\Folder\FolderUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Core\Event\ViewCheckEvent;
use Thelia\Model\FolderDocumentQuery;
use Thelia\Model\FolderImageQuery;
use Thelia\Model\FolderQuery;
@@ -155,6 +157,36 @@ class Folder extends BaseAction implements EventSubscriberInterface
}
}
/**
* Check if is a folder view and if folder_id is visible
*
* @param ViewCheckEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function viewCheck(ViewCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->getView() == 'folder') {
$folder = FolderQuery::create()
->filterById($event->getViewId())
->filterByVisible(1)
->count();
if ($folder == 0) {
$dispatcher->dispatch(TheliaEvents::VIEW_FOLDER_ID_NOT_VISIBLE, $event);
}
}
}
/**
* @param ViewCheckEvent $event
* @throws NotFoundHttpException
*/
public function viewFolderIdNotVisible(ViewCheckEvent $event)
{
throw new NotFoundHttpException();
}
/**
* {@inheritdoc}
*/
@@ -167,7 +199,10 @@ class Folder extends BaseAction implements EventSubscriberInterface
TheliaEvents::FOLDER_TOGGLE_VISIBILITY => array("toggleVisibility", 128),
TheliaEvents::FOLDER_UPDATE_POSITION => array("updatePosition", 128),
TheliaEvents::FOLDER_UPDATE_SEO => array('updateSeo', 128)
TheliaEvents::FOLDER_UPDATE_SEO => array('updateSeo', 128),
TheliaEvents::VIEW_CHECK => array('viewCheck', 128),
TheliaEvents::VIEW_FOLDER_ID_NOT_VISIBLE => array('viewFolderIdNotVisible', 128),
);
}
}

View File

@@ -301,11 +301,18 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
$delta_x = $delta_y = $border_width = $border_height = 0;
if ($width_diff > 1 && $height_diff > 1) {
$resize_width = $width_orig;
$resize_height = $height_orig;
// Set the default final size. If zoom is allowed, we will get the required
// image dimension. Otherwise, the final image may be smaller than required.
if ($allow_zoom) {
$resize_width = $dest_width;
$resize_height = $dest_height;
} else {
$resize_width = $width_orig;
$resize_height = $height_orig;
}
// When cropping, be sure to always generate an image which is
// no smaller than the required size, zooming it if required.
// not smaller than the required size, zooming it if required.
if ($resize_mode == self::EXACT_RATIO_WITH_CROP) {
if ($allow_zoom) {
if ($width_diff > $height_diff) {

View File

@@ -84,6 +84,12 @@ class Module extends BaseAction implements EventSubscriberInterface
if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId())) {
try {
if ($module->getActivate() == BaseModule::IS_ACTIVATED) {
if ($module->getMandatory() == BaseModule::IS_MANDATORY && $event->getAssumeDeactivate() === false) {
throw new \Exception(
Translator::getInstance()->trans('Can\'t deactivate a secure module')
);
}
if ($event->isRecursive()) {
$this->recursiveDeactivation($event, $eventName, $dispatcher);
}
@@ -233,6 +239,11 @@ class Module extends BaseAction implements EventSubscriberInterface
}
try {
if ($module->getMandatory() == BaseModule::IS_MANDATORY && $event->getAssumeDelete() === false) {
throw new \Exception(
Translator::getInstance()->trans('Can\'t remove a core module')
);
}
// First, try to create an instance
$instance = $module->createInstance();

View File

@@ -642,6 +642,19 @@ class Order extends BaseAction implements EventSubscriberInterface
$event->setOrder($order);
}
/**
* @param OrderEvent $event
*/
public function updateTransactionRef(OrderEvent $event)
{
$order = $event->getOrder();
$order->setTransactionRef($event->getTransactionRef());
$order->save();
$event->setOrder($order);
}
/**
* @param OrderAddressEvent $event
*/
@@ -710,6 +723,7 @@ class Order extends BaseAction implements EventSubscriberInterface
TheliaEvents::ORDER_SEND_NOTIFICATION_EMAIL => array("sendNotificationEmail", 128),
TheliaEvents::ORDER_UPDATE_STATUS => array("updateStatus", 128),
TheliaEvents::ORDER_UPDATE_DELIVERY_REF => array("updateDeliveryRef", 128),
TheliaEvents::ORDER_UPDATE_TRANSACTION_REF => array("updateTransactionRef", 128),
TheliaEvents::ORDER_UPDATE_ADDRESS => array("updateAddress", 128),
TheliaEvents::ORDER_CREATE_MANUAL => array("createManual", 128),
);

View File

@@ -14,17 +14,53 @@ namespace Thelia\Action;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Exception\PropelException;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Thelia\Core\Event\Feature\FeatureAvCreateEvent;
use Thelia\Core\Event\Feature\FeatureAvDeleteEvent;
use Thelia\Core\Event\FeatureProduct\FeatureProductDeleteEvent;
use Thelia\Core\Event\FeatureProduct\FeatureProductUpdateEvent;
use Thelia\Core\Event\File\FileDeleteEvent;
use Thelia\Core\Event\Product\ProductAddAccessoryEvent;
use Thelia\Core\Event\Product\ProductAddCategoryEvent;
use Thelia\Core\Event\Product\ProductAddContentEvent;
use Thelia\Core\Event\Product\ProductCloneEvent;
use Thelia\Model\AttributeCombinationQuery;
use Thelia\Core\Event\Product\ProductCreateEvent;
use Thelia\Core\Event\Product\ProductDeleteAccessoryEvent;
use Thelia\Core\Event\Product\ProductDeleteCategoryEvent;
use Thelia\Core\Event\Product\ProductDeleteContentEvent;
use Thelia\Core\Event\Product\ProductDeleteEvent;
use Thelia\Core\Event\Product\ProductSetTemplateEvent;
use Thelia\Core\Event\Product\ProductToggleVisibilityEvent;
use Thelia\Core\Event\Product\ProductUpdateEvent;
use Thelia\Core\Event\ProductSaleElement\ProductSaleElementDeleteEvent;
use Thelia\Core\Event\Template\TemplateDeleteAttributeEvent;
use Thelia\Core\Event\Template\TemplateDeleteFeatureEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Core\Event\ViewCheckEvent;
use Thelia\Model\Accessory;
use Thelia\Model\AccessoryQuery;
use Thelia\Model\AttributeTemplateQuery;
use Thelia\Model\Currency as CurrencyModel;
use Thelia\Model\FeatureAvI18n;
use Thelia\Model\FeatureAvI18nQuery;
use Thelia\Model\FeatureAvQuery;
use Thelia\Model\FeatureProduct;
use Thelia\Model\FeatureProductQuery;
use Thelia\Model\FeatureTemplateQuery;
use Thelia\Model\Map\AttributeTemplateTableMap;
use Thelia\Model\Map\FeatureTemplateTableMap;
use Thelia\Model\Map\ProductSaleElementsTableMap;
use Thelia\Model\Map\ProductTableMap;
use Thelia\Model\Product as ProductModel;
use Thelia\Model\ProductAssociatedContent;
use Thelia\Model\ProductAssociatedContentQuery;
use Thelia\Model\ProductCategory;
use Thelia\Model\ProductCategoryQuery;
use Thelia\Model\ProductDocument;
use Thelia\Model\ProductDocumentQuery;
use Thelia\Model\ProductI18n;
@@ -34,34 +70,8 @@ use Thelia\Model\ProductImageQuery;
use Thelia\Model\ProductPrice;
use Thelia\Model\ProductPriceQuery;
use Thelia\Model\ProductQuery;
use Thelia\Model\Product as ProductModel;
use Thelia\Model\ProductAssociatedContent;
use Thelia\Model\ProductAssociatedContentQuery;
use Thelia\Model\ProductCategory;
use Thelia\Model\TaxRuleQuery;
use Thelia\Model\AccessoryQuery;
use Thelia\Model\Accessory;
use Thelia\Model\FeatureProduct;
use Thelia\Model\FeatureProductQuery;
use Thelia\Model\ProductCategoryQuery;
use Thelia\Model\ProductSaleElementsQuery;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Product\ProductUpdateEvent;
use Thelia\Core\Event\Product\ProductCreateEvent;
use Thelia\Core\Event\Product\ProductDeleteEvent;
use Thelia\Core\Event\Product\ProductToggleVisibilityEvent;
use Thelia\Core\Event\Product\ProductAddContentEvent;
use Thelia\Core\Event\Product\ProductDeleteContentEvent;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Core\Event\FeatureProduct\FeatureProductUpdateEvent;
use Thelia\Core\Event\FeatureProduct\FeatureProductDeleteEvent;
use Thelia\Core\Event\Product\ProductSetTemplateEvent;
use Thelia\Core\Event\Product\ProductDeleteCategoryEvent;
use Thelia\Core\Event\Product\ProductAddCategoryEvent;
use Thelia\Core\Event\Product\ProductAddAccessoryEvent;
use Thelia\Core\Event\Product\ProductDeleteAccessoryEvent;
use Propel\Runtime\Propel;
use Thelia\Model\TaxRuleQuery;
class Product extends BaseAction implements EventSubscriberInterface
{
@@ -80,6 +90,11 @@ class Product extends BaseAction implements EventSubscriberInterface
*/
public function create(ProductCreateEvent $event)
{
$defaultTaxRuleId = null;
if (null !== $defaultTaxRule = TaxRuleQuery::create()->findOneByIsDefault(true)) {
$defaultTaxRuleId = $defaultTaxRule->getId();
}
$product = new ProductModel();
$product
@@ -90,17 +105,14 @@ class Product extends BaseAction implements EventSubscriberInterface
->setTitle($event->getTitle())
->setVisible($event->getVisible() ? 1 : 0)
->setVirtual($event->getVirtual() ? 1 : 0)
// Set the default tax rule to this product
->setTaxRule(TaxRuleQuery::create()->findOneByIsDefault(true))
->setTemplateId($event->getTemplateId())
->create(
$event->getDefaultCategory(),
$event->getBasePrice(),
$event->getCurrencyId(),
$event->getTaxRuleId(),
// Set the default tax rule if not defined
$event->getTaxRuleId() ?: $defaultTaxRuleId,
$event->getBaseWeight(),
$event->getBaseQuantity()
)
@@ -278,6 +290,7 @@ class Product extends BaseAction implements EventSubscriberInterface
->findByProductId($event->getOriginalProduct()->getId());
// Set clone product associated contents
/** @var ProductAssociatedContent $originalProductAssocCont */
foreach ($originalProductAssocConts as $originalProductAssocCont) {
$clonedProductCreatePAC = new ProductAddContentEvent($event->getClonedProduct(), $originalProductAssocCont->getContentId());
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_ADD_CONTENT, $clonedProductCreatePAC);
@@ -291,6 +304,7 @@ class Product extends BaseAction implements EventSubscriberInterface
->findByProductId($event->getOriginalProduct()->getId());
// Set clone product accessories
/** @var Accessory $originalProductAccessory */
foreach ($originalProductAccessoryList as $originalProductAccessory) {
$clonedProductAddAccessoryEvent = new ProductAddAccessoryEvent($event->getClonedProduct(), $originalProductAccessory->getAccessory());
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_ADD_ACCESSORY, $clonedProductAddAccessoryEvent);
@@ -387,22 +401,6 @@ class Product extends BaseAction implements EventSubscriberInterface
->findByProductId($event->getProductId());
$fileList['documentList']['type'] = TheliaEvents::DOCUMENT_DELETE;
// Delete free_text_feature AV (see issue #2061)
$featureAvs = FeatureAvQuery::create()
->useFeatureProductQuery()
->filterByFreeTextValue(true)
->filterByProductId($event->getProductId())
->endUse()
->find($con)
;
foreach ($featureAvs as $featureAv) {
$featureAv
->setDispatcher($this->eventDispatcher)
->delete($con)
;
}
// Delete product
$product
->setDispatcher($this->eventDispatcher)
@@ -563,28 +561,69 @@ class Product extends BaseAction implements EventSubscriberInterface
try {
$product = $event->getProduct();
// Delete all product feature relations
if (null !== $featureProducts = FeatureProductQuery::create()->findByProductId($product->getId())) {
/** @var \Thelia\Model\FeatureProduct $featureProduct */
foreach ($featureProducts as $featureProduct) {
$eventDelete = new FeatureProductDeleteEvent($product->getId(), $featureProduct->getFeatureId());
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE, $eventDelete);
}
// Check differences between current coobination and the next one, and clear obsoletes values.
$nextTemplateId = $event->getTemplateId();
$currentTemplateId = $product->getTemplateId();
// 1. Process product features.
$currentFeatures = FeatureTemplateQuery::create()
->filterByTemplateId($currentTemplateId)
->select([ FeatureTemplateTableMap::FEATURE_ID ])
->find($con);
$nextFeatures = FeatureTemplateQuery::create()
->filterByTemplateId($nextTemplateId)
->select([ FeatureTemplateTableMap::FEATURE_ID ])
->find($con);
// Find features values we shoud delete. To do this, we have to
// find all features in $currentFeatures that are not present in $nextFeatures
$featuresToDelete = array_diff($currentFeatures->getData(), $nextFeatures->getData());
// Delete obsolete features values
foreach ($featuresToDelete as $featureId) {
$this->eventDispatcher->dispatch(
TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE,
new FeatureProductDeleteEvent($product->getId(), $featureId)
);
}
// 2. Process product Attributes
$currentAttributes = AttributeTemplateQuery::create()
->filterByTemplateId($currentTemplateId)
->select([ AttributeTemplateTableMap::ATTRIBUTE_ID ])
->find($con);
$nextAttributes = AttributeTemplateQuery::create()
->filterByTemplateId($nextTemplateId)
->select([ AttributeTemplateTableMap::ATTRIBUTE_ID ])
->find($con);
// Find attributes values we shoud delete. To do this, we have to
// find all attributes in $currentAttributes that are not present in $nextAttributes
$attributesToDelete = array_diff($currentAttributes->getData(), $nextAttributes->getData());
// Delete all product attributes sale elements
AttributeCombinationQuery::create()
->filterByProductSaleElements($product->getProductSaleElementss())
->delete($con)
;
//Delete all productSaleElements except the default one (to keep price, weight, ean, etc...)
ProductSaleElementsQuery::create()
->filterByProduct($product)
->filterByIsDefault(1, Criteria::NOT_EQUAL)
->delete($con)
;
// Find PSE which includes $attributesToDelete for the current product/
$pseToDelete = ProductSaleElementsQuery::create()
->filterByProductId($product->getId())
->useAttributeCombinationQuery()
->filterByAttributeId($attributesToDelete, Criteria::IN)
->endUse()
->select([ ProductSaleElementsTableMap::ID ])
->find();
// Delete obsolete PSEs
foreach ($pseToDelete->getData() as $pseId) {
$this->eventDispatcher->dispatch(
TheliaEvents::PRODUCT_DELETE_PRODUCT_SALE_ELEMENT,
new ProductSaleElementDeleteEvent(
$pseId,
CurrencyModel::getDefaultCurrency()->getId()
)
);
}
// Update the product template
$template_id = $event->getTemplateId();
@@ -596,16 +635,6 @@ class Product extends BaseAction implements EventSubscriberInterface
$product->setTemplateId($template_id)->save($con);
//Be sure that the product has a default productSaleElements
/** @var \Thelia\Model\ProductSaleElements $defaultPse */
if (null == $defaultPse = ProductSaleElementsQuery::create()
->filterByProduct($product)
->filterByIsDefault(1)
->findOne()) {
// Create a new default product sale element
$product->createProductSaleElement($con, 0, 0, 0, $event->getCurrencyId(), true);
}
$product->clearProductSaleElementss();
$event->setProduct($product);
@@ -618,7 +647,7 @@ class Product extends BaseAction implements EventSubscriberInterface
throw $ex;
}
}
/**
* Changes accessry position, selecting absolute ou relative change.
*
@@ -781,6 +810,93 @@ class Product extends BaseAction implements EventSubscriberInterface
$model->getProductSaleElementsProductDocuments()->delete();
}
}
/**
* When a feature is removed from a template, the products which are using this feature should be updated.
*
* @param TemplateDeleteFeatureEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function deleteTemplateFeature(TemplateDeleteFeatureEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
// Detete the removed feature in all products which are using this template
$products = ProductQuery::create()
->filterByTemplateId($event->getTemplate()->getId())
->find()
;
foreach ($products as $product) {
$dispatcher->dispatch(
TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE,
new FeatureProductDeleteEvent($product->getId(), $event->getFeatureId())
);
}
}
/**
* When an attribute is removed from a template, the conbinations and PSE of products which are using this template
* should be updated.
*
* @param TemplateDeleteAttributeEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function deleteTemplateAttribute(TemplateDeleteAttributeEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
// Detete the removed attribute in all products which are using this template
$pseToDelete = ProductSaleElementsQuery::create()
->useProductQuery()
->filterByTemplateId($event->getTemplate()->getId())
->endUse()
->useAttributeCombinationQuery()
->filterByAttributeId($event->getAttributeId())
->endUse()
->select([ ProductSaleElementsTableMap::ID ])
->find();
$currencyId = CurrencyModel::getDefaultCurrency()->getId();
foreach ($pseToDelete->getData() as $pseId) {
$dispatcher->dispatch(
TheliaEvents::PRODUCT_DELETE_PRODUCT_SALE_ELEMENT,
new ProductSaleElementDeleteEvent(
$pseId,
$currencyId
)
);
}
}
/**
* Check if is a product view and if product_id is visible
*
* @param ViewCheckEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function viewCheck(ViewCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->getView() == 'product') {
$product = ProductQuery::create()
->filterById($event->getViewId())
->filterByVisible(1)
->count();
if ($product == 0) {
$dispatcher->dispatch(TheliaEvents::VIEW_PRODUCT_ID_NOT_VISIBLE, $event);
}
}
}
/**
* @param ViewCheckEvent $event
* @throws NotFoundHttpException
*/
public function viewProductIdNotVisible(ViewCheckEvent $event)
{
throw new NotFoundHttpException();
}
/**
* {@inheritDoc}
@@ -812,10 +928,16 @@ class Product extends BaseAction implements EventSubscriberInterface
TheliaEvents::PRODUCT_FEATURE_UPDATE_VALUE => array("updateFeatureProductValue", 128),
TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE => array("deleteFeatureProductValue", 128),
TheliaEvents::TEMPLATE_DELETE_ATTRIBUTE => array("deleteTemplateAttribute", 128),
TheliaEvents::TEMPLATE_DELETE_FEATURE => array("deleteTemplateFeature", 128),
// Those two have to be executed before
TheliaEvents::IMAGE_DELETE => array("deleteImagePSEAssociations", 192),
TheliaEvents::DOCUMENT_DELETE => array("deleteDocumentPSEAssociations", 192),
TheliaEvents::VIEW_CHECK => array('viewCheck', 128),
TheliaEvents::VIEW_PRODUCT_ID_NOT_VISIBLE => array('viewProductIdNotVisible', 128),
);
}
}

View File

@@ -15,24 +15,26 @@ namespace Thelia\Action;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Model\CategoryQuery;
use Thelia\Model\Map\TemplateTableMap;
use Thelia\Model\TemplateQuery;
use Thelia\Model\Template as TemplateModel;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Template\TemplateUpdateEvent;
use Thelia\Core\Event\Template\TemplateCreateEvent;
use Thelia\Core\Event\Template\TemplateDeleteEvent;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Model\ProductQuery;
use Thelia\Core\Event\Template\TemplateAddAttributeEvent;
use Thelia\Core\Event\Template\TemplateDeleteAttributeEvent;
use Thelia\Model\AttributeTemplateQuery;
use Thelia\Model\AttributeTemplate;
use Thelia\Core\Event\Template\TemplateDeleteFeatureEvent;
use Thelia\Core\Event\Template\TemplateAddFeatureEvent;
use Thelia\Model\FeatureTemplateQuery;
use Thelia\Core\Event\Template\TemplateCreateEvent;
use Thelia\Core\Event\Template\TemplateDeleteAttributeEvent;
use Thelia\Core\Event\Template\TemplateDeleteEvent;
use Thelia\Core\Event\Template\TemplateDeleteFeatureEvent;
use Thelia\Core\Event\Template\TemplateDuplicateEvent;
use Thelia\Core\Event\Template\TemplateUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Translation\Translator;
use Thelia\Model\AttributeTemplate;
use Thelia\Model\AttributeTemplateQuery;
use Thelia\Model\CategoryQuery;
use Thelia\Model\FeatureTemplate;
use Thelia\Model\FeatureTemplateQuery;
use Thelia\Model\Map\TemplateTableMap;
use Thelia\Model\ProductQuery;
use Thelia\Model\Template as TemplateModel;
use Thelia\Model\TemplateQuery;
class Template extends BaseAction implements EventSubscriberInterface
{
@@ -46,19 +48,66 @@ class Template extends BaseAction implements EventSubscriberInterface
public function create(TemplateCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$template = new TemplateModel();
$template
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setName($event->getTemplateName())
->save()
;
$event->setTemplate($template);
}
/**
* Dupliucate an existing template entry
*
* @param \Thelia\Core\Event\Template\TemplateCreateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function duplicate(TemplateDuplicateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $source = TemplateQuery::create()->findPk($event->getSourceTemplateId())) {
$source->setLocale($event->getLocale());
$createEvent = new TemplateCreateEvent();
$createEvent
->setLocale($event->getLocale())
->setTemplateName(
Translator::getInstance()->trans("Copy of %tpl", ["%tpl" => $source->getName() ])
);
$dispatcher->dispatch(TheliaEvents::TEMPLATE_CREATE, $createEvent);
$clone = $createEvent->getTemplate();
$attrList = AttributeTemplateQuery::create()->findByTemplateId($source->getId());
/** @var $feat AttributeTemplate */
foreach ($attrList as $feat) {
$dispatcher->dispatch(
TheliaEvents::TEMPLATE_ADD_ATTRIBUTE,
new TemplateAddAttributeEvent($clone, $feat->getAttributeId())
);
}
$featList = FeatureTemplateQuery::create()->findByTemplateId($source->getId());
/** @var $feat FeatureTemplate */
foreach ($featList as $feat) {
$dispatcher->dispatch(
TheliaEvents::TEMPLATE_ADD_FEATURE,
new TemplateAddFeatureEvent($clone, $feat->getFeatureId())
);
}
$event->setTemplate($clone);
}
}
/**
* Change a product template
*
@@ -71,15 +120,15 @@ class Template extends BaseAction implements EventSubscriberInterface
if (null !== $template = TemplateQuery::create()->findPk($event->getTemplateId())) {
$template
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setName($event->getTemplateName())
->save();
->setLocale($event->getLocale())
->setName($event->getTemplateName())
->save();
$event->setTemplate($template);
}
}
/**
* Delete a product template entry
*
@@ -92,54 +141,54 @@ class Template extends BaseAction implements EventSubscriberInterface
{
if (null !== ($template = TemplateQuery::create()->findPk($event->getTemplateId()))) {
// Check if template is used by a product
$product_count = ProductQuery::create()->findByTemplateId($template->getId())->count();
if ($product_count <= 0) {
$productCount = ProductQuery::create()->findByTemplateId($template->getId())->count();
if ($productCount <= 0) {
$con = Propel::getWriteConnection(TemplateTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$template
->setDispatcher($dispatcher)
->delete($con);
// We have to also delete any reference of this template in category tables
// We can't use a FK here, as the DefaultTemplateId column may be NULL
// so let's take care of this.
CategoryQuery::create()
->filterByDefaultTemplateId($event->getTemplateId())
->update([ 'DefaultTemplateId' => null], $con);
$con->commit();
} catch (\Exception $ex) {
$con->rollback();
throw $ex;
}
}
$event->setTemplate($template);
$event->setProductCount($product_count);
$event->setProductCount($productCount);
}
}
public function addAttribute(TemplateAddAttributeEvent $event)
{
if (null === AttributeTemplateQuery::create()
->filterByAttributeId($event->getAttributeId())
->filterByTemplate($event->getTemplate())
->findOne()) {
$attribute_template = new AttributeTemplate();
$attribute_template
$attributeTemplate = new AttributeTemplate();
$attributeTemplate
->setAttributeId($event->getAttributeId())
->setTemplate($event->getTemplate())
->save()
->save()
;
}
}
/**
* Changes position, selecting absolute ou relative change.
*
@@ -151,7 +200,7 @@ class Template extends BaseAction implements EventSubscriberInterface
{
$this->genericUpdatePosition(AttributeTemplateQuery::create(), $event, $dispatcher);
}
/**
* Changes position, selecting absolute ou relative change.
*
@@ -163,19 +212,24 @@ class Template extends BaseAction implements EventSubscriberInterface
{
$this->genericUpdatePosition(FeatureTemplateQuery::create(), $event, $dispatcher);
}
public function deleteAttribute(TemplateDeleteAttributeEvent $event)
public function deleteAttribute(TemplateDeleteAttributeEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$attribute_template = AttributeTemplateQuery::create()
$attributeTemplate = AttributeTemplateQuery::create()
->filterByAttributeId($event->getAttributeId())
->filterByTemplate($event->getTemplate())->findOne()
;
if ($attribute_template !== null) {
$attribute_template->delete();
if ($attributeTemplate !== null) {
$attributeTemplate
->setDispatcher($dispatcher)
->delete();
} else {
// Prevent event propagation
$event->stopPropagation();
}
}
public function addFeature(TemplateAddFeatureEvent $event)
{
if (null === FeatureTemplateQuery::create()
@@ -183,28 +237,33 @@ class Template extends BaseAction implements EventSubscriberInterface
->filterByTemplate($event->getTemplate())
->findOne()
) {
$feature_template = new FeatureTemplate();
$feature_template
->setFeatureId($event->getFeatureId())
->setTemplate($event->getTemplate())
->save()
$featureTemplate = new FeatureTemplate();
$featureTemplate
->setFeatureId($event->getFeatureId())
->setTemplate($event->getTemplate())
->save()
;
}
}
public function deleteFeature(TemplateDeleteFeatureEvent $event)
public function deleteFeature(TemplateDeleteFeatureEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$feature_template = FeatureTemplateQuery::create()
$featureTemplate = FeatureTemplateQuery::create()
->filterByFeatureId($event->getFeatureId())
->filterByTemplate($event->getTemplate())->findOne()
;
if ($feature_template !== null) {
$feature_template->delete();
if ($featureTemplate !== null) {
$featureTemplate
->setDispatcher($dispatcher)
->delete();
} else {
// Prevent event propagation
$event->stopPropagation();
}
}
/**
* {@inheritDoc}
*/
@@ -214,16 +273,16 @@ class Template extends BaseAction implements EventSubscriberInterface
TheliaEvents::TEMPLATE_CREATE => array("create", 128),
TheliaEvents::TEMPLATE_UPDATE => array("update", 128),
TheliaEvents::TEMPLATE_DELETE => array("delete", 128),
TheliaEvents::TEMPLATE_DUPLICATE => array("duplicate", 128),
TheliaEvents::TEMPLATE_ADD_ATTRIBUTE => array("addAttribute", 128),
TheliaEvents::TEMPLATE_DELETE_ATTRIBUTE => array("deleteAttribute", 128),
TheliaEvents::TEMPLATE_ADD_FEATURE => array("addFeature", 128),
TheliaEvents::TEMPLATE_DELETE_FEATURE => array("deleteFeature", 128),
TheliaEvents::TEMPLATE_CHANGE_ATTRIBUTE_POSITION => array('updateAttributePosition', 128),
TheliaEvents::TEMPLATE_CHANGE_FEATURE_POSITION => array('updateFeaturePosition', 128),
);
}
}

View File

@@ -96,8 +96,9 @@ class ContainerAwareCommand extends Command implements ContainerAwareInterface
$requestContext = new RequestContext();
$requestContext->fromRequest($request);
$url = new URL($container);
$url = $container->get('thelia.url.manager');
$url->setRequestContext($requestContext);
$this->getContainer()->get('router.admin')->setContext($requestContext);
}
/**

View File

@@ -71,7 +71,6 @@ class HookCleanCommand extends ContainerAwareCommand
} catch (\Exception $ex) {
$output->writeln(sprintf("<error>%s</error>", $ex->getMessage()));
}
}
private function getModule(InputInterface $input)

View File

@@ -12,10 +12,13 @@
namespace Thelia\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Thelia\Action\Module;
use Thelia\Core\Event\Module\ModuleToggleActivationEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\ModuleQuery;
@@ -47,6 +50,12 @@ class ModuleDeactivateCommand extends BaseModuleGenerate
InputArgument::REQUIRED,
"module to deactivate"
)
->addOption(
"assume-yes",
'y',
InputOption::VALUE_NONE,
'Assume to deactivate a mandatory module'
)
;
}
@@ -67,6 +76,15 @@ class ModuleDeactivateCommand extends BaseModuleGenerate
try {
$event = new ModuleToggleActivationEvent($module->getId());
$module = ModuleQuery::create()->findPk($module->getId());
if ($module->getMandatory() == BaseModule::IS_MANDATORY) {
if (!$this->askConfirmation($input, $output)) {
return;
}
$event->setAssumeDeactivate(true);
}
if ($input->getOption("with-dependencies")) {
$event->setRecursive(true);
}
@@ -84,4 +102,33 @@ class ModuleDeactivateCommand extends BaseModuleGenerate
), "bg=green;fg=black");
}
}
private function askConfirmation(InputInterface $input, OutputInterface $output)
{
$assumeYes = $input->getOption("assume-yes");
$moduleCode = $input->getArgument("module");
if (!$assumeYes) {
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$questionText = "Module ";
$questionText .= (empty($moduleCode))
? ""
: $moduleCode;
$questionText .= " is mandatory.\n";
$questionText .= "Would you like to deactivate the module ";
$questionText .= (empty($moduleCode))
? ""
: $moduleCode;
$questionText .= " ? (yes, or no) ";
$question = new ConfirmationQuestion($questionText, false);
if (!$helper->ask($input, $output, $question)) {
return false;
}
}
return true;
}
}

View File

@@ -38,4 +38,6 @@
-->
<thelia>2.2.0</thelia>
<stability>other</stability>
<mandatory>0</mandatory>
<hidden>0</hidden>
</module>

View File

@@ -99,18 +99,18 @@ class CartContainsCategories extends ConditionAbstract
/** @var Category $category */
foreach ($categories as $category) {
$catecoryInCart = $this->conditionValidator->variableOpComparison(
if (! $this->conditionValidator->variableOpComparison(
$category->getId(),
$this->operators[self::CATEGORIES_LIST],
$this->values[self::CATEGORIES_LIST]
);
if ($catecoryInCart) {
return true;
)) {
// cart item doesn't match go to next cart item
continue 2;
}
}
// cart item match
return true;
}
return false;
}

View File

@@ -103,7 +103,6 @@ class CartContainsProducts extends ConditionAbstract
return true;
}
}
return false;
}

View File

@@ -79,5 +79,4 @@ class MatchForXArticlesIncludeQuantity extends MatchForXArticles
return $toolTip;
}
}

View File

@@ -5,6 +5,7 @@ return array(
' note: only non-visible documents can be associated.' => ' note: only non-visible documents can be associated.',
'"%param" parameter cannot be empty in loop type: %type, name: %name' => '"%param" parameter cannot be empty in loop type: %type, name: %name',
'"%param" parameter is missing in loop type: %type, name: %name' => '"%param" parameter is missing in loop type: %type, name: %name',
'#000000' => '#000000',
'%module (version: %version)' => '%module (version: %version)',
'%n for number, %c for the currency code, %s for the currency symbol' => '%n for number, %c for the currency code, %s for the currency symbol',
'%obj SEO modification' => '%obj SEO modification',
@@ -52,6 +53,7 @@ return array(
'All countries' => 'All countries',
'All shipping methods' => 'All shipping methods',
'Amount' => 'Amount',
'An administrator with thie email address already exists' => 'An administrator with this email address already exists',
'An administrator with this email address already exists' => 'An administrator with this email address already exists',
'An invalid token was provided, your password cannot be changed' => 'An invalid token was provided, your password cannot be changed',
'Apply exchange rates on price in %sym' => 'Apply exchange rates on price in %sym',
@@ -81,6 +83,8 @@ return array(
'By Module' => 'By Module',
'CSS' => 'CSS',
'CSS stylesheet' => 'CSS stylesheet',
'Can\'t deactivate a secure module' => 'Can\'t deactivate a secure module',
'Can\'t remove a core module' => 'Can\'t remove a core module',
'Cannot disable the default language' => 'Cannot disable the default language',
'Cannot find a default country. Please define one.' => 'Cannot find a default country. Please define one.',
'Cannot find the shop country. Please select a shop country.' => 'Cannot find the shop country. Please select a shop country.',
@@ -106,6 +110,7 @@ return array(
'Change password' => 'Change password',
'Check country iso codes <a href="http://en.wikipedia.org/wiki/ISO_3166-1#Current_codes" target="_blank">here</a>.' => 'Check country iso codes <a href="http://en.wikipedia.org/wiki/ISO_3166-1#Current_codes" target="_blank">in Wikipedia</a>.',
'Check the total Cart amount in the given currency' => 'Check the total Cart amount in the given currency',
'Choice a color for this order status code' => 'Choice a color for this order status code',
'City' => 'City',
'Code' => 'Code',
'Combination builder' => 'Combination builder',
@@ -180,6 +185,8 @@ return array(
'Enter here the brand name in the default language (%title%)' => 'Enter here the brand name in the default language (%title%)',
'Enter here the category title in the default language (%title%)' => 'Enter here the category title in the default language (%title%)',
'Enter here the mail template purpose in the default language (%title%)' => 'Enter here the mail template purpose in the default language (%title%)',
'Enter here the order status code' => 'Enter here the order status code',
'Enter here the order status name in the default language (%title%)' => 'Enter here the order status name in the default language (%title%)',
'Enter here the sale name in the default language (%title%)' => 'Enter here the sale name in the default language (%title%)',
'Enter the new password' => 'Enter the new password',
'Enter the new password again' => 'Enter the new password again',
@@ -187,6 +194,8 @@ return array(
'Error during %action process : %error. Exception was %exc' => 'Error during %action process : %error. Exception was %exc',
'Error occured while processing order ref. %ref, ID %id: %err' => 'Error occurred while processing order ref. %ref, ID %id: %err',
'Error occured.' => 'Error occurred.',
'Error occurred while cancelling order ref. %ref, ID %id: %err' => 'Error occurred while cancelling order ref. %ref, ID %id: %err',
'Error occurred while saving payment transaction %transaction_ref for order ID %id.' => 'Error occurred while saving payment transaction %transaction_ref for order ID %id.',
'Error(s) in import&nbsp;:<br />%errors' => 'Error(s) in import&nbsp;:<br />%errors',
'Errors' => 'Errors',
'Export' => 'Export',
@@ -346,6 +355,9 @@ return array(
'Order failed' => 'Order failed',
'Order ref. %ref is now unpaid.' => 'Order ref. %ref is now unpaid.',
'Order ref. %ref, ID %id has been successfully paid.' => 'Order ref. %ref, ID %id has been successfully paid.',
'Order status code' => 'Order status code',
'Order status color' => 'Order status color',
'Order status name' => 'Order status name',
'Orders' => 'Orders',
'Overall' => 'Overall',
'Page 404' => 'Page 404',
@@ -357,6 +369,8 @@ return array(
'Password confirmation' => 'Password confirmation',
'Payment failed' => 'Payment failed',
'Payment gateway' => 'Payment gateway',
'Payment module ID not found' => 'Payment module ID not found',
'Payment transaction %transaction_ref for order ref. %ref, ID %id has been successfully saved.' => 'Payment transaction %transaction_ref for order ref. %ref, ID %id has been successfully saved.',
'Per customer' => 'Per customer',
'Percent' => 'Percent',
'Percentage' => 'Percentage',
@@ -421,6 +435,7 @@ return array(
'Range date Start' => 'Range date Start',
'Rate' => 'Rate',
'Rate from %currencyCode' => 'Rate from %currencyCode',
'Recipient e-mail address' => 'Recipient e-mail address',
'Redirecting ...' => 'Redirecting ...',
'Redirecting to %url' => 'Redirecting to %url',
'Reference' => 'Reference',
@@ -453,6 +468,7 @@ return array(
'Select the product brand, or supplier.' => 'Select the product brand, or supplier.',
'Select the products covered by this operation' => 'Select the products covered by this operation',
'Select the virtual document' => 'Select the virtual document',
'Send test e-mail to:' => 'Send test e-mail to:',
'Service ID' => 'Service ID',
'Shipping configuration' => 'Shipping configuration',
'Shipping zone name' => 'Shipping zone name',
@@ -460,6 +476,8 @@ return array(
'Short description text' => 'Short description text',
'Show redirections *' => 'Show redirections *',
'Sitemap' => 'Sitemap',
'Some commands use this status.' => 'Some commands use this status.',
'Something goes wrong, the message was not sent to recipient. Error is : %err' => 'Something goes wrong, the message was not sent to recipient. Error is : %err',
'Sorry, an error occured.' => 'Sorry, an error occurred.',
'Sorry, an error occured: %msg' => 'Sorry, an error occurred: %msg',
'Sorry, an error occured: %s' => 'Sorry, an error occurred: %s',
@@ -536,6 +554,7 @@ return array(
'The lost admin password recovery feature is disabled.' => 'The lost admin password recovery feature is disabled.',
'The mailing template in HTML format.' => 'The mailing template in HTML format.',
'The mailing template in text-only format.' => 'The mailing template in text-only format.',
'The message has been successfully sent to %recipient.' => 'The message has been successfully sent to %recipient.',
'The method %method% doesn\'t exist in classname %classname%' => 'The method %method% doesn\'t exist in classname %classname%',
'The method name that will handle the hook event.' => 'The method name that will handle the hook event.',
'The module "%name%" is currently in use by at least one order, and can\'t be deleted.' => 'The module "%name%" is currently in use by at least one order, and can\'t be deleted.',
@@ -545,6 +564,8 @@ return array(
'The module has to be activated.' => 'The module has to be activated.',
'The module is not valid : %message' => 'The module is not valid : %message',
'The module zip file' => 'The module zip file',
'The order status code' => 'The order status code',
'The order status name or title' => 'The order status name or title',
'The product document id %id doesn\'t exists' => 'The product document id %id doesn\'t exists',
'The product image id %id doesn\'t exists' => 'The product image id %id doesn\'t exists',
'The product sale element id %id doesn\'t exist' => 'The product sale element id %id doesn\'t exist',
@@ -574,6 +595,7 @@ return array(
'This administrator login already exists' => 'This administrator login already exists',
'This brand is online' => 'This brand is online',
'This category is online' => 'This category is online',
'This code is already used.' => 'This code is already used.',
'This condition is always true' => 'This condition is always true',
'This content is online.' => 'This content is online.',
'This country has states / provinces' => 'This country has states / provinces',
@@ -596,6 +618,8 @@ return array(
'This image is online' => 'This image is online',
'This is a comma separated list of email addresses where store notifications (such as order placed) are sent.' => 'This is a comma separated list of email addresses where store notifications (such as order placed) are sent.',
'This is an identifier that will be used in the code to get this message' => 'This is an identifier that will be used in the code to get this message',
'This is not a hexadecimal color.' => 'This is not a hexadecimal color.',
'This is not a valid code.' => 'This is not a valid code.',
'This is the contact email address, and the sender email of all e-mails sent by your store.' => 'This is the contact email address, and the sender email of all e-mails sent by your store.',
'This is the message purpose, such as \'Order confirmation\'.' => 'This is the message purpose, such as \'Order confirmation\'.',
'This is the name used on the login screen' => 'This is the name used on the login screen',
@@ -608,6 +632,7 @@ return array(
'This product_sale_elements_id does not exists for this product : %d' => 'This product_sale_elements_id does not exists for this product : %d',
'This state doesn\'t belong to this country.' => 'This state doesn\'t belong to this country.',
'This state is online' => 'This state is online',
'This status is protected.' => 'This status is protected.',
'This template is in use in some of your products, and cannot be deleted. Delete it from all your products and try again.' => 'This template is in use in some of your products, and cannot be deleted. Delete it from all your products and try again.',
'This the unique name of this message. Do not change this value unless you understand what you do.' => 'This the unique name of this message. Do not change this value unless you understand what you do.',
'This value should not be blank.' => 'This value should not be blank.',
@@ -654,6 +679,7 @@ return array(
'Wrong form method, %s expected.' => 'Wrong form method, %s expected.',
'Yes, I have a password :' => 'Yes, I have a password :',
'You are already registered!' => 'You are already registered!',
'You can not delete it.' => 'You can not delete it.',
'You don\'t need to use commas or other punctuations.' => 'You don\'t need to use commas or other punctuations.',
'You have to configure your store email first !' => 'You have to configure your store email first !',
'You must select at least one attribute.' => 'You must select at least one attribute.',

View File

@@ -1,6 +1,6 @@
<?php
return [
return array(
' content create form' => 'Formulaire de création de contenu',
' note: only non-visible documents can be associated.' => 'remarque : seuls ces modules. les documents non visibles peuvent être associés.',
'"%param" parameter cannot be empty in loop type: %type, name: %name' => 'Le paramètre "%param" ne peut être vide dans la boucle type: %type, nom: %name ',
@@ -70,6 +70,8 @@ return [
'Available shipping zones' => 'Zones de livraison disponibles',
'Back Office' => 'Back Office',
'Bad tax list JSON' => 'Mauvais JSON de la liste des taxes',
'Banner' => 'Bannière',
'Banner of the website. Used in the e-mails send to the customers.' => 'Bannière du site utilisée dans le contenu des mails envoyés aux clients.',
'Billing country' => 'Pays de facturation',
'Billing country is' => 'Le pays de facturation est',
'Brand' => 'Marque',
@@ -80,6 +82,8 @@ return [
'By Module' => 'Par module',
'CSS' => 'CSS',
'CSS stylesheet' => 'Feuille de style CSS',
'Can\'t deactivate a secure module' => 'Impossible de désactiver un module sécurisé',
'Can\'t remove a core module' => 'Impossible de supprimer un module de base',
'Cannot disable the default language' => 'Impossible de désactiver la langue par défaut',
'Cannot find a default country. Please define one.' => 'Impossible de trouver un pays par défaut. Veuillez en définir un.',
'Cannot find the shop country. Please select a shop country.' => 'Impossible de trouver le pays du magasin. Veuillez en sélectionner un.',
@@ -203,6 +207,7 @@ return [
'Failed to open translation file %file. Please be sure that this file is writable by your Web server' => 'L\'ouverture du fichier %file a échoué. Merci de vérifier que ce fichier est accessible en écriture pour votre serveur Web.',
'Failed to send message %code. Failed recipients: %failed_addresses' => 'Erreur lors de l\'envoi du message %code. Echec pour les destinataires %failed_addresses',
'Failed to update language definition: %ex' => 'Erreur lors de la mise à jour de la définition de la langue : %ex',
'Favicon image' => 'Icône du site',
'Fax' => 'Fax',
'Feature' => 'Caractéristique',
'Feature value does not match FLOAT format' => 'valeur de caractéristique n\'est pas un FLOAT',
@@ -240,6 +245,7 @@ return [
'Hooks' => 'Points d\'accroche',
'Host' => 'Nom de l\'hôte',
'I would like to receive the newsletter or the latest news.' => 'Je souhaite recevoir la lettre d\'information ou les dernières actualités.',
'Icon of the website. Only PNG and ICO files are allowed.' => 'Icône du site. Seuls les fichiers au format PNG ou ICO sont autorisés.',
'ISO 4217 code' => 'Code ISO 4217',
'ISO 639-1 Code' => 'Code ISO 639-1',
'ISO Alpha-2 code' => 'Code ISO Alpha-2',
@@ -355,6 +361,7 @@ return [
'Password confirmation' => 'Confirmation du mot de passe.',
'Payment failed' => 'Echec du paiement',
'Payment gateway' => 'Passerelle de paiement',
'Payment module ID not found' => 'ID du module de paiement non trouvé',
'Per customer' => 'Par client',
'Percent' => 'Pourcent',
'Percentage' => 'Pourcentage',
@@ -419,6 +426,7 @@ return [
'Range date Start' => 'Date de début',
'Rate' => 'Taux',
'Rate from %currencyCode' => 'Taux pour %currencyCode',
'Recipient e-mail address' => 'Adresse e-mail du destinataire',
'Redirecting ...' => 'Redirection ...',
'Redirecting to %url' => 'Redirection vers %url',
'Reference' => 'Référence',
@@ -451,6 +459,7 @@ return [
'Select the product brand, or supplier.' => 'Choisissez la marque ou le fournisseur du produit.',
'Select the products covered by this operation' => 'Produits inclus dans cette promotion',
'Select the virtual document' => 'Sélectionnez le document virtuel',
'Send test e-mail to:' => 'Envoyer un email de test à: ',
'Service ID' => 'ID du service',
'Shipping configuration' => 'Configuration du transport',
'Shipping zone name' => 'Nom de la zone de livraison',
@@ -478,6 +487,7 @@ return [
'Store configuration failed.' => 'Erreur de configuration du magasin.',
'Store description' => 'Description du magasin',
'Store email address' => 'Adresse mail du magasin',
'Store logo' => 'Logo de la boutique',
'Store logs into text file' => 'Conserver les logs dans des fichiers texte',
'Store logs into text file, up to a certian size, then a new file is created' => 'Sauvegarder les logs dans un fichier texte. A partir d\'une certaine taille un nouveau fichier est créé',
'Store name' => 'Nom du magasin',
@@ -535,7 +545,7 @@ return [
'The method %method% doesn\'t exist in classname %classname%' => 'La méthode %method% n\'existe pas dans la classe %classname%',
'The method name that will handle the hook event.' => 'Le nom de la méthode qui va traiter l\'évènement du point d\'accroche.',
'The module "%name%" is currently in use by at least one order, and can\'t be deleted.' => 'Le module "%name%" est utilisé par au moins une commande, et ne peut être supprimé.',
'The module %module has been installed successfully.' => 'Le module %module a été activé avec succès.',
'The module %module has been installed successfully.' => 'Le module %module a été installé avec succès.',
'The module %name is already installed in the same or greater version.' => 'Le module %name est déja installé dans la même version, ou dans une version plus récente.',
'The module %name requires Thelia %version or newer' => 'Le module %name nécessite Thelia %version ou plus récent',
'The module has to be activated.' => 'Le module doit être activé.',
@@ -820,4 +830,4 @@ return [
'update form' => 'Formulaire de modification',
'value table header' => 'colonne tableau valeur',
'value table row' => 'ligne tableau valeurs',
];
);

View File

@@ -28,6 +28,10 @@
<tag name="kernel.event_subscriber"/>
</service>
<service id="thelia.action.order_status" class="Thelia\Action\OrderStatus">
<tag name="kernel.event_subscriber"/>
</service>
<service id="thelia.action.coupon" class="Thelia\Action\Coupon">
<argument type="service" id="request_stack"/>
<argument type="service" id="thelia.coupon.factory"/>
@@ -171,6 +175,7 @@
</service>
<service id="thelia.action.cache" class="Thelia\Action\Cache">
<argument type="service" id="thelia.cache"/>
<tag name="kernel.event_subscriber"/>
</service>

View File

@@ -63,6 +63,7 @@
<parameter key="HOOK">admin.hook</parameter>
<parameter key="MODULE_HOOK">admin.module-hook</parameter>
<parameter key="ORDER">admin.order</parameter>
<parameter key="ORDER_STATUS">admin.configuration.order-status</parameter>
<parameter key="PRODUCT">admin.product</parameter>
<parameter key="PROFILE">admin.configuration.profile</parameter>
<parameter key="SHIPPING_ZONE">admin.configuration.shipping-zone</parameter>
@@ -90,6 +91,9 @@
<!-- Thelia logger class -->
<parameter key="thelia.logger.class">Thelia\Log\Tlog</parameter>
<!-- Thelia Cache Config -->
<parameter key="thelia.cache.namespace">thelia_cache</parameter>
</parameters>
@@ -352,7 +356,6 @@
<argument>%admin.resources%</argument>
</service>
<!-- Handlers -->
<service id="thelia.export.handler" class="Thelia\Handler\ExportHandler">
<argument type="service" id="event_dispatcher" />
@@ -365,5 +368,12 @@
<argument type="service" id="thelia.archiver.manager" />
<argument type="service" id="service_container" />
</service>
<!-- Cache -->
<service id="thelia.cache" class="Symfony\Component\Cache\Adapter\FilesystemAdapter">
<argument>%thelia.cache.namespace%</argument>
<argument>600</argument>
<argument>%kernel.cache_dir%</argument>
</service>
</services>
</config>

View File

@@ -74,6 +74,7 @@
<form name="thelia.admin.message.creation" class="Thelia\Form\MessageCreationForm"/>
<form name="thelia.admin.message.modification" class="Thelia\Form\MessageModificationForm"/>
<form name="thelia.admin.message.send-sample" class="Thelia\Form\MessageSendSampleForm"/>
<form name="thelia.admin.currency.creation" class="Thelia\Form\CurrencyCreationForm"/>
<form name="thelia.admin.currency.modification" class="Thelia\Form\CurrencyModificationForm"/>
@@ -162,6 +163,9 @@
<form name="thelia_api_create" class="Thelia\Form\Api\ApiCreateForm"/>
<form name="thelia_api_update" class="Thelia\Form\Api\ApiUpdateForm"/>
<form name="thelia.admin.order-status.creation" class="Thelia\Form\OrderStatus\OrderStatusCreationForm"/>
<form name="thelia.admin.order-status.modification" class="Thelia\Form\OrderStatus\OrderStatusModificationForm"/>
</forms>
</config>

View File

@@ -15,8 +15,9 @@
<services>
<service id="thelia.listener.view" class="Thelia\Core\EventListener\ViewListener">
<tag name="kernel.event_subscriber"/>
<argument type="service" id="service_container"/>
<argument type="service" id="event_dispatcher"/>
<tag name="kernel.event_subscriber"/>
</service>
<service id="controller.default" class="Thelia\Controller\DefaultController"/>

View File

@@ -173,7 +173,7 @@
<route id="admin.customer.update.process" path="/admin/customer/save">
<default key="_controller">Thelia\Controller\Admin\CustomerController::processUpdateAction</default>
</route>
</route>
<route id="admin.customer.delete" path="/admin/customer/delete">
<default key="_controller">Thelia\Controller\Admin\CustomerController::deleteAction</default>
@@ -252,6 +252,36 @@
<!-- end order management -->
<!-- order status management -->
<route id="admin.order-status.default" path="/admin/configuration/order-status">
<default key="_controller">Thelia\Controller\Admin\OrderStatusController::defaultAction</default>
</route>
<route id="admin.order-status.create" path="/admin/configuration/order-status/create">
<default key="_controller">Thelia\Controller\Admin\OrderStatusController::createAction</default>
</route>
<route id="admin.order-status.update" path="/admin/configuration/order-status/update/{order_status_id}">
<default key="_controller">Thelia\Controller\Admin\OrderStatusController::updateAction</default>
<requirement key="order_status_id">\d+</requirement>
</route>
<route id="admin.order-status.save" path="/admin/configuration/order-status/save/{order_status_id}">
<default key="_controller">Thelia\Controller\Admin\OrderStatusController::processUpdateAction</default>
<requirement key="order_status_id">\d+</requirement>
</route>
<route id="admin.order-status.delete" path="/admin/configuration/order-status/delete">
<default key="_controller">Thelia\Controller\Admin\OrderStatusController::deleteAction</default>
</route>
<route id="admin.order-status.update-position" path="/admin/configuration/order-status/update-position">
<default key="_controller">Thelia\Controller\Admin\OrderStatusController::updatePositionAction</default>
</route>
<!-- end order status management -->
<!-- Categories management -->
<route id="admin.categories.default" path="/admin/categories">
@@ -725,6 +755,10 @@
<default key="_controller">Thelia\Controller\Admin\TemplateController::deleteAction</default>
</route>
<route id="admin.configuration.templates.duplicate" path="/admin/configuration/templates/duplicate">
<default key="_controller">Thelia\Controller\Admin\TemplateController::duplicateAction</default>
</route>
<route id="admin.configuration.templates.features.list" path="/admin/configuration/templates/features/list">
<default key="_controller">Thelia\Controller\Admin\TemplateController::getAjaxFeaturesAction</default>
</route>
@@ -1544,6 +1578,11 @@
<requirement key="messageId">\d+</requirement>
</route>
<route id="admin.email.test_send" path="/admin/message/send/{messageId}" methods="post">
<default key="_controller">Thelia:Admin\Message:sendSampleByEmail</default>
<requirement key="messageId">\d+</requirement>
</route>
<!-- The default route, to display a template -->
<route id="admin.processTemplate" path="/admin/{template}">

View File

@@ -366,8 +366,6 @@ class BaseAdminController extends BaseController
// Add the template standard extension
$templateName .= '.html';
$session = $this->getSession();
// Find the current edit language ID
$edition_language = $this->getCurrentEditionLang();
@@ -376,16 +374,9 @@ class BaseAdminController extends BaseController
// Prepare common template variables
$args = array_merge($args, array(
'locale' => $session->getLang()->getLocale(),
'lang_code' => $session->getLang()->getCode(),
'lang_id' => $session->getLang()->getId(),
'edit_language_id' => $edition_language->getId(),
'edit_language_locale' => $edition_language->getLocale(),
'edit_currency_id' => $edition_currency->getId(),
'current_url' => $this->getRequest()->getUri()
));
// Update the current edition language & currency in session

View File

@@ -43,6 +43,29 @@ class ConfigStoreController extends BaseAdminController
return $this->renderTemplate();
}
protected function getAndWriteStoreMediaFileInConfig($form, $inputName, $configKey, $storeMediaUploadDir)
{
$file = $form->get($inputName)->getData();
if ($file != null) {
// Delete the old file
$fs = new \Symfony\Component\Filesystem\Filesystem();
$oldFileName = ConfigQuery::read($configKey);
if ($oldFileName !== null) {
$oldFilePath = $storeMediaUploadDir . DS . $oldFileName;
if ($fs->exists($oldFilePath)) {
$fs->remove($oldFilePath);
}
}
// Write the new file
$newFileName = uniqid() . '-' . $file->getClientOriginalName();
$file->move($storeMediaUploadDir, $newFileName);
ConfigQuery::write($configKey, $newFileName, false);
}
}
public function saveAction()
{
if (null !== $response = $this->checkAuth(AdminResources::STORE, array(), AccessManager::UPDATE)) {
@@ -56,11 +79,33 @@ class ConfigStoreController extends BaseAdminController
try {
$form = $this->validateForm($configStoreForm);
$storeMediaUploadDir = ConfigQuery::read('images_library_path');
if ($storeMediaUploadDir === null) {
$storeMediaUploadDir = THELIA_LOCAL_DIR . 'media' . DS . 'images';
} else {
$storeMediaUploadDir = THELIA_ROOT . $storeMediaUploadDir;
}
$storeMediaUploadDir .= DS . 'store';
// List of medias that can be uploaded through this form.
// [Name of the form input] => [Key in the config table]
$storeMediaList = [
'favicon_file' => 'favicon_file',
'logo_file' => 'logo_file',
'banner_file' => 'banner_file'
];
foreach ($storeMediaList as $input_name => $config_key) {
$this->getAndWriteStoreMediaFileInConfig($form, $input_name, $config_key, $storeMediaUploadDir);
}
$data = $form->getData();
// Update store
foreach ($data as $name => $value) {
if (! $configStoreForm->isTemplateDefinedHiddenFieldName($name)) {
if (!array_key_exists($name, $storeMediaList) && !$configStoreForm->isTemplateDefinedHiddenFieldName($name)) {
ConfigQuery::write($name, $value, false);
}
}

View File

@@ -127,7 +127,6 @@ class CountryController extends AbstractCrudController
;
return $event;
}
protected function hydrateEvent($event, $formData)

View File

@@ -647,7 +647,7 @@ class CouponController extends BaseAdminController
$this->pageNotFound();
}
$response = new ResponseRest($couponManager->drawBackOfficeInputs());
$response = new ResponseRest($couponManager->drawBackOfficeInputs(), 'text');
} else {
// Return an empty response if the service ID is not defined
// Typically, when the user chooses "Please select a coupon type"
@@ -815,7 +815,7 @@ class CouponController extends BaseAdminController
protected function getDefaultDateFormat()
{
return LangQuery::create()->findOneByByDefault(true)->getDateFormat();
return LangQuery::create()->findOneByByDefault(true)->getDatetimeFormat();
}
/**

View File

@@ -205,7 +205,6 @@ class CurrencyController extends AbstractCrudController
'undefined_rates' => $event->getUndefinedRates()
]);
}
} catch (\Exception $ex) {
// Any error
return $this->errorPage($ex);

View File

@@ -435,7 +435,6 @@ class LangController extends BaseAdminController
),
$changedObject->getId()
);
} catch (\Exception $e) {
Tlog::getInstance()->error(sprintf("Error on changing languages with message : %s", $e->getMessage()));
$errorMessage = $e->getMessage();

View File

@@ -22,7 +22,7 @@ use Thelia\Model\ConfigQuery;
class MailingSystemController extends BaseAdminController
{
const RESOURCE_CODE = "admin.mailing-system";
const RESOURCE_CODE = "admin.configuration.mailing-system";
public function defaultAction()
{
@@ -130,7 +130,7 @@ class MailingSystemController extends BaseAdminController
$htmlMessage = "<p>$message</p>";
$instance = \Swift_Message::newInstance()
$instance = $this->getMailer()->getMessageInstance()
->addTo($emailTest, $storeName)
->addFrom($contactEmail, $storeName)
->setSubject($message)

View File

@@ -22,6 +22,7 @@ use Thelia\Core\Security\AccessManager;
use Thelia\Core\Security\Resource\AdminResources;
use Thelia\Core\Template\TemplateDefinition;
use Thelia\Form\Definition\AdminForm;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Message;
use Thelia\Model\MessageQuery;
use Thelia\Model\Module;
@@ -48,35 +49,35 @@ class MessageController extends AbstractCrudController
null // No position update
);
}
protected function getCreationForm()
{
return $this->createForm(AdminForm::MESSAGE_CREATION);
}
protected function getUpdateForm()
{
return $this->createForm(AdminForm::MESSAGE_MODIFICATION);
}
protected function getCreationEvent($formData)
{
$createEvent = new MessageCreateEvent();
$createEvent
->setMessageName($formData['name'])
->setLocale($formData["locale"])
->setTitle($formData['title'])
->setSecured($formData['secured'] ? true : false)
;
;
return $createEvent;
}
protected function getUpdateEvent($formData)
{
$changeEvent = new MessageUpdateEvent($formData['id']);
// Create and dispatch the change event
$changeEvent
->setMessageName($formData['name'])
@@ -91,20 +92,20 @@ class MessageController extends AbstractCrudController
->setHtmlMessage($formData['html_message'])
->setTextMessage($formData['text_message'])
;
return $changeEvent;
}
protected function getDeleteEvent()
{
return new MessageDeleteEvent($this->getRequest()->get('message_id'));
}
protected function eventContainsObject($event)
{
return $event->hasMessage();
}
protected function hydrateObjectForm($object)
{
// Prepare the data that will hydrate the form
@@ -117,34 +118,34 @@ class MessageController extends AbstractCrudController
'subject' => $object->getSubject(),
'html_message' => $object->getHtmlMessage(),
'text_message' => $object->getTextMessage(),
'html_layout_file_name' => $object->getHtmlLayoutFileName(),
'html_template_file_name' => $object->getHtmlTemplateFileName(),
'text_layout_file_name' => $object->getTextLayoutFileName(),
'text_template_file_name' => $object->getTextTemplateFileName(),
);
// Setup the object form
return $this->createForm(AdminForm::MESSAGE_MODIFICATION, "form", $data);
}
protected function getObjectFromEvent($event)
{
return $event->hasMessage() ? $event->getMessage() : null;
}
protected function getExistingObject()
{
$message = MessageQuery::create()
->findOneById($this->getRequest()->get('message_id', 0));
->findOneById($this->getRequest()->get('message_id', 0));
if (null !== $message) {
$message->setLocale($this->getCurrentEditionLocale());
}
return $message;
}
/**
* @param Message $object
* @return string
@@ -153,7 +154,7 @@ class MessageController extends AbstractCrudController
{
return $object->getName();
}
/**
* @param Message $object
* @return int
@@ -162,30 +163,30 @@ class MessageController extends AbstractCrudController
{
return $object->getId();
}
protected function renderListTemplate($currentOrder)
{
return $this->render('messages');
}
protected function listDirectoryContent($requiredExtension)
{
$list = array();
$dir = $this->getTemplateHelper()->getActiveMailTemplate()->getAbsolutePath();
$finder = Finder::create()->files()->in($dir)->ignoreDotFiles(true)->sortByName()->name("*.$requiredExtension");
foreach ($finder as $file) {
$list[] = $file->getBasename();
}
// Add modules templates
$modules = ModuleQuery::getActivated();
/** @var Module $module */
foreach ($modules as $module) {
$dir = $module->getAbsoluteTemplateBasePath() . DS . TemplateDefinition::EMAIL_SUBDIR . DS . 'default';
if (file_exists($dir)) {
$finder = Finder::create()
->files()
@@ -193,7 +194,7 @@ class MessageController extends AbstractCrudController
->ignoreDotFiles(true)
->sortByName()
->name("*.$requiredExtension");
foreach ($finder as $file) {
$fileName = $file->getBasename();
if (!in_array($fileName, $list)) {
@@ -202,20 +203,20 @@ class MessageController extends AbstractCrudController
}
}
}
return $list;
}
protected function renderEditionTemplate()
{
return $this->render('message-edit', array(
'message_id' => $this->getRequest()->get('message_id'),
'layout_list' => $this->listDirectoryContent('tpl'),
'html_template_list' => $this->listDirectoryContent('html'),
'text_template_list' => $this->listDirectoryContent('txt'),
'message_id' => $this->getRequest()->get('message_id'),
'layout_list' => $this->listDirectoryContent('tpl'),
'html_template_list' => $this->listDirectoryContent('html'),
'text_template_list' => $this->listDirectoryContent('txt'),
));
}
protected function redirectToEditionTemplate()
{
return $this->generateRedirectFromRoute(
@@ -225,47 +226,95 @@ class MessageController extends AbstractCrudController
]
);
}
protected function redirectToListTemplate()
{
return $this->generateRedirectFromRoute('admin.configuration.messages.default');
}
public function previewAction($messageId, $html = true)
{
if (null !== $response = $this->checkAuth(AdminResources::MESSAGE, [], AccessManager::VIEW)) {
return $response;
}
if (null === $message = MessageQuery::create()->findPk($messageId)) {
$this->pageNotFound();
}
$parser = $this->getParser($this->getTemplateHelper()->getActiveMailTemplate());
foreach ($this->getRequest()->query->all() as $key => $value) {
$parser->assign($key, $value);
}
if ($html) {
$content = $message->setLocale($this->getCurrentEditionLocale())->getHtmlMessageBody($parser);
} else {
$content = $message->setLocale($this->getCurrentEditionLocale())->getTextMessageBody($parser);
}
return new Response($content);
}
public function previewAsHtmlAction($messageId)
{
return $this->previewAction($messageId);
}
public function previewAsTextAction($messageId)
{
$response = $this->previewAction($messageId, false);
$response->headers->add(["Content-Type" => "text/plain"]);
return $response;
}
public function sendSampleByEmailAction($messageId)
{
if (null !== $response = $this->checkAuth(AdminResources::MESSAGE, [], AccessManager::VIEW)) {
return $response;
}
if (null !== $message = MessageQuery::create()->findPk($messageId)) {
// Ajax submission: prevent CRSF control, as page is not refreshed
$baseForm = $this->createForm(AdminForm::MESSAGE_SEND_SAMPLE, 'form', [], ['csrf_protection' => false]);
try {
$form = $this->validateForm($baseForm, "POST");
$data = $form->getData();
$messageParameters = [];
foreach ($this->getRequest()->request->all() as $key => $value) {
$messageParameters[$key] = $value;
}
$this->getMailer()->sendEmailMessage(
$message->getName(),
[ConfigQuery::getStoreEmail() => ConfigQuery::getStoreName()],
[ $data['recipient_email'] => $data['recipient_email'] ],
$messageParameters,
$this->getCurrentEditionLocale()
);
return new Response(
$this->getTranslator()->trans(
"The message has been successfully sent to %recipient.",
[ '%recipient' => $data['recipient_email'] ]
)
);
} catch (\Exception $ex) {
return new Response(
$this->getTranslator()->trans(
"Something goes wrong, the message was not sent to recipient. Error is : %err",
[ '%err' => $ex->getMessage() ]
)
);
}
} else {
return $this->pageNotFound();
}
}
}

View File

@@ -63,7 +63,7 @@ class OrderController extends BaseAdminController
$order = OrderQuery::create()->findPk($order_id);
$statusId = $this->getRequest()->request->get("status_id");
$statusId = $this->getRequest()->get("status_id");
$status = OrderStatusQuery::create()->findPk($statusId);
if (null === $order) {

View File

@@ -329,7 +329,6 @@ class ProductController extends AbstractSeoCrudController
$this->appendValue($combinationPseData, "isdefault", $saleElement->getIsDefault() > 0 ? 1 : 0);
$this->appendValue($combinationPseData, "ean_code", $saleElement->getEanCode());
}
}
$defaultPseForm = $this->createForm(AdminForm::PRODUCT_DEFAULT_SALE_ELEMENT_UPDATE, "form", $defaultPseData);

View File

@@ -81,7 +81,7 @@ class SaleController extends AbstractCrudController
/** @var SaleProduct $saleProduct */
foreach ($saleProducts as $saleProduct) {
$categories[] = $saleProduct->getProduct()->getDefaultCategoryId();
$products[] = $saleProduct->getProduct()->getId();
$products[$saleProduct->getProduct()->getId()] = $saleProduct->getProduct()->getId();
}
$dateFormat = SaleModificationForm::PHP_DATE_FORMAT;

View File

@@ -185,7 +185,6 @@ class SessionController extends BaseAdminController
$this->getSession()->set(self::ADMIN_TOKEN_SESSION_VAR_NAME, null);
return $this->generateSuccessRedirect($adminCreatePasswordForm);
} catch (FormValidationException $ex) {
// Validation problem
$message = $this->createStandardFormValidationErrorMessage($ex);

View File

@@ -241,5 +241,4 @@ class StateController extends AbstractCrudController
{
return new StateToggleVisibilityEvent($this->getExistingObject());
}
}

View File

@@ -332,7 +332,6 @@ class TaxRuleController extends AbstractCrudController
);
return $this->jsonResponse(json_encode($responseData));
} catch (FormValidationException $ex) {
// Form cannot be validated
$error_msg = $this->createStandardFormValidationErrorMessage($ex);
@@ -411,7 +410,6 @@ class TaxRuleController extends AbstractCrudController
$taxRuleCountry = $taxRuleCountries->getNext();
}
}
$data = [

View File

@@ -18,8 +18,10 @@ use Thelia\Core\Event\Template\TemplateCreateEvent;
use Thelia\Core\Event\Template\TemplateDeleteAttributeEvent;
use Thelia\Core\Event\Template\TemplateDeleteEvent;
use Thelia\Core\Event\Template\TemplateDeleteFeatureEvent;
use Thelia\Core\Event\Template\TemplateDuplicateEvent;
use Thelia\Core\Event\Template\TemplateUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\HttpFoundation\Request;
use Thelia\Core\Security\AccessManager;
use Thelia\Core\Security\Resource\AdminResources;
use Thelia\Form\Definition\AdminForm;
@@ -157,13 +159,18 @@ class TemplateController extends AbstractCrudController
)
);
}
protected function redirectToEditionTemplate()
/**
* @param Request $request
* @param int $id
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function redirectToEditionTemplate($request = null, $id = null)
{
return $this->generateRedirectFromRoute(
"admin.configuration.templates.update",
[
'template_id' => $this->getRequest()->get('template_id'),
'template_id' => $id ?: $this->getRequest()->get('template_id'),
]
);
}
@@ -195,6 +202,33 @@ class TemplateController extends AbstractCrudController
return null;
}
public function duplicateAction()
{
// Check current user authorization
if (null !== $response = $this->checkAuth(AdminResources::TEMPLATE, array(), AccessManager::CREATE)) {
return $response;
}
$template_id = intval($this->getRequest()->get('template_id'));
if ($template_id > 0) {
try {
$event = new TemplateDuplicateEvent($template_id, $this->getCurrentEditionLocale());
$this->dispatch(TheliaEvents::TEMPLATE_DUPLICATE, $event);
if ($event->hasTemplate()) {
$template_id = $event->getTemplate()->getId();
}
} catch (\Exception $ex) {
// Any error
return $this->errorPage($ex);
}
}
return $this->redirectToEditionTemplate(null, $template_id);
}
public function getAjaxFeaturesAction()
{
return $this->render(

View File

@@ -126,18 +126,7 @@ class BaseFrontController extends BaseController
// Add the template standard extension
$templateName .= '.html';
$session = $this->getSession();
// Prepare common template variables
$args = array_merge($args, array(
'locale' => $session->getLang()->getLocale(),
'lang_code' => $session->getLang()->getCode(),
'lang_id' => $session->getLang()->getId(),
'current_url' => $this->getRequest()->getUri()
));
// Render the template.
$data = $this->getParser($templateDir)->render($templateName, $args);
return $data;

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

View File

@@ -85,7 +85,6 @@ class CouponFactory
// Check coupon usage count
if (! $couponModel->isUsageUnlimited()) {
if (null === $customer = $this->facade->getCustomer()) {
throw new UnmatchableConditionException($couponCode);
}

View File

@@ -219,13 +219,10 @@ class CouponManager
/** @var CouponInterface $coupon */
foreach ($coupons as $coupon) {
try {
if ($coupon->isMatching()) {
$couponsKept[] = $coupon;
}
} catch (UnmatchableConditionException $e) {
// ignore unmatchable coupon
continue;
@@ -319,10 +316,8 @@ class CouponManager
public function decrementQuantity(Coupon $coupon, $customerId = null)
{
if ($coupon->isUsageUnlimited()) {
$ret = true;
return true;
} else {
$ret = false;
try {
$usageLeft = $coupon->getUsagesLeft($customerId);
@@ -355,15 +350,13 @@ class CouponManager
->save()
;
$ret = $usageLeft - $newCount;
return $usageLeft - $newCount;
} else {
$usageLeft--;
$coupon->setMaxUsage($usageLeft);
$coupon->setMaxUsage(--$usageLeft);
$coupon->save();
$ret = $usageLeft;
return $usageLeft;
}
}
} catch (\Exception $ex) {
@@ -372,6 +365,58 @@ class CouponManager
}
}
return $ret;
return false;
}
/**
* Add a coupon usage, for the case the related order is canceled.
*
* @param Coupon $coupon
* @param int $customerId
*/
public function incrementQuantity(Coupon $coupon, $customerId = null)
{
if ($coupon->isUsageUnlimited()) {
return true;
} else {
try {
$usageLeft = $coupon->getUsagesLeft($customerId);
// If the coupon usage is per user, remove an entry from coupon customer usage count table
if ($coupon->getPerCustomerUsageCount()) {
if (null === $customerId) {
throw new \LogicException("Customer should not be null at this time.");
}
$ccc = CouponCustomerCountQuery::create()
->filterByCouponId($coupon->getId())
->filterByCustomerId($customerId)
->findOne()
;
if ($ccc !== null && $ccc->getCount() > 0) {
$newCount = $ccc->getCount() - 1;
$ccc
->setCount($newCount)
->save();
return $usageLeft - $newCount;
}
} else {
// Ad one usage to coupon
$coupon->setMaxUsage(++$usageLeft);
$coupon->save();
return $usageLeft;
}
} catch (\Exception $ex) {
// Just log the problem.
Tlog::getInstance()->addError(sprintf("Failed to increment coupon %s: %s", $coupon->getCode(), $ex->getMessage()));
}
}
return false;
}
}

View File

@@ -14,7 +14,6 @@ namespace Thelia\Coupon\Type;
use Thelia\Coupon\FacadeInterface;
use Thelia\Model\CartItem;
use Thelia\Model\Category;
/**
* Allow to remove an amount from the checkout total
@@ -27,7 +26,7 @@ abstract class AbstractRemove extends CouponAbstract implements AmountAndPercent
/**
* Set the value of specific coupon fields.
*
* @param Array $effects the Coupon effects params
* @param array $effects the Coupon effects params
*/
abstract public function setFieldsValue($effects);
@@ -81,35 +80,6 @@ abstract class AbstractRemove extends CouponAbstract implements AmountAndPercent
return $this;
}
/**
* @inheritdoc
*/
public function exec()
{
// This coupon subtracts the specified amount from the order total
// for each product of the selected categories.
$discount = 0;
$cartItems = $this->facade->getCart()->getCartItems();
/** @var CartItem $cartItem */
foreach ($cartItems as $cartItem) {
if (! $cartItem->getPromo() || $this->isAvailableOnSpecialOffers()) {
$categories = $cartItem->getProduct()->getCategories();
/** @var Category $category */
foreach ($categories as $category) {
if (in_array($category->getId(), $this->category_list)) {
$discount += $this->getCartItemDiscount($cartItem);
break;
}
}
}
}
return $discount;
}
/**
* @inheritdoc

View File

@@ -33,7 +33,7 @@ abstract class AbstractRemoveOnAttributeValues extends CouponAbstract implements
/**
* Set the value of specific coupon fields.
* @param Array $effects the Coupon effects params
* @param array $effects the Coupon effects params
*/
abstract public function setFieldsValue($effects);

View File

@@ -15,6 +15,7 @@ namespace Thelia\Coupon\Type;
use Thelia\Core\Translation\Translator;
use Thelia\Coupon\FacadeInterface;
use Thelia\Model\CartItem;
use Thelia\Model\Category;
/**
* Allow to remove an amount from the checkout total
@@ -31,7 +32,7 @@ abstract class AbstractRemoveOnCategories extends CouponAbstract implements Amou
/**
* Set the value of specific coupon fields.
*
* @param Array $effects the Coupon effects params
* @param array $effects the Coupon effects params
*/
abstract public function setFieldsValue($effects);

Some files were not shown because too many files have changed in this diff Show More