Skip to content
Snippets Groups Projects
Verified Commit 1561edc2 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3043330 by acbramley, nterbogt, phenaproxima, Sam152, tim.plunkett,...

Issue #3043330 by acbramley, nterbogt, phenaproxima, Sam152, tim.plunkett, quietone, smustgrave, jibran, larowlan, azinck, catch, alexpott, tedbow, kim.pepper: Reduce the number of field blocks created for entities (possibly to zero)
parent eb10b686
No related branches found
No related tags found
29 merge requests!11131[10.4.x-only-DO-NOT-MERGE]: Issue ##2842525 Ajax attached to Views exposed filter form does not trigger callbacks,!9470[10.3.x-only-DO-NOT-MERGE]: #3331771 Fix file_get_contents(): Passing null to parameter,!8540Issue #3457061: Bootstrap Modal dialog Not closing after 10.3.0 Update,!8528Issue #3456871 by Tim Bozeman: Support NULL services,!8373Issue #3427374 by danflanagan8, Vighneshh: taxonomy_tid ViewsArgumentDefault...,!7526Expose roles in response,!7352Draft: Resolve #3203489 "Set filename as",!3878Removed unused condition head title for views,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3133core/modules/system/css/components/hidden.module.css,!2964Issue #2865710 : Dependencies from only one instance of a widget are used in display modes,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2794Issue #3100732: Allow specifying `meta` data on JSON:API objects,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2062Issue #3246454: Add weekly granularity to views date sort,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!877Issue #2708101: Default value for link text is not saved,!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493
Pipeline #113602 canceled
Pipeline: drupal

