Customer::validateCustomerEmail()   A
last analyzed

Complexity

Conditions 4
Paths 8

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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