vendor/shopware/storefront/Controller/AddressController.php line 89

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Storefront\Controller;
  3. use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException;
  4. use Shopware\Core\Checkout\Cart\Order\Transformer\CustomerTransformer;
  5. use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity;
  6. use Shopware\Core\Checkout\Customer\CustomerEntity;
  7. use Shopware\Core\Checkout\Customer\Exception\AddressNotFoundException;
  8. use Shopware\Core\Checkout\Customer\Exception\CannotDeleteDefaultAddressException;
  9. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractChangeCustomerProfileRoute;
  10. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractDeleteAddressRoute;
  11. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractListAddressRoute;
  12. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractUpsertAddressRoute;
  13. use Shopware\Core\Checkout\Customer\SalesChannel\AccountService;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  16. use Shopware\Core\Framework\Feature;
  17. use Shopware\Core\Framework\Routing\Annotation\LoginRequired;
  18. use Shopware\Core\Framework\Routing\Annotation\RouteScope;
  19. use Shopware\Core\Framework\Routing\Annotation\Since;
  20. use Shopware\Core\Framework\Routing\Exception\MissingRequestParameterException;
  21. use Shopware\Core\Framework\Uuid\Exception\InvalidUuidException;
  22. use Shopware\Core\Framework\Uuid\Uuid;
  23. use Shopware\Core\Framework\Validation\DataBag\DataBag;
  24. use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
  25. use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
  26. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  27. use Shopware\Storefront\Framework\Routing\Annotation\NoStore;
  28. use Shopware\Storefront\Page\Address\AddressEditorModalStruct;
  29. use Shopware\Storefront\Page\Address\Detail\AddressDetailPageLoadedHook;
  30. use Shopware\Storefront\Page\Address\Detail\AddressDetailPageLoader;
  31. use Shopware\Storefront\Page\Address\Listing\AddressBookWidgetLoadedHook;
  32. use Shopware\Storefront\Page\Address\Listing\AddressListingPageLoadedHook;
  33. use Shopware\Storefront\Page\Address\Listing\AddressListingPageLoader;
  34. use Symfony\Component\HttpFoundation\RedirectResponse;
  35. use Symfony\Component\HttpFoundation\Request;
  36. use Symfony\Component\HttpFoundation\Response;
  37. use Symfony\Component\Routing\Annotation\Route;
  38. /**
  39.  * @RouteScope(scopes={"storefront"})
  40.  */
  41. class AddressController extends StorefrontController
  42. {
  43.     private const ADDRESS_TYPE_BILLING 'billing';
  44.     private const ADDRESS_TYPE_SHIPPING 'shipping';
  45.     private AccountService $accountService;
  46.     private AddressListingPageLoader $addressListingPageLoader;
  47.     private AddressDetailPageLoader $addressDetailPageLoader;
  48.     private AbstractListAddressRoute $listAddressRoute;
  49.     private AbstractUpsertAddressRoute $updateAddressRoute;
  50.     private AbstractDeleteAddressRoute $deleteAddressRoute;
  51.     private AbstractChangeCustomerProfileRoute $updateCustomerProfileRoute;
  52.     public function __construct(
  53.         AddressListingPageLoader $addressListingPageLoader,
  54.         AddressDetailPageLoader $addressDetailPageLoader,
  55.         AccountService $accountService,
  56.         AbstractListAddressRoute $listAddressRoute,
  57.         AbstractUpsertAddressRoute $updateAddressRoute,
  58.         AbstractDeleteAddressRoute $deleteAddressRoute,
  59.         AbstractChangeCustomerProfileRoute $updateCustomerProfileRoute
  60.     ) {
  61.         $this->accountService $accountService;
  62.         $this->addressListingPageLoader $addressListingPageLoader;
  63.         $this->addressDetailPageLoader $addressDetailPageLoader;
  64.         $this->listAddressRoute $listAddressRoute;
  65.         $this->updateAddressRoute $updateAddressRoute;
  66.         $this->deleteAddressRoute $deleteAddressRoute;
  67.         $this->updateCustomerProfileRoute $updateCustomerProfileRoute;
  68.     }
  69.     /**
  70.      * @Since("6.0.0.0")
  71.      * @LoginRequired()
  72.      * @Route("/account/address", name="frontend.account.address.page", options={"seo"="false"}, methods={"GET"})
  73.      * @NoStore
  74.      *
  75.      * @throws CustomerNotLoggedInException
  76.      */
  77.     public function accountAddressOverview(Request $requestSalesChannelContext $contextCustomerEntity $customer): Response
  78.     {
  79.         $page $this->addressListingPageLoader->load($request$context$customer);
  80.         $this->hook(new AddressListingPageLoadedHook($page$context));
  81.         return $this->renderStorefront('@Storefront/storefront/page/account/addressbook/index.html.twig', ['page' => $page]);
  82.     }
  83.     /**
  84.      * @Since("6.0.0.0")
  85.      * @LoginRequired()
  86.      * @Route("/account/address/create", name="frontend.account.address.create.page", options={"seo"="false"}, methods={"GET"})
  87.      * @NoStore
  88.      *
  89.      * @throws CustomerNotLoggedInException
  90.      */
  91.     public function accountCreateAddress(Request $requestRequestDataBag $dataSalesChannelContext $contextCustomerEntity $customer): Response
  92.     {
  93.         $page $this->addressDetailPageLoader->load($request$context$customer);
  94.         $this->hook(new AddressDetailPageLoadedHook($page$context));
  95.         return $this->renderStorefront('@Storefront/storefront/page/account/addressbook/create.html.twig', [
  96.             'page' => $page,
  97.             'data' => $data,
  98.         ]);
  99.     }
  100.     /**
  101.      * @Since("6.0.0.0")
  102.      * @LoginRequired()
  103.      * @Route("/account/address/{addressId}", name="frontend.account.address.edit.page", options={"seo"="false"}, methods={"GET"})
  104.      * @NoStore
  105.      *
  106.      * @throws CustomerNotLoggedInException
  107.      */
  108.     public function accountEditAddress(Request $requestSalesChannelContext $contextCustomerEntity $customer): Response
  109.     {
  110.         $page $this->addressDetailPageLoader->load($request$context$customer);
  111.         $this->hook(new AddressDetailPageLoadedHook($page$context));
  112.         return $this->renderStorefront('@Storefront/storefront/page/account/addressbook/edit.html.twig', ['page' => $page]);
  113.     }
  114.     /**
  115.      * @Since("6.0.0.0")
  116.      * @LoginRequired()
  117.      * @Route("/account/address/default-{type}/{addressId}", name="frontend.account.address.set-default-address", methods={"POST"})
  118.      *
  119.      * @throws CustomerNotLoggedInException
  120.      * @throws InvalidUuidException
  121.      */
  122.     public function switchDefaultAddress(string $typestring $addressIdSalesChannelContext $contextCustomerEntity $customer): RedirectResponse
  123.     {
  124.         if (!Uuid::isValid($addressId)) {
  125.             throw new InvalidUuidException($addressId);
  126.         }
  127.         $success true;
  128.         try {
  129.             if ($type === self::ADDRESS_TYPE_SHIPPING) {
  130.                 $this->accountService->setDefaultShippingAddress($addressId$context$customer);
  131.             } elseif ($type === self::ADDRESS_TYPE_BILLING) {
  132.                 $this->accountService->setDefaultBillingAddress($addressId$context$customer);
  133.             } else {
  134.                 $success false;
  135.             }
  136.         } catch (AddressNotFoundException $exception) {
  137.             $success false;
  138.         }
  139.         return new RedirectResponse(
  140.             $this->generateUrl('frontend.account.address.page', ['changedDefaultAddress' => $success])
  141.         );
  142.     }
  143.     /**
  144.      * @Since("6.0.0.0")
  145.      * @LoginRequired()
  146.      * @Route("/account/address/delete/{addressId}", name="frontend.account.address.delete", options={"seo"="false"}, methods={"POST"})
  147.      *
  148.      * @throws CustomerNotLoggedInException
  149.      */
  150.     public function deleteAddress(string $addressIdSalesChannelContext $contextCustomerEntity $customer): Response
  151.     {
  152.         $success true;
  153.         if (!$addressId) {
  154.             throw new MissingRequestParameterException('addressId');
  155.         }
  156.         try {
  157.             $this->deleteAddressRoute->delete($addressId$context$customer);
  158.         } catch (InvalidUuidException AddressNotFoundException CannotDeleteDefaultAddressException $exception) {
  159.             $success false;
  160.         }
  161.         return new RedirectResponse($this->generateUrl('frontend.account.address.page', ['addressDeleted' => $success]));
  162.     }
  163.     /**
  164.      * @Since("6.0.0.0")
  165.      * @LoginRequired()
  166.      * @Route("/account/address/create", name="frontend.account.address.create", options={"seo"="false"}, methods={"POST"})
  167.      * @Route("/account/address/{addressId}", name="frontend.account.address.edit.save", options={"seo"="false"}, methods={"POST"})
  168.      *
  169.      * @throws CustomerNotLoggedInException
  170.      */
  171.     public function saveAddress(RequestDataBag $dataSalesChannelContext $contextCustomerEntity $customer): Response
  172.     {
  173.         /** @var RequestDataBag $address */
  174.         $address $data->get('address');
  175.         try {
  176.             $this->updateAddressRoute->upsert(
  177.                 $address->get('id'),
  178.                 $address->toRequestDataBag(),
  179.                 $context,
  180.                 $customer
  181.             );
  182.             return new RedirectResponse($this->generateUrl('frontend.account.address.page', ['addressSaved' => true]));
  183.         } catch (ConstraintViolationException $formViolations) {
  184.         }
  185.         if (!$address->get('id')) {
  186.             return $this->forwardToRoute('frontend.account.address.create.page', ['formViolations' => $formViolations]);
  187.         }
  188.         return $this->forwardToRoute(
  189.             'frontend.account.address.edit.page',
  190.             ['formViolations' => $formViolations],
  191.             ['addressId' => $address->get('id')]
  192.         );
  193.     }
  194.     /**
  195.      * @Since("6.0.0.0")
  196.      * @LoginRequired(allowGuest=true)
  197.      * @Route("/widgets/account/address-book", name="frontend.account.addressbook", options={"seo"=true}, methods={"POST"}, defaults={"XmlHttpRequest"=true})
  198.      */
  199.     public function addressBook(Request $requestRequestDataBag $dataBagSalesChannelContext $contextCustomerEntity $customer): Response
  200.     {
  201.         $viewData = new AddressEditorModalStruct();
  202.         $this->handleChangeableAddresses($viewData$dataBag$context$customer);
  203.         $this->handleAddressCreation($viewData$dataBag$context$customer);
  204.         $this->handleAddressSelection($viewData$dataBag$context$customer);
  205.         $page $this->addressListingPageLoader->load($request$context$customer);
  206.         $this->hook(new AddressBookWidgetLoadedHook($page$context));
  207.         $viewData->setPage($page);
  208.         if (Feature::isActive('FEATURE_NEXT_15957')) {
  209.             $this->handleCustomerVatIds($dataBag$context$customer);
  210.         }
  211.         if ($request->get('redirectTo') || $request->get('forwardTo')) {
  212.             return $this->createActionResponse($request);
  213.         }
  214.         $response $this->renderStorefront(
  215.             '@Storefront/storefront/component/address/address-editor-modal.html.twig',
  216.             $viewData->getVars()
  217.         );
  218.         $response->headers->set('x-robots-tag''noindex');
  219.         return $response;
  220.     }
  221.     private function handleAddressCreation(
  222.         AddressEditorModalStruct $viewData,
  223.         RequestDataBag $dataBag,
  224.         SalesChannelContext $context,
  225.         CustomerEntity $customer
  226.     ): void {
  227.         /** @var DataBag|null $addressData */
  228.         $addressData $dataBag->get('address');
  229.         $addressId null;
  230.         if ($addressData === null) {
  231.             return;
  232.         }
  233.         try {
  234.             $response $this->updateAddressRoute->upsert(
  235.                 $addressData->get('id'),
  236.                 $addressData->toRequestDataBag(),
  237.                 $context,
  238.                 $customer
  239.             );
  240.             $addressId $response->getAddress()->getId();
  241.             $addressType null;
  242.             if ($viewData->isChangeBilling()) {
  243.                 $addressType self::ADDRESS_TYPE_BILLING;
  244.             } elseif ($viewData->isChangeShipping()) {
  245.                 $addressType self::ADDRESS_TYPE_SHIPPING;
  246.             }
  247.             // prepare data to set newly created address as customers default
  248.             if ($addressType) {
  249.                 $dataBag->set('selectAddress', new RequestDataBag([
  250.                     'id' => $addressId,
  251.                     'type' => $addressType,
  252.                 ]));
  253.             }
  254.             $success true;
  255.             $messages = ['type' => 'success''text' => $this->trans('account.addressSaved')];
  256.         } catch (\Exception $exception) {
  257.             $success false;
  258.             $messages = ['type' => 'danger''text' => $this->trans('error.message-default')];
  259.         }
  260.         $viewData->setAddressId($addressId);
  261.         $viewData->setSuccess($success);
  262.         $viewData->setMessages($messages);
  263.     }
  264.     private function handleChangeableAddresses(
  265.         AddressEditorModalStruct $viewData,
  266.         RequestDataBag $dataBag,
  267.         SalesChannelContext $context,
  268.         CustomerEntity $customer
  269.     ): void {
  270.         $changeableAddresses $dataBag->get('changeableAddresses');
  271.         if ($changeableAddresses === null) {
  272.             return;
  273.         }
  274.         $viewData->setChangeShipping((bool) $changeableAddresses->get('changeShipping'));
  275.         $viewData->setChangeBilling((bool) $changeableAddresses->get('changeBilling'));
  276.         $addressId $dataBag->get('id');
  277.         if (!$addressId) {
  278.             return;
  279.         }
  280.         $viewData->setAddress($this->getById($addressId$context$customer));
  281.     }
  282.     /**
  283.      * @throws CustomerNotLoggedInException
  284.      * @throws InvalidUuidException
  285.      */
  286.     private function handleAddressSelection(
  287.         AddressEditorModalStruct $viewData,
  288.         RequestDataBag $dataBag,
  289.         SalesChannelContext $context,
  290.         CustomerEntity $customer
  291.     ): void {
  292.         $selectedAddress $dataBag->get('selectAddress');
  293.         if ($selectedAddress === null) {
  294.             return;
  295.         }
  296.         $addressType $selectedAddress->get('type');
  297.         $addressId $selectedAddress->get('id');
  298.         if (!Uuid::isValid($addressId)) {
  299.             throw new InvalidUuidException($addressId);
  300.         }
  301.         $success true;
  302.         try {
  303.             if ($addressType === self::ADDRESS_TYPE_SHIPPING) {
  304.                 $address $this->getById($addressId$context$customer);
  305.                 $context->getCustomer()->setDefaultShippingAddress($address);
  306.                 $this->accountService->setDefaultShippingAddress($addressId$context$customer);
  307.             } elseif ($addressType === self::ADDRESS_TYPE_BILLING) {
  308.                 $address $this->getById($addressId$context$customer);
  309.                 $context->getCustomer()->setDefaultBillingAddress($address);
  310.                 $this->accountService->setDefaultBillingAddress($addressId$context$customer);
  311.             } else {
  312.                 $success false;
  313.             }
  314.         } catch (AddressNotFoundException $exception) {
  315.             $success false;
  316.         }
  317.         if ($success) {
  318.             $this->addFlash(self::SUCCESS$this->trans('account.addressDefaultChanged'));
  319.         } else {
  320.             $this->addFlash(self::DANGER$this->trans('account.addressDefaultNotChanged'));
  321.         }
  322.         $viewData->setSuccess($success);
  323.     }
  324.     private function getById(string $addressIdSalesChannelContext $contextCustomerEntity $customer): CustomerAddressEntity
  325.     {
  326.         if (!Uuid::isValid($addressId)) {
  327.             throw new InvalidUuidException($addressId);
  328.         }
  329.         $criteria = new Criteria();
  330.         $criteria->addFilter(new EqualsFilter('id'$addressId));
  331.         $criteria->addFilter(new EqualsFilter('customerId'$customer->getId()));
  332.         $address $this->listAddressRoute->load($criteria$context$customer)->getAddressCollection()->get($addressId);
  333.         if (!$address) {
  334.             throw new AddressNotFoundException($addressId);
  335.         }
  336.         return $address;
  337.     }
  338.     private function handleCustomerVatIds(RequestDataBag $dataBagSalesChannelContext $contextCustomerEntity $customer): void
  339.     {
  340.         if (!$dataBag->has('vatIds')) {
  341.             return;
  342.         }
  343.         $newVatIds $dataBag->get('vatIds')->all();
  344.         $oldVatIds $customer->getVatIds() ?? [];
  345.         if (!array_diff($newVatIds$oldVatIds) && !array_diff($oldVatIds$newVatIds)) {
  346.             return;
  347.         }
  348.         $dataCustomer CustomerTransformer::transform($customer);
  349.         $dataCustomer['vatIds'] = $newVatIds;
  350.         $dataCustomer['accountType'] = $customer->getCompany() === null CustomerEntity::ACCOUNT_TYPE_PRIVATE CustomerEntity::ACCOUNT_TYPE_BUSINESS;
  351.         $newDataBag = new RequestDataBag($dataCustomer);
  352.         $this->updateCustomerProfileRoute->change($newDataBag$context$customer);
  353.     }
  354. }