Skip to content

Commit 8a81386

Browse files
mglamanmglaman
authored andcommitted
Issue #2952529 by mglaman, dravenk, valic, Morbus Iff, ctrlADel, waspper, freelock, Regnoy, Christian.wiedemann, tim.plunkett, andyg5000: Support for Layout Builder module
1 parent dc29aad commit 8a81386

File tree

10 files changed

+657
-2
lines changed

10 files changed

+657
-2
lines changed

modules/product/commerce_product.module

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use Drupal\Core\Access\AccessResult;
99
use Drupal\Core\Entity\EntityTypeInterface;
1010
use Drupal\Core\Session\AccountInterface;
1111
use Drupal\Core\StringTranslation\TranslatableMarkup;
12+
use Drupal\commerce_product\Plugin\Block\VariationFieldBlock;
13+
use Drupal\Component\Plugin\PluginBase;
1214
use Drupal\entity\BundleFieldDefinition;
1315
use Drupal\commerce\EntityHelper;
1416
use Drupal\commerce_product\Entity\ProductTypeInterface;
@@ -374,3 +376,17 @@ function commerce_product_jsonapi_commerce_product_variation_filter_access(Entit
374376
JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view commerce_product'),
375377
];
376378
}
379+
380+
/**
381+
* Implements hook_block_alter().
382+
*/
383+
function commerce_product_block_alter(array &$info) {
384+
if (\Drupal::moduleHandler()->moduleExists('layout_builder')) {
385+
$base_plugin_id = 'field_block' . PluginBase::DERIVATIVE_SEPARATOR . 'commerce_product_variation' . PluginBase::DERIVATIVE_SEPARATOR;
386+
foreach ($info as $block_plugin_id => $block_definition) {
387+
if (strpos($block_plugin_id, $base_plugin_id) !== FALSE) {
388+
$info[$block_plugin_id]['class'] = VariationFieldBlock::class;
389+
}
390+
}
391+
}
392+
}

modules/product/commerce_product.services.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ services:
2929
tags:
3030
- { name: 'context_provider' }
3131

