Skip to content
Snippets Groups Projects
Verified Commit 9f4b1f4e 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)

(cherry picked from commit 1561edc2)
parent 39df12b2
No related branches found
No related tags found
26 merge requests!11185Issue #3477324 by andypost, alexpott: Fix usage of str_getcsv() and fgetcsv() for PHP 8.4,!10602Issue #3438769 by vinmayiswamy, antonnavi, michelle, amateescu: Sub workspace does not clear,!10301Issue #3469309 by mstrelan, smustgrave, moshe weitzman: Use one-time login...,!10187Issue #3487488 by dakwamine: ExtensionMimeTypeGuesser::guessMimeType must support file names with "0" (zero) like foo.0.zip,!9944Issue #3483353: Consider making the createCopy config action optionally fail...,!9929Issue #3445469 by pooja_sharma, smustgrave: Add additional test coverage for...,!9787Resolve issue 3479427 - bootstrap barrio issue under Windows,!9742Issue #3463908 by catch, quietone: Split OptionsFieldUiTest into two,!9526Issue #3458177 by mondrake, catch, quietone, godotislate, longwave, larowlan,...,!8738Issue #3424162 by camilledavis, dineshkumarbollu, smustgrave: Claro...,!8704Make greek characters available in ckeditor5,!8597Draft: Issue #3442259 by catch, quietone, dww: Reduce time of Migrate Upgrade tests...,!8533Issue #3446962 by kim.pepper: Remove incorrectly added...,!8517Issue #3443748 by NexusNovaz, smustgrave: Testcase creates false positive,!8325Update file Sort.php,!8095Expose document root on install,!7930Resolve #3427374 "Taxonomytid viewsargumentdefault plugin",!7627Issue #3439440 by nicxvan, Binoli Lalani, longwave: Remove country support from DateFormatter,!7445Issue #3440169: When using drupalGet(), provide an associative array for $headers,!7401#3271894 Fix documented StreamWrapperInterface return types for realpath() and dirname(),!7384Add constraints to system.advisories,!7078Issue #3320569 by Spokje, mondrake, smustgrave, longwave, quietone, Lendude,...,!6622Issue #2559833 by piggito, mohit_aghera, larowlan, guptahemant, vakulrai,...,!6502Draft: Resolve #2938524 "Plach testing issue",!38582585169-10.1.x,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key
Pipeline #113589 passed with warnings
Pipeline: drupal

#113601

    Pipeline: drupal

    #113597

      Pipeline: drupal

      #113594

        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