Skip to content
Snippets Groups Projects
Commit 50203298 authored by catch's avatar catch
Browse files

Issue #3264633 by mstrelan, Spokje, dww, larowlan, tim.plunkett, xjm: Remove...

Issue #3264633 by mstrelan, Spokje, dww, larowlan, tim.plunkett, xjm: Remove \Drupal\layout_builder\QuickEditIntegration and refactor it so that quickedit contrib provides the integration with layout builder

(cherry picked from commit 9423caee)
parent 8aee2406
Branches
Tags
21 merge requests!12482Issue #3291780 by longwave, xjm: guzzlehttp/guzzle 6.5.8 requires guzzlehttp/psr7 ^1.9,!8357Issue #2994000 by Lendude, Pasqualle, quietone, pameeela: Notice in logs when...,!4488Issue #3376281: Random machine names no longer need to be wrapped in strtolower(),!3149Issue #3282285: Email "" does not comply with addr-spec of RFC 2822,!3000Issue #793660: Check for failure of hook_install,!2940Issue #3320240: Entity count query returns a string instead of int,!2937Issue #3315245: Order of languages overrides default language fallback,!2877Issue #3056652 by yogeshmpawar, mashermike, aalin, ranjith_kumar_k_u: Link...,!2804URL of image field formatter use absolute URL option.,!2749Issue #3309024: Focus on the wrong submit button after AJAX submit,!2692Issue #3284010 by _shY, mherchel, Abhijith S: "Create content" link within...,!2608Issue #2430379 by quietone, znerol, larowlan: Add explicit test for session...,!2575Issue #3198340 by alexpott, xjm, cilefen, Mile23, mmjvb, catch, longwave, mfb,...,!2555Issue #3277148 by rpayanm, andregp, joachim, Farnoosh, Athrylith, Jingting:...,!2554Issue #3277148 by rpayanm, andregp, joachim, Farnoosh, Athrylith, Jingting:...,!2539Issue #3299806 by BenStallings: Include uuid field in d7_node migration, if present.,!2453Issue #332796 by voleger, dww, Steve Dondley, ykhadilkar, Dave Reid,...,!1627Issue #3082958: Add gitignore(s) to composer-ready project templates,!1014Issue #3226806: Move filter implementations from filter.module to plugin classes,!939Issue #2971209: Allow the MediaLibraryUiBuilder service to use an alternative view display,!88Issue #3163299: Ajax exposed filters not working for multiple instances of the same Views block placed on one page
Showing
with 614 additions and 367 deletions
<?php
namespace Drupal\Tests\hal\Functional\quickedit;
use Drupal\Tests\hal\Functional\layout_builder\LayoutBuilderEntityViewDisplayHalJsonAnonTest;
/**
* @group hal
* @group legacy
*/
class QuickEditLayoutBuilderEntityViewDisplayHalJsonAnonTest extends LayoutBuilderEntityViewDisplayHalJsonAnonTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['quickedit'];
}
<?php
namespace Drupal\Tests\hal\Functional\quickedit;
use Drupal\Tests\hal\Functional\layout_builder\LayoutBuilderEntityViewDisplayHalJsonBasicAuthTest;
/**
* @group hal
* @group legacy
*/
class QuickEditLayoutBuilderEntityViewDisplayHalJsonBasicAuthTest extends LayoutBuilderEntityViewDisplayHalJsonBasicAuthTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['quickedit'];
}
<?php
namespace Drupal\Tests\hal\Functional\quickedit;
use Drupal\Tests\hal\Functional\layout_builder\LayoutBuilderEntityViewDisplayHalJsonCookieTest;
/**
* @group hal
* @group legacy
*/
class QuickEditLayoutBuilderEntityViewDisplayHalJsonCookieTest extends LayoutBuilderEntityViewDisplayHalJsonCookieTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['quickedit'];
}
......@@ -192,7 +192,7 @@ protected function serializeField($field, array $context, $format) {
// @todo Replace this workaround after https://www.drupal.org/node/3043245
// or remove the need for this in https://www.drupal.org/node/2942975.
// See \Drupal\layout_builder\Normalizer\LayoutEntityDisplayNormalizer.
if ($context['resource_object']->getResourceType()->getDeserializationTargetClass() === 'Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay' && $context['resource_object']->getField('third_party_settings') === $field) {
if (is_a($context['resource_object']->getResourceType()->getDeserializationTargetClass(), 'Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay', TRUE) && $context['resource_object']->getField('third_party_settings') === $field) {
unset($field['layout_builder']['sections']);
}
......
......@@ -26,7 +26,6 @@
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
use Drupal\layout_builder\QuickEditIntegration;
/**
* Implements hook_help().
......@@ -162,12 +161,6 @@ function layout_builder_entity_view_alter(array &$build, EntityInterface $entity
if ($display instanceof LayoutBuilderEntityViewDisplay && strpos($route_name, 'layout_builder.') === 0) {
unset($build['#contextual_links']);
}
if (\Drupal::moduleHandler()->moduleExists('quickedit')) {
/** @var \Drupal\layout_builder\QuickEditIntegration $quick_edit_integration */
$quick_edit_integration = \Drupal::classResolver(QuickEditIntegration::class);
$quick_edit_integration->entityViewAlter($build, $entity, $display);
}
}
/**
......@@ -351,15 +344,6 @@ function layout_builder_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMa
}
}
/**
* Implements hook_quickedit_render_field().
*/
function layout_builder_quickedit_render_field(EntityInterface $entity, $field_name, $view_mode_id, $langcode) {
/** @var \Drupal\layout_builder\QuickEditIntegration $quick_edit_integration */
$quick_edit_integration = \Drupal::classResolver(QuickEditIntegration::class);
return $quick_edit_integration->quickEditRenderField($entity, $field_name, $view_mode_id, $langcode);
}
/**
* Implements hook_entity_translation_create().
*/
......
......@@ -18,7 +18,6 @@
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\layout_builder\LayoutEntityHelperTrait;
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
use Drupal\layout_builder\QuickEditIntegration;
use Drupal\layout_builder\Section;
use Drupal\layout_builder\SectionComponent;
use Drupal\layout_builder\SectionListTrait;
......@@ -473,7 +472,7 @@ private function sectionStorageManager() {
* {@inheritdoc}
*/
public function getComponent($name) {
if ($this->isLayoutBuilderEnabled() && $section_component = $this->getQuickEditSectionComponent() ?: $this->getSectionComponentForFieldName($name)) {
if ($this->isLayoutBuilderEnabled() && $section_component = $this->getSectionComponentForFieldName($name)) {
$plugin = $section_component->getPlugin();
if ($plugin instanceof ConfigurableInterface) {
$configuration = $plugin->getConfiguration();
......@@ -485,43 +484,6 @@ public function getComponent($name) {
return parent::getComponent($name);
}
/**
* Returns the Quick Edit formatter settings.
*
* @return \Drupal\layout_builder\SectionComponent|null
* The section component if it is available.
*
* @see \Drupal\layout_builder\QuickEditIntegration::entityViewAlter()
* @see \Drupal\quickedit\MetadataGenerator::generateFieldMetadata()
*/
private function getQuickEditSectionComponent() {
// To determine the Quick Edit view_mode ID we need an originalMode set.
if ($original_mode = $this->getOriginalMode()) {
$parts = explode('-', $original_mode);
// The Quick Edit view mode ID is created by
// \Drupal\layout_builder\QuickEditIntegration::entityViewAlter()
// concatenating together the information we need to retrieve the Layout
// Builder component. It follows the structure prescribed by the
// documentation of hook_quickedit_render_field().
if (count($parts) === 6 && $parts[0] === 'layout_builder') {
[, $delta, $component_uuid, $entity_id] = QuickEditIntegration::deconstructViewModeId($original_mode);
$entity = $this->entityTypeManager()->getStorage($this->getTargetEntityTypeId())->load($entity_id);
$sections = $this->getEntitySections($entity);
if (isset($sections[$delta])) {
$component = $sections[$delta]->getComponent($component_uuid);
$plugin = $component->getPlugin();
// We only care about FieldBlock because these are only components
// that provide Quick Edit integration: Quick Edit enables in-place
// editing of fields of entities, not of anything else.
if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'field_block') {
return $component;
}
}
}
}
return NULL;
}
/**
* Gets the component for a given field name if any.
*
......
......@@ -2,21 +2,9 @@
namespace Drupal\layout_builder;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@trigger_error(__NAMESPACE__ . '\QuickEditIntegration is deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. Instead, use \Drupal\quickedit\LayoutBuilderIntegration. See https://www.drupal.org/node/3265518', E_USER_DEPRECATED);
use Drupal\quickedit\LayoutBuilderIntegration;
/**
* Helper methods for Quick Edit module integration.
......@@ -24,298 +12,4 @@
* @internal
* This is an internal utility class wrapping hook implementations.
*/
class QuickEditIntegration implements ContainerInjectionInterface {
use LoggerChannelTrait;
/**
* The section storage manager.
*
* @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
*/
protected $sectionStorageManager;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new QuickEditIntegration object.
*
* @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
* The section storage manager.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(SectionStorageManagerInterface $section_storage_manager, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager) {
$this->sectionStorageManager = $section_storage_manager;
$this->currentUser = $current_user;
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.layout_builder.section_storage'),
$container->get('current_user'),
$container->get('entity_type.manager')
);
}
/**
* Alters the entity view build for Quick Edit compatibility.
*
* When rendering fields outside of normal view modes, Quick Edit requires
* that modules identify themselves with a view mode ID in the format
* [module_name]-[information the module needs to rerender], as prescribed by
* hook_quickedit_render_field().
*
* @param array $build
* The built entity render array.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
* The entity view display.
*
* @see hook_quickedit_render_field()
* @see layout_builder_quickedit_render_field()
*/
public function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
if (!$entity instanceof FieldableEntityInterface || !isset($build['_layout_builder'])) {
return;
}
$build['#cache']['contexts'][] = 'user.permissions';
if (!$this->currentUser->hasPermission('access in-place editing')) {
return;
}
$cacheable_metadata = CacheableMetadata::createFromRenderArray($build);
$section_list = $this->sectionStorageManager->findByContext(
[
'display' => EntityContext::fromEntity($display),
'entity' => EntityContext::fromEntity($entity),
'view_mode' => new Context(new ContextDefinition('string'), $display->getMode()),
],
$cacheable_metadata
);
$cacheable_metadata->applyTo($build);
if (empty($section_list)) {
return;
}
// Create a hash of the sections and use it in the unique Quick Edit view
// mode ID. Any changes to the sections will result in a different hash,
// forcing Quick Edit's JavaScript to recognize any changes and retrieve
// up-to-date metadata.
$sections_hash = hash('sha256', serialize($section_list->getSections()));
// Track each component by their plugin ID, delta, region, and UUID.
$plugin_ids_to_update = [];
foreach (Element::children($build['_layout_builder']) as $delta) {
$section = $build['_layout_builder'][$delta];
if (!Element::isEmpty($section)) {
/** @var \Drupal\Core\Layout\LayoutDefinition $layout */
$layout = $section['#layout'];
$regions = $layout->getRegionNames();
foreach ($regions as $region) {
if (isset($section[$region])) {
foreach ($section[$region] as $uuid => $component) {
if (isset($component['#plugin_id']) && $this->supportQuickEditOnComponent($component, $entity)) {
$plugin_ids_to_update[$component['#plugin_id']][$delta][$region][$uuid] = $uuid;
}
}
}
}
}
}
// @todo Remove when https://www.drupal.org/node/3041850 is resolved.
$plugin_ids_to_update = array_filter($plugin_ids_to_update, function ($info) {
// Delta, region, and UUID each count as one.
return count($info, COUNT_RECURSIVE) === 3;
});
$plugin_ids_to_update = NestedArray::mergeDeepArray($plugin_ids_to_update, TRUE);
foreach ($plugin_ids_to_update as $delta => $regions) {
foreach ($regions as $region => $uuids) {
foreach ($uuids as $uuid => $component) {
$build['_layout_builder'][$delta][$region][$uuid]['content']['#view_mode'] = static::getViewModeId($entity, $display, $delta, $uuid, $sections_hash);
}
}
}
// Alter the Quick Edit view mode ID of all fields outside of the Layout
// Builder sections to force Quick Edit to request to the field metadata.
// @todo Remove this logic in https://www.drupal.org/project/node/2966136.
foreach (Element::children($build) as $field_name) {
if ($field_name !== '_layout_builder') {
$field_build = &$build[$field_name];
if (isset($field_build['#view_mode'])) {
$field_build['#view_mode'] = "layout_builder-{$display->getMode()}-non_component-$sections_hash";
}
}
}
}
/**
* Generates a Quick Edit view mode ID.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
* The entity view display.
* @param int $delta
* The delta.
* @param string $component_uuid
* The component UUID.
* @param string $sections_hash
* The hash of the sections; must change whenever the sections change.
*
* @return string
* The Quick Edit view mode ID.
*
* @see \Drupal\layout_builder\QuickEditIntegration::deconstructViewModeId()
*/
private static function getViewModeId(EntityInterface $entity, EntityViewDisplayInterface $display, $delta, $component_uuid, $sections_hash) {
return implode('-', [
'layout_builder',
$display->getMode(),
$delta,
// Replace the dashes in the component UUID because we need to
// use dashes to join the parts.
str_replace('-', '_', $component_uuid),
$entity->id(),
$sections_hash,
]);
}
/**
* Deconstructs the Quick Edit view mode ID into its constituent parts.
*
* @param string $quick_edit_view_mode_id
* The Quick Edit view mode ID.
*
* @return array
* An array containing the entity view mode ID, the delta, the component
* UUID, and the entity ID.
*
* @see \Drupal\layout_builder\QuickEditIntegration::getViewModeId()
*/
public static function deconstructViewModeId($quick_edit_view_mode_id) {
[, $entity_view_mode_id, $delta, $component_uuid, $entity_id] = explode('-', $quick_edit_view_mode_id, 7);
return [
$entity_view_mode_id,
// @todo Explicitly cast delta to an integer, remove this in
// https://www.drupal.org/project/drupal/issues/2984509.
(int) $delta,
// Replace the underscores with dash to get back the component UUID.
str_replace('_', '-', $component_uuid),
$entity_id,
];
}
/**
* Re-renders a field rendered by Layout Builder, edited with Quick Edit.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity.
* @param string $field_name
* The field name.
* @param string $quick_edit_view_mode_id
* The Quick Edit view mode ID.
* @param string $langcode
* The language code.
*
* @return array
* The re-rendered field.
*/
public function quickEditRenderField(FieldableEntityInterface $entity, $field_name, $quick_edit_view_mode_id, $langcode) {
[$entity_view_mode, $delta, $component_uuid] = static::deconstructViewModeId($quick_edit_view_mode_id);
$entity_build = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId())->view($entity, $entity_view_mode, $langcode);
$this->buildEntityView($entity_build);
if (isset($entity_build['_layout_builder'][$delta])) {
foreach (Element::children($entity_build['_layout_builder'][$delta]) as $region) {
if (isset($entity_build['_layout_builder'][$delta][$region][$component_uuid])) {
return $entity_build['_layout_builder'][$delta][$region][$component_uuid]['content'];
}
}
}
$this->getLogger('layout_builder')->warning('The field "%field" failed to render.', ['%field' => $field_name]);
return [];
}
/**
* {@inheritdoc}
*
* @todo Replace this hardcoded processing when
* https://www.drupal.org/project/drupal/issues/3041635 is resolved.
*
* @see \Drupal\Tests\EntityViewTrait::buildEntityView()
*/
private function buildEntityView(array &$elements) {
// If the default values for this element have not been loaded yet,
// populate them.
if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
$elements += \Drupal::service('element_info')->getInfo($elements['#type']);
}
// Make any final changes to the element before it is rendered. This means
// that the $element or the children can be altered or corrected before
// the element is rendered into the final text.
if (isset($elements['#pre_render'])) {
foreach ($elements['#pre_render'] as $callable) {
$elements = call_user_func($callable, $elements);
}
}
// And recurse.
$children = Element::children($elements, TRUE);
foreach ($children as $key) {
$this->buildEntityView($elements[$key]);
}
}
/**
* Determines whether a component has Quick Edit support.
*
* Only field_block components for display configurable fields should be
* supported.
*
* @param array $component
* The component render array.
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity being displayed.
*
* @return bool
* Whether Quick Edit is supported on the component.
*
* @see \Drupal\layout_builder\Plugin\Block\FieldBlock
*/
private function supportQuickEditOnComponent(array $component, FieldableEntityInterface $entity) {
if (isset($component['content']['#field_name'], $component['#base_plugin_id']) && $component['#base_plugin_id'] === 'field_block' && $entity->hasField($component['content']['#field_name'])) {
return $entity->getFieldDefinition($component['content']['#field_name'])->isDisplayConfigurable('view');
}
return FALSE;
}
}
class QuickEditIntegration extends LayoutBuilderIntegration {}
......@@ -16,6 +16,8 @@
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\quickedit\Entity\QuickEditLayoutBuilderEntityViewDisplay;
use Drupal\quickedit\LayoutBuilderIntegration;
/**
* Implements hook_help().
......@@ -174,10 +176,28 @@ function quickedit_preprocess_field(&$variables) {
}
}
/**
* Implements hook_entity_type_alter().
*/
function quickedit_entity_type_alter(array &$entity_types) {
if (\Drupal::moduleHandler()->moduleExists('layout_builder')) {
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
if ($entity_types['entity_view_display']->getClass() === 'Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay\LayoutBuilderEntityViewDisplay') {
$entity_types['entity_view_display']->setClass(QuickEditLayoutBuilderEntityViewDisplay::class);
}
}
}
/**
* Implements hook_entity_view_alter().
*/
function quickedit_entity_view_alter(&$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
if (\Drupal::moduleHandler()->moduleExists('layout_builder')) {
/** @var \Drupal\quickedit\LayoutBuilderIntegration $layout_builder_integration */
$layout_builder_integration = \Drupal::classResolver(LayoutBuilderIntegration::class);
$layout_builder_integration->entityViewAlter($build, $entity, $display);
}
if (isset($build['#embed'])) {
return;
}
......@@ -189,3 +209,12 @@ function quickedit_entity_view_alter(&$build, EntityInterface $entity, EntityVie
$build['#attributes']['data-quickedit-entity-id'] = $entity->getEntityTypeId() . '/' . $entity->id();
}
/**
* Implements hook_quickedit_render_field().
*/
function layout_builder_quickedit_render_field(EntityInterface $entity, $field_name, $view_mode_id, $langcode) {
/** @var \Drupal\quickedit\LayoutBuilderIntegration $layout_builder_integration */
$layout_builder_integration = \Drupal::classResolver(LayoutBuilderIntegration::class);
return $layout_builder_integration->quickEditRenderField($entity, $field_name, $view_mode_id, $langcode);
}
<?php
namespace Drupal\quickedit\Entity;
use Drupal\Component\Plugin\ConfigurableInterface;
use Drupal\Component\Plugin\DerivativeInspectionInterface;
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
use Drupal\quickedit\LayoutBuilderIntegration;
/**
* Provides an entity view display entity that has a layout with quickedit.
*/
class QuickEditLayoutBuilderEntityViewDisplay extends LayoutBuilderEntityViewDisplay {
/**
* {@inheritdoc}
*/
public function getComponent($name) {
if ($this->isLayoutBuilderEnabled() && $section_component = $this->getQuickEditSectionComponent()) {
$plugin = $section_component->getPlugin();
if ($plugin instanceof ConfigurableInterface) {
$configuration = $plugin->getConfiguration();
if (isset($configuration['formatter'])) {
return $configuration['formatter'];
}
}
}
return parent::getComponent($name);
}
/**
* Returns the Quick Edit formatter settings.
*
* @return \Drupal\layout_builder\SectionComponent|null
* The section component if it is available.
*
* @see \Drupal\quickedit\LayoutBuilderIntegration::entityViewAlter()
* @see \Drupal\quickedit\MetadataGenerator::generateFieldMetadata()
*/
private function getQuickEditSectionComponent() {
// To determine the Quick Edit view_mode ID we need an originalMode set.
if ($original_mode = $this->getOriginalMode()) {
$parts = explode('-', $original_mode);
// The Quick Edit view mode ID is created by
// \Drupal\quickedit\LayoutBuilderIntegration::entityViewAlter()
// concatenating together the information we need to retrieve the Layout
// Builder component. It follows the structure prescribed by the
// documentation of hook_quickedit_render_field().
if (count($parts) === 6 && $parts[0] === 'layout_builder') {
[, $delta, $component_uuid, $entity_id] = LayoutBuilderIntegration::deconstructViewModeId($original_mode);
$entity = $this->entityTypeManager()->getStorage($this->getTargetEntityTypeId())->load($entity_id);
$sections = $this->getEntitySections($entity);
if (isset($sections[$delta])) {
$component = $sections[$delta]->getComponent($component_uuid);
$plugin = $component->getPlugin();
// We only care about FieldBlock because these are only components
// that provide Quick Edit integration: Quick Edit enables in-place
// editing of fields of entities, not of anything else.
if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'field_block') {
return $component;
}
}
}
}
return NULL;
}
}
<?php
namespace Drupal\quickedit;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Helper methods for Layout Builder module integration.
*
* @internal
* This is an internal utility class wrapping hook implementations.
*/
class LayoutBuilderIntegration implements ContainerInjectionInterface {
use LoggerChannelTrait;
/**
* The section storage manager.
*
* @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
*/
protected $sectionStorageManager;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new LayoutBuilderIntegration object.
*
* @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
* The section storage manager.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(SectionStorageManagerInterface $section_storage_manager, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager) {
$this->sectionStorageManager = $section_storage_manager;
$this->currentUser = $current_user;
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.layout_builder.section_storage'),
$container->get('current_user'),
$container->get('entity_type.manager')
);
}
/**
* Alters the entity view build for Layout Builder compatibility.
*
* When rendering fields outside of normal view modes, Quick Edit requires
* that modules identify themselves with a view mode ID in the format
* [module_name]-[information the module needs to rerender], as prescribed by
* hook_quickedit_render_field().
*
* @param array $build
* The built entity render array.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
* The entity view display.
*
* @see hook_quickedit_render_field()
* @see layout_builder_quickedit_render_field()
*/
public function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
if (!$entity instanceof FieldableEntityInterface || !isset($build['_layout_builder'])) {
return;
}
$build['#cache']['contexts'][] = 'user.permissions';
if (!$this->currentUser->hasPermission('access in-place editing')) {
return;
}
$cacheable_metadata = CacheableMetadata::createFromRenderArray($build);
$section_list = $this->sectionStorageManager->findByContext(
[
'display' => EntityContext::fromEntity($display),
'entity' => EntityContext::fromEntity($entity),
'view_mode' => new Context(new ContextDefinition('string'), $display->getMode()),
],
$cacheable_metadata
);
$cacheable_metadata->applyTo($build);
if (empty($section_list)) {
return;
}
// Create a hash of the sections and use it in the unique Quick Edit view
// mode ID. Any changes to the sections will result in a different hash,
// forcing Quick Edit's JavaScript to recognize any changes and retrieve
// up-to-date metadata.
$sections_hash = hash('sha256', serialize($section_list->getSections()));
// Track each component by their plugin ID, delta, region, and UUID.
$plugin_ids_to_update = [];
foreach (Element::children($build['_layout_builder']) as $delta) {
$section = $build['_layout_builder'][$delta];
if (!Element::isEmpty($section)) {
/** @var \Drupal\Core\Layout\LayoutDefinition $layout */
$layout = $section['#layout'];
$regions = $layout->getRegionNames();
foreach ($regions as $region) {
if (isset($section[$region])) {
foreach ($section[$region] as $uuid => $component) {
if (isset($component['#plugin_id']) && $this->supportQuickEditOnComponent($component, $entity)) {
$plugin_ids_to_update[$component['#plugin_id']][$delta][$region][$uuid] = $uuid;
}
}
}
}
}
}
// @todo Remove when https://www.drupal.org/node/3041850 is resolved.
$plugin_ids_to_update = array_filter($plugin_ids_to_update, function ($info) {
// Delta, region, and UUID each count as one.
return count($info, COUNT_RECURSIVE) === 3;
});
$plugin_ids_to_update = NestedArray::mergeDeepArray($plugin_ids_to_update, TRUE);
foreach ($plugin_ids_to_update as $delta => $regions) {
foreach ($regions as $region => $uuids) {
foreach ($uuids as $uuid => $component) {
$build['_layout_builder'][$delta][$region][$uuid]['content']['#view_mode'] = static::getViewModeId($entity, $display, $delta, $uuid, $sections_hash);
}
}
}
// Alter the Quick Edit view mode ID of all fields outside of the Layout
// Builder sections to force Quick Edit to request to the field metadata.
// @todo Remove this logic in https://www.drupal.org/project/node/2966136.
foreach (Element::children($build) as $field_name) {
if ($field_name !== '_layout_builder') {
$field_build = &$build[$field_name];
if (isset($field_build['#view_mode'])) {
$field_build['#view_mode'] = "layout_builder-{$display->getMode()}-non_component-$sections_hash";
}
}
}
}
/**
* Generates a Quick Edit view mode ID.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
* The entity view display.
* @param int $delta
* The delta.
* @param string $component_uuid
* The component UUID.
* @param string $sections_hash
* The hash of the sections; must change whenever the sections change.
*
* @return string
* The Quick Edit view mode ID.
*
* @see \Drupal\quickedit\LayoutBuilderIntegration::deconstructViewModeId()
*/
private static function getViewModeId(EntityInterface $entity, EntityViewDisplayInterface $display, $delta, $component_uuid, $sections_hash) {
return implode('-', [
'layout_builder',
$display->getMode(),
$delta,
// Replace the dashes in the component UUID because we need to
// use dashes to join the parts.
str_replace('-', '_', $component_uuid),
$entity->id(),
$sections_hash,
]);
}
/**
* Deconstructs the Quick Edit view mode ID into its constituent parts.
*
* @param string $quick_edit_view_mode_id
* The Quick Edit view mode ID.
*
* @return array
* An array containing the entity view mode ID, the delta, the component
* UUID, and the entity ID.
*
* @see \Drupal\quickedit\LayoutBuilderIntegration::getViewModeId()
*/
public static function deconstructViewModeId($quick_edit_view_mode_id) {
[, $entity_view_mode_id, $delta, $component_uuid, $entity_id] = explode('-', $quick_edit_view_mode_id, 7);
return [
$entity_view_mode_id,
// @todo Explicitly cast delta to an integer, remove this in
// https://www.drupal.org/project/drupal/issues/2984509.
(int) $delta,
// Replace the underscores with dash to get back the component UUID.
str_replace('_', '-', $component_uuid),
$entity_id,
];
}
/**
* Re-renders a field rendered by Layout Builder, edited with Quick Edit.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity.
* @param string $field_name
* The field name.
* @param string $quick_edit_view_mode_id
* The Quick Edit view mode ID.
* @param string $langcode
* The language code.
*
* @return array
* The re-rendered field.
*/
public function quickEditRenderField(FieldableEntityInterface $entity, $field_name, $quick_edit_view_mode_id, $langcode) {
[$entity_view_mode, $delta, $component_uuid] = static::deconstructViewModeId($quick_edit_view_mode_id);
$entity_build = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId())->view($entity, $entity_view_mode, $langcode);
$this->buildEntityView($entity_build);
if (isset($entity_build['_layout_builder'][$delta])) {
foreach (Element::children($entity_build['_layout_builder'][$delta]) as $region) {
if (isset($entity_build['_layout_builder'][$delta][$region][$component_uuid])) {
return $entity_build['_layout_builder'][$delta][$region][$component_uuid]['content'];
}
}
}
$this->getLogger('layout_builder')->warning('The field "%field" failed to render.', ['%field' => $field_name]);
return [];
}
/**
* {@inheritdoc}
*
* @todo Replace this hardcoded processing when
* https://www.drupal.org/project/drupal/issues/3041635 is resolved.
*
* @see \Drupal\Tests\EntityViewTrait::buildEntityView()
*/
private function buildEntityView(array &$elements) {
// If the default values for this element have not been loaded yet,
// populate them.
if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
$elements += \Drupal::service('element_info')->getInfo($elements['#type']);
}
// Make any final changes to the element before it is rendered. This means
// that the $element or the children can be altered or corrected before
// the element is rendered into the final text.
if (isset($elements['#pre_render'])) {
foreach ($elements['#pre_render'] as $callable) {
$elements = call_user_func($callable, $elements);
}
}
// And recurse.
$children = Element::children($elements, TRUE);
foreach ($children as $key) {
$this->buildEntityView($elements[$key]);
}
}
/**
* Determines whether a component has Quick Edit support.
*
* Only field_block components for display configurable fields should be
* supported.
*
* @param array $component
* The component render array.
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity being displayed.
*
* @return bool
* Whether Quick Edit is supported on the component.
*
* @see \Drupal\layout_builder\Plugin\Block\FieldBlock
*/
private function supportQuickEditOnComponent(array $component, FieldableEntityInterface $entity) {
if (isset($component['content']['#field_name'], $component['#base_plugin_id']) && $component['#base_plugin_id'] === 'field_block' && $entity->hasField($component['content']['#field_name'])) {
return $entity->getFieldDefinition($component['content']['#field_name'])->isDisplayConfigurable('view');
}
return FALSE;
}
}
<?php
namespace Drupal\Tests\quickedit\Functional\Jsonapi;
use Drupal\Tests\layout_builder\Functional\Jsonapi\LayoutBuilderEntityViewDisplayTest;
/**
* JSON:API integration test for the "EntityViewDisplay" config entity type.
*
* @group jsonapi
* @group layout_builder
* @group quickedit
*/
class QuickEditLayoutBuilderEntityViewDisplayTest extends LayoutBuilderEntityViewDisplayTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['quickedit'];
}
<?php
namespace Drupal\Tests\quickedit\Functional\Rest;
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayJsonAnonTest;
/**
* @group quickedit
* @group layout_builder
* @group rest
*/
class QuickEditLayoutBuilderEntityViewDisplayJsonAnonTest extends LayoutBuilderEntityViewDisplayJsonAnonTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['quickedit'];
}
<?php
namespace Drupal\Tests\quickedit\Functional\Rest;
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayJsonBasicAuthTest;
/**
* @group quickedit
* @group layout_builder
* @group rest
*/
class QuickEditLayoutBuilderEntityViewDisplayJsonBasicAuthTest extends LayoutBuilderEntityViewDisplayJsonBasicAuthTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['quickedit'];
}
<?php
namespace Drupal\Tests\quickedit\Functional\Rest;
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayJsonCookieTest;
/**
* @group quickedit
* @group layout_builder
* @group rest
*/
class QuickEditLayoutBuilderEntityViewDisplayJsonCookieTest extends LayoutBuilderEntityViewDisplayJsonCookieTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['quickedit'];
}
<?php
namespace Drupal\Tests\quickedit\Functional\Rest;
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayXmlAnonTest;
/**
* @group quickedit
* @group layout_builder
* @group rest
*/
class QuickEditLayoutBuilderEntityViewDisplayXmlAnonTest extends LayoutBuilderEntityViewDisplayXmlAnonTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['quickedit'];
}
<?php
namespace Drupal\Tests\quickedit\Functional\Rest;
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayXmlBasicAuthTest;
/**
* @group quickedit
* @group layout_builder
* @group rest
*/
class QuickEditLayoutBuilderEntityViewDisplayXmlBasicAuthTest extends LayoutBuilderEntityViewDisplayXmlBasicAuthTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['quickedit'];
}
<?php
namespace Drupal\Tests\quickedit\Functional\Rest;
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayXmlCookieTest;
/**
* @group quickedit
* @group layout_builder
* @group rest
*/
class QuickEditLayoutBuilderEntityViewDisplayXmlCookieTest extends LayoutBuilderEntityViewDisplayXmlCookieTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['quickedit'];
}
......@@ -14,7 +14,7 @@
/**
* @group quickedit
*/
class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
class LayoutBuilderIntegrationTest extends QuickEditJavascriptTestBase {
use EntityReferenceTestTrait;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment