Passed
Push — trunk ( c1976e...a42427 )
by Christian
12:40 queued 13s
created

AddressController::handleAddressCreation()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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