diff --git a/Api/Resource/RestockingAlert.php b/Api/Resource/RestockingAlert.php new file mode 100644 index 0000000..d7289c5 --- /dev/null +++ b/Api/Resource/RestockingAlert.php @@ -0,0 +1,181 @@ + [self::GROUP_ADMIN_READ]], + denormalizationContext: ['groups' => [self::GROUP_ADMIN_WRITE]] +)] +#[ApiResource( + operations: [ + new Get( + uriTemplate: '/front/stock-alert/{id}', + name: 'api_stock_alert_get_id_front' + ), + new GetCollection( + uriTemplate: '/front/stock-alerts', + name: 'api_stock_alert_get_collection_front' + ), + new Post( + uriTemplate: '/front/stock-alert', + name: 'api_stock_alert_post_id_front' + ), + new Delete( + uriTemplate: '/front/stock-alert/{id}', + name: 'api_stock_alert_delete_id_front' + ), + new Patch( + uriTemplate: '/front/stock-alert/{id}', + name: 'api_stock_alert_patch_id_front' + ) + ], + normalizationContext: ['groups' => [self::GROUP_FRONT_READ]], + denormalizationContext: ['groups' => [self::GROUP_FRONT_WRITE]] +)] +class RestockingAlert implements PropelResourceInterface +{ + use PropelResourceTrait; + + public const GROUP_ADMIN_READ = 'admin:stock_alert:read'; + public const GROUP_ADMIN_WRITE = 'admin:stock_alert:write'; + public const GROUP_FRONT_READ = 'front:stock_alert:read'; + public const GROUP_FRONT_WRITE = 'front:stock_alert:write'; + + /** + * @var int|null + */ + #[Groups([self::GROUP_ADMIN_READ, self::GROUP_FRONT_READ])] + public ?int $id = null; + + /** + * @var ProductSaleElements|null + */ + #[Groups([self::GROUP_ADMIN_READ, self::GROUP_ADMIN_WRITE, self::GROUP_FRONT_READ, self::GROUP_FRONT_WRITE])] + #[Relation(targetResource: ProductSaleElements::class)] + public ?ProductSaleElements $productSaleElements = null; + + /** + * @var string|null + */ + #[Groups([self::GROUP_ADMIN_READ, self::GROUP_ADMIN_WRITE, self::GROUP_FRONT_READ, self::GROUP_FRONT_WRITE])] + public ?string $email = null; + + /** + * @var string|null + */ + #[Groups([self::GROUP_ADMIN_READ, self::GROUP_ADMIN_WRITE, self::GROUP_FRONT_READ, self::GROUP_FRONT_WRITE])] + public ?string $locale = null; + + /** + * @return int|null + */ + public function getId(): ?int + { + return $this->id; + } + + /** + * @param int|null $id + * @return void + */ + public function setId(?int $id): void + { + $this->id = $id; + } + + /** + * @return ProductSaleElements|null + */ + public function getProductSaleElements(): ?ProductSaleElements + { + return $this->productSaleElements; + } + + /** + * @param ProductSaleElements|null $productSaleElements + * @return void + */ + public function setProductSaleElements(?ProductSaleElements $productSaleElements): void + { + $this->productSaleElements = $productSaleElements; + } + + /** + * @return string|null + */ + public function getEmail(): ?string + { + return $this->email; + } + + /** + * @param string|null $email + * @return void + */ + public function setEmail(?string $email): void + { + $this->email = $email; + } + + /** + * @return string|null + */ + public function getLocale(): ?string + { + return $this->locale; + } + + /** + * @param string|null $locale + * @return void + */ + public function setLocale(?string $locale): void + { + $this->locale = $locale; + } + + /** + * @return TableMap|null + */ + #[Ignore] public static function getPropelRelatedTableMap(): ?TableMap + { + return new RestockingAlertTableMap(); + } +} \ No newline at end of file diff --git a/Config/module.xml b/Config/module.xml index 996524a..0949e0e 100644 --- a/Config/module.xml +++ b/Config/module.xml @@ -13,11 +13,15 @@ en_US fr_FR - 2.0.2 + 2.0.4 Julien Chanséaume julien@thelia.net + + Loïc MO + lmo@openstudio.fr + classic 2.5.0 rc diff --git a/Config/routing.xml b/Config/routing.xml index bb4b0b7..b2ce795 100644 --- a/Config/routing.xml +++ b/Config/routing.xml @@ -3,13 +3,4 @@ - - - diff --git a/Controller/StockAlertBackOfficeController.php b/Controller/StockAlertBackOfficeController.php index 9edd4fc..df9bc04 100644 --- a/Controller/StockAlertBackOfficeController.php +++ b/Controller/StockAlertBackOfficeController.php @@ -12,7 +12,7 @@ namespace StockAlert\Controller; - +use Propel\Runtime\Exception\PropelException; use StockAlert\Form\StockAlertConfig; use StockAlert\Model\RestockingAlertQuery; use StockAlert\StockAlert; @@ -20,39 +20,34 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\Session; use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Core\HttpFoundation\Response; use Thelia\Core\Template\ParserContext; use Thelia\Form\Exception\FormValidationException; use Thelia\Model\ConfigQuery; use Symfony\Component\Routing\Annotation\Route; use Thelia\Tools\URL; -/** - * @Route("/admin/module/stockalert", name="stockalert_back") - * Class StockAlertBackOfficeController - * @package StockAlert\Controller - * @author Baixas Alban - * @author Julien Chanséaume - */ +#[Route(path: '/admin/module/stockalert', name: 'stockalert_back')] class StockAlertBackOfficeController extends BaseAdminController { - /** - * @Route("/configuration", name="_configuration", methods="POST") + * @param ParserContext $parserContext + * @return Response|RedirectResponse|string|\Symfony\Component\HttpFoundation\Response|null */ - public function configuration(ParserContext $parserContext) + #[Route(path: '/configuration', name: '_configuration', methods: ['POST'])] + public function configuration(ParserContext $parserContext): Response|RedirectResponse|string|\Symfony\Component\HttpFoundation\Response|null { $errorMessage = null; - $form = $this->createForm(StockAlertConfig::getName()); try { $configForm = $this->validateForm($form)->getData(); - ConfigQuery::write(StockAlert::CONFIG_ENABLED, $configForm['enabled']); + ConfigQuery::write(StockAlert::CONFIG_ENABLED, $configForm['enabled']); ConfigQuery::write(StockAlert::CONFIG_THRESHOLD, $configForm['threshold']); $emails = str_replace(' ', '', $configForm['emails']); - ConfigQuery::write(StockAlert::CONFIG_EMAILS, $emails); - ConfigQuery::write(StockAlert::CONFIG_NOTIFY, $configForm['notify']); + ConfigQuery::write(StockAlert::CONFIG_EMAILS, $emails); + ConfigQuery::write(StockAlert::CONFIG_NOTIFY, $configForm['notify']); return $this->generateSuccessRedirect($form); } catch (FormValidationException $e) { @@ -68,23 +63,36 @@ public function configuration(ParserContext $parserContext) ->setGeneralError($errorMessage); return $this->render( - "module-configure", + 'module-configure', [ - "module_code" => StockAlert::getModuleCode() + 'module_code' => StockAlert::getModuleCode(), ] ); } /** - * @Route("/delete", name="_delete", methods="GET") + * @param RequestStack $requestStack + * @param Session $session + * @return RedirectResponse + * @throws PropelException */ - public function deleteEmail(RequestStack $requestStack, Session $session) + #[Route(path: '/delete', name: '_delete', methods: ['GET'])] + public function deleteEmail(RequestStack $requestStack, Session $session): RedirectResponse { - $restockingAlertId = $requestStack->getCurrentRequest()->get("id"); + $restockingAlertId = $requestStack->getCurrentRequest()->get('id'); + if ($restockingAlertId) { - $restockingAlert = RestockingAlertQuery::create()->filterById($restockingAlertId)->findOne(); - $restockingAlert->delete(); + $restockingAlert = RestockingAlertQuery::create() + ->filterById($restockingAlertId) + ->findOne(); + + if (null !== $restockingAlert) { + $restockingAlert->delete(); + } } - return new RedirectResponse(URL::getInstance()->absoluteUrl($session->getReturnToUrl())); + + return new RedirectResponse( + URL::getInstance()->absoluteUrl($session->getReturnToUrl()) + ); } } diff --git a/Controller/StockAlertFrontOfficeController.php b/Controller/StockAlertFrontOfficeController.php index 978cf64..b15bba8 100644 --- a/Controller/StockAlertFrontOfficeController.php +++ b/Controller/StockAlertFrontOfficeController.php @@ -12,72 +12,69 @@ namespace StockAlert\Controller; -use StockAlert\Event\StockAlertEvent; -use StockAlert\Event\StockAlertEvents; use StockAlert\Form\StockAlertSubscribe; -use StockAlert\StockAlert; +use StockAlert\Service\StockAlertSubscriptionService; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Thelia\Controller\Front\BaseFrontController; -use Thelia\Core\Translation\Translator; -use Thelia\Form\Exception\FormValidationException; +use Thelia\Core\HttpFoundation\Response; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\Annotation\Route; -/** - * @Route("/module/stockalert", name="stockalert_front") - * Class RestockingAlertFrontOfficeController - * @package StockAlert\Controller - * @author Baixas Alban - * @author Julien Chanséaume - */ +#[Route(path: "/module/stockalert", name: "stockalert_front")] class StockAlertFrontOfficeController extends BaseFrontController { - /** - * @Route("/subscribe", name="_subscribe", methods="POST") + * @param StockAlertSubscriptionService $subscriptionService + * @param RequestStack $requestStack + * @return Response|RedirectResponse + * @throws \JsonException */ - public function subscribe(EventDispatcherInterface $eventDispatcher, RequestStack $requestStack) + #[Route(path: "/subscribe", name: "_subscribe", methods: ["POST"])] + public function subscribe( + StockAlertSubscriptionService $subscriptionService, + RequestStack $requestStack + ): Response|RedirectResponse { - $success = true; - - $form = $this->createForm(StockAlertSubscribe::getName(), FormType::class, [], ['csrf_protection' => false]); + $request = $requestStack->getCurrentRequest(); - try { - $subscribeForm = $this->validateForm($form)->getData(); + $form = $this->createForm( + StockAlertSubscribe::getName(), + FormType::class, + [], + ['csrf_protection' => false] + ); - $subscriberEvent = new StockAlertEvent( - $subscribeForm['product_sale_elements_id'], - $subscribeForm['email'], - $subscribeForm['newsletter'], - $requestStack->getCurrentRequest()->getSession()->getLang()->getLocale() - ); + $data = []; + $success = true; - $eventDispatcher->dispatch($subscriberEvent, StockAlertEvents::STOCK_ALERT_SUBSCRIBE); + try { + $data = $this->validateForm($form)->getData(); - $message = Translator::getInstance()->trans( - "C’est noté ! Vous recevrez un e-mail dès que le produit sera de nouveau en stock.", - [], - StockAlert::MESSAGE_DOMAIN + $result = $subscriptionService->subscribe( + $data['product_sale_elements_id'], + $data['email'], + $data['newsletter'] ?? false ); + $success = $result['success']; + $message = $result['message']; } catch (\Exception $e) { $success = false; $message = $e->getMessage(); } - if (!$requestStack->getCurrentRequest()->isXmlHttpRequest()) { - $requestStack->getCurrentRequest()->getSession()->getFlashBag()->set('flashMessage', $message); - return RedirectResponse::create($requestStack->getCurrentRequest()->get('stockalert_subscribe_form')['success_url']); + if (!$request?->isXmlHttpRequest()) { + $request?->getSession() + ->getFlashBag() + ->set('flashMessage', $message); + + $redirectUrl = $data['success_url'] ?? $this->generateUrl('homepage'); + return new RedirectResponse($redirectUrl); } - return $this->jsonResponse( - json_encode( - [ - "success" => $success, - "message" => $message - ] - ) - ); + return $this->jsonResponse(json_encode([ + 'success' => $success, + 'message' => $message, + ], JSON_THROW_ON_ERROR)); } -} +} \ No newline at end of file diff --git a/Loop/RestockingAlertLoop.php b/Loop/RestockingAlertLoop.php index 7e3807e..1700ce5 100644 --- a/Loop/RestockingAlertLoop.php +++ b/Loop/RestockingAlertLoop.php @@ -14,6 +14,7 @@ namespace StockAlert\Loop; use Propel\Runtime\ActiveQuery\Criteria; +use Propel\Runtime\ActiveQuery\ModelCriteria; use StockAlert\Model\RestockingAlert; use StockAlert\Model\RestockingAlertQuery; use Thelia\Core\Template\Element\BaseLoop; @@ -32,36 +33,8 @@ */ class RestockingAlertLoop extends BaseLoop implements PropelSearchLoopInterface { - protected $timestampable = true; - /** - * @param LoopResult $loopResult - * - * @return LoopResult - */ - public function parseResults(LoopResult $loopResult) - { - /** @var RestockingAlert $item */ - foreach ($loopResult->getResultDataCollection() as $item) { - - $loopResultRow = new LoopResultRow($item); - - $loopResultRow - ->set("ID", $item->getId()) - ->set("PRODUCT_SALE_ELEMENTS_ID", $item->getProductSaleElementsId()) - ->set("EMAIL", $item->getEmail()) - ->set("LOCALE", $item->getLocale()) - ; - - $this->addOutputFields($loopResultRow, $item); - - $loopResult->addRow($loopResultRow); - } - - return $loopResult; - } - /** * Definition of loop arguments * @@ -86,7 +59,7 @@ public function parseResults(LoopResult $loopResult) * * @return \Thelia\Core\Template\Loop\Argument\ArgumentCollection */ - protected function getArgDefinitions() + protected function getArgDefinitions(): ArgumentCollection { return new ArgumentCollection( Argument::createIntListTypeArgument('id'), @@ -119,9 +92,9 @@ protected function getArgDefinitions() /** * this method returns a Propel ModelCriteria * - * @return \Propel\Runtime\ActiveQuery\ModelCriteria + * @return ModelCriteria */ - public function buildModelCriteria() + public function buildModelCriteria(): ModelCriteria { $query = RestockingAlertQuery::create(); @@ -183,4 +156,31 @@ public function buildModelCriteria() return $query; } + + /** + * @param LoopResult $loopResult + * + * @return LoopResult + */ + public function parseResults(LoopResult $loopResult): LoopResult + { + /** @var RestockingAlert $item */ + foreach ($loopResult->getResultDataCollection() as $item) { + + $loopResultRow = new LoopResultRow($item); + + $loopResultRow + ->set("ID", $item->getId()) + ->set("PRODUCT_SALE_ELEMENTS_ID", $item->getProductSaleElementsId()) + ->set("EMAIL", $item->getEmail()) + ->set("LOCALE", $item->getLocale()) + ; + + $this->addOutputFields($loopResultRow, $item); + + $loopResult->addRow($loopResultRow); + } + + return $loopResult; + } } diff --git a/Service/StockAlertSubscriptionService.php b/Service/StockAlertSubscriptionService.php new file mode 100644 index 0000000..c66cd92 --- /dev/null +++ b/Service/StockAlertSubscriptionService.php @@ -0,0 +1,54 @@ +requestStack->getCurrentRequest(); + $locale = $request?->getSession()?->getLang()?->getLocale() ?? 'fr_FR'; + + $success = true; + + try { + $event = new StockAlertEvent( + $pseId, + $email, + $newsletter, + $locale + ); + + $this->eventDispatcher->dispatch($event, StockAlertEvents::STOCK_ALERT_SUBSCRIBE); + + $message = Translator::getInstance()->trans( + "C’est noté ! Vous recevrez un e-mail dès que le produit sera de nouveau en stock.", + [], + StockAlert::MESSAGE_DOMAIN + ); + } catch (\Exception $e) { + $success = false; + $message = $e->getMessage(); + } + + return [ + 'success' => $success, + 'message' => $message, + ]; + } +} diff --git a/Twig/Plugins/StockAlertPluginTwig.php b/Twig/Plugins/StockAlertPluginTwig.php new file mode 100644 index 0000000..9402df3 --- /dev/null +++ b/Twig/Plugins/StockAlertPluginTwig.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SEOne\Twig\Plugins; + +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +class StockAlertPluginTwig extends AbstractExtension +{ + public function __construct( + private readonly Environment $twig + ) {} + + public function getFunctions(): array + { + return [ + new TwigFunction('onProductDetailsBottom', [$this, 'getOnProductDetailsBottom']), + new TwigFunction('onProductJavascriptInitialization', [$this, 'getOnProductJavascriptInitialization']) + ]; + } + + /** + * @throws RuntimeError + * @throws SyntaxError + * @throws LoaderError + */ + public function getOnProductDetailsBottom(): string + { + return $this->twig->render('product-details-bottom.html.twig'); + } + + /** + * @throws SyntaxError + * @throws RuntimeError + * @throws LoaderError + */ + public function getOnProductJavascriptInitialization(): string + { + return $this->twig->render('product-javascript-initialization.html.twig'); + } +} diff --git a/templates/frontOffice/default/product-details-bottom.html b/templates/frontOffice/default/product-details-bottom.html deleted file mode 100644 index e69fe9e..0000000 --- a/templates/frontOffice/default/product-details-bottom.html +++ /dev/null @@ -1,29 +0,0 @@ - diff --git a/templates/frontOffice/default/product-details-bottom.html.twig b/templates/frontOffice/default/product-details-bottom.html.twig new file mode 100644 index 0000000..d770b46 --- /dev/null +++ b/templates/frontOffice/default/product-details-bottom.html.twig @@ -0,0 +1,35 @@ + diff --git a/templates/frontOffice/default/product.javascript-initialization.html b/templates/frontOffice/default/product.javascript-initialization.html.twig similarity index 100% rename from templates/frontOffice/default/product.javascript-initialization.html rename to templates/frontOffice/default/product.javascript-initialization.html.twig