From eb61f11b363485994541485f88f3a4fa64a30b0b Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Fri, 1 Mar 2024 14:41:47 +0000
Subject: [PATCH] Issue #3420986 by kim.pepper, sorlov, larowlan, mstrelan:
 Convert Condition plugin discovery to attributes

---
 .../Core/Condition/Attribute/Condition.php    | 57 +++++++++++++++++++
 .../Core/Condition/ConditionInterface.php     |  1 +
 .../Core/Condition/ConditionManager.php       | 14 ++++-
 .../Core/Condition/ConditionPluginBase.php    |  1 +
 .../Entity/Plugin/Condition/EntityBundle.php  | 11 ++--
 .../src/Plugin/Condition/BaloneySpam.php      | 11 ++--
 .../src/Plugin/Condition/MissingSchema.php    | 11 ++--
 .../src/Plugin/Condition/Language.php         | 21 ++++---
 .../Condition/CurrentThemeCondition.php       | 11 ++--
 .../src/Plugin/Condition/RequestPath.php      | 11 ++--
 .../src/Plugin/Condition/ResponseStatus.php   | 11 ++--
 .../Condition/ConditionTestDualUser.php       | 26 ++++++---
 .../Condition/ConditionTestNoExistingType.php | 21 ++++---
 .../Condition/OptionalContextCondition.php    | 22 ++++---
 .../user/src/Plugin/Condition/UserRole.php    | 21 ++++---
 15 files changed, 176 insertions(+), 74 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Condition/Attribute/Condition.php

diff --git a/core/lib/Drupal/Core/Condition/Attribute/Condition.php b/core/lib/Drupal/Core/Condition/Attribute/Condition.php
new file mode 100644
index 000000000000..da1db7c21202
--- /dev/null
+++ b/core/lib/Drupal/Core/Condition/Attribute/Condition.php
@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Condition\Attribute;
+
+use Drupal\Component\Plugin\Attribute\Plugin;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+
+/**
+ * Defines a condition plugin attribute.
+ *
+ * Condition plugins provide generalized conditions for use in other
+ * operations, such as conditional block placement.
+ *
+ * Plugin Namespace: Plugin\Condition
+ *
+ * For a working example, see \Drupal\user\Plugin\Condition\UserRole.
+ *
+ * @see \Drupal\Core\Condition\ConditionManager
+ * @see \Drupal\Core\Condition\ConditionInterface
+ * @see \Drupal\Core\Condition\ConditionPluginBase
+ * @see block_api
+ *
+ * @ingroup plugin_api
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class Condition extends Plugin {
+
+  /**
+   * Constructs a Condition attribute.
+   *
+   * @param string $id
+   *   The plugin ID.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $label
+   *   (optional) The human-readable name of the condition.
+   * @param string|null $module
+   *   (optional) The name of the module providing the type.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $category
+   *   (optional) The category under which the condition should be listed in the
+   *   UI.
+   * @param array $context_definitions
+   *   (optional) An array of context definitions describing the context used by
+   *   the plugin.
+   * @param class-string|null $deriver
+   *   (optional) The deriver class.
+   */
+  public function __construct(
+    public readonly string $id,
+    public readonly ?TranslatableMarkup $label = NULL,
+    public readonly ?string $module = NULL,
+    public readonly ?TranslatableMarkup $category = NULL,
+    public readonly array $context_definitions = [],
+    public readonly ?string $deriver = NULL,
+  ) {}
+
+}
diff --git a/core/lib/Drupal/Core/Condition/ConditionInterface.php b/core/lib/Drupal/Core/Condition/ConditionInterface.php
index 4ebd1ce2ebf9..9ad23df0fba7 100644
--- a/core/lib/Drupal/Core/Condition/ConditionInterface.php
+++ b/core/lib/Drupal/Core/Condition/ConditionInterface.php
@@ -39,6 +39,7 @@
  * @see \Drupal\Core\Executable\ExecutableInterface
  * @see \Drupal\Core\Condition\ConditionManager
  * @see \Drupal\Core\Condition\Annotation\Condition
+ * @see \Drupal\Core\Condition\Attribute\Condition
  * @see \Drupal\Core\Condition\ConditionPluginBase
  *
  * @ingroup plugin_api
diff --git a/core/lib/Drupal/Core/Condition/ConditionManager.php b/core/lib/Drupal/Core/Condition/ConditionManager.php
index cb8782daa989..5f989fc529aa 100644
--- a/core/lib/Drupal/Core/Condition/ConditionManager.php
+++ b/core/lib/Drupal/Core/Condition/ConditionManager.php
@@ -4,9 +4,10 @@
 
 use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Executable\ExecutableException;
