Initial commit

This commit is contained in:
Nils 2025-03-05 02:08:16 +01:00
commit 897ca4c8bc
Signed by: slinicraftet204
GPG Key ID: 78E12696BAFC2A4B
17 changed files with 437 additions and 0 deletions

28
composer.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "slinicraftet204/notifyifavail",
"description": "benachrichtigt Kunden, sobald ein Produkt wieder verfügbar ist",
"version": "0.1.0",
"type": "shopware-platform-plugin",
"license": "MIT",
"authors": [
{
"name": "Nils Gerhardt | SLINIcraftet204"
}
],
"require": {
"shopware/core": "6.6.*",
"php": ">=8.0"
},
"autoload": {
"psr-4": {
"NotifyIfAvail\\": "src/"
}
},
"extra": {
"shopware-plugin-class": "NotifyIfAvail\\NotifyIfAvail",
"label": {
"de-DE": "NotifyIfAvail (von Nils G.|SLINI)",
"en-GB": "NotifyIfAvail (by Nils G.|SLINI)"
}
}
}

7
config.xml Normal file
View File

@ -0,0 +1,7 @@
<config xmlns="https://shopware.com/schemas/plugin-config/1.0">
<card>
<title>Benachrichtigung Plugin Einstellungen</title>
<input-field name="emailSender" type="text" label="E-Mail Absender" defaultValue="shop@example.com"/>
<input-field name="emailSubject" type="text" label="E-Mail Betreff" defaultValue="Ihr gewünschter Artikel ist wieder verfügbar!"/>
</card>
</config>

11
plugin.xml Normal file
View File

@ -0,0 +1,11 @@
<?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>
<label>Benachrichtigungs Plugin</label>
<version>1.0.0</version>
<author>TTT-Games</author>
<copyright>TTT-Games</copyright>
<license>MIT</license>
<compatibility minVersion="6.5.0" />
</plugin>

View File

