<?php
namespace ShopWithMe\UI\Controller;
use Divante\Nuvei\Service\Handler\NuveiSubscriptionPaymentHandler;
use Econsor\Amity\Services\AmityPosts\AmityPostService;
use Econsor\Amity\Services\Stream\StreamSorter;
use Econsor\Shopware\SearchBar\Storefront\Controller\EconsorTagSaverController;
use EconsorAppStorefront\Core\Validator\Duplications;
use EconsorAppStorefront\Services\CustomerDataService;
use EconsorAppStorefront\Services\DiscountCodeGenerator;
use EconsorAppStorefront\Storefront\registration\RegistrationAppPageLoader;
use Psr\Log\LoggerInterface;
use Shopware\Core\Checkout\Cart\CartCalculator;
use Shopware\Core\Checkout\Cart\CartPersisterInterface;
use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Cart\Order\OrderPersister;
use Shopware\Core\Checkout\Cart\Order\Transformer\CustomerTransformer;
use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
use Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity;
use Shopware\Core\Checkout\Customer\CustomerDefinition;
use Shopware\Core\Checkout\Customer\CustomerEntity;
use Shopware\Core\Checkout\Customer\Exception\AddressNotFoundException;
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractListAddressRoute;
use Shopware\Core\Checkout\Customer\SalesChannel\AccountService;
use Shopware\Core\Checkout\Customer\Validation\Constraint\CustomerEmailUnique;
use Shopware\Core\Checkout\Customer\Validation\CustomerValidationFactory;
use Shopware\Core\Checkout\Document\FileGenerator\FileTypes;
use Shopware\Core\Checkout\Document\Struct\DocumentGenerateOperation;
use Shopware\Core\Checkout\Document\Service\DocumentGenerator;
use Shopware\Core\Checkout\Payment\PaymentMethodEntity;
use Shopware\Core\Content\Media\File\MediaFile;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
use Shopware\Core\Framework\DataAbstractionLayer\Validation\EntityExists;
use Shopware\Core\Framework\Feature;
use Shopware\Core\Framework\Routing\RequestContextResolverInterface;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Shopware\Core\Framework\Uuid\Exception\InvalidUuidException;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\Framework\Validation\DataBag\DataBag;
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\System\Language\LanguageEntity;
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextFactory;
use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepositoryInterface;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Controller\RegisterController;
use Shopware\Storefront\Controller\StorefrontController;
use Shopware\Storefront\Framework\Cache\Annotation\HttpCache;
use Shopware\Storefront\Framework\Routing\Router;
use Shopware\Storefront\Page\Address\AddressEditorModalStruct;
use Shopware\Storefront\Page\Address\Listing\AddressBookWidgetLoadedHook;
use Shopware\Storefront\Page\Address\Listing\AddressListingPageLoader;
use Shopware\Storefront\Page\GenericPageLoader;
use Shopware\Storefront\Page\Product\ProductPageLoadedHook;
use Shopware\Storefront\Page\Product\ProductPageLoader;
use ShopWithMe\LP\Service\RankingService;
use ShopWithMe\Reward\Entities\Enums\RewardPointType;
use ShopWithMe\Reward\Entities\Enums\RewardShipLaterType;
use ShopWithMe\Reward\Service\RewardPointService;
use ShopWithMe\Reward\Service\RewardShipLaterService;
use ShopWithMe\UI\Service\CustomerBusinessNameService;
use ShopWithMe\UI\Service\HomePageService;
use Divante\Nuvei\Controller\NuveiSubscriptionController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Shopware\Storefront\Page\Navigation\NavigationPageLoaderInterface;
use Shopware\Storefront\Page\Navigation\NavigationPageLoadedHook;
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractChangeCustomerProfileRoute;
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractChangeEmailRoute;
use Shopware\Core\System\SalesChannel\SuccessResponse;
use Shopware\Storefront\Page\Address\Detail\AddressDetailPageLoader;
use Shopware\Storefront\Page\Address\Detail\AddressDetailPageLoadedHook;
use Shopware\Storefront\Page\Account\Profile\AccountProfilePageLoader;
use Shopware\Storefront\Page\Account\Profile\AccountProfilePageLoadedHook;
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractUpsertAddressRoute;
use Shopware\Core\Content\Newsletter\SalesChannel\AbstractNewsletterSubscribeRoute;
use Shopware\Core\Content\Newsletter\SalesChannel\AbstractNewsletterUnsubscribeRoute;
use Shopware\Storefront\Framework\Routing\RequestTransformer;
use Shopware\Storefront\Page\Account\Overview\AccountOverviewPageLoadedHook;
use Shopware\Storefront\Page\Account\Overview\AccountOverviewPageLoader;
use EconsorAppStorefront\Services\AccountService as EconsorAccountService;
use Shopware\Storefront\Page\Account\Order\AccountOrderPageLoader;
use Shopware\Storefront\Page\Account\Order\AccountOrderPageLoadedHook;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Content\Newsletter\Aggregate\NewsletterRecipient\NewsletterRecipientEntity;
use Symfony\Component\Validator\Constraints\NotEqualTo;
use Symfony\Component\Validator\Validation;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\OrFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
use ShopWithMe\MobileApi\Service\AIStreamService;
use ShopWithMe\Reward\Service\LuckyWheelService\LuckyWheelApiService;
use Symfony\Component\HttpFoundation\JsonResponse;
use Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct;
use Shopware\Core\Checkout\Payment\PaymentService;
use Shopware\Core\Checkout\Cart\Transaction\Struct;
use ShopWithMe\UI\Service\SubscriptionService;
use Shopware\Core\System\StateMachine\StateMachineRegistry;
use Shopware\Core\System\StateMachine\Transition;
use Shopware\Core\Checkout\Order\OrderDefinition;
use Doctrine\DBAL\Connection;
#[Route(defaults: ['_routeScope' => ['storefront']])]
class AppStorefrontController extends StorefrontController
{
private const ADDRESS_TYPE_BILLING = 'billing';
private const ADDRESS_TYPE_SHIPPING = 'shipping';
const LIMIT = 100;
/**
* @var RegistrationAppPageLoader
*/
private $registrationAppPageLoader;
/**
* @var RegisterController
*/
private $registerController;
/**
* @var CustomerValidationFactory
*/
private $customerValidationFactory;
/**
* @var DataValidator
*/
private $dataValidator;
/**
* @var SystemConfigService
*/
private $systemConfigService;
/**
* @var RequestContextResolverInterface
*/
private $contextResolver;
/**
* @var EconsorTagSaverController
*/
private $tagSaverController;
/**
* @var EntityRepository
*/
private $customerEntity;
/**
* @var Router
*/
private $router;
private NavigationPageLoaderInterface $navigationPageLoader;
private AbstractChangeCustomerProfileRoute $changeCustomerProfileRoute;
private AbstractChangeEmailRoute $changeEmailRoute;
private AddressDetailPageLoader $addressDetailPageLoader;
private AccountOverviewPageLoader $overviewPageLoader;
private AccountProfilePageLoader $profilePageLoader;
private AbstractUpsertAddressRoute $updateAddressRoute;
/**
* @var AbstractNewsletterSubscribeRoute
*/
private $newsletterSubscribeRoute;
/**
* @var AbstractNewsletterUnsubscribeRoute
*/
private $newsletterUnsubscribeRoute;
private EconsorAccountService $econsorAccountService;
private AccountOrderPageLoader $orderPageLoader;
/**
* @var EntityRepositoryInterface
*/
private $newsletterRecipientRepository;
protected EntityRepositoryInterface $languageRepository;
protected EntityRepositoryInterface $countryRepository;
protected $homepageService;
private LoggerInterface $logger;
private StreamSorter $amityService;
private EntityRepositoryInterface $luckyWheelTurnRepository;
private EntityRepositoryInterface $productBundleItemsRepository;
protected SalesChannelRepositoryInterface $salesChannelProductRepository;
protected NuveiSubscriptionController $nuveiSubscriptionController;
protected PaymentService $paymentService;
private EntityRepositoryInterface $orderRepository;
private EntityRepository $subscriptionRepository;
private EntityRepository $subscriptionOrderRepository;
private StateMachineRegistry $stateMachineRegistry;
private Connection $connection;
private EntityRepository $stateMachineStateRepository;
private DocumentGenerator $documentGenerator;
private EntityRepository $documentRepository;
public function __construct(
RegistrationAppPageLoader $registrationAppPageLoader,
RegisterController $registerController,
CustomerValidationFactory $customerValidationFactory,
DataValidator $dataValidator,
SystemConfigService $systemConfigService,
RequestContextResolverInterface $contextResolver,
EconsorTagSaverController $tagSaverController,
EntityRepository $customerEntity,
Router $router,
NavigationPageLoaderInterface $navigationPageLoader,
AbstractChangeCustomerProfileRoute $changeCustomerProfileRoute,
AbstractChangeEmailRoute $changeEmailRoute,
AddressDetailPageLoader $addressDetailPageLoader,
AccountProfilePageLoader $profilePageLoader,
AbstractUpsertAddressRoute $updateAddressRoute,
AbstractNewsletterSubscribeRoute $newsletterSubscribeRoute,
AbstractNewsletterUnsubscribeRoute $newsletterUnsubscribeRoute,
AccountOverviewPageLoader $overviewPageLoader,
EconsorAccountService $econsorAccountService,
AccountOrderPageLoader $orderPageLoader,
EntityRepositoryInterface $newsletterRecipientRepository,
EntityRepositoryInterface $languageRepository,
EntityRepositoryInterface $countryRepository,
HomePageService $homePageService,
LoggerInterface $logger,
protected AmityPostService $postService,
protected ProductPageLoader $productPageLoader,
protected DiscountCodeGenerator $discountCodeGenerator,
protected CustomerDataService $customerDataService,
protected EntityRepository $customerNotificationRepository,
protected AccountService $accountService,
protected AddressListingPageLoader $addressListingPageLoader,
protected AbstractChangeCustomerProfileRoute $updateCustomerProfileRoute,
protected AbstractListAddressRoute $listAddressRoute,
EntityRepositoryInterface $luckyWheelTurnRepository,
protected CustomerBusinessNameService $customerBusinessNameService,
protected EntityRepositoryInterface $videoRecordRepository,
protected EntityRepository $inviteFriendHistory,
protected AIStreamService $aiStreamService,
protected RankingService $rankingService,
EntityRepository $customerAcceptCookieRepository,
protected LuckyWheelApiService $luckyWheelApiService,
protected RewardShipLaterService $rewardShipLaterService,
protected EntityRepository $product,
protected CartService $cartService,
protected OrderPersister $orderPersister,
protected EntityRepository $paymentMethodRepository,
SalesChannelRepositoryInterface $salesChannelProductRepository,
protected EntityRepositoryInterface $customerAddressRepository,
protected NuveiSubscriptionPaymentHandler $nuveiSubscriptionPaymentHandler,
NuveiSubscriptionController $nuveiSubscriptionController,
PaymentService $paymentService,
protected SubscriptionService $subscriptionService,
EntityRepositoryInterface $orderRepository,
EntityRepository $subscriptionRepository,
EntityRepository $subscriptionOrderRepository,
StateMachineRegistry $stateMachineRegistry,
Connection $connection,
EntityRepository $stateMachineStateRepository,
protected GenericPageLoader $genericLoader,
EntityRepositoryInterface $productBundleItemsRepository = null,
protected SalesChannelRepositoryInterface $countrySalesChannel,
DocumentGenerator $documentGenerator,
EntityRepository $documentRepository,
)
{
$this->registrationAppPageLoader = $registrationAppPageLoader;
$this->registerController = $registerController;
$this->customerValidationFactory = $customerValidationFactory;
$this->dataValidator = $dataValidator;
$this->systemConfigService = $systemConfigService;
$this->contextResolver = $contextResolver;
$this->tagSaverController = $tagSaverController;
$this->customerEntity = $customerEntity;
$this->router = $router;
$this->navigationPageLoader = $navigationPageLoader;
$this->changeCustomerProfileRoute = $changeCustomerProfileRoute;
$this->changeEmailRoute = $changeEmailRoute;
$this->addressDetailPageLoader = $addressDetailPageLoader;
$this->profilePageLoader = $profilePageLoader;
$this->overviewPageLoader = $overviewPageLoader;
$this->updateAddressRoute = $updateAddressRoute;
$this->newsletterSubscribeRoute = $newsletterSubscribeRoute;
$this->newsletterUnsubscribeRoute = $newsletterUnsubscribeRoute;
$this->econsorAccountService = $econsorAccountService;
$this->orderPageLoader = $orderPageLoader;
$this->newsletterRecipientRepository = $newsletterRecipientRepository;
$this->languageRepository = $languageRepository;
$this->countryRepository = $countryRepository;
$this->homepageService = $homePageService;
$this->logger = $logger;
$this->luckyWheelTurnRepository = $luckyWheelTurnRepository;
$this->customerAcceptCookieRepository = $customerAcceptCookieRepository;
$this->product = $product;
$this->cartService = $cartService;
$this->orderPersister = $orderPersister;
$this->salesChannelProductRepository = $salesChannelProductRepository;
$this->nuveiSubscriptionController = $nuveiSubscriptionController;
$this->paymentService = $paymentService;
$this->orderRepository = $orderRepository;
$this->subscriptionRepository = $subscriptionRepository;
$this->subscriptionOrderRepository = $subscriptionOrderRepository;
$this->customerAddressRepository = $customerAddressRepository;
$this->stateMachineRegistry = $stateMachineRegistry;
$this->connection = $connection;
$this->stateMachineStateRepository = $stateMachineStateRepository;
$this->productBundleItemsRepository = $productBundleItemsRepository;
$this->documentGenerator = $documentGenerator;
$this->documentRepository = $documentRepository;
}
#[Route('/product/search', name: 'frontend.product.search', options: ["seo" => false], defaults: ['XmlHttpRequest' => true], methods: ['POST'])]
public function searchProduct(Request $request, SalesChannelContext $context): Response
{
$products = $this->homepageService->searchProduct($request->request->get('keyword') ?? $request->query->get('keyword'), $context);
return $this->renderStorefront('@Storefront/storefront/component/header/product-search-result.html.twig', ['products' => $products]);
}
#[Route('/app/login', name: 'frontend.econsor.app.storefront.login')]
public function showApp(Request $request, SalesChannelContext $context): Response
{
$url = $this->router->generate('frontend.account.login');
return $this->redirect($url);
if ($context->getCustomer() != null) {
$url = $this->router->generate('frontend.current-deals');
return $this->redirect($url);
}
if ($request->isMethod('POST')) {
if ($request->request->has('register')) {
$url = $this->router->generate('frontend.econsor.app.storefront.register');
return $this->redirect($url);
} else if ($request->request->has('login')) {
$url = $this->router->generate('frontend.account.login');
return $this->redirect($url);
} else if ($request->request->has('loginFacebook')) {
$url = $this->router->generate('frontend.account.login');
return $this->redirect($url);
}
}
$request->getSession()->remove('before-login-video-seen');
$page = $this->registrationAppPageLoader->load($request, $context);
return $this->renderStorefront('@Storefront/storefront/page/account/register/index.html.twig', [
'page' => $page,
'salesChannelContext' => $context,
'controllerName' => 'EconsorAppStorefrontController',
'controllerAction' => 'showApp'
]);
}
#[Route('/app/register', name: 'frontend.econsor.app.storefront.register')]
public function registration(Request $request, SalesChannelContext $context): Response
{
if ($context->getCustomer() != null) {
$url = $this->router->generate('frontend.current-deals');
return $this->redirect($url);
}
$page = $this->registrationAppPageLoader->load($request, $context);
return $this->renderStorefront('@Storefront/storefront/page/custom/register/index.html.twig', ['page' => $page]);
}
#[Route('/app/google/login/finish', name: 'frontend.google.login')]
public function googleLoginFinish(Request $request, SalesChannelContext $context): Response
{
if ($context->getCustomer() != null) {
$url = $this->router->generate('frontend.current-deals');
return $this->redirect($url);
}
$data = $request->query->all();
$page = $this->registrationAppPageLoader->load($request, $context);
return $this->renderStorefront('@Storefront/storefront/page/custom/google-login/google-login-finish.html.twig', [
'page' => $page,
'salesChannelContext' => $context,
'tag' => array_key_exists('tag', $data) ? $data['tag'] : [],
'salutationId' => array_key_exists('salutationId', $data) ? $data['salutationId'] : '',
'phoneNumber' => array_key_exists('phoneNumber', $data) ? $data['phoneNumber'] : '',
'firstName' => array_key_exists('firstName', $data) ? $data['firstName'] : '',
'lastName' => array_key_exists('lastName', $data) ? $data['lastName'] : '',
'userName' => array_key_exists('email', $data) ? $data['email'] : '',
'birthday' => array_key_exists('birthday', $data) ? $data['birthday'] : '',
'email' => array_key_exists('email', $data) ? $data['email'] : '',
'password' => array_key_exists('password', $data) ? $data['password'] : '',
'isConfirm' => array_key_exists('isConfirm', $data) ? $data['isConfirm'] : '',
'violations' => array_key_exists('violations', $data) ? $data['violations'] : [],
'controllerName' => 'EconsorAppStorefrontController',
'controllerAction' => 'registration'
]);
}
#[Route('/app/register/form', name: 'frontend.econsor.app.storefront.register.form')]
public function registrationInterest(Request $request, SalesChannelContext $context): Response
{
if ($context->getCustomer() != null) {
$url = $this->router->generate('frontend.current-deals');
return $this->redirect($url);
}
$data = $request->query->all();
$page = $this->registrationAppPageLoader->load($request, $context);
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('active', true));
$country = $this->countrySalesChannel->search(new Criteria(), $context)->getElements();
return $this->renderStorefront('@Storefront/storefront/page/custom/register-interest/index.html.twig', [
'page' => $page,
'salesChannelContext' => $context,
'tag' => array_key_exists('tag', $data) ? $data['tag'] : [],
'salutationId' => array_key_exists('salutationId', $data) ? $data['salutationId'] : '',
'phoneNumber' => array_key_exists('phoneNumber', $data) ? $data['phoneNumber'] : '',
'firstName' => array_key_exists('firstName', $data) ? $data['firstName'] : '',
'lastName' => array_key_exists('lastName', $data) ? $data['lastName'] : '',
'userName' => array_key_exists('email', $data) ? $data['email'] : '',
'birthday' => array_key_exists('birthday', $data) ? $data['birthday'] : '',
'email' => array_key_exists('email', $data) ? $data['email'] : '',
'password' => array_key_exists('password', $data) ? $data['password'] : '',
'isConfirm' => array_key_exists('isConfirm', $data) ? $data['isConfirm'] : '',
'violations' => array_key_exists('violations', $data) ? $data['violations'] : [],
'controllerName' => 'EconsorAppStorefrontController',
'country' => $country,
'controllerAction' => 'registration'
]);
}
#[Route('/', name: 'frontend.home.page')]
public function home(Request $request, SalesChannelContext $context): Response
{
// $currentLocaleId = $context->getSalesChannel()->getLanguageId();
// if (!empty($currentLocaleId)) {
// $localeCriteria = new Criteria([$currentLocaleId]);
// $localeCriteria->addAssociation('translationCode');
// /** @var LanguageEntity|null $currentLocale */
// $currentLocale = $this->languageRepository->search($localeCriteria, $context->getContext())->first();
// if (!empty(explode('-', $currentLocale->getTranslationCode()->getCode())[1])) {
// $isoCode = strtolower(explode('-', $currentLocale->getTranslationCode()->getCode())[1]);
// if ($isoCode === 'vn') {
// $page = $this->genericLoader->load($request, $context);
// return $this->renderStorefront('@Storefront/storefront/page/content/index-vn.html.twig', ['page' => $page]);
// }
// else if ($isoCode === 'hk') {
// $page = $this->genericLoader->load($request, $context);
// return $this->renderStorefront('@Storefront/storefront/page/content/index-en.html.twig', ['page' => $page]);
// }
// }
// }
return $this->redirectToRoute('frontend.current-deals');
}
#[Route('/account', name: 'frontend.account.home.page', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function accountOverView(Request $request, SalesChannelContext $context, Context $sale): Response
{
if ($request->query->get('confirmDoneOrder')){
$orderId = $request->query->get('orderIdConfirmDone');
if ($orderId) {
try {
// Get current custom fields for the order
$orderCriteria = new Criteria([$orderId]);
$orderCriteria->addAssociation('stateMachineState');
$order = $this->orderRepository->search($orderCriteria, $context->getContext())->first();
if (!$order) {
throw new \Exception('Order not found');
}
if ($order->getStateMachineState()->getTechnicalName() === 'completed') {
throw new \Exception('Order completed!');
}
$customFields = $order->getCustomFields() ?? [];
// Set the typeDoneOrder field
$customFields['typeDoneOrder'] = 'confirm';
try {
// Update order state to 'complete' using StateMachineRegistry
// First update the custom fields
$this->orderRepository->update([
[
'id' => $orderId,
'customFields' => $customFields
]
], $context->getContext());
// Then transition the order state to 'complete'
$this->stateMachineRegistry->transition(
new Transition(
'order',
$orderId,
'complete',
'stateId'
),
$context->getContext()
);
$this->logger->info('Order state updated successfully', [
'orderId' => $orderId,
'processState' => 'process',
'finalState' => 'complete',
'method' => 'StateMachineRegistry transition'
]);
} catch (\Exception $dbException) {
$this->logger->error('Error updating order state:', [
'orderId' => $orderId,
'method' => 'StateMachineRegistry transition',
'error' => $dbException->getMessage(),
'trace' => $dbException->getTraceAsString()
]);
throw new \Exception('Failed to update order state: ' . $dbException->getMessage());
}
// Add a success message for the user with more details
$this->addFlash('success', 'Order has been marked as complete successfully and custom fields have been updated using StateMachineRegistry transition.');
} catch (\Exception $e) {
// Log the error with detailed information
$this->logger->error('Failed to update order status:', [
'orderId' => $orderId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
// Add an error message for the user
$this->addFlash('danger', 'Failed to update order status: ' . $e->getMessage());
}
}
}
$page = $this->genericLoader->load($request, $context);
$status = $request->query->get('status');
$orders = $this->homepageService->getCustomerOrderByStatus($status, $context);
$customerCredit = $this->homepageService->getCustomerCredit($context, $sale);
$nextCreditProductLevel = $this->homepageService->loadNextCreditProductOfCustomer($customerCredit, $context);
$customerInvitedCount = $this->homepageService->getCustomerInvitedCount($context);
$interest = $this->homepageService->getCustomerTag($context);
$customerSubscriptions = $this->homepageService->getSubscriptions($context);
$sumPriceSubscriptions = $this->homepageService->sumPriceSubscriptions($customerSubscriptions);
$customer = $context->getCustomer();
$isPartner = $customer !== null
&& $customer->getAffiliateCode() !== null
&& $customer->getAffiliateCode() === $customer->getCampaignCode();
return $this->renderStorefront('@Storefront/storefront/page/custom/account/over-view/index.html.twig', [
'page' => $page,
'customer' => $customerCredit,
'orders' => $orders,
'interest' => $interest,
'nextCreditProductLevel' => $nextCreditProductLevel,
'customerInvitedCount' => $customerInvitedCount,
'sumPriceSubscriptions' => $sumPriceSubscriptions,
'isPartner' => $isPartner,
]);
}
#[Route('/account/dashboard', name: 'frontend.account.dashboard', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function accountDashboard(Request $request, SalesChannelContext $context, Context $sale): Response
{
$page = $this->genericLoader->load($request, $context);
$customer = $context->getCustomer();
$dataStreamLiked = $this->postService->fetchStream($context->getCustomer()->getId(), $context, "liked");
$dataStreamUpload = $this->postService->fetchStream($context->getCustomer()?->getId(), $context, "uploaded");
$dataStreamSaved = $this->postService->fetchStream($context->getCustomer()?->getId(), $context, "saved");
return $this->renderStorefront('@Storefront/storefront/page/account/dashboard/index.html.twig', ['page' => $page, 'dataStreamUpload' => $dataStreamUpload, 'dataStreamLiked' => $dataStreamLiked, 'dataStreamSaved' => $dataStreamSaved, 'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
}
#[Route('/account/order', name: 'frontend.account.order.page', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function accountMyOrder(Request $request, SalesChannelContext $context): Response
{
$status = $request->query->get('status');
$page = $this->genericLoader->load($request, $context);
$orders = $this->homepageService->getCustomerOrderByStatus($status, $context);
$customer = $context->getCustomer();
$isPartner = $customer !== null
&& $customer->getAffiliateCode() !== null
&& $customer->getAffiliateCode() === $customer->getCampaignCode();
return $this->renderStorefront('@Storefront/storefront/page/custom/account/my-order/index.html.twig', ['page' => $page, 'orders' => $orders, 'isPartner' => $isPartner]);
}
#[Route('/account/partner-dashboard', name: 'frontend.account.partner.dashboard', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function accountPartnerDashboard(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
$invitedList = $this->homepageService->getCustomerInvitedList($context);
return $this->renderStorefront('@Storefront/storefront/page/custom/account/partner-dashboard/index.html.twig', [
'page' => $page,
'invitedList' => $invitedList,
'totalVideoWatchedToday' => $this->videoRecordRepository->search((new Criteria())->addFilter(
new EqualsFilter('customerId', $context->getCustomerId()),
new EqualsFilter('type', 'watch'),
new RangeFilter('createdAt', [
RangeFilter::GTE => (new \DateTime())->setTime(0, 0)->format('Y-m-d H:i:s'),
RangeFilter::LTE => (new \DateTime())->setTime(23, 59, 59)->format('Y-m-d H:i:s')
]),
), $context->getContext())->count(),
'totalVideoSharedToday' => $this->videoRecordRepository->search((new Criteria())->addFilter(
new EqualsFilter('customerId', $context->getCustomerId()),
new EqualsFilter('type', 'share'),
new RangeFilter('createdAt', [
RangeFilter::GTE => (new \DateTime())->setTime(0, 0)->format('Y-m-d H:i:s'),
RangeFilter::LTE => (new \DateTime())->setTime(23, 59, 59)->format('Y-m-d H:i:s')
]),
), $context->getContext())->count(),
]);
}
#[Route('/account/order-list', name: 'frontend.account.order.page.list', options: ["seo" => false], defaults: ['_loginRequired' => true, 'XmlHttpRequest' => true], methods: ['POST'])]
public function accountMyOrderList(Request $request, SalesChannelContext $context): Response
{
$status = $request->request->get('status');
$isMobile = $request->request->get('isMobile');
$orders = $this->homepageService->getCustomerOrderByStatus($status, $context);
$customer = $context->getCustomer();
$isPartner = $customer !== null
&& $customer->getAffiliateCode() !== null
&& $customer->getAffiliateCode() === $customer->getCampaignCode();
return $this->renderStorefront('@Storefront/storefront/page/custom/account/my-order/list-order.html.twig', ['orders' => empty($orders['errorMsg']) ? $orders : [], 'isMobile' => $isMobile, 'isPartner' => $isPartner]);
}
#[Route(path: '/notification/list', name: 'frontend.notification.list', defaults: ['_loginRequired' => true, 'XmlHttpRequest' => true], methods: ['POST', 'GET'])]
public function getNotificationList(Request $request, SalesChannelContext $context): Response
{
$customerId = $context->getCustomer()->getId();
$type = $request->request->get('type', null);
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('customerId', $customerId));
$criteria->addAssociation('notification');
$criteria->addSorting(new FieldSorting('createdAt', FieldSorting::DESCENDING));
if (!empty($type)) {
$criteria->addFilter(new EqualsFilter('notification.type', $type));
}
$notifications = $this->customerNotificationRepository->search($criteria, $context->getContext());
// Update view notification of customer
$unviewNotifications = $notifications->filter(fn($notification) => $notification->getIsView() == 0);
if (count($unviewNotifications) > 0) {
foreach ($unviewNotifications as $customerNotification) {
$this->customerNotificationRepository->update([
[
'id' => $customerNotification->getId(),
'isView' => 1
]
], $context->getContext());
}
}
// Quantity of lucky wheel
$criteriaLuckyWheel = new Criteria();
$currentDate = (new \DateTime())->format('Y-m-d H:i:s');
$criteriaLuckyWheel->addFilter(
new MultiFilter(
MultiFilter::CONNECTION_AND,
[
new EqualsFilter('customerId', $customerId),
new RangeFilter('amountRemain', [RangeFilter::GT => 0]),
new OrFilter([
new EqualsFilter('expiredDate', null),
new RangeFilter('expiredDate', [RangeFilter::GT => $currentDate])
])
]
)
)->addFilter(
new NotFilter(
MultiFilter::CONNECTION_AND,
[
new EqualsFilter('reason', "Invalid"),
]
)
);
$notificationsLuckyWheel = $this->luckyWheelTurnRepository->search($criteriaLuckyWheel, $context->getContext());
$quantityLuckyWheel = array_reduce($notificationsLuckyWheel->getEntities()->getElements(), function ($carry, $item) {
return $carry + $item->get('amountRemain');
}, 0);
return $this->renderStorefront('@Storefront/storefront/component/header/notification.html.twig', ['notifications' => $notifications, 'quantityLuckyWheel' => $quantityLuckyWheel]);
}
#[Route(path: '/notification/count-not-view', name: 'api.notification.count-not-view', defaults: ['_loginRequired' => true, 'XmlHttpRequest' => true], methods: [Request::METHOD_POST])]
public function getCountNotificationNotView(SalesChannelContext $context): Response
{
$customerId = $context->getCustomer()->getId();
$countNotViewNotification = 0;
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('customerId', $customerId));
$criteria->addFilter(new EqualsFilter('isView', 0));
$notificationsNotView = $this->customerNotificationRepository->search($criteria, $context->getContext());
$countNotViewNotification = count($notificationsNotView);
return new JsonResponse([
'countNotViewNotification' => $countNotViewNotification
]);
}
#[Route('/my-deals', name: 'frontend.my.deals', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function myDeals(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
$customer = $context->getCustomer();
$activeDeals = [];
if ($customer) {
$activeDeals = $this->econsorAccountService->getActiveDeals($customer, $context);
}
$credits = $this->homepageService->getCredits($context);
$redeemedItems = $this->homepageService->getRedeemedItems($context);
return $this->renderStorefront('@Storefront/storefront/page/custom/my-deal/pageDeal.html.twig', ['page' => $page, 'activeDeals' => $activeDeals, 'customer' => $customer, 'credits' => $credits, 'redeemedItems' => $redeemedItems]);
}
#[Route('/ai-stream', name: 'frontend.ai.stream')]
public function aiStream(Request $request, SalesChannelContext $context)
{
$page = $this->genericLoader->load($request, $context);
$customer = $context->getCustomer();
$experienceId = $request->query->get('experienceId');
if ($experienceId) {
return $this->redirectToRoute('frontend.ai.stream.detail.page', ['id' => $experienceId]);
}
$dataStream = $this->postService->fetchStream($context->getCustomer()?->getId(), $context, "tags", null, $experienceId);
$dataStream = $dataStream;
$targetObject = null;
$index = -1;
if ($experienceId !== null) {
foreach ($dataStream as $key => $value) {
if ($value->id == $experienceId) {
$index = $key;
$targetObject = $value;
break;
}
}
if ($targetObject !== null && $index !== -1) {
unset($dataStream[$index]);
$dataStream = array_values($dataStream);
array_unshift($dataStream, $targetObject);
}
}
return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/home/index.html.twig', ['page' => $page, 'customer' => $customer, 'dataStream' => $dataStream, 'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
}
// #[Route('/ai-stream-list', name: 'frontend.ai.stream.page.list', options: ["seo" => false], defaults: ['XmlHttpRequest' => true], methods: ['POST'])]
// public function aiStreamList(Request $request, SalesChannelContext $context)
// {
// $page = $this->genericLoader->load($request, $context);
// $customer = $context->getCustomer();
// $experienceId = null;
// $pageNumber = 2;
// $dataStream = $this->postService->fetchStream($context->getCustomer()?->getId(), $context, "tags", null, $experienceId, $pageNumber);
// return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/home/list-video.html.twig', ['page' => $page, 'customer' => $customer, 'dataStream' => $dataStream['posts'], 'dashboardData' => $this->customerDataService->getBoughtProducts($context), 'totalVideo' => $dataStream['total']]);
// return 1;
// }
#[Route('/ai-stream/liked', name: 'frontend.ai.stream.liked', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function aiStreamLiked(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
$customer = $context->getCustomer();
$dataStream = $this->postService->fetchStream($context->getCustomer()->getId(), $context, "liked");
return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/liked/index.html.twig', ['page' => $page, 'customer' => $customer, 'dataStream' => $dataStream, 'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
}
#[Route('/ai-stream/upload/list', name: 'frontend.ai.stream.upload.list', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function aiStreamUploadList(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
$customer = $context->getCustomer();
$dataStream = $this->postService->fetchStream($context->getCustomer()?->getId(), $context, "uploaded");
return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/upload-list/index.html.twig', ['page' => $page, 'customer' => $customer, 'dataStream' => $dataStream, 'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
}
#[Route('/ai-stream/upload', name: 'frontend.ai.stream.upload', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function aiStreamUpload(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
$customer = $context->getCustomer();
return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/upload/index.html.twig', ['page' => $page, 'customer' => $customer, 'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
}
#[Route('/story-mode/upload', name: 'frontend.story.mode.upload', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function storyModeUpload(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
$customer = $context->getCustomer();
return $this->renderStorefront('@Storefront/storefront/page/custom/story-mode/upload/index.html.twig', ['page' => $page, 'customer' => $customer, 'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
}
#[Route('/dealDetail', name: 'frontend.my.deal.detail', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function myDealDetail(Request $request, SalesChannelContext $context, Context $sale): Response
{
$page = $this->genericLoader->load($request, $context);
$customer = $this->homepageService->getCustomerCredit($context, $sale);
$orderId = $request->query->get('orderId');
$orderData = $this->homepageService->getOrderById($orderId, $context);
$creditType = $this->homepageService->getOrderCreditType($orderId, $context);
return $this->renderStorefront('@Storefront/storefront/page/custom/my-deal/pageDealDetail.html.twig', ['page' => $page, 'orderData' => $orderData, 'customer' => $customer, 'creditType' => $creditType]);
}
#[Route('/trackingDetail', name: 'frontend.tracking.detail', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function trackingDetail(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
return $this->renderStorefront('@Storefront/storefront/page/custom/tracking/tracking-detail.html.twig', ['page' => $page]);
}
#[Route('/ai-stream/account/{customerId}', name: 'frontend.ai-stream.detail', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function aistreamDetail(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
$customerId = $request->attributes->get('customerId');
/** @var CustomerEntity $customer */
$customer = $this->customerEntity->search(new Criteria([$customerId]), $context->getContext())->first();
$dataStream = [];
$topProducts = [];
if (!empty($customer?->getId())) {
$topProducts = $this->homepageService->getTopProducts($context);
$dataStream = $this->postService->fetchStream($customerId, $context, "uploaded");
$customerStats = $this->postService->fetchCustomerStats($customerId, $context);
$customer->addArrayExtension('aiStreamStats', $customerStats);
}
return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/ai-stream-detail/index.html.twig', [
'page' => $page,
'dataStream' => $dataStream,
'topProducts' => $topProducts,
'aiStreamAccount' => $customer,
'dashboardData' => $this->customerDataService->getBoughtProducts($context)
]);
}
#[Route(path: '/lucky-wheel', name: 'frontend.credit.lucky-wheel', defaults: ['_loginRequired' => false], methods: ['GET'])]
public function luckyWheel(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
return $this->renderStorefront('@Storefront/storefront/page/custom/lucky-wheel/index.html.twig', ['page' => $page]);
}
#[Route(path: '/rewards', name: 'frontend.rewards', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function rewardsPage(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
$customer = $context->getCustomer();
$prizes = $this->luckyWheelApiService->queryUserPrize(null, null, $customer->getId(), $context)->getElements();
foreach ($prizes as $key => &$prize) {
if ($prize->prize == null) {
$prize->prize = [];
}
}
return $this->renderStorefront('@Storefront/storefront/page/custom/rewards/index.html.twig', ['page' => $page, 'dashboardData' => $this->customerDataService->getBoughtProducts($context), 'prizes' => $prizes]);
}
#[Route(path: '/rewards/add-to-cart', name: 'frontend.addToCart', defaults: ['_loginRequired' => true, 'XmlHttpRequest' => true], methods: ['POST'])]
public function addToCart(Request $request, SalesChannelContext $context): Response
{
$customer = $context->getCustomer();
$prizeId = $request->request->get('prizeId');
$prize = $this->luckyWheelApiService->queryUserPrizeById($prizeId, $customer->getId(), $context);
if (!$prize) {
return new JsonResponse([
'success' => false,
'message' => 'Prize not found',
], 404);
}
if ($prize->getCustomer()->getId() != $customer->getId()) {
return new JsonResponse([
'success' => false,
'message' => 'Not authorized',
], 403);
}
if ($prize->getIsClaimed()) {
return new JsonResponse([
'success' => false,
'message' => 'Prize not claimed',
], 400);
}
if ($prize->getTurn()->getReason() == 'Invalid') {
return new JsonResponse([
'success' => false,
'message' => 'Invalid prize',
], 400);
}
$this->rewardShipLaterService->createRewardShipLater([
'customerId' => $customer->getId(),
'fromOrderId' => null,
'referencedId' => $prize->getPrize()->getProduct()->getId(),
'payload' => [
'productId' => $prize->getPrize()->getProduct()->getId(),
'isShipWithNextOrder' => false,
],
'quantity' => 1,
'isShipped' => false,
'type' => RewardShipLaterType::LuckyWheel->value
]);
$this->luckyWheelApiService->updateIsClaimedById($prize->getId(), $context);
return new JsonResponse([
'success' => true,
'message' => 'Added to cart successfully'
]);
}
#[Route(path: '/policy', name: 'frontend.app.policy')]
public function policy(Request $request, SalesChannelContext $context): Response
{
return $this->renderStorefront('@Storefront/storefront/page/condition/index.html.twig');
}
/**
* @HttpCache()
* @Route("/detail/{productId}", name="frontend.detail.page", methods={"GET"})
*/
public function productDetail(SalesChannelContext $context, Request $request): Response
{
$page = $this->productPageLoader->load($request, $context);
$this->hook(new ProductPageLoadedHook($page, $context));
$productId = $request->attributes->get('productId');
$customerId = $context->getCustomer()?->getId();
// Check if the customer has an active subscription for this product
$hasActiveSubscription = $this->subscriptionService->hasActiveSubscription(
$customerId,
$productId,
$context
);
$product = $page->getProduct();
if (isset($product->getCustomFields()['productIsBuyWithCredit']) && $product->getCustomFields()['productIsBuyWithCredit'] === true) {
$url = $this->router->generate('frontend.shop4free');
return $this->redirect($url);
}
$outOfStock = false;
if ($product->getAvailableStock() > 0) {
$customFields = $product->getCustomFields();
if (isset($customFields['isProductBundle']) && $customFields['isProductBundle'] == true) {
$outOfStock = $this->isOutOfStock($product->getId(), $context);
}
} else {
$outOfStock = true;
}
// $this->hook(new ProductPageLoadedHook($page, $context));
return $this->renderStorefront('@Storefront/storefront/page/product-detail/index.html.twig', ['page' => $page, 'ourOfStock' => $outOfStock, 'hasActiveSubscription' => $hasActiveSubscription]);
}
private function isOutOfStock(string $productId, SalesChannelContext $context): bool
{
// Create criteria to search for bundle items by product ID
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('productId', $productId));
$criteria->addAssociation('relatedProduct');
$criteria->addAssociation('relatedProduct.swagDynamicAccessRules');
$criteria->addAssociation('product.swagDynamicAccessRules');
$criteria->addAssociation('product.swagDynamicAccessRules');
// Search for bundle items using the repository
$bundleItems = $this->productBundleItemsRepository->search($criteria, $context->getContext());
// If there are no bundle items, the product is not a bundle
if ($bundleItems->count() === 0) {
return true;
}
// Check if any of the bundle items are out of stock
$outOfStock = true;
foreach ($bundleItems as $bundleItem) {
// Get the related product and quantity
$relatedProduct = $bundleItem->getRelatedProduct();
// If the related product doesn't exist or doesn't have enough stock, the bundle is out of stock
if ($relatedProduct && $relatedProduct->getAvailableStock() > 0) {
$rulesProduct = $bundleItem->getProduct()->get('swagDynamicAccessRules');
$rulesRelatedProduct = $bundleItem->getRelatedProduct()->get('swagDynamicAccessRules');
if (count($rulesRelatedProduct) > 0 && count($rulesProduct) > 0) {
foreach ($rulesProduct as $ruleProduct) {
foreach ($rulesRelatedProduct as $ruleRelatedProduct) {
if ($ruleRelatedProduct->getId() === $ruleProduct->getId()) {
$outOfStock = false;
}
}
}
} elseif (count($rulesRelatedProduct) == 0 && count($rulesProduct) == 0) {
$outOfStock = false;
} else {
return true;
}
} else {
return true;
}
}
return $outOfStock;
}
/**
* @HttpCache()
* @Route("/ai-stream/{id}", name="frontend.ai.stream.detail.page", methods={"GET"})
*/
#[Route('/ai-stream/{id}', name: 'frontend.ai.stream.detail.page', defaults: ['_loginRequired' => false], methods: ['GET'])]
public function aiStreamDetailHomePage(Request $request, SalesChannelContext $context)
{
$page = $this->genericLoader->load($request, $context);
$customer = $context->getCustomer();
$experienceId = $request->attributes->get('id');
$dataStream = $this->postService->fetchStream($context->getCustomer()?->getId(), $context, "tags", null, $experienceId);
$targetObject = null;
$index = -1;
if ($experienceId !== null) {
foreach ($dataStream as $key => $value) {
if ($value->id == $experienceId) {
$index = $key;
$targetObject = $value;
break;
}
}
if ($targetObject !== null && $index !== -1) {
unset($dataStream[$index]);
$dataStream = array_values($dataStream);
array_unshift($dataStream, $targetObject);
}
}
return $this->renderStorefront('@Storefront/storefront/page/custom/ai-stream/home/index.html.twig', ['page' => $page, 'customer' => $customer, 'dataStream' => $dataStream, 'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
}
// #[Route('/current-deals', name: 'frontend.current-deals')]
/**
* @HttpCache()
* @Route("/current-deals", name="frontend.current-deals", methods={"GET"})
*/
public function currentDeals(Request $request, SalesChannelContext $context): Response
{
$arr_productId = [];
$page = $this->genericLoader->load($request, $context);
$categories = $this->homepageService->getCategories($context);
return $this->renderStorefront('@Storefront/storefront/page/content/current-deals.html.twig', ['page' => $page, 'repertusProductReservationOptions' => $arr_productId, 'categories' => $categories]);
}
#[Route(path: '/data-stream', name: 'frontend.data-stream', defaults: ['XmlHttpRequest' => true])]
public function getDataStream(Request $request, SalesChannelContext $context)
{
$page = $this->genericLoader->load($request, $context);
$productId = $request->request->get('productId');
$type = $request->request->get('type');
if ($type == 'details') {
$dataStream = $this->postService->fetchStream($context->getCustomer()?->getId(), $context, "tags", $productId, null, 1, 10);
return $this->renderStorefront('@Storefront/storefront/page/custom/list-data-stream/list-data-stream-detail.html.twig', ['page' => $page, 'dataStream' => $dataStream]);
} else {
$dataStream = $this->postService->fetchStream($context->getCustomer()?->getId(), $context, "tags", null, null, 1, 10);
}
return $this->renderStorefront('@Storefront/storefront/page/custom/list-data-stream/list-data-stream.html.twig', ['page' => $page, 'dataStream' => $dataStream]);
}
// #[Route('/invite-friends', name: 'frontend.invite-friends', defaults: ['_loginRequired' => true], methods: ['GET'])]
// public function inviteFriends(Request $request, SalesChannelContext $context): Response
// {
// $page = $this->genericLoader->load($request, $context);
// return $this->renderStorefront('@Storefront/storefront/page/custom/invite-friends/index.html.twig', ['page' => $page]);
// }
// #[Route('/navigation/{navigationId}', name: 'frontend.navigation.page')]
// public function index(SalesChannelContext $context, Request $request): Response
// {
// $page = $this->genericLoader->load($request, $context);
// $this->hook(new NavigationPageLoadedHook($page, $context));
//
// return $this->renderStorefront('@Storefront/storefront/page/content/index.html.twig', ['page' => $page]);
// }
#[Route('/app/register/create-account', name: 'frontend.econsor.app.storefront.register.create-account')]
public function register(Request $request, SalesChannelContext $context): Response
{
if ($request->isMethod('POST')) {
if (empty($request->request->get('userName')) && !empty($request->request->get('email'))) {
$request->request->set('userName', $request->request->get('email'));
}
if (!empty($request->request->get('countryId'))) {
$countryCriteria = new Criteria();
$countryCriteria->addFilter(new EqualsFilter('id', strtoupper($request->request->get('countryId'))));;
$country = $this->countryRepository->search($countryCriteria, $context->getContext())->first();
if (!empty($country)) {
$request->request->set('countryCode', $country->getIso());
}
} else {
if (!empty($request->request->get('countryCode'))) {
$countryCriteria = new Criteria();
$countryCriteria->addFilter(new EqualsFilter('iso', strtoupper($request->request->get('countryCode'))));
$country = $this->countryRepository->search($countryCriteria, $context->getContext())->first();
if (!empty($country)) {
$request->request->set('countryId', $country->getId());
}
}
}
$validation = $this->validateCustomer($request, $context);
if ($validation instanceof CustomerEntity) {
if (array_key_exists('tag', $request->request->all())) {
$this->contextResolver->resolve($request);
$context = $request->attributes->get('sw-sales-channel-context');
$this->setInterests($context, $request->request->all()['tag']);
$this->setCustomFields($context, [
'customerCountryCode' => $request->request->get('countryCode'),
'customerLanguageCode' => $request->request->get('languageCode'),
'customerCountryId' => $request->request->get('countryId'),
'customerLanguageId' => $request->request->get('languageId'),
]);
}
// if (in_array($request->request->get('countryCode'), ['vn'])) {
// return $this->redirectToRoute('frontend.account.progress');
// }
$url = $this->router->generate('frontend.home.page');
return $this->redirect($url);
}
return $validation;
} else {
$url = $this->router->generate('frontend.econsor.app.storefront.register.form');
return $this->redirect($url);
}
}
private function validateCustomer(Request $request, SalesChannelContext $context): RedirectResponse|CustomerEntity
{
$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('tag', new NotBlank());
if ($request->getSession()->has('affiliateCode')) {
$request->request->set('affiliateCode', $request->getSession()->get('affiliateCode'));
}
$constraints = $this->getAffiliateCodeConstraints($context->getContext());
foreach ($constraints as $constraint) {
$definition->add('affiliateCode', $constraint);
}
$validation = $this->dataValidator->getViolations($request->request->all(), $definition);
//check for validation errors.
if ($validation->count() > 0) {
$violations = ['violations' => []];
foreach ($validation as $violation) {
$violations['violations'][$violation->getPropertyPath()] = $violation->getMessage();
}
$request->request->add($violations);
$url = $this->router->generate('frontend.econsor.app.storefront.register.form', $request->request->all());
return $this->redirect($url);
}
//create account
$billingAddress = (array)$request->request->get('billingAddress');
$billingAddress['phoneNumber'] = $request->request->get('phoneNumber');
$billingAddress['countryId'] = $request->request->get('countryId');
$request->request->set('billingAddress', $billingAddress);
$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']);
}
$requestDataBag = new RequestDataBag($request->request->all());
$this->registerController->register($request, $requestDataBag, $context);
$this->contextResolver->resolve($request);
return $request->attributes->get('sw-sales-channel-context')->getCustomer();
}
/**
* @param Context $context
* @return array
*/
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, array $customFields)
{
$customer = $context->getCustomer();
$customerCustomFields = $customer?->getCustomFields() ?? [];
foreach ($customFields as $key => $value) {
$customerCustomFields[$key] = $value;
}
$this->customerEntity->update(
[
[
'id' => $customer->getId(),
'customFields' => $customerCustomFields,
]
],
$context->getContext()
);
}
private function setInterests(SalesChannelContext $context, array $tags)
{
$customer = $context->getCustomer();
$tagsTemp = [];
foreach ($tags as $tag => $ignored) {
array_push($tagsTemp, ['id' => $tag]);
}
$this->customerEntity->update(
[
[
'id' => $customer->getId(),
'customerInterests' => [
'id' => $this->getExtensionIdByCustomer($customer),
'customerId' => $customer->getId(),
'tags' => $tagsTemp
]
]
],
$context->getContext()
);
}
private function getExtensionIdByCustomer(CustomerEntity $customer): string
{
if (
$customer->getExtension('customerInterests') && isset(
$customer->getExtension('customerInterests')->all()['id']
)
) {
$extensionId = $customer->getExtension('customerInterests')->all()['id'];
} else {
$extensionId = Uuid::randomHex();
}
return $extensionId;
}
#[Route('/app/intro', name: 'frontend.econsor.app.storefront.intro')]
public function intro(Request $request, SalesChannelContext $context): Response
{
if ($context->getCustomer() == null) {
$url = $this->router->generate('frontend.econsor.app.storefront.login');
return $this->redirect($url);
}
$page = $this->registrationAppPageLoader->load($request, $context);
return $this->renderStorefront('@Storefront/storefront/page/custom/intro/index.html.twig', [
'page' => $page,
'salesChannelContext' => $context,
'request' => $request,
'controllerName' => 'EconsorAppStorefrontController',
'controllerAction' => 'intro'
]);
}
#[Route('/termCondition', name: 'frontend.term.condition')]
public function termCondition(Request $request, SalesChannelContext $context): Response
{
$page = $this->registrationAppPageLoader->load($request, $context);
return $this->renderStorefront('@Storefront/storefront/page/condition/index.html.twig', ['page' => $page]);
}
#[Route('/app/wishlist', name: 'frontend.wishlist.modal.note')]
public function wishlist(Request $request, SalesChannelContext $context): Response
{
return new Response("WISHLIST");
}
#[Route('/upload/create', name: 'frontend.econsor.app.storefront.upload.create', methods: ['GET'], defaults: ['_loginRequired' => true])]
public function create(Request $request, SalesChannelContext $context): Response
{
$page = $this->profilePageLoader->load($request, $context);
// $this->hook(new AccountProfilePageLoadedHook($page, $context));
return $this->renderStorefront('@Storefront/storefront/page/custom/uploads/index.html.twig', ['page' => $page]);
}
#[Route('/account/profile', name: 'frontend.account.profile.page', methods: ['GET'], defaults: ['_loginRequired' => true])]
public function profileOverview(Request $request, SalesChannelContext $context, CustomerEntity $customer): Response
{
$page = $this->profilePageLoader->load($request, $context);
$request->attributes->set('addressId', $customer->getDefaultBillingAddressId());
$country = $this->addressDetailPageLoader->load($request, $context, $customer);
$address = $this->homepageService->getCustomerAddress($context);
$pendingCustomerBusinessName = $this->customerBusinessNameService->getPendingCustomerBusinessName($customer, $context->getContext());
$customer = $context->getCustomer();
// $this->hook(new AccountProfilePageLoadedHook($page, $context));
// $this->hook(new AddressDetailPageLoadedHook($country, $context));
return $this->renderStorefront('@Storefront/storefront/page/account/profile/index.html.twig', [
'page' => $page,
'address' => $address,
'customer' => $customer,
'country' => $country,
'pendingCustomerBusinessName' => $pendingCustomerBusinessName,
'passwordFormViolation' => $request->get('passwordFormViolation'),
'emailFormViolation' => $request->get('emailFormViolation'),
]);
}
#[Route('/account/profile', name: 'frontend.account.profile.save', methods: ['POST'], defaults: ['_loginRequired' => true])]
public function saveProfile(Request $request, RequestDataBag $data, SalesChannelContext $context, CustomerEntity $customer): Response
{
$updateType = $data->get('updateType');
if (!empty($updateType)) {
switch ($updateType) {
case 'interest':
{
$this->setInterests($context, $request->request->all()['tag']);
return $this->redirectToRoute('frontend.account.home.page');
}
break;
case 'profile':
{
$businessNameUpdateId = $data->get('businessNameUpdateId');
$currentBusinessName = $data->get('currentBusinessName');
$businessName = $data->get('businessName');
$birthDay = !empty($data->get('birthday')) ? date_parse_from_format('Y-m-d', $data->get('birthday')) : null;
$dataProfile = new RequestDataBag([
'firstName' => $data->get('firstName'),
'lastName' => $data->get('lastName'),
'birthdayDay' => $birthDay['day'],
'birthdayMonth' => $birthDay['month'],
'birthdayYear' => $birthDay['year'],
]);
$customerData = [
'id' => $customer->getId(),
'email' => $data->get('email'),
'customFields' => [
'phoneNumber' => $data->get('phoneNumber'),
'countryCode' => $data->get('countryCode'),
],
];
if (!empty($data->get('email')) && $data->get('email') != $customer->getEmail()) {
$definition = new DataValidationDefinition('customer.email.update');
$definition->add('email', new CustomerEmailUnique(['context' => $context->getContext(), 'salesChannelContext' => $context]));
$validations = $this->dataValidator->getViolations([
'email' => $data->get('email'),
], $definition);
//check for validation errors.
$isValid = true;
if ($validations->count() > 0) {
foreach ($validations as $validation) {
if ($validation->getPropertyPath() == '/email') {
$this->addFlash('danger', $validation->getMessage());
$isValid = false;
}
}
}
if ($isValid) {
$customerData['email'] = $data->get('email');
}
}
$this->changeCustomerProfileRoute->change($dataProfile, $context, $customer);
$this->customerEntity->update([$customerData], $context->getContext());
if ($businessName != null && (empty($currentBusinessName) || $currentBusinessName != $businessName || !empty($businessNameUpdateId))) {
$this->customerBusinessNameService->store($customer, $businessName, $context->getContext());
}
}
break;
case 'avatar':
{
$avatarFile = $request->files->get('avatar');
if (!empty($avatarFile)) {
$media = $this->homepageService->customerAvatarUpload($avatarFile, $customer, $context);
if (!empty($media?->getId())) {
$customerData = [
'id' => $customer->getId(),
'customFields' => [
'avatar' => $media?->getId()
],
];
$this->customerEntity->update([$customerData], $context->getContext());
}
}
}
break;
case 'newsletter':
{
$dataNewsletter = new RequestDataBag();
$dataNewsletter->set('email', $customer->getEmail());
$dataNewsletter->set('zipCode', ($customer->getDefaultShippingAddress() ? $customer->getDefaultShippingAddress()->getZipCode() : null));
$dataNewsletter->set('title', $customer->getTitle());
$dataNewsletter->set('storefrontUrl', $request->attributes->get(RequestTransformer::STOREFRONT_URL));
$dataNewsletter->set('city', ($customer->getDefaultShippingAddress() ? $customer->getDefaultShippingAddress()->getCity() : null));
$dataNewsletter->set('street', ($customer->getDefaultShippingAddress() ? $customer->getDefaultShippingAddress()->getStreet() : null));
if ($data->get('newsletter') == 'on') {
$dataNewsletter->set('option', 'subscribe');
$this->newsletterSubscribeRoute->subscribe(
$dataNewsletter,
$context,
false
);
$this->setNewsletterFlag($customer, true, $context);
} else if ($this->getNewsletterRecipient($dataNewsletter->get('email'), $context) !== null) {
$dataNewsletter->set('option', 'unsubscribe');
$this->newsletterUnsubscribeRoute->unsubscribe(
$dataNewsletter,
$context
);
$this->setNewsletterFlag($customer, false, $context);
}
}
break;
}
}
return $this->redirectToRoute('frontend.account.profile.page');
}
private function setNewsletterFlag(CustomerEntity $customer, bool $newsletter, SalesChannelContext $context): void
{
$customer->setNewsletter($newsletter);
$this->customerEntity->update(
[
['id' => $customer->getId(), 'newsletter' => $newsletter],
],
$context->getContext()
);
}
private function getNewsletterRecipient(string $email, SalesChannelContext $context): ?NewsletterRecipientEntity
{
$criteria = new Criteria();
$criteria->addFilter(
new MultiFilter(MultiFilter::CONNECTION_AND),
new EqualsFilter('email', $email),
new EqualsFilter('salesChannelId', $context->getSalesChannel()->getId())
);
$criteria->addAssociation('salutation');
$criteria->setLimit(1);
return $this->newsletterRecipientRepository
->search($criteria, $context->getContext())
->first();
}
#[Route('/progress', name: 'frontend.account.progress', methods: ['GET'], defaults: ["_loginRequired" => true])]
public function progress(Request $request, SalesChannelContext $context, CustomerEntity $customer): Response
{
$page = $this->overviewPageLoader->load($request, $context, $customer);
// $this->hook(new AccountOverviewPageLoadedHook($page, $context));
$customerCustomFields = $customer->getCustomFields();
if (!empty($customerCustomFields['customerCountryCode']) && in_array(strtolower($customerCustomFields['customerCountryCode']), ['vn'])) {
$invites = $this->econsorAccountService->getInvites($customer, $context);
$creditBalance = $this->econsorAccountService->getCustomerCreditBalance($customer, $context);
$view = '@Storefront/storefront/page/progress/index-vn.html.twig';
// if ($customerCustomFields['customerCountryCode'] == 'hk') {
// $view = '@Storefront/storefront/page/progress/index-en.html.twig';
// }
return $this->renderStorefront($view, [
'page' => $page,
'invites' => $invites,
'creditBalance' => $creditBalance,
'customer' => $customer,
]);
}
$order = $this->orderPageLoader->load($request, $context);
// $this->hook(new AccountOrderPageLoadedHook($order, $context));
$activeDeals = $this->econsorAccountService->getActiveDeals($customer, $context);
$credits = $this->econsorAccountService->getCredits($customer, $context);
$redeemedItems = $this->econsorAccountService->getRedeemedItems($customer, $context);
$homepage = $this->genericLoader->load($request, $context);
$test = $homepage->getCmsPage()->getSections()->getBlocks()->getSlots('content')->getElements();
foreach ($test as $key => $value) {
if ($value->getType() == 'product-listing') {
$arr = $value->getData();
}
}
$listing = $arr->getListing()->getElements();
return $this->renderStorefront('@Storefront/storefront/page/progress/index.html.twig', ['page' => $page, 'order' => $order, 'activeDeals' => $activeDeals, 'credits' => $credits, 'redeemedItems' => $redeemedItems, 'listing' => $listing, 'customer' => $customer]);
}
#[Route('/shop4free', name: 'frontend.shop4free')]
public function shop4freeCustom(Request $request, SalesChannelContext $context, Context $sale): Response
{
$page = $this->genericLoader->load($request, $context);
$products = $this->homepageService->loadProductsWithCreditOption($context);
$customer = $this->homepageService->getCustomerCredit($context, $sale);
return $this->renderStorefront('@Storefront/storefront/page/shop4free/index.html.twig', ['page' => $page, 'products' => $products, 'customer' => $customer]);
}
#[Route('/contact', name: 'frontend.contact')]
public function contactCustom(Request $request, SalesChannelContext $context, Context $sale): Response
{
$page = $this->genericLoader->load($request, $context);
$products = $this->homepageService->loadProductsWithCreditOption($context);
$customer = $this->homepageService->getCustomerCredit($context, $sale);
return $this->renderStorefront('@Storefront/storefront/page/custom/contact/index.html.twig', ['page' => $page, 'products' => $products, 'customer' => $customer]);
}
// #[Route('/account/recover', name: 'frontend.account.recover')]
// public function accountRecover(Request $request, SalesChannelContext $context): Response
// {
// $page = $this->genericLoader->load($request, $context);
//
// $this->hook(new NavigationPageLoadedHook($page, $context));
// return $this->renderStorefront('@Storefront/storefront/page/account/profile/recover-password.html.twig');
// }
#[Route('/progress/credits/{creditId}', name: 'frontend.account.progress.credits.detail', methods: ['GET'], defaults: ["_loginRequired" => true])]
public function creditDetail(string $creditId, Request $request, SalesChannelContext $context, CustomerEntity $customer): Response
{
$page = $this->overviewPageLoader->load($request, $context, $customer);
// $this->hook(new AccountOverviewPageLoadedHook($page, $context));
$order = $this->orderPageLoader->load($request, $context);
// $this->hook(new AccountOrderPageLoadedHook($order, $context));
$credit = $this->econsorAccountService->getCreditDetail($creditId, $context);
return $this->renderStorefront('@Storefront/storefront/page/progress/credits/detail/index.html.twig', ['page' => $page, 'credit' => $credit]);
}
#[Route('/progress/redeemed-items/{itemId}', name: 'frontend.account.progress.redeemed-items.detail', methods: ['GET'], defaults: ["_loginRequired" => true])]
public function redeemedItemDetail(string $itemId, Request $request, SalesChannelContext $context, CustomerEntity $customer): Response
{
$page = $this->overviewPageLoader->load($request, $context, $customer);
// $this->hook(new AccountOverviewPageLoadedHook($page, $context));
$order = $this->orderPageLoader->load($request, $context);
// $this->hook(new AccountOrderPageLoadedHook($order, $context));
$item = $this->econsorAccountService->getRedeemedItemDetail($itemId, $context);
return $this->renderStorefront('@Storefront/storefront/page/progress/redeemded-items/detail/index.html.twig', ['page' => $page, 'item' => $item]);
}
// #[Route('/account/friend-invite', name: 'frontend.friend.invite', defaults: ["XmlHttpRequest" => true], methods: ['POST', 'GET'])]
// public function reservationDeal(Request $request, SalesChannelContext $context): Response
// {
// if ($context->getCustomer() == null) {
// $url = $this->router->generate('frontend.econsor.app.storefront.login');
// return $this->redirect($url);
// }
// $violations = [];
// $emails = $request->request->has('email') ? array_map('trim', array_filter($request->request->get('email'))) : [];
// $schemeAndHost = $request->getSchemeAndHttpHost();
// // Parse the scheme and host to get the hostname
// $parsedUrl = parse_url($schemeAndHost);
// $url = $parsedUrl['scheme'] . '://' . $parsedUrl['host'];
// if ($request->isMethod('POST')) {
// $validator = Validation::createValidator();
// $constraintEmail = new Email([], 'The input {{ value }} is not an valid e-mail address.');
// $constraintNotBlank = new NotBlank([], 'The e-mail field should not be blank.');
// $constraintUnique = new Duplications();
// $constraintNotEqualTo = new NotEqualTo($context->getCustomer()->getEmail(), null, "The e-mail should be of your friends.");
// foreach ($emails as $id => $email) {
// if (!array_key_exists($id, $violations)) {
// $violations[$id] = [];
// }
// $violations[$id][] = $validator->validate($email, [$constraintEmail, $constraintNotBlank, $constraintUnique, $constraintNotEqualTo]);
// }
// $success = count($emails) > 0;
// foreach ($violations as $violation) {
// if (count($violation[0]) > 0) {
// $success = false;
// }
// }
// if ($success && $this->discountCodeGenerator->canGenerate($context, DiscountCodeGenerator::SHARE_WITH_FRIENDS_CODE)) {
// $result = $this->discountCodeGenerator->generateCode($context, DiscountCodeGenerator::SHARE_WITH_FRIENDS_CODE, $emails, $url);
// if (array_key_exists('error', $result)) {
// //add flashes
// if (array_key_exists('flash', $result['error'])) {
// // $this->addFlash($result['error']['flash']['type'], $this->trans($result['error']['flash']['message']));
// }
// return $this->redirectToRoute('frontend.home.page');
// } else if (array_key_exists('success', $result)) {
// $customer = $context->getCustomer();
// $currentEmailsInvited = !empty($customer->getCustomFields()['emailsInvited']) ? (is_array($customer->getCustomFields()['emailsInvited']) ? $customer->getCustomFields()['emailsInvited'] : json_decode($customer->getCustomFields()['emailsInvited'])) : [];
// $currentEmailListInvited = !empty($customer->getCustomFields()['emailListInvited']) ? (is_array($customer->getCustomFields()['emailListInvited']) ? $customer->getCustomFields()['emailListInvited'] : json_decode($customer->getCustomFields()['emailListInvited'])) : [];
// $emailsListInvited = [];
// $emailsListInvite = [];
// foreach ($emails as $email) {
// if (!in_array($email, $currentEmailsInvited)) {
// $currentEmailListInvited[] = [
// 'email' => $email,
// 'inviteDate' => (new \DateTime())->getTimestamp(),
// ];
// $criteria = new Criteria();
// $criteria->addFilter(new EqualsFilter('email', $email));
// $this->inviteFriendHistory->create([
// [
// 'email' => $email,
// 'customerId' => $context->getCustomerId(),
// ]
// ], $context->getContext());
// $emailsListInvite[] = $email;
// } else {
// $emailsListInvited[] = $email;
// }
// }
// $uniqueEmails = array_unique(array_merge($currentEmailsInvited, $emailsListInvite));
// $this->customerEntity->update(
// [
// [
// 'id' => $context->getCustomerId(),
// 'customFields' => [
// 'emailsInvited' => $uniqueEmails,
// 'emailListInvited' => $currentEmailListInvited,
// ],
// ]
// ],
// $context->getContext()
// );
// $wheelCriteria = new Criteria();
// $wheelCriteria->addFilter(new EqualsFilter('customerId', $context->getCustomerId()));
// $wheelCriteria->addFilter(new EqualsFilter('reason', '10 emails invited'));
// if (count($uniqueEmails) >= $this->luckyWheelTurnRepository->search($wheelCriteria, $context->getContext())->count() * 10) {
// $startOfWeek = (new \DateTime('monday this week'))->setTime(0, 0, 0);
// $endOfWeek = (new \DateTime('sunday this week'))->setTime(23, 59, 59);
// $result = $this->luckyWheelTurnRepository->search($wheelCriteria
// ->addFilter(new RangeFilter('createdAt', [
// RangeFilter::GTE => $startOfWeek->format('Y-m-d H:i:s'),
// RangeFilter::LTE => $endOfWeek->format('Y-m-d H:i:s')
// ])), $context->getContext());
// if ($result->count() == 0) {
// $this->luckyWheelTurnRepository->create([
// [
// 'customerId' => $context->getCustomerId(),
// 'amountTotal' => 2,
// 'amountRemain' => 2,
// 'type' => 'awarded',
// 'reason' => '10 emails invited',
// ]
// ], $context->getContext());
// }
// }
// // $records = $this->rewardService->getRewardRecordFromCustomer(
// // $context->getCustomer()->getId(),
// // "10 emails invited"
// // );
// // if ($records->count() === 0) {
// // $this->rewardService->createRewardPoint(
// // $context->getCustomer()->getId(),
// // null,
// // 1000,
// // RewardPointType::AwardedPoint,
// // null,
// // null,
// // null,
// // "10 emails invited"
// // );
// // }
// // $this->addFlash('success', $this->trans('app.onboarding.inviteFriendsModal.emailSent'));
// return $this->renderStorefront('@Storefront/storefront/component/account/friend-invite.html.twig', [
// "page" => $this->registrationAppPageLoader->load($request, $context),
// 'emailGenerated' => true,
// 'emailsInvited' => $uniqueEmails,
// 'emailsListInvited' => $emailsListInvited
// ]);
// }
// }
// }
// return $this->renderStorefront('@Storefront/storefront/component/account/friend-invite.html.twig', [
// 'controllerName' => 'ReservationDealController',
// 'controllerAction' => 'reservationDeal',
// 'violations' => $violations,
// 'emails' => $emails,
// "page" => $this->registrationAppPageLoader->load($request, $context)
// ]);
// }
/**
* @Route("/account/address/default-{type}/{addressId}", name="frontend.account.address.set-default-address", methods={"POST"}, defaults={"_loginRequired"=true})
*/
public function switchDefaultAddress(Request $request, string $type, string $addressId, SalesChannelContext $context, CustomerEntity $customer): mixed
{
if (!Uuid::isValid($addressId)) {
throw new InvalidUuidException($addressId);
}
$success = true;
try {
if ($type === self::ADDRESS_TYPE_SHIPPING) {
$this->accountService->setDefaultShippingAddress($addressId, $context, $customer);
} elseif ($type === self::ADDRESS_TYPE_BILLING) {
$this->accountService->setDefaultBillingAddress($addressId, $context, $customer);
} else {
$success = false;
}
} catch (AddressNotFoundException $exception) {
$success = false;
}
if ($request->get('redirectTo') || $request->get('redirectTo') === '') {
return $this->createActionResponse($request);
}
return new RedirectResponse(
$this->generateUrl('frontend.account.address.page', ['changedDefaultAddress' => $success])
);
}
/**
* @Route("/widgets/account/address-book", name="frontend.account.addressbook", options={"seo"=true}, methods={"POST"}, defaults={"XmlHttpRequest"=true, "_loginRequired"=true, "_loginRequiredAllowGuest"=true})
*/
public function addressBook(Request $request, RequestDataBag $dataBag, SalesChannelContext $context, CustomerEntity $customer): Response
{
$viewData = new AddressEditorModalStruct();
$params = [];
try {
$this->handleChangeableAddresses($viewData, $dataBag, $context, $customer);
$this->handleAddressCreation($viewData, $dataBag, $context, $customer);
$this->handleAddressSelection($viewData, $dataBag, $context, $customer);
$page = $this->addressListingPageLoader->load($request, $context, $customer);
// $this->hook(new AddressBookWidgetLoadedHook($page, $context));
$viewData->setPage($page);
if (Feature::isActive('FEATURE_NEXT_15957')) {
$this->handleCustomerVatIds($dataBag, $context, $customer);
}
} catch (ConstraintViolationException $formViolations) {
$params['formViolations'] = $formViolations;
$params['postedData'] = $dataBag->get('address');
} catch (\Exception $exception) {
$viewData->setSuccess(false);
$viewData->setMessages([
'type' => self::DANGER,
'text' => $this->trans('error.message-default'),
]);
}
if ($request->get('redirectTo') || $request->get('forwardTo')) {
return $this->createActionResponse($request);
}
$params = array_merge($params, $viewData->getVars());
$response = $this->renderStorefront(
'@Storefront/storefront/component/address/address-editor-modal.html.twig',
$params
);
$response->headers->set('x-robots-tag', 'noindex');
return $response;
}
private function handleAddressCreation(
AddressEditorModalStruct $viewData,
RequestDataBag $dataBag,
SalesChannelContext $context,
CustomerEntity $customer
): void
{
/** @var DataBag|null $addressData */
$addressData = $dataBag->get('address');
if ($addressData === null) {
return;
}
$response = $this->updateAddressRoute->upsert(
$addressData->get('id'),
$addressData->toRequestDataBag(),
$context,
$customer
);
$addressId = $response->getAddress()->getId();
$addressType = null;
if ($viewData->isChangeBilling()) {
$addressType = self::ADDRESS_TYPE_BILLING;
} elseif ($viewData->isChangeShipping()) {
$addressType = self::ADDRESS_TYPE_SHIPPING;
}
// prepare data to set newly created address as customers default
if ($addressType) {
$dataBag->set('selectAddress', new RequestDataBag([
'id' => $addressId,
'type' => $addressType,
'isChangeBilling' => $viewData->isChangeBilling(),
'isChangeShipping' => $viewData->isChangeShipping(),
]));
}
$viewData->setAddressId($addressId);
$viewData->setSuccess(true);
$viewData->setMessages(['type' => 'success', 'text' => $this->trans('account.addressSaved')]);
}
private function handleChangeableAddresses(
AddressEditorModalStruct $viewData,
RequestDataBag $dataBag,
SalesChannelContext $context,
CustomerEntity $customer
): void
{
$changeableAddresses = $dataBag->get('changeableAddresses');
if ($changeableAddresses === null) {
return;
}
$viewData->setChangeShipping((bool)$changeableAddresses->get('changeShipping'));
$viewData->setChangeBilling((bool)$changeableAddresses->get('changeBilling'));
$addressId = $dataBag->get('id');
if (!$addressId) {
return;
}
$viewData->setAddress($this->getById($addressId, $context, $customer));
}
/**
* @throws CustomerNotLoggedInException
* @throws InvalidUuidException
*/
private function handleAddressSelection(
AddressEditorModalStruct $viewData,
RequestDataBag $dataBag,
SalesChannelContext $context,
CustomerEntity $customer
): void
{
$selectedAddress = $dataBag->get('selectAddress');
if ($selectedAddress === null) {
return;
}
$isChangeBilling = $selectedAddress->get('isChangeBilling');
$isChangeShipping = $selectedAddress->get('isChangeShipping');
$addressType = $selectedAddress->get('type');
$addressId = $selectedAddress->get('id');
if (!Uuid::isValid($addressId)) {
throw new InvalidUuidException($addressId);
}
$success = true;
try {
if ($isChangeShipping) {
$address = $this->getById($addressId, $context, $customer);
$customer->setDefaultShippingAddress($address);
$this->accountService->setDefaultShippingAddress($addressId, $context, $customer);
}
if ($isChangeBilling) {
$address = $this->getById($addressId, $context, $customer);
$customer->setDefaultBillingAddress($address);
$this->accountService->setDefaultBillingAddress($addressId, $context, $customer);
}
if (!$isChangeBilling && !$isChangeShipping) {
$success = false;
}
} catch (AddressNotFoundException $exception) {
$success = false;
}
// if ($success) {
// $this->addFlash(self::SUCCESS, $this->trans('account.addressDefaultChanged'));
// } else {
// $this->addFlash(self::DANGER, $this->trans('account.addressDefaultNotChanged'));
// }
$viewData->setSuccess($success);
}
private function getById(string $addressId, SalesChannelContext $context, CustomerEntity $customer): CustomerAddressEntity
{
if (!Uuid::isValid($addressId)) {
throw new InvalidUuidException($addressId);
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $addressId));
$criteria->addFilter(new EqualsFilter('customerId', $customer->getId()));
$address = $this->listAddressRoute->load($criteria, $context, $customer)->getAddressCollection()->get($addressId);
if (!$address) {
throw new AddressNotFoundException($addressId);
}
return $address;
}
private function handleCustomerVatIds(RequestDataBag $dataBag, SalesChannelContext $context, CustomerEntity $customer): void
{
if (!$dataBag->has('vatIds')) {
return;
}
$newVatIds = $dataBag->get('vatIds')->all();
$oldVatIds = $customer->getVatIds() ?? [];
if (!array_diff($newVatIds, $oldVatIds) && !array_diff($oldVatIds, $newVatIds)) {
return;
}
$dataCustomer = CustomerTransformer::transform($customer);
$dataCustomer['vatIds'] = $newVatIds;
$dataCustomer['accountType'] = $customer->getCompany() === null ? CustomerEntity::ACCOUNT_TYPE_PRIVATE : CustomerEntity::ACCOUNT_TYPE_BUSINESS;
$newDataBag = new RequestDataBag($dataCustomer);
$this->updateCustomerProfileRoute->change($newDataBag, $context, $customer);
}
/**
* @Route("/account/accept-cookie", name="frontend.account.accept-cookie", methods={"POST"}, defaults={"XmlHttpRequest"=true})
*/
public function saveAcceptCookie(Request $request, SalesChannelContext $context): mixed
{
$isAcceptCookie = $request->get('acceptCookie', false);
$ip = $request->getClientIp();
$devices = $request->headers->get('user-agent');
$acceptCookieId = $request->get('acceptCookieId', 0);
if ($customer = $context->getCustomer()) {
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('customerId', $customer->getId()));
} else {
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('acceptCookieId', $acceptCookieId));
$criteria->addFilter(new NotFilter(NotFilter::CONNECTION_AND, [
new EqualsFilter('acceptCookieId', 0)
]));
}
$customerAcceptCookie = $this->customerAcceptCookieRepository->search($criteria, $context->getContext())->first();
if (!$customerAcceptCookie) {
$dataCreate = [
'id' => Uuid::randomHex(),
'customerId' => null,
'acceptCookie' => $isAcceptCookie,
'acceptCookieIp' => $ip,
'acceptCookieId' => $acceptCookieId,
'acceptCookieDevice' => $devices,
'created_at' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT),
];
if ($customer) {
$dataCreate['customerId'] = $customer->getId();
$this->customerEntity->update([
[
'id' => $customer->getId(),
'customFields' => [
'customerAcceptCookie' => $isAcceptCookie
],
]
], $context->getContext());
}
$this->customerAcceptCookieRepository->create([
$dataCreate
], $context->getContext());
return new JsonResponse([
'success' => true,
'message' => 'Accept Cookie Saved Successfully!'
], 200);
}
return new JsonResponse([
'success' => true,
'message' => 'Cookies already accepted!'
], 200);
}
#[Route('/video-star-ranking', name: 'frontend.daily.ranking', methods: ['GET'])]
public function dailyRanking(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
$topThreeCustomer = $this->rankingService->getTopThreeCustomerByLike($request, $context);
$topVideoOfCurrentUser = $this->rankingService->getTopVideoOfCurrentUser($request, $context);
return $this->renderStorefront('@Storefront/storefront/page/custom/daily-ranking/index.html.twig', ['page' => $page, 'topThreeCustomer' => $topThreeCustomer, 'topVideoOfCurrentUser' => $topVideoOfCurrentUser]);
}
#[Route('/sharing-center', name: 'frontend.sharing.center', methods: ['GET'])]
public function sharingCenter(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
$customer = $context->getCustomer();
$dataStream = $this->postService->fetchStream($context->getCustomer()?->getId(), $context, "uploaded");
return $this->renderStorefront('@Storefront/storefront/page/custom/sharing-center/index.html.twig', ['page' => $page, 'customer' => $customer, 'dataStream' => $dataStream, 'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
}
#[Route('/short-link', name: 'frontend.short.link', defaults: ['XmlHttpRequest' => true], methods: ['POST'])]
public function renderShortLink(Request $request, SalesChannelContext $context)
{
$url = $this->aiStreamService->createShortLink($request, $context);
return new JsonResponse(['data' => $url]);
}
#[Route('/checkout/subscription/{id?}', name: 'frontend.checkout.subscription', defaults: ['XmlHttpRequest' => true, '_loginRequired' => true,], methods: ['GET'])]
public function subscriptionPage(Request $request, SalesChannelContext $context, RequestDataBag $dataBag, string $id = null): Response
{
$id = $id ?? $request->query->get('productId');
if ($id === null) {
return $this->redirectToRoute('frontend.home.page');
}
$criteria = new Criteria();
$criteria->addFilter(
new EqualsFilter('customerId', $context->getCustomer()->getId()),
new EqualsFilter('productId', $id),
new EqualsFilter('status', 'active')
);
$subscription = $this->subscriptionRepository->search($criteria, $context->getContext())->first();
if ($subscription) {
return $this->redirectToRoute('frontend.account.order-subscription', ['status' => 'error-create-order']);
}
$context->getCustomer()->setAddresses($this->customerAddressRepository->search((new Criteria())->addFilter(new EqualsFilter('customerId', $context->getCustomerId())), $context->getContext())->getEntities());
$criteria = new Criteria([$id]);
$criteria->addAssociation('cover');
$criteria->addAssociation('visibilities');
$product = $this->salesChannelProductRepository->search($criteria, $context)->first();
if (!$product || !$product->getCustomFields()['isBuyProductSubscription'] || empty($product->getCustomFields()['planId'])) {
throw new \RuntimeException('Product not found for the current Sales Channel!');
}
$productId = $product->getId();
$token = NuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME . "-" . $context->getToken();
$cart = $this->cartService->getCart($token, $context, NuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME);
if (!empty($cart->getLineItems())) {
foreach ($cart->getLineItems() as $item) {
$item->setRemovable(true);
$this->cartService->remove($cart, $item->getId(), $context);
}
}
$lineItem = new LineItem(
$productId,
LineItem::PRODUCT_LINE_ITEM_TYPE,
$productId,
);
$lineItem->setLabel($product->getName());
$lineItem->setPayloadValue('productId', $product->getId());
$lineItem->setPayloadValue('planId', $product->getCustomFields()['planId']);
$lineItem->setRemovable(true);
$this->cartService->add($cart, [$lineItem], $context);
// $cart = $this->cartService->createNew(NuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME . "-" . $context->getToken(), NuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME . "-" . $context->getToken());
// $cart = $this->cartService->add($cart, [$lineItem], $context);
$cart = $this->cartService->recalculate($cart, $context);
$page = $this->overviewPageLoader->load($request, $context, $context->getCustomer());
return $this->renderStorefront('@Storefront/storefront/page/custom/subscription-checkout/index.html.twig', [
'product' => $product,
'cart' => $cart,
'page' => $page,
]);
}
#[Route('/order/subscription', name: 'frontend.order.subscription', defaults: ['XmlHttpRequest' => true, '_loginRequired' => true,], methods: ['POST'])]
public function createOrderSubscription(RequestDataBag $request, SalesChannelContext $context)
{
$isConfirm = (bool)$request->get('isConfirm');
if (!$isConfirm) {
return $this->redirectToRoute('frontend.account.order-subscription', ['status' => 'error']);
}
$token = NuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME . "-" . $context->getToken();
$paymentMethod = $this->getSubscriptionPaymentMethod($context->getContext());
$context->assign([
'paymentMethod' => $paymentMethod,
]);
$cart = $this->cartService->getCart($token, $context, NuveiSubscriptionPaymentHandler::SUBSCRIPTION_CART_NAME);
$lineItem = $cart->getLineItems()->first();
$shippingAddressId = $context->getCustomer()->getDefaultShippingAddressId();
$request->set('paymentMethodId', $paymentMethod->getId());
$request->set('shippingAddressId', $shippingAddressId);
$request->set('planId', $lineItem->getPayload()['planId']);
$orderId = $this->cartService->order($cart, $context, $request);
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $orderId));
$criteria->addAssociation('transactions');
$criteria->addAssociation('lineItems');
$criteria->addAssociation('deliveries.shippingOrderAddress');
$criteria->addAssociation('addresses');
$order = $this->orderRepository->search($criteria, $context->getContext())->first();
// Update order customFields to mark it as a subscription order
$customFields = $order->getCustomFields() ?? [];
$customFields['isOrderSubscription'] = true;
$this->orderRepository->update([
[
'id' => $orderId,
'customFields' => $customFields
]
], $context->getContext());
// Get the first line item as the subscription product
$lineItem = $order->getLineItems()->first();
if (!$lineItem) {
throw new \Exception('No line items found in order');
}
$productId = $lineItem->getProductId();
$customerId = $context->getCustomerId();
if (!$customerId) {
throw new \Exception('Customer not logged in');
}
$criteria = (new Criteria())
->addFilter(new EqualsFilter('customerId', $context->getCustomerId()))
->addSorting(new FieldSorting('createdAt', FieldSorting::DESCENDING));
$addressId = $this->customerAddressRepository
->search($criteria, $context->getContext())
->first()->getId();
// Get the Nuvei token from the TransactionSubscription
if (!$token) {
throw new \Exception('No payment token found in subscription transaction');
}
// Calculate next payment date (30 days from now)
$nextPaymentDate = (new \DateTime())->modify('+1 month');
$now = new \DateTime();
$subscriptionActive = $this->findSubscription($customerId, $productId, 'active', $context->getContext());
if ($subscriptionActive) {
return $this->redirectToRoute('frontend.account.order-subscription', [
'status' => 'error-create-order'
]);
}
$subscriptionPending = $this->findSubscription($customerId, $productId, 'pending', $context->getContext());
if ($subscriptionPending) {
$this->subscriptionRepository->update([[
'id' => $subscriptionPending->getId(),
'status' => 'cancelled'
]], $context->getContext());
}
$id = Uuid::randomHex();
$this->subscriptionRepository->create([[
'id' => $id,
'customerId' => $customerId,
'productId' => $productId,
'addressId' => $addressId,
'nextPaymentDate' => $nextPaymentDate,
'status' => 'pending',
'createdAt' => (new \DateTime())->format('Y-m-d H:i:s')
]], $context->getContext());
// Create subscription order
$this->subscriptionOrderRepository->create([
[
'subscriptionId' => $id,
'orderId' => $orderId,
'createdAt' => $now->format('Y-m-d H:i:s')
]
], $context->getContext());
// Use the standard Shopware payment process
// The finishUrl will be used after the payment is completed and the finalize method is called
// $finishUrl = $this->generateUrl('frontend.checkout.finish.page', ['orderId' => $orderId]);
$finishUrl = $this->generateUrl('frontend.account.order-subscription.finish', ['id' => $orderId]);
$errorUrl = $this->generateUrl('frontend.account.order-subscription');
try {
$this->logger->info('Starting payment process for subscription order', [
'orderId' => $orderId,
'customerId' => $context->getCustomerId()
]);
// This will generate a payment token and redirect to the payment gateway
// The payment gateway will redirect back to /payment/finalize-transaction with the token
// which will call the finalize method in NuveiSubscriptionPaymentHandler
$response = $this->paymentService->handlePaymentByOrder($orderId, $request, $context, $finishUrl, $errorUrl);
if ($response) {
$this->logger->info('Redirecting to payment gateway', [
'orderId' => $orderId,
'redirectUrl' => $response->getTargetUrl()
]);
return $response;
}
} catch (\Exception $e) {
$this->logger->error('Error processing subscription payment', [
'orderId' => $orderId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return $this->redirectToRoute('frontend.account.order-subscription', ['status' => 'error']);
}
return $this->redirectToRoute('frontend.account.order-subscription', ['status' => 'success']);
}
private function findSubscription(string $customerId, string $productId, string $status, Context $context)
{
$criteria = new Criteria();
$criteria->addFilter(
new EqualsFilter('customerId', $customerId),
new EqualsFilter('productId', $productId),
new EqualsFilter('status', $status)
);
return $this->subscriptionRepository->search($criteria, $context)->first();
}
protected function getSubscriptionPaymentMethod(Context $context): PaymentMethodEntity|null
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('handlerIdentifier', NuveiSubscriptionPaymentHandler::class));
return $this->paymentMethodRepository->search($criteria, $context)->getEntities()->first();
}
#[Route('/account/order-subscription', name: 'frontend.account.order-subscription', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function accountMyOrderSubscription(Request $request, SalesChannelContext $context): Response
{
$errorCode = $request->query->get('error-code');
if ($errorCode !== null) {
return $this->redirectToRoute('frontend.account.order-subscription');
}
$page = $this->navigationPageLoader->load($request, $context);
$response = $this->nuveiSubscriptionController->searchSubscriptionsByCustomer($request, $context);
$data = json_decode($response->getContent(), true);
return $this->renderStorefront('@Storefront/storefront/page/custom/account/order-subscription/index.html.twig', [
'page' => $page,
'subscriptions' => $data['subscriptions']
]);
}
#[Route('/account/order-subscription/{id}', name: 'frontend.account.order-subscription.detail', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function accountMyOrderSubscriptionDetail(String $id, Request $request, SalesChannelContext $context): Response
{
$page = $this->navigationPageLoader->load($request, $context);
$response = $this->nuveiSubscriptionController->searchOrdersByCustomerSubscription($id, $context);
if ($response['error'] != 0 ){
$subscriptionOrders = [];
} else {
$subscriptionOrders = $response['subscriptionOrders'];
}
return $this->renderStorefront('@Storefront/storefront/page/custom/account/subscription-detail/index.html.twig', [
'page' => $page,
'subscriptionOrders' => $subscriptionOrders
]);
}
#[Route('/account/order-subscription/finish/{id}', name: 'frontend.account.order-subscription.finish', defaults: ['_loginRequired' => true], methods: ['GET'])]
public function accountMyOrderSubscriptionFinish(String $id, Request $request, SalesChannelContext $context): Response
{
if ($id === null) {
return $this->redirectToRoute('frontend.home.page');
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $id));
$criteria->addAssociation('transactions.paymentMethod');
$criteria->addAssociation('lineItems.cover');
$criteria->addAssociation('deliveries.shippingOrderAddress');
$criteria->addAssociation('deliveries.stateId');
$criteria->addAssociation('addresses');
$order = $this->orderRepository->search($criteria, $context->getContext())->first();
$productId = $order->getLineItems()->first()->getproductId();
$customerId = $context->getCustomer()?->getId();
// Check if the customer has an active subscription for this product
$hasActiveSubscription = $this->subscriptionService->hasActiveSubscription(
$customerId,
$productId,
$context
);
$page = $this->navigationPageLoader->load($request, $context);
return $this->renderStorefront('@Storefront/storefront/page/custom/account/subscription-finish/index.html.twig', [
'page' => $page,
'order' => $order,
'hasActiveSubscription' => $hasActiveSubscription
]);
}
#[Route('/search/orders-by-customer-subscription', name: 'frontend.search.orders_by_customer_subscription', options: ["seo" => false], defaults: ['_loginRequired' => true, 'XmlHttpRequest' => true], methods: ['POST'])]
public function accountMyProductSubscription(Request $request, SalesChannelContext $context): Response
{
$status = $request->request->get('status');
$isMobile = $request->request->get('isMobile');
$isProduct = $request->request->get('isProduct');
// $page = $this->navigationPageLoader->load($request, $context);
$response = $this->nuveiSubscriptionController->searchSubscriptionsByCustomer($request, $context);
$data = json_decode($response->getContent(), true);
return $this->renderStorefront('@Storefront/storefront/page/custom/account/order-subscription/list-product.html.twig', [
'subscriptions' => $data['subscriptions'],
'isProduct' => $isProduct,
'isMobile' => $isMobile,
'status' => $status
]);
}
#[Route('/mobile/order/finish', name: 'frontend.mobile.order.finish', methods: ['GET'])]
public function mobileOrderFinish(Request $request, SalesChannelContext $context): Response
{
$page = $this->navigationPageLoader->load($request, $context);
$customer = $context->getCustomer();
$this->hook(new NavigationPageLoadedHook($page, $context));
return $this->renderStorefront('@Storefront/storefront/page/checkout/finish/mobile/index.html.twig', ['page' => $page, 'customer' => $customer, 'dashboardData' => $this->customerDataService->getBoughtProducts($context)]);
}
#[Route('/join-app', name: 'frontend.join.app', methods: ['GET'])]
public function joinApp(Request $request, SalesChannelContext $context): Response
{
$page = $this->genericLoader->load($request, $context);
return $this->renderStorefront('@Storefront/storefront/page/custom/account/join-app/index.html.twig', ['page' => $page]);
}
#[Route('/save4more-home', name: 'frontend.save4more.home', methods: ['GET'])]
public function save4more(Request $request, SalesChannelContext $context)
{
$productCriteria = new Criteria();
$productCriteria->addFilter(new EqualsFilter('customFields.isBuyProductSubscription', true));
$productResult = $this->salesChannelProductRepository->search($productCriteria, $context)->first();
$productId = $productResult ? $productResult->getId() : null;
$page = $this->navigationPageLoader->load($request, $context);
$pageNumber = 1;
$this->hook(new NavigationPageLoadedHook($page, $context));
$dataStream = $this->postService->fetchStream($context->getCustomer()?->getId(), $context, "details", $productId, null, $pageNumber);
return $this->renderStorefront('@Storefront/storefront/page/custom/save-4-more/index.html.twig', ['page' => $page, 'dataStream' => $dataStream, 'productResult' => $productResult]);
}
#[Route('/save4more-terms-condition', name: 'frontend.save4more.terms', methods: ['GET'])]
public function save4moreTerms(Request $request, SalesChannelContext $context)
{
$page = $this->navigationPageLoader->load($request, $context);
$this->hook(new NavigationPageLoadedHook($page, $context));
return $this->renderStorefront('@Storefront/storefront/page/custom/save-4-more/terms/index.html.twig', ['page' => $page]);
}
// Storefront API: Download invoice PDF for an order. If missing, create it first.
// Only accessible by the owning customer; requires login (guest allowed).
#[Route(
path: '/account/order/{orderId}/invoice',
name: 'frontend.account.order.invoice.download',
methods: ['POST'],
defaults: ['_routeScope' => ['storefront'], '_loginRequired' => true, '_loginRequiredAllowGuest' => true, "XmlHttpRequest" => true]
)]
public function downloadInvoice(Request $request, string $orderId, SalesChannelContext $salesChannelContext): Response
{
$customer = $salesChannelContext->getCustomer();
if ($customer === null) {
return new Response('Not Login!', Response::HTTP_UNAUTHORIZED);
}
$context = $salesChannelContext->getContext();
// Try to find latest invoice document for this order
$docCriteria = new Criteria();
$docCriteria->addFilter(new EqualsFilter('orderId', $orderId));
$docCriteria->addFilter(new EqualsFilter('documentType.technicalName', 'invoice'));
$docCriteria->addAssociation('documentType');
$docCriteria->setLimit(1);
// Sort by createdAt DESC (newest first)
$docCriteria->addSorting(new \Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting('createdAt', \Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting::DESCENDING));
$existing = $this->documentRepository->search($docCriteria, $context)->first();
if ($existing) {
$documentId = $existing->getId();
$deepLinkCode = $existing->getDeepLinkCode();
} else {
// Create a new invoice document
// Avoid duplicated media filename by providing a unique filename
$uniqueSuffix = (new \DateTimeImmutable())->format('Ymd_His');
$fileName = sprintf('invoice_%s_%s.pdf', $orderId, $uniqueSuffix);
$operation = new DocumentGenerateOperation(
$orderId,
FileTypes::PDF,
[
'fileName' => $fileName,
],
null,
false
);
$result = $this->documentGenerator->generate('invoice', [$orderId => $operation], $context);
$success = $result->getSuccess();
if ($success->count() === 0) {
return new Response('Cannot generate invoice', Response::HTTP_INTERNAL_SERVER_ERROR);
}
/** @var \Shopware\Core\Checkout\Document\DocumentIdStruct $document */
$document = $success->first();
$deepLinkCode = $document->getDeepLinkCode();
$documentId = $document->getId();
}
// Build URL to built-in storefront document download route to stream the PDF
$url = $this->generateUrl('frontend.account.order.single.document', [
'documentId' => $documentId,
'deepLinkCode' => $deepLinkCode,
'download' => 1,
]);
// Return JSON response as requested
return new JsonResponse([
'url' => $url,
'success' => true,
], Response::HTTP_OK);
}
}