updated some things and made plugin work with snippets and mail-template

//TODO configure menu and button on productpage
This commit is contained in:
Nils 2025-03-05 16:41:57 +01:00
parent 897ca4c8bc
commit 7510dc362f
Signed by: slinicraftet204
GPG Key ID: 78E12696BAFC2A4B
13 changed files with 332 additions and 90 deletions

View File

@ -1,7 +1,7 @@
{
"name": "slinicraftet204/notifyifavail",
"description": "benachrichtigt Kunden, sobald ein Produkt wieder verfügbar ist",
"version": "0.1.0",
"version": "0.1.41",
"type": "shopware-platform-plugin",
"license": "MIT",
"authors": [

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://developer.shopware.com/schemas/plugin.xsd">
<name>NotifyIfAvail</name>
@ -8,4 +7,19 @@
<copyright>TTT-Games</copyright>
<license>MIT</license>
<compatibility minVersion="6.5.0" />
<administration>
<menu>
<item>
<name>NotifyIfAvail</name>
<label>Benachrichtigungs-Einstellungen</label>
<parent>sw-settings-index</parent>
<position>50</position>
<target>sw.mail.template.detail</target>
<params>
<param name="id">c6d2c6141e9f44c4a3eb110d2c58c823</param>
</params>
</item>
</menu>
</administration>
</plugin>

View File

@ -2,24 +2,21 @@
namespace NotifyIfAvail\Controller;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Uuid\Uuid;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use NotifyIfAvail\Service\NotificationService;
use Psr\Log\LoggerInterface;
class NotificationController
{
private Connection $connection;
private MailerInterface $mailer;
private NotificationService $notificationService;
private LoggerInterface $logger;
public function __construct(Connection $connection, MailerInterface $mailer)
public function __construct(NotificationService $notificationService, LoggerInterface $logger)
{
$this->connection = $connection;
$this->mailer = $mailer;
$this->notificationService = $notificationService;
$this->logger = $logger;
}
/**
@ -34,13 +31,12 @@ class NotificationController
return new JsonResponse(['message' => 'Invalid data'], 400);
}
$this->connection->insert('notifyifavail_plugin_notification', [
'id' => Uuid::randomBytes(),
'product_id' => Uuid::fromHexToBytes($productId),
'email' => $email,
'created_at' => (new \DateTime())->format('Y-m-d H:i:s')
]);
return new JsonResponse(['message' => 'Successfully subscribed']);
try {
$this->notificationService->saveNotification($email, $productId);
return new JsonResponse(['message' => 'Successfully subscribed']);
} catch (\Exception $e) {
$this->logger->error('Fehler beim Speichern der Benachrichtigung: ' . $e->getMessage());
return new JsonResponse(['message' => 'An error occurred'], 500);
}
}
}

View File

@ -1,43 +1,67 @@
<?php
namespace NotifyIfAvail\Entity;
namespace NotifyIfAvail\Repository;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Uuid\Uuid;
use Doctrine\DBAL\Connection;
use Psr\Log\LoggerInterface;
class NotificationRepository
{
private EntityRepository $repository;
private Connection $connection;
private LoggerInterface $logger;
public function __construct(Connection $connection)
public function __construct(EntityRepository $repository, Connection $connection, LoggerInterface $logger)
{
$this->repository = $repository;
$this->connection = $connection;
$this->logger = $logger;
}
public function saveNotification(string $email, string $productId): void
{
$this->connection->insert('notifyifavail_plugin_notification', [
'id' => Uuid::randomBytes(),
'email' => $email,
'product_id' => Uuid::fromHexToBytes($productId),
'created_at' => (new \DateTime())->format('Y-m-d H:i:s'),
]);
try {
$notification = [
'id' => Uuid::randomHex(),
'email' => $email,
'productId' => $productId,
'createdAt' => (new \DateTime())->format(DATE_ATOM),
];
$this->repository->create([$notification], \Shopware\Core\Framework\Context::createDefaultContext());
} catch (\Exception $e) {
$this->logger->error('Fehler beim Speichern der Benachrichtigung: ' . $e->getMessage());
}
}
public function deleteNotification(string $productId): void
{
$this->connection->delete('notifyifavail_plugin_notification', [
'product_id' => Uuid::fromHexToBytes($productId),
]);
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('productId', $productId));
$result = $this->repository->search($criteria, \Shopware\Core\Framework\Context::createDefaultContext());
$idsToDelete = [];
foreach ($result->getEntities() as $entity) {
$idsToDelete[] = ['id' => $entity->getId()];
}
if (!empty($idsToDelete)) {
$this->repository->delete($idsToDelete, \Shopware\Core\Framework\Context::createDefaultContext());
}
}
public function getNotificationsForProduct(string $productId): array
{
return $this->connection->fetchFirstColumn(
"SELECT email FROM notifyifavail_plugin_notification WHERE product_id = :productId",
['productId' => Uuid::fromHexToBytes($productId)]
);
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('productId', $productId));
$notifications = $this->repository->search($criteria, \Shopware\Core\Framework\Context::createDefaultContext());
return array_map(fn ($entity) => $entity->getEmail(), $notifications->getEntities()->getElements());
}
}

143
src/NotifyIfAvail.php Normal file
View File

@ -0,0 +1,143 @@
<?php
namespace NotifyIfAvail;
use Shopware\Core\Framework\Plugin;
use Shopware\Core\Framework\Plugin\Context\ActivateContext;
use Shopware\Core\Framework\Plugin\Context\DeactivateContext;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use Shopware\Core\Framework\Plugin\Context\UninstallContext;
use Doctrine\DBAL\Connection;
use Psr\Log\LoggerInterface;
use Shopware\Core\Content\MailTemplate\Service\MailTemplateService;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\System\Snippet\SnippetService;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
class NotifyIfAvail extends Plugin
{
public function install(InstallContext $context): void
{
parent::install($context);
$this->registerMailTemplateType($context->getContext());
$this->registerEmailTemplate($context->getContext());
$this->registerSnippets($context->getContext());
}
private function registerEmailTemplate(Context $context): void
{
$mailTemplateRepository = $this->container->get('mail_template.repository');
$mailTemplateRepository->upsert([
[
'id' => 'c6d2c6141e9f44c4a3eb110d2c58c823',
'mailTemplateTypeId' => '9f5b31d14c9d4b6a8d2e8f01b5b2a3d7', // ID des Mail-Template-Typs
'systemDefault' => false,
'description' => 'Benachrichtigung, wenn Produkt wieder verfügbar ist',
'contentHtml' => file_get_contents(__DIR__ . '/Resources/email-templates/notification_email.html.twig'),
'contentPlain' => strip_tags(file_get_contents(__DIR__ . '/Resources/email-templates/notification_email.html.twig')),
'subject' => 'Ihr gewünschter Artikel ist wieder verfügbar!'
]
], $context);
}
private function registerMailTemplateType(Context $context): void
{
$mailTemplateTypeRepository = $this->container->get('mail_template_type.repository');
$mailTemplateTypeRepository->upsert([
[
'id' => '9f5b31d14c9d4b6a8d2e8f01b5b2a3d7',
'name' => 'Notify If Available Email',
'technicalName' => 'notify_if_avail_email',
'availableEntities' => ['product' => 'product'],
]
], $context);
}
private function getMailTemplateTypeId(Context $context): ?string
{
$mailTemplateTypeRepository = $this->container->get('mail_template_type.repository');
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('technicalName', 'notify_if_avail_email'));
$mailTemplateType = $mailTemplateTypeRepository->search($criteria, $context)->first();
return $mailTemplateType ? $mailTemplateType->getId() : '9f5b31d14c9d4b6a8d2e8f01b5b2a3d7';
}
private function registerSnippets(Context $context): void
{
$snippetRepository = $this->container->get('snippet.repository');
$snippetRepository->upsert([
[
'id' => '1f5b31d14c9d4b6a8d2e8f01b5b2a3d8',
'setId' => '0194da4af1f7720a8183c85d4b7fae35', // Standard-Set-ID für Shopware-Snippets
'translationKey' => 'NotifyIfAvail.notify_me',
'value' => 'Benachrichtigen, wenn verfügbar',
'author' => 'NotifyIfAvail Plugin',
],
[
'id' => '2f5b31d14c9d4b6a8d2e8f01b5b2a3d8',
'setId' => '0194da4af1f7720a8183c85d4b7fae35',
'translationKey' => 'NotifyIfAvail.email_placeholder',
'value' => 'Geben Sie Ihre E-Mail-Adresse ein',
'author' => 'NotifyIfAvail Plugin',
],
[
'id' => '3f5b31d14c9d4b6a8d2e8f01b5b2a3d8',
'setId' => '0194da4af1f7720a8183c85d4b7fae35',
'translationKey' => 'NotifyIfAvail.success_message',
'value' => 'Sie werden benachrichtigt, sobald der Artikel verfügbar ist.',
'author' => 'NotifyIfAvail Plugin',
],
[
'id' => '4f5b31d14c9d4b6a8d2e8f01b5b2a3d8',
'setId' => '0194da4af1f7720a8183c85d4c20138e', // Englische ID
'translationKey' => 'NotifyIfAvail.notify_me',
'value' => 'Notify me when available',
'author' => 'NotifyIfAvail Plugin',
],
[
'id' => '5f5b31d14c9d4b6a8d2e8f01b5b2a3d8',
'setId' => '0194da4af1f7720a8183c85d4c20138e',
'translationKey' => 'NotifyIfAvail.email_placeholder',
'value' => 'Enter your email address',
'author' => 'NotifyIfAvail Plugin',
],
[
'id' => '6f5b31d14c9d4b6a8d2e8f01b5b2a3d8',
'setId' => '0194da4af1f7720a8183c85d4c20138e',
'translationKey' => 'NotifyIfAvail.success_message',
'value' => 'You will be notified when the item is available.',
'author' => 'NotifyIfAvail Plugin',
]
], $context);
}
public function uninstall(UninstallContext $context): void
{
if (!$context->keepUserData()) {
$connection = $this->container->get(Connection::class);
$connection->executeStatement('DROP TABLE IF EXISTS notifyifavail_plugin_notification');
}
parent::uninstall($context);
}
public function activate(ActivateContext $context): void
{
parent::activate($context);
}
public function deactivate(DeactivateContext $context): void
{
parent::deactivate($context);
}
}

View File

@ -0,0 +1,4 @@
import PluginManager from 'src/plugin-system/plugin.manager';
import NotifyMe from './plugin/notify-me.plugin';
PluginManager.register('NotifyMe', NotifyMe, '[data-notify-me]');

View File

@ -0,0 +1,35 @@
import Plugin from 'src/plugin-system/plugin.class';
export default class NotifyMe extends Plugin {
init() {
this.notifyButton = this.el;
this.notifyForm = this.el.nextElementSibling;
this.emailInput = this.notifyForm.querySelector('#notify-me-email');
this.submitButton = this.notifyForm.querySelector('#submit-notify');
this.registerEvents();
}
registerEvents() {
this.notifyButton.addEventListener('click', () => this.showForm());
this.submitButton.addEventListener('click', () => this.submitForm());
}
showForm() {
this.notifyForm.style.display = 'block';
}
submitForm() {
const email = this.emailInput.value;
const productId = this.notifyButton.dataset.productId;
fetch('/notification/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `email=${encodeURIComponent(email)}&productId=${encodeURIComponent(productId)}`
})
.then(response => response.json())
.then(data => alert(data.message))
.catch(error => console.error('Error:', error));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -6,18 +6,22 @@
<services>
<service id="NotifyIfAvail\Storefront\Subscriber\ProductSubscriber">
<argument type="service" id="product.repository"/>
<tag name="kernel.event_subscriber"/>
</service>
<service id="NotifyIfAvail\Controller\NotificationController">
<argument type="service" id="doctrine.dbal.default_connection"/>
<argument type="service" id="Doctrine\DBAL\Connection"/>
<argument type="service" id="mailer.mailer"/>
<argument type="service" id="logger"/> <!-- Logger hinzugefügt -->
<tag name="controller.service_arguments"/>
</service>
<service id="NotifyIfAvail\Service\NotificationService">
<argument type="service" id="doctrine.dbal.default_connection"/>
<argument type="service" id="Doctrine\DBAL\Connection"/>
<argument type="service" id="mailer.mailer"/>
<argument type="service" id="logger"/> <!-- Logger hinzugefügt -->
</service>
</services>
</container>

View File

@ -1,13 +1,15 @@
{SALUTATION} {CUSTOMER_NAME},
{% block content %}
{{ salutation }} {{ customerName }},
Sie haben sich für eine Benachrichtigung angemeldet, sobald der folgende Artikel wieder verfügbar ist:
Sie haben sich für eine Benachrichtigung angemeldet, sobald der folgende Artikel wieder verfügbar ist:
**{PRODUCT_NAME}**
Jetzt wieder auf Lager! Sie können den Artikel hier aufrufen und bestellen:
**{{ productName }}**
Jetzt wieder auf Lager! Sie können den Artikel hier aufrufen und bestellen:
[Zum Artikel]({PRODUCT_URL})
[Zum Artikel]({{ productUrl }})
Vielen Dank für Ihr Interesse an unseren Produkten!
Vielen Dank für Ihr Interesse an unseren Produkten!
Beste Grüße,
Ihr {SHOP_NAME}-Team
Beste Grüße,
Ihr {{ shopName }}-Team
{% endblock %}

View File

@ -1,4 +1,4 @@
{% if product.extensions.notification is defined %}
{% if product.extensions.notifyIfAvail is defined and product.extensions.notifyIfAvail.enabled %}
<div id="notification-container">
<button class="btn btn-primary" id="notify-me-button">
{{ "NotifyIfAvail.notify_me"|trans }}
@ -12,23 +12,5 @@
</button>
</div>
</div>
<script>
document.getElementById('notify-me-button').addEventListener('click', function() {
document.getElementById('notify-me-form').style.display = 'block';
});
document.getElementById('submit-notify').addEventListener('click', function() {
let email = document.getElementById('notify-me-email').value;
let productId = '{{ product.id }}';
fetch('/notification/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `email=${encodeURIComponent(email)}&productId=${encodeURIComponent(productId)}`
}).then(response => response.json()).then(data => {
alert(data.message);
});
});
</script>
{% endif %}

View File

@ -6,43 +6,77 @@ use Doctrine\DBAL\Connection;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Shopware\Core\Framework\Uuid\Uuid;
use Psr\Log\LoggerInterface;
use Shopware\Core\System\SystemConfig\SystemConfigService;
class NotificationService
{
private Connection $connection;
private MailerInterface $mailer;
private LoggerInterface $logger;
private SystemConfigService $configService;
public function __construct(Connection $connection, MailerInterface $mailer)
{
public function __construct(
Connection $connection,
MailerInterface $mailer,
LoggerInterface $logger,
SystemConfigService $configService
) {
$this->connection = $connection;
$this->mailer = $mailer;
$this->logger = $logger;
$this->configService = $configService;
}
public function saveNotification(string $email, string $productId): void
{
try {
$this->connection->insert('notifyifavail_plugin_notification', [
'id' => Uuid::randomBytes(),
'email' => $email,
'product_id' => Uuid::fromHexToBytes($productId),
'created_at' => (new \DateTime())->format('Y-m-d H:i:s'),
]);
} catch (\Exception $e) {
$this->logger->error('Fehler beim Speichern der Benachrichtigung: ' . $e->getMessage());
throw $e;
}
}
public function notifyCustomers(string $productId, string $productName, string $productUrl, string $shopName): void
{
$sql = "SELECT email FROM notifyifavail_plugin_notification WHERE product_id = :productId";
$emails = $this->connection->fetchFirstColumn($sql, ['productId' => Uuid::fromHexToBytes($productId)]);
try {
$emails = $this->connection->fetchFirstColumn(
"SELECT email FROM notifyifavail_plugin_notification WHERE product_id = :productId",
['productId' => Uuid::fromHexToBytes($productId)]
);
foreach ($emails as $email) {
$message = (new Email())
->from('shop@example.com')
->to($email)
->subject('Ihr gewünschter Artikel ist wieder verfügbar!')
->html("
<p>Hallo,</p>
<p>Sie haben sich für eine Benachrichtigung angemeldet, sobald der folgende Artikel wieder verfügbar ist:</p>
<p><strong>{$productName}</strong></p>
<p><a href='{$productUrl}'>Jetzt bestellen</a></p>
<p>Vielen Dank für Ihr Interesse an unseren Produkten!</p>
<p>Beste Grüße,<br>{$shopName}-Team</p>
");
$emailSender = $this->configService->get('NotifyIfAvail.config.emailSender') ?? 'shop@example.com';
$emailSubject = $this->configService->get('NotifyIfAvail.config.emailSubject') ?? 'Ihr gewünschter Artikel ist wieder verfügbar!';
$this->mailer->send($message);
foreach ($emails as $email) {
$message = (new Email())
->from($emailSender)
->to($email)
->subject($emailSubject)
->html("
<p>Hallo,</p>
<p>Sie haben sich für eine Benachrichtigung angemeldet, sobald der folgende Artikel wieder verfügbar ist:</p>
<p><strong>{$productName}</strong></p>
<p><a href='{$productUrl}'>Jetzt bestellen</a></p>
<p>Vielen Dank für Ihr Interesse an unseren Produkten!</p>
<p>Beste Grüße,<br>{$shopName}-Team</p>
");
$this->mailer->send($message);
}
$this->connection->executeStatement(
"DELETE FROM notifyifavail_plugin_notification WHERE product_id = :productId",
['productId' => Uuid::fromHexToBytes($productId)]
);
} catch (\Exception $e) {
$this->logger->error('Fehler beim Versand der Benachrichtigungen: ' . $e->getMessage());
}
// Nach Versand aus der Datenbank löschen
$this->connection->executeStatement("DELETE FROM notifyifavail_plugin_notification WHERE product_id = :productId", [
'productId' => Uuid::fromHexToBytes($productId)
]);
}
}

View File

@ -4,6 +4,8 @@ namespace NotifyIfAvail\Storefront\Subscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\Struct\ArrayStruct;
class ProductSubscriber implements EventSubscriberInterface
{
@ -19,7 +21,9 @@ class ProductSubscriber implements EventSubscriberInterface
$product = $event->getPage()->getProduct();
if ($product->getAvailableStock() <= 0) {
$product->addExtension('notification', true);
$product->addExtension('notifyIfAvail', new ArrayStruct([
'enabled' => true
]));
}
}
}