<?php
namespace ShopWithMe\MobileApi\Controller;
use Shopware\Core\Checkout\Cart\CartException;
use Shopware\Core\Checkout\Customer\CustomerDefinition;
use Shopware\Core\Checkout\Customer\CustomerEntity;
use Shopware\Core\Checkout\Customer\Event\CustomerBeforeLoginEvent;
use Shopware\Core\Checkout\Customer\Event\CustomerLoginEvent;
use Shopware\Core\Checkout\Customer\Password\LegacyPasswordVerifier;
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractRegisterRoute;
use Shopware\Core\Checkout\Customer\SalesChannel\CustomerResponse;
use Shopware\Core\Checkout\Customer\Validation\Constraint\CustomerEmailUnique;
use Shopware\Core\Checkout\Customer\Validation\CustomerValidationFactory;
use Shopware\Core\Framework\Api\Controller\ApiController;
use Shopware\Core\Framework\Api\Response\ResponseFactoryInterface;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Validation\EntityExists;
use Shopware\Core\Framework\RateLimiter\Exception\RateLimitExceededException;
use Shopware\Core\Framework\RateLimiter\RateLimiter;
use Shopware\Core\Framework\Routing\RequestContextResolverInterface;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Core\Framework\Validation\DataValidationDefinition;
use Shopware\Core\Framework\Validation\DataValidator;
use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
use Shopware\Core\PlatformRequest;
use Shopware\Core\System\NumberRange\ValueGenerator\NumberRangeValueGeneratorInterface;
use Shopware\Core\System\SalesChannel\Context\CartRestorer;
use Shopware\Core\System\SalesChannel\ContextTokenResponse;
use Shopware\Core\System\SalesChannel\Event\SalesChannelContextSwitchEvent;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SalesChannel\StoreApiResponse;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Controller\StorefrontController;
use ShopWithMe\MobileApi\Exception\CheckDeleteException;
use ShopWithMe\MobileApi\Exception\CustomApiException;
use ShopWithMe\MobileApi\Service\AccountService;
use ShopWithMe\MobileApi\Service\AIStreamService;
use ShopWithMe\Reward\Service\LuckyWheelService\LuckyWheelApiService;
use ShopWithMe\Reward\Service\RewardPointService;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService;
use Shopware\Core\Framework\Routing\Annotation\Since;
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextPersister;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Shopware\Core\Checkout\Customer\Exception\BadCredentialsException;
use Shopware\Core\Checkout\Customer\Exception\CustomerAuthThrottledException;
use Shopware\Core\Checkout\Customer\Exception\CustomerNotFoundException;
use Shopware\Core\Checkout\Customer\Exception\InactiveCustomerException;
use ShopWithMe\UI\ShopWithMeUI;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\ConstraintViolationList;
#[Route(defaults: ['_routeScope' => ['store-api']])]
class AuthController extends StorefrontController
{
private const SHIPPING_METHOD_ID = SalesChannelContextService::SHIPPING_METHOD_ID;
private const PAYMENT_METHOD_ID = SalesChannelContextService::PAYMENT_METHOD_ID;
private const BILLING_ADDRESS_ID = SalesChannelContextService::BILLING_ADDRESS_ID;
private const SHIPPING_ADDRESS_ID = SalesChannelContextService::SHIPPING_ADDRESS_ID;
private const COUNTRY_ID = SalesChannelContextService::COUNTRY_ID;
private const STATE_ID = SalesChannelContextService::COUNTRY_STATE_ID;
private const CURRENCY_ID = SalesChannelContextService::CURRENCY_ID;
private const LANGUAGE_ID = SalesChannelContextService::LANGUAGE_ID;
protected EntityRepository $tagRepository;
protected EntityRepository $customerRepository;
protected LuckyWheelApiService $luckyWheelApiService;
protected TranslatorInterface $translator;
private ValidatorInterface $validatorSWM;
public function __construct(
EntityRepository $tagRepository,
EntityRepository $repository,
protected ApiController $apiController,
protected ResponseFactoryInterface $responseFactory,
protected NumberRangeValueGeneratorInterface $numberRangeValueGenerator,
protected EntityRepository $customerEntity,
protected EntityRepository $languageRepository,
protected CustomerValidationFactory $customerValidationFactory,
protected DataValidator $dataValidator,
protected EntityRepository $countryRepository,
protected RequestContextResolverInterface $contextResolver,
protected SystemConfigService $systemConfigService,
protected AbstractRegisterRoute $registerRoute,
protected EntityRepository $salutationRepository,
protected AIStreamService $aiStreamService,
protected DataValidator $validator,
protected SalesChannelContextPersister $contextPersister,
protected EventDispatcherInterface $eventDispatcher,
protected LegacyPasswordVerifier $legacyPasswordVerifier,
protected CartRestorer $restorer,
protected RequestStack $requestStack,
protected RateLimiter $rateLimiter,
protected ?string $lifetimeInterval = 'P1D',
LuckyWheelApiService $luckyWheelApiService,
TranslatorInterface $translator,
ValidatorInterface $validatorSWM,
protected EntityRepository $cmsPageRepository,
protected AccountService $accountService,
protected RewardPointService $rewardPointService,
)
{
$this->tagRepository = $tagRepository;
$this->customerRepository = $repository;
$this->luckyWheelApiService = $luckyWheelApiService;
$this->translator = $translator;
$this->validatorSWM = $validatorSWM;
}
#[Route('/store-api/mobile/common', name: 'mobile.register.common', defaults: ["XmlHttpRequest" => true], methods: ['GET'])]
public function commonBeforeLogin(SalesChannelContext $context): JsonResponse
{
$request = $this->requestStack->getCurrentRequest();
$languageCode = $request->headers->get('language-code', $request->getLocale());
try {
$criteria = new Criteria();
$tags = $this->tagRepository->search($criteria, $context->getContext())->getEntities();
$languages = $this->languageList($context)->getEntities()->jsonSerialize();
$policy = $this->getPolicy($context);
return new JsonResponse(
[
'interest' => $this->aiStreamService->convertArray($tags),
'languages' => $languages,
'policy' => $policy['policy'],
'eula' => $policy['eula']
]
);
} catch (\Exception $exception) {
return (new CustomApiException($this->translator->trans('message.common.00005', [], null, $languageCode)))->jsonResponse();
}
}
#[Route('/store-api/mobile/register-interest', name: 'mobile.register.register-interest', defaults: ["XmlHttpRequest" => true], methods: ['GET'])]
public function getTags(SalesChannelContext $context)
{
$criteria = new Criteria();
$tags = $this->tagRepository->search($criteria, $context->getContext());
return new JsonResponse(
['data' => $this->aiStreamService->convertArray($tags)]
);
}
public function getPolicy(SalesChannelContext $context)
{
$criteria = new Criteria(["60475197d7974ca4a731d844c990f93f", '5a6121d4321e4104a0f08ab0d296739d']);
$criteria->addAssociation('sections.blocks.slots');
$data = $this->cmsPageRepository->search($criteria, $context->getContext())->getElements();
$result = array_map(function ($value) {
return $value->getId() === '60475197d7974ca4a731d844c990f93f' ? 'policy' : 'eula';
}, $data);
$result = array_combine($result, $data);
foreach ($result as $key => &$value) {
$value = $value->getSections()?->getBlocks()?->getSlots();
}
return $result;
}
#[Route('/store-api/mobile/country-code', name: 'mobile.register.country-code', defaults: ["XmlHttpRequest" => true], methods: ['GET'])]
public function getCountryCode(Request $request, SalesChannelContext $context): JsonResponse
{
$languageCode = $request->headers->get('language-code', $request->getLocale());
$filePath = dirname((new \ReflectionClass(ShopWithMeUI::class))->getFileName()) . '/Resources/config/countryCode.json';
if (!is_file($filePath)) {
return (new CustomApiException($this->translator->trans('message.auth.00103', [], null, $languageCode) . $filePath))->jsonResponse();
}
$dataCountryCode = json_decode(file_get_contents($filePath), true) ?? [];
return new JsonResponse(['data' => $dataCountryCode]);
}
#[Route('/store-api/mobile/language', name: 'mobile.register.language', defaults: ["XmlHttpRequest" => true], methods: ['GET'])]
public function getLanguageList(SalesChannelContext $context)
{
$languages = $this->languageList($context);
return new JsonResponse(
['data' => $languages, 'message' => 'success']
);
}
public function languageList(SalesChannelContext $context)
{
return $this->languageRepository->search((new Criteria())
->addFilter(new EqualsFilter('salesChannelDomains.salesChannelId', $context->getSalesChannelId()))
->addFilter(new NotFilter(MultiFilter::CONNECTION_OR, [
new ContainsFilter('translationCode.code', 'th-TH'),
new ContainsFilter('translationCode.code', 'en-HK'),
new ContainsFilter('translationCode.code', 'es-ES'),
]))
->addAssociations(['locale', 'translationCode']), $context->getContext());
}
#[Route('/store-api/mobile/register', name: 'mobile.register.register', defaults: ["XmlHttpRequest" => true], methods: ['POST'])]
public function registerApi(RequestDataBag $dataBag, Request $request, SalesChannelContext $context)
{
$this->fillRequiredData($request, $context);
$result = $this->validateCustomer($request, $context);
$languageCode = $request->headers->get('language-code', $request->getLocale());
if (!empty($result) && $result->getCustomer() instanceof CustomerEntity) {
$this->contextResolver->resolve($request);
$context = $request->attributes->get('sw-sales-channel-context');
$customer = $result->getCustomer();
if (array_key_exists('tags', $request->request->all())) {
$tags = json_decode($request->request->all()['tags']);
$this->aiStreamService->setInterests($context, $customer, $tags);
}
$this->setCustomFields($context, $customer, [
'customerCountryCode' => $request->request->get('countryCode'),
'customerLanguageCode' => $request->request->get('languageCode'),
'customerCountryId' => $request->request->get('countryId'),
'customerLanguageId' => $request->request->get('languageId'),
'luckyWheelFirstTurn' => false
]);
$customerSerialized = (new CustomerResponse($customer))->getCustomer()->jsonSerialize();
if (!empty($customerSerialized['password'])) {
unset($customerSerialized['password']);
}
return new JsonResponse([
'data' => [
'customer' => $customerSerialized,
'contextToken' => $result->headers->get(PlatformRequest::HEADER_CONTEXT_TOKEN),
],
'message' => 'Success',
]);
}
return (new CustomApiException($this->translator->trans('message.common.00004', [], null, $languageCode)))->jsonResponse();
}
#[Route('/store-api/mobile/update-affiliateCode', name: 'mobile.register.affiliateCode', defaults: ['_loginRequired' => true], methods: ['POST'])]
public function updateCampaignCode(Request $request, SalesChannelContext $context)
{
$languageCode = $request->headers->get('language-code', $request->getLocale());
try {
$referral = $this->validateAffiliateCode($request, $context);
$checkCampaignCode = $context->getCustomer()->getCampaignCode();
if (empty($referral)) {
return (new CustomApiException($this->translator->trans('message.auth.00106', [], null, $languageCode)))->jsonResponse();
}
if (empty($checkCampaignCode)) {
$affiliateCode = $request->request->get('affiliateCode');
$this->customerRepository->update([['id' => $context->getCustomerId(), 'campaignCode' => $affiliateCode]], $context->getContext());
$this->luckyWheelApiService->addTurnAfterAffiliateCode($referral, $context->getCustomer(), $context->getContext());
return new JsonResponse(['message' => $this->translator->trans('message.common.00002', [], null, $languageCode)], 200);
}
return (new CustomApiException($this->translator->trans('message.auth.00107', [], null, $languageCode)))->jsonResponse();
} catch (ConstraintViolationException $exception) {
return (new CustomApiException($this->translator->trans('message.common.00005', [], null, $languageCode)))->jsonResponse();
}
}
#[Route('/store-api/mobile/create-interest', name: 'mobile.register.create-interest', defaults: ["XmlHttpRequest" => true], methods: ['POST'])]
public function createInterest(Request $request, SalesChannelContext $context): JsonResponse
{
$languageCode = $request->headers->get('language-code', $request->getLocale());
$customer = $context->getCustomer();
if (array_key_exists('tags', $request->request->all())) {
$tags = json_decode($request->request->all()['tags']);
$this->aiStreamService->setInterests($context, $customer, $tags);
return new JsonResponse(['message' => $this->translator->trans('message.auth.00104', [], null, $languageCode)]);
}
return (new CustomApiException($this->translator->trans('message.auth.00105', [], null, $languageCode)))->jsonResponse();
}
private function fillRequiredData(Request $request, SalesChannelContext $context): void
{
$currentSalesChannelLanguageId = $context->getContext()->getLanguageId();
$currentCountryId = $context->getSalesChannel()->getCountryId();
$currentSalesChannelDomain = $context->getSalesChannel()?->getDomains()?->filterByProperty('languageId', $currentSalesChannelLanguageId)?->first();
$this->handleFillData($request, $currentCountryId, $currentSalesChannelDomain, $context);
$billingAddress = (array)$request->request->get('billingAddress');
if (empty($billingAddress)) {
$billingAddress = [
'countryId' => $currentCountryId,
'salutationId' => $request->request->get('salutationId'),
'firstName' => $request->request->get('firstName') ?? '',
'lastName' => $request->request->get('lastName') ?? '',
'zipcode' => 'noinfo',
'city' => 'noinfo',
'street' => 'noinfo',
'phoneNumber' => $request->request->get('phoneNumber'),
];
} else {
$billingAddress['phoneNumber'] = $request->request->get('phoneNumber');
}
$request->request->set('billingAddress', $billingAddress);
}
private function validateCustomer(Request $request, SalesChannelContext $context): ?CustomerResponse
{
$languageCode = $request->headers->get('language-code', $request->getLocale());
$definition = $this->customerValidationFactory->create($context);
$minLength = $this->systemConfigService->get('core.loginRegistration.passwordMinLength', $context->getSalesChannel()->getId());
$definition->add('password', new NotBlank(), new Length(['min' => $minLength]));
$options = ['context' => $context->getContext(), 'salesChannelContext' => $context];
$definition->add('email', new CustomerEmailUnique($options));
// $definition->add("phoneNumber", new NotBlank());
$definition->add('salutationId', new NotBlank());
$definition->add('userName', new NotBlank());
// $definition->add('birthday', new NotBlank());
$definition->add('tags', new NotBlank());
$constraints = $this->getAffiliateCodeConstraints($context->getContext());
if (!empty($request->get('affiliateCode'))) {
foreach ($constraints as $constraint) {
$definition->add('affiliateCode', $constraint);
}
}
$validation = $this->dataValidator->getViolations($request->request->all(), $definition);
//check for validation errors.
if ($validation->count() > 0) {
$translatedViolations = new ConstraintViolationList();
foreach ($validation as $violation) {
$message = $violation->getMessageTemplate();
$propertyPath = $violation->getPropertyPath();
switch ($propertyPath) {
case '/firstName':
{
$translatedMessage = $this->translator->trans("message.auth.00110.$message", [], null, $languageCode);
break;
}
case '/lastName':
{
$translatedMessage = $this->translator->trans("message.auth.00110.$message", [], null, $languageCode);
break;
}
case '/password':
{
$translatedMessage = $this->translator->trans("message.auth.00110.$message", [], null, $languageCode);
break;
}
case '/email':
{
if (isset($violation->getParameters()['{{ email }}'])) {
$email = $violation->getParameters()['{{ email }}'];
$translatedMessage = $this->translator->trans("message.auth.00110.$message", ['{{ email }}' => $email], null, $languageCode);
} else {
$translatedMessage = $this->translator->trans("message.auth.00110.$message", [], null, $languageCode);
}
break;
}
case '/userName':
{
$translatedMessage = $this->translator->trans("message.auth.00110.$message", [], null, $languageCode);
break;
}
default:
{
$translatedMessage = $this->translator->trans("message.auth.00111", [], null, $languageCode);
}
}
$translatedViolations->add(new ConstraintViolation(
$translatedMessage,
$violation->getMessageTemplate(),
$violation->getParameters(),
$violation->getRoot(),
$violation->getPropertyPath(),
$violation->getInvalidValue()
));
}
throw new ConstraintViolationException($translatedViolations, $request->request->all());
}
$requestDataBag = new RequestDataBag($request->request->all());
return $this->registerRoute->register($requestDataBag, $context);
}
public function validateAffiliateCode(Request $request, SalesChannelContext $context)
{
$affiliateCriteria = new Criteria();
$affiliateCriteria->addFilter(new EqualsFilter('affiliateCode', $request->request->get('affiliateCode')));
return $this->customerRepository->search($affiliateCriteria, $context->getContext())->first();
}
protected function getAffiliateCodeConstraints(Context $context): array
{
$notBlankConstraint = new NotBlank();
$customerExistsConstraint = new EntityExists([
'entity' => CustomerDefinition::ENTITY_NAME,
'context' => $context,
]);
$customerExistsConstraint->primaryProperty = 'affiliateCode';
return [
$notBlankConstraint,
$customerExistsConstraint
];
}
private function setCustomFields(SalesChannelContext $context, CustomerEntity $customer, array $customFields): void
{
$customerCustomFields = $customer->getCustomFields() ?? [];
foreach ($customFields as $key => $value) {
$customerCustomFields[$key] = $value;
}
$this->customerEntity->update(
[
[
'id' => $customer->getId(),
'customFields' => $customerCustomFields,
]
],
$context->getContext()
);
}
/**
* @Since("6.2.0.0")
* @Route("/store-api/mobile/context", name="store-api.mobile.switch-context", methods={"PATCH"})
*/
public function switchContext(RequestDataBag $data, SalesChannelContext $context): ContextTokenResponse
{
$definition = new DataValidationDefinition('context_switch');
$parameters = $data->only(
self::SHIPPING_METHOD_ID,
self::PAYMENT_METHOD_ID,
self::BILLING_ADDRESS_ID,
self::SHIPPING_ADDRESS_ID,
self::COUNTRY_ID,
self::STATE_ID,
self::CURRENCY_ID,
self::LANGUAGE_ID
);
$addressCriteria = new Criteria();
if ($context->getCustomer()) {
$addressCriteria->addFilter(new EqualsFilter('customer_address.customerId', $context->getCustomer()->getId()));
} else {
// do not allow to set address ids if the customer is not logged in
if (isset($parameters[self::SHIPPING_ADDRESS_ID])) {
throw CartException::customerNotLoggedIn();
}
if (isset($parameters[self::BILLING_ADDRESS_ID])) {
throw CartException::customerNotLoggedIn();
}
}
$currencyCriteria = new Criteria();
$currencyCriteria->addFilter(
new EqualsFilter('currency.salesChannels.id', $context->getSalesChannel()->getId())
);
$languageCriteria = new Criteria();
$languageCriteria->addFilter(
new EqualsFilter('language.salesChannels.id', $context->getSalesChannel()->getId())
);
$paymentMethodCriteria = new Criteria();
$paymentMethodCriteria->addFilter(
new EqualsFilter('payment_method.salesChannels.id', $context->getSalesChannel()->getId())
);
$shippingMethodCriteria = new Criteria();
$shippingMethodCriteria->addFilter(
new EqualsFilter('shipping_method.salesChannels.id', $context->getSalesChannel()->getId())
);
$definition
->add(self::LANGUAGE_ID, new EntityExists(['entity' => 'language', 'context' => $context->getContext(), 'criteria' => $languageCriteria]))
->add(self::CURRENCY_ID, new EntityExists(['entity' => 'currency', 'context' => $context->getContext(), 'criteria' => $currencyCriteria]))
->add(self::SHIPPING_METHOD_ID, new EntityExists(['entity' => 'shipping_method', 'context' => $context->getContext(), 'criteria' => $shippingMethodCriteria]))
->add(self::PAYMENT_METHOD_ID, new EntityExists(['entity' => 'payment_method', 'context' => $context->getContext(), 'criteria' => $paymentMethodCriteria]))
->add(self::BILLING_ADDRESS_ID, new EntityExists(['entity' => 'customer_address', 'context' => $context->getContext(), 'criteria' => $addressCriteria]))
->add(self::SHIPPING_ADDRESS_ID, new EntityExists(['entity' => 'customer_address', 'context' => $context->getContext(), 'criteria' => $addressCriteria]))
->add(self::COUNTRY_ID, new EntityExists(['entity' => 'country', 'context' => $context->getContext()]))
->add(self::STATE_ID, new EntityExists(['entity' => 'country_state', 'context' => $context->getContext()]));
$this->validator->validate($parameters, $definition);
$customer = $context->getCustomer();
$this->contextPersister->save(
$context->getToken(),
$parameters,
$context->getSalesChannel()->getId(),
$customer && empty($context->getPermissions()) ? $customer->getId() : null
);
// Language was switched - Check new Domain
$changeUrl = $this->checkNewDomain($parameters, $context);
$event = new SalesChannelContextSwitchEvent($context, $data);
$this->eventDispatcher->dispatch($event);
// dd([$context->getPaymentMethod(), $parameters]);
return new ContextTokenResponse($context->getToken(), $changeUrl);
}
private function checkNewDomain(array $parameters, SalesChannelContext $context): ?string
{
// if (
// !isset($parameters[self::LANGUAGE_ID])
// || $parameters[self::LANGUAGE_ID] === $context->getLanguageId()
// ) {
// return null;
// }
$domains = $context->getSalesChannel()->getDomains();
if ($domains === null) {
return null;
}
$langDomain = $domains->filterByProperty('languageId', $parameters[self::LANGUAGE_ID])->first();
if ($langDomain === null) {
return null;
}
return $langDomain->getUrl();
}
/**
* @Since("6.2.0.0")
* @Route(path="/store-api/mobile/account/login", name="store-api.mobile.account.login", methods={"POST"})
*/
public function login(RequestDataBag $data, SalesChannelContext $context): JsonResponse
{
$request = $this->requestStack->getCurrentRequest();
$languageCode = $request->headers->get('language-code', $request->getLocale());
$now = new \DateTimeImmutable();
$email = $data->get('email', $data->get('username'));
if (empty($email)) {
return (new CustomApiException($this->translator->trans('message.auth.00101', [], null, $languageCode)))->jsonResponse();
}
if (empty($data->get('password'))) {
return (new CustomApiException($this->translator->trans('message.auth.00102', [], null, $languageCode)))->jsonResponse();
}
$event = new CustomerBeforeLoginEvent($context, $email);
$this->eventDispatcher->dispatch($event);
if ($this->requestStack->getMainRequest() !== null) {
$cacheKey = strtolower($email) . '-' . $this->requestStack->getMainRequest()->getClientIp();
try {
$this->rateLimiter->ensureAccepted(RateLimiter::LOGIN_ROUTE, $cacheKey);
} catch (RateLimitExceededException $exception) {
return (new CustomApiException($this->translator->trans('message.auth.00102', [], null, $languageCode)))->jsonResponse();
}
}
try {
$customer = $this->getCustomerByLogin(
$email,
$data->get('password'),
$context
);
} catch (CustomerNotFoundException|BadCredentialsException $exception) {
return (new CustomApiException($this->translator->trans('message.auth.00108', [], null, $languageCode)))->jsonResponse();
} catch (CheckDeleteException $exception) {
return (new CustomApiException($this->translator->trans('message.auth.00109', [], null, $languageCode)))->jsonResponse();
}
if (isset($cacheKey)) {
$this->rateLimiter->reset(RateLimiter::LOGIN_ROUTE, $cacheKey);
}
$context = $this->restorer->restore($customer->getId(), $context);
$newToken = $context->getToken();
$this->customerRepository->update([
[
'id' => $customer->getId(),
'lastLogin' => $now,
'languageId' => $context->getLanguageId(),
],
], $context->getContext());
$event = new CustomerLoginEvent($context, $customer, $newToken);
$this->eventDispatcher->dispatch($event);
$firstTurn = true;
if (isset($customer->getCustomFields()['luckyWheelFirstTurn'])) {
$firstTurn = $customer->getCustomFields()['luckyWheelFirstTurn'];
}
$unView = $this->accountService->getCountUnviewNotification($context, $customer->getId());
$credits = $this->rewardPointService->getCustomerTotalRewardPoint($customer->getId(), $context->getContext());
$turns = $this->luckyWheelApiService->handleUserTurn($context);
$hasBoughtProduct = $this->accountService->checkHasBoughtProduct($customer->getId(), $context);
$customer->common = [
'unView' => $unView,
'credits' => $credits,
'turns' => $turns == null ? 0 : $turns,
'hasBoughtProduct' => $hasBoughtProduct > 0
];
return new JsonResponse(new ArrayStruct(
[
'contextToken' => $newToken,
'redirectUrl' => null,
'expiresAt' => $now->add(new \DateInterval($this->lifetimeInterval)),
'active' => $customer->getActive(),
'luckyWheelFirstTurn' => $firstTurn,
'profile' => $customer
]
));
}
private function getCustomerByLogin(string $email, string $password, SalesChannelContext $context): CustomerEntity
{
$customer = $this->getCustomerByEmail($email, $context);
if ($customer->hasLegacyPassword()) {
if (!$this->legacyPasswordVerifier->verify($password, $customer)) {
throw new BadCredentialsException();
}
$this->updatePasswordHash($password, $customer, $context->getContext());
return $customer;
}
if (!password_verify($password, $customer->getPassword() ?? '')) {
throw new BadCredentialsException();
}
return $customer;
}
private function getCustomerByEmail(string $email, SalesChannelContext $context): CustomerEntity
{
$criteria = new Criteria();
$criteria->setTitle('login-route');
$criteria->addFilter(new EqualsFilter('customer.email', $email));
$result = $this->customerRepository->search($criteria, $context->getContext());
$result = $result->filter(static function (CustomerEntity $customer) use ($context) {
$isConfirmed = !$customer->getDoubleOptInRegistration() || $customer->getDoubleOptInConfirmDate();
// Skip guest and not active users
if (isset($customer->getCustomFields()['check_delete']) && $customer->getCustomFields()['check_delete'] && !$customer->getActive()) {
throw new CheckDeleteException('deactive');
} else {
if ($customer->getGuest() || (!$customer->getActive() && $isConfirmed)) {
return null;
}
}
// If not bound, we still need to consider it
if ($customer->getBoundSalesChannelId() === null) {
return true;
}
// It is bound, but not to the current one. Skip it
if ($customer->getBoundSalesChannelId() !== $context->getSalesChannel()->getId()) {
return null;
}
return true;
});
if ($result->count() !== 1) {
throw new BadCredentialsException();
}
return $result->first();
}
private function updatePasswordHash(string $password, CustomerEntity $customer, Context $context): void
{
$this->customerRepository->update([
[
'id' => $customer->getId(),
'password' => $password,
'legacyPassword' => null,
'legacyEncoder' => null,
],
], $context);
}
#[Route('/store-api/solomon/register', name: 'mobile.solomon.register', methods: ['POST'])]
public function solomonRegister(Request $request, SalesChannelContext $context): JsonResponse
{
$this->solomonFillData($request, $context);
$languageCode = $request->headers->get('language-code', $request->getLocale());
$result = $this->solomonRegisterValidate($request, $context);
if (!empty($result) && $result->getCustomer() instanceof CustomerEntity) {
$this->contextResolver->resolve($request);
$context = $request->attributes->get('sw-sales-channel-context');
$customer = $result->getCustomer();
$this->setCustomFields($context, $customer, [
'customerCountryCode' => $request->request->get('countryCode'),
'customerLanguageCode' => $request->request->get('languageCode'),
'customerCountryId' => $request->request->get('countryId'),
'customerLanguageId' => $request->request->get('languageId'),
'luckyWheelFirstTurn' => false
]);
$customerSerialized = (new CustomerResponse($customer))->getCustomer()->jsonSerialize();
if (!empty($customerSerialized['password'])) {
unset($customerSerialized['password']);
}
$dataLogin = new RequestDataBag();
$dataLogin->set('email', $customer->getEmail());
$dataLogin->set('password', $request->get('password'));
$data = $this->solomonLogin($dataLogin, $context);
return new JsonResponse(new ArrayStruct($data));
}
return (new CustomApiException($this->translator->trans('message.common.00004', [], null, $languageCode)))->jsonResponse();
// return new JsonResponse(new ArrayStruct());
}
public function solomonFillData(Request $request, SalesChannelContext $context)
{
$currentSalesChannelLanguageId = $context->getContext()->getLanguageId();
// $currentCountryId = $context->getSalesChannel()->getCountryId();
$currentCountryId = $request->request->get('countryId');;
$currentSalesChannelDomain = $context->getSalesChannel()?->getDomains()?->filterByProperty('languageId', $currentSalesChannelLanguageId)?->first();
$this->handleFillData($request, $currentCountryId, $currentSalesChannelDomain, $context);
$language = $this->languageRepository->search((new Criteria([$currentSalesChannelLanguageId]))->addAssociations(['translationCode', 'locale']), $context->getContext())->first();
$request->request->set('languageId', $currentSalesChannelLanguageId);
$languageCode = strtolower(explode('-', $language?->getTranslationCode()?->getCode())[0]);
$request->request->set('languageCode', $languageCode);
if (!empty($currentCountryId)) {
$countryCriteria = new Criteria();
$countryCriteria->addFilter(new EqualsFilter('id', $currentCountryId));;
$country = $this->countryRepository->search($countryCriteria, $context->getContext())->first();
if (!empty($country)) {
$request->request->set('countryCode', $country->getIso());
}
}
$billingAddress = [
'countryId' => $currentCountryId,
'salutationId' => $request->request->get('salutationId'),
'firstName' => $request->request->get('firstName') ?? '',
'lastName' => $request->request->get('lastName') ?? '',
'zipcode' => 'noinfo',
'city' => 'noinfo',
'street' => 'noinfo',
'phoneNumber' => $request->request->get('phoneNumber'),
];
$request->request->set('billingAddress', $billingAddress);
if (empty($request->request->get('storefrontUrl')) && !empty($currentSalesChannelDomain)) {
$request->request->set('storefrontUrl', $currentSalesChannelDomain->getUrl());
}
}
public function solomonLogin($data, SalesChannelContext $context)
{
$request = $this->requestStack->getCurrentRequest();
$languageCode = $request->headers->get('language-code', $request->getLocale());
$now = new \DateTimeImmutable();
$email = $data->get('email', $data->get('username'));
if (empty($email)) {
return (new CustomApiException($this->translator->trans('message.auth.00101', [], null, $languageCode)))->jsonResponse();
}
if (empty($data->get('password'))) {
return (new CustomApiException($this->translator->trans('message.auth.00102', [], null, $languageCode)))->jsonResponse();
}
$event = new CustomerBeforeLoginEvent($context, $email);
$this->eventDispatcher->dispatch($event);
if ($this->requestStack->getMainRequest() !== null) {
$cacheKey = strtolower($email) . '-' . $this->requestStack->getMainRequest()->getClientIp();
try {
$this->rateLimiter->ensureAccepted(RateLimiter::LOGIN_ROUTE, $cacheKey);
} catch (RateLimitExceededException $exception) {
return (new CustomApiException($this->translator->trans('message.auth.00102', [], null, $languageCode)))->jsonResponse();
}
}
try {
$customer = $this->getCustomerByLogin(
$email,
$data->get('password'),
$context
);
} catch (CustomerNotFoundException|BadCredentialsException $exception) {
return (new CustomApiException($this->translator->trans('message.auth.00108', [], null, $languageCode)))->jsonResponse();
} catch (CheckDeleteException $exception) {
return (new CustomApiException($this->translator->trans('message.auth.00109', [], null, $languageCode)))->jsonResponse();
}
if (isset($cacheKey)) {
$this->rateLimiter->reset(RateLimiter::LOGIN_ROUTE, $cacheKey);
}
$context = $this->restorer->restore($customer->getId(), $context);
$newToken = $context->getToken();
$this->customerRepository->update([
[
'id' => $customer->getId(),
'lastLogin' => $now,
'languageId' => $context->getLanguageId(),
],
], $context->getContext());
$event = new CustomerLoginEvent($context, $customer, $newToken);
$this->eventDispatcher->dispatch($event);
$firstTurn = true;
if (isset($customer->getCustomFields()['luckyWheelFirstTurn'])) {
$firstTurn = $customer->getCustomFields()['luckyWheelFirstTurn'];
}
$unView = $this->accountService->getCountUnviewNotification($context, $customer->getId());
$credits = $this->rewardPointService->getCustomerTotalRewardPoint($customer->getId(), $context->getContext());
$turns = $this->luckyWheelApiService->handleUserTurn($context);
$hasBoughtProduct = $this->accountService->checkHasBoughtProduct($customer->getId(), $context);
$customer->common = [
'unView' => $unView,
'credits' => $credits,
'turns' => $turns == null ? 0 : $turns,
'hasBoughtProduct' => $hasBoughtProduct > 0
];
return [
'contextToken' => $newToken,
'redirectUrl' => null,
'expiresAt' => $now->add(new \DateInterval($this->lifetimeInterval)),
'active' => $customer->getActive(),
'luckyWheelFirstTurn' => $firstTurn,
'profile' => $customer
];
}
public function handleFillData($request, $currentCountryId, $currentSalesChannelDomain, SalesChannelContext $context)
{
if (!empty($currentSalesChannelLanguageId)) {
$language = $this->languageRepository->search((new Criteria([$currentSalesChannelLanguageId]))->addAssociations(['translationCode', 'locale']), $context->getContext())->first();
$request->request->set('languageId', $currentSalesChannelLanguageId);
if (!empty($language?->getTranslationCode()?->getCode())) {
$languageCode = strtolower(explode('-', $language?->getTranslationCode()?->getCode())[0]);
$countryCode = strtolower(explode('-', $language?->getTranslationCode()?->getCode())[1]);
$request->request->set('languageCode', $languageCode);
$request->request->set('countryCode', $countryCode);
if (!empty($countryCode)) {
$isoCode = strtoupper($countryCode);
if ($isoCode === 'GB') {
$isoCode = 'DE';
$countryIds = $this->countryRepository->searchIds((new Criteria())->addFilter(new EqualsFilter('iso', $isoCode)), $context->getContext())->getIds();
if (!empty($countryIds) && $countryIds[0] != $currentCountryId) {
$currentCountryId = $countryIds[0];
}
}
if (!empty($currentCountryId)) {
$request->request->set('countryId', $currentCountryId);
}
}
}
}
if (empty($request->request->get('userName')) && !empty($request->request->get('email'))) {
$request->request->set('userName', $request->request->get('email'));
}
$birthDay = !empty($request->request->get('birthday')) ? date_parse_from_format('Y-m-d', $request->request->get('birthday')) : null;
if (!empty($birthDay)) {
$request->request->set('birthdayDay', $birthDay['day']);
$request->request->set('birthdayMonth', $birthDay['month']);
$request->request->set('birthdayYear', $birthDay['year']);
}
if (empty($request->request->get('salutationId'))) {
$salutation = $this->salutationRepository->search(new Criteria(), $context->getContext())->first();
if (!empty($salutation)) {
$request->request->set('salutationId', $salutation->getId());
}
}
if (empty($request->request->get('storefrontUrl')) && !empty($currentSalesChannelDomain)) {
$request->request->set('storefrontUrl', $currentSalesChannelDomain->getUrl());
}
}
public function solomonRegisterValidate($request, $context)
{
$languageCode = $request->headers->get('language-code', $request->getLocale());
$definition = $this->customerValidationFactory->create($context);
$minLength = $this->systemConfigService->get('core.loginRegistration.passwordMinLength', $context->getSalesChannel()->getId());
$definition->add('password', new NotBlank(), new Length(['min' => $minLength]));
$options = ['context' => $context->getContext(), 'salesChannelContext' => $context];
$definition->add('email', new CustomerEmailUnique($options));
// $definition->add("phoneNumber", new NotBlank());
$definition->add('affiliateCode', new NotBlank());
$definition->add('userName', new NotBlank());
$definition->add('birthday', new NotBlank());
$constraints = $this->getAffiliateCodeConstraints($context->getContext());
if (!empty($request->get('affiliateCode'))) {
foreach ($constraints as $constraint) {
$definition->add('affiliateCode', $constraint);
}
}
$validation = $this->dataValidator->getViolations($request->request->all(), $definition);
//check for validation errors.
if ($validation->count() > 0) {
$translatedViolations = new ConstraintViolationList();
foreach ($validation as $violation) {
$message = $violation->getMessageTemplate();
$propertyPath = $violation->getPropertyPath();
switch ($propertyPath) {
case '/firstName':
{
$translatedMessage = $this->translator->trans("message.auth.00110.$message", [], null, $languageCode);
break;
}
case '/lastName':
{
$translatedMessage = $this->translator->trans("message.auth.00110.$message", [], null, $languageCode);
break;
}
case '/password':
{
$translatedMessage = $this->translator->trans("message.auth.00110.$message", [], null, $languageCode);
break;
}
case '/email':
{
if (isset($violation->getParameters()['{{ email }}'])) {
$email = $violation->getParameters()['{{ email }}'];
$translatedMessage = $this->translator->trans("message.auth.00110.$message", ['{{ email }}' => $email], null, $languageCode);
} else {
$translatedMessage = $this->translator->trans("message.auth.00110.$message", [], null, $languageCode);
}
break;
}
case '/userName':
{
$translatedMessage = $this->translator->trans("message.auth.00110.$message", [], null, $languageCode);
break;
}
default:
{
$translatedMessage = $this->translator->trans("message.auth.00111", [], null, $languageCode);
}
}
$translatedViolations->add(new ConstraintViolation(
$translatedMessage,
$violation->getMessageTemplate(),
$violation->getParameters(),
$violation->getRoot(),
$violation->getPropertyPath(),
$violation->getInvalidValue()
));
}
throw new ConstraintViolationException($translatedViolations, $request->request->all());
}
$requestDataBag = new RequestDataBag($request->request->all());
return $this->registerRoute->register($requestDataBag, $context);
}
#[Route('/store-api/home/solomon-language-list', name: 'mobile.cms.language.list', methods: ['GET'])]
public function getSolomonLanguageList(Request $request, SalesChannelContext $context)
{
$languages = $this->languageRepository->search((new Criteria())
->addFilter(new EqualsFilter('salesChannelDomains.salesChannelId', $context->getSalesChannelId()))
// ->addFilter(new MultiFilter(MultiFilter::CONNECTION_OR, [
// new ContainsFilter('translationCode.code', 'en-GB'),
// new ContainsFilter('translationCode.code', 'de-DE'),
// ]))
->addFilter(new NotFilter(MultiFilter::CONNECTION_OR, [
new ContainsFilter('translationCode.code', 'th-TH'),
new ContainsFilter('translationCode.code', 'en-HK'),
new ContainsFilter('translationCode.code', 'es-ES'),
]))
->addAssociations(['locale', 'translationCode']), $context->getContext())->getEntities()->jsonSerialize();
return new JsonResponse(
['data' => $languages, 'message' => 'success']
);
}
}