Verified Commit 8339e0eb authored by Alex Pott's avatar Alex Pott
Browse files

Issue #2858897 by ericgsmith, smustgrave, schlaukopf, fabiansierra5191,...

Issue #2858897 by ericgsmith, smustgrave, schlaukopf, fabiansierra5191, tobiasb, ben.kyriakou, RoSk0, rpayanm, _utsavsharma, Dakwamine, xjm, larowlan, Aki Tendo, tim.plunkett, longwave, mxh, alexpott: Block name collision on theme creation
parent 6f97533d
Loading
Loading
Loading
Loading
Loading
+3 −2
Changes for core/modules/block/block.module: 3 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -128,11 +128,12 @@ function block_theme_initialize($theme) {
    $default_theme_blocks = \Drupal::entityTypeManager()->getStorage('block')->loadByProperties(['theme' => $default_theme]);
    foreach ($default_theme_blocks as $default_theme_block_id => $default_theme_block) {
      if (str_starts_with($default_theme_block_id, $default_theme . '_')) {
        $id = str_replace($default_theme, $theme, $default_theme_block_id);
        $id = str_replace($default_theme . '_', '', $default_theme_block_id);
      }
      else {
        $id = $theme . '_' . $default_theme_block_id;
        $id = $default_theme_block_id;
      }
      $id = \Drupal::service('block.repository')->getUniqueMachineName($id, $theme);
      $block = $default_theme_block->createDuplicateBlock($id, $theme);
      // If the region isn't supported by the theme, assign the block to the
      // theme's default region.
+19 −25
Changes for core/modules/block/src/BlockForm.php: 19 added lines, 25 removed lines.
Original line number Diff line number Diff line
@@ -73,6 +73,13 @@ class BlockForm extends EntityForm {
   */
  protected $pluginFormFactory;

  /**
   * The block repository service.
   *
   * @var \Drupal\block\BlockRepositoryInterface
   */
  protected $blockRepository;

  /**
   * Constructs a BlockForm object.
   *
@@ -88,14 +95,21 @@ class BlockForm extends EntityForm {
   *   The theme handler.
   * @param \Drupal\Core\Plugin\PluginFormFactoryInterface $plugin_form_manager
   *   The plugin form manager.
   * @param \Drupal\block\BlockRepositoryInterface|null $block_repository
   *   The block repository service.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, ExecutableManagerInterface $manager, ContextRepositoryInterface $context_repository, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler, PluginFormFactoryInterface $plugin_form_manager) {
  public function __construct(EntityTypeManagerInterface $entity_type_manager, ExecutableManagerInterface $manager, ContextRepositoryInterface $context_repository, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler, PluginFormFactoryInterface $plugin_form_manager, ?BlockRepositoryInterface $block_repository = NULL) {
    if ($block_repository === NULL) {
      @trigger_error('Calling ' . __METHOD__ . ' without the $block_repository argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3333575', E_USER_DEPRECATED);
      $block_repository = \Drupal::service('block.repository');
    }
    $this->storage = $entity_type_manager->getStorage('block');
    $this->manager = $manager;
    $this->contextRepository = $context_repository;
    $this->language = $language;
    $this->themeHandler = $theme_handler;
    $this->pluginFormFactory = $plugin_form_manager;
    $this->blockRepository = $block_repository;
  }

  /**
@@ -108,7 +122,8 @@ public static function create(ContainerInterface $container) {
      $container->get('context.repository'),
      $container->get('language_manager'),
      $container->get('theme_handler'),
      $container->get('plugin_form.factory')
      $container->get('plugin_form.factory'),
      $container->get('block.repository')
    );
  }

@@ -380,7 +395,7 @@ protected function submitVisibility(array $form, FormStateInterface $form_state)
  }

  /**
   * Generates a unique machine name for a block.
   * Generates a unique machine name for a block based on a suggested string.
   *
   * @param \Drupal\block\BlockInterface $block
   *   The block entity.
@@ -390,28 +405,7 @@ protected function submitVisibility(array $form, FormStateInterface $form_state)
   */
  public function getUniqueMachineName(BlockInterface $block) {
    $suggestion = $block->getPlugin()->getMachineNameSuggestion();
    if ($block->getTheme()) {
      $suggestion = $block->getTheme() . '_' . $suggestion;
    }

    // Get all the blocks which starts with the suggested machine name.
    $query = $this->storage->getQuery();
    $query->condition('id', $suggestion, 'CONTAINS');
    $block_ids = $query->execute();

    $block_ids = array_map(function ($block_id) {
      $parts = explode('.', $block_id);
      return end($parts);
    }, $block_ids);

    // Iterate through potential IDs until we get a new one. E.g.
    // 'plugin', 'plugin_2', 'plugin_3', etc.
    $count = 1;
    $machine_default = $suggestion;
    while (in_array($machine_default, $block_ids)) {
      $machine_default = $suggestion . '_' . ++$count;
    }
    return $machine_default;
    return $this->blockRepository->getUniqueMachineName($suggestion, $block->getTheme());
  }

  /**
+28 −0
Changes for core/modules/block/src/BlockRepository.php: 28 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -83,4 +83,32 @@ public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []) {
    return $assignments;
  }

  /**
   * {@inheritdoc}
   */
  public function getUniqueMachineName(string $suggestion, string $theme = NULL): string {
    if ($theme) {
      $suggestion = $theme . '_' . $suggestion;
    }
    // Get all the block machine names that begin with the suggested string.
    $query = $this->blockStorage->getQuery();
    $query->accessCheck(FALSE);
    $query->condition('id', $suggestion, 'CONTAINS');
    $block_ids = $query->execute();

    $block_ids = array_map(function ($block_id) {
      $parts = explode('.', $block_id);
      return end($parts);
    }, $block_ids);

    // Iterate through potential IDs until we get a new one. E.g.
    // For example, 'plugin', 'plugin_2', 'plugin_3', etc.
    $count = 1;
    $machine_default = $suggestion;
    while (in_array($machine_default, $block_ids)) {
      $machine_default = $suggestion . '_' . ++$count;
    }
    return $machine_default;
  }

}
+13 −0
Changes for core/modules/block/src/BlockRepositoryInterface.php: 13 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -31,4 +31,17 @@ interface BlockRepositoryInterface {
   */
  public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []);

  /**
   * Based on a suggested string generates a unique machine name for a block.
   *
   * @param string $suggestion
   *   The suggested block ID.
   * @param string $theme
   *   The machine name of the theme.
   *
   * @return string
   *   Returns the unique name.
   */
  public function getUniqueMachineName(string $suggestion, string $theme): string;

}
+98 −5
Changes for core/modules/block/tests/src/Kernel/NewDefaultThemeBlocksTest.php: 98 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -23,14 +23,36 @@ class NewDefaultThemeBlocksTest extends KernelTestBase {
    'user',
  ];

  /**
   * The theme installer service.
   *
   * @var \Drupal\Core\Extension\ThemeInstallerInterface
   */
  protected $themeInstaller;

  /**
   * The default theme.
   */
  protected $defaultTheme;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $this->installConfig(['system']);
    /** @var \Drupal\Core\Extension\ThemeInstallerInterface $themeInstaller */
    $this->themeInstaller = $this->container->get('theme_installer');
    $this->defaultTheme = $this->config('system.theme')->get('default');
  }

  /**
   * Check the blocks are correctly copied by block_themes_installed().
   */
  public function testNewDefaultThemeBlocks() {
    $this->installConfig(['system']);
    /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */
    $theme_installer = $this->container->get('theme_installer');
    $default_theme = $this->config('system.theme')->get('default');
    $default_theme = $this->defaultTheme;
    $theme_installer = $this->themeInstaller;
    $theme_installer->install([$default_theme]);

    // Add two instances of the user login block.
@@ -61,25 +83,96 @@ public function testNewDefaultThemeBlocks() {
    // Ensure that the new default theme has the same blocks as the previous
    // default theme.
    $default_block_names = $block_storage->getQuery()
      ->accessCheck(FALSE)
      ->condition('theme', $default_theme)
      ->execute();
    $new_blocks = $block_storage->getQuery()
      ->accessCheck(FALSE)
      ->condition('theme', $new_theme)
      ->execute();
    $this->assertSameSize($default_block_names, $new_blocks);

    foreach ($default_block_names as $default_block_name) {
      // Remove the matching block from the list of blocks in the new theme.
      // For example, if the old theme has block.block.stark_admin,
      // unset block.block.olivero_admin.
      unset($new_blocks[str_replace($default_theme . '_', $new_theme . '_', $default_block_name)]);
    }
    $this->assertEmpty($new_blocks);

    // Install a hidden base theme and ensure blocks are not copied.
    $base_theme = 'test_basetheme';
    $theme_installer->install([$base_theme]);
    $new_blocks = $block_storage->getQuery()
      ->accessCheck(FALSE)
      ->condition('theme', $base_theme)
      ->execute();
    $this->assertEmpty($new_blocks);
  }

  /**
   * Checks that a theme block is still created when same ID exists.
   */
  public function testBlockCollision() {
    $default_theme = $this->defaultTheme;
    $theme_installer = $this->themeInstaller;
    $theme_installer->install([$default_theme]);

    // Add two instances of the user login block with machine
    // names that will collide.
    $this->placeBlock('user_login_block', [
      'id' => 'user_login_block',
    ]);
    $this->placeBlock('user_login_block', [
      'id' => $default_theme . '_user_login_block',
    ]);

    // Add an instance of a different block.
    $this->placeBlock('system_powered_by_block', [
      'id' => $default_theme . '_' . strtolower($this->randomMachineName(8)),
    ]);

    // Install a different theme that does not have blocks.
    $new_theme = 'test_theme';
    // The new theme is different from the previous default theme.
    $this->assertNotEquals($new_theme, $default_theme);

    $theme_installer->install([$new_theme]);
    $this->config('system.theme')
      ->set('default', $new_theme)
      ->save();

    $block_storage = $this->container->get('entity_type.manager')->getStorage('block');

    // Ensure that the new default theme has the same blocks as the previous
    // default theme.
    $default_block_names = $block_storage->getQuery()
      ->accessCheck(FALSE)
      ->condition('theme', $default_theme)
      ->execute();
    $new_blocks = $block_storage->getQuery()
      ->accessCheck(FALSE)
      ->condition('theme', $new_theme)
      ->execute();
    $this->assertSameSize($default_block_names, $new_blocks);

    foreach ($default_block_names as $default_block_name) {
      // Remove the matching block from the list of blocks in the new theme.
      // E.g., if the old theme has block.block.stark_admin,
      // For example, if the old theme has block.block.stark_admin,
      // unset block.block.olivero_admin.
      unset($new_blocks[str_replace($default_theme . '_', $new_theme . '_', $default_block_name)]);
    }
    // The test_theme_user_login_block machine name is already in use, so therefore
    // \Drupal\block\BlockRepository::getUniqueMachineName
    // appends a counter.
    unset($new_blocks[$new_theme . '_user_login_block_2']);
    $this->assertEmpty($new_blocks);

    // Install a hidden base theme and ensure blocks are not copied.
    $base_theme = 'test_basetheme';
    $theme_installer->install([$base_theme]);
    $new_blocks = $block_storage->getQuery()
      ->accessCheck(FALSE)
      ->condition('theme', $base_theme)
      ->execute();
    // Installing a hidden base theme does not copy the blocks from the default
Loading