Init lodner set builder plugin.
This commit is contained in:
@@ -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
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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'))
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 %}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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__);
|
||||
Reference in New Issue
Block a user