<?php declare(strict_types=1);
namespace CoeWishlistSw6\Storefront\Controller;
use App\Product;
use CoeWishlistSw6\Core\Content\Wishlist\Service\WishlistPriceQuantityService;
use CoeWishlistSw6\Core\Content\Wishlist\Service\WishlistPriceQuantityServiceInterface;
use CoeWishlistSw6\Core\Content\Wishlist\Service\WishlistService;
use CoeWishlistSw6\Core\Content\Wishlist\Service\WishlistServiceInterface;
use CoeWishlistSw6\Core\Content\Wishlist\WishlistCollection;
use CoeWishlistSw6\Core\Content\Wishlist\WishlistEntity;
use CoeWishlistSw6\Core\Content\Wishlist\Aggregate\WishlistNoteEntity;
use CoeWishlistSw6\Storefront\Page\Wishlist\WishlistPage;
use CoeWishlistSw6\Storefront\Page\Wishlist\WishlistPageLoader;
use http\Exception\InvalidArgumentException;
use Shopware\Core\Content\Product\Exception\ProductNotFoundException;
use Shopware\Core\Content\Product\ProductCollection;
use Shopware\Core\Content\Product\ProductEntity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Routing\Exception\MissingRequestParameterException;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepositoryInterface;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Storefront\Controller\StorefrontController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
/**
* Class WishlistController
* @package CoeWishlistSw6\Storefront\Controller
* @author Jeffry Block <jeffry.block@codeenterprise.de>
*/
class WishlistController extends StorefrontController
{
/**
* @var WishlistPageLoader
*/
private $wishlistPageLoader;
/**
* @var WishlistServiceInterface
*/
private $wishlistService;
/**
* @var EntityRepositoryInterface
*/
private $wishlistRepository;
/**
* @var WishlistPriceQuantityServiceInterface
*/
private $wishlistPriceQuantityService;
/**
* @var SalesChannelRepositoryInterface
*/
private SalesChannelRepositoryInterface $productRepository;
/**
* WishlistController constructor.
* @param WishlistPageLoader $wishlistPageLoader
* @param WishlistServiceInterface $wishlistService
* @param EntityRepositoryInterface $wishlistRepository
* @param WishlistPriceQuantityServiceInterface $wishlistPriceQuantityService
* @param SalesChannelRepositoryInterface $productRepository
*/
public function __construct(
WishlistPageLoader $wishlistPageLoader,
WishlistServiceInterface $wishlistService,
EntityRepositoryInterface $wishlistRepository,
WishlistPriceQuantityServiceInterface $wishlistPriceQuantityService,
SalesChannelRepositoryInterface $productRepository
) {
$this->wishlistPageLoader = $wishlistPageLoader;
$this->wishlistService = $wishlistService;
$this->wishlistRepository = $wishlistRepository;
$this->wishlistPriceQuantityService = $wishlistPriceQuantityService;
$this->productRepository = $productRepository;
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/note", name="frontend.wishlist.page", methods={"GET"}, options={"seo"="false"})
* @Route("/note/{listId}", name="frontend.wishlist.page", methods={"GET"}, options={"seo"="false"}, requirements={"listId"=".{32}"}, defaults={"listId"=null})
* @param string|null $listId
* @param Request $request
* @param SalesChannelContext $context
* @return \Symfony\Component\HttpFoundation\Response
* @throws MissingRequestParameterException
* @throws \Shopware\Core\Content\Category\Exception\CategoryNotFoundException
* @throws \Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException
* @author Jeffry Block <jeffry.block@codeenterprise.de>
* @throws \Exception
*/
public function index(?string $listId = null, Request $request, SalesChannelContext $context): Response
{
if ($listId) {
if (!$this->wishlistService->loadList($listId, $context->getContext())) {
throw new \Exception(sprintf("Wishlist with ID %s does not exist", $listId));
}
}
try {
/** @var WishlistPage $page */
$page = $this->wishlistPageLoader->load($request, $context);
} catch (\Exception $e) {
$this->container->get("session")->getFlashBag()->add('warning', $e->getMessage());
$url = $this->container->get("router")->generate('frontend.wishlist.page');
return new RedirectResponse($url);
}
/** @var Response $response */
$response = $this->renderStorefront('@CoeWishlistSw6/storefront/page/wishlist/overview/index.html.twig', [
'page' => $page,
]);
$response->headers->set("X-Robots", "noindex,nofollow");
return $response;
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/note/save/note", name="frontend.wishlist.save.note", methods={"POST"}, defaults={"XmlHttpRequest": true})
* @param RequestDataBag $dataBag
* @param Request $request
* @param SalesChannelContext $context
*/
public function saveNote(RequestDataBag $dataBag, Request $request, SalesChannelContext $context): Response
{
/** @var string|null $sku */
$sku = $dataBag->get("sku");
// If parameter "useSKU" is set, get the product ID based on the SKU.
// Using SKU means, that the request came from the "add product manually"-form where users can enter the quantity
// manually as well. In this case we also need to make sure that the entered quantity is actually valid.
if ($sku) {
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter("productNumber", trim($sku)));
/** @var ProductCollection $products */
$products = $this->productRepository->search($criteria, $context);
if ($products->count() === 0) {
throw new ProductNotFoundException($sku);
}
/** @var ProductEntity $product */
$product = $products->first();
/** @var int $quantity */
$quantity = $dataBag->getInt("quantity", 1);
if (!$this->wishlistService->isQuantityValidForProduct($product, $quantity)) {
throw new \Exception(sprintf("Quantity %s not allowed for product %s", $quantity, $product->getProductNumber()));
}
$dataBag->set("productId", $product->getId());
}
/** @var string $productId */
$productId = $dataBag->get("productId");
if (!$productId) {
throw new MissingRequestParameterException('productId');
}
/** @var string $listId */
$listId = $dataBag->get("listId");
if ($dataBag->getBoolean("addNewList")) {
$listId = ($this -> wishlistService -> createList($dataBag, $context, ["notes","notes.product"]))->getId();
}
/** @var WishlistEntity $list */
$list = $this -> wishlistService -> loadList($listId, $context->getContext(), ["notes","notes.product"]);
if (!$list) {
throw new \Exception(sprintf("Wishlist with ID %s does not exist", $list));
}
/** @var WishlistNoteEntity $addedNote */
$addedNote = $this -> wishlistService -> addNote($dataBag, $list, $context);
return new JsonResponse([
"success" => true,
"data" => [
"noteId" => $addedNote->getId(),
"listId" => $list->getId(),
],
], 200);
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/note/save/list", name="frontend.wishlist.save.list", methods={"POST"}, defaults={"XmlHttpRequest": true})
* @param RequestDataBag $dataBag
* @param Request $request
* @param SalesChannelContext $context
*/
public function saveList(RequestDataBag $dataBag, Request $request, SalesChannelContext $context): Response
{
$listId = ($this -> wishlistService -> createList($dataBag, $context, ["notes","notes.product"]))->getId();
return new JsonResponse([
"success" => true,
"data" => [
"listId" => $listId,
],
], 200);
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/note/save/list/visibility", name="frontend.wishlist.save.list.visiblity", methods={"POST"}, defaults={"XmlHttpRequest": true})
* @param RequestDataBag $dataBag
* @param Request $request
* @param SalesChannelContext $context
*/
public function saveListVisibility(RequestDataBag $dataBag, Request $request, SalesChannelContext $context): Response
{
$listId = $dataBag->get("listId");
$visibility = $dataBag->getBoolean("public", false);
if (!$listId) {
throw new MissingRequestParameterException('listId');
}
if (!$this->wishlistService->listBelongsToCurrentUser($listId, $context)) {
throw new \Exception(sprintf("Access to resource with ID %s denied", $listId));
}
$this->wishlistRepository->update([[
"id" => $listId,
"public" => $visibility,
]], $context->getContext());
return new JsonResponse([
"success" => true,
"data" => [
"listId" => $listId,
],
], 200);
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/note/save/list/name", name="frontend.wishlist.save.list.name", methods={"POST"}, defaults={"XmlHttpRequest": true})
* @param RequestDataBag $dataBag
* @param Request $request
* @param SalesChannelContext $context
*/
public function saveListName(RequestDataBag $dataBag, Request $request, SalesChannelContext $context): Response
{
$listId = $dataBag->get("listId");
$name = $dataBag->get("listName", "No Name");
if (!$listId) {
throw new MissingRequestParameterException('listId');
}
if (!$this->wishlistService->listBelongsToCurrentUser($listId, $context)) {
throw new \Exception(sprintf("Access to resource with ID %s denied", $listId));
}
$this->wishlistRepository->update([[
"id" => $listId,
"name" => $name,
]], $context->getContext());
return new JsonResponse([
"success" => true,
], 200);
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/note/save/note/quantity", name="frontend.wishlist.save.note.quantity", methods={"POST"}, defaults={"XmlHttpRequest": true})
* @param RequestDataBag $dataBag
* @param Request $request
* @param SalesChannelContext $context
*/
public function saveNoteQuantity(RequestDataBag $dataBag, Request $request, SalesChannelContext $context): Response
{
$quantity = $dataBag->getInt("quantity");
if (!$quantity || $quantity === 0) {
throw new MissingRequestParameterException('quantity');
}
$noteId = $dataBag->get("noteId");
if (!$noteId) {
throw new MissingRequestParameterException('noteId');
}
$productId = $dataBag->get("productId");
if (!$productId) {
throw new MissingRequestParameterException('productId');
}
// Update the Quantity
// @todo check if the list of the note belongs to the user. Currently the note qty can be changed by everyone
$this->wishlistService->updateNoteQuantity($noteId, $quantity, $context->getContext());
try {
// When updating the quantity, the price per piece may have changed. Recalculate the price to show in storefront.
$quantityPrice = $this->wishlistPriceQuantityService->buildPrices($productId, $quantity, $context);
return new JsonResponse([
"success" => true,
"data" => [
"noteId" => $noteId,
"priceQty" => $this->wishlistPriceQuantityService->format($quantityPrice, $context),
"priceSum" => $this->wishlistPriceQuantityService->format($quantity * $quantityPrice, $context),
"qty" => $quantity,
],
], 200);
} catch (\Exception $e) {
// If an exception occured during price calculation, just return null as data and reload the storefront
// instead of replacing the elemens dynamically.
return new JsonResponse([
"success" => true,
"data" => [
"qty" => $quantity,
"noteId" => $noteId,
],
]);
}
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/note/count", name="frontend.wishlist.count.snippet", methods={"GET"}, defaults={"XmlHttpRequest": true})
* @param RequestDataBag $dataBag
* @param Request $request
* @param SalesChannelContext $context
*/
public function getNoteCount(RequestDataBag $dataBag, Request $request, SalesChannelContext $context): Response
{
/** @var WishlistCollection $lists */
$lists = $this->wishlistService->loadLists($context, ["notes"]);
$counter = $lists->getAccumulatedNoteCount();
return new Response((string)$counter);
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/note/productids", name="frontend.wishlist.productids.snippet", methods={"GET"}, defaults={"XmlHttpRequest": true})
* @param RequestDataBag $dataBag
* @param Request $request
* @param SalesChannelContext $context
*/
public function getNoteProductIds(RequestDataBag $dataBag, Request $request, SalesChannelContext $context): Response
{
/** @var WishlistCollection $lists */
$lists = $this->wishlistService->loadLists($context, ["notes","notes.product"]);
$numbers = $lists->getAllProductsIds();
return new JsonResponse($numbers);
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/note/delete/list", name="frontend.wishlist.delete.list", methods={"POST"}, defaults={"XmlHttpRequest": true})
* @param RequestDataBag $dataBag
* @param Request $request
* @param SalesChannelContext $context
*/
public function deleteList(RequestDataBag $dataBag, Request $request, SalesChannelContext $context): Response
{
$listId = $dataBag->get("listId");
if (!$listId) {
throw new MissingRequestParameterException('listId');
}
if (!$this->wishlistService->listBelongsToCurrentUser($listId, $context)) {
throw new \Exception(sprintf("Access to resource with ID %s denied", $listId));
}
$this->wishlistService->deleteList($listId, $context->getContext());
if ($context->getCustomer()) {
$this->wishlistService->resetDefault($context->getCustomer()->getId(), $context->getContext());
}
return new JsonResponse([
"success" => true,
"data" => [
"listId" => $listId,
],
], 200);
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/note/delete/note", name="frontend.wishlist.delete.note", methods={"POST"}, defaults={"XmlHttpRequest": true})
* @param RequestDataBag $dataBag
* @param Request $request
* @param SalesChannelContext $context
*/
public function deleteNote(RequestDataBag $dataBag, Request $request, SalesChannelContext $context): Response
{
$noteId = $dataBag->get("noteId");
if (!$noteId) {
throw new MissingRequestParameterException('noteId');
}
// @todo check if the list of the note belongs to the user. Currently notes can be deleted by everyone
$this->wishlistService->deleteNote($noteId, $context->getContext());
return new JsonResponse([
"success" => true,
"data" => [
"noteId" => $noteId,
],
], 200);
}
/**
* @RouteScope(scopes={"storefront"})
* @Route("/note/debug", name="frontend.wishlist.debug.page", methods={"GET"})
* @param Request $request
* @param SalesChannelContext $context
* @return \Symfony\Component\HttpFoundation\Response
*/
public function debug(Request $request, SalesChannelContext $context)
{
return new JsonResponse("[]");
}
}