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

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