Add InPost Pay integration to admin templates
- Created a new template for the cart rule form with custom label, switch, and choice widgets. - Implemented the InPost Pay block in the order details template for displaying delivery method, APM, and VAT invoice request. - Added legacy support for the order details template to maintain compatibility with older PrestaShop versions.
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace izi\prestashop\Controller\Admin;
|
||||
|
||||
use izi\prestashop\Configuration\ApiConfigurationInterface;
|
||||
use izi\prestashop\Configuration\Initializer\ConfigurationInitializerInterface;
|
||||
use izi\prestashop\Translation\LegacyTranslator;
|
||||
use PrestaShopBundle\Security\Voter\PageVoter;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
|
||||
/* IGNORE_THIS_FILE_FOR_TRANSLATION */
|
||||
abstract class AbstractConfigurationController extends AbstractController
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* @var LegacyTranslator
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
* @var \Context
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* @var ApiConfigurationInterface
|
||||
*/
|
||||
protected $apiConfiguration;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $debug;
|
||||
|
||||
/**
|
||||
* @param iterable<ConfigurationInitializerInterface> $configInitializers
|
||||
*/
|
||||
public function __construct(LegacyTranslator $translator, \Context $context, iterable $configInitializers, ApiConfigurationInterface $apiConfiguration, bool $debug = false)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->context = $context;
|
||||
$this->apiConfiguration = $apiConfiguration;
|
||||
$this->debug = $debug;
|
||||
|
||||
foreach ($configInitializers as $initializer) {
|
||||
$initializer->init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $view
|
||||
*
|
||||
* @internal public visibility for compatibility with Sf 2.8
|
||||
*/
|
||||
public function render($view, array $parameters = [], Response $response = null): Response
|
||||
{
|
||||
$parameters['is_legacy_admin_page'] = version_compare(_PS_VERSION_, '1.7.4.0', '<');
|
||||
|
||||
return parent::render($view, $parameters, $response);
|
||||
}
|
||||
|
||||
final protected static function getConfigPermission(): array
|
||||
{
|
||||
$role = \Access::sluggifyModule([
|
||||
'name' => 'inpostizi',
|
||||
], \Access::getAuthorizationFromLegacy('configure'));
|
||||
|
||||
return [$role, null];
|
||||
}
|
||||
|
||||
protected function checkAccess(): void
|
||||
{
|
||||
foreach ($this->getRequiredPermissions() as [$attributes, $subject]) {
|
||||
$this->denyAccessUnlessGranted($attributes, $subject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<array{0: string|string[], 1: mixed}>
|
||||
*/
|
||||
protected function getRequiredPermissions(): iterable
|
||||
{
|
||||
yield self::getConfigPermission();
|
||||
}
|
||||
|
||||
protected function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string
|
||||
{
|
||||
return $this->context->getTranslator()->trans($id, $parameters, $domain, $locale);
|
||||
}
|
||||
|
||||
protected function renderNav(Request $request): string
|
||||
{
|
||||
$pages = [
|
||||
'general' => [
|
||||
'route' => 'admin_inpost_izi_config_general',
|
||||
'title' => $this->translator->l('Configuration', ConfigurationController::TRANSLATION_SOURCE),
|
||||
],
|
||||
'consents' => [
|
||||
'route' => 'admin_inpost_izi_config_consents',
|
||||
'title' => $this->translator->l('Consents', ConfigurationController::TRANSLATION_SOURCE),
|
||||
],
|
||||
'shipping' => [
|
||||
'route' => 'admin_inpost_izi_config_shipping',
|
||||
'title' => $this->translator->l('Shipping configuration', ConfigurationController::TRANSLATION_SOURCE),
|
||||
],
|
||||
'gui' => [
|
||||
'route' => 'admin_inpost_izi_config_gui',
|
||||
'title' => $this->translator->l('GUI configuration', ConfigurationController::TRANSLATION_SOURCE),
|
||||
],
|
||||
'support' => [
|
||||
'route' => 'admin_inpost_izi_config_support',
|
||||
'title' => $this->translator->l('Support', ConfigurationController::TRANSLATION_SOURCE),
|
||||
],
|
||||
];
|
||||
|
||||
if (null !== $this->apiConfiguration->getClientCredentials() && $this->isGranted(PageVoter::READ, 'AdminProducts_')) {
|
||||
$pages['products'] = [
|
||||
'route' => 'admin_inpost_izi_products_index',
|
||||
'title' => $this->translator->l('Hot products', HotProductController::TRANSLATION_SOURCE),
|
||||
'active_checker' => static function (Request $request): bool {
|
||||
return $request->attributes->has('_inpost_izi_hot_product_page');
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return $this->renderView('@Modules/inpostizi/views/templates/admin/config/nav.html.twig', [
|
||||
'nav_items' => array_map(function (array $page) use ($request): array {
|
||||
return [
|
||||
'url' => $this->generateUrl($page['route']),
|
||||
'label' => $page['title'],
|
||||
'active' => isset($page['active_checker'])
|
||||
? $page['active_checker']($request)
|
||||
: $page['route'] === $request->attributes->get('_route'),
|
||||
];
|
||||
}, $pages),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function handleError(\Throwable $e, Request $request): void
|
||||
{
|
||||
$this->logger = $this->logger ?? new NullLogger();
|
||||
$this->logger->critical('An error occurred while processing the request: {exception}', [
|
||||
'route' => $request->attributes->get('_route'),
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
if ($this->debug) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->addFlash('error', $this->trans('An unexpected error occurred. [%type% code %code%]', [
|
||||
'%type%' => get_class($e),
|
||||
'%code%' => $e->getCode(),
|
||||
], 'Admin.Notifications.Error'));
|
||||
}
|
||||
|
||||
final protected function isGranted($attributes, $subject = null): bool
|
||||
{
|
||||
if (parent::isGranted($attributes, $subject)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (null !== $subject || !str_starts_with($attributes, 'ROLE_')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->checkUserRole($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks module configuration role using the user entity instead of the security token, since the token may not contain
|
||||
* all the user roles if it was reloaded from session.
|
||||
*
|
||||
* Implementing a custom security voter (PS does not provide a default one) would have been a cleaner solution,
|
||||
* but that could cause problems if the container cache was not refreshed after disabling or uninstalling the module.
|
||||
*/
|
||||
private function checkUserRole(string $role): bool
|
||||
{
|
||||
if (null === $user = $this->getUser()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$userRoles = array_map(static function ($role): string {
|
||||
if ($role instanceof Role) {
|
||||
return $role->getRole();
|
||||
}
|
||||
|
||||
return (string) $role;
|
||||
}, $user->getRoles());
|
||||
|
||||
return in_array($role, $userRoles, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace izi\prestashop\Controller\Admin;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as BaseController;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
|
||||
if (class_exists(BaseController::class)) {
|
||||
abstract class AbstractController extends BaseController
|
||||
{
|
||||
}
|
||||
} else {
|
||||
abstract class AbstractController extends Controller
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace izi\prestashop\Controller\Admin;
|
||||
|
||||
use izi\prestashop\Command\Config\CheckStatusCommand;
|
||||
use izi\prestashop\Command\Config\DownloadModuleDataCommand;
|
||||
use izi\prestashop\Command\Config\UpdateAdvancedConfigurationCommand;
|
||||
use izi\prestashop\Command\Config\UpdateConsentsConfigurationCommand;
|
||||
use izi\prestashop\Command\Config\UpdateGeneralConfigurationCommandFactory;
|
||||
use izi\prestashop\Command\Config\UpdateGuiConfigurationCommand;
|
||||
use izi\prestashop\Command\Config\UpdateShippingConfigurationCommand;
|
||||
use izi\prestashop\CommandBusInterface;
|
||||
use izi\prestashop\Configuration\AdvancedConfiguration;
|
||||
use izi\prestashop\Configuration\AdvancedConfigurationInterface;
|
||||
use izi\prestashop\Configuration\ConsentsConfigurationInterface;
|
||||
use izi\prestashop\Configuration\GuiConfiguration;
|
||||
use izi\prestashop\Configuration\GuiConfigurationInterface;
|
||||
use izi\prestashop\Configuration\ShippingConfiguration;
|
||||
use izi\prestashop\Configuration\ShippingConfigurationInterface;
|
||||
use izi\prestashop\Form\Type\AdvancedConfigurationType;
|
||||
use izi\prestashop\Form\Type\ConsentsConfigurationType;
|
||||
use izi\prestashop\Form\Type\GeneralConfigurationType;
|
||||
use izi\prestashop\Form\Type\GuiConfigurationType;
|
||||
use izi\prestashop\Form\Type\ShippingConfigurationType;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route(path="config")
|
||||
*/
|
||||
final class ConfigurationController extends AbstractConfigurationController
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const TRANSLATION_SOURCE = 'configurationcontroller';
|
||||
|
||||
/**
|
||||
* @Route(path="/general", name="admin_inpost_izi_config_general", methods={"GET", "POST"})
|
||||
*/
|
||||
public function generalConfig(Request $request, UpdateGeneralConfigurationCommandFactory $commandFactory, CommandBusInterface $bus): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
$command = $commandFactory->create();
|
||||
|
||||
$form = $this->createForm(GeneralConfigurationType::class, $command);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
$bus->handle($form->getData());
|
||||
$this->addFlash('success', $this->trans('Successful update.', [], 'Admin.Notifications.Success'));
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_config_general');
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleError($e, $request);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('@Modules/inpostizi/views/templates/admin/config/general.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'layoutTitle' => $this->translator->l('Configuration', self::TRANSLATION_SOURCE),
|
||||
'headerTabContent' => $this->renderNav($request),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/consents", name="admin_inpost_izi_config_consents", methods={"GET", "POST"})
|
||||
*/
|
||||
public function consentConfig(Request $request, ConsentsConfigurationInterface $configuration, CommandBusInterface $bus): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
$command = new UpdateConsentsConfigurationCommand(...$configuration->getConsents());
|
||||
|
||||
$form = $this->createForm(ConsentsConfigurationType::class, $command);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
$bus->handle($form->getData());
|
||||
$this->addFlash('success', $this->trans('Successful update.', [], 'Admin.Notifications.Success'));
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_config_consents');
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleError($e, $request);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('@Modules/inpostizi/views/templates/admin/config/consents.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'layoutTitle' => $this->translator->l('Consents', self::TRANSLATION_SOURCE),
|
||||
'headerTabContent' => $this->renderNav($request),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GuiConfiguration $configuration
|
||||
*
|
||||
* @Route(path="/gui", name="admin_inpost_izi_config_gui", methods={"GET", "POST"})
|
||||
*/
|
||||
public function guiConfig(Request $request, GuiConfigurationInterface $configuration, CommandBusInterface $bus): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
$form = $this->createForm(GuiConfigurationType::class, $configuration->copy());
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$command = new UpdateGuiConfigurationCommand($form->getData());
|
||||
|
||||
try {
|
||||
$bus->handle($command);
|
||||
$this->addFlash('success', $this->trans('Successful update.', [], 'Admin.Notifications.Success'));
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_config_gui');
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleError($e, $request);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('@Modules/inpostizi/views/templates/admin/config/gui.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'layoutTitle' => $this->translator->l('GUI configuration', self::TRANSLATION_SOURCE),
|
||||
'headerTabContent' => $this->renderNav($request),
|
||||
'widget_js_uri' => $this->apiConfiguration->getEnvironment()->getWidgetJavaScriptUri(),
|
||||
'merchant_client_id' => $this->apiConfiguration->getMerchantClientId(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShippingConfiguration $configuration
|
||||
*
|
||||
* @Route(path="/shipping", name="admin_inpost_izi_config_shipping", methods={"GET", "POST"})
|
||||
*/
|
||||
public function shippingConfig(Request $request, ShippingConfigurationInterface $configuration, CommandBusInterface $bus): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
$form = $this->createForm(ShippingConfigurationType::class, $configuration->copy());
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$command = new UpdateShippingConfigurationCommand($form->getData());
|
||||
|
||||
try {
|
||||
$bus->handle($command);
|
||||
$this->addFlash('success', $this->trans('Successful update.', [], 'Admin.Notifications.Success'));
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_config_shipping');
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleError($e, $request);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('@Modules/inpostizi/views/templates/admin/config/shipping.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'layoutTitle' => $this->translator->l('Shipping configuration', self::TRANSLATION_SOURCE),
|
||||
'headerTabContent' => $this->renderNav($request),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AdvancedConfiguration $configuration
|
||||
*
|
||||
* @Route(path="/support", name="admin_inpost_izi_config_support", methods={"GET"})
|
||||
*/
|
||||
public function support(Request $request, AdvancedConfigurationInterface $configuration, CommandBusInterface $bus): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
$form = $this->createForm(AdvancedConfigurationType::class, $configuration->copy(), [
|
||||
'action' => $this->generateUrl('admin_inpost_izi_config_support_save', $request->query->all()),
|
||||
]);
|
||||
|
||||
return $this->render('@Modules/inpostizi/views/templates/admin/config/support.html.twig', [
|
||||
'layoutTitle' => $this->translator->l('Support', self::TRANSLATION_SOURCE),
|
||||
'headerTabContent' => $this->renderNav($request),
|
||||
'form' => $form->createView(),
|
||||
'status' => $bus->handle(new CheckStatusCommand()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AdvancedConfiguration $configuration
|
||||
*
|
||||
* @Route(path="/support", name="admin_inpost_izi_config_support_save", methods={"POST"})
|
||||
*/
|
||||
public function supportSave(Request $request, AdvancedConfigurationInterface $configuration, CommandBusInterface $bus): Response
|
||||
{
|
||||
if (!$this->isGranted(...self::getConfigPermission())) {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'Access Denied.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$form = $this->createForm(AdvancedConfigurationType::class, $configuration->copy());
|
||||
$form->handleRequest($request);
|
||||
|
||||
if (!$form->isSubmitted()) {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => 'Malformed request.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
if (!$form->isValid()) {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => (string) $form->getErrors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$command = new UpdateAdvancedConfigurationCommand($form->getData());
|
||||
|
||||
try {
|
||||
$bus->handle($command);
|
||||
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'message' => $this->trans('Successful update.', [], 'Admin.Notifications.Success'),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => $this->trans('Oops... looks like an unexpected error occurred', [], 'Admin.Notifications.Error'),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/download-data", name="admin_inpost_izi_config_download_data", methods={"GET"})
|
||||
*/
|
||||
public function downloadData(CommandBusInterface $bus): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
$callback = $bus->handle(new DownloadModuleDataCommand());
|
||||
$response = new StreamedResponse($callback);
|
||||
|
||||
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'module_data.zip');
|
||||
|
||||
$response->headers->add([
|
||||
'Content-Type' => 'application/x-zip',
|
||||
'Content-Disposition' => $disposition,
|
||||
]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
355
modules/inpostizi/src/Controller/Admin/HotProductController.php
Normal file
355
modules/inpostizi/src/Controller/Admin/HotProductController.php
Normal file
@@ -0,0 +1,355 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace izi\prestashop\Controller\Admin;
|
||||
|
||||
use izi\prestashop\BasketApp\Exception\BasketAppException;
|
||||
use izi\prestashop\BasketApp\Product\Exception\MaxProductLimitReachedException;
|
||||
use izi\prestashop\BasketApp\Product\Response\Status;
|
||||
use izi\prestashop\CommandBusInterface;
|
||||
use izi\prestashop\Configuration\ApiConfigurationInterface;
|
||||
use izi\prestashop\Configuration\Initializer\ConfigurationInitializerInterface;
|
||||
use izi\prestashop\HotProduct\Exception\HotProductExceptionInterface;
|
||||
use izi\prestashop\HotProduct\Exception\HotProductExistsException;
|
||||
use izi\prestashop\HotProduct\Form\CreateHotProductType;
|
||||
use izi\prestashop\HotProduct\Form\UpdateHotProductType;
|
||||
use izi\prestashop\HotProduct\HotProductRepositoryInterface;
|
||||
use izi\prestashop\HotProduct\Message\CreateHotProductCommand;
|
||||
use izi\prestashop\HotProduct\Message\DeleteHotProductCommand;
|
||||
use izi\prestashop\HotProduct\Message\DeleteRemoteProductCommand;
|
||||
use izi\prestashop\HotProduct\Message\ImportHotProductCommand;
|
||||
use izi\prestashop\HotProduct\Message\UpdateHotProductCommand;
|
||||
use izi\prestashop\HotProduct\View\HotProductViewDataFactory;
|
||||
use izi\prestashop\Http\Exception\HttpExceptionInterface;
|
||||
use izi\prestashop\OAuth2\Exception\OAuth2ExceptionInterface;
|
||||
use izi\prestashop\ObjectModel\Repository\ProductRepository;
|
||||
use izi\prestashop\Translation\LegacyTranslator;
|
||||
use PrestaShop\PrestaShop\Adapter\Shop\Context;
|
||||
use PrestaShopBundle\Security\Voter\PageVoter;
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route(path="hot-products", defaults={"_inpost_izi_hot_product_page"=true})
|
||||
*/
|
||||
final class HotProductController extends AbstractConfigurationController
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const TRANSLATION_SOURCE = 'hotproductcontroller';
|
||||
|
||||
/**
|
||||
* @var Context
|
||||
*/
|
||||
private $shopContext;
|
||||
|
||||
/**
|
||||
* @param iterable<ConfigurationInitializerInterface> $configInitializers
|
||||
*/
|
||||
public function __construct(Context $shopContext, LegacyTranslator $translator, \Context $context, ApiConfigurationInterface $apiConfiguration, iterable $configInitializers, bool $debug = false)
|
||||
{
|
||||
parent::__construct($translator, $context, $configInitializers, $apiConfiguration, $debug);
|
||||
$this->shopContext = $shopContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(name="admin_inpost_izi_products_index", methods={"GET"})
|
||||
*/
|
||||
public function index(Request $request, HotProductRepositoryInterface $repository, HotProductViewDataFactory $viewDataFactory): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
if (null === $this->apiConfiguration->getClientCredentials()) {
|
||||
return $this->redirectToRoute('admin_inpost_izi_config_general');
|
||||
}
|
||||
|
||||
if (null !== $shopId = $this->shopContext->getContextShopID()) {
|
||||
$shopId = (int) $shopId;
|
||||
}
|
||||
|
||||
$products = $repository->findAll($shopId);
|
||||
$viewData = $viewDataFactory->createForProducts($products);
|
||||
|
||||
if (!$statusAvailable = $viewData->isStatusAvailable()) {
|
||||
$this->addFlash('warning', $this->translator->l('Failed to fetch product statuses from the API.', self::TRANSLATION_SOURCE));
|
||||
}
|
||||
|
||||
return $this->render('@Modules/inpostizi/views/templates/admin/hot_products/index.html.twig', [
|
||||
'products' => $viewData->getProducts(),
|
||||
'is_status_available' => $statusAvailable,
|
||||
'is_multistore_context' => null === $shopId,
|
||||
'layoutTitle' => $this->translator->l('Hot products', self::TRANSLATION_SOURCE),
|
||||
'headerTabContent' => $this->renderNav($request),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/new", name="admin_inpost_izi_products_create", methods={"GET", "POST"})
|
||||
*/
|
||||
public function create(Request $request, CommandBusInterface $bus): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
if (null === $this->apiConfiguration->getClientCredentials()) {
|
||||
return $this->redirectToRoute('admin_inpost_izi_config_general');
|
||||
}
|
||||
|
||||
if (null !== $shopId = $this->shopContext->getContextShopID()) {
|
||||
$shopId = (int) $shopId;
|
||||
} else {
|
||||
$this->addFlash('warning', $this->translator->l('You are creating a hot product in a multistore context. Product data associated with the default shop will be sent to the Basket App.', self::TRANSLATION_SOURCE));
|
||||
}
|
||||
|
||||
$form = $this->createForm(CreateHotProductType::class, new CreateHotProductCommand($shopId));
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
$bus->handle($form->getData());
|
||||
$this->addFlash('success', $this->translator->l('Hot product has been created successfully and will be awaiting approval.', self::TRANSLATION_SOURCE));
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_products_index');
|
||||
} catch (HotProductExistsException $e) {
|
||||
$this->addFlash('error', $this->translator->l('There already exists a hot product for product and combination.', self::TRANSLATION_SOURCE));
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleUpdateError($e, $request);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('@Modules/inpostizi/views/templates/admin/hot_products/create.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'layoutTitle' => $this->translator->l('New hot product', self::TRANSLATION_SOURCE),
|
||||
'headerTabContent' => $this->renderNav($request),
|
||||
'is_ps_176' => str_starts_with(_PS_VERSION_, '1.7.6'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/{id}/edit", name="admin_inpost_izi_products_edit", methods={"GET", "POST"}, requirements={"id"="\d+"})
|
||||
*/
|
||||
public function edit(int $id, Request $request, CommandBusInterface $bus, HotProductRepositoryInterface $repository, ProductRepository $productRepository): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
if (null === $this->apiConfiguration->getClientCredentials()) {
|
||||
return $this->redirectToRoute('admin_inpost_izi_config_general');
|
||||
}
|
||||
|
||||
if (null === $product = $repository->find($id)) {
|
||||
$this->addFlash('error', $this->translator->l('Hot product was not found.', self::TRANSLATION_SOURCE));
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_products_index');
|
||||
}
|
||||
|
||||
$command = UpdateHotProductCommand::for($product)->setCreateIfNotFound(true);
|
||||
$form = $this->createForm(UpdateHotProductType::class, $command);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
/** @var Status $status */
|
||||
$status = $bus->handle($form->getData());
|
||||
if (Status::Active() === $status) {
|
||||
$this->addFlash('success', $this->trans('Successful update.', [], 'Admin.Notifications.Success'));
|
||||
} else {
|
||||
$this->addFlash('success', $this->translator->l('Hot product has been updated successfully and will be awaiting approval.', self::TRANSLATION_SOURCE));
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_products_index');
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleUpdateError($e, $request);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('@Modules/inpostizi/views/templates/admin/hot_products/edit.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'layoutTitle' => sprintf(
|
||||
$this->translator->l('Edit hot product: %s', self::TRANSLATION_SOURCE),
|
||||
$productRepository->getProductNameByProductId($product->getProductId(), (int) $this->context->language->id, $product->getCombinationId())
|
||||
),
|
||||
'headerTabContent' => $this->renderNav($request),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/{id}/delete", name="admin_inpost_izi_products_delete", methods={"DELETE"}, requirements={"id"="\d+"})
|
||||
*/
|
||||
public function delete(int $id, Request $request, CommandBusInterface $bus): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
if (null === $this->apiConfiguration->getClientCredentials()) {
|
||||
return $this->redirectToRoute('admin_inpost_izi_config_general');
|
||||
}
|
||||
|
||||
if (!$this->isCsrfTokenValid('inpost-izi-delete-product', $request->request->get('_csrf_token'))) {
|
||||
$this->addFlash('error', $this->translator->l('The CSRF token is invalid.', self::TRANSLATION_SOURCE));
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_products_index');
|
||||
}
|
||||
|
||||
$command = new DeleteHotProductCommand($id);
|
||||
|
||||
try {
|
||||
$bus->handle($command);
|
||||
$this->addFlash('success', $this->translator->l('Hot product has been deleted successfully.', self::TRANSLATION_SOURCE));
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleUpdateError($e, $request);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_products_index');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/import/{referenceId}", name="admin_inpost_izi_products_api_import", methods={"POST"})
|
||||
*/
|
||||
public function import(string $referenceId, Request $request, CommandBusInterface $bus): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
if (null === $this->apiConfiguration->getClientCredentials()) {
|
||||
return $this->redirectToRoute('admin_inpost_izi_config_general');
|
||||
}
|
||||
|
||||
if (!$this->isCsrfTokenValid('inpost-izi-import-product', $request->request->get('_csrf_token'))) {
|
||||
$this->addFlash('error', $this->translator->l('The CSRF token is invalid.', self::TRANSLATION_SOURCE));
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_products_index');
|
||||
}
|
||||
|
||||
if (null !== $shopId = $this->shopContext->getContextShopID()) {
|
||||
$shopId = (int) $shopId;
|
||||
}
|
||||
|
||||
try {
|
||||
$command = new ImportHotProductCommand($referenceId, $shopId);
|
||||
$bus->handle($command);
|
||||
|
||||
$this->addFlash('success', $this->translator->l('Product has been imported successfully.', self::TRANSLATION_SOURCE));
|
||||
} catch (HotProductExistsException $e) {
|
||||
$this->addFlash('error', $this->translator->l('There already exists a hot product for product and combination.', self::TRANSLATION_SOURCE));
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleUpdateError($e, $request);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_products_index');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/delete-remote/{referenceId}", name="admin_inpost_izi_products_api_delete", methods={"DELETE"})
|
||||
*/
|
||||
public function deleteRemote(string $referenceId, Request $request, CommandBusInterface $bus): Response
|
||||
{
|
||||
$this->checkAccess();
|
||||
|
||||
if (null === $this->apiConfiguration->getClientCredentials()) {
|
||||
return $this->redirectToRoute('admin_inpost_izi_config_general');
|
||||
}
|
||||
|
||||
if (!$this->isCsrfTokenValid('inpost-izi-delete-remote-product', $request->request->get('_csrf_token'))) {
|
||||
$this->addFlash('error', $this->translator->l('The CSRF token is invalid.', self::TRANSLATION_SOURCE));
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_products_index');
|
||||
}
|
||||
|
||||
try {
|
||||
$command = new DeleteRemoteProductCommand($referenceId);
|
||||
$bus->handle($command);
|
||||
|
||||
$this->addFlash('success', $this->translator->l('Product has been deleted from API successfully.', self::TRANSLATION_SOURCE));
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleUpdateError($e, $request);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_inpost_izi_products_index');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @Route(path="/autocomplete", name="admin_inpost_izi_products_autocomplete", methods={"GET"})
|
||||
*/
|
||||
public function autocomplete(Request $request, ProductRepository $repository): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(...self::getProductsReadPermission());
|
||||
|
||||
if ('' === $query = (string) $request->query->get('query')) {
|
||||
throw new UnprocessableEntityHttpException('Query cannot be empty.');
|
||||
}
|
||||
|
||||
if (0 >= $page = $request->query->getInt('page', 1)) {
|
||||
throw new UnprocessableEntityHttpException('Page number must be greater than 0.');
|
||||
}
|
||||
|
||||
$qb = $repository->createSearchQueryBuilder($query, (int) $this->context->language->id, (int) $this->context->shop->id);
|
||||
$countQb = clone $qb;
|
||||
|
||||
$results = $qb
|
||||
->limit(10, ($page - 1) * 10)
|
||||
->build()
|
||||
->getResult();
|
||||
|
||||
$count = (int) $countQb
|
||||
->setSelect('COUNT(DISTINCT p.id_product)')
|
||||
->build()
|
||||
->getSingleScalarResult();
|
||||
|
||||
if ($page < ceil($count / 10)) {
|
||||
$parameters = array_merge($request->attributes->get('_route_params'), $request->query->all(), ['page' => $page + 1]);
|
||||
$nextPage = $this->generateUrl($request->attributes->get('_route'), $parameters);
|
||||
} else {
|
||||
$nextPage = null;
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'results' => array_map(static function (\Product $product): array {
|
||||
$label = $product->name ?? 'Product #' . $product->id;
|
||||
|
||||
if ($product->reference) {
|
||||
$label .= ' (ref. ' . $product->reference . ')';
|
||||
}
|
||||
|
||||
return [
|
||||
'value' => $product->id,
|
||||
'text' => $label,
|
||||
];
|
||||
}, $results),
|
||||
'next_page' => $nextPage,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getRequiredPermissions(): iterable
|
||||
{
|
||||
yield from parent::getRequiredPermissions();
|
||||
yield self::getProductsReadPermission();
|
||||
}
|
||||
|
||||
private static function getProductsReadPermission(): array
|
||||
{
|
||||
// appending underscore to controller name was required before PS 1.7.5
|
||||
return [PageVoter::READ, 'AdminProducts_'];
|
||||
}
|
||||
|
||||
private function handleUpdateError(\Throwable $e, Request $request): void
|
||||
{
|
||||
if ($e instanceof HotProductExceptionInterface) {
|
||||
$this->addFlash('error', $e->getMessage());
|
||||
} elseif ($e instanceof MaxProductLimitReachedException) {
|
||||
$this->addFlash('error', $this->translator->l('Maximum number of hot products reached.', self::TRANSLATION_SOURCE));
|
||||
} elseif ($e instanceof BasketAppException) {
|
||||
$this->addFlash('error', sprintf('Basket App API error "%s": "%s".', $e->getError()->getCode(), $e->getMessage()));
|
||||
} elseif ($e instanceof NetworkExceptionInterface || $e instanceof OAuth2ExceptionInterface) {
|
||||
$this->addFlash('error', 'API connection error.');
|
||||
} elseif ($e instanceof HttpExceptionInterface) {
|
||||
$this->addFlash('error', sprintf('Unexpected Basket App API response status code: %d.', $e->getResponse()->getStatusCode()));
|
||||
} else {
|
||||
$this->handleError($e, $request);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace izi\prestashop\Controller\Api;
|
||||
|
||||
use izi\prestashop\BasketApp\BasketAppClientInterface;
|
||||
use izi\prestashop\CommandBusInterface;
|
||||
use izi\prestashop\MerchantApi\Exception\InternalServerErrorException;
|
||||
use izi\prestashop\MerchantApi\Exception\MalformedRequestException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Serializer\Exception\ExceptionInterface;
|
||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
|
||||
abstract class AbstractApiController
|
||||
{
|
||||
/**
|
||||
* @var SerializerInterface
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* @var CommandBusInterface
|
||||
*/
|
||||
protected $bus;
|
||||
|
||||
public function __construct(SerializerInterface $serializer, CommandBusInterface $bus)
|
||||
{
|
||||
$this->serializer = $serializer;
|
||||
$this->bus = $bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @param class-string<T> $class
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
protected function decodeRequest(Request $request, string $class)
|
||||
{
|
||||
try {
|
||||
return $this->serializer->deserialize($request->getContent(), $class, 'json', [
|
||||
'datetime_format' => BasketAppClientInterface::DATETIME_FORMAT,
|
||||
'datetime_timezone' => BasketAppClientInterface::DATETIME_ZONE,
|
||||
]);
|
||||
} catch (UnexpectedValueException $e) {
|
||||
throw new MalformedRequestException('Could not decode request.', 0, $e);
|
||||
} catch (ExceptionInterface $e) {
|
||||
throw new InternalServerErrorException('Could not decode request.', 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
modules/inpostizi/src/Controller/Api/BasketController.php
Normal file
83
modules/inpostizi/src/Controller/Api/BasketController.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace izi\prestashop\Controller\Api;
|
||||
|
||||
use izi\prestashop\BasketApp\BasketAppClientInterface;
|
||||
use izi\prestashop\MerchantApi\Command\AddProductToBasketCommand;
|
||||
use izi\prestashop\MerchantApi\Command\ConfirmBasketBindingCommand;
|
||||
use izi\prestashop\MerchantApi\Command\DeleteBasketBindingCommand;
|
||||
use izi\prestashop\MerchantApi\Command\GetBasketCommand;
|
||||
use izi\prestashop\MerchantApi\Command\UpdateBasketCommand;
|
||||
use izi\prestashop\MerchantApi\Model\Basket\Request\BasketEvent;
|
||||
use izi\prestashop\MerchantApi\Model\Basket\Request\BasketId;
|
||||
use izi\prestashop\MerchantApi\Model\Basket\Request\BindingConfirmation;
|
||||
use izi\prestashop\MerchantApi\Model\Basket\Response\Basket;
|
||||
use izi\prestashop\MerchantApi\Model\Basket\Response\IdentifiableBasket;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
final class BasketController extends AbstractApiController
|
||||
{
|
||||
public function get(string $basketId): JsonResponse
|
||||
{
|
||||
/** @var Basket $basket */
|
||||
$basket = $this->bus->handle(new GetBasketCommand($basketId));
|
||||
|
||||
return $this->basketResponse($basket);
|
||||
}
|
||||
|
||||
public function confirm(string $basketId, Request $request): JsonResponse
|
||||
{
|
||||
$confirmation = $this->decodeRequest($request, BindingConfirmation::class);
|
||||
$command = new ConfirmBasketBindingCommand($basketId, $confirmation);
|
||||
|
||||
/** @var Basket $basket */
|
||||
$basket = $this->bus->handle($command);
|
||||
|
||||
return $this->basketResponse($basket);
|
||||
}
|
||||
|
||||
public function update(string $basketId, Request $request): JsonResponse
|
||||
{
|
||||
$event = $this->decodeRequest($request, BasketEvent::class);
|
||||
$command = new UpdateBasketCommand($basketId, $event);
|
||||
|
||||
/** @var Basket $basket */
|
||||
$basket = $this->bus->handle($command);
|
||||
|
||||
return $this->basketResponse($basket);
|
||||
}
|
||||
|
||||
public function deleteBinding(string $basketId): JsonResponse
|
||||
{
|
||||
$this->bus->handle(new DeleteBasketBindingCommand($basketId));
|
||||
|
||||
return JsonResponse::create()->setContent(null);
|
||||
}
|
||||
|
||||
public function addProduct(string $productId, Request $request): JsonResponse
|
||||
{
|
||||
$basketId = $this->decodeRequest($request, BasketId::class);
|
||||
$command = new AddProductToBasketCommand($productId, $basketId);
|
||||
|
||||
/** @var IdentifiableBasket $basket */
|
||||
$basket = $this->bus->handle($command);
|
||||
|
||||
return $this->basketResponse($basket);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Basket|IdentifiableBasket $basket
|
||||
*/
|
||||
private function basketResponse($basket): JsonResponse
|
||||
{
|
||||
$data = $this->serializer->serialize($basket, 'json', [
|
||||
'datetime_format' => BasketAppClientInterface::DATETIME_FORMAT,
|
||||
'datetime_timezone' => BasketAppClientInterface::DATETIME_ZONE,
|
||||
]);
|
||||
|
||||
return JsonResponse::create()->setContent($data);
|
||||
}
|
||||
}
|
||||
59
modules/inpostizi/src/Controller/Api/OrderController.php
Normal file
59
modules/inpostizi/src/Controller/Api/OrderController.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace izi\prestashop\Controller\Api;
|
||||
|
||||
use izi\prestashop\BasketApp\BasketAppClientInterface;
|
||||
use izi\prestashop\MerchantApi\Command\CreateOrderCommand;
|
||||
use izi\prestashop\MerchantApi\Command\GetOrderCommand;
|
||||
use izi\prestashop\MerchantApi\Command\UpdateOrderCommand;
|
||||
use izi\prestashop\MerchantApi\Model\Order\Request\CreateOrderRequest;
|
||||
use izi\prestashop\MerchantApi\Model\Order\Request\OrderEvent;
|
||||
use izi\prestashop\MerchantApi\Model\Order\Response\Order;
|
||||
use izi\prestashop\MerchantApi\Model\Order\Response\OrderStatusData;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
final class OrderController extends AbstractApiController
|
||||
{
|
||||
public function create(Request $request): JsonResponse
|
||||
{
|
||||
$data = $this->decodeRequest($request, CreateOrderRequest::class);
|
||||
$command = new CreateOrderCommand($data);
|
||||
|
||||
/** @var Order $order */
|
||||
$order = $this->bus->handle($command);
|
||||
|
||||
return $this->orderResponse($order, 201);
|
||||
}
|
||||
|
||||
public function get(string $orderId): JsonResponse
|
||||
{
|
||||
/** @var Order $order */
|
||||
$order = $this->bus->handle(new GetOrderCommand($orderId));
|
||||
|
||||
return $this->orderResponse($order);
|
||||
}
|
||||
|
||||
public function update(string $orderId, Request $request): JsonResponse
|
||||
{
|
||||
$event = $this->decodeRequest($request, OrderEvent::class);
|
||||
$command = new UpdateOrderCommand($orderId, $event);
|
||||
|
||||
/** @var OrderStatusData $orderStatus */
|
||||
$orderStatus = $this->bus->handle($command);
|
||||
|
||||
return new JsonResponse($orderStatus);
|
||||
}
|
||||
|
||||
private function orderResponse(Order $order, int $status = 200): JsonResponse
|
||||
{
|
||||
$data = $this->serializer->serialize($order, 'json', [
|
||||
'datetime_format' => BasketAppClientInterface::DATETIME_FORMAT,
|
||||
'datetime_timezone' => BasketAppClientInterface::DATETIME_ZONE,
|
||||
]);
|
||||
|
||||
return JsonResponse::create(null, $status)->setContent($data);
|
||||
}
|
||||
}
|
||||
47
modules/inpostizi/src/Controller/Api/ProductController.php
Normal file
47
modules/inpostizi/src/Controller/Api/ProductController.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace izi\prestashop\Controller\Api;
|
||||
|
||||
use izi\prestashop\BasketApp\BasketAppClientInterface;
|
||||
use izi\prestashop\MerchantApi\Command\GetProductsCommand;
|
||||
use izi\prestashop\MerchantApi\Model\Product\Response\Products;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
final class ProductController extends AbstractApiController
|
||||
{
|
||||
public function getProducts(Request $request): JsonResponse
|
||||
{
|
||||
$pageIndex = $request->query->get('page_index');
|
||||
$pageSize = $request->query->get('page_size');
|
||||
$productIds = $request->query->get('product_ids');
|
||||
|
||||
if (null !== $productIds && !is_array($productIds)) {
|
||||
$productIds = explode(',', $productIds);
|
||||
}
|
||||
|
||||
$command = new GetProductsCommand(
|
||||
$request->attributes->getInt('_inpost_izi_shop_id'),
|
||||
$pageIndex ? (int) $pageIndex : null,
|
||||
$pageSize ? (int) $pageSize : null,
|
||||
$productIds
|
||||
);
|
||||
|
||||
/** @var Products $products */
|
||||
$products = $this->bus->handle($command);
|
||||
|
||||
return $this->productsResponse($products);
|
||||
}
|
||||
|
||||
private function productsResponse(Products $products): JsonResponse
|
||||
{
|
||||
$data = $this->serializer->serialize($products, 'json', [
|
||||
'datetime_format' => BasketAppClientInterface::DATETIME_FORMAT,
|
||||
'datetime_timezone' => BasketAppClientInterface::DATETIME_ZONE,
|
||||
]);
|
||||
|
||||
return JsonResponse::create()->setContent($data);
|
||||
}
|
||||
}
|
||||
177
modules/inpostizi/src/Controller/WidgetController.php
Normal file
177
modules/inpostizi/src/Controller/WidgetController.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace izi\prestashop\Controller;
|
||||
|
||||
use izi\prestashop\BasketApp\Exception\BasketAppException;
|
||||
use izi\prestashop\Command\GetBasketBindingKeyCommand;
|
||||
use izi\prestashop\Command\GetOrderConfirmationUrlCommand;
|
||||
use izi\prestashop\CommandBusInterface;
|
||||
use izi\prestashop\DependencyInjection\ServiceSubscriberInterface;
|
||||
use izi\prestashop\Entities\BasketInterface;
|
||||
use izi\prestashop\Entities\Cart;
|
||||
use izi\prestashop\Handler\Result\BasketBindingKey;
|
||||
use izi\prestashop\Http\Exception\HttpExceptionInterface as BasketAppHttpException;
|
||||
use izi\prestashop\Security\Voter\BindingWidgetVoter;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
final class WidgetController implements ServiceSubscriberInterface
|
||||
{
|
||||
public const TRANSLATION_SOURCE = 'widgetcontroller';
|
||||
|
||||
/**
|
||||
* @var \Module
|
||||
*/
|
||||
private $module;
|
||||
|
||||
/**
|
||||
* @var \Context
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @var CommandBusInterface
|
||||
*/
|
||||
private $bus;
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
|
||||
public function __construct(\Module $module, \Context $context, CommandBusInterface $bus, ContainerInterface $container)
|
||||
{
|
||||
$this->module = $module;
|
||||
$this->context = $context;
|
||||
$this->bus = $bus;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public static function getSubscribedServices(): array
|
||||
{
|
||||
return [
|
||||
'?' . AuthorizationCheckerInterface::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function getBindingKey(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$this->denyAccessUnlessGranted(BindingWidgetVoter::VIEW, $request);
|
||||
|
||||
if (!\Validate::isLoadedObject($this->context->cart)) {
|
||||
return new JsonResponse([
|
||||
'message' => $this->module->l('Cart does not exist.', self::TRANSLATION_SOURCE),
|
||||
'error_code' => 'CART_NOT_FOUND',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$command = new GetBasketBindingKeyCommand(
|
||||
$this->createBasket(),
|
||||
$request->query->getBoolean('refresh')
|
||||
);
|
||||
|
||||
/** @var BasketBindingKey $result */
|
||||
$result = $this->bus->handle($command);
|
||||
$this->context->cookie->inpostizi_basket_id = $result->getBasketId();
|
||||
|
||||
return new JsonResponse($result->getBindingKey());
|
||||
} catch (\Exception $e) {
|
||||
return $this->handleException($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function getOrderConfirmationUrl(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$command = new GetOrderConfirmationUrlCommand((string) $this->context->cookie->inpostizi_basket_id);
|
||||
|
||||
/** @var string $url */
|
||||
$url = $this->bus->handle($command);
|
||||
|
||||
return new JsonResponse($url);
|
||||
} catch (\Exception $e) {
|
||||
return $this->handleException($e);
|
||||
}
|
||||
}
|
||||
|
||||
private function createBasket(): BasketInterface
|
||||
{
|
||||
if (!\Validate::isLoadedObject($this->context->cart)) {
|
||||
throw new BadRequestHttpException($this->module->l('Cart does not exist.', self::TRANSLATION_SOURCE));
|
||||
}
|
||||
|
||||
return new Cart($this->context->cart);
|
||||
}
|
||||
|
||||
private function handleException(\Exception $e): JsonResponse
|
||||
{
|
||||
if ($e instanceof HttpExceptionInterface) {
|
||||
return new JsonResponse([
|
||||
'message' => $e->getMessage(),
|
||||
], $e->getStatusCode(), $e->getHeaders());
|
||||
}
|
||||
|
||||
if ($e instanceof NetworkExceptionInterface || $e instanceof BasketAppException || $e instanceof BasketAppHttpException) {
|
||||
return new JsonResponse([
|
||||
'message' => $this->module->l('There was a problem communicating with the mobile application. Please try again later.', self::TRANSLATION_SOURCE),
|
||||
], 502);
|
||||
}
|
||||
|
||||
if ($e instanceof \DomainException) {
|
||||
return new JsonResponse([
|
||||
'message' => $this->module->l('Your request could not be processed.', self::TRANSLATION_SOURCE),
|
||||
], 422);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'message' => $this->module->l('Something went wrong. Please try again later.', self::TRANSLATION_SOURCE),
|
||||
], 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $attributes
|
||||
* @param mixed $subject
|
||||
*/
|
||||
private function denyAccessUnlessGranted($attributes, $subject = null, string $message = 'Access Denied.'): void
|
||||
{
|
||||
if ($this->isGranted($attributes, $subject)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new AccessDeniedHttpException($message);
|
||||
}
|
||||
|
||||
private function isGranted($attributes, $subject = null): bool
|
||||
{
|
||||
if (null === $authChecker = $this->get(AuthorizationCheckerInterface::class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $authChecker->isGranted($attributes, $subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of object
|
||||
*
|
||||
* @param class-string<T> $name
|
||||
*
|
||||
* @return T|null
|
||||
*/
|
||||
private function get(string $name)
|
||||
{
|
||||
if (!$this->container->has($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->container->get($name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user