diff --git a/admin-dev/themes/new-theme/js/pages/customer/index.js b/admin-dev/themes/new-theme/js/pages/customer/index.js index cdea00ad0bb83..e5213054a5abe 100644 --- a/admin-dev/themes/new-theme/js/pages/customer/index.js +++ b/admin-dev/themes/new-theme/js/pages/customer/index.js @@ -55,4 +55,7 @@ $(() => { customerGrid.addExtension(new SubmitBulkExtension()); customerGrid.addExtension(new SubmitGridExtension()); customerGrid.addExtension(new LinkRowActionExtension()); + + // needed for "Group access" input in Add/Edit customer forms + new ChoiceTable(); }); diff --git a/src/Adapter/Customer/CommandHandler/AddCustomerHandler.php b/src/Adapter/Customer/CommandHandler/AddCustomerHandler.php new file mode 100644 index 0000000000000..7e302f040c374 --- /dev/null +++ b/src/Adapter/Customer/CommandHandler/AddCustomerHandler.php @@ -0,0 +1,152 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Adapter\Customer\CommandHandler; + +use Customer; +use PrestaShop\PrestaShop\Core\Crypto\Hashing; +use PrestaShop\PrestaShop\Core\Domain\Customer\Command\AddCustomerCommand; +use PrestaShop\PrestaShop\Core\Domain\Customer\CommandHandler\AddCustomerHandlerInterface; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerDefaultGroupAccessException; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerException; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\DuplicateCustomerEmailException; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Email; + +/** + * Handles command that adds new customer + * + * @internal + */ +final class AddCustomerHandler implements AddCustomerHandlerInterface +{ + /** + * @var Hashing + */ + private $hashing; + + /** + * @var string Value of legacy _COOKIE_KEY_ + */ + private $legacyCookieKey; + + /** + * @param Hashing $hashing + * @param string $legacyCookieKey + */ + public function __construct(Hashing $hashing, $legacyCookieKey) + { + $this->hashing = $hashing; + $this->legacyCookieKey = $legacyCookieKey; + } + + /** + * {@inheritdoc} + */ + public function handle(AddCustomerCommand $command) + { + $customer = new Customer(); + + $this->fillCustomerWithCommandData($customer, $command); + + if (false === $customer->validateFields(false)) { + throw new CustomerException('Customer contains invalid field values'); + } + + $this->assertCustomerWithGivenEmailDoesNotExist($command->getEmail()); + $this->assertCustomerCanAccessDefaultGroup($command); + + $customer->add(); + + return new CustomerId((int) $customer->id); + } + + /** + * @param Email $email + */ + private function assertCustomerWithGivenEmailDoesNotExist(Email $email) + { + $customer = new Customer(); + $customer->getByEmail($email->getValue()); + + if ($customer->id) { + throw new DuplicateCustomerEmailException( + $email, + sprintf('Customer with email "%s" already exists', $email->getValue()) + ); + } + } + + /** + * @param Customer $customer + * @param AddCustomerCommand $command + */ + private function fillCustomerWithCommandData(Customer $customer, AddCustomerCommand $command) + { + $apeCode = null !== $command->getApeCode() ? + $command->getApeCode()->getValue() : + null + ; + + $hashedPassword = $this->hashing->hash( + $command->getPassword()->getValue(), + $this->legacyCookieKey + ); + + $customer->firstname = $command->getFirstName()->getValue(); + $customer->lastname = $command->getLastName()->getValue(); + $customer->email = $command->getEmail()->getValue(); + $customer->passwd = $hashedPassword; + $customer->id_default_group = $command->getDefaultGroupId(); + $customer->groupBox = $command->getGroupIds(); + $customer->id_gender = $command->getGenderId(); + $customer->active = $command->isEnabled(); + $customer->optin = $command->isPartnerOffersSubscribed(); + $customer->birthday = $command->getBirthday()->getValue(); + $customer->id_shop = $command->getShopId(); + + // fill b2b customer fields + $customer->company = $command->getCompanyName(); + $customer->siret = $command->getSiretCode(); + $customer->ape = $apeCode; + $customer->website = $command->getWebsite(); + $customer->outstanding_allow_amount = $command->getAllowedOutstandingAmount(); + $customer->max_payment_days = $command->getMaxPaymentDays(); + $customer->id_risk = $command->getRiskId(); + } + + /** + * @param AddCustomerCommand $command + */ + private function assertCustomerCanAccessDefaultGroup(AddCustomerCommand $command) + { + if (!in_array($command->getDefaultGroupId(), $command->getGroupIds())) { + throw new CustomerDefaultGroupAccessException( + sprintf('Customer default group with id "%s" must be in access groups', $command->getDefaultGroupId()) + ); + } + } +} diff --git a/src/Adapter/Customer/CommandHandler/EditCustomerHandler.php b/src/Adapter/Customer/CommandHandler/EditCustomerHandler.php new file mode 100644 index 0000000000000..f366ddec3204c --- /dev/null +++ b/src/Adapter/Customer/CommandHandler/EditCustomerHandler.php @@ -0,0 +1,239 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Adapter\Customer\CommandHandler; + +use Customer; +use PrestaShop\PrestaShop\Core\Crypto\Hashing; +use PrestaShop\PrestaShop\Core\Domain\Customer\Command\EditCustomerCommand; +use PrestaShop\PrestaShop\Core\Domain\Customer\CommandHandler\EditCustomerHandlerInterface; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerDefaultGroupAccessException; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerException; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerNotFoundException; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\DuplicateCustomerEmailException; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Email; + +/** + * Handles command which edits customer's data + */ +final class EditCustomerHandler implements EditCustomerHandlerInterface +{ + /** + * @var Hashing + */ + private $hashing; + + /** + * @var string Value of legacy _COOKIE_KEY_ + */ + private $legacyCookieKey; + + /** + * @param Hashing $hashing + * @param string $legacyCookieKey + */ + public function __construct(Hashing $hashing, $legacyCookieKey) + { + $this->hashing = $hashing; + $this->legacyCookieKey = $legacyCookieKey; + } + + /** + * {@inheritdoc} + */ + public function handle(EditCustomerCommand $command) + { + $customerId = $command->getCustomerId(); + $customer = new Customer($customerId->getValue()); + + if ($customer->id !== $customerId->getValue()) { + throw new CustomerNotFoundException( + $customerId, + sprintf('Customer with id "%s" was not found', $customerId->getValue()) + ); + } + + $this->assertCustomerWithUpdatedEmailDoesNotExist($customer, $command); + $this->assertCustomerCanAccessDefaultGroup($customer, $command); + + $this->updateCustomerWithCommandData($customer, $command); + + if (false === $customer->validateFields(false)) { + throw new CustomerException('Customer contains invalid field values'); + } + + $customer->update(); + + return $customerId; + } + + /** + * @param Customer $customer + * @param EditCustomerCommand $command + */ + private function updateCustomerWithCommandData(Customer $customer, EditCustomerCommand $command) + { + if (null !== $command->getGenderId()) { + $customer->id_gender = $command->getGenderId(); + } + + if (null !== $command->getFirstName()) { + $customer->firstname = $command->getFirstName()->getValue(); + } + + if (null !== $command->getLastName()) { + $customer->lastname = $command->getLastName()->getValue(); + } + + if (null !== $command->getEmail()) { + $customer->email = $command->getEmail()->getValue(); + } + + if (null !== $command->getPassword()) { + $hashedPassword = $this->hashing->hash( + $command->getPassword()->getValue(), + $this->legacyCookieKey + ); + + $customer->passwd = $hashedPassword; + } + + if (null !== $command->getBirthday()) { + $customer->birthday = $command->getBirthday()->getValue(); + } + + if (null !== $command->isEnabled()) { + $customer->active = $command->isEnabled(); + } + + if (null !== $command->isPartnerOffersSubscribed()) { + $customer->optin = $command->isPartnerOffersSubscribed(); + } + + if (null !== $command->getGroupIds()) { + $customer->groupBox = $command->getGroupIds(); + } + + if (null !== $command->getDefaultGroupId()) { + $customer->id_default_group = $command->getDefaultGroupId(); + } + + $this->updateCustomerB2bData($customer, $command); + } + + /** + * @param Customer $customer + * @param EditCustomerCommand $command + */ + private function updateCustomerB2bData(Customer $customer, EditCustomerCommand $command) + { + if (null !== $command->getCompanyName()) { + $customer->company = $command->getCompanyName(); + } + + if (null !== $command->getSiretCode()) { + $customer->siret = $command->getSiretCode(); + } + + if (null !== $command->getApeCode()) { + $customer->ape = $command->getApeCode()->getValue(); + } + + if (null !== $command->getWebsite()) { + $customer->website = $command->getWebsite(); + } + + if (null !== $command->getAllowedOutstandingAmount()) { + $customer->outstanding_allow_amount = $command->getAllowedOutstandingAmount(); + } + + if (null !== $command->getMaxPaymentDays()) { + $customer->max_payment_days = $command->getMaxPaymentDays(); + } + + if (null !== $command->getRiskId()) { + $customer->id_risk = $command->getRiskId(); + } + } + + /** + * @param Customer $customer + * @param EditCustomerCommand $command + */ + private function assertCustomerWithUpdatedEmailDoesNotExist(Customer $customer, EditCustomerCommand $command) + { + // if email is not being updated + // then assertion is not needed + if (null === $command->getEmail()) { + return; + } + + if ($command->getEmail()->isEqualTo(new Email($customer->email))) { + return; + } + + $customerByEmail = new Customer(); + $customerByEmail->getByEmail($command->getEmail()->getValue()); + + if ($customerByEmail->id) { + throw new DuplicateCustomerEmailException( + $command->getEmail(), + sprintf('Customer with email "%s" already exists', $command->getEmail()->getValue()) + ); + } + } + + /** + * @param Customer $customer + * @param EditCustomerCommand $command + */ + private function assertCustomerCanAccessDefaultGroup(Customer $customer, EditCustomerCommand $command) + { + // if neither default group + // nor group ids are being edited + // then no need to assert + if (null === $command->getDefaultGroupId() + || null === $command->getGroupIds() + ) { + return; + } + + $defaultGroupId = null !== $command->getDefaultGroupId() ? + $command->getDefaultGroupId() : + $customer->id_default_group + ; + $groupIds = null !== $command->getGroupIds() ? + $command->getGroupIds() : + $customer->getGroups() + ; + + if (!in_array($defaultGroupId, $groupIds)) { + throw new CustomerDefaultGroupAccessException( + sprintf('Customer default group with id "%s" must be in access groups', $command->getDefaultGroupId()) + ); + } + } +} diff --git a/src/Adapter/Customer/QueryHandler/GetCustomerForEditingHandler.php b/src/Adapter/Customer/QueryHandler/GetCustomerForEditingHandler.php new file mode 100644 index 0000000000000..19cb736dcc3bb --- /dev/null +++ b/src/Adapter/Customer/QueryHandler/GetCustomerForEditingHandler.php @@ -0,0 +1,86 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Adapter\Customer\QueryHandler; + +use Customer; +use PrestaShop\PrestaShop\Core\Domain\Customer\Dto\EditableCustomer; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerNotFoundException; +use PrestaShop\PrestaShop\Core\Domain\Customer\Query\GetCustomerForEditing; +use PrestaShop\PrestaShop\Core\Domain\Customer\QueryHandler\GetCustomerForEditingHandlerInterface; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Birthday; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Email; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\FirstName; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\LastName; + +/** + * Handles command that gets customer for editing + * + * @internal + */ +final class GetCustomerForEditingHandler implements GetCustomerForEditingHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(GetCustomerForEditing $query) + { + $customerId = $query->getCustomerId(); + $customer = new Customer($customerId->getValue()); + + if ($customer->id !== $customerId->getValue()) { + throw new CustomerNotFoundException( + $customerId, + sprintf('Customer with id "%s" was not found', $customerId->getValue()) + ); + } + + $birthday = null === $customer->birthday ? + Birthday::createEmpty() : + new Birthday($customer->birthday) + ; + + return new EditableCustomer( + $customerId, + $customer->id_gender, + new FirstName($customer->firstname), + new LastName($customer->lastname), + new Email($customer->email), + $birthday, + (bool) $customer->active, + (bool) $customer->optin, + $customer->getGroups(), + $customer->id_default_group, + (string) $customer->company, + (string) $customer->siret, + (string) $customer->ape, + (string) $customer->website, + (float) $customer->outstanding_allow_amount, + (int) $customer->max_payment_days, + (int) $customer->id_risk + ); + } +} diff --git a/src/Adapter/Form/ChoiceProvider/GroupByIdChoiceProvider.php b/src/Adapter/Form/ChoiceProvider/GroupByIdChoiceProvider.php new file mode 100644 index 0000000000000..60ffa40ef476d --- /dev/null +++ b/src/Adapter/Form/ChoiceProvider/GroupByIdChoiceProvider.php @@ -0,0 +1,83 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Adapter\Form\ChoiceProvider; + +use Group; +use PrestaShop\PrestaShop\Core\ConfigurationInterface; +use PrestaShop\PrestaShop\Core\Form\FormChoiceProviderInterface; + +/** + * Provides choices for customer groups + */ +final class GroupByIdChoiceProvider implements FormChoiceProviderInterface +{ + /** + * @var ConfigurationInterface + */ + private $configuration; + + /** + * @var int + */ + private $contextLangId; + + /** + * @param ConfigurationInterface $configuration + * @param int $contextLangId + */ + public function __construct(ConfigurationInterface $configuration, $contextLangId) + { + $this->configuration = $configuration; + $this->contextLangId = $contextLangId; + } + + /** + * {@inheritdoc} + */ + public function getChoices() + { + $choices = []; + $groups = Group::getGroups($this->contextLangId, true); + + $groupsToSkip = [ + (int) $this->configuration->get('PS_UNIDENTIFIED_GROUP'), + (int) $this->configuration->get('PS_GUEST_GROUP'), + ]; + + foreach ($groups as $group) { + $groupId = $group['id_group']; + + if (in_array($groups, $groupsToSkip)) { + continue; + } + + $choices[$group['name']] = (int) $groupId; + } + + return $choices; + } +} diff --git a/src/Adapter/Form/ChoiceProvider/RiskByIdChoiceProvider.php b/src/Adapter/Form/ChoiceProvider/RiskByIdChoiceProvider.php new file mode 100644 index 0000000000000..203da3ddd38d1 --- /dev/null +++ b/src/Adapter/Form/ChoiceProvider/RiskByIdChoiceProvider.php @@ -0,0 +1,54 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Adapter\Form\ChoiceProvider; + +use PrestaShop\PrestaShop\Core\Form\FormChoiceProviderInterface; +use Risk; + +/** + * Class RiskByIdChoiceProvider + * + * @internal + */ +final class RiskByIdChoiceProvider implements FormChoiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function getChoices() + { + $risks = Risk::getRisks(); + $choices = []; + + /** @var Risk $risk */ + foreach ($risks as $risk) { + $choices[$risk->name] = (int) $risk->id; + } + + return $choices; + } +} diff --git a/src/Adapter/Group/Provider/DefaultGroupsProvider.php b/src/Adapter/Group/Provider/DefaultGroupsProvider.php new file mode 100644 index 0000000000000..597f73f3a2a6e --- /dev/null +++ b/src/Adapter/Group/Provider/DefaultGroupsProvider.php @@ -0,0 +1,95 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Adapter\Group\Provider; + +use Group; +use PrestaShop\PrestaShop\Core\ConfigurationInterface; +use PrestaShop\PrestaShop\Core\Group\Provider\DefaultGroup; +use PrestaShop\PrestaShop\Core\Group\Provider\DefaultGroupsProviderInterface; +use PrestaShop\PrestaShop\Core\Group\Provider\DefaultGroups; + +/** + * Provides default customer groups + * + * @internal + */ +final class DefaultGroupsProvider implements DefaultGroupsProviderInterface +{ + /** + * @var ConfigurationInterface + */ + private $configuration; + + /** + * @var int + */ + private $contextLangId; + + /** + * @param ConfigurationInterface $configuration + * @param int $contextLangId + */ + public function __construct(ConfigurationInterface $configuration, $contextLangId) + { + $this->configuration = $configuration; + $this->contextLangId = $contextLangId; + } + + /** + * {@inheritdoc} + */ + public function getGroups() + { + $visitorsGroup = new Group($this->configuration->get('PS_UNIDENTIFIED_GROUP')); + $guestsGroup = new Group($this->configuration->get('PS_GUEST_GROUP')); + $customersGroup = new Group($this->configuration->get('PS_CUSTOMER_GROUP')); + + $visitorName = isset($visitorsGroup->name[$this->contextLangId]) ? + $visitorsGroup->name[$this->contextLangId] : + reset($visitorsGroup->name) + ; + $visitorsGroupDto = new DefaultGroup((int) $visitorsGroup->id, $visitorName); + + $groupsName = isset($guestsGroup->name[$this->contextLangId]) ? + $guestsGroup->name[$this->contextLangId] : + $guestsGroup->name + ; + $guestsGroupDto = new DefaultGroup((int) $guestsGroup->id, $groupsName); + + $customersName = isset($customersGroup->name[$this->contextLangId]) ? + $customersGroup->name[$this->contextLangId] : + reset($customersGroup->name) + ; + $customersGroupDto = new DefaultGroup((int) $customersGroup->id, $customersName); + + return new DefaultGroups( + $visitorsGroupDto, + $guestsGroupDto, + $customersGroupDto + ); + } +} diff --git a/src/Core/Domain/Customer/Command/AddCustomerCommand.php b/src/Core/Domain/Customer/Command/AddCustomerCommand.php new file mode 100644 index 0000000000000..872b025b01fe1 --- /dev/null +++ b/src/Core/Domain/Customer/Command/AddCustomerCommand.php @@ -0,0 +1,397 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\Command; + +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\ApeCode; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Birthday; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Email; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\FirstName; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\LastName; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Password; + +/** + * Adds new customer with provided data + */ +class AddCustomerCommand +{ + /** + * @var FirstName + */ + private $firstName; + + /** + * @var LastName + */ + private $lastName; + + /** + * @var Email + */ + private $email; + + /** + * @var Password + */ + private $password; + + /** + * @var int + */ + private $defaultGroupId; + + /** + * @var int[] + */ + private $groupIds; + + /** + * @var int|null + */ + private $genderId; + + /** + * @var bool + */ + private $isEnabled; + + /** + * @var bool + */ + private $isPartnerOffersSubscribed; + + /** + * @var Birthday + */ + private $birthday; + + /** + * @var int + */ + private $shopId; + + /** + * @var string|null Only for B2b customers + */ + private $companyName; + + /** + * @var string|null Only for B2b customers + */ + private $siretCode; + + /** + * @var ApeCode|null Only for B2b customers + */ + private $apeCode; + + /** + * @var string|null Only for B2b customers + */ + private $website; + + /** + * @var float|null Only for B2b customers + */ + private $allowedOutstandingAmount; + + /** + * @var int|null Only for B2b customers + */ + private $maxPaymentDays; + + /** + * @var int|null Only for B2b customers + */ + private $riskId; + + /** + * @param string $firstName + * @param string $lastName + * @param string $email + * @param string $password + * @param int $defaultGroupId + * @param int[] $groupIds + * @param int $shopId + * @param int|null $genderId + * @param bool $isEnabled + * @param bool $isPartnerOffersSubscribed + * @param string|null $birthday + */ + public function __construct( + $firstName, + $lastName, + $email, + $password, + $defaultGroupId, + array $groupIds, + $shopId, + $genderId = null, + $isEnabled = true, + $isPartnerOffersSubscribed = false, + $birthday = null + ) { + $this->firstName = new FirstName($firstName); + $this->lastName = new LastName($lastName); + $this->email = new Email($email); + $this->password = new Password($password); + $this->defaultGroupId = $defaultGroupId; + $this->groupIds = $groupIds; + $this->shopId = $shopId; + $this->genderId = $genderId; + $this->isEnabled = $isEnabled; + $this->isPartnerOffersSubscribed = $isPartnerOffersSubscribed; + $this->birthday = null !== $birthday ? new Birthday($birthday) : Birthday::createEmpty(); + } + + /** + * @return FirstName + */ + public function getFirstName() + { + return $this->firstName; + } + + /** + * @return LastName + */ + public function getLastName() + { + return $this->lastName; + } + + /** + * @return Email + */ + public function getEmail() + { + return $this->email; + } + + /** + * @return Password + */ + public function getPassword() + { + return $this->password; + } + + /** + * @return int + */ + public function getDefaultGroupId() + { + return $this->defaultGroupId; + } + + /** + * @return int[] + */ + public function getGroupIds() + { + return $this->groupIds; + } + + /** + * @return int|null + */ + public function getGenderId() + { + return $this->genderId; + } + + /** + * @return bool + */ + public function isEnabled() + { + return $this->isEnabled; + } + + /** + * @return bool + */ + public function isPartnerOffersSubscribed() + { + return $this->isPartnerOffersSubscribed; + } + + /** + * @return Birthday + */ + public function getBirthday() + { + return $this->birthday; + } + + /** + * @return int + */ + public function getShopId() + { + return $this->shopId; + } + + /** + * @return string|null + */ + public function getCompanyName() + { + return $this->companyName; + } + + /** + * @param string $companyName + * + * @return self + */ + public function setCompanyName($companyName) + { + $this->companyName = $companyName; + + return $this; + } + + /** + * @return string|null + */ + public function getSiretCode() + { + return $this->siretCode; + } + + /** + * @param string $siretCode + * + * @return self + */ + public function setSiretCode($siretCode) + { + $this->siretCode = $siretCode; + + return $this; + } + + /** + * @return ApeCode|null + */ + public function getApeCode() + { + return $this->apeCode; + } + + /** + * @param string $apeCode + * + * @return self + */ + public function setApeCode($apeCode) + { + $this->apeCode = new ApeCode($apeCode); + + return $this; + } + + /** + * @return string|null + */ + public function getWebsite() + { + return $this->website; + } + + /** + * @param string $website + * + * @return self + */ + public function setWebsite($website) + { + $this->website = $website; + + return $this; + } + + /** + * @return float|null + */ + public function getAllowedOutstandingAmount() + { + return $this->allowedOutstandingAmount; + } + + /** + * @param float $allowedOutstandingAmount + * + * @return self + */ + public function setAllowedOutstandingAmount($allowedOutstandingAmount) + { + $this->allowedOutstandingAmount = $allowedOutstandingAmount; + + return $this; + } + + /** + * @return int|null + */ + public function getMaxPaymentDays() + { + return $this->maxPaymentDays; + } + + /** + * @param int $maxPaymentDays + * + * @return self + */ + public function setMaxPaymentDays($maxPaymentDays) + { + $this->maxPaymentDays = $maxPaymentDays; + + return $this; + } + + /** + * @return int|null + */ + public function getRiskId() + { + return $this->riskId; + } + + /** + * @param int $riskId + * + * @return self + */ + public function setRiskId($riskId) + { + $this->riskId = $riskId; + + return $this; + } +} diff --git a/src/Core/Domain/Customer/Command/EditCustomerCommand.php b/src/Core/Domain/Customer/Command/EditCustomerCommand.php new file mode 100644 index 0000000000000..7d28b9bfa334c --- /dev/null +++ b/src/Core/Domain/Customer/Command/EditCustomerCommand.php @@ -0,0 +1,492 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\Command; + +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\ApeCode; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Birthday; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Email; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\FirstName; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\LastName; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Password; + +/** + * Edits provided customer. + * It can edit either all or partial data. + * + * Only not-null values are considered when editing customer. + * For example, if the email is null, then the original value is not modified, + * however, if email is set, then the original value will be overwritten. + */ +class EditCustomerCommand +{ + /** + * @var CustomerId + */ + private $customerId; + + /** + * @var FirstName|null + */ + private $firstName; + + /** + * @var LastName|null + */ + private $lastName; + + /** + * @var Email|null + */ + private $email; + + /** + * @var Password|null + */ + private $password; + + /** + * @var int|null + */ + private $defaultGroupId; + + /** + * @var int[]|null + */ + private $groupIds; + + /** + * @var int|null + */ + private $genderId; + + /** + * @var bool|null + */ + private $isEnabled; + + /** + * @var bool|null + */ + private $isPartnerOffersSubscribed; + + /** + * @var Birthday|null + */ + private $birthday; + + /** + * @var string|null + */ + private $companyName; + + /** + * @var string|null + */ + private $siretCode; + + /** + * @var ApeCode|null + */ + private $apeCode; + + /** + * @var string|null + */ + private $website; + + /** + * @var float|null + */ + private $allowedOutstandingAmount; + + /** + * @var int|null + */ + private $maxPaymentDays; + + /** + * @var int|null + */ + private $riskId; + + /** + * @param int $customerId + */ + public function __construct($customerId) + { + $this->customerId = new CustomerId($customerId); + } + + /** + * @return CustomerId + */ + public function getCustomerId() + { + return $this->customerId; + } + + /** + * @return FirstName|null + */ + public function getFirstName() + { + return $this->firstName; + } + + /** + * @param string $firstName + * + * @return self + */ + public function setFirstName($firstName) + { + $this->firstName = new FirstName($firstName); + + return $this; + } + + /** + * @return LastName|null + */ + public function getLastName() + { + return $this->lastName; + } + + /** + * @param string $lastName + * + * @return self + */ + public function setLastName($lastName) + { + $this->lastName = new LastName($lastName); + + return $this; + } + + /** + * @return Email|null + */ + public function getEmail() + { + return $this->email; + } + + /** + * @param string $email + * + * @return self + */ + public function setEmail($email) + { + $this->email = new Email($email); + + return $this; + } + + /** + * @return Password|null + */ + public function getPassword() + { + return $this->password; + } + + /** + * @param string $password + * + * @return self + */ + public function setPassword($password) + { + $this->password = new Password($password); + + return $this; + } + + /** + * @return int|null + */ + public function getDefaultGroupId() + { + return $this->defaultGroupId; + } + + /** + * @param int $defaultGroupId + * + * @return self + */ + public function setDefaultGroupId($defaultGroupId) + { + $this->defaultGroupId = $defaultGroupId; + + return $this; + } + + /** + * @return int[]|null + */ + public function getGroupIds() + { + return $this->groupIds; + } + + /** + * @param int[] $groupIds + * + * @return self + */ + public function setGroupIds(array $groupIds) + { + $this->groupIds = $groupIds; + + return $this; + } + + /** + * @return int|null + */ + public function getGenderId() + { + return $this->genderId; + } + + /** + * @param int $genderId + * + * @return self + */ + public function setGenderId($genderId) + { + $this->genderId = $genderId; + + return $this; + } + + /** + * @return bool + */ + public function isEnabled() + { + return $this->isEnabled; + } + + /** + * @param bool $isEnabled + * + * @return self + */ + public function setIsEnabled($isEnabled) + { + $this->isEnabled = $isEnabled; + + return $this; + } + + /** + * @return bool + */ + public function isPartnerOffersSubscribed() + { + return $this->isPartnerOffersSubscribed; + } + + /** + * @param bool $isPartnerOffersSubscribed + * + * @return self + */ + public function setIsPartnerOffersSubscribed($isPartnerOffersSubscribed) + { + $this->isPartnerOffersSubscribed = $isPartnerOffersSubscribed; + + return $this; + } + + /** + * @return Birthday|null + */ + public function getBirthday() + { + return $this->birthday; + } + + /** + * @param string $birthday + * + * @return self + */ + public function setBirthday($birthday) + { + $this->birthday = new Birthday($birthday); + + return $this; + } + + /** + * @return string|null + */ + public function getCompanyName() + { + return $this->companyName; + } + + /** + * @param string $companyName + * + * @return self + */ + public function setCompanyName($companyName) + { + $this->companyName = $companyName; + + return $this; + } + + /** + * @return string|null + */ + public function getSiretCode() + { + return $this->siretCode; + } + + /** + * @param string $siretCode + * + * @return self + */ + public function setSiretCode($siretCode) + { + $this->siretCode = $siretCode; + + return $this; + } + + /** + * @return ApeCode|null + */ + public function getApeCode() + { + return $this->apeCode; + } + + /** + * @param string $apeCode + * + * @return self + */ + public function setApeCode($apeCode) + { + $this->apeCode = new ApeCode($apeCode); + + return $this; + } + + /** + * @return string|null + */ + public function getWebsite() + { + return $this->website; + } + + /** + * @param string $website + * + * @return self + */ + public function setWebsite($website) + { + $this->website = $website; + + return $this; + } + + /** + * @return float|null + */ + public function getAllowedOutstandingAmount() + { + return $this->allowedOutstandingAmount; + } + + /** + * @param float $allowedOutstandingAmount + * + * @return self + */ + public function setAllowedOutstandingAmount($allowedOutstandingAmount) + { + $this->allowedOutstandingAmount = $allowedOutstandingAmount; + + return $this; + } + + /** + * @return int|null + */ + public function getMaxPaymentDays() + { + return $this->maxPaymentDays; + } + + /** + * @param int $maxPaymentDays + * + * @return self + */ + public function setMaxPaymentDays($maxPaymentDays) + { + $this->maxPaymentDays = $maxPaymentDays; + + return $this; + } + + /** + * @return int|null + */ + public function getRiskId() + { + return $this->riskId; + } + + /** + * @param int $riskId + * + * @return self + */ + public function setRiskId($riskId) + { + $this->riskId = $riskId; + + return $this; + } +} diff --git a/src/Core/Domain/Customer/CommandHandler/AddCustomerHandlerInterface.php b/src/Core/Domain/Customer/CommandHandler/AddCustomerHandlerInterface.php new file mode 100644 index 0000000000000..43af952223e47 --- /dev/null +++ b/src/Core/Domain/Customer/CommandHandler/AddCustomerHandlerInterface.php @@ -0,0 +1,43 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\CommandHandler; + +use PrestaShop\PrestaShop\Core\Domain\Customer\Command\AddCustomerCommand; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; + +/** + * Interface for service that handles command that adds new customer + */ +interface AddCustomerHandlerInterface +{ + /** + * @param AddCustomerCommand $command + * + * @return CustomerId + */ + public function handle(AddCustomerCommand $command); +} diff --git a/src/Core/Domain/Customer/CommandHandler/EditCustomerHandlerInterface.php b/src/Core/Domain/Customer/CommandHandler/EditCustomerHandlerInterface.php new file mode 100644 index 0000000000000..f4f791c189fd1 --- /dev/null +++ b/src/Core/Domain/Customer/CommandHandler/EditCustomerHandlerInterface.php @@ -0,0 +1,40 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\CommandHandler; + +use PrestaShop\PrestaShop\Core\Domain\Customer\Command\EditCustomerCommand; + +/** + * Interface for service that handles customer editing command + */ +interface EditCustomerHandlerInterface +{ + /** + * @param EditCustomerCommand $command + */ + public function handle(EditCustomerCommand $command); +} diff --git a/src/Core/Domain/Customer/Dto/EditableCustomer.php b/src/Core/Domain/Customer/Dto/EditableCustomer.php new file mode 100644 index 0000000000000..12ed76c98f47f --- /dev/null +++ b/src/Core/Domain/Customer/Dto/EditableCustomer.php @@ -0,0 +1,317 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\Dto; + +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Birthday; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Email; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\FirstName; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\LastName; + +/** + * Stores editable data for customer + */ +class EditableCustomer +{ + /** + * @var CustomerId + */ + private $customerId; + + /** + * @var int + */ + private $genderId; + + /** + * @var string + */ + private $firstName; + + /** + * @var string + */ + private $lastName; + + /** + * @var string + */ + private $email; + + /** + * @var Birthday + */ + private $birthday; + + /** + * @var bool + */ + private $isEnabled; + + /** + * @var bool + */ + private $isPartnerOffersSubscribed; + + /** + * @var array|int[] + */ + private $groupIds; + + /** + * @var int + */ + private $defaultGroupId; + + /** + * @var string + */ + private $companyName; + + /** + * @var string + */ + private $siretCode; + + /** + * @var string + */ + private $apeCode; + + /** + * @var string + */ + private $website; + + /** + * @var float + */ + private $allowedOutstandingAmount; + + /** + * @var int + */ + private $maxPaymentDays; + + /** + * @var int + */ + private $riskId; + + /** + * @param CustomerId $customerId + * @param int $genderId + * @param FirstName $firstName + * @param LastName $lastName + * @param Email $email + * @param Birthday $birthday + * @param bool $isEnabled + * @param bool $isPartnerOffersSubscribed + * @param int[] $groupIds + * @param int $defaultGroupId + * @param string $companyName + * @param string $siretCode + * @param string $apeCode + * @param string $website + * @param float $allowedOutstandingAmount + * @param int $maxPaymentDays + * @param int $riskId + */ + public function __construct( + CustomerId $customerId, + $genderId, + FirstName $firstName, + LastName $lastName, + Email $email, + Birthday $birthday, + $isEnabled, + $isPartnerOffersSubscribed, + array $groupIds, + $defaultGroupId, + $companyName, + $siretCode, + $apeCode, + $website, + $allowedOutstandingAmount, + $maxPaymentDays, + $riskId + ) { + $this->customerId = $customerId; + $this->genderId = $genderId; + $this->firstName = $firstName; + $this->lastName = $lastName; + $this->email = $email; + $this->birthday = $birthday; + $this->isEnabled = $isEnabled; + $this->isPartnerOffersSubscribed = $isPartnerOffersSubscribed; + $this->groupIds = $groupIds; + $this->defaultGroupId = $defaultGroupId; + $this->companyName = $companyName; + $this->siretCode = $siretCode; + $this->apeCode = $apeCode; + $this->website = $website; + $this->allowedOutstandingAmount = $allowedOutstandingAmount; + $this->maxPaymentDays = $maxPaymentDays; + $this->riskId = $riskId; + } + + /** + * @return CustomerId + */ + public function getCustomerId() + { + return $this->customerId; + } + + /** + * @return int + */ + public function getGenderId() + { + return $this->genderId; + } + + /** + * @return FirstName + */ + public function getFirstName() + { + return $this->firstName; + } + + /** + * @return LastName + */ + public function getLastName() + { + return $this->lastName; + } + + /** + * @return Email + */ + public function getEmail() + { + return $this->email; + } + + /** + * @return Birthday + */ + public function getBirthday() + { + return $this->birthday; + } + + /** + * @return bool + */ + public function isEnabled() + { + return $this->isEnabled; + } + + /** + * @return bool + */ + public function isPartnerOffersSubscribed() + { + return $this->isPartnerOffersSubscribed; + } + + /** + * @return array|int[] + */ + public function getGroupIds() + { + return $this->groupIds; + } + + /** + * @return int + */ + public function getDefaultGroupId() + { + return $this->defaultGroupId; + } + + /** + * @return string + */ + public function getCompanyName() + { + return $this->companyName; + } + + /** + * @return string + */ + public function getSiretCode() + { + return $this->siretCode; + } + + /** + * @return string + */ + public function getApeCode() + { + return $this->apeCode; + } + + /** + * @return string + */ + public function getWebsite() + { + return $this->website; + } + + /** + * @return float + */ + public function getAllowedOutstandingAmount() + { + return $this->allowedOutstandingAmount; + } + + /** + * @return int + */ + public function getMaxPaymentDays() + { + return $this->maxPaymentDays; + } + + /** + * @return int + */ + public function getRiskId() + { + return $this->riskId; + } +} diff --git a/src/Core/Domain/Customer/Exception/CustomerConstraintException.php b/src/Core/Domain/Customer/Exception/CustomerConstraintException.php index a6124857253e7..cd427299b224f 100644 --- a/src/Core/Domain/Customer/Exception/CustomerConstraintException.php +++ b/src/Core/Domain/Customer/Exception/CustomerConstraintException.php @@ -26,10 +26,43 @@ namespace PrestaShop\PrestaShop\Core\Domain\Customer\Exception; +/** + * Is thrown when customer constraint is violated + */ class CustomerConstraintException extends CustomerException { + /** + * @var int Code is used when invalid email is provided for customer + */ + const INVALID_EMAIL = 1; + + /** + * @var int Code is used when invalid first name is provided for customer + */ + const INVALID_FIRST_NAME = 2; + + /** + * @var int Code is used when invalid last name is provided for customer + */ + const INVALID_LAST_NAME = 3; + + /** + * @var int Code is used when invalid password is provided for customer + */ + const INVALID_PASSWORD = 4; + + /** + * @var int Code is used when invalid APE code is provided + */ + const INVALID_APE_CODE = 5; + /** * @var int Is used when invalid (not string) private note is provided as private note */ - const INVALID_PRIVATE_NOTE = 1; + const INVALID_PRIVATE_NOTE = 6; + + /** + * @var int Code is used when invalid customer birthday is provided + */ + const INVALID_BIRTHDAY = 7; } diff --git a/src/Core/Domain/Customer/Exception/CustomerDefaultGroupAccessException.php b/src/Core/Domain/Customer/Exception/CustomerDefaultGroupAccessException.php new file mode 100644 index 0000000000000..19ccc9820b817 --- /dev/null +++ b/src/Core/Domain/Customer/Exception/CustomerDefaultGroupAccessException.php @@ -0,0 +1,35 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\Exception; + +/** + * Exception is thrown when customer's default groups is not configured as access group. + * This means that default group must also be configured as access group for customer. + */ +class CustomerDefaultGroupAccessException extends CustomerException +{ +} diff --git a/src/Core/Domain/Customer/Exception/DuplicateCustomerEmailException.php b/src/Core/Domain/Customer/Exception/DuplicateCustomerEmailException.php new file mode 100644 index 0000000000000..acce3f1f87bb2 --- /dev/null +++ b/src/Core/Domain/Customer/Exception/DuplicateCustomerEmailException.php @@ -0,0 +1,61 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\Exception; + +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Email; + +/** + * Exception is thrown when email which already exists is being used to create or update other customer + */ +class DuplicateCustomerEmailException extends CustomerException +{ + /** + * @var Email + */ + private $email; + + /** + * @param Email $email + * @param string $message + * @param int $code + * @param null $previous + */ + public function __construct(Email $email, $message = '', $code = 0, $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->email = $email; + } + + /** + * @return Email + */ + public function getEmail() + { + return $this->email; + } +} diff --git a/src/Core/Domain/Customer/Query/GetCustomerForEditing.php b/src/Core/Domain/Customer/Query/GetCustomerForEditing.php new file mode 100644 index 0000000000000..fe39ef2e4aaef --- /dev/null +++ b/src/Core/Domain/Customer/Query/GetCustomerForEditing.php @@ -0,0 +1,56 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\Query; + +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; + +/** + * Gets customer information for editing. + */ +final class GetCustomerForEditing +{ + /** + * @var CustomerId + */ + private $customerId; + + /** + * @param CustomerId $customerId + */ + public function __construct(CustomerId $customerId) + { + $this->customerId = $customerId; + } + + /** + * @return CustomerId + */ + public function getCustomerId() + { + return $this->customerId; + } +} diff --git a/src/Core/Domain/Customer/QueryHandler/GetCustomerForEditingHandlerInterface.php b/src/Core/Domain/Customer/QueryHandler/GetCustomerForEditingHandlerInterface.php new file mode 100644 index 0000000000000..5a193f6f40329 --- /dev/null +++ b/src/Core/Domain/Customer/QueryHandler/GetCustomerForEditingHandlerInterface.php @@ -0,0 +1,43 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\QueryHandler; + +use PrestaShop\PrestaShop\Core\Domain\Customer\Dto\EditableCustomer; +use PrestaShop\PrestaShop\Core\Domain\Customer\Query\GetCustomerForEditing; + +/** + * Interface for service that implements getting customer for editing + */ +interface GetCustomerForEditingHandlerInterface +{ + /** + * @param GetCustomerForEditing $query + * + * @return EditableCustomer + */ + public function handle(GetCustomerForEditing $query); +} diff --git a/src/Core/Domain/Customer/ValueObject/ApeCode.php b/src/Core/Domain/Customer/ValueObject/ApeCode.php new file mode 100644 index 0000000000000..46aad0abc18e4 --- /dev/null +++ b/src/Core/Domain/Customer/ValueObject/ApeCode.php @@ -0,0 +1,75 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject; + +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; + +/** + * Every business in France is classified under an activity code + * entitled APE - Activite Principale de l’Entreprise + */ +class ApeCode +{ + /** + * @var string + */ + private $code; + + /** + * @param $code + */ + public function __construct($code) + { + $this->assertIsApeCode($code); + + $this->code = $code; + } + + /** + * @return string + */ + public function getValue() + { + return $this->code; + } + + private function assertIsApeCode($code) + { + if (is_string($code) && empty($code)) { + return; + } + + $isApeCode = is_string($code) && (bool) preg_match('/^\d{3,4}[a-zA-Z]{1}$/', $code); + + if (!$isApeCode) { + throw new CustomerConstraintException( + sprintf('Invalid ape code %s provided', var_export($code, true)), + CustomerConstraintException::INVALID_APE_CODE + ); + } + } +} diff --git a/src/Core/Domain/Customer/ValueObject/Birthday.php b/src/Core/Domain/Customer/ValueObject/Birthday.php new file mode 100644 index 0000000000000..60b3324c54e97 --- /dev/null +++ b/src/Core/Domain/Customer/ValueObject/Birthday.php @@ -0,0 +1,127 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject; + +use DateTime; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; + +/** + * Defines rules for customer birthday and stores it's value + */ +class Birthday +{ + /** + * @var string empty birthday value + * + * It is used as a placeholder value when real birthday is not provided + */ + const EMPTY_BIRTHDAY = '0000-00-00'; + + /** + * @var string Date in format of Y-m-d or empty string for non defined birthday + */ + private $birthday; + + /** + * @return Birthday + */ + public static function createEmpty() + { + return new self(self::EMPTY_BIRTHDAY); + } + + /** + * @param string $birthday + */ + public function __construct($birthday) + { + $this->assertBirthdayIsInValidFormat($birthday); + $this->assertBirthdayIsNotAFutureDate($birthday); + + $this->birthday = $birthday; + } + + /** + * @return string + */ + public function getValue() + { + return $this->birthday; + } + + /** + * @return bool + */ + public function isEmpty() + { + return self::EMPTY_BIRTHDAY === $this->birthday; + } + + /** + * Birthday cannot be date in a future + * + * @param string $birthday + */ + private function assertBirthdayIsNotAFutureDate($birthday) + { + if (self::EMPTY_BIRTHDAY === $birthday) { + return; + } + + $birthdayDateTime = new DateTime($birthday); + $now = new DateTime(); + + if ($birthdayDateTime > $now) { + throw new CustomerConstraintException( + sprintf( + 'Invalid birthday "%s" provided. Birthday must be a past date.', + $birthdayDateTime->format('Y-m-d') + ), + CustomerConstraintException::INVALID_BIRTHDAY + ); + } + } + + /** + * Assert that birthday is actual date + * + * @param string $birthday + */ + private function assertBirthdayIsInValidFormat($birthday) + { + if (self::EMPTY_BIRTHDAY === $birthday) { + return; + } + + if (!is_string($birthday) || false === strtotime($birthday)) { + throw new CustomerConstraintException( + sprintf('Invalid birthday %s value provided.', var_export($birthday, true)), + CustomerConstraintException::INVALID_BIRTHDAY + ); + } + } +} diff --git a/src/Core/Domain/Customer/ValueObject/Email.php b/src/Core/Domain/Customer/ValueObject/Email.php new file mode 100644 index 0000000000000..4091fd39883fc --- /dev/null +++ b/src/Core/Domain/Customer/ValueObject/Email.php @@ -0,0 +1,131 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject; + +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; + +/** + * Stores customer's email address + */ +class Email +{ + /** + * @var int Maximum allowed length for customer email + */ + const MAX_LENGTH = 255; + + /** + * @var string + */ + private $email; + + /** + * @param string $email + */ + public function __construct($email) + { + $this->assertEmailIsString($email); + $this->assertEmailDoesNotExceedAllowedLength($email); + $this->assertEmailIsValid($email); + + $this->email = $email; + } + + /** + * @return string + */ + public function getValue() + { + return $this->email; + } + + /** + * Check if given email is the same as current + * + * @param Email $email + * + * @return bool + */ + public function isEqualTo(Email $email) + { + return $email->getValue() === $this->getValue(); + } + + /** + * Assert that email is in valid format + * + * @param string $email + * + * @throws CustomerConstraintException + */ + private function assertEmailIsValid($email) + { + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + throw new CustomerConstraintException( + sprintf('Customer email %s is invalid.', var_export($email, true)), + CustomerConstraintException::INVALID_EMAIL + ); + } + } + + /** + * Assert that email length does not exceed allowed value + * + * @param string $email + * + * @throws CustomerConstraintException + */ + private function assertEmailDoesNotExceedAllowedLength($email) + { + $email = html_entity_decode($email, ENT_COMPAT, 'UTF-8'); + + $length = function_exists('mb_strlen') ? mb_strlen($email, 'UTF-8') : strlen($email); + if (self::MAX_LENGTH < $length) { + throw new CustomerConstraintException( + sprintf('Customer email is too long. Max allowed length is %s', self::MAX_LENGTH), + CustomerConstraintException::INVALID_EMAIL + ); + } + } + + /** + * Assert email is of type string + * + * @param string $email + * + * @throws CustomerConstraintException + */ + private function assertEmailIsString($email) + { + if (!is_string($email)) { + throw new CustomerConstraintException( + 'Customer email must be of type string', + CustomerConstraintException::INVALID_EMAIL + ); + } + } +} diff --git a/src/Core/Domain/Customer/ValueObject/FirstName.php b/src/Core/Domain/Customer/ValueObject/FirstName.php new file mode 100644 index 0000000000000..6806400436789 --- /dev/null +++ b/src/Core/Domain/Customer/ValueObject/FirstName.php @@ -0,0 +1,99 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject; + +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; + +/** + * Stores customer's first name + */ +class FirstName +{ + /** + * @var string Maximum allowed length for first name + */ + const MAX_LENGTH = 255; + + /** + * @var string + */ + private $firstName; + + /** + * @param string $firstName + */ + public function __construct($firstName) + { + $this->assertFirstNameDoesNotExceedAllowedLength($firstName); + $this->assertFirstNameIsValid($firstName); + + $this->firstName = $firstName; + } + + /** + * @return string + */ + public function getValue() + { + return $this->firstName; + } + + /** + * @param string $firstName + * + * @throws CustomerConstraintException + */ + private function assertFirstNameIsValid($firstName) + { + $matchesFirstNamePattern = preg_match('/^[^0-9!<>,;?=+()@#"°{}_$%:¤|]*$/u', stripslashes($firstName)); + + if (!$matchesFirstNamePattern) { + throw new CustomerConstraintException( + sprintf('Customer first name %s is invalid', var_export($firstName, true)), + CustomerConstraintException::INVALID_FIRST_NAME + ); + } + } + + /** + * @param string $firstName + * + * @throws CustomerConstraintException + */ + private function assertFirstNameDoesNotExceedAllowedLength($firstName) + { + $firstName = html_entity_decode($firstName, ENT_COMPAT, 'UTF-8'); + + $length = function_exists('mb_strlen') ? mb_strlen($firstName, 'UTF-8') : strlen($firstName); + if (self::MAX_LENGTH < $length) { + throw new CustomerConstraintException( + sprintf('Customer first name is too long. Max allowed length is %s', self::MAX_LENGTH), + CustomerConstraintException::INVALID_FIRST_NAME + ); + } + } +} diff --git a/src/Core/Domain/Customer/ValueObject/LastName.php b/src/Core/Domain/Customer/ValueObject/LastName.php new file mode 100644 index 0000000000000..c688d34efc143 --- /dev/null +++ b/src/Core/Domain/Customer/ValueObject/LastName.php @@ -0,0 +1,99 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject; + +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; + +/** + * Stores customer's last name + */ +class LastName +{ + /** + * @var int Maximum allowed length for customer's last name + */ + const MAX_LENGTH = 255; + + /** + * @var string + */ + private $lastName; + + /** + * @param string $lastName + */ + public function __construct($lastName) + { + $this->assertLastNameDoesNotExceedAllowedLength($lastName); + $this->assertLastNameIsValid($lastName); + + $this->lastName = $lastName; + } + + /** + * @return string + */ + public function getValue() + { + return $this->lastName; + } + + /** + * @param string $lastName + * + * @throws CustomerConstraintException + */ + private function assertLastNameIsValid($lastName) + { + $matchesLastNamePattern = preg_match('/^[^0-9!<>,;?=+()@#"°{}_$%:¤|]*$/u', stripslashes($lastName)); + + if (!$matchesLastNamePattern) { + throw new CustomerConstraintException( + sprintf('Customer last name %s is invalid', var_export($lastName, true)), + CustomerConstraintException::INVALID_LAST_NAME + ); + } + } + + /** + * @param string $lastName + * + * @throws CustomerConstraintException + */ + private function assertLastNameDoesNotExceedAllowedLength($lastName) + { + $lastName = html_entity_decode($lastName, ENT_COMPAT, 'UTF-8'); + + $length = function_exists('mb_strlen') ? mb_strlen($lastName, 'UTF-8') : strlen($lastName); + if (self::MAX_LENGTH < $length) { + throw new CustomerConstraintException( + sprintf('Customer email is too long. Max allowed length is %s', self::MAX_LENGTH), + CustomerConstraintException::INVALID_LAST_NAME + ); + } + } +} diff --git a/src/Core/Domain/Customer/ValueObject/Password.php b/src/Core/Domain/Customer/ValueObject/Password.php new file mode 100644 index 0000000000000..0f5cd02c3bdc9 --- /dev/null +++ b/src/Core/Domain/Customer/ValueObject/Password.php @@ -0,0 +1,86 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject; + +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; + +/** + * Stores customer's plain password + */ +class Password +{ + /** + * @var string Minimum required password length for customer + */ + const MIN_LENGTH = 5; + + /** + * @var string Maximum allowed password length for customer. + * + * It's limited to 72 chars because of PASSWORD_BCRYPT algorithm + * used in password_hash() function. + */ + const MAX_LENGTH = 72; + + /** + * @var string + */ + private $password; + + /** + * @param string $password + */ + public function __construct($password) + { + $this->assertPasswordIsWithinAllowedLength($password); + + $this->password = $password; + } + + /** + * @return string + */ + public function getValue() + { + return $this->password; + } + + /** + * @param string $password + */ + private function assertPasswordIsWithinAllowedLength($password) + { + $length = function_exists('mb_strlen') ? mb_strlen($password, 'UTF-8') : strlen($password); + + if (self::MIN_LENGTH > $length || $length > self::MAX_LENGTH) { + throw new CustomerConstraintException( + sprintf('Customer password length must be between %s and %s', self::MIN_LENGTH, self::MAX_LENGTH), + CustomerConstraintException::INVALID_PASSWORD + ); + } + } +} diff --git a/src/Core/Form/IdentifiableObject/DataHandler/CustomerFormDataHandler.php b/src/Core/Form/IdentifiableObject/DataHandler/CustomerFormDataHandler.php new file mode 100644 index 0000000000000..c22a9ef46aaf5 --- /dev/null +++ b/src/Core/Form/IdentifiableObject/DataHandler/CustomerFormDataHandler.php @@ -0,0 +1,180 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler; + +use PrestaShop\PrestaShop\Core\CommandBus\CommandBusInterface; +use PrestaShop\PrestaShop\Core\Domain\Customer\Command\AddCustomerCommand; +use PrestaShop\PrestaShop\Core\Domain\Customer\Command\EditCustomerCommand; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Birthday; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; + +/** + * Saves or updates customer data submitted in form + */ +final class CustomerFormDataHandler implements FormDataHandlerInterface +{ + /** + * @var CommandBusInterface + */ + private $bus; + + /** + * @var int + */ + private $contextShopId; + + /** + * @var bool + */ + private $isB2bFeatureEnabled; + + /** + * @param CommandBusInterface $bus + * @param int $contextShopId + * @param bool $isB2bFeatureEnabled + */ + public function __construct( + CommandBusInterface $bus, + $contextShopId, + $isB2bFeatureEnabled + ) { + $this->bus = $bus; + $this->contextShopId = $contextShopId; + $this->isB2bFeatureEnabled = $isB2bFeatureEnabled; + } + + /** + * {@inheritdoc} + */ + public function create(array $data) + { + $command = $this->buildCustomerAddCommandFromFormData($data); + + /** @var CustomerId $customerId */ + $customerId = $this->bus->handle($command); + + return $customerId->getValue(); + } + + /** + * {@inheritdoc} + */ + public function update($customerId, array $data) + { + $command = $this->buildCustomerEditCommand($customerId, $data); + + /** @var CustomerId $customerId */ + $customerId = $this->bus->handle($command); + + return $customerId->getValue(); + } + + /** + * @param array $data + * + * @return AddCustomerCommand + */ + private function buildCustomerAddCommandFromFormData(array $data) + { + $groupIds = array_map(function ($groupId) { + return (int) $groupId; + }, $data['group_ids']); + + $command = new AddCustomerCommand( + $data['first_name'], + $data['last_name'], + $data['email'], + $data['password'], + (int) $data['default_group_id'], + $groupIds, + $this->contextShopId, + (int) $data['gender_id'], + (bool) $data['is_enabled'], + (bool) $data['is_partner_offers_subscribed'], + $data['birthday'] ?: Birthday::EMPTY_BIRTHDAY + ); + + if (!$this->isB2bFeatureEnabled) { + return $command; + } + + $command + ->setCompanyName((string) $data['company_name']) + ->setSiretCode((string) $data['siret_code']) + ->setApeCode((string) $data['ape_code']) + ->setWebsite((string) $data['website']) + ->setAllowedOutstandingAmount((float) $data['allowed_outstanding_amount']) + ->setMaxPaymentDays((int) $data['max_payment_days']) + ->setRiskId((int) $data['risk_id']) + ; + + return $command; + } + + /** + * @param int $customerId + * @param array $data + * + * @return EditCustomerCommand + */ + private function buildCustomerEditCommand($customerId, array $data) + { + $groupIds = array_map(function ($groupId) { + return (int) $groupId; + }, $data['group_ids']); + + $command = (new EditCustomerCommand($customerId)) + ->setGenderId($data['gender_id']) + ->setEmail($data['email']) + ->setFirstName($data['first_name']) + ->setLastName($data['last_name']) + ->setIsEnabled($data['is_enabled']) + ->setIsPartnerOffersSubscribed($data['is_partner_offers_subscribed']) + ->setDefaultGroupId((int) $data['default_group_id']) + ->setGroupIds($groupIds) + ->setBirthday($data['birthday'] ?: Birthday::EMPTY_BIRTHDAY) + ; + + if (null !== $data['password']) { + $command->setPassword($data['password']); + } + + if ($this->isB2bFeatureEnabled) { + $command + ->setCompanyName((string) $data['company_name']) + ->setSiretCode((string) $data['siret_code']) + ->setApeCode((string) $data['ape_code']) + ->setWebsite((string) $data['website']) + ->setAllowedOutstandingAmount((float) $data['allowed_outstanding_amount']) + ->setMaxPaymentDays((int) $data['max_payment_days']) + ->setRiskId((int) $data['risk_id']) + ; + } + + return $command; + } +} diff --git a/src/Core/Form/IdentifiableObject/DataProvider/CustomerFormDataProvider.php b/src/Core/Form/IdentifiableObject/DataProvider/CustomerFormDataProvider.php new file mode 100644 index 0000000000000..96912859bc7aa --- /dev/null +++ b/src/Core/Form/IdentifiableObject/DataProvider/CustomerFormDataProvider.php @@ -0,0 +1,142 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataProvider; + +use PrestaShop\PrestaShop\Core\CommandBus\CommandBusInterface; +use PrestaShop\PrestaShop\Core\ConfigurationInterface; +use PrestaShop\PrestaShop\Core\Domain\Customer\Dto\EditableCustomer; +use PrestaShop\PrestaShop\Core\Domain\Customer\Query\GetCustomerForEditing; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; +use PrestaShop\PrestaShop\Core\Group\Provider\DefaultGroupsProviderInterface; + +/** + * Provides data for customer forms + */ +final class CustomerFormDataProvider implements FormDataProviderInterface +{ + /** + * @var CommandBusInterface + */ + private $queryBus; + + /** + * @var ConfigurationInterface + */ + private $configuration; + + /** + * @var DefaultGroupsProviderInterface + */ + private $defaultGroupsProvider; + + /** + * @var bool + */ + private $isB2bFeatureEnabled; + + /** + * @param CommandBusInterface $queryBus + * @param ConfigurationInterface $configuration + * @param DefaultGroupsProviderInterface $defaultGroupsProvider + * @param bool $isB2bFeatureEnabled + */ + public function __construct( + CommandBusInterface $queryBus, + ConfigurationInterface $configuration, + DefaultGroupsProviderInterface $defaultGroupsProvider, + $isB2bFeatureEnabled + ) { + $this->queryBus = $queryBus; + $this->configuration = $configuration; + $this->defaultGroupsProvider = $defaultGroupsProvider; + $this->isB2bFeatureEnabled = $isB2bFeatureEnabled; + } + + /** + * {@inheritdoc} + */ + public function getData($customerId) + { + /** @var EditableCustomer $editableCustomer */ + $editableCustomer = $this->queryBus->handle(new GetCustomerForEditing(new CustomerId((int) $customerId))); + $birthday = $editableCustomer->getBirthday(); + + $data = [ + 'gender_id' => $editableCustomer->getGenderId(), + 'first_name' => $editableCustomer->getFirstName()->getValue(), + 'last_name' => $editableCustomer->getLastName()->getValue(), + 'email' => $editableCustomer->getEmail()->getValue(), + 'birthday' => $birthday->isEmpty() ? null : $birthday->getValue(), + 'is_enabled' => $editableCustomer->isEnabled(), + 'is_partner_offers_subscribed' => $editableCustomer->isPartnerOffersSubscribed(), + 'group_ids' => $editableCustomer->getGroupIds(), + 'default_group_id' => $editableCustomer->getDefaultGroupId(), + ]; + + if ($this->isB2bFeatureEnabled) { + $data = array_merge($data, [ + 'company_name' => $editableCustomer->getCompanyName(), + 'siret_code' => $editableCustomer->getSiretCode(), + 'ape_code' => $editableCustomer->getApeCode(), + 'website' => $editableCustomer->getWebsite(), + 'allowed_outstanding_amount' => $editableCustomer->getAllowedOutstandingAmount(), + 'max_payment_days' => $editableCustomer->getMaxPaymentDays(), + 'risk_id' => $editableCustomer->getRiskId(), + ]); + } + + return $data; + } + + /** + * {@inheritdoc} + */ + public function getDefaultData() + { + $defaultGroups = $this->defaultGroupsProvider->getGroups(); + + $data = [ + 'is_enabled' => true, + 'is_partner_offers_subscribed' => false, + 'group_ids' => [ + $defaultGroups->getVisitorsGroup()->getId(), + $defaultGroups->getGuestsGroup()->getId(), + $defaultGroups->getCustomersGroup()->getId(), + ], + 'default_group_id' => (int) $this->configuration->get('PS_CUSTOMER_GROUP'), + ]; + + if ($this->isB2bFeatureEnabled) { + $data = array_merge($data, [ + 'allowed_outstanding_amount' => 0, + 'max_payment_days' => 0, + ]); + } + + return $data; + } +} diff --git a/src/Core/Group/Provider/DefaultGroup.php b/src/Core/Group/Provider/DefaultGroup.php new file mode 100644 index 0000000000000..20cf99d1211db --- /dev/null +++ b/src/Core/Group/Provider/DefaultGroup.php @@ -0,0 +1,69 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Group\Provider; + +/** + * Stores information for default group + */ +class DefaultGroup +{ + /** + * @var int + */ + private $groupId; + + /** + * @var string + */ + private $name; + + /** + * @param int $groupId + * @param string $name + */ + public function __construct($groupId, $name) + { + $this->groupId = $groupId; + $this->name = $name; + } + + /** + * @return int + */ + public function getId() + { + return $this->groupId; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } +} diff --git a/src/Core/Group/Provider/DefaultGroups.php b/src/Core/Group/Provider/DefaultGroups.php new file mode 100644 index 0000000000000..26943cae66753 --- /dev/null +++ b/src/Core/Group/Provider/DefaultGroups.php @@ -0,0 +1,104 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Group\Provider; + +/** + * Stores default group options + */ +class DefaultGroups +{ + /** + * @var DefaultGroup + */ + private $visitorsGroup; + + /** + * @var DefaultGroup + */ + private $guestsGroup; + + /** + * @var DefaultGroup + */ + private $customersGroup; + + /** + * @param DefaultGroup $visitorsGroup + * @param DefaultGroup $guestsGroup + * @param DefaultGroup $customersGroup + */ + public function __construct(DefaultGroup $visitorsGroup, DefaultGroup $guestsGroup, DefaultGroup $customersGroup) + { + $this->visitorsGroup = $visitorsGroup; + $this->guestsGroup = $guestsGroup; + $this->customersGroup = $customersGroup; + } + + /** + * Get default visitors group + * + * @return DefaultGroup + */ + public function getVisitorsGroup() + { + return $this->visitorsGroup; + } + + /** + * Get default guests group + * + * @return DefaultGroup + */ + public function getGuestsGroup() + { + return $this->guestsGroup; + } + + /** + * Get customers group + * + * @return DefaultGroup + */ + public function getCustomersGroup() + { + return $this->customersGroup; + } + + /** + * Get default groups + * + * @return DefaultGroup[] + */ + public function getGroups() + { + return [ + $this->getVisitorsGroup(), + $this->getGuestsGroup(), + $this->getCustomersGroup(), + ]; + } +} diff --git a/src/Core/Group/Provider/DefaultGroupsProviderInterface.php b/src/Core/Group/Provider/DefaultGroupsProviderInterface.php new file mode 100644 index 0000000000000..6911b70040089 --- /dev/null +++ b/src/Core/Group/Provider/DefaultGroupsProviderInterface.php @@ -0,0 +1,38 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShop\PrestaShop\Core\Group\Provider; + +/** + * Interface for service that retrieves default customer group options + */ +interface DefaultGroupsProviderInterface +{ + /** + * @return DefaultGroups + */ + public function getGroups(); +} diff --git a/src/PrestaShopBundle/Controller/Admin/FrameworkBundleAdminController.php b/src/PrestaShopBundle/Controller/Admin/FrameworkBundleAdminController.php index f6754af337908..696f797530871 100644 --- a/src/PrestaShopBundle/Controller/Admin/FrameworkBundleAdminController.php +++ b/src/PrestaShopBundle/Controller/Admin/FrameworkBundleAdminController.php @@ -27,6 +27,7 @@ namespace PrestaShopBundle\Controller\Admin; use PrestaShop\PrestaShop\Adapter\Configuration; +use Exception; use PrestaShop\PrestaShop\Adapter\Shop\Context; use PrestaShop\PrestaShop\Core\ConfigurationInterface; use PrestaShop\PrestaShop\Core\Grid\GridInterface; @@ -162,7 +163,7 @@ protected function dispatchHook($hookName, array $parameters) * * @return array The responses of hooks * - * @throws \Exception + * @throws Exception */ protected function renderHook($hookName, array $parameters) { @@ -333,7 +334,7 @@ protected function actionIsAllowed($action, $object = '', $suffix = '') * * @return string * - * @throws \Exception + * @throws Exception */ protected function getForbiddenActionMessage($action, $suffix = '') { @@ -349,7 +350,7 @@ protected function getForbiddenActionMessage($action, $suffix = '') return $this->trans('You do not have permission to add this.', 'Admin.Notifications.Error'); } - throw new \Exception(sprintf('Invalid action (%s)', $action . $suffix)); + throw new Exception(sprintf('Invalid action (%s)', $action . $suffix)); } /** @@ -448,4 +449,35 @@ protected function getContextShopId() { return $this->getContext()->shop->id; } + + /** + * Get error by exception from given messages + * + * @param Exception $e + * @param array $messages + * + * @return string + */ + protected function getErrorMessageForException(Exception $e, array $messages) + { + $exceptionType = get_class($e); + $exceptionCode = $e->getCode(); + + if (isset($messages[$exceptionType])) { + $message = $messages[$exceptionType]; + + if (is_string($message)) { + return $message; + } + + if (is_array($message) && isset($message[$exceptionCode])) { + return $message[$exceptionCode]; + } + } + + return $this->getFallbackErrorMessage( + $exceptionType, + $exceptionCode + ); + } } diff --git a/src/PrestaShopBundle/Controller/Admin/Sell/Customer/CustomerController.php b/src/PrestaShopBundle/Controller/Admin/Sell/Customer/CustomerController.php index 077dbbfbeee13..ee8502df0e7a1 100644 --- a/src/PrestaShopBundle/Controller/Admin/Sell/Customer/CustomerController.php +++ b/src/PrestaShopBundle/Controller/Admin/Sell/Customer/CustomerController.php @@ -27,16 +27,20 @@ namespace PrestaShopBundle\Controller\Admin\Sell\Customer; use PrestaShop\PrestaShop\Core\Domain\Customer\Command\SavePrivateNoteForCustomerCommand; -use PrestaShop\PrestaShop\Core\Domain\Customer\Command\SetRequiredFieldsForCustomerCommand; use PrestaShop\PrestaShop\Core\Domain\Customer\Command\TransformGuestToCustomerCommand; -use PrestaShop\PrestaShop\Core\Domain\Customer\Dto\CustomerInformation; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerDefaultGroupAccessException; use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerException; -use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerNotFoundException; use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerTransformationException; -use PrestaShop\PrestaShop\Core\Domain\Customer\Query\GetCustomerForViewing; +use PrestaShop\PrestaShop\Core\Domain\Customer\Command\SetRequiredFieldsForCustomerCommand; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\DuplicateCustomerEmailException; use PrestaShop\PrestaShop\Core\Domain\Customer\Query\GetRequiredFieldsForCustomer; -use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Password; use PrestaShop\PrestaShop\Core\Search\Filters\CustomerFilters; +use PrestaShop\PrestaShop\Core\Domain\Customer\Dto\CustomerInformation; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerNotFoundException; +use PrestaShop\PrestaShop\Core\Domain\Customer\Query\GetCustomerForViewing; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController as AbstractAdminController; use PrestaShopBundle\Form\Admin\Sell\Customer\PrivateNoteType; use PrestaShopBundle\Form\Admin\Sell\Customer\RequiredFieldsType; @@ -73,6 +77,7 @@ public function indexAction(Request $request, CustomerFilters $filters) 'customerGrid' => $this->presentGrid($customerGrid), 'customersKpi' => $customersKpiFactory->build(), 'customerRequiredFieldsForm' => $this->getRequiredFieldsForm()->createView(), + 'isSingleShopContext' => $this->get('prestashop.adapter.shop.context')->isSingleShopContext(), ]); } @@ -85,11 +90,34 @@ public function indexAction(Request $request, CustomerFilters $filters) */ public function createAction(Request $request) { - return $this->redirect( - $this->getAdminLink($request->attributes->get('_legacy_controller'), [ - 'addcustomer' => 1, - ]) - ); + if (!$this->get('prestashop.adapter.shop.context')->isSingleShopContext()) { + return $this->redirectToRoute('admin_customers_index'); + } + + $this->addGroupSelectionToRequest($request); + + $customerForm = $this->get('prestashop.core.form.identifiable_object.builder.customer_form_builder')->getForm(); + $customerForm->handleRequest($request); + + $customerFormHandler = $this->get('prestashop.core.form.identifiable_object.handler.customer_form_handler'); + + try { + $result = $customerFormHandler->handle($customerForm); + + if ($customerId = $result->getIdentifiableObjectId()) { + $this->addFlash('success', $this->trans('Successful creation.', 'Admin.Notifications.Success')); + + return $this->redirectToRoute('admin_customers_index'); + } + } catch (CustomerException $e) { + $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); + } + + return $this->render('@PrestaShop/Admin/Sell/Customer/create.html.twig', [ + 'customerForm' => $customerForm->createView(), + 'isB2bFeatureActive' => $this->get('prestashop.core.b2b.b2b_feature')->isActive(), + 'minPasswordLength' => Password::MIN_LENGTH, + ]); } /** @@ -102,12 +130,42 @@ public function createAction(Request $request) */ public function editAction($customerId, Request $request) { - return $this->redirect( - $this->getAdminLink($request->attributes->get('_legacy_controller'), [ - 'updatecustomer' => 1, - 'id_customer' => $customerId, - ]) - ); + $this->addGroupSelectionToRequest($request); + + try { + /** @var CustomerInformation $customerInformation */ + $customerInformation = $this->getQueryBus()->handle(new GetCustomerForViewing(new CustomerId((int) $customerId))); + + $customerFormOptions = [ + 'is_password_required' => false, + ]; + $customerForm = $this->get('prestashop.core.form.identifiable_object.builder.customer_form_builder') + ->getFormFor((int) $customerId, [], $customerFormOptions) + ; + $customerForm->handleRequest($request); + + $customerFormHandler = $this->get('prestashop.core.form.identifiable_object.handler.customer_form_handler'); + $result = $customerFormHandler->handleFor((int) $customerId, $customerForm); + + if (null !== $result->getIdentifiableObjectId()) { + $this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success')); + + return $this->redirectToRoute('admin_customers_index'); + } + } catch (CustomerException $e) { + $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); + + if ($e instanceof CustomerNotFoundException) { + return $this->redirectToRoute('admin_customers_index'); + } + } + + return $this->render('@PrestaShop/Admin/Sell/Customer/edit.html.twig', [ + 'customerForm' => $customerForm->createView(), + 'customerInformation' => $customerInformation, + 'isB2bFeatureActive' => $this->get('prestashop.core.b2b.b2b_feature')->isActive(), + 'minPasswordLength' => Password::MIN_LENGTH, + ]); } /** @@ -184,17 +242,10 @@ public function savePrivateNoteAction($customerId, Request $request) )); $this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success')); - } catch (CustomerNotFoundException $e) { - $this->addFlash( - 'error', - $this->trans('This customer does not exist.', 'Admin.Orderscustomers.Notification') - ); - - return $this->redirectToRoute('admin_customers_index'); } catch (CustomerException $e) { $this->addFlash( 'error', - $this->getFallbackErrorMessage(get_class($e), $e->getCode()) + $this->getErrorMessageForException($e, $this->getErrorMessages($e)) ); } } @@ -222,29 +273,8 @@ public function transformGuestToCustomerAction($customerId) $this->getCommandBus()->handle(new TransformGuestToCustomerCommand(new CustomerId((int) $customerId))); $this->addFlash('success', $this->trans('Successful creation.', 'Admin.Notifications.Success')); - } catch (CustomerNotFoundException $e) { - $this->addFlash( - 'error', - $this->trans('This customer does not exist.', 'Admin.Orderscustomers.Notification') - ); - - return $this->redirectToRoute('admin_customers_index'); - } catch (CustomerTransformationException $e) { - $errors = [ - CustomerTransformationException::CUSTOMER_IS_NOT_GUEST => $this->trans('This customer already exists as a non-guest.', 'Admin.Orderscustomers.Notification'), - CustomerTransformationException::TRANSFORMATION_FAILED => $this->trans('An error occurred while updating customer information.', 'Admin.Orderscustomers.Notification'), - ]; - - $error = isset($errors[$e->getCode()]) ? - $errors[$e->getCode()] : - $this->getFallbackErrorMessage(get_class($e), $e->getCode()); - - $this->addFlash('error', $error); } catch (CustomerException $e) { - $this->addFlash( - 'error', - $this->getFallbackErrorMessage(get_class($e), $e->getCode()) - ); + $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); } return $this->redirectToRoute('admin_customers_view', [ @@ -289,4 +319,96 @@ private function getRequiredFieldsForm() return $this->createForm(RequiredFieldsType::class, ['required_fields' => $requiredFields]); } + + /** + * If customer form is submitted and groups are not selected + * we add empty groups to request + * + * @param Request $request + */ + private function addGroupSelectionToRequest(Request $request) + { + if (!$request->isMethod(Request::METHOD_POST)) { + return; + } + + if (!$request->request->has('customer') + || isset($request->request->get('customer')['group_ids']) + ) { + return; + } + + $customerData = $request->request->get('customer'); + $customerData['group_ids'] = []; + + $request->request->set('customer', $customerData); + } + + /** + * Get errors that can be used to translate exceptions into user friendly messages + * + * @param CustomerException $e + * + * @return array + */ + private function getErrorMessages(CustomerException $e) + { + return [ + CustomerNotFoundException::class => $this->trans( + 'This customer does not exist.', + 'Admin.Orderscustomers.Notification' + ), + DuplicateCustomerEmailException::class => sprintf( + '%s %s', + $this->trans('An account already exists for this email address:', 'Admin.Orderscustomers.Notification'), + $e instanceof DuplicateCustomerEmailException ? $e->getEmail()->getValue() : '' + ), + CustomerDefaultGroupAccessException::class => $this->trans( + 'A default customer group must be selected in group box.', + 'Admin.Orderscustomers.Notification' + ), + CustomerConstraintException::class => [ + CustomerConstraintException::INVALID_PASSWORD => $this->trans( + 'Password should be at least %length% characters long.', + 'Admin.Orderscustomers.Help', + ['%length%' => Password::MIN_LENGTH] + ), + CustomerConstraintException::INVALID_FIRST_NAME => $this->trans( + 'The %s field is invalid.', + 'Admin.Notifications.Error', + [sprintf('"%s"', $this->trans('First name', 'Admin.Global'))] + ), + CustomerConstraintException::INVALID_LAST_NAME => $this->trans( + 'The %s field is invalid.', + 'Admin.Notifications.Error', + [sprintf('"%s"', $this->trans('Last name', 'Admin.Global'))] + ), + CustomerConstraintException::INVALID_EMAIL => $this->trans( + 'The %s field is invalid.', + 'Admin.Notifications.Error', + [sprintf('"%s"', $this->trans('Email', 'Admin.Global'))] + ), + CustomerConstraintException::INVALID_BIRTHDAY => $this->trans( + 'The %s field is invalid.', + 'Admin.Notifications.Error', + [sprintf('"%s"', $this->trans('Birthday', 'Admin.Orderscustomers.Feature'))] + ), + CustomerConstraintException::INVALID_APE_CODE => $this->trans( + 'The %s field is invalid.', + 'Admin.Notifications.Error', + [sprintf('"%s"', $this->trans('APE', 'Admin.Orderscustomers.Feature'))] + ), + ], + CustomerTransformationException::class => [ + CustomerTransformationException::CUSTOMER_IS_NOT_GUEST => $this->trans( + 'This customer already exists as a non-guest.', + 'Admin.Orderscustomers.Notification' + ), + CustomerTransformationException::TRANSFORMATION_FAILED => $this->trans( + 'An error occurred while updating customer information.', + 'Admin.Orderscustomers.Notification' + ), + ], + ]; + } } diff --git a/src/PrestaShopBundle/Form/Admin/Sell/Customer/CustomerType.php b/src/PrestaShopBundle/Form/Admin/Sell/Customer/CustomerType.php new file mode 100644 index 0000000000000..ab76c6099c379 --- /dev/null +++ b/src/PrestaShopBundle/Form/Admin/Sell/Customer/CustomerType.php @@ -0,0 +1,256 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace PrestaShopBundle\Form\Admin\Sell\Customer; + +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\FirstName; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\LastName; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Password; +use PrestaShopBundle\Form\Admin\Type\Material\MaterialChoiceTableType; +use PrestaShopBundle\Form\Admin\Type\SwitchType; +use PrestaShopBundle\Translation\TranslatorAwareTrait; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\BirthdayType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\EmailType; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\Type; + +/** + * Type is used to created form for customer add/edit actions + */ +class CustomerType extends AbstractType +{ + use TranslatorAwareTrait; + + /** + * @var array + */ + private $genderChoices; + + /** + * @var array + */ + private $groupChoices; + + /** + * @var bool + */ + private $isB2bFeatureEnabled; + + /** + * @var array + */ + private $riskChoices; + + /** + * @var bool + */ + private $isPartnerOffersEnabled; + + /** + * @param array $genderChoices + * @param array $groupChoices + * @param array $riskChoices + * @param bool $isB2bFeatureEnabled + * @param bool $isPartnerOffersEnabled + */ + public function __construct( + array $genderChoices, + array $groupChoices, + array $riskChoices, + $isB2bFeatureEnabled, + $isPartnerOffersEnabled + ) { + $this->genderChoices = $genderChoices; + $this->groupChoices = $groupChoices; + $this->isB2bFeatureEnabled = $isB2bFeatureEnabled; + $this->riskChoices = $riskChoices; + $this->isPartnerOffersEnabled = $isPartnerOffersEnabled; + } + + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('gender_id', ChoiceType::class, [ + 'choices' => $this->genderChoices, + 'multiple' => false, + 'expanded' => true, + 'required' => false, + 'placeholder' => null, + ]) + ->add('first_name', TextType::class, [ + 'constraints' => [ + new NotBlank([ + 'message' => $this->trans('This field cannot be empty', [], 'Admin.Notifications.Error'), + ]), + new Length([ + 'max' => FirstName::MAX_LENGTH, + 'maxMessage' => $this->trans( + 'This field cannot be longer than %limit% characters', + ['%limit%' => FirstName::MAX_LENGTH], + 'Admin.Notifications.Error' + ), + ]), + ], + ]) + ->add('last_name', TextType::class, [ + 'constraints' => [ + new NotBlank([ + 'message' => $this->trans('This field cannot be empty', [], 'Admin.Notifications.Error'), + ]), + new Length([ + 'max' => LastName::MAX_LENGTH, + 'maxMessage' => $this->trans( + 'This field cannot be longer than %limit% characters', + ['%limit%' => LastName::MAX_LENGTH], + 'Admin.Notifications.Error' + ), + ]), + ], + ]) + ->add('email', EmailType::class, [ + 'constraints' => [ + new NotBlank([ + 'message' => $this->trans('This field cannot be empty', [], 'Admin.Notifications.Error'), + ]), + new Email([ + 'message' => $this->trans('This field is invalid', [], 'Admin.Notifications.Error'), + ]), + ], + ]) + ->add('password', PasswordType::class, [ + 'constraints' => [ + new Length([ + 'max' => Password::MAX_LENGTH, + 'maxMessage' => $this->trans( + 'This field cannot be longer than %limit% characters', + ['%limit%' => Password::MAX_LENGTH], + 'Admin.Notifications.Error' + ), + 'min' => Password::MIN_LENGTH, + 'minMessage' => $this->trans( + 'This field cannot be shorter than %limit% characters', + ['%limit%' => Password::MIN_LENGTH], + 'Admin.Notifications.Error' + ), + ]), + ], + 'required' => $options['is_password_required'], + ]) + ->add('birthday', BirthdayType::class, [ + 'required' => false, + 'format' => 'yyyy MM dd', + 'input' => 'string', + ]) + ->add('is_enabled', SwitchType::class, [ + 'required' => false, + ]) + ->add('is_partner_offers_subscribed', SwitchType::class, [ + 'required' => false, + 'disabled' => !$this->isPartnerOffersEnabled, + ]) + ->add('group_ids', MaterialChoiceTableType::class, [ + 'empty_data' => [], + 'choices' => $this->groupChoices, + ]) + ->add('default_group_id', ChoiceType::class, [ + 'required' => false, + 'placeholder' => null, + 'choices' => $this->groupChoices, + ]) + ; + + if ($this->isB2bFeatureEnabled) { + $builder + ->add('company_name', TextType::class, [ + 'required' => false, + ]) + ->add('siret_code', TextType::class, [ + 'required' => false, + 'constraints' => [ + new Type([ + 'type' => 'numeric', + 'message' => $this->trans('This field is invalid', [], 'Admin.Notifications.Error'), + ]), + ], + ]) + ->add('ape_code', TextType::class, [ + 'required' => false, + 'constraints' => [ + new Type([ + 'type' => 'alnum', + 'message' => $this->trans('This field is invalid', [], 'Admin.Notifications.Error'), + ]), + ], + ]) + ->add('website', TextType::class, [ + 'required' => false, + ]) + ->add('allowed_outstanding_amount', NumberType::class, [ + 'scale' => 6, + 'required' => false, + 'invalid_message' => $this->trans('This field is invalid', [], 'Admin.Notifications.Error'), + ]) + ->add('max_payment_days', IntegerType::class, [ + 'required' => false, + 'invalid_message' => $this->trans('This field is invalid', [], 'Admin.Notifications.Error'), + ]) + ->add('risk_id', ChoiceType::class, [ + 'required' => false, + 'placeholder' => null, + 'choices' => $this->riskChoices, + ]) + ; + } + } + + /** + * {@inheritdoc} + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefaults([ + // password is configurable + // so it may be optional when editing customer + 'is_password_required' => true, + ]) + ->setAllowedTypes('is_password_required', 'bool') + ; + } +} diff --git a/src/PrestaShopBundle/Resources/config/services/adapter/customer.yml b/src/PrestaShopBundle/Resources/config/services/adapter/customer.yml index 8aabacffac4d3..0591d9b1ccdd4 100644 --- a/src/PrestaShopBundle/Resources/config/services/adapter/customer.yml +++ b/src/PrestaShopBundle/Resources/config/services/adapter/customer.yml @@ -2,7 +2,7 @@ services: _defaults: public: true - prestashop.adapter.customer.query_handler.get_customer_information: + prestashop.adapter.customer.query_handler.get_customer_for_viewing: class: 'PrestaShop\PrestaShop\Adapter\Customer\QueryHandler\GetCustomerForViewingHandler' arguments: - '@translator' @@ -37,3 +37,27 @@ services: tags: - name: tactician.handler command: 'PrestaShop\PrestaShop\Core\Domain\Customer\Query\GetRequiredFieldsForCustomer' + + prestashop.adapter.customer.command_handler.add_customer: + class: 'PrestaShop\PrestaShop\Adapter\Customer\CommandHandler\AddCustomerHandler' + arguments: + - '@prestashop.core.crypto.hashing' + - '@=service("prestashop.adapter.legacy.configuration").get("_COOKIE_KEY_")' + tags: + - name: tactician.handler + command: 'PrestaShop\PrestaShop\Core\Domain\Customer\Command\AddCustomerCommand' + + prestashop.adapter.customer.query_handler.get_customer_for_editing: + class: 'PrestaShop\PrestaShop\Adapter\Customer\QueryHandler\GetCustomerForEditingHandler' + tags: + - name: tactician.handler + command: 'PrestaShop\PrestaShop\Core\Domain\Customer\Query\GetCustomerForEditing' + + prestashop.adapter.customer.command_handler.edit_customer_handler: + class: 'PrestaShop\PrestaShop\Adapter\Customer\CommandHandler\EditCustomerHandler' + arguments: + - '@prestashop.core.crypto.hashing' + - '@=service("prestashop.adapter.legacy.configuration").get("_COOKIE_KEY_")' + tags: + - name: tactician.handler + command: 'PrestaShop\PrestaShop\Core\Domain\Customer\Command\EditCustomerCommand' diff --git a/src/PrestaShopBundle/Resources/config/services/adapter/form.yml b/src/PrestaShopBundle/Resources/config/services/adapter/form.yml index 208ef5e1f692c..d36ffeb686052 100644 --- a/src/PrestaShopBundle/Resources/config/services/adapter/form.yml +++ b/src/PrestaShopBundle/Resources/config/services/adapter/form.yml @@ -17,3 +17,12 @@ services: prestashop.adapter.form.choice_provider.gender_by_id_choice_provider: class: 'PrestaShop\PrestaShop\Adapter\Form\ChoiceProvider\GenderByIdChoiceProvider' + + prestashop.adapter.form.choice_provider.group_by_id_choice_provider: + class: 'PrestaShop\PrestaShop\Adapter\Form\ChoiceProvider\GroupByIdChoiceProvider' + arguments: + - '@prestashop.adapter.legacy.configuration' + - '@=service("prestashop.adapter.legacy.context").getLanguage().id' + + prestashop.adapter.form.choice_provider.risk_by_id_choice_provider: + class: 'PrestaShop\PrestaShop\Adapter\Form\ChoiceProvider\RiskByIdChoiceProvider' diff --git a/src/PrestaShopBundle/Resources/config/services/adapter/group.yml b/src/PrestaShopBundle/Resources/config/services/adapter/group.yml index 09968ebe086f2..0a4ff10269d2d 100644 --- a/src/PrestaShopBundle/Resources/config/services/adapter/group.yml +++ b/src/PrestaShopBundle/Resources/config/services/adapter/group.yml @@ -10,3 +10,9 @@ services: tags: - name: tactician.handler command: 'PrestaShop\PrestaShop\Core\Domain\Group\Query\GetDefaultGroups' + + prestashop.adapter.group.provider.default_groups_provider: + class: 'PrestaShop\PrestaShop\Adapter\Group\Provider\DefaultGroupsProvider' + arguments: + - '@prestashop.adapter.legacy.configuration' + - '@=service("prestashop.adapter.legacy.context").getContext().language.id' diff --git a/src/PrestaShopBundle/Resources/config/services/bundle/form/form_type.yml b/src/PrestaShopBundle/Resources/config/services/bundle/form/form_type.yml index be0c1f79277bb..8bde110de41b0 100644 --- a/src/PrestaShopBundle/Resources/config/services/bundle/form/form_type.yml +++ b/src/PrestaShopBundle/Resources/config/services/bundle/form/form_type.yml @@ -666,3 +666,16 @@ services: - '@=service("prestashop.core.form.choice_provider.customer_required_fields").getChoices()' tags: - { name: form.type } + + prestashop.bundle.form.admin.sell.customer.customer: + class: 'PrestaShopBundle\Form\Admin\Sell\Customer\CustomerType' + arguments: + - '@=service("prestashop.adapter.form.choice_provider.gender_by_id_choice_provider").getChoices()' + - '@=service("prestashop.adapter.form.choice_provider.group_by_id_choice_provider").getChoices()' + - '@=service("prestashop.adapter.form.choice_provider.risk_by_id_choice_provider").getChoices()' + - '@=service("prestashop.core.b2b.b2b_feature").isActive()' + - '@=service("prestashop.adapter.legacy.configuration").get("PS_CUSTOMER_OPTIN")' + calls: + - { method: setTranslator, arguments: ['@translator'] } + tags: + - { name: form.type } diff --git a/src/PrestaShopBundle/Resources/config/services/core/form/form_builder.yml b/src/PrestaShopBundle/Resources/config/services/core/form/form_builder.yml index 5f09c2215e805..9eb8d83aa3c69 100644 --- a/src/PrestaShopBundle/Resources/config/services/core/form/form_builder.yml +++ b/src/PrestaShopBundle/Resources/config/services/core/form/form_builder.yml @@ -14,3 +14,11 @@ services: arguments: - 'PrestaShopBundle\Form\Admin\Configure\AdvancedParameters\RequestSql\SqlRequestType' - '@prestashop.core.form.identifiable_object.data_provider.sql_request_form_data_provider' + + prestashop.core.form.identifiable_object.builder.customer_form_builder: + class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\Builder\FormBuilder' + factory: 'prestashop.core.form.builder.form_builder_factory:create' + arguments: + - 'PrestaShopBundle\Form\Admin\Sell\Customer\CustomerType' + - '@prestashop.core.form.identifiable_object.data_provider.customer_form_data_provider' + diff --git a/src/PrestaShopBundle/Resources/config/services/core/form/form_data_handler.yml b/src/PrestaShopBundle/Resources/config/services/core/form/form_data_handler.yml index ea35c1e28638e..584de0fc33ece 100644 --- a/src/PrestaShopBundle/Resources/config/services/core/form/form_data_handler.yml +++ b/src/PrestaShopBundle/Resources/config/services/core/form/form_data_handler.yml @@ -6,3 +6,10 @@ services: class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler\SqlRequestFormDataHandler' arguments: - '@prestashop.core.command_bus' + + prestashop.core.form.identifiable_object.data_handler.customer_form_data_handler: + class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler\CustomerFormDataHandler' + arguments: + - '@prestashop.core.command_bus' + - '@=service("prestashop.adapter.legacy.context").getContext().shop.id' + - '@=service("prestashop.core.b2b.b2b_feature").isActive()' diff --git a/src/PrestaShopBundle/Resources/config/services/core/form/form_data_provider.yml b/src/PrestaShopBundle/Resources/config/services/core/form/form_data_provider.yml index 829b7e218435b..45b2ac817080c 100644 --- a/src/PrestaShopBundle/Resources/config/services/core/form/form_data_provider.yml +++ b/src/PrestaShopBundle/Resources/config/services/core/form/form_data_provider.yml @@ -6,3 +6,11 @@ services: class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataProvider\SqlRequestFormDataProvider' arguments: - '@prestashop.core.query_bus' + + prestashop.core.form.identifiable_object.data_provider.customer_form_data_provider: + class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataProvider\CustomerFormDataProvider' + arguments: + - '@prestashop.core.query_bus' + - '@prestashop.adapter.legacy.configuration' + - '@prestashop.adapter.group.provider.default_groups_provider' + - '@=service("prestashop.core.b2b.b2b_feature").isActive()' diff --git a/src/PrestaShopBundle/Resources/config/services/core/form/form_handler.yml b/src/PrestaShopBundle/Resources/config/services/core/form/form_handler.yml index 4550738160829..30ff7b3e046d1 100644 --- a/src/PrestaShopBundle/Resources/config/services/core/form/form_handler.yml +++ b/src/PrestaShopBundle/Resources/config/services/core/form/form_handler.yml @@ -14,3 +14,9 @@ services: factory: 'prestashop.core.form.identifiable_object.handler.form_handler_factory:create' arguments: - '@prestashop.core.form.identifiable_object.sql_request_form_data_handler' + + prestashop.core.form.identifiable_object.handler.customer_form_handler: + class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\Handler\FormHandler' + factory: 'prestashop.core.form.identifiable_object.handler.form_handler_factory:create' + arguments: + - '@prestashop.core.form.identifiable_object.data_handler.customer_form_data_handler' diff --git a/src/PrestaShopBundle/Resources/config/services/core/security.yml b/src/PrestaShopBundle/Resources/config/services/core/security.yml new file mode 100644 index 0000000000000..a8d3e9a92b59a --- /dev/null +++ b/src/PrestaShopBundle/Resources/config/services/core/security.yml @@ -0,0 +1,6 @@ +services: + _defaults: + public: true + + prestashop.core.crypto.hashing: + class: 'PrestaShop\PrestaShop\Core\Crypto\Hashing' diff --git a/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/Blocks/form.html.twig b/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/Blocks/form.html.twig new file mode 100644 index 0000000000000..bcb9fd30f000f --- /dev/null +++ b/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/Blocks/form.html.twig @@ -0,0 +1,137 @@ +{#** + * 2007-2018 PrestaShop + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author PrestaShop SA + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + *#} + +{% import '@PrestaShop/Admin/macros.html.twig' as ps %} + +{% set allowedNameChars = '0-9!<>,;?=+()@#"°{}_$%:' %} +{% set isGuest = isGuest|default(false) %} + +{% block customer_form %} + {{ form_start(customerForm) }} +
+

+ person + {{ 'Customer'|trans({}, 'Admin.Global') }} +

+
+
+ {{ ps.form_group_row(customerForm.gender_id, {}, { + 'label': 'Social title'|trans({}, 'Admin.Global') + }) }} + + {{ ps.form_group_row(customerForm.first_name, {}, { + 'label': 'First name'|trans({}, 'Admin.Global'), + 'help': '%s %s'|format('Invalid characters:'|trans({}, 'Admin.Notifications.Info'), allowedNameChars) + }) }} + + {{ ps.form_group_row(customerForm.last_name, {}, { + 'label': 'Last name'|trans({}, 'Admin.Global'), + 'help': '%s %s'|format('Invalid characters:'|trans({}, 'Admin.Notifications.Info'), allowedNameChars) + }) }} + + {{ ps.form_group_row(customerForm.email, {}, { + 'label': 'Email'|trans({}, 'Admin.Global') + }) }} + + {{ ps.form_group_row(customerForm.password, {}, { + 'label': 'Password'|trans({}, 'Admin.Global'), + 'help': 'Password should be at least %length% characters long.'|trans({'%length%': minPasswordLength}, 'Admin.Notifications.Info'), + 'class': isGuest ? 'd-none' : '' + }) }} + + {{ ps.form_group_row(customerForm.birthday, {}, { + 'label': 'Birthday'|trans({}, 'Admin.Orderscustomers.Feature') + }) }} + + {{ ps.form_group_row(customerForm.is_enabled, {}, { + 'label': 'Enabled'|trans({}, 'Admin.Global'), + 'help': 'Enable or disable customer login.'|trans({}, 'Admin.Orderscustomers.Help') + }) }} + + {{ ps.form_group_row(customerForm.is_partner_offers_subscribed, {}, { + 'label': 'Partner offers'|trans({}, 'Admin.Orderscustomers.Feature'), + 'help': 'This customer will receive your ads via email.'|trans({}, 'Admin.Orderscustomers.Help') + }) }} + + {{ ps.form_group_row(customerForm.group_ids, {}, { + 'label': 'Group access'|trans({}, 'Admin.Orderscustomers.Feature'), + 'help': 'Select all the groups that you would like to apply to this customer.'|trans({}, 'Admin.Orderscustomers.Help') + }) }} + + {{ ps.form_group_row(customerForm.default_group_id, {}, { + 'label': 'Default customer group'|trans({}, 'Admin.Orderscustomers.Feature'), + 'help': '%s %s'|format('This group will be the user\'s default group.'|trans({}, 'Admin.Orderscustomers.Help'), 'Only the discount for the selected group will be applied to this customer.'|trans({}, 'Admin.Orderscustomers.Help')) + }) }} + + {% if isB2bFeatureActive %} + {{ ps.form_group_row(customerForm.company_name, {}, { + 'label': 'Company'|trans({}, 'Admin.Global') + }) }} + + {{ ps.form_group_row(customerForm.siret_code, {}, { + 'label': 'SIRET'|trans({}, 'Admin.Orderscustomers.Feature') + }) }} + + {{ ps.form_group_row(customerForm.ape_code, {}, { + 'label': 'APE'|trans({}, 'Admin.Orderscustomers.Feature') + }) }} + + {{ ps.form_group_row(customerForm.website, {}, { + 'label': 'Website'|trans({}, 'Admin.Orderscustomers.Feature') + }) }} + + {{ ps.form_group_row(customerForm.allowed_outstanding_amount, {}, { + 'label': 'Allowed outstanding amount'|trans({}, 'Admin.Orderscustomers.Feature'), + 'help': '%s 0-9'|format('Valid characters:'|trans({}, 'Admin.Orderscustomers.Help')) + }) }} + + {{ ps.form_group_row(customerForm.max_payment_days, {}, { + 'label': 'Maximum number of payment days'|trans({}, 'Admin.Orderscustomers.Feature'), + 'help': '%s 0-9'|format('Valid characters:'|trans({}, 'Admin.Orderscustomers.Help')) + }) }} + + {{ ps.form_group_row(customerForm.risk_id, {}, { + 'label': 'Risk rating'|trans({}, 'Admin.Orderscustomers.Feature') + }) }} + {% endif %} + + {% block customer_form_rest %} + {{ form_rest(customerForm) }} + {% endblock %} +
+
+ +
+ {{ form_end(customerForm) }} +{% endblock %} diff --git a/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/create.html.twig b/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/create.html.twig new file mode 100644 index 0000000000000..52caac597f053 --- /dev/null +++ b/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/create.html.twig @@ -0,0 +1,43 @@ +{#** + * 2007-2018 PrestaShop + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author PrestaShop SA + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + *#} + +{% set enableSidebar = true %} +{% set layoutTitle = 'Creating a new Customer'|trans({}, 'Admin.Orderscustomers.Feature') %} + +{% extends 'PrestaShopBundle:Admin:layout.html.twig' %} + +{% block content %} +
+
+ {% include '@PrestaShop/Admin/Sell/Customer/Blocks/form.html.twig' %} +
+
+{% endblock %} + +{% block javascripts %} + {{ parent() }} + + {% include '@PrestaShop/Admin/Sell/Customer/Blocks/javascript.html.twig' %} +{% endblock %} diff --git a/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/edit.html.twig b/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/edit.html.twig new file mode 100644 index 0000000000000..11453c4957f61 --- /dev/null +++ b/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/edit.html.twig @@ -0,0 +1,44 @@ +{#** + * 2007-2018 PrestaShop + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/OSL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author PrestaShop SA + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + *#} + +{% set enableSidebar = true %} +{% set fullName = '%s. %s'|format(customerInformation.personalInformation.firstName[:1], customerInformation.personalInformation.lastName) %} +{% set layoutTitle = 'Editing customer %name%'|trans({'%name%': fullName}, 'Admin.Orderscustomers.Feature') %} + +{% extends 'PrestaShopBundle:Admin:layout.html.twig' %} + +{% block content %} +
+
+ {% include '@PrestaShop/Admin/Sell/Customer/Blocks/form.html.twig' with {'isGuest': customerInformation.personalInformation.guest} %} +
+
+{% endblock %} + +{% block javascripts %} + {{ parent() }} + + {% include '@PrestaShop/Admin/Sell/Customer/Blocks/javascript.html.twig' %} +{% endblock %} diff --git a/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/index.html.twig b/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/index.html.twig index e8814c499cb22..3114ab4f618a5 100644 --- a/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/index.html.twig +++ b/src/PrestaShopBundle/Resources/views/Admin/Sell/Customer/index.html.twig @@ -25,13 +25,16 @@ {% set enableSidebar = true %} {% set layoutTitle = 'Manage your Customers'|trans({}, 'Admin.Orderscustomers.Feature') %} -{% set layoutHeaderToolbarBtn = { - 'add': { - 'href': path('admin_customers_create'), - 'desc': 'Add new customer'|trans({}, 'Admin.Orderscustomers.Feature'), - 'icon': 'add_circle_outline' - } -} %} + +{% if isSingleShopContext %} + {% set layoutHeaderToolbarBtn = { + 'add': { + 'href': path('admin_customers_create'), + 'desc': 'Add new customer'|trans({}, 'Admin.Orderscustomers.Feature'), + 'icon': 'add_circle_outline' + } + } %} +{% endif %} {% extends 'PrestaShopBundle:Admin:layout.html.twig' %} @@ -50,6 +53,16 @@ {% endblock %} {% block customers_listing %} + {% if not isSingleShopContext %} +
+ +
+ {% endif %} +
{% include '@PrestaShop/Admin/Common/Grid/grid_panel.html.twig' with {'grid': customerGrid} %}
diff --git a/src/PrestaShopBundle/Resources/views/Admin/TwigTemplateForm/bootstrap_4_layout.html.twig b/src/PrestaShopBundle/Resources/views/Admin/TwigTemplateForm/bootstrap_4_layout.html.twig index 56f48c8afb58f..e6025456d60e9 100644 --- a/src/PrestaShopBundle/Resources/views/Admin/TwigTemplateForm/bootstrap_4_layout.html.twig +++ b/src/PrestaShopBundle/Resources/views/Admin/TwigTemplateForm/bootstrap_4_layout.html.twig @@ -559,7 +559,7 @@ {% block material_choice_table_widget %} {% spaceless %}
- +
@@ -673,3 +673,28 @@ {% endif %} {%- endblock translatable_widget %} + +{% block birthday_widget %} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} + {% if datetime is not defined or not datetime -%} +
+ {%- endif %} + + {% set yearWidget = '
' ~ form_widget(form.year) ~ '
'|raw %} + {% set monthWidget = '
' ~ form_widget(form.month) ~ '
'|raw %} + {% set dayWidget = '
' ~ form_widget(form.day) ~ '
'|raw %} + + {{- date_pattern|replace({ + '{{ year }}': yearWidget, + '{{ month }}': monthWidget, + '{{ day }}': dayWidget, + })|raw -}} + + {% if datetime is not defined or not datetime -%} +
+ {%- endif -%} + {% endif %} +{% endblock birthday_widget %} diff --git a/src/PrestaShopBundle/Resources/views/Admin/macros.html.twig b/src/PrestaShopBundle/Resources/views/Admin/macros.html.twig index d8fef8b3a941d..a0b027d44d472 100644 --- a/src/PrestaShopBundle/Resources/views/Admin/macros.html.twig +++ b/src/PrestaShopBundle/Resources/views/Admin/macros.html.twig @@ -89,19 +89,49 @@ {% endmacro %} {# Show form widget with errors rendered below it #} -{% macro form_widget_with_error(form, vars) %} +{% macro form_widget_with_error(form, vars, extraVars) %} {% set vars = vars|default({}) %} - {% set attr = vars.attr|default({}) %} - {% set errors = form.vars.errors %} - - {% set attr = attr|merge({'class': (attr.class is defined ? attr.class : '') ~ (errors|length > 0 ? ' is-invalid' : '')} ) %} - {% set vars = vars|merge({'attr': attr}) %} + {% set extraVars = extraVars|default({}) %} {{ form_widget(form, vars) }} + {% if extraVars.help is defined %} + {{ extraVars.help }} + {% endif %} + {% for error in form.vars.errors %} -
+
{{ error.message }}
{% endfor %} {% endmacro %} + + {# + Helper function to render most common structure for single input + + @param form - form view to render + @param vars - custom vars that are passed to form_widget + @param form - parameters that are not related to form_widget, but are needed for input (label, help text & etc.) + #} +{% macro form_group_row(form, vars, extraVars) %} + {% import '@PrestaShop/Admin/macros.html.twig' as self %} + + {% set class = extraVars.class|default('') %} + +
+ {% set extraVars = extraVars|default({}) %} + + {% if extraVars.label is defined %} + + {% endif %} + +
+ {{ self.form_widget_with_error(form, vars, extraVars) }} +
+
+{% endmacro %} diff --git a/tests/Unit/Core/Domain/Customer/ValueObject/ApeCodeTest.php b/tests/Unit/Core/Domain/Customer/ValueObject/ApeCodeTest.php new file mode 100644 index 0000000000000..3ed2dd8ba395f --- /dev/null +++ b/tests/Unit/Core/Domain/Customer/ValueObject/ApeCodeTest.php @@ -0,0 +1,70 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace Tests\Unit\Core\Domain\Customer\ValueObject; + +use PHPUnit\Framework\TestCase; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\ApeCode; + +class ApeCodeTest extends TestCase +{ + /** + * @dataProvider getValidApeCodes + */ + public function testItCreatesApeCodeWithValidValue($code) + { + $apeCode = new ApeCode($code); + + $this->assertEquals($code, $apeCode->getValue()); + } + + /** + * @dataProvider getInvalidApeCodes + */ + public function testItThrowExceptionWhenCreatingApeCodeWithInvalidValue($code) + { + $this->expectException(CustomerConstraintException::class); + $this->expectExceptionCode(CustomerConstraintException::INVALID_APE_CODE); + + new ApeCode($code); + } + + public function getValidApeCodes() + { + yield ['']; + yield ['001A']; + yield ['1039B']; + } + + public function getInvalidApeCodes() + { + yield ['not_valid']; + yield ['1236']; + yield [123]; + yield [[]]; + } +} diff --git a/tests/Unit/Core/Domain/Customer/ValueObject/BirthdayTest.php b/tests/Unit/Core/Domain/Customer/ValueObject/BirthdayTest.php new file mode 100644 index 0000000000000..47242777ab592 --- /dev/null +++ b/tests/Unit/Core/Domain/Customer/ValueObject/BirthdayTest.php @@ -0,0 +1,60 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace Tests\Unit\Core\Domain\Customer\ValueObject; + +use PHPUnit\Framework\TestCase; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Birthday; + +class BirthdayTest extends TestCase +{ + public function testBirthdayCanBeCreatedWithValidDate() + { + $birthday = new Birthday('2008-07-31'); + + $this->assertEquals('2008-07-31', $birthday->getValue()); + } + + /** + * @dataProvider getInvalidBirthdays + */ + public function testItThrowsExceptionWhenCreatingBirthdayWithInvalidData($invalidBirthday) + { + $this->expectException(CustomerConstraintException::class); + $this->expectExceptionCode(CustomerConstraintException::INVALID_BIRTHDAY); + + new Birthday($invalidBirthday); + } + + public function getInvalidBirthdays() + { + yield ['2150-25-100']; + yield [new \stdClass()]; + yield ['1900-13-33']; + yield [(new \DateTime('+10 days'))->format('Y-m-d')]; + } +} diff --git a/tests/Unit/Core/Domain/Customer/ValueObject/EmailTest.php b/tests/Unit/Core/Domain/Customer/ValueObject/EmailTest.php new file mode 100644 index 0000000000000..7ea18748a14a0 --- /dev/null +++ b/tests/Unit/Core/Domain/Customer/ValueObject/EmailTest.php @@ -0,0 +1,83 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace Tests\Unit\Core\Domain\Customer\ValueObject; + +use PHPUnit\Framework\TestCase; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Email; + +class EmailTest extends TestCase +{ + /** + * @dataProvider getValidEmailValues + */ + public function testItCreatesEmailWithValidValues($validEmail) + { + $email = new Email($validEmail); + + $this->assertEquals($validEmail, $email->getValue()); + } + + /** + * @dataProvider getInvalidEmailValues + */ + public function testItThrowsExceptionWhenCreatingEmailWithInvalidValue($invalidEmail) + { + $this->expectException(CustomerConstraintException::class); + $this->expectExceptionCode(CustomerConstraintException::INVALID_EMAIL); + + new Email($invalidEmail); + } + + /** + * @dataProvider getEmailCompareValues + */ + public function testEmailComparesValuesCorrectly($firstEmail, $secondEmail, $expectedCompareResult) + { + $this->assertEquals($expectedCompareResult, (new Email($firstEmail))->isEqualTo(new Email($secondEmail))); + } + + public function getValidEmailValues() + { + yield ['demo.demo@prestashop.com']; + yield ['12312321@123.com']; + yield ['abc_123o@a.eu']; + } + + public function getInvalidEmailValues() + { + yield ['']; + yield [123]; + yield [sprintf('very_long_email_%s@demo.com', str_repeat('A', 231))]; + } + + public function getEmailCompareValues() + { + yield ['demo@demo.com', 'demo@demo.com', true]; + yield ['demo@demo.com', 'no_the_same@demo.com', false]; + } +} diff --git a/tests/Unit/Core/Domain/Customer/ValueObject/FirstNameTest.php b/tests/Unit/Core/Domain/Customer/ValueObject/FirstNameTest.php new file mode 100644 index 0000000000000..fbfb36b82cd27 --- /dev/null +++ b/tests/Unit/Core/Domain/Customer/ValueObject/FirstNameTest.php @@ -0,0 +1,82 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace Tests\Unit\Core\Domain\Customer\ValueObject; + +use PHPUnit\Framework\TestCase; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\FirstName; + +class FirstNameTest extends TestCase +{ + public function testExceptionIsThrownWhenSuppliedFirstNameIsTooLong() + { + $this->expectException(CustomerConstraintException::class); + $this->expectExceptionCode(CustomerConstraintException::INVALID_FIRST_NAME); + + $veryLongFirstName = str_repeat('A', 256); + + new FirstName($veryLongFirstName); + } + + /** + * @dataProvider getInvalidFirstNames + */ + public function testItThrowsExceptionWhenInvalidFirstNameIsSupplied($invalidFirstName) + { + $this->expectException(CustomerConstraintException::class); + $this->expectExceptionCode(CustomerConstraintException::INVALID_FIRST_NAME); + + new FirstName($invalidFirstName); + } + + /** + * @dataProvider getValidFirstNames + */ + public function testItCreatesFirstNameWithValid($validFirstName) + { + $firstName = new FirstName($validFirstName); + + $this->assertEquals($validFirstName, $firstName->getValue()); + } + + public function getInvalidFirstNames() + { + yield ['First123Name']; + yield ['My !@# name']; + yield ['26589']; + yield ['My+first+name']; + yield ['@My@first%name']; + } + + public function getValidFirstNames() + { + yield ['Demo Demo']; + yield ['MyNameIsPrettyLong']; + yield ['ABC']; + yield ['']; + } +} diff --git a/tests/Unit/Core/Domain/Customer/ValueObject/PasswordTest.php b/tests/Unit/Core/Domain/Customer/ValueObject/PasswordTest.php new file mode 100644 index 0000000000000..8c25c23447ab2 --- /dev/null +++ b/tests/Unit/Core/Domain/Customer/ValueObject/PasswordTest.php @@ -0,0 +1,73 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace Tests\Unit\Core\Domain\Customer\ValueObject; + +use PHPUnit\Framework\TestCase; +use PrestaShop\PrestaShop\Core\Domain\Customer\Exception\CustomerConstraintException; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Password; + +class PasswordTest extends TestCase +{ + /** + * @dataProvider getTooShortOrTooLongPasswords + */ + public function testItThrowsExceptionWhenCreatingTooShortOrTooLongPassword($password) + { + $this->expectException(CustomerConstraintException::class); + $this->expectExceptionCode(CustomerConstraintException::INVALID_PASSWORD); + + new Password($password); + } + + /** + * @dataProvider getValidPasswords + */ + public function testItCreatesNewPassword($passwordValue) + { + $password = new Password($passwordValue); + + $this->assertEquals($passwordValue, $password->getValue()); + } + + public function getTooShortOrTooLongPasswords() + { + yield ['']; + yield ['p']; + yield ['ps']; + yield ['pwd']; + yield [123]; + yield ['pwds']; + yield ['very_long_and_super_secret_password_which_is_one_char_longer_than_allowed']; + } + + public function getValidPasswords() + { + yield ['short']; + yield [12345]; + yield ['a_BIT_longer_password_1593!@']; + } +} diff --git a/tests/Unit/Core/Form/IdentifiableObject/DataProvider/CustomerFormDataProviderTest.php b/tests/Unit/Core/Form/IdentifiableObject/DataProvider/CustomerFormDataProviderTest.php new file mode 100644 index 0000000000000..eeb91bfc4895f --- /dev/null +++ b/tests/Unit/Core/Form/IdentifiableObject/DataProvider/CustomerFormDataProviderTest.php @@ -0,0 +1,203 @@ + + * @copyright 2007-2018 PrestaShop SA + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +namespace Tests\Unit\Core\Form\IdentifiableObject\DataProvider; + +use DateTime; +use PHPUnit\Framework\TestCase; +use PrestaShop\PrestaShop\Core\CommandBus\CommandBusInterface; +use PrestaShop\PrestaShop\Core\ConfigurationInterface; +use PrestaShop\PrestaShop\Core\Domain\Customer\Dto\EditableCustomer; +use PrestaShop\PrestaShop\Core\Domain\Customer\Query\GetCustomerForEditing; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Birthday; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\Email; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\FirstName; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\LastName; +use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataProvider\CustomerFormDataProvider; +use PrestaShop\PrestaShop\Core\Group\Provider\DefaultGroup; +use PrestaShop\PrestaShop\Core\Group\Provider\DefaultGroups; +use PrestaShop\PrestaShop\Core\Group\Provider\DefaultGroupsProviderInterface; + +class CustomerFormDataProviderTest extends TestCase +{ + /** + * @var CommandBusInterface + */ + private $queryBus; + + /** + * @var ConfigurationInterface + */ + private $configuration; + + /** + * @var DefaultGroupsProviderInterface + */ + private $defaultGroupsProvider; + + /** + * Set up dependencies for CustomerFormDataProvider + */ + public function setUp() + { + $this->queryBus = $this->createMock(CommandBusInterface::class); + $this->queryBus + ->method('handle') + ->with($this->isInstanceOf(GetCustomerForEditing::class)) + ->willReturn( + new EditableCustomer( + new CustomerId(1), + 2, + new FirstName('Firstname'), + new LastName('Lastname'), + new Email('firstname.lastname@prestashop.com'), + new Birthday('1990-01-01'), + true, + true, + [1, 2, 3], + 3, + 'Demo company', + 'siret_code', + 'ape_code', + 'prestashop.com', + 36.99, + 10, + 1 + ) + ) + ; + + $this->configuration = $this->createMock(ConfigurationInterface::class); + $this->configuration + ->method('get') + ->will( + $this->returnValueMap([ + [ + 'PS_CUSTOMER_GROUP', 3, + ] + ]) + ) + ; + + $this->defaultGroupsProvider = $this->createMock(DefaultGroupsProviderInterface::class); + $this->defaultGroupsProvider + ->method('getGroups') + ->willReturn(new DefaultGroups( + new DefaultGroup(1, 'Visitors'), + new DefaultGroup(2, 'Guests'), + new DefaultGroup(3, 'Customers') + )) + ; + } + + public function testItProvidesCorrectFormDataWithB2bFeatureBeingOff() + { + $customerFormDataProvider = new CustomerFormDataProvider( + $this->queryBus, + $this->configuration, + $this->defaultGroupsProvider, + false + ); + + $this->assertEquals([ + 'gender_id' => 2, + 'first_name' => 'Firstname', + 'last_name' => 'Lastname', + 'email' => 'firstname.lastname@prestashop.com', + 'birthday' => '1990-01-01', + 'is_enabled' => true, + 'is_partner_offers_subscribed' => true, + 'group_ids' => [1, 2, 3], + 'default_group_id' => 3, + ], $customerFormDataProvider->getData(1)); + } + + public function testItProvidesCorrectFormDataWithB2bFeatureBeingOn() + { + $customerFormDataProvider = new CustomerFormDataProvider( + $this->queryBus, + $this->configuration, + $this->defaultGroupsProvider, + true + ); + + $this->assertEquals([ + 'gender_id' => 2, + 'first_name' => 'Firstname', + 'last_name' => 'Lastname', + 'email' => 'firstname.lastname@prestashop.com', + 'birthday' => '1990-01-01', + 'is_enabled' => true, + 'is_partner_offers_subscribed' => true, + 'group_ids' => [1, 2, 3], + 'default_group_id' => 3, + 'company_name' => 'Demo company', + 'siret_code' => 'siret_code', + 'ape_code' => 'ape_code', + 'website' => 'prestashop.com', + 'allowed_outstanding_amount' => 36.99, + 'max_payment_days' => 10, + 'risk_id' => 1, + ], $customerFormDataProvider->getData(1)); + } + + public function testItProvidesCorrectDefaultDataWhenB2bFeatureIsOff() + { + $customerFormDataProvider = new CustomerFormDataProvider( + $this->queryBus, + $this->configuration, + $this->defaultGroupsProvider, + false + ); + + $this->assertEquals([ + 'is_enabled' => true, + 'is_partner_offers_subscribed' => false, + 'group_ids' => [1, 2, 3], + 'default_group_id' => 3, + ], $customerFormDataProvider->getDefaultData()); + } + + public function testItProvidesAdditionalDefaultDataWhenB2bFeatureIsOn() + { + $customerFormDataProvider = new CustomerFormDataProvider( + $this->queryBus, + $this->configuration, + $this->defaultGroupsProvider, + true + ); + + $this->assertEquals([ + 'is_enabled' => true, + 'is_partner_offers_subscribed' => false, + 'group_ids' => [1, 2, 3], + 'default_group_id' => 3, + 'allowed_outstanding_amount' => 0, + 'max_payment_days' => 0, + ], $customerFormDataProvider->getDefaultData()); + } +}