Init lodner set builder plugin.

This commit is contained in:
2025-09-10 02:53:03 +02:00
commit 6236aa8010
21 changed files with 606 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
{
"name": "lodner-set-builder/lodner-set-builder",
"description": "lodner-set-builder/lodner-set-builder",
"type": "shopware-platform-plugin",
"version": "1.0.0",
"authors": [
{
"name": "Matthias Lodner",
"email": "matthias@lodner.de"
}
],
"license": "MIT",
"require": {
"shopware/core": "~6.7.0"
},
"extra": {
"shopware-plugin-class": "LodnerSetBuilder\\LodnerSetBuilder",
"copyright": "(c) Matthias Lodner",
"manufacturerLink": {
"de-DE": "example.de",
"en-GB": "example.de"
},
"supportLink": {
"de-DE": "example.de",
"en-GB": "example.de"
},
"label": {
"de-DE": "Lodner Set Builder",
"en-GB": "Lodner Set Builder"
},
"description": {
"de-DE": "Lodner Set Builder ist ein Plugin um einen Kunden die Möglichkeit zu geben eigene Sets zu erstellen.",
"en-GB": "Lodner Set Builder is a plugin to let customers build thery own set."
}
},
"autoload": {
"psr-4": {
"LodnerSetBuilder\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"LodnerSetBuilder\\Tests\\": "tests/"
}
}
}
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
bootstrap="tests/TestBootstrap.php"
executionOrder="random">
<coverage>
<include>
<directory>./src/</directory>
</include>
</coverage>
<php>
<ini name="error_reporting" value="-1"/>
<server name="KERNEL_CLASS" value="Shopware\Core\Kernel"/>
<env name="APP_ENV" value="test"/>
<env name="APP_DEBUG" value="1"/>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak"/>
</php>
<testsuites>
<testsuite name="LodnerSetBuilder Testsuite">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
+30
View File
@@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace LodnerSetBuilder\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(
name: 'swag-commands:example',
description: 'Add a short description for your command',
)]
class ExampleCommand extends Command
{
// Provides a description, printed out in bin/console
protected function configure(): void
{
$this->setDescription('Does something very special.');
}
// Actual code executed in the command
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('It works!');
// Exit code 0 for success
return 0;
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php declare(strict_types=1);
namespace LodnerSetBuilder\Core\Content\Set;
use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
/**
* @method void add(SetEntity $entity)
* @method void set(string $key, SetEntity $entity)
* @method SetEntity[] getIterator()
* @method SetEntity[] getElements()
* @method SetEntity|null get(string $key)
* @method SetEntity|null first()
* @method SetEntity|null last()
*/
class SetCollection extends EntityCollection
{
protected function getExpectedClass(): string
{
return SetEntity::class;
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php declare(strict_types=1);
namespace LodnerSetBuilder\Core\Content\Set;
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
use Shopware\Core\Framework\DataAbstractionLayer\Field\BoolField;
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;
class SetDefinition extends EntityDefinition
{
public const ENTITY_NAME = 'set';
public function getEntityName(): string
{
return self::ENTITY_NAME;
}
public function getEntityClass(): string
{
return SetEntity::class;
}
public function getCollectionClass(): string
{
return SetCollection::class;
}
protected function defineFields(): FieldCollection
{
return new FieldCollection([
(new IdField('id', 'id'))->addFlags(new Required(), new PrimaryKey()),
(new StringField('name', 'name')),
(new StringField('description', 'description')),
(new BoolField('active', 'active'))
]);
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php declare(strict_types=1);
namespace LodnerSetBuilder\Core\Content\Set;
use Shopware\Core\Framework\DataAbstractionLayer\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityIdTrait;
class SetEntity extends Entity
{
use EntityIdTrait;
protected ?string $name;
protected ?string $description;
protected bool $active;
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): void
{
$this->name = $name;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): void
{
$this->description = $description;
}
public function isActive(): bool
{
return $this->active;
}
public function setActive(bool $active): void
{
$this->active = $active;
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php declare(strict_types=1);
namespace LodnerSetBuilder;
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 Shopware\Core\Framework\Plugin\Context\UpdateContext;
use LodnerSetBuilder\Service\CustomFieldsInstaller;
class LodnerSetBuilder extends Plugin
{
public function install(InstallContext $installContext): void
{
// Do stuff such as creating a new payment method
$this->getCustomFieldsInstaller()->install($installContext->getContext());
}
public function uninstall(UninstallContext $uninstallContext): void
{
parent::uninstall($uninstallContext);
if ($uninstallContext->keepUserData()) {
return;
}
// Remove or deactivate the data created by the plugin
}
public function activate(ActivateContext $activateContext): void
{
// Activate entities, such as a new payment method
// Or create new entities here, because now your plugin is installed and active for sure
$this->getCustomFieldsInstaller()->addRelations($activateContext->getContext());
}
public function deactivate(DeactivateContext $deactivateContext): void
{
// Deactivate entities, such as a new payment method
// Or remove previously created entities
}
public function update(UpdateContext $updateContext): void
{
// Update necessary stuff, mostly non-database related
}
public function postInstall(InstallContext $installContext): void
{
}
public function postUpdate(UpdateContext $updateContext): void
{
}
private function getCustomFieldsInstaller(): CustomFieldsInstaller
{
if ($this->container->has(CustomFieldsInstaller::class)) {
return $this->container->get(CustomFieldsInstaller::class);
}
return new CustomFieldsInstaller(
$this->container->get('custom_field_set.repository'),
$this->container->get('custom_field_set_relation.repository')
);
}
}
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);
namespace LodnerSetBuilder\Migration;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Migration\MigrationStep;
class Migration1757463502CreateSetTable extends MigrationStep
{
public function getCreationTimestamp(): int
{
return 1757463502;
}
public function update(Connection $connection): void
{
$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `set` (
`id` BINARY(16) NOT NULL,
`name` VARCHAR(255) COLLATE utf8mb4_unicode_ci,
`description` VARCHAR(255) COLLATE utf8mb4_unicode_ci,
`active` TINYINT(1) COLLATE utf8mb4_unicode_ci,
`created_at` DATETIME(3) NOT NULL,
`updated_at` DATETIME(3),
PRIMARY KEY (`id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
SQL;
$connection->executeStatement($sql);
}
public function updateDestructive(Connection $connection): void
{
}
}
@@ -0,0 +1,2 @@
// Import admin module
import './module/swag-example';
@@ -0,0 +1,46 @@
// <plugin root>/src/Resources/app/administration/src/module/swag-example/index.js
import deDE from './snippet/de-DE';
import enGB from './snippet/en-GB';
Shopware.Module.register('swag-example', {
type: 'plugin',
name: 'Example',
title: 'swag-example.general.mainMenuItemGeneral',
description: 'sw-property.general.descriptionTextModule',
color: '#ff3d58',
icon: 'default-shopping-paper-bag-product',
snippets: {
'de-DE': deDE,
'en-GB': enGB
},
routes: {
list: {
component: 'swag-example-list',
path: 'list'
},
detail: {
component: 'swag-example-detail',
path: 'detail/:id',
meta: {
parentPath: 'swag.example.list'
}
},
create: {
component: 'swag-example-create',
path: 'create',
meta: {
parentPath: 'swag.example.list'
}
}
},
navigation: [{
label: 'swag-example.general.mainMenuItemGeneral',
color: '#ff3d58',
path: 'swag.example.list',
icon: 'default-shopping-paper-bag-product',
position: 100
}]
});
@@ -0,0 +1,8 @@
{
"swag-example": {
"general": {
"mainMenuItemGeneral": "My custom module",
"descriptionTextModule": "Manage this custom module here"
}
}
}
@@ -0,0 +1,8 @@
{
"swag-example": {
"general": {
"mainMenuItemGeneral": "My custom module",
"descriptionTextModule": "Manage this custom module here"
}
}
}
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/System/SystemConfig/Schema/config.xsd">
<card>
<title>Minimal configuration</title>
<input-field type="text">
<name>textField</name>
<label>Test field with default value</label>
<defaultValue>test</defaultValue>
</input-field>
</card>
</config>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
https://symfony.com/schema/routing/routing-1.0.xsd">
<import resource="../../Storefront/Controller/**/*Controller.php" type="attribute" />
</routes>
+37
View File
@@ -0,0 +1,37 @@
<?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="LodnerSetBuilder\Command\ExampleCommand">
<tag name="console.command"/>
</service>
<service id="LodnerSetBuilder\ScheduledTask\ExampleTask">
<tag name="shopware.scheduled.task"/>
</service>
<service id="LodnerSetBuilder\Subscriber\MySubscriber">
<tag name="kernel.event_subscriber"/>
</service>
<service id="LodnerSetBuilder\Storefront\Controller\ExampleController" public="true">
<call method="setContainer">
<argument type="service" id="service_container"/>
</call>
</service>
<service id="LodnerSetBuilder\Core\Content\Set\SetDefinition">
<tag name="shopware.entity.definition" entity="set" />
</service>
<service id="LodnerSetBuilder\Service\CustomFieldsInstaller">
<argument type="service" id="custom_field_set.repository"/>
<argument type="service" id="custom_field_set_relation.repository"/>
</service>
</services>
</container>
@@ -0,0 +1,5 @@
{% sw_extends '@Storefront/storefront/base.html.twig' %}
{% block base_content %}
<h1>{{ example }}</h1>
{% endblock %}
+18
View File
@@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace LodnerSetBuilder\ScheduledTask;
use Shopware\Core\Framework\MessageQueue\ScheduledTask\ScheduledTask;
class ExampleTask extends ScheduledTask
{
public static function getTaskName(): string
{
return 'swag.example_task';
}
public static function getDefaultInterval(): int
{
return 30; // 5 minutes
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php declare(strict_types=1);
namespace LodnerSetBuilder\Service;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\System\CustomField\CustomFieldTypes;
class CustomFieldsInstaller
{
private const CUSTOM_FIELDSET_NAME = 'lodner_set_builder_set';
private const CUSTOM_FIELDSET = [
'name' => self::CUSTOM_FIELDSET_NAME,
'config' => [
'label' => [
'en-GB' => 'English custom field set label',
'de-DE' => 'German custom field set label',
Defaults::LANGUAGE_SYSTEM => 'Mention the fallback label here'
]
],
'customFields' => [
[
'name' => 'swag_example_size',
'type' => CustomFieldTypes::INT,
'config' => [
'label' => [
'en-GB' => 'English custom field label',
'de-DE' => 'German custom field label',
Defaults::LANGUAGE_SYSTEM => 'Mention the fallback label here'
],
'customFieldPosition' => 1
]
]
]
];
public function __construct(
private readonly EntityRepository $customFieldSetRepository,
private readonly EntityRepository $customFieldSetRelationRepository
) {
}
public function install(Context $context): void
{
$this->customFieldSetRepository->upsert([
self::CUSTOM_FIELDSET
], $context);
}
public function addRelations(Context $context): void
{
$this->customFieldSetRelationRepository->upsert(array_map(function (string $customFieldSetId) {
return [
'customFieldSetId' => $customFieldSetId,
'entityName' => 'product',
];
}, $this->getCustomFieldSetIds($context)), $context);
}
/**
* @return string[]
*/
private function getCustomFieldSetIds(Context $context): array
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('name', self::CUSTOM_FIELDSET_NAME));
return $this->customFieldSetRepository->searchIds($criteria, $context)->getIds();
}
}
@@ -0,0 +1,27 @@
<?php declare(strict_types=1);
namespace LodnerSetBuilder\Storefront\Controller;
use Shopware\Core\PlatformRequest;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Storefront\Controller\StorefrontController;
use Shopware\Storefront\Framework\Routing\StorefrontRouteScope;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route(defaults: [PlatformRequest::ATTRIBUTE_ROUTE_SCOPE => [StorefrontRouteScope::ID]])]
class ExampleController extends StorefrontController
{
#[Route(
path: '/example',
name: 'frontend.example.example',
methods: ['GET']
)]
public function showExample(Request $request, SalesChannelContext $context): Response
{
return $this->renderStorefront('@LodnerSetBuilder/storefront/page/example.html.twig', [
'example' => 'Hello world'
]);
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php declare(strict_types=1);
namespace LodnerSetBuilder\Subscriber;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Core\Content\Product\ProductEvents;
class MySubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
// Return the events to listen to as array like this: <event to listen to> => <method to execute>
return [
ProductEvents::PRODUCT_LOADED_EVENT => 'onProductsLoaded'
];
}
public function onProductsLoaded(EntityLoadedEvent $event)
{
// Do something
// E.g. work with the loaded entities: $event->getEntities()
}
}
+12
View File
@@ -0,0 +1,12 @@
<?php declare(strict_types=1);
use Shopware\Core\TestBootstrapper;
$loader = (new TestBootstrapper())
->addCallingPlugin()
->addActivePlugins('LodnerSetBuilder')
->setForceInstallPlugins(true)
->bootstrap()
->getClassLoader();
$loader->addPsr4('LodnerSetBuilder\\Tests\\', __DIR__);