diff --git a/core/lib/Thelia/Config/Resources/coupon.xml b/core/lib/Thelia/Config/Resources/coupon.xml index a8b2df45d..68486c969 100644 --- a/core/lib/Thelia/Config/Resources/coupon.xml +++ b/core/lib/Thelia/Config/Resources/coupon.xml @@ -30,12 +30,12 @@ - + diff --git a/core/lib/Thelia/Controller/Admin/CouponController.php b/core/lib/Thelia/Controller/Admin/CouponController.php index 11abe29a3..abf3177aa 100644 --- a/core/lib/Thelia/Controller/Admin/CouponController.php +++ b/core/lib/Thelia/Controller/Admin/CouponController.php @@ -25,9 +25,7 @@ use Thelia\Core\Security\AccessManager; use Thelia\Coupon\CouponFactory; use Thelia\Coupon\CouponManager; use Thelia\Condition\ConditionCollection; -use Thelia\Coupon\Type\CouponAbstract; use Thelia\Coupon\Type\CouponInterface; -use Thelia\Coupon\Type\RemoveXPercent; use Thelia\Form\CouponCreationForm; use Thelia\Form\Exception\FormValidationException; use Thelia\Log\Tlog; @@ -577,7 +575,7 @@ class CouponController extends BaseAdminController $condition['serviceId'] = $availableCoupon->getServiceId(); $condition['name'] = $availableCoupon->getName(); $condition['toolTip'] = $availableCoupon->getToolTip(); - // $condition['inputName'] = $availableCoupon->getInputName(); + $cleanedCoupons[] = $condition; } @@ -676,35 +674,6 @@ class CouponController extends BaseAdminController } - /** - * Add percentage logic if found in the Coupon post data - * - * @param array $effects Effect parameters to populate - * @param array $extendedInputNames Extended Inputs to manage - * - * @return array Populated effect with percentage - */ - protected function addExtendedLogic(array $effects, array $extendedInputNames) - { - /** @var Request $request */ - $request = $this->container->get('request'); - $postData = $request->request; - // Validate quantity input - - if ($postData->has(RemoveXPercent::INPUT_EXTENDED__NAME)) { - $extentedPostData = $postData->get(RemoveXPercent::INPUT_EXTENDED__NAME); - - foreach ($extendedInputNames as $extendedInputName) { - if (isset($extentedPostData[$extendedInputName])) { - $inputValue = $extentedPostData[$extendedInputName]; - $effects[$extendedInputName] = $inputValue; - } - } - } - - return $effects; - } - /** * Feed the Coupon Create or Update event with the User inputs * @@ -718,16 +687,15 @@ class CouponController extends BaseAdminController // Get the form field values $data = $form->getData(); $serviceId = $data['type']; - /** @var CouponInterface $couponManager */ - $couponManager = $this->container->get($serviceId); - $effects = [CouponAbstract::INPUT_AMOUNT_NAME => $data[CouponAbstract::INPUT_AMOUNT_NAME]]; - $effects = $this->addExtendedLogic($effects, $couponManager->getExtendedInputs()); + + /** @var CouponInterface $coupon */ + $coupon = $this->container->get($serviceId); $couponEvent = new CouponCreateOrUpdateEvent( $data['code'], $serviceId, $data['title'], - $effects, + $coupon->getEffects($data), $data['shortDescription'], $data['description'], $data['isEnabled'], diff --git a/core/lib/Thelia/Core/Event/Coupon/CouponCreateOrUpdateEvent.php b/core/lib/Thelia/Core/Event/Coupon/CouponCreateOrUpdateEvent.php index 755dbe9f7..ae2312aae 100644 --- a/core/lib/Thelia/Core/Event/Coupon/CouponCreateOrUpdateEvent.php +++ b/core/lib/Thelia/Core/Event/Coupon/CouponCreateOrUpdateEvent.php @@ -327,10 +327,9 @@ class CouponCreateOrUpdateEvent extends ActionEvent */ public function setEffects(array $effects) { - if (null === $effects['amount']) { - throw new InvalidArgumentException('Missing key \'amount\' in Coupon effect ready to be serialized array'); - } - $this->amount = $effects['amount']; + // Amount is now optionnal. + $this->amount = isset($effects['amount']) ? $effects['amount'] : 0; + $this->effects = $effects; } diff --git a/core/lib/Thelia/Coupon/Type/CouponAbstract.php b/core/lib/Thelia/Coupon/Type/CouponAbstract.php index 424de8323..d04c29b89 100644 --- a/core/lib/Thelia/Coupon/Type/CouponAbstract.php +++ b/core/lib/Thelia/Coupon/Type/CouponAbstract.php @@ -17,6 +17,7 @@ use Thelia\Condition\ConditionEvaluator; use Thelia\Condition\ConditionOrganizerInterface; use Thelia\Core\Translation\Translator; use Thelia\Coupon\FacadeInterface; +use Thelia\Form\CouponCreationForm; use Thelia\Model\CouponCountry; use Thelia\Model\CouponModule; @@ -29,12 +30,21 @@ use Thelia\Model\CouponModule; */ abstract class CouponAbstract implements CouponInterface { - const INPUT_EXTENDED__NAME = 'thelia_coupon_creation_extended'; + /** + * The dataset name for all coupon specific input fields, that do not appear in the CouPonCreationForm form. + * + * In the input form, these fields have to be created like: + * + * thelia_coupon_specific[my_field, thelia_coupon_creation_extended[my_other_field] + * + * use the makeCouponField() method to do that safely. + */ + const COUPON_DATASET_NAME = 'coupon_specific'; - const INPUT_AMOUNT_NAME = 'amount'; - - /** @var array Extended Inputs to manage */ - protected $extendedInputs = array(); + /** + * A standard 'amount' filed name, thant can be used in coupons which extends this class + */ + const AMOUNT_FIELD_NAME = 'amount'; /** @var FacadeInterface Provide necessary value from Thelia */ protected $facade = null; @@ -161,7 +171,8 @@ abstract class CouponAbstract implements CouponInterface $this->facade = $facade; $this->effects = $effects; - $this->amount = $effects[self::INPUT_AMOUNT_NAME]; + // Amount is now optional. + $this->amount = isset($effects[self::AMOUNT_FIELD_NAME]) ? $effects[self::AMOUNT_FIELD_NAME] : 0; $this->freeShippingForCountries = $freeShippingForCountries; $this->freeShippingForModules = $freeShippingForModules; @@ -383,6 +394,18 @@ abstract class CouponAbstract implements CouponInterface return $this->conditionEvaluator->isMatching($this->conditions); } + /** + * This is the field label than will be displayed in the form. + * This method should be overridden to be useful. + * + * For backward compatibility only. + * + * @return string + */ + public function getInputName() { + return "Please override getInputName() method"; + } + /** * Draw the input displayed in the BackOffice * allowing Admin to set its Coupon effect @@ -394,19 +417,86 @@ abstract class CouponAbstract implements CouponInterface { return $this->facade->getParser()->render('coupon/type-fragments/remove-x.html', [ 'label' => $this->getInputName(), - 'fieldName' => self::INPUT_AMOUNT_NAME, + 'fieldId' => self::AMOUNT_FIELD_NAME, + 'fieldName' => $this->makeCouponFieldName(self::AMOUNT_FIELD_NAME), 'value' => $this->amount ]); } /** - * Get all extended inputs name to manage + * This methods checks a field value. If the field has a correct value, this value is returned + * Otherwise, an InvalidArgumentException describing the problem should be thrown. * + * This method should be ovveriden to be useful. + * + * @param $fieldName + * @param $fieldValue * @return mixed + * @throws \InvalidArgumentException if the field valiue is not valid. */ - public function getExtendedInputs() - { - return $this->extendedInputs; + protected function checkCouponFieldValue($fieldName, $fieldValue) { + return $fieldValue; } + /** + * A helper to get the value of a standard field name + * + * @param string $fieldName the field name + * @param array $data the input form data (e.g. $form->getData()) + * @param mixed $defaultValue the default value if the field is not found. + * + * @return mixed the input value, or the default one + * + * @throws \InvalidArgumentException if the field is not found, and no default value has been defined. + */ + protected function getCouponFieldValue($fieldName, $data, $defaultValue = null) { + if (isset($data[self::COUPON_DATASET_NAME][$fieldName])) { + + return $this->checkCouponFieldValue( + $fieldName, + $data[self::COUPON_DATASET_NAME][$fieldName] + ); + } + else if (null !== $defaultValue) { + return $defaultValue; + } + else { + throw new \InvalidArgumentException(sprintf("The coupon field name %s was not found in the coupon form", $fieldName)); + } + } + + /** + * A helper to create an standard field name that will be used in the coupon form + * + * @param string $fieldName the field name + * @return string the complete name, ready to be used in a form. + */ + protected function makeCouponFieldName($fieldName) { + return sprintf("%s[%s][%s]", CouponCreationForm::COUPON_CREATION_FORM_NAME, self::COUPON_DATASET_NAME, $fieldName); + } + + /** + * Return a list of the fields name for this coupon. + * + * @return array + */ + protected function getFieldList() { + return [self::AMOUNT_FIELD_NAME]; + } + + /** + * Create the effect array from the list of fields + * + * @param array $data the input form data (e.g. $form->getData()) + * @return array a filedName => fieldValue array + */ + public function getEffects($data) { + $effects = []; + + foreach($this->getFieldList() as $fieldName) { + $effects[$fieldName] = $this->getCouponFieldValue($fieldName, $data); + } + + return $effects; + } } diff --git a/core/lib/Thelia/Coupon/Type/CouponInterface.php b/core/lib/Thelia/Coupon/Type/CouponInterface.php index 69eca002a..f9acc973d 100644 --- a/core/lib/Thelia/Coupon/Type/CouponInterface.php +++ b/core/lib/Thelia/Coupon/Type/CouponInterface.php @@ -15,6 +15,7 @@ namespace Thelia\Coupon\Type; use Propel\Runtime\Collection\ObjectCollection; use Thelia\Condition\ConditionCollection; use Thelia\Coupon\FacadeInterface; +use Thelia\Form\CouponCreationForm; /** * Represents a Coupon ready to be processed in a Checkout process @@ -220,13 +221,6 @@ interface CouponInterface */ public function drawBackOfficeInputs(); - /** - * Get all extended inputs name to manage - * - * @return mixed - */ - public function getExtendedInputs(); - /** * @return ObjectCollection list of country IDs for which shipping is free. All if empty */ @@ -236,4 +230,14 @@ interface CouponInterface * @return ObjectCollection list of module IDs for which shipping is free. All if empty */ public function getFreeShippingForModules(); + + /** + * Create the effect array from the list of fields + * + * @param array $data the input form data (e.g. $form->getData()) + * + * @return array a filedName => fieldValue array + */ + public function getEffects($data); + } diff --git a/core/lib/Thelia/Coupon/Type/RemoveAmountOnCategories.php b/core/lib/Thelia/Coupon/Type/RemoveAmountOnCategories.php index 2372dbfb4..9994219e2 100644 --- a/core/lib/Thelia/Coupon/Type/RemoveAmountOnCategories.php +++ b/core/lib/Thelia/Coupon/Type/RemoveAmountOnCategories.php @@ -12,6 +12,9 @@ namespace Thelia\Coupon\Type; +use Thelia\Core\Translation\Translator; +use Thelia\Coupon\FacadeInterface; + /** * Allow to remove an amount from the checkout total * @@ -26,6 +29,44 @@ class RemoveAmountOnCategories extends CouponAbstract /** @var string Service Id */ protected $serviceId = 'thelia.coupon.type.remove_amount_on_categories'; + var $category_list = array(); + + /** + * @inheritdoc + */ + public function set( + FacadeInterface $facade, + $code, + $title, + $shortDescription, + $description, + array $effects, + $isCumulative, + $isRemovingPostage, + $isAvailableOnSpecialOffers, + $isEnabled, + $maxUsage, + \DateTime $expirationDate, + $freeShippingForCountries, + $freeShippingForModules, + $perCustomerUsageCount + ) + { + parent::set( + $facade, $code, $title, $shortDescription, $description, $effects, + $isCumulative, $isRemovingPostage, $isAvailableOnSpecialOffers, $isEnabled, $maxUsage, $expirationDate, + $freeShippingForCountries, + $freeShippingForModules, + $perCustomerUsageCount + ); + + $this->category_list = isset($effects[self::CATEGORIES_LIST]) ? $effects[self::CATEGORIES_LIST] : array(); + + if (! is_array($this->category_list)) $this->category_list = array($this->category_list); + + return $this; + } + /** * Get I18n name * @@ -64,18 +105,64 @@ class RemoveAmountOnCategories extends CouponAbstract */ public function exec() { + // TODO !!! return $this->amount; } public function drawBackOfficeInputs() { return $this->facade->getParser()->render('coupon/type-fragments/remove-amount-on-categories.html', [ - 'amount_field_name' => self::INPUT_AMOUNT_NAME, - 'amount_value' => $this->amount, - 'categories_field_name' => self::CATEGORIES_LIST, - 'categories_values' => isset($this->values[self::CATEGORIES_LIST]) ? $this->values[self::CATEGORIES_LIST] : array() + // The standard "Amount" field + 'amount_field_name' => $this->makeCouponFieldName(self::AMOUNT_FIELD_NAME), + 'amount_value' => $this->amount, - ]); + // The categories list field + 'categories_field_id' => self::CATEGORIES_LIST, + 'categories_field_name' => $this->makeCouponFieldName(self::CATEGORIES_LIST), + + // The selected categories + 'categories_values' => $this->category_list + ]); } + + /** + * Return a list of the fields name for this coupon. + * + * @return array + */ + protected function getFieldList() { + return [self::AMOUNT_FIELD_NAME, self::CATEGORIES_LIST]; + } + + + /** + * @inheritdoc + */ + protected function checkCouponFieldValue($fieldName, $fieldValue) + { + if ($fieldName === self::AMOUNT_FIELD_NAME) { + + if (floatval($fieldValue) < 0) { + throw new \InvalidArgumentException( + Translator::getInstance()->trans( + 'Value %val for Discount Amount is invalid. Please enter a positive value.', + [ '%val' => $fieldValue] + ) + ); + } + } + else if ($fieldName === self::CATEGORIES_LIST) { + if (empty($fieldValue)) { + throw new \InvalidArgumentException( + Translator::getInstance()->trans( + 'Please select at least one category' + ) + ); + } + } + + return $fieldValue; + } + } diff --git a/core/lib/Thelia/Coupon/Type/RemoveXAmount.php b/core/lib/Thelia/Coupon/Type/RemoveXAmount.php index c644706a2..fe5322e22 100644 --- a/core/lib/Thelia/Coupon/Type/RemoveXAmount.php +++ b/core/lib/Thelia/Coupon/Type/RemoveXAmount.php @@ -12,6 +12,8 @@ namespace Thelia\Coupon\Type; +use Thelia\Core\Translation\Translator; + /** * Allow to remove an amount from the checkout total * @@ -36,18 +38,6 @@ class RemoveXAmount extends CouponAbstract ->trans('Fixed Amount Discount', array(), 'coupon'); } - /** - * Get I18n amount input name - * - * @return string - */ - public function getInputName() - { - return $this->facade - ->getTranslator() - ->trans('Discount amount', array(), 'coupon'); - } - /** * Get I18n tooltip * @@ -69,9 +59,38 @@ class RemoveXAmount extends CouponAbstract public function drawBackOfficeInputs() { return $this->facade->getParser()->render('coupon/type-fragments/remove-x-amount.html', [ - 'label' => $this->getInputName(), - 'fieldName' => self::INPUT_AMOUNT_NAME, + 'fieldName' => $this->makeCouponFieldName(self::AMOUNT_FIELD_NAME), 'value' => $this->amount ]); } -} + + /** + * Return a list of the fields name for this coupon. + * + * @return array + */ + protected function getFieldList() { + return [self::AMOUNT_FIELD_NAME]; + } + + /** + * @inheritdoc + */ + protected function checkCouponFieldValue($fieldName, $fieldValue) + { + if ($fieldName === self::AMOUNT_FIELD_NAME) { + + if (floatval($fieldValue) < 0) { + throw new \InvalidArgumentException( + Translator::getInstance()->trans( + 'Value %val for Disount Amount is invalid. Please enter a positive value.', + [ '%val' => $fieldValue] + ) + ); + } + } + + return $fieldValue; + } + +} \ No newline at end of file diff --git a/core/lib/Thelia/Coupon/Type/RemoveXPercent.php b/core/lib/Thelia/Coupon/Type/RemoveXPercent.php index e5a98c5fb..06c73981a 100644 --- a/core/lib/Thelia/Coupon/Type/RemoveXPercent.php +++ b/core/lib/Thelia/Coupon/Type/RemoveXPercent.php @@ -12,6 +12,7 @@ namespace Thelia\Coupon\Type; +use Thelia\Core\Translation\Translator; use Thelia\Coupon\FacadeInterface; /** @@ -29,11 +30,6 @@ class RemoveXPercent extends CouponAbstract /** @var float Percentage removed from the Cart */ protected $percentage = 0; - /** @var array Extended Inputs to manage */ - protected $extendedInputs = array( - self::INPUT_PERCENTAGE_NAME - ); - /** * @inheritdoc */ @@ -87,6 +83,26 @@ class RemoveXPercent extends CouponAbstract return round($this->facade->getCartTotalTaxPrice() * $this->percentage/100, 2); } + /** + * @inheritdoc + */ + protected function checkCouponFieldValue($fieldName, $fieldValue) + { + if ($fieldName === self::INPUT_PERCENTAGE_NAME) { + + if (floatval($fieldValue) <= 0) { + throw new \InvalidArgumentException( + Translator::getInstance()->trans( + 'Value %val for Percent Discount is invalid. Please enter a positive value between 1 and 100.', + [ '%val' => $fieldValue] + ) + ); + } + } + + return $fieldValue; + } + /** * Get I18n name * @@ -99,18 +115,6 @@ class RemoveXPercent extends CouponAbstract ->trans('Remove X percent to total cart', array(), 'coupon'); } - /** - * Get I18n amount input name - * - * @return string - */ - public function getInputName() - { - return $this->facade - ->getTranslator() - ->trans('Percent Discount', array(), 'coupon'); - } - /** * Get I18n tooltip * @@ -138,11 +142,17 @@ class RemoveXPercent extends CouponAbstract public function drawBackOfficeInputs() { return $this->facade->getParser()->render('coupon/type-fragments/remove-x-percent.html', [ - 'label' => $this->getInputName(), - 'typeKey' => self::INPUT_AMOUNT_NAME, - 'fieldId' => self::INPUT_PERCENTAGE_NAME, - 'fieldName' => self::INPUT_EXTENDED__NAME, + 'fieldName' => $this->makeCouponFieldName(self::INPUT_PERCENTAGE_NAME), 'value' => $this->percentage ]); } + + /** + * Return a list of the fields name for this coupon. + * + * @return array + */ + protected function getFieldList() { + return [self::INPUT_PERCENTAGE_NAME]; + } } diff --git a/core/lib/Thelia/Form/CouponCreationForm.php b/core/lib/Thelia/Form/CouponCreationForm.php index 688e8a6ba..acb16ce70 100644 --- a/core/lib/Thelia/Form/CouponCreationForm.php +++ b/core/lib/Thelia/Form/CouponCreationForm.php @@ -33,6 +33,8 @@ use Thelia\Module\BaseModule; */ class CouponCreationForm extends BaseForm { + const COUPON_CREATION_FORM_NAME = 'thelia_coupon_creation'; + /** * Build Coupon form * @@ -110,14 +112,6 @@ class CouponCreationForm extends BaseForm ) ) ) - ->add( - 'amount', - 'money', - array( - 'constraints' => array( - new NotBlank() - )) - ) ->add( 'isEnabled', 'text', @@ -198,7 +192,12 @@ class CouponCreationForm extends BaseForm new NotBlank() ) ) - ); + ) + ->add('coupon_specific', 'collection', array( + 'allow_add' => true, + 'allow_delete' => true, + )) + ; } /** @@ -226,6 +225,6 @@ class CouponCreationForm extends BaseForm */ public function getName() { - return 'thelia_coupon_creation'; + return self::COUPON_CREATION_FORM_NAME; } } diff --git a/core/lib/Thelia/Model/Coupon.php b/core/lib/Thelia/Model/Coupon.php index 45f701876..921078eda 100644 --- a/core/lib/Thelia/Model/Coupon.php +++ b/core/lib/Thelia/Model/Coupon.php @@ -184,7 +184,8 @@ class Coupon extends BaseCoupon */ public function getAmount() { - $amount = $this->getEffects()['amount']; + // Amount is now optional + $amount = isset($this->getEffects()['amount']) ? $this->getEffects()['amount'] : 0; return floatval($amount); } @@ -199,10 +200,6 @@ class Coupon extends BaseCoupon { $effects = $this->unserializeEffects($this->getSerializedEffects()); - if (null === $effects['amount']) { - throw new InvalidArgumentException('Missing key \'amount\' in Coupon effect coming from database'); - } - return $effects; } @@ -210,18 +207,12 @@ class Coupon extends BaseCoupon * Get the Coupon effects * * @param array $effects Effect ready to be serialized - * Needs at least the key 'amount' - * with the amount removed from the cart * * @throws Exception\InvalidArgumentException * @return $this */ public function setEffects(array $effects) { - if (null === $effects['amount']) { - throw new InvalidArgumentException('Missing key \'amount\' in Coupon effect ready to be serialized array'); - } - $this->setSerializedEffects($this->serializeEffects($effects)); return $this; diff --git a/templates/backOffice/default/coupon/form.html b/templates/backOffice/default/coupon/form.html index 19ed9254e..bdb8b7226 100644 --- a/templates/backOffice/default/coupon/form.html +++ b/templates/backOffice/default/coupon/form.html @@ -181,9 +181,7 @@ {/form_field}
- {form_field form=$form field='amount'} {$couponInputsHtml nofilter} - {/form_field}
diff --git a/templates/backOffice/default/coupon/type-fragments/remove-amount-on-categories.html b/templates/backOffice/default/coupon/type-fragments/remove-amount-on-categories.html index b14a171a5..396a1ef01 100644 --- a/templates/backOffice/default/coupon/type-fragments/remove-amount-on-categories.html +++ b/templates/backOffice/default/coupon/type-fragments/remove-amount-on-categories.html @@ -1,7 +1,11 @@ -
- + +
+ + +
- + + {loop type="currency" name="get-symbol" default_only="true"}
{$SYMBOL}
{/loop} @@ -9,11 +13,14 @@
- - {loop type="category-tree" category=0 name="list-of-categories" backend_context="1"} - + {/loop} + {intl l='Use Ctrl+click to select (or deselect) more that one category'}
diff --git a/templates/backOffice/default/coupon/type-fragments/remove-x-amount.html b/templates/backOffice/default/coupon/type-fragments/remove-x-amount.html index 6159af73f..fe9cf285f 100644 --- a/templates/backOffice/default/coupon/type-fragments/remove-x-amount.html +++ b/templates/backOffice/default/coupon/type-fragments/remove-x-amount.html @@ -1,9 +1,11 @@ -
- +
+ +
- + + {loop type="currency" name="get-symbol" default_only="true"} -
{$SYMBOL}
+
{$SYMBOL}
{/loop}
diff --git a/templates/backOffice/default/coupon/type-fragments/remove-x-percent.html b/templates/backOffice/default/coupon/type-fragments/remove-x-percent.html index 4c7c8d9d9..f06b42a6a 100644 --- a/templates/backOffice/default/coupon/type-fragments/remove-x-percent.html +++ b/templates/backOffice/default/coupon/type-fragments/remove-x-percent.html @@ -1,8 +1,9 @@ - -
- +
+ + +
- +
%
diff --git a/templates/backOffice/default/coupon/type-fragments/remove-x.html b/templates/backOffice/default/coupon/type-fragments/remove-x.html index 3ac1bab7b..05a606d5a 100644 --- a/templates/backOffice/default/coupon/type-fragments/remove-x.html +++ b/templates/backOffice/default/coupon/type-fragments/remove-x.html @@ -1,4 +1,4 @@ -
- - +
+ +