Customer::createErrorCustomerResponseTransfer()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
namespace Spryker\Zed\Customer\Business\Customer;
9
10
use DateTime;
11
use Generated\Shared\Transfer\AddressesTransfer;
12
use Generated\Shared\Transfer\AddressTransfer;
13
use Generated\Shared\Transfer\CustomerCollectionTransfer;
14
use Generated\Shared\Transfer\CustomerErrorTransfer;
15
use Generated\Shared\Transfer\CustomerResponseTransfer;
16
use Generated\Shared\Transfer\CustomerTransfer;
17
use Generated\Shared\Transfer\LocaleTransfer;
18
use Generated\Shared\Transfer\MailTransfer;
19
use Generated\Shared\Transfer\MessageTransfer;
20
use Orm\Zed\Customer\Persistence\SpyCustomer;
21
use Orm\Zed\Customer\Persistence\SpyCustomerAddress;
22
use Orm\Zed\Locale\Persistence\SpyLocaleQuery;
23
use Propel\Runtime\Collection\ObjectCollection;
24
use Spryker\Service\UtilText\UtilTextService;
25
use Spryker\Shared\Customer\Code\Messages;
26
use Spryker\Shared\Customer\CustomerConfig as SharedCustomerConfig;
27
use Spryker\Zed\Customer\Business\Customer\Checker\PasswordResetExpirationCheckerInterface;
28
use Spryker\Zed\Customer\Business\CustomerExpander\CustomerExpanderInterface;
29
use Spryker\Zed\Customer\Business\CustomerPasswordPolicy\CustomerPasswordPolicyValidatorInterface;
30
use Spryker\Zed\Customer\Business\Exception\CustomerNotFoundException;
31
use Spryker\Zed\Customer\Business\Executor\CustomerPluginExecutorInterface;
32
use Spryker\Zed\Customer\Business\ReferenceGenerator\CustomerReferenceGeneratorInterface;
33
use Spryker\Zed\Customer\Communication\Plugin\Mail\CustomerRestoredPasswordConfirmationMailTypePlugin;
34
use Spryker\Zed\Customer\Communication\Plugin\Mail\CustomerRestorePasswordMailTypePlugin;
35
use Spryker\Zed\Customer\CustomerConfig;
36
use Spryker\Zed\Customer\Dependency\Facade\CustomerToLocaleInterface;
37
use Spryker\Zed\Customer\Dependency\Facade\CustomerToMailInterface;
38
use Spryker\Zed\Customer\Persistence\CustomerQueryContainerInterface;
39
use Symfony\Component\Console\Output\OutputInterface;
40
use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher;
41
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
42
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
43
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
44
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
45
46
class Customer implements CustomerInterface
47
{
48
    /**
49
     * @var int
50
     */
51
    protected const BCRYPT_FACTOR = 12;
52
53
    /**
54
     * @var string
55
     */
56
    protected const BCRYPT_SALT = '';
57
58
    /**
59
     * @var string
60
     */
61
    protected const GLOSSARY_KEY_CUSTOMER_AUTHORIZATION_VALIDATE_EMAIL_ADDRESS = 'customer.authorization.validate_email_address';
62
63
    /**
64
     * @var string
65
     */
66
    protected const GLOSSARY_KEY_CUSTOMER_REGISTRATION_SUCCESS = 'customer.registration.success';
67
68
    /**
69
     * @param \Spryker\Zed\Customer\Persistence\CustomerQueryContainerInterface $queryContainer
70
     * @param \Spryker\Zed\Customer\Business\ReferenceGenerator\CustomerReferenceGeneratorInterface $customerReferenceGenerator
71
     * @param \Spryker\Zed\Customer\CustomerConfig $customerConfig
72
     * @param \Spryker\Zed\Customer\Business\Customer\EmailValidatorInterface $emailValidator
73
     * @param \Spryker\Zed\Customer\Dependency\Facade\CustomerToMailInterface $mailFacade
74
     * @param \Orm\Zed\Locale\Persistence\SpyLocaleQuery $localePropelQuery $localePropelQuery
75
     * @param \Spryker\Zed\Customer\Dependency\Facade\CustomerToLocaleInterface $localeFacade
76
     * @param \Spryker\Zed\Customer\Business\CustomerExpander\CustomerExpanderInterface $customerExpander
77
     * @param \Spryker\Zed\Customer\Business\CustomerPasswordPolicy\CustomerPasswordPolicyValidatorInterface $customerPasswordPolicyValidator
78
     * @param \Spryker\Zed\Customer\Business\Customer\Checker\PasswordResetExpirationCheckerInterface $passwordResetExpirationChecker
79
     * @param \Spryker\Zed\Customer\Business\Executor\CustomerPluginExecutorInterface $customerPluginExecutor
80
     * @param array<\Spryker\Zed\CustomerExtension\Dependency\Plugin\CustomerPreUpdatePluginInterface> $customerPreUpdatePlugins
81
     */
82
    public function __construct(
83
        protected CustomerQueryContainerInterface $queryContainer,
84
        protected CustomerReferenceGeneratorInterface $customerReferenceGenerator,
85
        protected CustomerConfig $customerConfig,
86
        protected EmailValidatorInterface $emailValidator,
87
        protected CustomerToMailInterface $mailFacade,
88
        protected SpyLocaleQuery $localePropelQuery,
89
        protected CustomerToLocaleInterface $localeFacade,
90
        protected CustomerExpanderInterface $customerExpander,
91
        protected CustomerPasswordPolicyValidatorInterface $customerPasswordPolicyValidator,
92
        protected PasswordResetExpirationCheckerInterface $passwordResetExpirationChecker,
93
        protected CustomerPluginExecutorInterface $customerPluginExecutor,
94
        protected array $customerPreUpdatePlugins
95
    ) {
96
    }
97
98
    /**
99
     * @param string $email
100
     *
101
     * @return bool
102
     */
103
    public function hasEmail($email)
104
    {
105
        $customerCount = $this->queryContainer
106
            ->queryCustomerByEmail($email)
107
            ->count();
108
109
        return ($customerCount > 0);
110
    }
111
112
    /**
113
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
114
     *
115
     * @return \Generated\Shared\Transfer\CustomerTransfer
116
     */
117
    public function get(CustomerTransfer $customerTransfer)
118
    {
119
        $customerEntity = $this->getCustomer($customerTransfer);
120
        $customerTransfer->fromArray($customerEntity->toArray(), true);
121
122
        $customerTransfer = $this->attachAddresses($customerTransfer, $customerEntity);
123
        $customerTransfer = $this->attachLocale($customerTransfer, $customerEntity);
124
        $customerTransfer = $this->customerExpander->expand($customerTransfer);
125
126
        return $customerTransfer;
127
    }
128
129
    /**
130
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
131
     * @param \Orm\Zed\Customer\Persistence\SpyCustomer $customerEntity
132
     *
133
     * @return \Generated\Shared\Transfer\CustomerTransfer
134
     */
135
    protected function attachAddresses(CustomerTransfer $customerTransfer, SpyCustomer $customerEntity)
136
    {
137
        $addresses = $customerEntity->getAddresses();
138
        $addressesTransfer = $this->entityCollectionToTransferCollection($addresses, $customerEntity);
139
        $customerTransfer->setAddresses($addressesTransfer);
140
141
        $customerTransfer = $this->attachAddressesTransfer($customerTransfer, $addressesTransfer);
142
143
        return $customerTransfer;
144
    }
145
146
    /**
147
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
148
     *
149
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
150
     */
151
    public function add($customerTransfer)
152
    {
153
        if ($customerTransfer->getPassword()) {
154
            $customerResponseTransfer = $this->customerPasswordPolicyValidator->validatePassword($customerTransfer->getPassword());
155
            if (!$customerResponseTransfer->getIsSuccess()) {
156
                return $customerResponseTransfer;
157
            }
158
        }
159
160
        $customerTransfer = $this->encryptPassword($customerTransfer);
161
162
        $customerEntity = new SpyCustomer();
163
        $customerEntity->fromArray($customerTransfer->toArray());
164
165
        if ($customerTransfer->getLocale() !== null) {
166
            $this->addLocaleByLocaleName($customerEntity, $customerTransfer->getLocale()->getLocaleName());
167
        }
168
169
        $this->addLocale($customerEntity);
170
171
        $customerResponseTransfer = $this->createCustomerResponseTransfer();
172
        $customerResponseTransfer = $this->validateCustomerEmail($customerResponseTransfer, $customerEntity);
173
        if ($customerResponseTransfer->getIsSuccess() !== true) {
174
            return $customerResponseTransfer;
175
        }
176
177
        $customerEntity->setCustomerReference($this->customerReferenceGenerator->generateCustomerReference($customerTransfer));
178
        $customerEntity->setRegistrationKey($this->generateKey());
179
180
        $customerEntity->save();
181
182
        $customerTransfer->setIdCustomer($customerEntity->getPrimaryKey());
183
        $customerTransfer->setCustomerReference($customerEntity->getCustomerReference());
184
        $customerTransfer->setRegistrationKey($customerEntity->getRegistrationKey());
185
        $customerTransfer->setCreatedAt($customerEntity->getCreatedAt()->format('Y-m-d H:i:s.u'));
186
        $customerTransfer->setUpdatedAt($customerEntity->getUpdatedAt()->format('Y-m-d H:i:s.u'));
187
188
        $customerResponseTransfer
189
            ->setIsSuccess(true)
190
            ->setCustomerTransfer($customerTransfer);
191
192
        return $customerResponseTransfer;
193
    }
194
195
    /**
196
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
197
     *
198
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
199
     */
200
    public function register(CustomerTransfer $customerTransfer)
201
    {
202
        $customerResponseTransfer = $this->add($customerTransfer);
203
204
        if (!$customerResponseTransfer->getIsSuccess()) {
205
            return $customerResponseTransfer;
206
        }
207
208
        $this->customerPluginExecutor->executePostCustomerRegistrationPlugins($customerTransfer);
209
        $customerTransfer = $this->customerExpander->expand($customerTransfer);
210
211
        $this->sendRegistrationToken($customerTransfer);
212
213
        if ($customerTransfer->getSendPasswordToken()) {
214
            $this->sendPasswordRestoreMail($customerTransfer);
215
        }
216
217
        $message = static::GLOSSARY_KEY_CUSTOMER_REGISTRATION_SUCCESS;
218
        if ($this->customerConfig->isDoubleOptInEnabled()) {
219
            $message = static::GLOSSARY_KEY_CUSTOMER_AUTHORIZATION_VALIDATE_EMAIL_ADDRESS;
220
        }
221
        $messageTransfer = (new MessageTransfer())
222
            ->setValue($message);
223
224
        $customerResponseTransfer->setMessage($messageTransfer);
225
226
        return $customerResponseTransfer;
227
    }
228
229
    /**
230
     * @param \Orm\Zed\Customer\Persistence\SpyCustomer $customerEntity
231
     *
232
     * @return void
233
     */
234
    protected function addLocale(SpyCustomer $customerEntity)
235
    {
236
        if ($customerEntity->getLocale()) {
237
            return;
238
        }
239
240
        $localeName = $this->localeFacade->getCurrentLocaleName();
241
        $localeEntity = $this->localePropelQuery->findByLocaleName($localeName)->getFirst();
242
243
        if ($localeEntity) {
244
            $customerEntity->setLocale($localeEntity);
245
        }
246
    }
247
248
    /**
249
     * @param \Orm\Zed\Customer\Persistence\SpyCustomer $customerEntity
250
     * @param string $localeName
251
     *
252
     * @return void
253
     */
254
    protected function addLocaleByLocaleName(SpyCustomer $customerEntity, $localeName)
255
    {
256
        $localeEntity = $this->localePropelQuery->findByLocaleName($localeName)->getFirst();
257
258
        if ($localeEntity) {
259
            $customerEntity->setLocale($localeEntity);
260
        }
261
    }
262
263
    /**
264
     * @return string
265
     */
266
    protected function generateKey()
267
    {
268
        $utilTextService = new UtilTextService();
269
270
        return $utilTextService->generateRandomString(32);
271
    }
272
273
    /**
274
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
275
     *
276
     * @return void
277
     */
278
    protected function sendPasswordRestoreToken(CustomerTransfer $customerTransfer)
279
    {
280
        $customerTransfer = $this->get($customerTransfer);
281
        $restorePasswordLink = $this->customerConfig
282
            ->getCustomerPasswordRestoreTokenUrl(
283
                $customerTransfer->getRestorePasswordKey(),
284
                $customerTransfer->getStoreName(),
285
            );
286
287
        $restorePasswordLink = $this->appendCustomerLocaleToUrl($restorePasswordLink, $customerTransfer->getLocale());
288
289
        $customerTransfer->setRestorePasswordLink($restorePasswordLink);
290
291
        $mailTransfer = new MailTransfer();
292
        $mailTransfer->setType(CustomerRestorePasswordMailTypePlugin::MAIL_TYPE);
293
        $mailTransfer->setCustomer($customerTransfer);
294
        $mailTransfer->setLocale($customerTransfer->getLocale());
295
        $mailTransfer->setStoreName($customerTransfer->getStoreName());
296
297
        $this->mailFacade->handleMail($mailTransfer);
298
    }
299
300
    /**
301
     * @param string $url
302
     * @param \Generated\Shared\Transfer\LocaleTransfer|null $locale
303
     *
304
     * @return string
305
     */
306
    protected function appendCustomerLocaleToUrl(string $url, ?LocaleTransfer $locale)
307
    {
308
        if (!$locale) {
309
            return $url;
310
        }
311
312
        $urlComponents = parse_url($url);
313
314
        $url .= isset($urlComponents['query']) ? '&' : '?';
315
        $url .= http_build_query([
316
            SharedCustomerConfig::URL_PARAM_LOCALE => $locale->getLocaleName(),
317
        ]);
318
319
        return $url;
320
    }
321
322
    /**
323
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
324
     *
325
     * @return bool
326
     */
327
    protected function sendRegistrationToken(CustomerTransfer $customerTransfer)
328
    {
329
        $confirmationLink = $this->customerConfig
330
            ->getRegisterConfirmTokenUrl(
331
                $customerTransfer->getRegistrationKey(),
332
                $customerTransfer->getStoreName(),
333
            );
334
335
        $confirmationLink = $this->appendCustomerLocaleToUrl($confirmationLink, $customerTransfer->getLocale());
336
337
        $customerTransfer->setConfirmationLink($confirmationLink);
338
339
        $mailType = $this->customerConfig->isDoubleOptInEnabled()
340
            ? CustomerConfig::CUSTOMER_REGISTRATION_WITH_CONFIRMATION_MAIL_TYPE
341
            : CustomerConfig::CUSTOMER_REGISTRATION_MAIL_TYPE;
342
343
        $mailTransfer = new MailTransfer();
344
        $mailTransfer->setType($mailType);
345
        $mailTransfer->setCustomer($customerTransfer);
346
        $mailTransfer->setLocale($customerTransfer->getLocale());
347
        $mailTransfer->setStoreName($customerTransfer->getStoreName());
348
349
        $this->mailFacade->handleMail($mailTransfer);
350
351
        return true;
352
    }
353
354
    /**
355
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
356
     *
357
     * @return void
358
     */
359
    protected function sendPasswordRestoreConfirmation(CustomerTransfer $customerTransfer)
360
    {
361
        $customerTransfer = $this->get($customerTransfer);
362
363
        $mailTransfer = new MailTransfer();
364
        $mailTransfer->setType(CustomerRestoredPasswordConfirmationMailTypePlugin::MAIL_TYPE);
365
        $mailTransfer->setCustomer($customerTransfer);
366
        $mailTransfer->setLocale($customerTransfer->getLocale());
367
        $mailTransfer->setStoreName($customerTransfer->getStoreName());
368
369
        $this->mailFacade->handleMail($mailTransfer);
370
    }
371
372
    /**
373
     * @deprecated Use {@link \Spryker\Zed\Customer\Business\Customer\Customer::confirmCustomerRegistration()} instead.
374
     *
375
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
376
     *
377
     * @throws \Spryker\Zed\Customer\Business\Exception\CustomerNotFoundException
378
     *
379
     * @return \Generated\Shared\Transfer\CustomerTransfer
380
     */
381
    public function confirmRegistration(CustomerTransfer $customerTransfer)
382
    {
383
        $customerResponseTransfer = $this->confirmCustomerRegistration($customerTransfer);
384
        if (!$customerResponseTransfer->getIsSuccess()) {
385
            throw new CustomerNotFoundException(sprintf('Customer for registration key `%s` not found', $customerTransfer->getRegistrationKey()));
386
        }
387
388
        return $customerResponseTransfer->getCustomerTransfer();
389
    }
390
391
    /**
392
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
393
     *
394
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
395
     */
396
    public function confirmCustomerRegistration(CustomerTransfer $customerTransfer): CustomerResponseTransfer
397
    {
398
        $customerResponseTransfer = (new CustomerResponseTransfer())
399
            ->setCustomerTransfer($customerTransfer)
400
            ->setIsSuccess(true);
401
402
        $customerEntity = $this->queryContainer->queryCustomerByRegistrationKey($customerTransfer->getRegistrationKey())->findOne();
403
404
        if (!$customerEntity) {
405
            return $customerResponseTransfer
406
                ->setIsSuccess(false)
407
                ->addError((new CustomerErrorTransfer())->setMessage(CustomerConfig::GLOSSARY_KEY_CONFIRM_EMAIL_LINK_INVALID_OR_USED));
408
        }
409
410
        $customerEntity->setRegistered(new DateTime());
411
        $customerEntity->setRegistrationKey(null);
412
        $customerEntity->save();
413
414
        $customerTransfer = $customerTransfer->fromArray($customerEntity->toArray(), true);
415
416
        return $customerResponseTransfer->setCustomerTransfer($customerTransfer);
417
    }
418
419
    /**
420
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
421
     *
422
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
423
     */
424
    public function sendPasswordRestoreMail(CustomerTransfer $customerTransfer)
425
    {
426
        $customerResponseTransfer = $this->createCustomerResponseTransfer();
427
428
        try {
429
            $customerEntity = $this->getCustomer($customerTransfer);
430
        } catch (CustomerNotFoundException $e) {
431
            return $customerResponseTransfer;
432
        }
433
434
        $customerEntity->setRestorePasswordDate(new DateTime());
435
        $customerEntity->setRestorePasswordKey($this->generateKey());
436
437
        $customerEntity->save();
438
439
        $customerTransfer->fromArray($customerEntity->toArray(), true);
440
        $this->sendPasswordRestoreToken($customerTransfer);
441
442
        $customerResponseTransfer->setCustomerTransfer($customerTransfer);
443
444
        return $customerResponseTransfer;
445
    }
446
447
    /**
448
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
449
     *
450
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
451
     */
452
    public function restorePassword(CustomerTransfer $customerTransfer)
453
    {
454
        if ($this->customerConfig->isRestorePasswordValidationEnabled()) {
455
            $customerResponseTransfer = $this->customerPasswordPolicyValidator->validatePassword($customerTransfer->getPassword());
456
            if (!$customerResponseTransfer->getIsSuccess()) {
457
                return $customerResponseTransfer;
458
            }
459
        }
460
461
        $customerTransfer = $this->encryptPassword($customerTransfer);
462
463
        $customerResponseTransfer = $this->createCustomerResponseTransfer();
464
465
        try {
466
            $customerEntity = $this->getCustomer($customerTransfer);
467
        } catch (CustomerNotFoundException $e) {
468
            $customerError = new CustomerErrorTransfer();
469
            $customerError->setMessage(Messages::CUSTOMER_TOKEN_INVALID);
470
471
            $customerResponseTransfer
472
                ->setIsSuccess(false)
473
                ->addError($customerError);
474
475
            return $customerResponseTransfer;
476
        }
477
478
        $customerResponseTransfer = $this
479
            ->passwordResetExpirationChecker
480
            ->checkPasswordResetExpiration($customerEntity, $customerResponseTransfer);
481
482
        if (!$customerResponseTransfer->getIsSuccess()) {
483
             return $customerResponseTransfer;
484
        }
485
486
        $customerEntity->setRestorePasswordDate(null);
487
        $customerEntity->setRestorePasswordKey(null);
488
489
        $customerEntity->setPassword($customerTransfer->getPassword());
490
491
        $customerEntity->save();
492
        $customerTransfer->fromArray($customerEntity->toArray(), true);
493
        $this->sendPasswordRestoreConfirmation($customerTransfer);
494
495
        $customerResponseTransfer->setCustomerTransfer($customerTransfer);
496
497
        return $customerResponseTransfer;
498
    }
499
500
    /**
501
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
502
     *
503
     * @return bool
504
     */
505
    public function delete(CustomerTransfer $customerTransfer)
506
    {
507
        $customerEntity = $this->getCustomer($customerTransfer);
508
        $customerEntity->delete();
509
510
        $this->customerPluginExecutor->executeCustomerPostDeletePlugins($customerTransfer);
511
512
        return true;
513
    }
514
515
    /**
516
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
517
     *
518
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
519
     */
520
    public function update(CustomerTransfer $customerTransfer)
521
    {
522
        $customerResponseTransfer = $this->createCustomerResponseTransfer();
523
        if ($this->preValidateCustomerEmail($customerTransfer, $customerResponseTransfer)->getIsSuccess() === false) {
524
            return $customerResponseTransfer;
525
        }
526
        $customerTransfer = $this->executePreUpdatePlugins($customerTransfer, $customerResponseTransfer);
527
528
        if ($customerTransfer->getNewPassword()) {
529
            $customerResponseTransfer = $this->updatePassword(clone $customerTransfer);
530
531
            if ($customerResponseTransfer->getIsSuccess() === false) {
532
                return $customerResponseTransfer;
533
            }
534
535
            $updatedPasswordCustomerTransfer = $customerResponseTransfer->getCustomerTransfer();
536
            $customerTransfer->setNewPassword($updatedPasswordCustomerTransfer->getNewPassword())
537
                ->setPassword($updatedPasswordCustomerTransfer->getPassword());
538
        }
539
540
        $customerResponseTransfer->setCustomerTransfer($customerTransfer);
541
542
        $customerEntity = $this->getCustomer($customerTransfer);
543
        $customerEntity->fromArray($customerTransfer->modifiedToArray());
544
545
        if ($customerTransfer->getLocale() !== null) {
546
            $this->addLocaleByLocaleName($customerEntity, $customerTransfer->getLocale()->getLocaleName());
547
        }
548
549
        $customerResponseTransfer = $this->validateCustomerEmail($customerResponseTransfer, $customerEntity);
550
        if (!$customerResponseTransfer->getIsSuccess()) {
551
            return $customerResponseTransfer;
552
        }
553
554
        if ($customerTransfer->getSendPasswordToken()) {
555
            $this->sendPasswordRestoreMail($customerTransfer);
556
        }
557
558
        if (!$customerEntity->isModified()) {
559
            return $customerResponseTransfer;
560
        }
561
562
        $customerEntity->save();
563
564
        return $customerResponseTransfer;
565
    }
566
567
    /**
568
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
569
     * @param \Generated\Shared\Transfer\CustomerResponseTransfer $customerResponseTransfer
570
     *
571
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
572
     */
573
    protected function preValidateCustomerEmail(
574
        CustomerTransfer $customerTransfer,
575
        CustomerResponseTransfer $customerResponseTransfer
576
    ): CustomerResponseTransfer {
577
        if (!$this->emailValidator->isEmailAvailableForCustomer($customerTransfer->getEmailOrFail(), $customerTransfer->getIdCustomerOrFail())) {
578
            return $this->addErrorToCustomerResponseTransfer(
579
                $customerResponseTransfer,
580
                Messages::CUSTOMER_EMAIL_ALREADY_USED,
581
            );
582
        }
583
584
        return $customerResponseTransfer;
585
    }
586
587
    /**
588
     * @param bool $isSuccess
589
     *
590
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
591
     */
592
    protected function createCustomerResponseTransfer($isSuccess = true)
593
    {
594
        $customerResponseTransfer = new CustomerResponseTransfer();
595
        $customerResponseTransfer->setIsSuccess($isSuccess);
596
597
        return $customerResponseTransfer;
598
    }
599
600
    /**
601
     * @param \Generated\Shared\Transfer\CustomerResponseTransfer $customerResponseTransfer
602
     * @param \Orm\Zed\Customer\Persistence\SpyCustomer $customerEntity
603
     *
604
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
605
     */
606
    protected function validateCustomerEmail(CustomerResponseTransfer $customerResponseTransfer, SpyCustomer $customerEntity)
607
    {
608
        if (!$this->emailValidator->isFormatValid($customerEntity->getEmail())) {
609
            $customerResponseTransfer = $this->addErrorToCustomerResponseTransfer(
610
                $customerResponseTransfer,
611
                Messages::CUSTOMER_EMAIL_FORMAT_INVALID,
612
            );
613
        }
614
615
        if (!$this->emailValidator->isEmailAvailableForCustomer($customerEntity->getEmail(), $customerEntity->getIdCustomer())) {
616
            $customerResponseTransfer = $this->addErrorToCustomerResponseTransfer(
617
                $customerResponseTransfer,
618
                Messages::CUSTOMER_EMAIL_ALREADY_USED,
619
            );
620
        }
621
622
        if (!$this->emailValidator->isEmailLengthValid($customerEntity->getEmail())) {
623
            $customerResponseTransfer = $this->addErrorToCustomerResponseTransfer(
624
                $customerResponseTransfer,
625
                Messages::CUSTOMER_EMAIL_TOO_LONG,
626
            );
627
        }
628
629
        return $customerResponseTransfer;
630
    }
631
632
    /**
633
     * @param string $message
634
     *
635
     * @return \Generated\Shared\Transfer\CustomerErrorTransfer
636
     */
637
    protected function createErrorCustomerResponseTransfer($message)
638
    {
639
        $customerErrorTransfer = new CustomerErrorTransfer();
640
        $customerErrorTransfer->setMessage($message);
641
642
        return $customerErrorTransfer;
643
    }
644
645
    /**
646
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
647
     *
648
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
649
     */
650
    public function updatePassword(CustomerTransfer $customerTransfer)
651
    {
652
        $customerEntity = $this->getCustomer($customerTransfer);
653
654
        $customerResponseTransfer = $this->getCustomerPasswordInvalidResponse($customerEntity, $customerTransfer);
655
        if (!$customerResponseTransfer->getIsSuccess()) {
656
            return $customerResponseTransfer;
657
        }
658
659
        $customerResponseTransfer = $this->customerPasswordPolicyValidator->validatePassword($customerTransfer->getNewPassword());
660
        if (!$customerResponseTransfer->getIsSuccess()) {
661
            return $customerResponseTransfer;
662
        }
663
664
        $customerTransfer = $this->encryptNewPassword($customerTransfer);
665
666
        $customerEntity->setPassword($customerTransfer->getNewPassword());
667
668
        $changedRows = $customerEntity->save();
669
670
        $customerTransfer->fromArray($customerEntity->toArray(), true);
671
672
        $customerResponseTransfer
673
            ->setIsSuccess($changedRows > 0)
674
            ->setCustomerTransfer($customerTransfer);
675
676
        return $customerResponseTransfer;
677
    }
678
679
    /**
680
     * @param \Orm\Zed\Customer\Persistence\SpyCustomer $customerEntity
681
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
682
     *
683
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
684
     */
685
    protected function getCustomerPasswordInvalidResponse(SpyCustomer $customerEntity, CustomerTransfer $customerTransfer)
686
    {
687
        $customerResponseTransfer = new CustomerResponseTransfer();
688
        $customerResponseTransfer->setIsSuccess(true);
689
690
        if (!$this->isValidPassword($customerEntity->getPassword(), $customerTransfer->getPassword())) {
691
            $customerErrorTransfer = new CustomerErrorTransfer();
692
            $customerErrorTransfer
693
                ->setMessage(Messages::CUSTOMER_PASSWORD_INVALID);
694
            $customerResponseTransfer
695
                ->setIsSuccess(false)
696
                ->addError($customerErrorTransfer);
697
        }
698
699
        return $customerResponseTransfer;
700
    }
701
702
    /**
703
     * @param \Orm\Zed\Customer\Persistence\SpyCustomerAddress $addressEntity
704
     *
705
     * @return \Generated\Shared\Transfer\AddressTransfer
706
     */
707
    protected function entityToTransfer(SpyCustomerAddress $addressEntity)
708
    {
709
        $addressTransfer = new AddressTransfer();
710
        $addressTransfer->fromArray($addressEntity->toArray(), true);
711
        $addressTransfer->setIso2Code($addressEntity->getCountry()->getIso2Code());
712
713
        return $addressTransfer;
714
    }
715
716
    /**
717
     * @param \Propel\Runtime\Collection\ObjectCollection $addressEntities
718
     * @param \Orm\Zed\Customer\Persistence\SpyCustomer $customer
719
     *
720
     * @return \Generated\Shared\Transfer\AddressesTransfer
721
     */
722
    protected function entityCollectionToTransferCollection(ObjectCollection $addressEntities, SpyCustomer $customer)
723
    {
724
        $addressCollection = new AddressesTransfer();
725
726
        foreach ($addressEntities as $address) {
727
            $addressTransfer = $this->entityToTransfer($address);
728
729
            if ($customer->getDefaultBillingAddress() === $address->getIdCustomerAddress()) {
730
                $addressTransfer->setIsDefaultBilling(true);
731
            }
732
733
            if ($customer->getDefaultShippingAddress() === $address->getIdCustomerAddress()) {
734
                $addressTransfer->setIsDefaultShipping(true);
735
            }
736
737
            $addressCollection->addAddress($addressTransfer);
738
        }
739
740
        return $addressCollection;
741
    }
742
743
    /**
744
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
745
     * @param \Generated\Shared\Transfer\AddressesTransfer $addressesTransfer
746
     *
747
     * @return \Generated\Shared\Transfer\CustomerTransfer
748
     */
749
    protected function attachAddressesTransfer(CustomerTransfer $customerTransfer, AddressesTransfer $addressesTransfer)
750
    {
751
        foreach ($addressesTransfer->getAddresses() as $addressTransfer) {
752
            if ($addressTransfer->getIsDefaultBilling()) {
753
                $customerTransfer->addBillingAddress($addressTransfer);
754
            }
755
756
            if ($addressTransfer->getIsDefaultShipping()) {
757
                $customerTransfer->addShippingAddress($addressTransfer);
758
            }
759
        }
760
761
        return $customerTransfer;
762
    }
763
764
    /**
765
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
766
     *
767
     * @throws \Spryker\Zed\Customer\Business\Exception\CustomerNotFoundException
768
     *
769
     * @return \Orm\Zed\Customer\Persistence\SpyCustomer
770
     */
771
    protected function getCustomer(CustomerTransfer $customerTransfer)
772
    {
773
        $customerEntity = null;
774
775
        if ($customerTransfer->getIdCustomer()) {
776
            $customerEntity = $this->queryContainer->queryCustomerById($customerTransfer->getIdCustomer())
777
                ->findOne();
778
        } elseif ($customerTransfer->getEmail()) {
779
            $customerEntity = $this->queryContainer->queryCustomerByEmail($customerTransfer->getEmail())
780
                ->findOne();
781
        } elseif ($customerTransfer->getRestorePasswordKey()) {
782
            $customerEntity = $this->queryContainer->queryCustomerByRestorePasswordKey($customerTransfer->getRestorePasswordKey())
783
                ->findOne();
784
        }
785
786
        if ($customerEntity !== null) {
787
            return $customerEntity;
788
        }
789
790
        throw new CustomerNotFoundException(sprintf(
791
            'Customer not found by either ID `%s`, email `%s` or restore password key `%s`.',
792
            $customerTransfer->getIdCustomer(),
793
            $customerTransfer->getEmail(),
794
            $customerTransfer->getRestorePasswordKey(),
795
        ));
796
    }
797
798
    /**
799
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
800
     *
801
     * @return bool
802
     */
803
    protected function hasCustomer(CustomerTransfer $customerTransfer)
804
    {
805
        $result = false;
806
        $customerEntity = null;
807
808
        if ($customerTransfer->getIdCustomer()) {
809
            $customerEntity = $this->queryContainer
810
                ->queryCustomerById($customerTransfer->getIdCustomer())
811
                ->findOne();
812
        } elseif ($customerTransfer->getEmail()) {
813
            $customerEntity = $this->queryContainer
814
                ->queryCustomerByEmail($customerTransfer->getEmail())
815
                ->findOne();
816
        }
817
818
        if ($customerEntity !== null) {
819
            $result = true;
820
        }
821
822
        return $result;
823
    }
824
825
    /**
826
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
827
     *
828
     * @return bool
829
     */
830
    public function tryAuthorizeCustomerByEmailAndPassword(CustomerTransfer $customerTransfer)
831
    {
832
        $customerEntity = $this->queryContainer->queryCustomerByEmail($customerTransfer->getEmail())
833
            ->findOne();
834
835
        if (!$customerEntity) {
836
            return false;
837
        }
838
839
        if (!$this->isValidPassword($customerEntity->getPassword(), $customerTransfer->getPassword())) {
840
            return false;
841
        }
842
843
        if ($this->customerConfig->isDoubleOptInEnabled() && !$customerEntity->getRegistered()) {
844
            return false;
845
        }
846
847
        return true;
848
    }
849
850
    /**
851
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
852
     *
853
     * @return \Generated\Shared\Transfer\CustomerTransfer
854
     */
855
    protected function encryptPassword(CustomerTransfer $customerTransfer)
856
    {
857
        $currentPassword = $customerTransfer->getPassword();
858
        $customerTransfer->setPassword($this->getEncodedPassword($currentPassword));
859
860
        return $customerTransfer;
861
    }
862
863
    /**
864
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
865
     *
866
     * @return \Generated\Shared\Transfer\CustomerTransfer
867
     */
868
    protected function encryptNewPassword(CustomerTransfer $customerTransfer)
869
    {
870
        $currentPassword = $customerTransfer->getNewPassword();
871
        $customerTransfer->setNewPassword($this->getEncodedPassword($currentPassword));
872
873
        return $customerTransfer;
874
    }
875
876
    /**
877
     * @param string|null $currentPassword
878
     *
879
     * @return string|null
880
     */
881
    protected function getEncodedPassword($currentPassword)
882
    {
883
        if ($currentPassword === null) {
884
            return $currentPassword;
885
        }
886
887
        if ($this->isSymfonyVersion5() === true) {
888
            return $this->getPasswordEncoder()->encodePassword($currentPassword, static::BCRYPT_SALT);
889
        }
890
891
        return $this->createPasswordHasher()->hash($currentPassword);
892
    }
893
894
    /**
895
     * @return \Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface
896
     */
897
    protected function getPasswordEncoder(): PasswordEncoderInterface
898
    {
899
        return new NativePasswordEncoder(null, null, static::BCRYPT_FACTOR);
900
    }
901
902
    /**
903
     * @return \Symfony\Component\PasswordHasher\PasswordHasherInterface
904
     */
905
    public function createPasswordHasher(): PasswordHasherInterface
906
    {
907
        return new NativePasswordHasher(null, null, static::BCRYPT_FACTOR);
908
    }
909
910
    /**
911
     * @param string $hash
912
     * @param string $rawPassword
913
     *
914
     * @return bool
915
     */
916
    protected function isValidPassword($hash, $rawPassword)
917
    {
918
        if ($this->isSymfonyVersion5() === true) {
919
            return $this->getPasswordEncoder()->isPasswordValid($hash, $rawPassword, static::BCRYPT_SALT);
920
        }
921
922
        return $this->createPasswordHasher()->verify($hash, $rawPassword);
923
    }
924
925
    /**
926
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer $customerTransfer
927
     *
928
     * @return \Generated\Shared\Transfer\CustomerTransfer|null
929
     */
930
    public function findById($customerTransfer)
931
    {
932
        $customerTransfer->requireIdCustomer();
933
934
        $customerEntity = $this->queryContainer->queryCustomerById($customerTransfer->getIdCustomer())
935
            ->findOne();
936
        if ($customerEntity === null) {
937
            return null;
938
        }
939
940
        $customerTransfer = $this->hydrateCustomerTransferFromEntity($customerTransfer, $customerEntity);
941
942
        return $customerTransfer;
943
    }
944
945
    /**
946
     * @param string $customerReference
947
     *
948
     * @return \Generated\Shared\Transfer\CustomerTransfer|null
949
     */
950
    public function findByReference($customerReference)
951
    {
952
        $customerEntity = $this->queryContainer
953
            ->queryCustomerByReference($customerReference)
954
            ->findOne();
955
956
        if ($customerEntity === null) {
957
            return null;
958
        }
959
960
        $customerTransfer = new CustomerTransfer();
961
        $customerTransfer = $this->hydrateCustomerTransferFromEntity($customerTransfer, $customerEntity);
962
963
        return $customerTransfer;
964
    }
965
966
    /**
967
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
968
     * @param \Orm\Zed\Customer\Persistence\SpyCustomer $customerEntity
969
     *
970
     * @return \Generated\Shared\Transfer\CustomerTransfer
971
     */
972
    protected function hydrateCustomerTransferFromEntity(
973
        CustomerTransfer $customerTransfer,
974
        SpyCustomer $customerEntity
975
    ) {
976
        $customerTransfer->fromArray($customerEntity->toArray(), true);
977
        $customerTransfer = $this->attachAddresses($customerTransfer, $customerEntity);
978
        $customerTransfer = $this->attachLocale($customerTransfer, $customerEntity);
979
980
        return $customerTransfer;
981
    }
982
983
    /**
984
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
985
     * @param \Orm\Zed\Customer\Persistence\SpyCustomer $customerEntity
986
     *
987
     * @return \Generated\Shared\Transfer\CustomerTransfer
988
     */
989
    protected function attachLocale(CustomerTransfer $customerTransfer, SpyCustomer $customerEntity)
990
    {
991
        $localeEntity = $customerEntity->getLocale();
992
        if (!$localeEntity) {
993
            return $customerTransfer;
994
        }
995
996
        $localeTransfer = new LocaleTransfer();
997
        $localeTransfer->fromArray($localeEntity->toArray(), true);
998
        $customerTransfer->setLocale($localeTransfer);
999
1000
        return $customerTransfer;
1001
    }
1002
1003
    /**
1004
     * @param \Generated\Shared\Transfer\CustomerResponseTransfer $customerResponseTransfer
1005
     * @param string $message
1006
     *
1007
     * @return \Generated\Shared\Transfer\CustomerResponseTransfer
1008
     */
1009
    protected function addErrorToCustomerResponseTransfer(CustomerResponseTransfer $customerResponseTransfer, string $message): CustomerResponseTransfer
1010
    {
1011
        $customerResponseTransfer->setIsSuccess(false);
1012
        $customerResponseTransfer->addError(
1013
            $this->createErrorCustomerResponseTransfer($message),
1014
        );
1015
1016
        return $customerResponseTransfer;
1017
    }
1018
1019
    /**
1020
     * @param \Generated\Shared\Transfer\CustomerCollectionTransfer $customerCollectionTransfer
1021
     * @param \Symfony\Component\Console\Output\OutputInterface|null $output
1022
     *
1023
     * @return void
1024
     */
1025
    public function sendPasswordRestoreMailForCustomerCollection(
1026
        CustomerCollectionTransfer $customerCollectionTransfer,
1027
        ?OutputInterface $output = null
1028
    ): void {
1029
        $customersCount = $customerCollectionTransfer->getCustomers()->count();
1030
        foreach ($customerCollectionTransfer->getCustomers() as $index => $customer) {
1031
            $this->sendPasswordRestoreMail($customer);
1032
1033
            if (!$output) {
1034
                continue;
1035
            }
1036
1037
            $output->write(sprintf(
1038
                "%d out of %d emails sent \r%s",
1039
                ++$index,
1040
                $customersCount,
1041
                $index === $customersCount ? PHP_EOL : '',
1042
            ));
1043
        }
1044
    }
1045
1046
    /**
1047
     * @deprecated Shim for Symfony Security Core 5.x, to be removed when Symfony Security Core dependency becomes 6.x+.
1048
     *
1049
     * @return bool
1050
     */
1051
    protected function isSymfonyVersion5(): bool
1052
    {
1053
        return class_exists(AuthenticationProviderManager::class);
1054
    }
1055
1056
    /**
1057
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
1058
     * @param \Generated\Shared\Transfer\CustomerResponseTransfer $customerResponseTransfer
1059
     *
1060
     * @return \Generated\Shared\Transfer\CustomerTransfer
1061
     */
1062
    protected function executePreUpdatePlugins(CustomerTransfer $customerTransfer, CustomerResponseTransfer $customerResponseTransfer): CustomerTransfer
1063
    {
1064
        if ($this->shouldSkipPreUpdatePlugins($customerTransfer)) {
1065
            return $customerTransfer;
1066
        }
1067
1068
        foreach ($this->customerPreUpdatePlugins as $customerPreUpdatePlugin) {
1069
            $customerTransfer = $customerPreUpdatePlugin->preUpdate($customerTransfer);
1070
1071
            if ($customerTransfer->getMessage() === null) {
1072
                continue;
1073
            }
1074
1075
            $customerResponseTransfer->addMessage(
1076
                (new MessageTransfer())->setValue($customerTransfer->getMessage()),
1077
            );
1078
        }
1079
1080
        return $customerTransfer;
1081
    }
1082
1083
    /**
1084
     * @param \Generated\Shared\Transfer\CustomerTransfer $customerTransfer
1085
     *
1086
     * @return bool
1087
     */
1088
    protected function shouldSkipPreUpdatePlugins(CustomerTransfer $customerTransfer): bool
1089
    {
1090
        return $customerTransfer->getAnonymizedAt() !== null || $customerTransfer->getIsEditedInBackoffice() === true;
1091
    }
1092
}
1093