Commit 9a90e5e0 authored by catch's avatar catch
Browse files

Issue #3478224 by plopesc, m4olivei, penyaskito, dmitry.korhov, phenaproxima:...

Issue #3478224 by plopesc, m4olivei, penyaskito, dmitry.korhov, phenaproxima: Provide Config Action to add new blocks to navigation from recipes

(cherry picked from commit f91792e4)
parent 7406530a
Loading
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@ navigation_layout:
  library: navigation/navigation.layout
  category: 'Navigation'
  class: \Drupal\navigation\NavigationLayout
  default_region: first
  default_region: content
  icon_map:
    - [ content ]
    - [ footer ]
+85 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\navigation\Plugin\ConfigAction;

use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Config\Action\Attribute\ConfigAction;
use Drupal\Core\Config\Action\ConfigActionException;
use Drupal\Core\Config\Action\ConfigActionPluginInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\layout_builder\SectionComponent;
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
use Drupal\layout_builder\SectionStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * @internal
 *   This API is experimental.
 */
#[ConfigAction(
  id: 'addNavigationBlock',
  admin_label: new TranslatableMarkup('Add navigation block'),
)]
final class AddNavigationBlock implements ConfigActionPluginInterface, ContainerFactoryPluginInterface {

  public function __construct(
    protected readonly SectionStorageManagerInterface $sectionStorageManager,
    protected readonly UuidInterface $uuidGenerator,
  ) {
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    return new static(
      $container->get(SectionStorageManagerInterface::class),
      $container->get(UuidInterface::class),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function apply(string $configName, mixed $value): void {
    if ($configName !== 'navigation.block_layout') {
      throw new ConfigActionException('addNavigationBlock can only be executed for the navigation.block_layout config.');
    }
    // Load the navigation section storage.
    $navigation_storage = $this->sectionStorageManager->load('navigation', [
      'navigation' => new Context(new ContextDefinition('string'), 'navigation'),
    ]);
    if (!$navigation_storage instanceof SectionStorageInterface) {
      throw new ConfigActionException('Unable to load Navigation Layout storage.');
    }

    $section = $navigation_storage->getSection(0);
    // Create the component from the recipe values.
    $delta = $value['delta'] ?? 0;
    // Weight is set to 0 because it is irrelevant now. It will be adjusted to
    // its final value in insertComponent() or appendComponent().
    $component = [
      'uuid' => $this->uuidGenerator->generate(),
      'region' => $section->getDefaultRegion(),
      'weight' => 0,
      'configuration' => $value['configuration'] ?? [],
      'additional' => $value['additional'] ?? [],
    ];

    // Insert the new component in Navigation.
    $new_component = SectionComponent::fromArray($component);
    try {
      $section->insertComponent($delta, $new_component);
    }
    catch (\OutOfBoundsException) {
      $section->appendComponent($new_component);
    }
    $navigation_storage->save();
  }

}
+97 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\navigation\Kernel\ConfigAction;

use Drupal\Core\Config\Action\ConfigActionException;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\FunctionalTests\Core\Recipe\RecipeTestTrait;
use Drupal\KernelTests\KernelTestBase;

/**
 * @covers \Drupal\navigation\Plugin\ConfigAction\AddNavigationBlock
 * @group navigation
 * @group Recipe
 */
class AddNavigationBlockConfigActionTest extends KernelTestBase {

  use RecipeTestTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'navigation',
    'layout_builder',
    'layout_discovery',
    'system',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installConfig('navigation');
  }

  /**
   * Tests add item logic.
   *
   * @testWith [0, 0]
   * [1, 1]
   * [3, 3]
   * [7, 3]
   */
  public function testAddBlockToNavigation($delta, $computed_delta): void {
    // Load the navigation section storage.
    $navigation_storage = \Drupal::service('plugin.manager.layout_builder.section_storage')->load('navigation', [
      'navigation' => new Context(new ContextDefinition('string'), 'navigation'),
    ]);
    $section = $navigation_storage->getSection(0);
    $components = $section->getComponentsByRegion('content');
    $this->assertCount(3, $components);
    $data = [
      'delta' => $delta,
      'configuration' => [
        'id' => 'navigation_menu:content',
        'label' => 'Content From Recipe',
        'label_display' => 1,
        'provider' => 'navigation',
        'level' => 1,
        'depth' => 2,
      ],
    ];

    // Use the action to add a new block to Navigation.
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
    $manager = $this->container->get('plugin.manager.config_action');
    $manager->applyAction('addNavigationBlock', 'navigation.block_layout', $data);

    // Load the config after the execution.
    $navigation_storage = \Drupal::service('plugin.manager.layout_builder.section_storage')->load('navigation', [
      'navigation' => new Context(new ContextDefinition('string'), 'navigation'),
    ]);
    $section = $navigation_storage->getSection(0);
    $components = $section->getComponentsByRegion('content');
    $this->assertCount(4, $components);
    $component = array_values($components)[$computed_delta];
    $this->assertSame('content', $component->getRegion());
    $this->assertEquals($data['configuration'], $component->get('configuration'));
  }

  /**
   * Checks invalid config exception.
   */
  public function testActionOnlySupportsNavigationConfig(): void {
    $this->expectException(ConfigActionException::class);
    $this->expectExceptionMessage('addNavigationBlock can only be executed for the navigation.block_layout config.');
    // Try to apply the Config Action against an unexpected config entity.
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
    $manager = $this->container->get('plugin.manager.config_action');
    $manager->applyAction('addNavigationBlock', 'navigation.settings', []);
  }

}