@ -0,0 +1,46 @@
<?php
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;
class NotificationController
{
private Connection $connection;
private MailerInterface $mailer;
public function __construct(Connection $connection, MailerInterface $mailer)
{
$this->connection = $connection;
$this->mailer = $mailer;
}
/**
* @Route("/notification/subscribe", name="frontend.notification.subscribe", methods={"POST"})
*/
public function subscribe(Request $request): JsonResponse
{
$email = $request->request->get('email');
$productId = $request->request->get('productId');
if (!$email || !$productId) {
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']);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace NotifyIfAvail\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityIdTrait;
class Notification extends Entity
{
use EntityIdTrait;
protected string $email;
protected string $productId;
protected ?\DateTimeInterface $createdAt;
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): void
{
$this->email = $email;
}
public function getProductId(): string
{
return $this->productId;
}
public function setProductId(string $productId): void
{
$this->productId = $productId;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): void
{
$this->createdAt = $createdAt;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace NotifyIfAvail\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
/**
* @method void add(Notification $entity)
* @method void set(string $key, Notification $entity)
* @method Notification[] getIterator()
* @method Notification[] getElements()
* @method Notification|null get(string $key)
* @method Notification|null first()
* @method Notification|null last()
*/
class NotificationCollection extends EntityCollection
{
protected function getExpectedClass(): string
{
return Notification::class;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace NotifyIfAvail\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Field\EmailField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\CreatedAtField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
class NotificationDefinition extends EntityDefinition
{
public const ENTITY_NAME = 'notifyifavail_plugin_notification';
public function getEntityName(): string
{
return self::ENTITY_NAME;
}
public function getCollectionClass(): string
{
return NotificationCollection::class;
}
public function getEntityClass(): string
{
return Notification::class;
}
protected function defineFields(): FieldCollection
{
return new FieldCollection([
(new IdField('id', 'id'))->addFlags(new PrimaryKey(), new Required()),
(new EmailField('email', 'email'))->addFlags(new Required()),
(new StringField('product_id', 'productId'))->addFlags(new Required()),
new CreatedAtField()
]);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace NotifyIfAvail\Entity;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Uuid\Uuid;
class NotificationRepository
{
private Connection $connection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
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'),
]);
}
public function deleteNotification(string $productId): void
{
$this->connection->delete('notifyifavail_plugin_notification', [
'product_id' => Uuid::fromHexToBytes($productId),
]);
}
public function getNotificationsForProduct(string $productId): array
{
return $this->connection->fetchFirstColumn(
"SELECT email FROM notifyifavail_plugin_notification WHERE product_id = :productId",
['productId' => Uuid::fromHexToBytes($productId)]
);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace NotifyIfAvail\Migration;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Migration\MigrationStep;
class Migration20250305AddNotificationTable extends MigrationStep
{
public function getCreationTimestamp(): int
{
return 20250305000000;
}
public function update(Connection $connection): void
{
$connection->executeStatement("
CREATE TABLE IF NOT EXISTS `notifyifavail_plugin_notification` (
`id` BINARY(16) NOT NULL,
`product_id` BINARY(16) NOT NULL,
`email` VARCHAR(255) NOT NULL,
`created_at` DATETIME(3) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
}
public function updateDestructive(Connection $connection): void
{
// Not needed
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" ?>
<migration xmlns="https://shopware.com/schemas/migration/1.0">
<update>NotifyIfAvail\Migration\Migration20250305AddNotificationTable</update>
</migration>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="NotifyIfAvail\Storefront\Subscriber\ProductSubscriber">
<tag name="kernel.event_subscriber"/>
</service>
<service id="NotifyIfAvail\Controller\NotificationController">
<argument type="service" id="doctrine.dbal.default_connection"/>
<argument type="service" id="mailer.mailer"/>
<tag name="controller.service_arguments"/>
</service>
<service id="NotifyIfAvail\Service\NotificationService">
<argument type="service" id="doctrine.dbal.default_connection"/>
<argument type="service" id="mailer.mailer"/>
</service>
</services>
</container>

View File

@ -0,0 +1,13 @@
{SALUTATION} {CUSTOMER_NAME},
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:
[Zum Artikel]({PRODUCT_URL})
Vielen Dank für Ihr Interesse an unseren Produkten!
Beste Grüße,
Ihr {SHOP_NAME}-Team

View File

@ -0,0 +1,7 @@
{
"NotifyIfAvail": {
"notify_me": "Benachrichtigen, wenn verfügbar",
"email_placeholder": "Geben Sie Ihre E-Mail-Adresse ein",
"success_message": "Sie werden benachrichtigt, sobald der Artikel verfügbar ist."
}
}

View File

@ -0,0 +1,7 @@
{
"NotifyIfAvail": {
"notify_me": "Notify me when available",
"email_placeholder": "Enter your email address",
"success_message": "You will be notified when the item is available."
}
}

View File

@ -0,0 +1,34 @@
{% if product.extensions.notification is defined %}
<div id="notification-container">
<button class="btn btn-primary" id="notify-me-button">
{{ "NotifyIfAvail.notify_me"|trans }}
</button>
<div id="notify-me-form" style="display: none;">
<input type="email" id="notify-me-email" class="form-control"
placeholder="{{ 'NotifyIfAvail.email_placeholder'|trans }}" required>
<button class="btn btn-success" id="submit-notify">
{{ "NotifyIfAvail.notify_me"|trans }}
</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

@ -0,0 +1,48 @@
<?php
namespace NotifyIfAvail\Service;
use Doctrine\DBAL\Connection;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Shopware\Core\Framework\Uuid\Uuid;
class NotificationService
{
private Connection $connection;
private MailerInterface $mailer;
public function __construct(Connection $connection, MailerInterface $mailer)
{
$this->connection = $connection;
$this->mailer = $mailer;
}
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)]);
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>
");
$this->mailer->send($message);
}
// 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

@ -0,0 +1,25 @@
<?php
namespace NotifyIfAvail\Storefront\Subscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
class ProductSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
ProductPageLoadedEvent::class => 'onProductPageLoaded'
];
}
public function onProductPageLoaded(ProductPageLoadedEvent $event): void
{
$product = $event->getPage()->getProduct();
if ($product->getAvailableStock() <= 0) {
$product->addExtension('notification', true);
}
}
}