From 9aacc9de2411cc85a2361232772fbdfdfd95ec42 Mon Sep 17 00:00:00 2001 From: Michal Tomka Date: Fri, 9 Aug 2024 07:48:21 +0200 Subject: [PATCH 1/6] added new CustomerUserRole::ROLE_API_CUSTOMER_SEES_PRICES --- src/Model/Customer/User/Role/CustomerUserRole.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Model/Customer/User/Role/CustomerUserRole.php b/src/Model/Customer/User/Role/CustomerUserRole.php index 5c8c7db6bb..7f3bfb5f15 100644 --- a/src/Model/Customer/User/Role/CustomerUserRole.php +++ b/src/Model/Customer/User/Role/CustomerUserRole.php @@ -9,6 +9,7 @@ class CustomerUserRole public const ROLE_API_LOGGED_CUSTOMER = 'ROLE_API_LOGGED_CUSTOMER'; public const ROLE_API_ALL = 'ROLE_API_ALL'; public const ROLE_API_CUSTOMER_SELF_MANAGE = 'ROLE_API_CUSTOMER_SELF_MANAGE'; + public const ROLE_API_CUSTOMER_SEES_PRICES = 'ROLE_API_CUSTOMER_SEES_PRICES'; /** * @return array @@ -18,6 +19,7 @@ public function getAvailableRoles(): array return [ t('B2B data and user management') => self::ROLE_API_ALL, t('Customer self manage') => self::ROLE_API_CUSTOMER_SELF_MANAGE, + t('Customer sees prices') => self::ROLE_API_CUSTOMER_SEES_PRICES, ]; } } From 5b49569e7d23425a7c5aa0e1800e07a8151b0b5d Mon Sep 17 00:00:00 2001 From: Michal Tomka Date: Mon, 12 Aug 2024 10:41:51 +0200 Subject: [PATCH 2/6] hide prices in order mail --- src/Model/Customer/User/CustomerUser.php | 8 ++++ .../views/Mail/Order/products.html.twig | 4 +- src/Twig/HiddenPriceExtension.php | 40 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/Twig/HiddenPriceExtension.php diff --git a/src/Model/Customer/User/CustomerUser.php b/src/Model/Customer/User/CustomerUser.php index 264e304758..8802f903f8 100644 --- a/src/Model/Customer/User/CustomerUser.php +++ b/src/Model/Customer/User/CustomerUser.php @@ -501,4 +501,12 @@ public function updateLastSecurityChange(): void { $this->lastSecurityChange = new DateTime(); } + + /** + * @return bool + */ + public function canSeePrices() + { + return in_array(CustomerUserRole::ROLE_API_CUSTOMER_SEES_PRICES, $this->getRoles(), true); + } } diff --git a/src/Resources/views/Mail/Order/products.html.twig b/src/Resources/views/Mail/Order/products.html.twig index 8a5dcd278c..5500009742 100644 --- a/src/Resources/views/Mail/Order/products.html.twig +++ b/src/Resources/views/Mail/Order/products.html.twig @@ -9,8 +9,8 @@ {{ item.name }} {{ item.quantity|formatNumber(orderLocale) }} {{ item.unitName }} - {{ item.unitPriceWithVat|priceTextWithCurrencyByCurrencyIdAndLocale(order.currency.id, orderLocale) }} - {{ orderItemTotalPricesById[item.id].priceWithVat|priceTextWithCurrencyByCurrencyIdAndLocale(order.currency.id, orderLocale) }} + {{ item.unitPriceWithVat|priceTextWithCurrencyByCurrencyIdAndLocale(order.currency.id, orderLocale)|hidePrice(order.customerUser) }} + {{ orderItemTotalPricesById[item.id].priceWithVat|priceTextWithCurrencyByCurrencyIdAndLocale(order.currency.id, orderLocale)|hidePrice(order.customerUser) }} {% endfor %} diff --git a/src/Twig/HiddenPriceExtension.php b/src/Twig/HiddenPriceExtension.php new file mode 100644 index 0000000000..48d6e268c5 --- /dev/null +++ b/src/Twig/HiddenPriceExtension.php @@ -0,0 +1,40 @@ +hidePriceFilter(...), + ), + ]; + } + + /** + * @param string $price + * @param \Shopsys\FrameworkBundle\Model\Customer\User\CustomerUser|null $customerUser + * @return string + */ + public function hidePriceFilter(string $price, ?CustomerUser $customerUser): string + { + if ($customerUser !== null && !$customerUser->canSeePrices()) { + return MoneyFormatterHelper::HIDDEN_FORMAT; + } + + return $price; + } +} From 25b311c53171c22c56cff9b9f8f6e1fdab9a091d Mon Sep 17 00:00:00 2001 From: Michal Tomka Date: Tue, 13 Aug 2024 15:14:00 +0200 Subject: [PATCH 3/6] move PersonalDataController and related logic from project-base to framework package --- .../Front/PersonalDataController.php | 81 ++++++++++++++ .../Content/PersonalData/adress.xml.twig | 21 ++++ .../Content/PersonalData/export.xml.twig | 43 ++++++++ .../Content/PersonalData/orders.xml.twig | 104 ++++++++++++++++++ 4 files changed, 249 insertions(+) create mode 100644 src/Controller/Front/PersonalDataController.php create mode 100644 src/Resources/views/Front/Content/PersonalData/adress.xml.twig create mode 100644 src/Resources/views/Front/Content/PersonalData/export.xml.twig create mode 100644 src/Resources/views/Front/Content/PersonalData/orders.xml.twig diff --git a/src/Controller/Front/PersonalDataController.php b/src/Controller/Front/PersonalDataController.php new file mode 100644 index 0000000000..c9913d1e08 --- /dev/null +++ b/src/Controller/Front/PersonalDataController.php @@ -0,0 +1,81 @@ +personalDataAccessRequestFacade->findByHashAndDomainId( + $hash, + $this->domain->getId(), + ); + + if ( + $personalDataAccessRequest !== null + && $personalDataAccessRequest->getType() === PersonalDataAccessRequest::TYPE_EXPORT + ) { + $customerUser = $this->customerUserFacade->findCustomerUserByEmailAndDomain( + $personalDataAccessRequest->getEmail(), + $this->domain->getId(), + ); + + $orders = $this->orderFacade->getOrderListForEmailByDomainId( + $personalDataAccessRequest->getEmail(), + $this->domain->getId(), + ); + + $newsletterSubscriber = $this->newsletterFacade->findNewsletterSubscriberByEmailAndDomainId( + $personalDataAccessRequest->getEmail(), + $this->domain->getId(), + ); + + $xmlContent = $this->render('@ShopsysFramework/Front/Content/PersonalData/export.xml.twig', [ + 'customerUser' => $customerUser, + 'newsletterSubscriber' => $newsletterSubscriber, + 'orders' => $orders, + ])->getContent(); + + $fileName = $personalDataAccessRequest->getEmail() . '.xml'; + + return $this->xmlResponse->getXmlResponse($fileName, $xmlContent); + } + + throw new NotFoundHttpException(); + } +} diff --git a/src/Resources/views/Front/Content/PersonalData/adress.xml.twig b/src/Resources/views/Front/Content/PersonalData/adress.xml.twig new file mode 100644 index 0000000000..dad7bb0e88 --- /dev/null +++ b/src/Resources/views/Front/Content/PersonalData/adress.xml.twig @@ -0,0 +1,21 @@ + + {% set billingAddress = customerUser.customer.billingAddress %} +
+ billing + + + + +
+ {% if customerUser.customer.deliveryAddresses is not null %} + {% for deliveryAddress in customerUser.customer.deliveryAddresses %} +
+ shipping + {% if deliveryAddress.street is not null %}{% endif %} + {% if deliveryAddress.city is not null %}{% endif %} + {% if deliveryAddress.postCode is not null %}{% endif %} + {% if deliveryAddress.country is not null %}{% endif %} +
+ {% endfor %} + {% endif %} +
diff --git a/src/Resources/views/Front/Content/PersonalData/export.xml.twig b/src/Resources/views/Front/Content/PersonalData/export.xml.twig new file mode 100644 index 0000000000..82e0a0f256 --- /dev/null +++ b/src/Resources/views/Front/Content/PersonalData/export.xml.twig @@ -0,0 +1,43 @@ + + + + + + {% if customerUser is not null %} + + + + + + + + {% if customerUser.customer.billingAdress is defined or customerUser.customer.deliveryAddresses is defined %} + {% include '@ShopsysFramework/Front/Content/PersonalData/adress.xml.twig' with {'customerUser' : customerUser} %} + {% endif %} + + {% if customerUser.customer.billingAddress is defined and customerUser.customer.billingAddress.companyCustomer %} + {% set billingAddress = customerUser.customer.billingAddress %} + + {% if billingAddress.companyName is not null %} + + {% endif %} + {% if billingAddress.companyNumber is not null %} + + {% endif %} + {% if billingAddress.companyTaxNumber is not null %} + + {% endif %} + + {% endif %} + {% endif %} + + + {% if newsletterSubscriber is null %}0{% else %}1{% endif %} + + + {% if orders|length > 0 %} + {% include('@ShopsysFramework/Front/Content/PersonalData/orders.xml.twig' ) with {'orders': orders} %} + {% endif %} + + + diff --git a/src/Resources/views/Front/Content/PersonalData/orders.xml.twig b/src/Resources/views/Front/Content/PersonalData/orders.xml.twig new file mode 100644 index 0000000000..c8e0552f9e --- /dev/null +++ b/src/Resources/views/Front/Content/PersonalData/orders.xml.twig @@ -0,0 +1,104 @@ + + {% for order in orders %} + {% set customerUser = order.customerUser %} + + {{ order.number }} + {{ order.createdAt|date('c') }} + {% if order.isCancelled %}1{% else %}0{% endif %} + +
+ billing + + + + + + {% if order.companyName is not null %} + + {% if order.companyName is not null %} + + {% endif %} + {% if order.companyNumber is not null %} + + {% endif %} + {% if order.companyTaxNumber is not null %} + + {% endif %} + + {% endif %} + + + {% if order.firstName is not null %} + + {% endif %} + {% if order.lastName is not null %} + + {% endif %} + {% if order.companyName is not null %} + + {% endif %} + +
+ + {% if order.deliveryAddressSameAsBillingAddress == false %} +
+ shipping + + {% if order.deliveryFirstName is not null %} + + {% endif %} + {% if order.deliveryLastName is not null %} + + {% endif %} + + + {% if order.deliveryCompanyName is not null %} + + + + {% endif %} + + {% if order.deliveryStreet is not null %} + + {% endif %} + {% if order.deliveryCity is not null %} + + {% endif %} + {% if order.deliveryPostCode is not null %} + + {% endif %} + {% if order.deliveryCountry is not null %} + + {% endif %} +
+ {% endif %} + +
+ + + {% for item in order.items %} + + + {{ item.quantity }} + + {% if not item.unitPriceWithoutVat.isZero %} + {{ item.unitPriceWithoutVat|moneyFormat|hidePrice(customerUser) }} + {% endif %} + {% if not item.unitPriceWithVat.isZero %} + {{ item.unitPriceWithVat|moneyFormat|hidePrice(customerUser) }} + {% endif %} + {% if order.currency.code %} + + {% endif %} + + {% endfor %} + + + + {{ order.status.name }} + + + +
+ {% endfor %} +
From d360c09c27933d0e9fcbcd677d8f7b5f128aa97c Mon Sep 17 00:00:00 2001 From: Michal Tomka Date: Tue, 13 Aug 2024 15:40:02 +0200 Subject: [PATCH 4/6] dump translations --- src/Resources/translations/messages.cs.po | 3 +++ src/Resources/translations/messages.en.po | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/Resources/translations/messages.cs.po b/src/Resources/translations/messages.cs.po index d4ca04c94a..03d3f81632 100644 --- a/src/Resources/translations/messages.cs.po +++ b/src/Resources/translations/messages.cs.po @@ -979,6 +979,9 @@ msgstr "Jméno zákazníka" msgid "Customer phone number" msgstr "Telefonní číslo zákazníka" +msgid "Customer sees prices" +msgstr "Zákazník vidí ceny" + msgid "Customer self manage" msgstr "Spravovat své údaje" diff --git a/src/Resources/translations/messages.en.po b/src/Resources/translations/messages.en.po index 9bd1add867..aa878ccc64 100644 --- a/src/Resources/translations/messages.en.po +++ b/src/Resources/translations/messages.en.po @@ -979,6 +979,9 @@ msgstr "" msgid "Customer phone number" msgstr "" +msgid "Customer sees prices" +msgstr "" + msgid "Customer self manage" msgstr "" From 6e5f23a145a52d5205f942980a303a849e88a7d0 Mon Sep 17 00:00:00 2001 From: Michal Tomka Date: Thu, 15 Aug 2024 09:25:37 +0200 Subject: [PATCH 5/6] CustomerUser::getRoles() doesn't know about the role hierarchy - see warning in https://symfony.com/doc/current/security.html#hierarchical-roles --- src/Model/Customer/User/CustomerUser.php | 8 ---- .../User/Role/CustomerUserRoleProvider.php | 41 +++++++++++++++++++ src/Twig/HiddenPriceExtension.php | 11 ++++- 3 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 src/Model/Customer/User/Role/CustomerUserRoleProvider.php diff --git a/src/Model/Customer/User/CustomerUser.php b/src/Model/Customer/User/CustomerUser.php index 8802f903f8..264e304758 100644 --- a/src/Model/Customer/User/CustomerUser.php +++ b/src/Model/Customer/User/CustomerUser.php @@ -501,12 +501,4 @@ public function updateLastSecurityChange(): void { $this->lastSecurityChange = new DateTime(); } - - /** - * @return bool - */ - public function canSeePrices() - { - return in_array(CustomerUserRole::ROLE_API_CUSTOMER_SEES_PRICES, $this->getRoles(), true); - } } diff --git a/src/Model/Customer/User/Role/CustomerUserRoleProvider.php b/src/Model/Customer/User/Role/CustomerUserRoleProvider.php new file mode 100644 index 0000000000..05b5603241 --- /dev/null +++ b/src/Model/Customer/User/Role/CustomerUserRoleProvider.php @@ -0,0 +1,41 @@ +roleHierarchy->getReachableRoleNames($customerUser->getRoles()); + + return array_unique($roles); + } + + /** + * @param \Shopsys\FrameworkBundle\Model\Customer\User\CustomerUser $customerUser + * @return bool + */ + public function canSeePrices(CustomerUser $customerUser): bool + { + $roles = $this->getRolesForCustomerUser($customerUser); + + return in_array(CustomerUserRole::ROLE_API_CUSTOMER_SEES_PRICES, $roles, true); + } +} diff --git a/src/Twig/HiddenPriceExtension.php b/src/Twig/HiddenPriceExtension.php index 48d6e268c5..f73f426ab2 100644 --- a/src/Twig/HiddenPriceExtension.php +++ b/src/Twig/HiddenPriceExtension.php @@ -5,12 +5,21 @@ namespace Shopsys\FrameworkBundle\Twig; use Shopsys\FrameworkBundle\Model\Customer\User\CustomerUser; +use Shopsys\FrameworkBundle\Model\Customer\User\Role\CustomerUserRoleProvider; use Shopsys\FrontendApiBundle\Component\Price\MoneyFormatterHelper; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; class HiddenPriceExtension extends AbstractExtension { + /** + * @param \Shopsys\FrameworkBundle\Model\Customer\User\Role\CustomerUserRoleProvider $customerUserRoleProvider + */ + public function __construct( + protected readonly CustomerUserRoleProvider $customerUserRoleProvider, + ) { + } + /** * @return array */ @@ -31,7 +40,7 @@ public function getFilters(): array */ public function hidePriceFilter(string $price, ?CustomerUser $customerUser): string { - if ($customerUser !== null && !$customerUser->canSeePrices()) { + if ($customerUser !== null && !$this->customerUserRoleProvider->canSeePrices($customerUser)) { return MoneyFormatterHelper::HIDDEN_FORMAT; } From a22c7b9d7c90bf1f1da8303f6328079d1512dd68 Mon Sep 17 00:00:00 2001 From: Rostislav Vitek Date: Thu, 5 Sep 2024 12:59:03 +0200 Subject: [PATCH 6/6] admin: customer user edit: logout customer user user when his roles are changed --- src/Model/Customer/User/CustomerUserFacade.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Model/Customer/User/CustomerUserFacade.php b/src/Model/Customer/User/CustomerUserFacade.php index 65e162f14e..91c7ef40de 100644 --- a/src/Model/Customer/User/CustomerUserFacade.php +++ b/src/Model/Customer/User/CustomerUserFacade.php @@ -165,6 +165,7 @@ public function edit( ?DeliveryAddress $deliveryAddress = null, ) { $customerUser = $this->getCustomerUserById($customerUserId); + $customerUserOriginalRoles = $customerUser->getRoles(); if ( $customerUserUpdateData->deliveryAddressData @@ -199,6 +200,10 @@ public function edit( $this->newsletterFacade->deleteSubscribedEmailIfExists($customerUser->getEmail(), $customerUser->getDomainId()); } + if ($this->areRolesChanged($customerUser->getRoles(), $customerUserOriginalRoles)) { + $this->customerUserRefreshTokenChainFacade->removeAllCustomerUserRefreshTokenChains($customerUser); + } + return $customerUser; } @@ -465,4 +470,14 @@ public function isLastSecurityChangeOlderThan(string $customerUserUuid, DateTime { return $this->customerUserRepository->isLastSecurityChangeOlderThan($customerUserUuid, $referenceDateTime); } + + /** + * @param string[] $customerUserCurrentRoles + * @param string[] $customerUserOriginalRoles + * @return bool + */ + protected function areRolesChanged(array $customerUserCurrentRoles, array $customerUserOriginalRoles): bool + { + return array_diff($customerUserCurrentRoles, $customerUserOriginalRoles) !== [] || array_diff($customerUserOriginalRoles, $customerUserCurrentRoles) !== []; + } }