lb_plus-1.0.x-dev/src/Dropzones.php
src/Dropzones.php
<?php
declare(strict_types=1);
namespace Drupal\lb_plus;
use Drupal\Core\Render\Element;
use Drupal\layout_builder\Section;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\lb_plus\Event\PlaceBlockEvent;
use Drupal\layout_builder\SectionComponent;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\layout_builder\SectionStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Layout\LayoutPluginManagerInterface;
use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* Common functionality for LB based dropzones.
*/
final class Dropzones {
use StringTranslationTrait;
use ContextAwarePluginAssignmentTrait;
use LayoutBuilderContextTrait;
use LbPlusSettingsTrait;
/**
* Constructs a Dropzones object.
*/
public function __construct(
protected SectionStorageHandler $lbPlusSectionStorageHandler,
protected EntityTypeManagerInterface $entityTypeManager,
protected LayoutPluginManagerInterface $layoutManager,
protected EventDispatcherInterface $eventDispatcher,
protected ModuleHandlerInterface $moduleHandler,
protected BlockManagerInterface $blockManager,
protected UuidInterface $uuid,
) {}
/**
* Create block plugin.
*
* @param string $block_plugin_id
* The inline block plugin ID.
* @param \Drupal\layout_builder\SectionStorageInterface $current_section_storage
* The current section storage.
*
* @return object
* The block plugin instance.
* @throws \Drupal\Component\Plugin\Exception\PluginException
*/
public function createBlockPlugin(string $block_plugin_id, SectionStorageInterface $current_section_storage) {
// Ensure we have a valid block plugin ID.
$block_definitions = $this->blockManager->getDefinitions();
if (empty($block_definitions[$block_plugin_id])) {
throw new \InvalidArgumentException('Invalid block_plugin_id');
}
// Create a block to place.
$block_plugin = $this->blockManager->createInstance($block_plugin_id);
// Add context mapping to the configuration. Field blocks especially expect
// context mapping.
$configuration = $block_plugin->getConfiguration();
$contexts = $this->getPopulatedContexts($current_section_storage);
$assigned_context_element = $this->addContextAssignmentElement($block_plugin, $contexts);
foreach (Element::children($assigned_context_element) as $key) {
$configuration['context_mapping'][$key] = $assigned_context_element[$key]['#value'];
}
if (!empty($configuration['context_mapping']) && empty($configuration['context_mapping']['entity']) && array_key_exists('entity', $configuration['context_mapping'])) {
// Multiple context options are available, but I think we always want the
// layout_builder.entity context here if there's no default set.
$configuration['context_mapping']['entity'] = 'layout_builder.entity';
}
$block_plugin->setConfiguration($configuration);
return $block_plugin;
}
/**
* Create block content.
*
* @param $block_plugin
* The inline block plugin instance.
*
* @return \Drupal\Core\Entity\EntityInterface
* The block plugin entity instance.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function createBlockContent($block_plugin) {
[$block_plugin_id, $bundle] = explode(':', $block_plugin->getPluginId());
$configuration = $block_plugin->getConfiguration();
if ($block_plugin_id === 'inline_block') {
// Create a block content entity with placeholder content.
$block_content = $this->entityTypeManager->getStorage('block_content')->create([
'type' => $bundle,
'reusable' => FALSE,
]);
foreach ($block_content as $field) {
if ($field->getFieldDefinition()->getFieldStorageDefinition()->isBaseField()) {
continue;
}
if ($this->moduleHandler->moduleExists('field_sample_value')) {
\Drupal::service('field_sample_value.generator')->populateWithSampleValue($field);
} else {
$field->generateSampleItems();
}
}
$configuration['block_serialized'] = serialize($block_content);
// @todo File a core patch to change InlineBlock's view_mode to default.
$configuration['view_mode'] = _navigation_plus_get_view_mode($block_content, $configuration['view_mode']);
}
$configuration['label_display'] = 0;
$block_plugin->setConfiguration($configuration);
$this->eventDispatcher->dispatch(new PlaceBlockEvent($block_plugin), PlaceBlockEvent::class);
return $block_content;
}
/**
* Insert block.
*
* Inserts a block into section storage.
*
* @param array $destination
* An array of block destination parameters.
* @param object $block_plugin
* The block plugin instance.
* @param mixed $section
* The section.
*
* @return \Drupal\layout_builder\SectionComponent
* The inserted component.
*/
public function insertBlock(array $destination, object $block_plugin, mixed $section): SectionComponent {
// Ensure we have a valid region.
if ($destination['type'] === 'region' && empty($this->layoutManager->getDefinition($section->getLayoutId())->getRegions()[$destination['region']])) {
throw new \InvalidArgumentException($destination['region'] . ' is an invalid region to place block.');
}
// Add the new block to the section.
$component = new SectionComponent($this->uuid->generate(), $destination['region'], ['id' => $block_plugin->getPluginId()]);
$component->setConfiguration($block_plugin->getConfiguration());
if (!empty($destination['preceding_block_uuid']) && $destination['preceding_block_uuid'] !== 'undefined') {
$section->insertAfterComponent($destination['preceding_block_uuid'], $component);
}
else {
// Insert to first place.
$section->insertComponent(0, $component);
}
return $component;
}
/**
* Get section.
*
* Find the relevant section or create a new one for block placement.
*
* @param array $destination
* The block section destination.
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
* The section storage.
*
* @return array
* An array of the section and its delta.
*
* @throws \Drupal\Component\Plugin\Exception\ContextException
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function getSection(array $destination, SectionStorageInterface $section_storage): array {
if ($destination['type'] === 'section' && $destination['section'] === 'last') {
$delta = $section_storage->count();
}
else {
// Find the section and delta.
for ($delta = 0; $delta < $section_storage->count(); $delta++) {
$section = $section_storage->getSection($delta);
if ($section->getThirdPartySetting('lb_plus', 'uuid') === $destination['section']) {
break;
}
}
}
// Insert a new section.
if ($destination['type'] === 'section') {
$layout_settings = $this->getLbPlusSetting($section_storage, 'default_section');
$layout_plugin_id = $layout_settings['layout_plugin'];
$section = new Section($layout_plugin_id, $layout_settings);
$section_uuid = $this->uuid->generate();
$section->setThirdPartySetting('lb_plus', 'uuid', $section_uuid);
$section_storage->insertSection($delta, $section);
}
if (is_null($section->getLayoutId())) {
throw new \InvalidArgumentException('Please configure a default layout for this section.');
}
return [$section, $delta];
}
}
