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

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.  * @Route(defaults={"_routeScope"={"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.      * @Route("/account/address", name="frontend.account.address.page", options={"seo"="false"}, methods={"GET"}, defaults={"_loginRequired"=true})
  72.      * @NoStore
  73.      */
  74.     public function accountAddressOverview(Request $requestSalesChannelContext $contextCustomerEntity $customer): Response
  75.     {
  76.         $page $this->addressListingPageLoader->load($request$context$customer);
  77.         $this->hook(new AddressListingPageLoadedHook($page$context));
  78.         return $this->renderStorefront('@Storefront/storefront/page/account/addressbook/index.html.twig', ['page' => $page]);
  79.     }
  80.     /**
  81.      * @Since("6.0.0.0")
  82.      * @Route("/account/address/create", name="frontend.account.address.create.page", options={"seo"="false"}, methods={"GET"}, defaults={"_loginRequired"=true})
  83.      * @NoStore
  84.      */
  85.     public function accountCreateAddress(Request $requestRequestDataBag $dataSalesChannelContext $contextCustomerEntity $customer): Response
  86.     {
  87.         $page $this->addressDetailPageLoader->load($request$context$customer);
  88.         $this->hook(new AddressDetailPageLoadedHook($page$context));
  89.         return $this->renderStorefront('@Storefront/storefront/page/account/addressbook/create.html.twig', [
  90.             'page' => $page,
  91.             'data' => $data,
  92.         ]);
  93.     }
  94.     /**
  95.      * @Since("6.0.0.0")
  96.      * @Route("/account/address/{addressId}", name="frontend.account.address.edit.page", options={"seo"="false"}, methods={"GET"}, defaults={"_loginRequired"=true})
  97.      * @NoStore
  98.      */
  99.     public function accountEditAddress(Request $requestSalesChannelContext $contextCustomerEntity $customer): Response
  100.     {
  101.         $page $this->addressDetailPageLoader->load($request$context$customer);
  102.         $this->hook(new AddressDetailPageLoadedHook($page$context));
  103.         return $this->renderStorefront('@Storefront/storefront/page/account/addressbook/edit.html.twig', ['page' => $page]);
  104.     }
  105.     /**
  106.      * @Since("6.0.0.0")
  107.      * @Route("/account/address/default-{type}/{addressId}", name="frontend.account.address.set-default-address", methods={"POST"}, defaults={"_loginRequired"=true})
  108.      */
  109.     public function switchDefaultAddress(string $typestring $addressIdSalesChannelContext $contextCustomerEntity $customer): RedirectResponse
  110.     {
  111.         if (!Uuid::isValid($addressId)) {
  112.             throw new InvalidUuidException($addressId);
  113.         }
  114.         $success true;
  115.         try {
  116.             if ($type === self::ADDRESS_TYPE_SHIPPING) {
  117.                 $this->accountService->setDefaultShippingAddress($addressId$context$customer);
  118.             } elseif ($type === self::ADDRESS_TYPE_BILLING) {
  119.                 $this->accountService->setDefaultBillingAddress($addressId$context$customer);
  120.             } else {
  121.                 $success false;
  122.             }
  123.         } catch (AddressNotFoundException $exception) {
  124.             $success false;
  125.         }
  126.         return new RedirectResponse(
  127.             $this->generateUrl('frontend.account.address.page', ['changedDefaultAddress' => $success])
  128.         );
  129.     }
  130.     /**
  131.      * @Since("6.0.0.0")
  132.      * @Route("/account/address/delete/{addressId}", name="frontend.account.address.delete", options={"seo"="false"}, methods={"POST"}, defaults={"_loginRequired"=true})
  133.      */
  134.     public function deleteAddress(string $addressIdSalesChannelContext $contextCustomerEntity $customer): Response
  135.     {
  136.         $success true;
  137.         if (!$addressId) {
  138.             throw new MissingRequestParameterException('addressId');
  139.         }
  140.         try {
  141.             $this->deleteAddressRoute->delete($addressId$context$customer);
  142.         } catch (InvalidUuidException AddressNotFoundException CannotDeleteDefaultAddressException $exception) {
  143.             $success false;
  144.         }
  145.         return new RedirectResponse($this->generateUrl('frontend.account.address.page', ['addressDeleted' => $success]));
  146.     }
  147.     /**
  148.      * @Since("6.0.0.0")
  149.      * @Route("/account/address/create", name="frontend.account.address.create", options={"seo"="false"}, methods={"POST"}, defaults={"_loginRequired"=true})
  150.      * @Route("/account/address/{addressId}", name="frontend.account.address.edit.save", options={"seo"="false"}, methods={"POST"}, defaults={"_loginRequired"=true})
  151.      */
  152.     public function saveAddress(RequestDataBag $dataSalesChannelContext $contextCustomerEntity $customer): Response
  153.     {
  154.         /** @var RequestDataBag $address */
  155.         $address $data->get('address');
  156.         try {
  157.             $this->updateAddressRoute->upsert(
  158.                 $address->get('id'),
  159.                 $address->toRequestDataBag(),
  160.                 $context,
  161.                 $customer
  162.             );
  163.             return new RedirectResponse($this->generateUrl('frontend.account.address.page', ['addressSaved' => true]));
  164.         } catch (ConstraintViolationException $formViolations) {
  165.         }
  166.         if (!$address->get('id')) {
  167.             return $this->forwardToRoute('frontend.account.address.create.page', ['formViolations' => $formViolations]);
  168.         }
  169.         return $this->forwardToRoute(
  170.             'frontend.account.address.edit.page',
  171.             ['formViolations' => $formViolations],
  172.             ['addressId' => $address->get('id')]
  173.         );
  174.     }
  175.     /**
  176.      * @Since("6.0.0.0")
  177.      * @Route("/widgets/account/address-book", name="frontend.account.addressbook", options={"seo"=true}, methods={"POST"}, defaults={"XmlHttpRequest"=true, "_loginRequired"=true, "_loginRequiredAllowGuest"=true})
  178.      */
  179.     public function addressBook(Request $requestRequestDataBag $dataBagSalesChannelContext $contextCustomerEntity $customer): Response
  180.     {
  181.         $viewData = new AddressEditorModalStruct();
  182.         $this->handleChangeableAddresses($viewData$dataBag$context$customer);
  183.         $this->handleAddressCreation($viewData$dataBag$context$customer);
  184.         $this->handleAddressSelection($viewData$dataBag$context$customer);
  185.         $page $this->addressListingPageLoader->load($request$context$customer);
  186.         $this->hook(new AddressBookWidgetLoadedHook($page$context));
  187.         $viewData->setPage($page);
  188.         if (Feature::isActive('FEATURE_NEXT_15957')) {
  189.             $this->handleCustomerVatIds($dataBag$context$customer);
  190.         }
  191.         if ($request->get('redirectTo') || $request->get('forwardTo')) {
  192.             return $this->createActionResponse($request);
  193.         }
  194.         $response $this->renderStorefront(
  195.             '@Storefront/storefront/component/address/address-editor-modal.html.twig',
  196.             $viewData->getVars()
  197.         );
  198.         $response->headers->set('x-robots-tag''noindex');
  199.         return $response;
  200.     }
  201.     private function handleAddressCreation(
  202.         AddressEditorModalStruct $viewData,
  203.         RequestDataBag $dataBag,
  204.         SalesChannelContext $context,
  205.         CustomerEntity $customer
  206.     ): void {
  207.         /** @var DataBag|null $addressData */
  208.         $addressData $dataBag->get('address');
  209.         $addressId null;
  210.         if ($addressData === null) {
  211.             return;
  212.         }
  213.         try {
  214.             $response $this->updateAddressRoute->upsert(
  215.                 $addressData->get('id'),
  216.                 $addressData->toRequestDataBag(),
  217.                 $context,
  218.                 $customer
  219.             );
  220.             $addressId $response->getAddress()->getId();
  221.             $addressType null;
  222.             if ($viewData->isChangeBilling()) {
  223.                 $addressType self::ADDRESS_TYPE_BILLING;
  224.             } elseif ($viewData->isChangeShipping()) {
  225.                 $addressType self::ADDRESS_TYPE_SHIPPING;
  226.             }
  227.             // prepare data to set newly created address as customers default
  228.             if ($addressType) {
  229.                 $dataBag->set('selectAddress', new RequestDataBag([
  230.                     'id' => $addressId,
  231.                     'type' => $addressType,
  232.                 ]));
  233.             }
  234.             $success true;
  235.             $messages = ['type' => 'success''text' => $this->trans('account.addressSaved')];
  236.         } catch (\Exception $exception) {
  237.             $success false;
  238.             $messages = ['type' => 'danger''text' => $this->trans('error.message-default')];
  239.         }
  240.         $viewData->setAddressId($addressId);
  241.         $viewData->setSuccess($success);
  242.         $viewData->setMessages($messages);
  243.     }
  244.     private function handleChangeableAddresses(
  245.         AddressEditorModalStruct $viewData,
  246.         RequestDataBag $dataBag,
  247.         SalesChannelContext $context,
  248.         CustomerEntity $customer
  249.     ): void {
  250.         $changeableAddresses $dataBag->get('changeableAddresses');
  251.         if ($changeableAddresses === null) {
  252.             return;
  253.         }
  254.         $viewData->setChangeShipping((bool) $changeableAddresses->get('changeShipping'));
  255.         $viewData->setChangeBilling((bool) $changeableAddresses->get('changeBilling'));
  256.         $addressId $dataBag->get('id');
  257.         if (!$addressId) {
  258.             return;
  259.         }
  260.         $viewData->setAddress($this->getById($addressId$context$customer));
  261.     }
  262.     /**
  263.      * @throws CustomerNotLoggedInException
  264.      * @throws InvalidUuidException
  265.      */
  266.     private function handleAddressSelection(
  267.         AddressEditorModalStruct $viewData,
  268.         RequestDataBag $dataBag,
  269.         SalesChannelContext $context,
  270.         CustomerEntity $customer
  271.     ): void {
  272.         $selectedAddress $dataBag->get('selectAddress');
  273.         if ($selectedAddress === null) {
  274.             return;
  275.         }
  276.         $addressType $selectedAddress->get('type');
  277.         $addressId $selectedAddress->get('id');
  278.         if (!Uuid::isValid($addressId)) {
  279.             throw new InvalidUuidException($addressId);
  280.         }
  281.         $success true;
  282.         try {
  283.             if ($addressType === self::ADDRESS_TYPE_SHIPPING) {
  284.                 $address $this->getById($addressId$context$customer);
  285.                 $context->getCustomer()->setDefaultShippingAddress($address);
  286.                 $this->accountService->setDefaultShippingAddress($addressId$context$customer);
  287.             } elseif ($addressType === self::ADDRESS_TYPE_BILLING) {
  288.                 $address $this->getById($addressId$context$customer);
  289.                 $context->getCustomer()->setDefaultBillingAddress($address);
  290.                 $this->accountService->setDefaultBillingAddress($addressId$context$customer);
  291.             } else {
  292.                 $success false;
  293.             }
  294.         } catch (AddressNotFoundException $exception) {
  295.             $success false;
  296.         }
  297.         if ($success) {
  298.             $this->addFlash(self::SUCCESS$this->trans('account.addressDefaultChanged'));
  299.         } else {
  300.             $this->addFlash(self::DANGER$this->trans('account.addressDefaultNotChanged'));
  301.         }
  302.         $viewData->setSuccess($success);
  303.     }
  304.     private function getById(string $addressIdSalesChannelContext $contextCustomerEntity $customer): CustomerAddressEntity
  305.     {
  306.         if (!Uuid::isValid($addressId)) {
  307.             throw new InvalidUuidException($addressId);
  308.         }
  309.         $criteria = new Criteria();
  310.         $criteria->addFilter(new EqualsFilter('id'$addressId));
  311.         $criteria->addFilter(new EqualsFilter('customerId'$customer->getId()));
  312.         $address $this->listAddressRoute->load($criteria$context$customer)->getAddressCollection()->get($addressId);
  313.         if (!$address) {
  314.             throw new AddressNotFoundException($addressId);
  315.         }
  316.         return $address;
  317.     }
  318.     private function handleCustomerVatIds(RequestDataBag $dataBagSalesChannelContext $contextCustomerEntity $customer): void
  319.     {
  320.         if (!$dataBag->has('vatIds')) {
  321.             return;
  322.         }
  323.         $newVatIds $dataBag->get('vatIds')->all();
  324.         $oldVatIds $customer->getVatIds() ?? [];
  325.         if (!array_diff($newVatIds$oldVatIds) && !array_diff($oldVatIds$newVatIds)) {
  326.             return;
  327.         }
  328.         $dataCustomer CustomerTransformer::transform($customer);
  329.         $dataCustomer['vatIds'] = $newVatIds;
  330.         $dataCustomer['accountType'] = $customer->getCompany() === null CustomerEntity::ACCOUNT_TYPE_PRIVATE CustomerEntity::ACCOUNT_TYPE_BUSINESS;
  331.         $newDataBag = new RequestDataBag($dataCustomer);
  332.         $this->updateCustomerProfileRoute->change($newDataBag$context$customer);
  333.     }
  334. }