From bdbbb0462ed0d888364d9f4dec4878c399415e67 Mon Sep 17 00:00:00 2001
From: phenaproxima <phenaproxima@205645.no-reply.drupal.org>
Date: Wed, 11 May 2022 20:02:52 +0000
Subject: [PATCH] Issue #3279527 by phenaproxima, tedbow: Ensure plugins are
 reloaded after an update

---
 .../1.0.0/src/Plugin/Block/DeletedBlock.php   | 26 ++++++++++
 .../1.0.0/src/Plugin/Block/IgnoredBlock.php   | 29 +++++++++++
 .../1.0.0/src/Plugin/Block/UpdatedBlock.php   | 29 +++++++++++
 .../1.1.0/src/Plugin/Block/AddedBlock.php     | 26 ++++++++++
 .../1.1.0/src/Plugin/Block/IgnoredBlock.php   | 29 +++++++++++
 .../1.1.0/src/Plugin/Block/UpdatedBlock.php   | 29 +++++++++++
 .../package_manager_test_api.services.yml     |  1 +
 .../src/SystemChangeRecorder.php              | 48 ++++++++++++++++++-
 .../tests/src/Build/PackageUpdateTest.php     | 27 +++++++++++
 9 files changed, 243 insertions(+), 1 deletion(-)
 create mode 100644 package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/DeletedBlock.php
 create mode 100644 package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/IgnoredBlock.php
 create mode 100644 package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/UpdatedBlock.php
 create mode 100644 package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/AddedBlock.php
 create mode 100644 package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/IgnoredBlock.php
 create mode 100644 package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/UpdatedBlock.php

diff --git a/package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/DeletedBlock.php b/package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/DeletedBlock.php
new file mode 100644
index 0000000000..e4c501b199
--- /dev/null
+++ b/package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/DeletedBlock.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\updated_module\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+
+/**
+ * This block is removed from version 1.1.0 of this module.
+ *
+ * @Block(
+ *   id = "updated_module_deleted_block",
+ *   admin_label = @Translation("Deleted block"),
+ * )
+ */
+class DeletedBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return [
+      '#markup' => $this->t('Goodbye!'),
+    ];
+  }
+
+}
diff --git a/package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/IgnoredBlock.php b/package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/IgnoredBlock.php
new file mode 100644
index 0000000000..ce7682274a
--- /dev/null
+++ b/package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/IgnoredBlock.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\updated_module\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+
+/**
+ * Defines a block plugin to test plugin reloading during an update.
+ *
+ * This block should NOT be loaded before updating to version 1.1.0 of this
+ * module.
+ *
+ * @Block(
+ *   id = "updated_module_ignored_block",
+ *   admin_label = @Translation("1.0.0")
+ * )
+ */
+class IgnoredBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return [
+      '#markup' => $this->t("I should not be instantiated before the update."),
+    ];
+  }
+
+}
diff --git a/package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/UpdatedBlock.php b/package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/UpdatedBlock.php
new file mode 100644
index 0000000000..4ac05fbc58
--- /dev/null
+++ b/package_manager/tests/fixtures/updated_module/1.0.0/src/Plugin/Block/UpdatedBlock.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\updated_module\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+
+/**
+ * Defines a block plugin to test plugin reloading during an update.
+ *
+ * In version 1.1.0 of this module, this block exists but its plugin definition
+ * and implementation are different.
+ *
+ * @Block(
+ *   id = "updated_module_updated_block",
+ *   admin_label = @Translation("1.0.0")
+ * )
+ */
+class UpdatedBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return [
+      '#markup' => $this->t('1.0.0'),
+    ];
+  }
+
+}
diff --git a/package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/AddedBlock.php b/package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/AddedBlock.php
new file mode 100644
index 0000000000..fd41ce3468
--- /dev/null
+++ b/package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/AddedBlock.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\updated_module\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+
+/**
+ * This block doesn't exist in version 1.0.0 of this module.
+ *
+ * @Block(
+ *   id = "updated_module_added_block",
+ *   admin_label = @Translation("Added block"),
+ * )
+ */
+class AddedBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return [
+      '#markup' => $this->t('Hello!'),
+    ];
+  }
+
+}
diff --git a/package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/IgnoredBlock.php b/package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/IgnoredBlock.php
new file mode 100644
index 0000000000..db675ccf76
--- /dev/null
+++ b/package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/IgnoredBlock.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\updated_module\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+
+/**
+ * Defines a block plugin to test plugin reloading during an update.
+ *
+ * This block should only be loaded and built after updating to version 1.1.0 of
+ * this module.
+ *
+ * @Block(
+ *   id = "updated_module_ignored_block",
+ *   admin_label = @Translation("1.1.0")
+ * )
+ */
+class IgnoredBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return [
+      '#markup' => $this->t('I was ignored before the update.'),
+    ];
+  }
+
+}
diff --git a/package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/UpdatedBlock.php b/package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/UpdatedBlock.php
new file mode 100644
index 0000000000..fb014cb33b
--- /dev/null
+++ b/package_manager/tests/fixtures/updated_module/1.1.0/src/Plugin/Block/UpdatedBlock.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\updated_module\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+
+/**
+ * Defines a block plugin to test plugin reloading during an update.
+ *
+ * In version 1.0.0 of this module, this block exists but its plugin definition
+ * and implementation are different.
+ *
+ * @Block(
+ *   id = "updated_module_updated_block",
+ *   admin_label = @Translation("1.1.0")
+ * )
+ */
+class UpdatedBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return [
+      '#markup' => $this->t('1.1.0'),
+    ];
+  }
+
+}
diff --git a/package_manager/tests/modules/package_manager_test_api/package_manager_test_api.services.yml b/package_manager/tests/modules/package_manager_test_api/package_manager_test_api.services.yml
index 6bf6af37d1..257393c899 100644
--- a/package_manager/tests/modules/package_manager_test_api/package_manager_test_api.services.yml
+++ b/package_manager/tests/modules/package_manager_test_api/package_manager_test_api.services.yml
@@ -6,5 +6,6 @@ services:
       - '@state'
       - '@router.no_access_checks'
       - '@user.permissions'