-use Drupal\Core\Executable\ExecutableManagerInterface;
 use Drupal\Core\Executable\ExecutableInterface;
+use Drupal\Core\Executable\ExecutableManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
 use Drupal\Core\Plugin\DefaultPluginManager;
@@ -16,7 +17,7 @@
 /**
  * A plugin manager for condition plugins.
  *
- * @see \Drupal\Core\Condition\Annotation\Condition
+ * @see \Drupal\Core\Condition\Attribute\Condition
  * @see \Drupal\Core\Condition\ConditionInterface
  * @see \Drupal\Core\Condition\ConditionPluginBase
  *
@@ -42,7 +43,14 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
     $this->alterInfo('condition_info');
     $this->setCacheBackend($cache_backend, 'condition_plugins');
 
-    parent::__construct('Plugin/Condition', $namespaces, $module_handler, 'Drupal\Core\Condition\ConditionInterface', 'Drupal\Core\Condition\Annotation\Condition');
+    parent::__construct(
+      'Plugin/Condition',
+      $namespaces,
+      $module_handler,
+      ConditionInterface::class,
+      Condition::class,
+      'Drupal\Core\Condition\Annotation\Condition'
+    );
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Condition/ConditionPluginBase.php b/core/lib/Drupal/Core/Condition/ConditionPluginBase.php
index 48d6cc64faed..bb310787dcfa 100644
--- a/core/lib/Drupal/Core/Condition/ConditionPluginBase.php
+++ b/core/lib/Drupal/Core/Condition/ConditionPluginBase.php
@@ -12,6 +12,7 @@
  * Provides a basis for fulfilling contexts for condition plugins.
  *
  * @see \Drupal\Core\Condition\Annotation\Condition
+ * @see \Drupal\Core\Condition\Attribute\Condition
  * @see \Drupal\Core\Condition\ConditionInterface
  * @see \Drupal\Core\Condition\ConditionManager
  *
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Condition/EntityBundle.php b/core/lib/Drupal/Core/Entity/Plugin/Condition/EntityBundle.php
index f073fdbcc12e..d9f19258361c 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Condition/EntityBundle.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Condition/EntityBundle.php
@@ -2,20 +2,21 @@
 
 namespace Drupal\Core\Entity\Plugin\Condition;
 
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Condition\ConditionPluginBase;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\Plugin\Condition\Deriver\EntityBundle as EntityBundleDeriver;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides the 'Entity Bundle' condition.
- *
- * @Condition(
- *   id = "entity_bundle",
- *   deriver = "\Drupal\Core\Entity\Plugin\Condition\Deriver\EntityBundle",
- * )
  */