32+
commerce_product.product_variation_route_context:
33+
class: Drupal\commerce_product\ContextProvider\ProductVariationContext
34+
arguments: ['@current_route_match', '@entity_type.manager']
35+
tags:
36+
- { name: 'context_provider' }
37+
3238
commerce_product.variation_attribute_mapper:
3339
class: Drupal\commerce_product\ProductVariationAttributeMapper
3440
arguments: ['@commerce_product.attribute_field_manager', '@entity_type.manager', '@entity.repository']
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Drupal\commerce_product;
4+
5+
use Drupal\Core\DependencyInjection\ContainerBuilder;
6+
use Drupal\Core\DependencyInjection\ServiceProviderBase;
7+
use Symfony\Component\DependencyInjection\Reference;
8+
9+
/**
10+
* Swap field rendered when layout builder module is on.
11+
*/
12+
class CommerceProductServiceProvider extends ServiceProviderBase {
13+
14+
/**
15+
* {@inheritdoc}
16+
*/
17+
public function alter(ContainerBuilder $container) {
18+
// Get list of modules.
19+
$modules = $container->getParameter('container.modules');
20+
21+
// Check if there is layout builder and swap field renderer service.
22+
if (isset($modules['layout_builder'])) {
23+
$definition = $container->getDefinition('commerce_product.variation_field_renderer');
24+
$definition->setClass(ProductVariationFieldRendererLayoutBuilder::class)
25+
->addArgument(new Reference('entity_display.repository'));
26+
}
27+
}
28+
29+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
namespace Drupal\commerce_product\ContextProvider;
4+
5+
use Drupal\commerce_product\Entity\ProductInterface;
6+
use Drupal\commerce_product\Entity\ProductType;
7+
use Drupal\Core\Cache\CacheableMetadata;
8+
use Drupal\Core\Entity\Display\EntityDisplayInterface;
9+
use Drupal\Core\Entity\EntityTypeManagerInterface;
10+
use Drupal\Core\Plugin\Context\Context;
11+
use Drupal\Core\Plugin\Context\ContextProviderInterface;
12+
use Drupal\Core\Plugin\Context\EntityContextDefinition;
13+
use Drupal\Core\Routing\RouteMatchInterface;
14+
use Drupal\Core\StringTranslation\StringTranslationTrait;
15+
use Drupal\Core\StringTranslation\TranslatableMarkup;
16+
use Drupal\layout_builder\DefaultsSectionStorageInterface;
17+
use Drupal\layout_builder\OverridesSectionStorageInterface;
18+
19+
/**
20+
* @todo
21+
*/
22+
class ProductVariationContext implements ContextProviderInterface {
23+
24+
use StringTranslationTrait;
25+
26+
/**
27+
* The route match.
28+
*
29+
* @var \Drupal\Core\Routing\RouteMatchInterface
30+
*/
31+
protected $routeMatch;
32+
33+
/**
34+
* The product variation storage.
35+
*
36+
* @var \Drupal\commerce_product\ProductVariationStorageInterface
37+
*/
38+
protected $productVariationStorage;
39+
40+
/**
41+
* Constructs a new ProductRouteContext object.
42+
*
43+
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
44+
* The route match.
45+
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
46+
* The entity type manager.
47+
*/
48+
public function __construct(RouteMatchInterface $route_match, EntityTypeManagerInterface $entity_type_manager) {
49+
$this->routeMatch = $route_match;
50+
$this->productVariationStorage = $entity_type_manager->getStorage('commerce_product_variation');
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
public function getRuntimeContexts(array $unqualified_context_ids) {
57+
$context_definition = new EntityContextDefinition('entity:commerce_product_variation', new TranslatableMarkup('Product variation'));
58+
$value = $this->routeMatch->getParameter('commerce_product_variation');
59+
if ($value === NULL) {
60+
if ($product = $this->routeMatch->getParameter('commerce_product')) {
61+
$value = $this->productVariationStorage->loadFromContext($product);
62+
}
63+
/** @var \Drupal\commerce_product\Entity\ProductTypeInterface $product_type */
64+
elseif ($product_type = $this->routeMatch->getParameter('commerce_product_type')) {
65+
if (is_string($product_type)) {
66+
$product_type = ProductType::load($product_type);
67+
}
68+
$value = $this->productVariationStorage->createWithSampleValues($product_type->getVariationTypeId());
69+
}
70+
// @todo Simplify this logic once EntityTargetInterface is available
71+
// @see https://www.drupal.org/project/drupal/issues/3054490
72+
elseif (strpos($this->routeMatch->getRouteName(), 'layout_builder') !== FALSE) {
73+
/** @var \Drupal\layout_builder\SectionStorageInterface $section_storage */
74+
$section_storage = $this->routeMatch->getParameter('section_storage');
75+
if ($section_storage instanceof DefaultsSectionStorageInterface) {
76+
$context = $section_storage->getContextValue('display');
77+
assert($context instanceof EntityDisplayInterface);
78+
if ($context->getTargetEntityTypeId() === 'commerce_product') {
79+
$product_type = ProductType::load($context->getTargetBundle());
80+
$value = $this->productVariationStorage->createWithSampleValues($product_type->getVariationTypeId());
81+
}
82+
}
83+
elseif ($section_storage instanceof OverridesSectionStorageInterface) {
84+
$context = $section_storage->getContextValue('entity');
85+
if ($context instanceof ProductInterface) {
86+
$value = $context->getDefaultVariation();
87+
if ($value === NULL) {
88+
$product_type = ProductType::load($context->bundle());
89+
$value = $this->productVariationStorage->createWithSampleValues($product_type->getVariationTypeId());
90+
}
91+
}
92+
}
93+
}
94+
}
95+
96+
$cacheability = new CacheableMetadata();
97+
$cacheability->setCacheContexts(['route']);
98+
$context = new Context($context_definition, $value);
99+
$context->addCacheableDependency($cacheability);
100+
101+
return ['commerce_product_variation' => $context];
102+
}
103+
104+
/**
105+
* {@inheritdoc}
106+
*/
107+
public function getAvailableContexts() {
108+
return $this->getRuntimeContexts([]);
109+
}
110+
111+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace Drupal\commerce_product\Plugin\Block;
4+
5+
use Drupal\commerce_product\Entity\ProductVariationInterface;
6+
use Drupal\commerce_product\ProductVariationFieldRendererInterface;
7+
use Drupal\Core\Cache\CacheableMetadata;
8+
use Drupal\Core\Entity\EntityFieldManagerInterface;
9+
use Drupal\Core\Extension\ModuleHandlerInterface;
10+
use Drupal\Core\Field\FormatterPluginManager;
11+
use Drupal\layout_builder\Plugin\Block\FieldBlock;
12+
use Psr\Log\LoggerInterface;
13+
use Symfony\Component\DependencyInjection\ContainerInterface;
14+
15+
/**
16+
* Variation field block.
17+
*
18+
* Specific block class for Layout Builder's field block and variations to
19+
* ensure field replacement works.
20+
*/
21+
class VariationFieldBlock extends FieldBlock {
22+
23+
/**
24+
* The variation field renderer.
25+
*
26+
* @var \Drupal\commerce_product\ProductVariationFieldRendererInterface
27+
*/
28+
protected $productVariationFieldRenderer;
29+
30+
/**
31+
* Constructs a new VariationFieldBlock object.
32+
*
33+
* @param array $configuration
34+
* A configuration array containing information about the plugin instance.
35+
* @param string $plugin_id
36+
* The plugin ID for the plugin instance.
37+
* @param mixed $plugin_definition
38+
* The plugin implementation definition.
39+
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
40+
* The entity field manager.
41+
* @param \Drupal\Core\Field\FormatterPluginManager $formatter_manager
42+
* The formatter manager.
43+
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
44+
* The module handler.
45+
* @param \Psr\Log\LoggerInterface $logger
46+
* The logger.
47+
* @param \Drupal\commerce_product\ProductVariationFieldRendererInterface $product_variation_field_render
48+
* The variation field renderer.
49+
*/
50+
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, FormatterPluginManager $formatter_manager, ModuleHandlerInterface $module_handler, LoggerInterface $logger, ProductVariationFieldRendererInterface $product_variation_field_render) {
51+
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_field_manager, $formatter_manager, $module_handler, $logger);
52+
$this->productVariationFieldRenderer = $product_variation_field_render;
53+
}
54+
55+
/**
56+
* {@inheritdoc}
57+
*/
58+
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
59+
return new static(
60+
$configuration,
61+
$plugin_id,
62+
$plugin_definition,
63+
$container->get('entity_field.manager'),
64+
$container->get('plugin.manager.field.formatter'),
65+
$container->get('module_handler'),
66+
$container->get('logger.channel.layout_builder'),
67+
$container->get('commerce_product.variation_field_renderer')
68+
);
69+
}
70+
71+
/**
72+
* {@inheritdoc}
73+
*/
74+
public function build() {
75+
$display_settings = $this->getConfiguration()['formatter'];
76+
$entity = $this->getEntity();
77+
assert($entity instanceof ProductVariationInterface);
78+
try {
79+
$build = $this->productVariationFieldRenderer->renderField($this->fieldName, $entity, $display_settings);
80+
}
81+
catch (\Exception $e) {
82+
$build = [];
83+
$this->logger->warning('The field "%field" failed to render with the error of "%error".', ['%field' => $this->fieldName, '%error' => $e->getMessage()]);
84+
}
85+
CacheableMetadata::createFromObject($this)->applyTo($build);
86+
return $build;
87+
}
88+
89+
}