+      - '@plugin.manager.block'
     tags:
       - { name: event_subscriber }
diff --git a/package_manager/tests/modules/package_manager_test_api/src/SystemChangeRecorder.php b/package_manager/tests/modules/package_manager_test_api/src/SystemChangeRecorder.php
index a86a6c24f7..de50090860 100644
--- a/package_manager/tests/modules/package_manager_test_api/src/SystemChangeRecorder.php
+++ b/package_manager/tests/modules/package_manager_test_api/src/SystemChangeRecorder.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\package_manager_test_api;
 
+use Drupal\Core\Block\BlockManagerInterface;
 use Drupal\Core\State\StateInterface;
 use Drupal\package_manager\Event\PostApplyEvent;
 use Drupal\package_manager\Event\PostDestroyEvent;
@@ -45,6 +46,13 @@ class SystemChangeRecorder implements EventSubscriberInterface {
    */
   private $permissionHandler;
 
+  /**
+   * The block plugin manager.
+   *
+   * @var \Drupal\Core\Block\BlockManagerInterface
+   */
+  private $blockManager;
+
   /**
    * Constructs a SystemChangeRecorder object.
    *
@@ -56,12 +64,15 @@ class SystemChangeRecorder implements EventSubscriberInterface {
    *   The router service.
    * @param \Drupal\user\PermissionHandlerInterface $permission_handler
    *   The permission handler service.
+   * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
+   *   The block plugin manager.
    */
-  public function __construct(PathLocator $path_locator, StateInterface $state, RouterInterface $router, PermissionHandlerInterface $permission_handler) {
+  public function __construct(PathLocator $path_locator, StateInterface $state, RouterInterface $router, PermissionHandlerInterface $permission_handler, BlockManagerInterface $block_manager) {
     $this->pathLocator = $path_locator;
     $this->state = $state;
     $this->router = $router;
     $this->permissionHandler = $permission_handler;
+    $this->blockManager = $block_manager;
   }
 
   /**
@@ -106,6 +117,14 @@ class SystemChangeRecorder implements EventSubscriberInterface {
 
     $phase = $event instanceof PreApplyEvent ? 'pre' : 'post';
 
+    // Check if changes to a block plugin's definition and implementation are
+    // picked up.
+    $this->recordBlockState('updated_module_deleted_block', $results, $phase === 'pre');
+    $this->recordBlockState('updated_module_updated_block', $results, TRUE);
+    $this->recordBlockState('updated_module_added_block', $results, $phase === 'post');
+    // Record the state of a block that was NOT instantiated before the update.
+    $this->recordBlockState('updated_module_ignored_block', $results, $phase === 'post');
+
     // Check if changes to an existing class are picked up.
     $this->recordClassValue('ChangedClass', $results);
     // Check if changes to a class that has not been loaded before the update is
@@ -125,6 +144,33 @@ class SystemChangeRecorder implements EventSubscriberInterface {
     $this->state->set("system_changes:$phase", $results);
   }
 
+  /**
+   * Records the state of a block plugin.
+   *
+   * @param string $block_id
+   *   Tbe block plugin ID.
+   * @param array $results
+   *   The current set of results, passed by reference.
+   * @param bool $build
+   *   Whether or not to instantiate the block plugin and include its output
+   *   in the results.
+   */
+  private function recordBlockState(string $block_id, array &$results, bool $build): void {
+    if ($this->blockManager->hasDefinition($block_id)) {
+      $results["$block_id block exists"] = 'exists';
+      $plugin_definition = $this->blockManager->getDefinition($block_id);
+      $results["$block_id block label"] = (string) $plugin_definition['admin_label'];
+
+      if ($build) {
+        $build = $this->blockManager->createInstance($block_id)->build();
+        $results["$block_id block output"] = (string) $build['#markup'];
+      }
+    }
+    else {
+      $results["$block_id block exists"] = 'not exists';
+    }
+  }
+
   /**
    * Checks if a given class exists, and records its 'value' property.
    *
diff --git a/package_manager/tests/src/Build/PackageUpdateTest.php b/package_manager/tests/src/Build/PackageUpdateTest.php
index bb4485d099..f0016d4440 100644
--- a/package_manager/tests/src/Build/PackageUpdateTest.php
+++ b/package_manager/tests/src/Build/PackageUpdateTest.php
@@ -82,6 +82,16 @@ class PackageUpdateTest extends TemplateProjectTestBase {
       'updated_module.deleted_service exists' => 'exists',
       'value of updated_module.deleted_service' => 'Deleted service, should not exist after update',
       'updated_module.added_service exists' => 'not exists',
+      'updated_module_deleted_block block exists' => 'exists',
+      'updated_module_deleted_block block label' => 'Deleted block',
+      'updated_module_deleted_block block output' => 'Goodbye!',
+      'updated_module_updated_block block exists' => 'exists',
+      'updated_module_updated_block block label' => '1.0.0',
+      'updated_module_updated_block block output' => '1.0.0',
+      'updated_module_added_block block exists' => 'not exists',
+      // This block is not instantiated until the update is done.
+      'updated_module_ignored_block block exists' => 'exists',
+      'updated_module_ignored_block block label' => '1.0.0',
       'ChangedClass exists' => 'exists',
       'value of ChangedClass' => 'Before Update',
       'LoadedAndDeletedClass exists' => 'exists',
@@ -115,6 +125,23 @@ class PackageUpdateTest extends TemplateProjectTestBase {
       // the expected value.
       'updated_module.added_service exists' => 'exists',
       'value of updated_module.added_service' => 'New service, should not exist before update',
+      // A block removed from the updated module should not be defined anymore.
+      'updated_module_deleted_block block exists' => 'not exists',
+      // A block that was updated should have a changed definition, but an
+      // unchanged implementation.
+      'updated_module_updated_block block exists' => 'exists',
+      'updated_module_updated_block block label' => '1.1.0',
+      'updated_module_updated_block block output' => '1.0.0',
+      // A block added to the module should be defined.
+      'updated_module_added_block block exists' => 'exists',
+      'updated_module_added_block block label' => 'Added block',
+      'updated_module_added_block block output' => 'Hello!',
+      // A block whose definition and implementation were updated, but was NOT
+      // instantiated before the update, should have an updated definition and
+      // implementation.
+      'updated_module_ignored_block block exists' => 'exists',
+      'updated_module_ignored_block block label' => '1.1.0',
+      'updated_module_ignored_block block output' => 'I was ignored before the update.',
       // Existing class should be available.
       'ChangedClass exists' => 'exists',
       // Existing class will still use the pre-update version.
-- 
GitLab