+#[Condition(
+  id: "entity_bundle",
+  deriver: EntityBundleDeriver::class,
+)]
 class EntityBundle extends ConditionPluginBase implements ContainerFactoryPluginInterface {
 
   /**
diff --git a/core/modules/block/tests/modules/block_test/src/Plugin/Condition/BaloneySpam.php b/core/modules/block/tests/modules/block_test/src/Plugin/Condition/BaloneySpam.php
index a977c166e214..050e93ca2758 100644
--- a/core/modules/block/tests/modules/block_test/src/Plugin/Condition/BaloneySpam.php
+++ b/core/modules/block/tests/modules/block_test/src/Plugin/Condition/BaloneySpam.php
@@ -2,16 +2,17 @@
 
 namespace Drupal\block_test\Plugin\Condition;
 
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Condition\ConditionPluginBase;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Provides a 'baloney_spam' condition.
- *
- * @Condition(
- *   id = "baloney_spam",
- *   label = @Translation("Baloney spam"),
- * )
  */
+#[Condition(
+  id: "baloney_spam",
+  label: new TranslatableMarkup("Baloney spam"),
+)]
 class BaloneySpam extends ConditionPluginBase {
 
   /**
diff --git a/core/modules/block/tests/modules/block_test/src/Plugin/Condition/MissingSchema.php b/core/modules/block/tests/modules/block_test/src/Plugin/Condition/MissingSchema.php
index 977374c51323..e856ffdaed5d 100644
--- a/core/modules/block/tests/modules/block_test/src/Plugin/Condition/MissingSchema.php
+++ b/core/modules/block/tests/modules/block_test/src/Plugin/Condition/MissingSchema.php
@@ -2,16 +2,17 @@
 
 namespace Drupal\block_test\Plugin\Condition;
 
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Condition\ConditionPluginBase;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Provides a 'missing_schema' condition.
- *
- * @Condition(
- *   id = "missing_schema",
- *   label = @Translation("Missing schema"),
- * )
  */
+#[Condition(
+  id: "missing_schema",
+  label: new TranslatableMarkup("Missing schema"),
+)]
 class MissingSchema extends ConditionPluginBase {
 
   /**
diff --git a/core/modules/language/src/Plugin/Condition/Language.php b/core/modules/language/src/Plugin/Condition/Language.php
index 62008496ecfe..9fea9d010376 100644
--- a/core/modules/language/src/Plugin/Condition/Language.php
+++ b/core/modules/language/src/Plugin/Condition/Language.php
@@ -2,24 +2,29 @@
 
 namespace Drupal\language\Plugin\Condition;
 
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Condition\ConditionPluginBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides a 'Language' condition.
- *
- * @Condition(
- *   id = "language",
- *   label = @Translation("Language"),
- *   context_definitions = {
- *     "language" = @ContextDefinition("language", label = @Translation("Language"))
- *   }
- * )
  */
+#[Condition(
+  id: "language",
+  label: new TranslatableMarkup("Language"),
+  context_definitions: [
+    "language" => new ContextDefinition(
+      data_type: "language",
+      label: new TranslatableMarkup("Language"),
+    ),
+  ]
+)]
 class Language extends ConditionPluginBase implements ContainerFactoryPluginInterface {
 
   /**
diff --git a/core/modules/system/src/Plugin/Condition/CurrentThemeCondition.php b/core/modules/system/src/Plugin/Condition/CurrentThemeCondition.php
index d282963aa6ff..5c0f4ad48f6c 100644
--- a/core/modules/system/src/Plugin/Condition/CurrentThemeCondition.php
+++ b/core/modules/system/src/Plugin/Condition/CurrentThemeCondition.php
@@ -2,21 +2,22 @@
 
 namespace Drupal\system\Plugin\Condition;
 
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Condition\ConditionPluginBase;
 use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Theme\ThemeManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides a 'Current Theme' condition.
- *
- * @Condition(
- *   id = "current_theme",
- *   label = @Translation("Current Theme"),
- * )
  */
+#[Condition(
+  id: "current_theme",
+  label: new TranslatableMarkup("Current Theme"),
+)]
 class CurrentThemeCondition extends ConditionPluginBase implements ContainerFactoryPluginInterface {
 
   /**
diff --git a/core/modules/system/src/Plugin/Condition/RequestPath.php b/core/modules/system/src/Plugin/Condition/RequestPath.php
index 9b4bd474d1a7..7e6aa074d7f8 100644
--- a/core/modules/system/src/Plugin/Condition/RequestPath.php
+++ b/core/modules/system/src/Plugin/Condition/RequestPath.php
@@ -2,23 +2,24 @@
 
 namespace Drupal\system\Plugin\Condition;
 
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Condition\ConditionPluginBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Path\CurrentPathStack;
 use Drupal\Core\Path\PathMatcherInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\path_alias\AliasManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
  * Provides a 'Request Path' condition.
- *
- * @Condition(
- *   id = "request_path",
- *   label = @Translation("Request Path"),
- * )
  */
+#[Condition(
+  id: "request_path",
+  label: new TranslatableMarkup("Request Path"),
+)]
 class RequestPath extends ConditionPluginBase implements ContainerFactoryPluginInterface {
 
   /**
diff --git a/core/modules/system/src/Plugin/Condition/ResponseStatus.php b/core/modules/system/src/Plugin/Condition/ResponseStatus.php
index aa82db66b99e..9dcf47db301f 100644
--- a/core/modules/system/src/Plugin/Condition/ResponseStatus.php
+++ b/core/modules/system/src/Plugin/Condition/ResponseStatus.php
@@ -4,10 +4,12 @@
 
 namespace Drupal\system\Plugin\Condition;
 
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Condition\ConditionPluginBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\HttpFoundation\Response;
@@ -15,12 +17,11 @@
 
 /**
  * Provides a 'Response status' condition.
- *
- * @Condition(
- *   id = "response_status",
- *   label = @Translation("Response status"),
- * )
  */
+#[Condition(
+  id: "response_status",
+  label: new TranslatableMarkup("Response status"),
+)]
 class ResponseStatus extends ConditionPluginBase implements ContainerFactoryPluginInterface {
 
   /**
diff --git a/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestDualUser.php b/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestDualUser.php
index 81d3e7cea27b..7463a5a04e90 100644
--- a/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestDualUser.php
+++ b/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestDualUser.php
@@ -2,20 +2,28 @@
 
 namespace Drupal\condition_test\Plugin\Condition;
 
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Condition\ConditionPluginBase;
+use Drupal\Core\Plugin\Context\EntityContextDefinition;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Provides a condition that requires two users.
- *
- * @Condition(
- *   id = "condition_test_dual_user",
- *   label = @Translation("Dual user"),
- *   context_definitions = {
- *     "user1" = @ContextDefinition("entity:user", label = @Translation("User 1")),
- *     "user2" = @ContextDefinition("entity:user", label = @Translation("User 2"))
- *   }
- * )
  */
+#[Condition(
+  id: "condition_test_dual_user",
+  label: new TranslatableMarkup("Dual user"),
+  context_definitions: [
+    "user1" => new EntityContextDefinition(
+      data_type: "entity:user",
+      label: new TranslatableMarkup("User 1"),
+    ),
+    "user2" => new EntityContextDefinition(
+      data_type: "entity:user",
+      label: new TranslatableMarkup("User 2"),
+    ),
+  ]
+)]
 class ConditionTestDualUser extends ConditionPluginBase {
 
   /**
diff --git a/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestNoExistingType.php b/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestNoExistingType.php
index f465916e84dd..717dc39dfd5d 100644
--- a/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestNoExistingType.php
+++ b/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestNoExistingType.php
@@ -2,19 +2,24 @@
 
 namespace Drupal\condition_test\Plugin\Condition;
 
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Condition\ConditionPluginBase;
+use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Provides a condition that has a no existing context.
- *
- * @Condition(
- *   id = "condition_test_no_existing_type",
- *   label = @Translation("No existing type"),
- *   context_definitions = {
- *     "no_existing_type" = @ContextDefinition("no_existing_type", label = @Translation("No existing type")),
- *   }
- * )
  */
+#[Condition(
+  id: "condition_test_no_existing_type",
+  label: new TranslatableMarkup("No existing type"),
+  context_definitions: [
+    "no_existing_type" => new ContextDefinition(
+      data_type: "no_existing_type",
+      label: new TranslatableMarkup("No existing type"),
+    ),
+  ]
+)]
 class ConditionTestNoExistingType extends ConditionPluginBase {
 
   /**
diff --git a/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/OptionalContextCondition.php b/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/OptionalContextCondition.php
index 46f48d24a123..031c96129249 100644
--- a/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/OptionalContextCondition.php
+++ b/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/OptionalContextCondition.php
@@ -2,22 +2,28 @@
 
 namespace Drupal\condition_test\Plugin\Condition;
 
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Condition\ConditionPluginBase;
+use Drupal\Core\Plugin\Context\EntityContextDefinition;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Provides a condition with an optional node context.
  *
  * The context type entity:node is used since that would allow to also use this
  * for web tests with the node route context.
- *
- * @Condition(
- *   id = "condition_test_optional_context",
- *   label = @Translation("Optional context"),
- *   context_definitions = {
- *     "node" = @ContextDefinition("entity:node", label = @Translation("Node"), required = FALSE),
- *   }
- * )
  */
+#[Condition(
+  id: "condition_test_optional_context",
+  label: new TranslatableMarkup("Optional context"),
+  context_definitions: [
+    "node" => new EntityContextDefinition(
+      data_type: "entity:node",
+      label: new TranslatableMarkup("Node"),
+      required: FALSE,
+    ),
+  ]
+)]
 class OptionalContextCondition extends ConditionPluginBase {
 
   /**
diff --git a/core/modules/user/src/Plugin/Condition/UserRole.php b/core/modules/user/src/Plugin/Condition/UserRole.php
index abd08467e1cb..b046adf86900 100644
--- a/core/modules/user/src/Plugin/Condition/UserRole.php
+++ b/core/modules/user/src/Plugin/Condition/UserRole.php
@@ -3,22 +3,27 @@
 namespace Drupal\user\Plugin\Condition;
 
 use Drupal\Component\Utility\Html;
+use Drupal\Core\Condition\Attribute\Condition;
 use Drupal\Core\Condition\ConditionPluginBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\Context\EntityContextDefinition;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\user\Entity\Role;
 use Drupal\user\RoleInterface;
 
 /**
  * Provides a 'User Role' condition.
- *
- * @Condition(
- *   id = "user_role",
- *   label = @Translation("User Role"),
- *   context_definitions = {
- *     "user" = @ContextDefinition("entity:user", label = @Translation("User"))
- *   }
- * )
  */
+#[Condition(
+  id: "user_role",
+  label: new TranslatableMarkup("User Role"),
+  context_definitions: [
+    "user" => new EntityContextDefinition(
+      data_type: "entity:user",
+      label: new TranslatableMarkup("User"),
+    ),
+  ],
+)]
 class UserRole extends ConditionPluginBase {
 
   /**
-- 
GitLab