#113604

    Showing
    with 427 additions and 5 deletions
    expose_all_field_blocks: false
    ......@@ -86,3 +86,11 @@ layout_plugin.settings.layout_twocol_section:
    layout_plugin.settings.layout_threecol_section:
    type: layout_builder_multi_width
    layout_builder.settings:
    type: config_object
    label: 'Layout builder settings'
    mapping:
    expose_all_field_blocks:
    type: boolean
    label: 'Expose all field blocks'
    name: 'Layout Builder'
    type: module
    description: 'Allows users to add and arrange blocks and content fields directly on the content.'
    configure: layout_builder.settings
    package: Core
    version: VERSION
    dependencies:
    ......
    layout_builder.settings:
    title: 'Layout Builder'
    parent: system.admin_config_content
    description: 'Configure Layout Builder settings.'
    route_name: layout_builder.settings
    administer layout builder:
    title: 'Administer layout builder'
    restrict access: true
    configure any layout:
    title: 'Configure any layout'
    restrict access: true
    ......
    ......@@ -69,3 +69,12 @@ function layout_builder_post_update_timestamp_formatter(array &$sandbox = NULL):
    return $update;
    });
    }
    /**
    * Configure the default expose all fields setting.
    */
    function layout_builder_post_update_default_expose_field_block_setting(): void {
    \Drupal::configFactory()->getEditable('layout_builder.settings')
    ->set('expose_all_field_blocks', TRUE)
    ->save(TRUE);
    }
    layout_builder.settings:
    path: '/admin/config/content/layout-builder'
    defaults:
    _form: '\Drupal\layout_builder\Form\LayoutBuilderSettingsForm'
    _title: 'Layout Builder'
    requirements:
    _permission: 'administer layout builder'
    layout_builder.choose_section:
    path: '/layout_builder/choose/section/{section_storage_type}/{section_storage}/{delta}'
    defaults:
    ......
    ......@@ -68,3 +68,6 @@ services:
    layout_builder.element.prepare_layout:
    class: Drupal\layout_builder\EventSubscriber\PrepareLayout
    arguments: ['@layout_builder.tempstore_repository', '@messenger']
    layout_builder.config_subscriber:
    class: Drupal\layout_builder\EventSubscriber\LayoutBuilderConfigSubscriber
    arguments: ['@plugin.manager.block']
    ......@@ -159,6 +159,19 @@ public function preSave(EntityStorageInterface $storage) {
    }
    }
    /**
    * {@inheritdoc}
    */
    public function save(): int {
    $return = parent::save();
    if (!\Drupal::config('layout_builder.settings')->get('expose_all_field_blocks')) {
    // Invalidate the block cache in order to regenerate field block
    // definitions.
    \Drupal::service('plugin.manager.block')->clearCachedDefinitions();
    }
    return $return;
    }
    /**
    * Removes a layout section field if it is no longer needed.
    *
    ......
    <?php
    declare(strict_types=1);
    namespace Drupal\layout_builder\EventSubscriber;
    use Drupal\Core\Block\BlockManagerInterface;
    use Drupal\Core\Config\ConfigCrudEvent;
    use Drupal\Core\Config\ConfigEvents;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    /**
    * Layout Builder Config subscriber.
    */
    final class LayoutBuilderConfigSubscriber implements EventSubscriberInterface {
    /**
    * Constructs a LayoutBuilderConfigSubscriber.
    */
    public function __construct(
    protected BlockManagerInterface $blockManager,
    ) {
    }
    /**
    * Clears the block plugin cache when expose_all_field_blocks changes.
    *
    * @param \Drupal\Core\Config\ConfigCrudEvent $event
    * The configuration event.
    */
    public function onConfigSave(ConfigCrudEvent $event): void {
    $saved_config = $event->getConfig();
    if ($saved_config->getName() == 'layout_builder.settings' && $event->isChanged('expose_all_field_blocks')) {
    $this->blockManager->clearCachedDefinitions();
    }
    }
    /**
    * {@inheritdoc}
    */
    public static function getSubscribedEvents(): array {
    $events[ConfigEvents::SAVE][] = ['onConfigSave'];
    return $events;
    }
    }
    <?php
    declare(strict_types=1);
    namespace Drupal\layout_builder\Form;
    use Drupal\Core\Form\ConfigFormBase;
    use Drupal\Core\Form\FormStateInterface;
    use Drupal\Core\Form\RedundantEditableConfigNamesTrait;
    /**
    * Configure layout builder settings for this site.
    *
    * @internal
    */
    final class LayoutBuilderSettingsForm extends ConfigFormBase {
    use RedundantEditableConfigNamesTrait;
    /**
    * {@inheritdoc}
    */
    public function getFormId(): string {
    return 'layout_builder_settings';
    }
    /**
    * {@inheritdoc}
    */
    public function buildForm(array $form, FormStateInterface $form_state): array {
    $form['expose_all_field_blocks'] = [
    '#type' => 'checkbox',
    '#title' => $this->t('Expose all fields as blocks to layout builder'),
    '#description' => $this->t('When enabled, this setting exposes all fields for all entity view displays.<br/> When disabled, only entity type bundles that have layout builder enabled will have their fields exposed.<br/> Enabling this setting could <strong>significantly decrease performance</strong> on sites with a large number of entity types and bundles.'),
    '#config_target' => 'layout_builder.settings:expose_all_field_blocks',
    ];
    return parent::buildForm($form, $form_state);
    }
    }
    ......@@ -4,6 +4,7 @@
    use Drupal\Component\Plugin\Derivative\DeriverBase;
    use Drupal\Component\Plugin\PluginBase;
    use Drupal\Core\Config\ConfigFactoryInterface;
    use Drupal\Core\Entity\EntityFieldManagerInterface;
    use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
    use Drupal\Core\Entity\EntityTypeManagerInterface;
    ......@@ -63,8 +64,16 @@ class ExtraFieldBlockDeriver extends DeriverBase implements ContainerDeriverInte
    * The entity type bundle info.
    * @param \Drupal\Core\Entity\EntityTypeRepositoryInterface $entity_type_repository
    * The entity type repository.
    * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
    * The config factory.
    */
    public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeRepositoryInterface $entity_type_repository) {
    public function __construct(
    EntityFieldManagerInterface $entity_field_manager,
    EntityTypeManagerInterface $entity_type_manager,
    EntityTypeBundleInfoInterface $entity_type_bundle_info,
    EntityTypeRepositoryInterface $entity_type_repository,
    protected ConfigFactoryInterface $configFactory,
    ) {
    $this->entityFieldManager = $entity_field_manager;
    $this->entityTypeManager = $entity_type_manager;
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
    ......@@ -79,7 +88,8 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
    $container->get('entity_field.manager'),
    $container->get('entity_type.manager'),
    $container->get('entity_type.bundle.info'),
    $container->get('entity_type.repository')
    $container->get('entity_type.repository'),
    $container->get('config.factory')
    );
    }
    ......@@ -88,14 +98,26 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
    */
    public function getDerivativeDefinitions($base_plugin_definition) {
    $entity_type_labels = $this->entityTypeRepository->getEntityTypeLabels();
    $enabled_bundle_ids = $this->bundleIdsWithLayoutBuilderDisplays();
    $expose_all_fields = $this->configFactory->get('layout_builder.settings')->get('expose_all_field_blocks');
    foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
    // Only process fieldable entity types.
    if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
    continue;
    }
    // If not loading everything, skip entity types that aren't included.
    if (!$expose_all_fields && !isset($enabled_bundle_ids[$entity_type_id])) {
    continue;
    }
    $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
    foreach ($bundles as $bundle_id => $bundle) {
    // If not loading everything, skip bundle types that aren't included.
    if (!$expose_all_fields && !isset($enabled_bundle_ids[$entity_type_id][$bundle_id])) {
    continue;
    }
    $extra_fields = $this->entityFieldManager->getExtraFields($entity_type_id, $bundle_id);
    // Skip bundles without any extra fields.
    if (empty($extra_fields['display'])) {
    ......@@ -123,4 +145,23 @@ public function getDerivativeDefinitions($base_plugin_definition) {
    return $this->derivatives;
    }
    /**
    * Gets a list of entity type and bundle tuples that have layout builder enabled.
    *
    * @return array
    * A structured array with entity type as first key, bundle as second.
    */
    protected function bundleIdsWithLayoutBuilderDisplays(): array {
    /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface[] $displays */
    $displays = $this->entityTypeManager->getStorage('entity_view_display')->loadByProperties([
    'third_party_settings.layout_builder.enabled' => TRUE,
    ]);
    $layout_bundles = [];
    foreach ($displays as $display) {
    $bundle = $display->getTargetBundle();
    $layout_bundles[$display->getTargetEntityTypeId()][$bundle] = $bundle;
    }
    return $layout_bundles;
    }
    }
    ......@@ -4,6 +4,8 @@
    use Drupal\Component\Plugin\Derivative\DeriverBase;
    use Drupal\Component\Plugin\PluginBase;
    use Drupal\Core\Config\ConfigFactoryInterface;
    use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
    use Drupal\Core\Entity\EntityFieldManagerInterface;
    use Drupal\Core\Entity\EntityTypeRepositoryInterface;
    use Drupal\Core\Field\FieldConfigInterface;
    ......@@ -66,8 +68,19 @@ class FieldBlockDeriver extends DeriverBase implements ContainerDeriverInterface
    * The field type manager.
    * @param \Drupal\Core\Field\FormatterPluginManager $formatter_manager
    * The formatter manager.
    * @param \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $entityViewDisplayStorage
    * The entity view display storage.
    * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
    * The config factory.
    */
    public function __construct(EntityTypeRepositoryInterface $entity_type_repository, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager, FormatterPluginManager $formatter_manager) {
    public function __construct(
    EntityTypeRepositoryInterface $entity_type_repository,
    EntityFieldManagerInterface $entity_field_manager,
    FieldTypePluginManagerInterface $field_type_manager,
    FormatterPluginManager $formatter_manager,
    protected ConfigEntityStorageInterface $entityViewDisplayStorage,
    protected ConfigFactoryInterface $configFactory,
    ) {
    $this->entityTypeRepository = $entity_type_repository;
    $this->entityFieldManager = $entity_field_manager;
    $this->fieldTypeManager = $field_type_manager;
    ......@@ -82,7 +95,9 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
    $container->get('entity_type.repository'),
    $container->get('entity_field.manager'),
    $container->get('plugin.manager.field.field_type'),
    $container->get('plugin.manager.field.formatter')
    $container->get('plugin.manager.field.formatter'),
    $container->get('entity_type.manager')->getStorage('entity_view_display'),
    $container->get('config.factory')
    );
    }
    ......@@ -91,7 +106,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
    */
    public function getDerivativeDefinitions($base_plugin_definition) {
    $entity_type_labels = $this->entityTypeRepository->getEntityTypeLabels();
    foreach ($this->entityFieldManager->getFieldMap() as $entity_type_id => $entity_field_map) {
    foreach ($this->getFieldMap() as $entity_type_id => $entity_field_map) {
    foreach ($entity_field_map as $field_name => $field_info) {
    // Skip fields without any formatters.
    $options = $this->formatterManager->getOptions($field_info['type']);
    ......@@ -142,4 +157,50 @@ public function getDerivativeDefinitions($base_plugin_definition) {
    return $this->derivatives;
    }
    /**
    * Returns the entity field map for deriving block definitions.
    *
    * @return array
    * The entity field map.
    *
    * @see \Drupal\Core\Entity\EntityFieldManagerInterface::getFieldMap()
    */
    protected function getFieldMap(): array {
    $field_map = $this->entityFieldManager->getFieldMap();
    // If all fields are exposed as field blocks, just return the field map
    // without any further processing.
    if ($this->configFactory->get('layout_builder.settings')->get('expose_all_field_blocks')) {
    return $field_map;
    }
    // Load all entity view displays which are using Layout Builder.
    /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface[] $displays */
    $displays = $this->entityViewDisplayStorage->loadByProperties([
    'third_party_settings.layout_builder.enabled' => TRUE,
    ]);
    $layout_bundles = [];
    foreach ($displays as $display) {
    $bundle = $display->getTargetBundle();
    $layout_bundles[$display->getTargetEntityTypeId()][$bundle] = $bundle;
    }
    // Process $field_map, removing any entity types which are not using Layout
    // Builder.
    $field_map = array_intersect_key($field_map, $layout_bundles);
    foreach ($field_map as $entity_type_id => $fields) {
    foreach ($fields as $field_name => $field_info) {
    $field_map[$entity_type_id][$field_name]['bundles'] = array_intersect($field_info['bundles'], $layout_bundles[$entity_type_id]);
    // If no bundles are using Layout Builder, remove this field from the
    // field map.
    if (empty($field_info['bundles'])) {
    unset($field_map[$entity_type_id][$field_name]);
    }
    }
    }
    return $field_map;
    }
    }
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\layout_builder\Functional;
    use Drupal\Core\Url;
    use Drupal\Tests\BrowserTestBase;
    /**
    * Tests the Layout Builder settings form.
    *
    * @coversDefaultClass \Drupal\layout_builder\Form\LayoutBuilderSettingsForm
    * @group layout_builder
    */
    class SettingsFormTest extends BrowserTestBase {
    /**
    * {@inheritdoc}
    */
    protected static $modules = ['layout_builder'];
    /**
    * {@inheritdoc}
    */
    protected $defaultTheme = 'stark';
    /**
    * Tests the Layout Builder settings form.
    */
    public function testSettingsForm(): void {
    $this->drupalLogin($this->drupalCreateUser([
    'access administration pages',
    'administer layout builder',
    ]));
    $this->drupalGet(Url::fromRoute('layout_builder.settings'));
    $this->assertSession()->statusCodeEquals(200);
    $this->submitForm([
    'expose_all_field_blocks' => 1,
    ], 'Save configuration');
    $this->assertSession()->pageTextContains('The configuration options have been saved');
    $this->assertSession()->checkboxChecked('expose_all_field_blocks');
    $this->submitForm([
    'expose_all_field_blocks' => 0,
    ], 'Save configuration');
    $this->assertSession()->pageTextContains('The configuration options have been saved');
    $this->assertSession()->checkboxNotChecked('expose_all_field_blocks');
    }
    }
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\layout_builder\Functional\Update;
    use Drupal\FunctionalTests\Update\UpdatePathTestBase;
    /**
    * Tests creation of layout_builder settings.
    *
    * @see layout_builder_post_update_default_expose_field_block_setting()
    *
    * @group Update
    */
    class LayoutBuilderSettingsUpdateTest extends UpdatePathTestBase {
    /**
    * {@inheritdoc}
    */
    protected function setDatabaseDumpFiles() {
    $this->databaseDumpFiles = [
    __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz',
    __DIR__ . '/../../../fixtures/update/layout-builder.php',
    ];
    }
    /**
    * Tests layout_builder_post_update_default_expose_field_block_setting().
    */
    public function testLayoutBuilderPostUpdateExposeFieldBlockSetting(): void {
    // Ensure config is not present.
    $config = $this->config('layout_builder.settings');
    $this->assertTrue($config->isNew());
    $this->runUpdates();
    // Ensure config is present and setting is enabled.
    $updated_config = $this->config('layout_builder.settings');
    $this->assertFalse($updated_config->isNew());
    $this->assertTrue($updated_config->get('expose_all_field_blocks'));
    }
    }
    ......@@ -37,6 +37,12 @@ class BlockFilterTest extends WebDriverTestBase {
    protected function setUp(): void {
    parent::setUp();
    // This test makes assertions on lists of blocks, this includes a derived
    // block from the user entity.
    \Drupal::configFactory()->getEditable('layout_builder.settings')
    ->set('expose_all_field_blocks', TRUE)
    ->save();
    $this->drupalLogin($this->drupalCreateUser([
    'configure any layout',
    ]));
    ......
    ......@@ -40,6 +40,9 @@ class FieldBlockTest extends WebDriverTestBase {
    protected function setUp(): void {
    parent::setUp();
    \Drupal::configFactory()->getEditable('layout_builder.settings')
    ->set('expose_all_field_blocks', TRUE)
    ->save();
    $field_storage = FieldStorageConfig::create([
    'field_name' => 'field_date',
    'entity_type' => 'user',
    ......
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\layout_builder\Kernel;
    use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
    use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
    /**
    * Tests field block plugin derivatives.
    *
    * @group layout_builder
    */
    class FieldBlockDeriverTest extends EntityKernelTestBase {
    /**
    * {@inheritdoc}
    */
    protected static $modules = [
    'layout_builder',
    'layout_discovery',
    ];
    /**
    * Tests that field block derivers respect expose_all_field_blocks config.
    *
    * When expose_all_field_blocks is disabled (the default setting), only
    * bundles that have layout builder enabled will expose their fields as
    * field blocks.
    */
    public function testFieldBlockDerivers(): void {
    $plugins = $this->getBlockPluginIds();
    // Setting is disabled and entity_test bundles do not have layout builder
    // configured.
    $this->assertNotContains('field_block:user:user:name', $plugins);
    $this->assertNotContains('extra_field_block:user:user:member_for', $plugins);
    $this->assertNotContains('field_block:entity_test:entity_test:id', $plugins);
    // Enabling layout builder for entity_test adds field blocks for entity_test
    // bundles, but not for the user entity type.
    $display = LayoutBuilderEntityViewDisplay::create([
    'targetEntityType' => 'entity_test',
    'bundle' => 'entity_test',
    'mode' => 'default',
    'status' => TRUE,
    'third_party_settings' => [
    'layout_builder' => [
    'enabled' => TRUE,
    ],
    ],
    ]);
    $display->save();
    $plugins = $this->getBlockPluginIds();
    $this->assertContains('field_block:entity_test:entity_test:id', $plugins);
    $this->assertNotContains('field_block:user:user:name', $plugins);
    $this->assertNotContains('extra_field_block:user:user:member_for', $plugins);
    // Exposing all field blocks adds them for the user entity type.
    \Drupal::configFactory()->getEditable('layout_builder.settings')
    ->set('expose_all_field_blocks', TRUE)
    ->save();
    $plugins = $this->getBlockPluginIds();
    $this->assertContains('field_block:user:user:name', $plugins);
    $this->assertContains('extra_field_block:user:user:member_for', $plugins);
    }
    /**
    * Get an uncached list of block plugin IDs.
    *
    * @return array
    * A list of block plugin IDs.
    */
    private function getBlockPluginIds(): array {
    return \array_keys(\Drupal::service('plugin.manager.block')->getDefinitions());
    }
    }
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment