Skip to content

Commit

Permalink
Merge pull request #2679 from breakone/variant-creator
Browse files Browse the repository at this point in the history
Variant Creator
  • Loading branch information
dpfaffenbauer authored Aug 12, 2024
2 parents f26277d + 53f046a commit 1ef7209
Show file tree
Hide file tree
Showing 20 changed files with 688 additions and 12 deletions.
2 changes: 1 addition & 1 deletion docs/01_Getting_Started/00_Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ CoreShop also uses Symfony Messenger for async tasks like sending E-Mails or Pro
Please run these 2 transports to process the data

```yaml
bin/console messenger:consume coreshop_notification coreshop_index --time-limit=300
bin/console messenger:consume coreshop_notification coreshop_index coreshop_variant --time-limit=300
```

## Payment
Expand Down
12 changes: 12 additions & 0 deletions docs/03_Bundles/Variant_Bundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,15 @@ include these fields:

The Variant Bundle significantly enhances the flexibility of product management in CoreShop, allowing for detailed and
diverse product variant configurations.

## Variant Generator
The Variant Generator is a tool that automatically generates variants for a VariantAware Class based on the attribute groups
defined in the Data Object. The Generator is available in the Pimcore backend at the Toolbar on your VariantAware Class.

### Installation

Variant Generator uses Symfony Messenger for async processing, you can run it with the following command:

```yaml
bin/console messenger:consume coreshop_variant --time-limit=300
```
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pimcore.registerNS('coreshop.core.resource');
coreshop.core.resource = Class.create(coreshop.resource, {
initialize: function () {
coreshop.broker.addListener('pimcore.ready', this.pimcoreReady, this);
coreshop.broker.addListener('pimcore.postOpenObject', this.postOpenObject, this);
coreshop.broker.addListener('pimcore.postOpenObject', this.postOpenObject, this, false, -10);
coreshop.broker.fireEvent('resource.register', 'coreshop.core', this);
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* files that are distributed with this source code.
*
* @copyright Copyright (c) CoreShop GmbH (https://www.coreshop.org)
* @license https://www.coreshop.org/license GPLv3 and CCL
* @license https://www.coreshop.org/license GNU General Public License version 3 (GPLv3)
*
*/

Expand All @@ -27,6 +27,10 @@ coreshop.broker = {

var list = coreshop.broker._listeners[name];

list = list.sort(function(a, b) {
return a.priority - b.priority;
});

//copy arguments
var args = [];
for (var j = 1; j < arguments.length; j++) {
Expand Down Expand Up @@ -59,15 +63,16 @@ coreshop.broker = {
}
},

addListener: function (name, func, scope, once) {
addListener: function (name, func, scope, once, priority) {
if (coreshop.broker._listeners[name] === undefined) {
coreshop.broker._listeners[name] = [];
}

coreshop.broker._listeners[name].push({
func: func,
scope: scope,
once: Ext.isDefined(once) ? once : false
once: Ext.isDefined(once) ? once : false,
priority: priority ?? 0
});
},

Expand Down
133 changes: 133 additions & 0 deletions src/CoreShop/Bundle/VariantBundle/Controller/VariantController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php

declare(strict_types=1);

/*
* CoreShop
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - CoreShop Commercial License (CCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) CoreShop GmbH (https://www.coreshop.org)
* @license https://www.coreshop.org/license GPLv3 and CCL
*
*/

namespace CoreShop\Bundle\VariantBundle\Controller;

use CoreShop\Bundle\ResourceBundle\Controller\AdminController;
use CoreShop\Bundle\VariantBundle\Messenger\CreateVariantMessage;
use CoreShop\Bundle\VariantBundle\Service\VariantGeneratorService;
use CoreShop\Component\Variant\Model\AttributeGroupInterface;
use CoreShop\Component\Variant\Model\AttributeInterface;
use CoreShop\Component\Variant\Model\ProductVariantAwareInterface;
use Pimcore\Model\DataObject;
use Pimcore\Model\DataObject\AbstractObject;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
* @psalm-suppress InternalClass
*/
class VariantController extends AdminController
{
public function getAttributesAction(Request $request)
{
$id = $this->getParameterFromRequest($request, 'id');

if (!$id) {
throw new \InvalidArgumentException('no product id given');
}

$product = DataObject::getById($id);

if (!$product instanceof ProductVariantAwareInterface) {
throw new NotFoundHttpException('no product found');
}

if (AbstractObject::OBJECT_TYPE_VARIANT === $product->getType()) {
$product = $product->getVariantParent();
}

$attributeGroups = $product->getAllowedAttributeGroups();

$data = array_map(static function (AttributeGroupInterface $group) {
return [
'text' => sprintf('%s (ID: %s)', $group->getKey(), $group->getId()),
'sorting' => $group->getSorting(),
'leaf' => false,
'iconCls' => 'pimcore_icon_object',
'data' => array_map(static function (AttributeInterface $attribute) use ($group) {
return [
'text' => sprintf('%s (ID: %s)', $attribute->getKey(), $attribute->getId()),
'id' => $attribute->getId(),
'group_id' => $group->getId(),
'sorting' => $attribute->getSorting(),
'leaf' => true,
'checked' => false,
'iconCls' => 'pimcore_icon_object',
];
}, $group->getAttributes()),
];
}, $attributeGroups);

return $this->json(
[
'success' => true,
'data' => $data,
]
);
}

public function generateVariantsAction(
Request $request,
VariantGeneratorService $variantGeneratorService,
MessageBusInterface $messageBus,
TranslatorInterface $translator,
) {
$id = $this->getParameterFromRequest($request, 'id');
$attributes = $this->getParameterFromRequest($request, 'attributes');

if (!$id) {
throw new \InvalidArgumentException('no product id given');
}

if (!$attributes) {
throw new \InvalidArgumentException('no attributes given');
}

$product = DataObject::getById($id);

if (!$product instanceof ProductVariantAwareInterface) {
throw new NotFoundHttpException('no product found');
}

if (AbstractObject::OBJECT_TYPE_VARIANT === $product->getType()) {
$product = $product->getVariantParent();
}

$combinations = [];
$variantGeneratorService->generateCombinations($attributes, [], 0, $combinations);

foreach ($combinations as $attributeIds) {
/**
* @psalm-suppress InternalMethod
*/
$messageBus->dispatch(
new CreateVariantMessage($product->getId(), $attributeIds, $this->getAdminUser()?->getId())
);
}

return $this->json(
[
'success' => true,
'message' => $translator->trans('coreshop.variant_generator.generate_in_background', [], 'admin'),
]
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use CoreShop\Component\Variant\Model\AttributeGroupInterface;
use CoreShop\Component\Variant\Model\AttributeInterface;
use CoreShop\Component\Variant\Model\AttributeValueInterface;
use CoreShop\Component\Variant\Model\ProductVariantAwareInterface;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
Expand Down Expand Up @@ -58,6 +59,7 @@ private function addStack(ArrayNodeDefinition $node): void
->children()
->scalarNode('attribute_group')->defaultValue(AttributeGroupInterface::class)->cannotBeEmpty()->end()
->scalarNode('attribute')->defaultValue(AttributeInterface::class)->cannotBeEmpty()->end()
->scalarNode('variant_aware')->defaultValue(ProductVariantAwareInterface::class)->cannotBeEmpty()->end()
->end()
->end()
->end()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

/*
* CoreShop
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - CoreShop Commercial License (CCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) CoreShop GmbH (https://www.coreshop.org)
* @license https://www.coreshop.org/license GPLv3 and CCL
*
*/

namespace CoreShop\Bundle\VariantBundle\Messenger;


class CreateVariantMessage
{
public function __construct(
private int $objectId,
private array $attributeIds,
private ?int $userId = null
) {}

public function getObjectId(): int
{
return $this->objectId;
}

public function setObjectId(int $objectId): void
{
$this->objectId = $objectId;
}

public function getAttributeIds(): array
{
return $this->attributeIds;
}

public function setAttributeIds(array $attributeIds): void
{
$this->attributeIds = $attributeIds;
}

public function getUserId(): ?int
{
return $this->userId;
}

public function setUserId(?int $userId): void
{
$this->userId = $userId;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

/*
* CoreShop
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - CoreShop Commercial License (CCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) CoreShop GmbH (https://www.coreshop.org)
* @license https://www.coreshop.org/license GPLv3 and CCL
*
*/

namespace CoreShop\Bundle\VariantBundle\Messenger\Handler;

use CoreShop\Bundle\VariantBundle\Messenger\CreateVariantMessage;
use CoreShop\Bundle\VariantBundle\Service\VariantGeneratorServiceInterface;
use CoreShop\Component\Variant\Model\ProductVariantAwareInterface;
use Pimcore\Model\DataObject;
use Pimcore\Model\Notification\Service\NotificationService;

class CreateVariantMessageHandler
{
public function __construct(
protected VariantGeneratorServiceInterface $variantGeneratorService,
protected NotificationService $notificationService
) {
}

public function __invoke(CreateVariantMessage $message)
{
$object = DataObject::getById($message->getObjectId());

if (!$object instanceof ProductVariantAwareInterface) {
return;
}

$attributeIds = $message->getAttributeIds();
if (!$attributeIds) {
return;
}

$variant = $this->variantGeneratorService->generateVariant($attributeIds, $object);

if (null !== $variant && null !== $message->getUserId()) {
/**
* @psalm-suppress InternalMethod
*/
$this->notificationService->sendToUser(
$message->getUserId(),
0,
sprintf('Variant %s generated', $variant->getName()),
sprintf('Variant %s with ID %s for Product %s with ID %s has been generated', $variant->getName(),
$variant->getId(), $object->getKey(), $object->getId())
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
core_shop_variant:
pimcore_admin:
js:
resource: '/bundles/coreshopvariant/pimcore/js/resource.js'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
imports:
- { resource: admin.yml }
- { resource: messenger.yml }
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
framework:
messenger:
transports:
coreshop_variant:
dsn: "doctrine://default?queue_name=coreshop_variant"
failure_transport: coreshop_variant_failed
retry_strategy:
max_retries: 3
delay: 300000
multiplier: 2
# we store failed messages here for admins to manually review them later
coreshop_variant_failed:
dsn: "doctrine://default?queue_name=coreshop_variant_failed"
retry_strategy:
max_retries: 0

routing:
'CoreShop\Bundle\VariantBundle\Messenger\CreateVariantMessage': coreshop_variant
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
coreshop_variant_attribute:
path: /product/attribute/{product}
defaults: { _controller: CoreShop\Bundle\VariantBundle\Controller\ProductController::attributeAction }
coreshop_admin_variant_attributes:
path: /admin/variant/attributes
defaults: { _controller: CoreShop\Bundle\VariantBundle\Controller\VariantController::getAttributesAction }
options:
expose: true

coreshop_admin_variant_generator:
path: /admin/variant/generate
defaults: { _controller: CoreShop\Bundle\VariantBundle\Controller\VariantController::generateVariantsAction }
methods: [POST]
options:
expose: true
Loading

0 comments on commit 1ef7209

Please sign in to comment.