modules/product/src/Plugin/Field/FieldFormatter/AddToCartFormatter.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,33 @@ public function settingsSummary() {
6464
*/
6565
public function viewElements(FieldItemListInterface $items, $langcode) {
6666
$elements = [];
67+
68+
$product = $items->getEntity();
69+
if (!empty($product->in_preview)) {
70+
$elements[0]['add_to_cart_form'] = [
71+
'#type' => 'actions',
72+
['#type' => 'button', '#value' => $this->t('Add to cart')],
73+
];
74+
return $elements;
75+
}
76+
if ($product->isNew()) {
77+
return [];
78+
}
79+
80+
$view_mode = $this->viewMode;
81+
// If the field formatter is rendered in Layout Builder, the `viewMode`
82+
// property will be `_custom` and the original view mode is stored in the
83+
// third party settings.
84+
// @see \Drupal\layout_builder\Plugin\Block\FieldBlock::build
85+
if (isset($this->thirdPartySettings['layout_builder'])) {
86+
$view_mode = $this->thirdPartySettings['layout_builder']['view_mode'];
87+
}
88+
6789
$elements[0]['add_to_cart_form'] = [
6890
'#lazy_builder' => [
6991
'commerce_product.lazy_builders:addToCartForm', [
70-
$items->getEntity()->id(),
71-
$this->viewMode,
92+
$product->id(),
93+
$view_mode,
7294
$this->getSetting('combine'),
7395
$langcode,
7496
],

0 commit comments

Comments
 (0)