From 05f69f95cd9ee9b407e9920ca79e39b64f752568 Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 15 Mar 2025 13:39:59 -0400
Subject: [PATCH 01/22] Add modeler_api as a dependency

---
 eca.info.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/eca.info.yml b/eca.info.yml
index b3a604a25..aa9f4a9b1 100644
--- a/eca.info.yml
+++ b/eca.info.yml
@@ -6,3 +6,4 @@ package: ECA
 dependencies:
   - drupal:system
   - drupal:user
+  - modeler_api:modeler_api
-- 
GitLab


From b4f5c8a348331bcd315b7c871c81d65aaac5e8db Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 15 Mar 2025 13:40:28 -0400
Subject: [PATCH 02/22] Replace ModellerInterface with ModelerInterface

The new version is in the modeler_api module.
---
 .../modeller_bpmn/src/ModellerBpmnBase.php    |  12 +-
 .../src/Plugin/ECA/Modeller/DummyModeller.php |   4 +-
 src/Entity/Eca.php                            |   8 +-
 src/Entity/Model.php                          |   6 +-
 src/Plugin/ECA/Modeller/Fallback.php          |  10 +-
 src/Plugin/ECA/Modeller/ModellerBase.php      |   5 +-
 src/Plugin/ECA/Modeller/ModellerInterface.php | 276 ------------------
 src/PluginManager/Modeller.php                |   4 +-
 src/Service/Modellers.php                     |  14 +-
 9 files changed, 32 insertions(+), 307 deletions(-)
 delete mode 100644 src/Plugin/ECA/Modeller/ModellerInterface.php

diff --git a/modules/modeller_bpmn/src/ModellerBpmnBase.php b/modules/modeller_bpmn/src/ModellerBpmnBase.php
index ad410c429..bafd45379 100644
--- a/modules/modeller_bpmn/src/ModellerBpmnBase.php
+++ b/modules/modeller_bpmn/src/ModellerBpmnBase.php
@@ -13,9 +13,9 @@ use Drupal\eca\Plugin\ECA\Condition\ConditionInterface;
 use Drupal\eca\Plugin\ECA\EcaPluginBase;
 use Drupal\eca\Plugin\ECA\Event\EventInterface;
 use Drupal\eca\Plugin\ECA\Modeller\ModellerBase;
-use Drupal\eca\Plugin\ECA\Modeller\ModellerInterface;
 use Drupal\eca\Service\Modellers;
 use Drupal\eca_ui\Service\TokenBrowserService;
+use Drupal\modeler_api\Plugin\ModelerInterface;
 use Mtownsend\XmlToArray\XmlToArray;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -260,7 +260,7 @@ abstract class ModellerBpmnBase extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function enable(): ModellerInterface {
+  public function enable(): ModelerInterface {
     $this->prepareForUpdate($this->eca->getModel()->getModeldata());
     /** @var \DOMElement|null $element */
     $element = $this->xpath->query("//*[@id='{$this->getId()}']")->item(0);
@@ -279,7 +279,7 @@ abstract class ModellerBpmnBase extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function disable(): ModellerInterface {
+  public function disable(): ModelerInterface {
     $this->prepareForUpdate($this->eca->getModel()->getModeldata());
     /** @var \DOMElement|null $element */
     $element = $this->xpath->query("//*[@id='{$this->getId()}']")->item(0);
@@ -326,7 +326,7 @@ abstract class ModellerBpmnBase extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function setModeldata(string $data): ModellerInterface {
+  public function setModeldata(string $data): ModelerInterface {
     $this->prepareForUpdate($data);
     return $this;
   }
@@ -479,7 +479,7 @@ abstract class ModellerBpmnBase extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function readComponents(Eca $eca): ModellerInterface {
+  public function readComponents(Eca $eca): ModelerInterface {
     $this->eca = $eca;
     $this->eca->resetComponents();
     $idxExtension = $this->xmlNsPrefix() . 'extensionElements';
@@ -615,7 +615,7 @@ abstract class ModellerBpmnBase extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function exportTemplates(): ModellerInterface {
+  public function exportTemplates(): ModelerInterface {
     // Nothing to do by default.
     return $this;
   }
diff --git a/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php b/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php
index b79011359..f8de48c50 100644
--- a/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php
+++ b/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php
@@ -2,8 +2,8 @@
 
 namespace Drupal\eca_test_model_plugin_config_validation\Plugin\ECA\Modeller;
 
-use Drupal\eca\Plugin\ECA\Modeller\ModellerInterface;
 use Drupal\eca_modeller_bpmn\ModellerBpmnBase;
+use Drupal\modeler_api\Plugin\ModelerInterface;
 
 /**
  * Plugin implementation of the ECA Modeller.
@@ -32,7 +32,7 @@ class DummyModeller extends ModellerBpmnBase {
   /**
    * {@inheritdoc}
    */
-  public function exportTemplates(): ModellerInterface {
+  public function exportTemplates(): ModelerInterface {
     $this->templates = $this->getTemplates();
     return parent::exportTemplates();
   }
diff --git a/src/Entity/Eca.php b/src/Entity/Eca.php
index 28547c730..6cfddfdc2 100644
--- a/src/Entity/Eca.php
+++ b/src/Entity/Eca.php
@@ -20,8 +20,8 @@ use Drupal\eca\Entity\Objects\EcaEvent;
 use Drupal\eca\Entity\Objects\EcaGateway;
 use Drupal\eca\Entity\Objects\EcaObject;
 use Drupal\eca\Form\RuntimePluginForm;
-use Drupal\eca\Plugin\ECA\Modeller\ModellerInterface;
 use Drupal\eca\Plugin\PluginUsageInterface;
+use Drupal\modeler_api\Plugin\ModelerInterface;
 use Symfony\Contracts\EventDispatcher\Event;
 
 /**
@@ -252,13 +252,13 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
   /**
    * Provides the modeller plugin associated with this ECA config entity.
    *
-   * @return \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface|null
+   * @return \Drupal\modeler_api\Plugin\ModelerInterface|null
    *   Returns the modeller plugin if possible, NULL otherwise.
    */
-  public function getModeller(): ?ModellerInterface {
+  public function getModeller(): ?ModelerInterface {
     try {
       /**
-       * @var \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface $plugin
+       * @var \Drupal\modeler_api\Plugin\ModelerInterface $plugin
        */
       $plugin = $this->modellerPluginManager()->createInstance($this->get('modeller'));
     }
diff --git a/src/Entity/Model.php b/src/Entity/Model.php
index 8c995bf26..14acab72c 100644
--- a/src/Entity/Model.php
+++ b/src/Entity/Model.php
@@ -3,7 +3,7 @@
 namespace Drupal\eca\Entity;
 
 use Drupal\Core\Config\Entity\ConfigEntityBase;
-use Drupal\eca\Plugin\ECA\Modeller\ModellerInterface;
+use Drupal\modeler_api\Plugin\ModelerInterface;
 
 /**
  * Defines the ECA Model entity type.
@@ -54,13 +54,13 @@ class Model extends ConfigEntityBase {
   /**
    * Set the filename or raw data of the model by the modeller.
    *
-   * @param \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface $modeller
+   * @param \Drupal\modeler_api\Plugin\ModelerInterface $modeller
    *   The modeller instance which handles the model and can provide either the
    *   filename or the raw data to be stored.
    *
    * @return $this
    */
-  public function setData(ModellerInterface $modeller): Model {
+  public function setData(ModelerInterface $modeller): Model {
     $this
       ->setLabel($modeller->getLabel())
       ->setTags($modeller->getTags())
diff --git a/src/Plugin/ECA/Modeller/Fallback.php b/src/Plugin/ECA/Modeller/Fallback.php
index 462955875..2c973f6f2 100644
--- a/src/Plugin/ECA/Modeller/Fallback.php
+++ b/src/Plugin/ECA/Modeller/Fallback.php
@@ -46,7 +46,7 @@ class Fallback extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function enable(): ModellerInterface {
+  public function enable(): ModelerInterface {
     $this->eca
       ->setStatus(TRUE)
       ->save();
@@ -56,7 +56,7 @@ class Fallback extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function disable(): ModellerInterface {
+  public function disable(): ModelerInterface {
     $this->eca
       ->setStatus(FALSE)
       ->save();
@@ -87,7 +87,7 @@ class Fallback extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function setModeldata(string $data): ModellerInterface {
+  public function setModeldata(string $data): ModelerInterface {
     return $this;
   }
 
@@ -143,14 +143,14 @@ class Fallback extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function readComponents(Eca $eca): ModellerInterface {
+  public function readComponents(Eca $eca): ModelerInterface {
     return $this;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function exportTemplates(): ModellerInterface {
+  public function exportTemplates(): ModelerInterface {
     return $this;
   }
 
diff --git a/src/Plugin/ECA/Modeller/ModellerBase.php b/src/Plugin/ECA/Modeller/ModellerBase.php
index 000af6ad0..3cd3054e9 100644
--- a/src/Plugin/ECA/Modeller/ModellerBase.php
+++ b/src/Plugin/ECA/Modeller/ModellerBase.php
@@ -10,6 +10,7 @@ use Drupal\eca\Plugin\ECA\EcaPluginBase;
 use Drupal\eca\Service\Actions;
 use Drupal\eca\Service\Conditions;
 use Drupal\eca\Service\Modellers;
+use Drupal\modeler_api\Plugin\ModelerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\BinaryFileResponse;
 use Symfony\Component\HttpFoundation\Response;
@@ -17,7 +18,7 @@ use Symfony\Component\HttpFoundation\Response;
 /**
  * Base class for ECA modeller plugins.
  */
-abstract class ModellerBase extends EcaPluginBase implements ModellerInterface {
+abstract class ModellerBase extends EcaPluginBase implements ModelerInterface {
 
   /**
    * ECA action service.
@@ -101,7 +102,7 @@ abstract class ModellerBase extends EcaPluginBase implements ModellerInterface {
   /**
    * {@inheritdoc}
    */
-  final public function setConfigEntity(Eca $eca): ModellerInterface {
+  final public function setConfigEntity(Eca $eca): ModelerInterface {
     $this->eca = $eca;
     return $this;
   }
diff --git a/src/Plugin/ECA/Modeller/ModellerInterface.php b/src/Plugin/ECA/Modeller/ModellerInterface.php
deleted file mode 100644
index 64bbf1787..000000000
--- a/src/Plugin/ECA/Modeller/ModellerInterface.php
+++ /dev/null
@@ -1,276 +0,0 @@
-<?php
-
-namespace Drupal\eca\Plugin\ECA\Modeller;
-
-use Drupal\Component\Plugin\PluginInspectionInterface;
-use Drupal\eca\Entity\Eca;
-use Drupal\eca\Entity\Model;
-use Symfony\Component\HttpFoundation\Response;
-
-/**
- * Interface for ECA modeller plugins.
- */
-interface ModellerInterface extends PluginInspectionInterface {
-
-  /**
-   * Add the ECA config entity to the modeller.
-   *
-   * This allows the modeller to call back to the currently operating ECA
-   * config which holds additional information and functionality.
-   *
-   * @param \Drupal\eca\Entity\Eca $eca
-   *   The ECA config entity for the modeller to work on.
-   *
-   * @return \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface
-   *   The modeller instance itself.
-   */
-  public function setConfigEntity(Eca $eca): ModellerInterface;
-
-  /**
-   * Generate an ID for the model.
-   *
-   * @return string
-   *   The ID of the model.
-   */
-  public function generateId(): string;
-
-  /**
-   * Create a new ECA config and model entity.
-   *
-   * @param string $id
-   *   The ID for the new model.
-   * @param string $model_data
-   *   The data for the new model.
-   * @param string|null $filename
-   *   The optional filename, if the modeller requires the model to be stored
-   *   externally as a separate file.
-   * @param bool $save
-   *   TRUE, if the new entity should also be saved, FALSE otherwise (default).
-   *
-   * @return \Drupal\eca\Entity\Eca
-   *   The new ECA config entity.
-   *
-   * @throws \Drupal\Core\Entity\EntityStorageException
-   * @throws \LogicException
-   */
-  public function createNewModel(string $id, string $model_data, ?string $filename = NULL, bool $save = FALSE): Eca;
-
-  /**
-   * Saves an ECA config entity and its associated ECA model entity.
-   *
-   * @param string $data
-   *   The data of the model to be converted to ECA config and stored as the
-   *   modeller's own data.
-   * @param string|null $filename
-   *   The optional filename, if the modeller requires the model to be stored
-   *   externally as a separate file.
-   * @param bool|null $status
-   *   The optional status for the ECA config using TRUE or FALSE to force
-   *   that status regardless of setting in $data. Using NULL (=default) sets
-   *   the status to what is defined in $data.
-   *
-   * @return bool
-   *   Returns TRUE, if a reload of the saved model is required. That's the case
-   *   when this is either a new model or if the label had changed. It returns
-   *   FALSE otherwise, if none of those conditions applies.
-   *
-   * @see ::createNewModel
-   * @see ::disable
-   * @see ::enable
-   * @see \Drupal\eca\Commands\EcaCommands::import
-   * @see \Drupal\eca\Commands\EcaCommands::reimportAll
-   * @see \Drupal\eca\Commands\EcaCommands::updateAllModels
-   * @see \Drupal\eca_ui\Controller\EcaController::save
-   * @see \Drupal\eca_ui\Form\Import::submitForm
-   *
-   * @throws \Drupal\Core\Entity\EntityStorageException
-   * @throws \LogicException
-   */
-  public function save(string $data, ?string $filename = NULL, ?bool $status = NULL): bool;
-
-  /**
-   * Updates and ECA config entity from the given ECA model entity.
-   *
-   * @param \Drupal\eca\Entity\Model $model
-   *   The ECA model entity.
-   *
-   * @return bool
-   *   Returns TRUE, if successful, FALSE otherwise.
-   */
-  public function updateModel(Model $model): bool;
-
-  /**
-   * Enables the current ECA config entity.
-   *
-   * @return \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface
-   *   This.
-   */
-  public function enable(): ModellerInterface;
-
-  /**
-   * Disables the current ECA config entity.
-   *
-   * @return \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface
-   *   This.
-   */
-  public function disable(): ModellerInterface;
-
-  /**
-   * Clones the current ECA config entity.
-   *
-   * @return \Drupal\eca\Entity\Eca|null
-   *   The cloned ECA config entity, if successful, NULL otherwise.
-   */
-  public function clone(): ?Eca;
-
-  /**
-   * Exports the current ECA model.
-   *
-   * @return \Symfony\Component\HttpFoundation\Response|null
-   *   The response with the contained export if possible and successful,
-   *   NULL otherwise.
-   */
-  public function export(): ?Response;
-
-  /**
-   * Gets the external filename of the model, if applicable.
-   *
-   * @return string
-   *   The filename.
-   */
-  public function getFilename(): string;
-
-  /**
-   * Sets the model data.
-   *
-   * @param string $data
-   *   The modeller's data representing the model.
-   *
-   * @return \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface
-   *   This.
-   */
-  public function setModeldata(string $data): ModellerInterface;
-
-  /**
-   * Gets the model data.
-   *
-   * @return string
-   *   The model data.
-   */
-  public function getModeldata(): string;
-
-  /**
-   * Determines if the modeller supports editing in Drupal's admin interface.
-   *
-   * @return bool
-   *   TRUE, if the modellers supports editing inside Drupal's admin interface,
-   *   FALSE otherwise.
-   */
-  public function isEditable(): bool;
-
-  /**
-   * Returns a render array with everything required for model editing.
-   *
-   * @return array
-   *   The render array.
-   */
-  public function edit(): array;
-
-  /**
-   * Get the model ID.
-   *
-   * @return string
-   *   The model ID.
-   */
-  public function getId(): string;
-
-  /**
-   * Get the model's label.
-   *
-   * @return string
-   *   The label.
-   */
-  public function getLabel(): string;
-
-  /**
-   * Get the model's tags.
-   *
-   * @return array
-   *   The list of tags.
-   */
-  public function getTags(): array;
-
-  /**
-   * Get the model's changelog.
-   *
-   * @return array
-   *   The list of changelog items with the key identifying the version and the
-   *   value containing the plain text description.
-   */
-  public function getChangelog(): array;
-
-  /**
-   * Get the model's documentation.
-   *
-   * @return string
-   *   The documentation.
-   */
-  public function getDocumentation(): string;
-
-  /**
-   * Get the model's status.
-   *
-   * @return bool
-   *   TRUE, if the model is enabled, FALSE otherwise.
-   */
-  public function getStatus(): bool;
-
-  /**
-   * Get the model's version.
-   *
-   * @return string
-   *   The version string.
-   */
-  public function getVersion(): string;
-
-  /**
-   * Reads all ECA components and adds them to the ECA config entity.
-   *
-   * The model expects to have been given the model data prior to calling this
-   * method. It will then analyze its own data structure, extract all events,
-   * gateways, conditions and actions and stores them in the given ECA config
-   * entity.
-   *
-   * @param \Drupal\eca\Entity\Eca $eca
-   *   The ECA config entity.
-   *
-   * @return \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface
-   *   This.
-   */
-  public function readComponents(Eca $eca): ModellerInterface;
-
-  /**
-   * Returns the associated ECA config entity.
-   *
-   * @return \Drupal\eca\Entity\Eca
-   *   The associated ECA config entity.
-   */
-  public function getEca(): Eca;
-
-  /**
-   * Determines, if during ::readComponents at least one error occurred.
-   *
-   * @return bool
-   *   TRUE, if at least one error occurred, FALSE otherwise.
-   */
-  public function hasError(): bool;
-
-  /**
-   * Exports all templates in modeller specific format for external use.
-   *
-   * @return \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface
-   *   This.
-   */
-  public function exportTemplates(): ModellerInterface;
-
-}
diff --git a/src/PluginManager/Modeller.php b/src/PluginManager/Modeller.php
index 546020ceb..d01290b7b 100644
--- a/src/PluginManager/Modeller.php
+++ b/src/PluginManager/Modeller.php
@@ -7,7 +7,7 @@ use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
 use Drupal\eca\Annotation\EcaModeller;
-use Drupal\eca\Plugin\ECA\Modeller\ModellerInterface;
+use Drupal\modeler_api\Plugin\ModelerInterface;
 
 /**
  * ECA modeller plugin manager.
@@ -30,7 +30,7 @@ class Modeller extends DefaultPluginManager implements FallbackPluginManagerInte
       'Plugin/ECA/Modeller',
       $namespaces,
       $module_handler,
-      ModellerInterface::class,
+      ModelerInterface::class,
       EcaModeller::class
     );
     $this->alterInfo('eca_modeller_info');
diff --git a/src/Service/Modellers.php b/src/Service/Modellers.php
index 89e50ed8a..4ce17304a 100644
--- a/src/Service/Modellers.php
+++ b/src/Service/Modellers.php
@@ -14,9 +14,9 @@ use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Logger\LoggerChannelInterface;
 use Drupal\eca\Entity\Eca;
 use Drupal\eca\ErrorHandlerTrait;
-use Drupal\eca\Plugin\ECA\Modeller\ModellerInterface;
 use Drupal\eca\PluginManager\Event;
 use Drupal\eca\PluginManager\Modeller;
+use Drupal\modeler_api\Plugin\ModelerInterface;
 
 /**
  * Service class for ECA modellers.
@@ -152,7 +152,7 @@ class Modellers {
   /**
    * Save a model as config.
    *
-   * @param \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface $modeller
+   * @param \Drupal\modeler_api\Plugin\ModelerInterface $modeller
    *   The modeller controlling the ECA config entity.
    *
    * @return bool
@@ -163,7 +163,7 @@ class Modellers {
    * @throws \Drupal\Core\Entity\EntityStorageException
    * @throws \LogicException
    */
-  public function saveModel(ModellerInterface $modeller): bool {
+  public function saveModel(ModelerInterface $modeller): bool {
     $id = mb_strtolower($modeller->getId());
     /** @var \Drupal\eca\Entity\Eca|null $config */
     $config = $this->configStorage->load($id);
@@ -217,13 +217,13 @@ class Modellers {
    * @param string $plugin_id
    *   The id of the modeller plugin.
    *
-   * @return \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface|null
+   * @return \Drupal\modeler_api\Plugin\ModelerInterface|null
    *   The modeller instance, or NULL if the plugin doesn't exist.
    */
-  public function getModeller(string $plugin_id): ?ModellerInterface {
+  public function getModeller(string $plugin_id): ?ModelerInterface {
     try {
       /**
-       * @var \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface $modeller
+       * @var \Drupal\modeler_api\Plugin\ModelerInterface $modeller
        */
       $modeller = $this->pluginManagerModeller->createInstance($plugin_id);
     }
@@ -267,7 +267,7 @@ class Modellers {
     foreach ($this->pluginManagerModeller->getDefinitions() as $plugin_id => $definition) {
       try {
         /**
-         * @var \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface $modeller
+         * @var \Drupal\modeler_api\Plugin\ModelerInterface $modeller
          */
         $modeller = $this->pluginManagerModeller->createInstance($plugin_id);
         $modeller->exportTemplates();
-- 
GitLab


From c0b916f925e8c71ee2f900f0e3b36f99b87de57b Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 15 Mar 2025 15:01:22 -0400
Subject: [PATCH 03/22] Use the new Drupal\modeler_api\TargetInterface

---
 src/Entity/Eca.php | 83 +++++-----------------------------------------
 1 file changed, 8 insertions(+), 75 deletions(-)

diff --git a/src/Entity/Eca.php b/src/Entity/Eca.php
index 6cfddfdc2..103cd500a 100644
--- a/src/Entity/Eca.php
+++ b/src/Entity/Eca.php
@@ -21,6 +21,7 @@ use Drupal\eca\Entity\Objects\EcaGateway;
 use Drupal\eca\Entity\Objects\EcaObject;
 use Drupal\eca\Form\RuntimePluginForm;
 use Drupal\eca\Plugin\PluginUsageInterface;
+use Drupal\modeler_api\TargetInterface;
 use Drupal\modeler_api\Plugin\ModelerInterface;
 use Symfony\Contracts\EventDispatcher\Event;
 
@@ -64,7 +65,7 @@ use Symfony\Contracts\EventDispatcher\Event;
  *   }
  * )
  */
-class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterface {
+class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterface, TargetInterface {
 
   use EcaTrait;
 
@@ -281,10 +282,7 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
   }
 
   /**
-   * Provides the ECA model entity storing the data for this ECA config entity.
-   *
-   * @return \Drupal\eca\Entity\Model
-   *   The ECA model entity.
+   * {@inheritdoc}
    */
   public function getModel(): Model {
     if (!isset($this->model)) {
@@ -315,10 +313,7 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
   }
 
   /**
-   * Reset all component (events, conditions, actions, gateways) arrays.
-   *
-   * This should be called by the modeller once before the methods
-   * ::addEvent, ::addCondition, ::addAction or ::addGateway will be used.
+   * {@inheritdoc}
    */
   public function resetComponents(): void {
     $this->events = [];
@@ -328,20 +323,7 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
   }
 
   /**
-   * Add a condition item to this ECA config entity.
-   *
-   * @param string $id
-   *   The condition ID.
-   * @param string $plugin_id
-   *   The condition's plugin ID.
-   * @param array $fields
-   *   The configuration for this condition.
-   *
-   * @return bool
-   *   Returns TRUE if the condition's configuration is valid, FALSE otherwise.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   *   When the given condition plugin ID does not exist.
+   * {@inheritdoc}
    */
   public function addCondition(string $id, string $plugin_id, array $fields): bool {
     $plugin = $this->conditionPluginManager()->createInstance($plugin_id, []);
@@ -357,17 +339,7 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
   }
 
   /**
-   * Add a gateway item to this ECA config entity.
-   *
-   * @param string $id
-   *   The gateway ID.
-   * @param int $type
-   *   The gateway type.
-   * @param array $successors
-   *   A list of successor items linked to this gateway.
-   *
-   * @return bool
-   *   Returns TRUE if the gateway was successfully added, FALSE otherwise.
+   * {@inheritdoc}
    */
   public function addGateway(string $id, int $type, array $successors): bool {
     $this->gateways[$id] = [
@@ -378,24 +350,7 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
   }
 
   /**
-   * Add an event item to this ECA config entity.
-   *
-   * @param string $id
-   *   The event ID.
-   * @param string $plugin_id
-   *   The event's plugin ID.
-   * @param string $label
-   *   The event label.
-   * @param array $fields
-   *   The configuration for this event.
-   * @param array $successors
-   *   A list of successor items linked to this event.
-   *
-   * @return bool
-   *   Returns TRUE if the event's configuration is valid, FALSE otherwise.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   *   When the given event plugin ID does not exist.
+   * {@inheritdoc}
    */
   public function addEvent(string $id, string $plugin_id, string $label, array $fields, array $successors): bool {
     $plugin = $this->eventPluginManager()->createInstance($plugin_id, []);
@@ -416,29 +371,7 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
   }
 
   /**
-   * Add an action item to this ECA config entity.
-   *
-   * As action plugins are controlled by Drupal core's action plugin manager
-   * and not by ECA, this method will run new actions through the configuration
-   * form validation and submission and validates, if the given configuration
-   * is valid.
-   *
-   * @param string $id
-   *   The action ID.
-   * @param string $plugin_id
-   *   The action's plugin ID.
-   * @param string $label
-   *   The action label.
-   * @param array $fields
-   *   The configuration for this action.
-   * @param array $successors
-   *   A list of successor items linked to this action.
-   *
-   * @return bool
-   *   Returns TRUE if the action's configuration is valid, FALSE otherwise.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   *   When the given action plugin ID does not exist.
+   * {@inheritdoc}
    */
   public function addAction(string $id, string $plugin_id, string $label, array $fields, array $successors): bool {
     $plugin = $this->actionPluginManager()->createInstance($plugin_id, []);
-- 
GitLab


From 0b6779534377426da8cfd68300e03300e96149cc Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 15 Mar 2025 15:51:07 -0400
Subject: [PATCH 04/22] Remove the Model config entity

---
 config/schema/eca.schema.yml                  |  25 ---
 .../modeller_bpmn/src/ModellerBpmnBase.php    |   2 +-
 .../src/Kernel/PluginConfigValidationTest.php |   6 +-
 src/Entity/Eca.php                            |   9 +-
 src/Entity/Model.php                          | 163 ------------------
 src/Plugin/ECA/Modeller/Fallback.php          |   2 +-
 src/Service/Modellers.php                     |   2 +-
 7 files changed, 11 insertions(+), 198 deletions(-)
 delete mode 100644 src/Entity/Model.php

diff --git a/config/schema/eca.schema.yml b/config/schema/eca.schema.yml
index f65475a27..1c54349fa 100644
--- a/config/schema/eca.schema.yml
+++ b/config/schema/eca.schema.yml
@@ -111,31 +111,6 @@ eca.eca.*:
             sequence:
               type: eca.wrapper.successor
 
-eca.model.*:
-  type: config_entity
-  label: ECA
-  mapping:
-    id:
-      type: string
-      label: 'ID'
-    label:
-      type: label
-      label: 'Label'
-    tags:
-      type: sequence
-      label: 'tags'
-      sequence:
-        type: string
-    documentation:
-      type: string
-      label: 'Documentation'
-    filename:
-      type: string
-      label: 'Filename'
-    modeldata:
-      type: string
-      label: 'Raw model data'
-
 eca.wrapper.successor:
   type: mapping
   label: 'Successor'
diff --git a/modules/modeller_bpmn/src/ModellerBpmnBase.php b/modules/modeller_bpmn/src/ModellerBpmnBase.php
index bafd45379..7d0c26327 100644
--- a/modules/modeller_bpmn/src/ModellerBpmnBase.php
+++ b/modules/modeller_bpmn/src/ModellerBpmnBase.php
@@ -8,13 +8,13 @@ use Drupal\Core\Action\ActionInterface;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Form\FormState;
 use Drupal\eca\Entity\Eca;
-use Drupal\eca\Entity\Model;
 use Drupal\eca\Plugin\ECA\Condition\ConditionInterface;
 use Drupal\eca\Plugin\ECA\EcaPluginBase;
 use Drupal\eca\Plugin\ECA\Event\EventInterface;
 use Drupal\eca\Plugin\ECA\Modeller\ModellerBase;
 use Drupal\eca\Service\Modellers;
 use Drupal\eca_ui\Service\TokenBrowserService;
+use Drupal\modeler_api\Entity\Model;
 use Drupal\modeler_api\Plugin\ModelerInterface;
 use Mtownsend\XmlToArray\XmlToArray;
 use Symfony\Component\DependencyInjection\ContainerInterface;
diff --git a/modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php b/modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php
index 78b1e30a1..368e9cb4e 100644
--- a/modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php
+++ b/modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php
@@ -31,8 +31,8 @@ class PluginConfigValidationTest extends Base {
     $modelManager = \Drupal::service('plugin.manager.eca.modeller');
     /** @var \Drupal\eca_test_model_plugin_config_validation\Plugin\ECA\Modeller\DummyModeller $modeller */
     $modeller = $modelManager->createInstance('dummy');
-    /** @var \Drupal\eca\Entity\Model $model */
-    $model = \Drupal::entityTypeManager()->getStorage('eca_model')->load('eca_test_0011');
+    /** @var \Drupal\modeler_api\Entity\Model $model */
+    $model = \Drupal::entityTypeManager()->getStorage('modeler_api_model')->load('eca_test_0011');
     $data = $model->getModeldata();
 
     $fieldOrigin = '<camunda:string>correct</camunda:string>';
@@ -46,7 +46,7 @@ class PluginConfigValidationTest extends Base {
     $this->assertErrorMessages([
       'action "Test: Dummy action to validate configuration" (Dummy): This value is not allowed.',
     ]);
-    /** @var \Drupal\eca\Entity\Model $model */
+    /** @var \Drupal\modeler_api\Entity\Model $model */
     $eca = \Drupal::entityTypeManager()->getStorage('eca')->load('eca_test_0011');
     $actions = $eca->get('actions');
     $this->assertSame('correct', $actions['Dummy']['configuration']['dummy'], 'The config value "correct" should not have changed.');
diff --git a/src/Entity/Eca.php b/src/Entity/Eca.php
index 103cd500a..b3c4a935d 100644
--- a/src/Entity/Eca.php
+++ b/src/Entity/Eca.php
@@ -21,6 +21,7 @@ use Drupal\eca\Entity\Objects\EcaGateway;
 use Drupal\eca\Entity\Objects\EcaObject;
 use Drupal\eca\Form\RuntimePluginForm;
 use Drupal\eca\Plugin\PluginUsageInterface;
+use Drupal\modeler_api\Entity\Model;
 use Drupal\modeler_api\TargetInterface;
 use Drupal\modeler_api\Plugin\ModelerInterface;
 use Symfony\Contracts\EventDispatcher\Event;
@@ -126,7 +127,7 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
   /**
    * Model config entity for the ECA config entity.
    *
-   * @var \Drupal\eca\Entity\Model
+   * @var \Drupal\modeler_api\Entity\Model
    */
   protected Model $model;
 
@@ -287,7 +288,7 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
   public function getModel(): Model {
     if (!isset($this->model)) {
       try {
-        $storage = $this->entityTypeManager()->getStorage('eca_model');
+        $storage = $this->entityTypeManager()->getStorage('modeler_api_model');
       }
       catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
         // @todo Log this exception.
@@ -296,12 +297,12 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
         return $this->model;
       }
       /**
-       * @var \Drupal\eca\Entity\Model|null $model
+       * @var \Drupal\modeler_api\Entity\Model|null $model
        */
       $model = $storage->load($this->id());
       if ($model === NULL) {
         /**
-         * @var \Drupal\eca\Entity\Model $model
+         * @var \Drupal\modeler_api\Entity\Model $model
          */
         $model = $storage->create([
           'id' => $this->id(),
diff --git a/src/Entity/Model.php b/src/Entity/Model.php
deleted file mode 100644
index 14acab72c..000000000
--- a/src/Entity/Model.php
+++ /dev/null
@@ -1,163 +0,0 @@
-<?php
-
-namespace Drupal\eca\Entity;
-
-use Drupal\Core\Config\Entity\ConfigEntityBase;
-use Drupal\modeler_api\Plugin\ModelerInterface;
-
-/**
- * Defines the ECA Model entity type.
- *
- * @ConfigEntityType(
- *   id = "eca_model",
- *   label = @Translation("ECA Model"),
- *   label_collection = @Translation("ECA Models"),
- *   label_singular = @Translation("ECA Model"),
- *   label_plural = @Translation("ECA Models"),
- *   label_count = @PluralTranslation(
- *     singular = "@count ECA Model",
- *     plural = "@count ECA Models",
- *   ),
- *   config_prefix = "model",
- *   entity_keys = {
- *     "id" = "id",
- *     "uuid" = "uuid",
- *     "label" = "label"
- *   },
- *   config_export = {
- *     "id",
- *     "label",
- *     "tags",
- *     "documentation",
- *     "filename",
- *     "modeldata"
- *   }
- * )
- */
-class Model extends ConfigEntityBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function calculateDependencies() {
-    parent::calculateDependencies();
-
-    /** @var \Drupal\eca\Entity\Eca|null $eca */
-    $eca = $this->entityTypeManager()->getStorage('eca')->load($this->id());
-    if ($eca) {
-      $this->addDependency('config', $eca->getConfigDependencyName());
-    }
-
-    return $this;
-  }
-
-  /**
-   * Set the filename or raw data of the model by the modeller.
-   *
-   * @param \Drupal\modeler_api\Plugin\ModelerInterface $modeller
-   *   The modeller instance which handles the model and can provide either the
-   *   filename or the raw data to be stored.
-   *
-   * @return $this
-   */
-  public function setData(ModelerInterface $modeller): Model {
-    $this
-      ->setLabel($modeller->getLabel())
-      ->setTags($modeller->getTags())
-      ->setDocumentation($modeller->getDocumentation())
-      ->setFilename($modeller->getFilename())
-      ->setModeldata($modeller->getModeldata());
-    return $this;
-  }
-
-  /**
-   * Set the label of this model.
-   *
-   * @return $this
-   */
-  public function setLabel(string $label): Model {
-    $this->set('label', $label);
-    return $this;
-  }
-
-  /**
-   * Set the tags of this model.
-   *
-   * @return $this
-   */
-  public function setTags(array $tags): Model {
-    $this->set('tags', empty($tags) ? ['untagged'] : $tags);
-    return $this;
-  }
-
-  /**
-   * Get the tags of this model.
-   *
-   * @return array
-   *   The tags of this model.
-   */
-  public function getTags(): array {
-    return $this->get('tags') ?? [];
-  }
-
-  /**
-   * Set the documentation of this model.
-   *
-   * @return $this
-   */
-  public function setDocumentation(string $documentation): Model {
-    $this->set('documentation', $documentation);
-    return $this;
-  }
-
-  /**
-   * Get the documentation of this model.
-   *
-   * @return string
-   *   The documentation.
-   */
-  public function getDocumentation(): string {
-    return $this->get('documentation') ?? '';
-  }
-
-  /**
-   * Set the external filename of this model.
-   *
-   * @return $this
-   */
-  public function setFilename(string $filename): Model {
-    $this->set('filename', $filename);
-    return $this;
-  }
-
-  /**
-   * Get the external filename of this model.
-   *
-   * @return string
-   *   The external filename.
-   */
-  public function getFilename(): string {
-    return $this->get('filename') ?? '';
-  }
-
-  /**
-   * Set the external filename of this model.
-   *
-   * @return $this
-   */
-  public function setModeldata(string $modeldata): Model {
-    $this->set('modeldata', $modeldata);
-    return $this;
-  }
-
-  /**
-   * Get the raw model data of this model.
-   *
-   * @return string
-   *   The raw model data.
-   */
-  public function getModeldata(): string {
-    return $this->get('modeldata') ?? '';
-  }
-
-}
diff --git a/src/Plugin/ECA/Modeller/Fallback.php b/src/Plugin/ECA/Modeller/Fallback.php
index 2c973f6f2..b03ed419c 100644
--- a/src/Plugin/ECA/Modeller/Fallback.php
+++ b/src/Plugin/ECA/Modeller/Fallback.php
@@ -3,7 +3,7 @@
 namespace Drupal\eca\Plugin\ECA\Modeller;
 
 use Drupal\eca\Entity\Eca;
-use Drupal\eca\Entity\Model;
+use Drupal\modeler_api\Entity\Model;
 use Symfony\Component\HttpFoundation\Response;
 
 /**
diff --git a/src/Service/Modellers.php b/src/Service/Modellers.php
index 4ce17304a..7f55af75d 100644
--- a/src/Service/Modellers.php
+++ b/src/Service/Modellers.php
@@ -123,7 +123,7 @@ class Modellers {
    */
   public function __construct(EntityTypeManagerInterface $entity_type_manager, Modeller $plugin_manager_modeller, Event $plugin_manager_event, Actions $action_services, Conditions $condition_services, LoggerChannelInterface $logger, FileSystemInterface $file_system, StorageInterface $export_storage, ConfigFactoryInterface $config_factory) {
     $this->configStorage = $entity_type_manager->getStorage('eca');
-    $this->modelStorage = $entity_type_manager->getStorage('eca_model');
+    $this->modelStorage = $entity_type_manager->getStorage('modeler_api_model');
     $this->pluginManagerModeller = $plugin_manager_modeller;
     $this->pluginManagerEvent = $plugin_manager_event;
     $this->actionServices = $action_services;
-- 
GitLab


From 9dc6e36903141844531e2798d75e105784e0719c Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 15 Mar 2025 16:47:05 -0400
Subject: [PATCH 05/22] Restore the Model config entity so it can be deprecated

We also need to use it in the update function.
---
 config/schema/eca.schema.yml |  25 ++++++
 src/Entity/Model.php         | 163 +++++++++++++++++++++++++++++++++++
 2 files changed, 188 insertions(+)
 create mode 100644 src/Entity/Model.php

diff --git a/config/schema/eca.schema.yml b/config/schema/eca.schema.yml
index 1c54349fa..f65475a27 100644
--- a/config/schema/eca.schema.yml
+++ b/config/schema/eca.schema.yml
@@ -111,6 +111,31 @@ eca.eca.*:
             sequence:
               type: eca.wrapper.successor
 
+eca.model.*:
+  type: config_entity
+  label: ECA
+  mapping:
+    id:
+      type: string
+      label: 'ID'
+    label:
+      type: label
+      label: 'Label'
+    tags:
+      type: sequence
+      label: 'tags'
+      sequence:
+        type: string
+    documentation:
+      type: string
+      label: 'Documentation'
+    filename:
+      type: string
+      label: 'Filename'
+    modeldata:
+      type: string
+      label: 'Raw model data'
+
 eca.wrapper.successor:
   type: mapping
   label: 'Successor'
diff --git a/src/Entity/Model.php b/src/Entity/Model.php
new file mode 100644
index 000000000..14acab72c
--- /dev/null
+++ b/src/Entity/Model.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Drupal\eca\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\modeler_api\Plugin\ModelerInterface;
+
+/**
+ * Defines the ECA Model entity type.
+ *
+ * @ConfigEntityType(
+ *   id = "eca_model",
+ *   label = @Translation("ECA Model"),
+ *   label_collection = @Translation("ECA Models"),
+ *   label_singular = @Translation("ECA Model"),
+ *   label_plural = @Translation("ECA Models"),
+ *   label_count = @PluralTranslation(
+ *     singular = "@count ECA Model",
+ *     plural = "@count ECA Models",
+ *   ),
+ *   config_prefix = "model",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid",
+ *     "label" = "label"
+ *   },
+ *   config_export = {
+ *     "id",
+ *     "label",
+ *     "tags",
+ *     "documentation",
+ *     "filename",
+ *     "modeldata"
+ *   }
+ * )
+ */
+class Model extends ConfigEntityBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    parent::calculateDependencies();
+
+    /** @var \Drupal\eca\Entity\Eca|null $eca */
+    $eca = $this->entityTypeManager()->getStorage('eca')->load($this->id());
+    if ($eca) {
+      $this->addDependency('config', $eca->getConfigDependencyName());
+    }
+
+    return $this;
+  }
+
+  /**
+   * Set the filename or raw data of the model by the modeller.
+   *
+   * @param \Drupal\modeler_api\Plugin\ModelerInterface $modeller
+   *   The modeller instance which handles the model and can provide either the
+   *   filename or the raw data to be stored.
+   *
+   * @return $this
+   */
+  public function setData(ModelerInterface $modeller): Model {
+    $this
+      ->setLabel($modeller->getLabel())
+      ->setTags($modeller->getTags())
+      ->setDocumentation($modeller->getDocumentation())
+      ->setFilename($modeller->getFilename())
+      ->setModeldata($modeller->getModeldata());
+    return $this;
+  }
+
+  /**
+   * Set the label of this model.
+   *
+   * @return $this
+   */
+  public function setLabel(string $label): Model {
+    $this->set('label', $label);
+    return $this;
+  }
+
+  /**
+   * Set the tags of this model.
+   *
+   * @return $this
+   */
+  public function setTags(array $tags): Model {
+    $this->set('tags', empty($tags) ? ['untagged'] : $tags);
+    return $this;
+  }
+
+  /**
+   * Get the tags of this model.
+   *
+   * @return array
+   *   The tags of this model.
+   */
+  public function getTags(): array {
+    return $this->get('tags') ?? [];
+  }
+
+  /**
+   * Set the documentation of this model.
+   *
+   * @return $this
+   */
+  public function setDocumentation(string $documentation): Model {
+    $this->set('documentation', $documentation);
+    return $this;
+  }
+
+  /**
+   * Get the documentation of this model.
+   *
+   * @return string
+   *   The documentation.
+   */
+  public function getDocumentation(): string {
+    return $this->get('documentation') ?? '';
+  }
+
+  /**
+   * Set the external filename of this model.
+   *
+   * @return $this
+   */
+  public function setFilename(string $filename): Model {
+    $this->set('filename', $filename);
+    return $this;
+  }
+
+  /**
+   * Get the external filename of this model.
+   *
+   * @return string
+   *   The external filename.
+   */
+  public function getFilename(): string {
+    return $this->get('filename') ?? '';
+  }
+
+  /**
+   * Set the external filename of this model.
+   *
+   * @return $this
+   */
+  public function setModeldata(string $modeldata): Model {
+    $this->set('modeldata', $modeldata);
+    return $this;
+  }
+
+  /**
+   * Get the raw model data of this model.
+   *
+   * @return string
+   *   The raw model data.
+   */
+  public function getModeldata(): string {
+    return $this->get('modeldata') ?? '';
+  }
+
+}
-- 
GitLab


From 0d8f609346d9fc73a64dd2f63f82fb4661e6c1b0 Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 15 Mar 2025 17:43:10 -0400
Subject: [PATCH 06/22] Deprecate the ECA Model config entity

TODO: Create a draft CR and fill in the links.
---
 src/Entity/Model.php | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/src/Entity/Model.php b/src/Entity/Model.php
index 14acab72c..9da49a913 100644
--- a/src/Entity/Model.php
+++ b/src/Entity/Model.php
@@ -8,6 +8,11 @@ use Drupal\modeler_api\Plugin\ModelerInterface;
 /**
  * Defines the ECA Model entity type.
  *
+ * @deprecated in eca:3.0.0 and is removed from eca:4.0.0. Use
+ * \Drupal\modeler_api\Entity\Model instead.
+ *
+ * @see https://www.drupal.org/node/TODO
+ *
  * @ConfigEntityType(
  *   id = "eca_model",
  *   label = @Translation("ECA Model"),
@@ -36,6 +41,14 @@ use Drupal\modeler_api\Plugin\ModelerInterface;
  */
 class Model extends ConfigEntityBase {
 
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $values, $entity_type) {
+    parent::__construct($values, $entity_type);
+    @trigger_error(__CLASS__ . ' is deprecated in eca:3.0.0 and is removed from eca:4.0.0. Use Drupal\modeler_api\Entity\Model instead. See https://www.drupal.org/node/TODO', E_USER_DEPRECATED);
+  }
+
   /**
    * {@inheritdoc}
    */
-- 
GitLab


From b4fd757e2bfd479a84a6025bf5fdfc1457dbdd26 Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 15 Mar 2025 17:53:01 -0400
Subject: [PATCH 07/22] Update function: move all Model config entities

---
 eca.install | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/eca.install b/eca.install
index 39da662e0..f9f0b94ec 100644
--- a/eca.install
+++ b/eca.install
@@ -218,3 +218,18 @@ function eca_update_8010(): void {
 function eca_update_8011(): void {
   \Drupal::service('eca.update')->updateAllModels();
 }
+
+/**
+ * Replace eca_model config entities with modeler_api_model entities.
+ */
+function eca_update_8012(): void {
+  /** @var \Drupal\eca\Entity\Model $eca_model */
+  $model_storage = \Drupal::entityTypeManager()->getStorage('modeler_api_model');
+  foreach (\Drupal::entityTypeManager()->getStorage('eca_model')->loadMultiple() as $eca_model) {
+    $data = $eca_model->toArray();
+    unset($data['uuid']);
+    if ($model_storage->create($data)->save()) {
+      $eca_model->delete();
+    }
+  }
+}
-- 
GitLab


From a1d2ec450f52739fbb0d7ff6ff975d1ba8fdaf72 Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 15 Mar 2025 18:49:38 -0400
Subject: [PATCH 08/22] Update remaining references to Model config entities

---
 src/Service/ExportRecipe.php | 4 ++--
 src/Service/Modellers.php    | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/Service/ExportRecipe.php b/src/Service/ExportRecipe.php
index c879c4ba1..6e9fd9f6b 100644
--- a/src/Service/ExportRecipe.php
+++ b/src/Service/ExportRecipe.php
@@ -99,9 +99,9 @@ class ExportRecipe {
       ],
       'module' => [],
     ];
-    $comesWithEcaModel = $this->configStorage->read('eca.model.' . $eca->id());
+    $comesWithEcaModel = $this->configStorage->read('modeler_api.model.' . $eca->id());
     if ($comesWithEcaModel) {
-      $dependencies['config'][] = 'eca.model.' . $eca->id();
+      $dependencies['config'][] = 'model  er_api.model.' . $eca->id();
     }
     $this->modellerService->getNestedDependencies($dependencies, $eca->getDependencies());
 
diff --git a/src/Service/Modellers.php b/src/Service/Modellers.php
index 7f55af75d..c1b3bf48c 100644
--- a/src/Service/Modellers.php
+++ b/src/Service/Modellers.php
@@ -297,9 +297,9 @@ class Modellers {
       ],
       'module' => [],
     ];
-    $comesWithEcaModel = $this->exportStorage->read('eca.model.' . $eca->id());
+    $comesWithEcaModel = $this->exportStorage->read('modeler_api.model.' . $eca->id());
     if ($comesWithEcaModel) {
-      $dependencies['config'][] = 'eca.model.' . $eca->id();
+      $dependencies['config'][] = 'modeler_api.model.' . $eca->id();
     }
     $this->getNestedDependencies($dependencies, $eca->getDependencies());
     if (file_exists($archiveFileName)) {
@@ -323,7 +323,7 @@ class Modellers {
     // Remove "'eca.eca.ID" from the config dependencies.
     array_shift($dependencies['config']);
     if ($comesWithEcaModel) {
-      // Also remove "'eca.model.ID" from the config dependencies.
+      // Also remove "'moder_api.model.ID" from the config dependencies.
       array_shift($dependencies['config']);
     }
     foreach ($dependencies as $type => $values) {
-- 
GitLab


From 2271f27a4f063a51f03cef7548a6bcc91bdc8a5d Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 15 Mar 2025 19:07:38 -0400
Subject: [PATCH 09/22] Use the modeler_api Modeler annotation instead of
 EcaModeller

---
 .../src/Plugin/ECA/Modeller/DummyModeller.php                 | 2 +-
 src/Plugin/ECA/Modeller/Fallback.php                          | 2 +-
 src/PluginManager/Modeller.php                                | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php b/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php
index f8de48c50..8ea7d0020 100644
--- a/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php
+++ b/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php
@@ -8,7 +8,7 @@ use Drupal\modeler_api\Plugin\ModelerInterface;
 /**
  * Plugin implementation of the ECA Modeller.
  *
- * @EcaModeller(
+ * @Modeler(
  *   id = "dummy",
  *   nodocs = true
  * )
diff --git a/src/Plugin/ECA/Modeller/Fallback.php b/src/Plugin/ECA/Modeller/Fallback.php
index b03ed419c..a4a256b54 100644
--- a/src/Plugin/ECA/Modeller/Fallback.php
+++ b/src/Plugin/ECA/Modeller/Fallback.php
@@ -9,7 +9,7 @@ use Symfony\Component\HttpFoundation\Response;
 /**
  * Fallback plugin implementation of the ECA Modeller.
  *
- * @EcaModeller(
+ * @Modeler(
  *   id = "fallback",
  * )
  */
diff --git a/src/PluginManager/Modeller.php b/src/PluginManager/Modeller.php
index d01290b7b..76cc5d322 100644
--- a/src/PluginManager/Modeller.php
+++ b/src/PluginManager/Modeller.php
@@ -6,7 +6,7 @@ use Drupal\Component\Plugin\FallbackPluginManagerInterface;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
-use Drupal\eca\Annotation\EcaModeller;
+use Drupal\modeler_api\Annotation\Modeler;
 use Drupal\modeler_api\Plugin\ModelerInterface;
 
 /**
@@ -31,7 +31,7 @@ class Modeller extends DefaultPluginManager implements FallbackPluginManagerInte
       $namespaces,
       $module_handler,
       ModelerInterface::class,
-      EcaModeller::class
+      Modeler::class
     );
     $this->alterInfo('eca_modeller_info');
     $this->setCacheBackend($cache_backend, 'eca_modeller_plugins', ['eca_modeller_plugins']);
-- 
GitLab


From 2422a32b988b7428c90c12efd632de38ad44fa03 Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 15 Mar 2025 19:12:42 -0400
Subject: [PATCH 10/22] Deprecate the EcaModeller annotation class

---
 src/Annotation/EcaModeller.php | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/src/Annotation/EcaModeller.php b/src/Annotation/EcaModeller.php
index 8e39e5c54..f27ac669d 100644
--- a/src/Annotation/EcaModeller.php
+++ b/src/Annotation/EcaModeller.php
@@ -7,10 +7,23 @@ use Drupal\Component\Annotation\Plugin;
 /**
  * Defines ECA modeller annotation object.
  *
+ * @deprecated in eca:3.0.0 and is removed from eca:4.0.0. Use
+ * \Drupal\modeler_api\Entity\Model instead.
+ *
+ * @see https://www.drupal.org/node/TODO
+ *
  * @Annotation
  */
 class EcaModeller extends Plugin {
 
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct($values) {
+    parent::__construct($values);
+    @trigger_error(__CLASS__ . ' is deprecated in eca:3.0.0 and is removed from eca:4.0.0. Use Drupal\modeler_api\Entity\Model instead. See https://www.drupal.org/node/TODO', E_USER_DEPRECATED);
+  }
+
   /**
    * Label of the modeller.
    *
-- 
GitLab


From 28878c306326cf1f3eb253ff9219480e289aea9d Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 15 Mar 2025 21:06:29 -0400
Subject: [PATCH 11/22] Require the modeler_api module

Use the related dev branch.
---
 composer.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/composer.json b/composer.json
index 8ac5dc76b..562889d3c 100644
--- a/composer.json
+++ b/composer.json
@@ -19,6 +19,7 @@
     "ext-json": "*",
     "dragonmantank/cron-expression": "^3.1",
     "drupal/core": "^10.3||^11",
+    "drupal/modeler_api": "dev-3513173-modeler_api",
     "mtownsend/xml-to-array": "^2.0"
   },
   "require-dev": {
-- 
GitLab


From f110ac3181ced3d7f2bd282de468a526773c5608 Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 22 Mar 2025 13:07:06 -0400
Subject: [PATCH 12/22] Replace Eca entity with TargetInterface in modelers

---
 modules/modeller_bpmn/src/ModellerBpmnBase.php |  7 ++++---
 src/Plugin/ECA/Modeller/Fallback.php           |  8 +++++---
 src/Plugin/ECA/Modeller/ModellerBase.php       | 14 +++++++-------
 3 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/modules/modeller_bpmn/src/ModellerBpmnBase.php b/modules/modeller_bpmn/src/ModellerBpmnBase.php
index 7d0c26327..cd673f815 100644
--- a/modules/modeller_bpmn/src/ModellerBpmnBase.php
+++ b/modules/modeller_bpmn/src/ModellerBpmnBase.php
@@ -16,6 +16,7 @@ use Drupal\eca\Service\Modellers;
 use Drupal\eca_ui\Service\TokenBrowserService;
 use Drupal\modeler_api\Entity\Model;
 use Drupal\modeler_api\Plugin\ModelerInterface;
+use Drupal\modeler_api\TargetInterface;
 use Mtownsend\XmlToArray\XmlToArray;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -139,7 +140,7 @@ abstract class ModellerBpmnBase extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function createNewModel(string $id, string $model_data, ?string $filename = NULL, bool $save = FALSE): Eca {
+  public function createNewModel(string $id, string $model_data, ?string $filename = NULL, bool $save = FALSE): TargetInterface {
     $eca = Eca::create(['id' => mb_strtolower($id)]);
     $eca->getModel()->setModeldata($model_data);
     $this->setConfigEntity($eca);
@@ -298,7 +299,7 @@ abstract class ModellerBpmnBase extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function clone(): ?Eca {
+  public function clone(): ?TargetInterface {
     $this->prepareForUpdate($this->eca->getModel()->getModeldata());
     $id = $this->generateId();
     /** @var \DOMElement|null $element */
@@ -479,7 +480,7 @@ abstract class ModellerBpmnBase extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function readComponents(Eca $eca): ModelerInterface {
+  public function readComponents(TargetInterface $eca): ModelerInterface {
     $this->eca = $eca;
     $this->eca->resetComponents();
     $idxExtension = $this->xmlNsPrefix() . 'extensionElements';
diff --git a/src/Plugin/ECA/Modeller/Fallback.php b/src/Plugin/ECA/Modeller/Fallback.php
index a4a256b54..be8c586b2 100644
--- a/src/Plugin/ECA/Modeller/Fallback.php
+++ b/src/Plugin/ECA/Modeller/Fallback.php
@@ -4,6 +4,8 @@ namespace Drupal\eca\Plugin\ECA\Modeller;
 
 use Drupal\eca\Entity\Eca;
 use Drupal\modeler_api\Entity\Model;
+use Drupal\modeler_api\Plugin\ModelerInterface;
+use Drupal\modeler_api\TargetInterface;
 use Symfony\Component\HttpFoundation\Response;
 
 /**
@@ -25,7 +27,7 @@ class Fallback extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function createNewModel(string $id, string $model_data, ?string $filename = NULL, bool $save = FALSE): Eca {
+  public function createNewModel(string $id, string $model_data, ?string $filename = NULL, bool $save = FALSE): TargetInterface {
     return $this->eca;
   }
 
@@ -66,7 +68,7 @@ class Fallback extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function clone(): ?Eca {
+  public function clone(): ?TargetInterface {
     return $this->eca;
   }
 
@@ -143,7 +145,7 @@ class Fallback extends ModellerBase {
   /**
    * {@inheritdoc}
    */
-  public function readComponents(Eca $eca): ModelerInterface {
+  public function readComponents(TargetInterface $eca): ModelerInterface {
     return $this;
   }
 
diff --git a/src/Plugin/ECA/Modeller/ModellerBase.php b/src/Plugin/ECA/Modeller/ModellerBase.php
index 3cd3054e9..e1ee0e59f 100644
--- a/src/Plugin/ECA/Modeller/ModellerBase.php
+++ b/src/Plugin/ECA/Modeller/ModellerBase.php
@@ -5,12 +5,12 @@ namespace Drupal\eca\Plugin\ECA\Modeller;
 use Drupal\Component\Uuid\UuidInterface;
 use Drupal\Core\Extension\ExtensionPathResolver;
 use Drupal\Core\Logger\LoggerChannelInterface;
-use Drupal\eca\Entity\Eca;
 use Drupal\eca\Plugin\ECA\EcaPluginBase;
 use Drupal\eca\Service\Actions;
 use Drupal\eca\Service\Conditions;
 use Drupal\eca\Service\Modellers;
 use Drupal\modeler_api\Plugin\ModelerInterface;
+use Drupal\modeler_api\TargetInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\BinaryFileResponse;
 use Symfony\Component\HttpFoundation\Response;
@@ -70,11 +70,11 @@ abstract class ModellerBase extends EcaPluginBase implements ModelerInterface {
   protected ?string $documentationDomain;
 
   /**
-   * ECA config entity.
+   * Target config entity.
    *
-   * @var \Drupal\eca\Entity\Eca
+   * @var \Drupal\modeler_api\TargetInterface
    */
-  protected Eca $eca;
+  protected TargetInterface $eca;
 
   /**
    * Error flag.
@@ -102,8 +102,8 @@ abstract class ModellerBase extends EcaPluginBase implements ModelerInterface {
   /**
    * {@inheritdoc}
    */
-  final public function setConfigEntity(Eca $eca): ModelerInterface {
-    $this->eca = $eca;
+  final public function setConfigEntity(TargetInterface $target): ModelerInterface {
+    $this->eca = $target;
     return $this;
   }
 
@@ -146,7 +146,7 @@ abstract class ModellerBase extends EcaPluginBase implements ModelerInterface {
   /**
    * {@inheritdoc}
    */
-  public function getEca(): Eca {
+  public function getEca(): TargetInterface {
     return $this->eca;
   }
 
-- 
GitLab


From facba6dbc4995420db2429a9942e0c0b8e786f3e Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sat, 22 Mar 2025 13:26:11 -0400
Subject: [PATCH 13/22] In the update function, skip config entities already
 converted

---
 eca.install | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/eca.install b/eca.install
index f9f0b94ec..04eb70864 100644
--- a/eca.install
+++ b/eca.install
@@ -226,6 +226,9 @@ function eca_update_8012(): void {
   /** @var \Drupal\eca\Entity\Model $eca_model */
   $model_storage = \Drupal::entityTypeManager()->getStorage('modeler_api_model');
   foreach (\Drupal::entityTypeManager()->getStorage('eca_model')->loadMultiple() as $eca_model) {
+    if ($model_storage->load($eca_model->id()) !== NULL) {
+      continue;
+    }
     $data = $eca_model->toArray();
     unset($data['uuid']);
     if ($model_storage->create($data)->save()) {
-- 
GitLab


From cf3ae40d627351a99ad8f75a9b186870a24eb1c8 Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sun, 23 Mar 2025 17:50:30 -0400
Subject: [PATCH 14/22] Move ModellerBase and Fallback to the modeler_api
 module

---
 .../modeller_bpmn/src/ModellerBpmnBase.php    |   4 +-
 src/Plugin/ECA/Modeller/Fallback.php          | 159 -----------------
 src/Plugin/ECA/Modeller/ModellerBase.php      | 167 ------------------
 3 files changed, 2 insertions(+), 328 deletions(-)
 delete mode 100644 src/Plugin/ECA/Modeller/Fallback.php
 delete mode 100644 src/Plugin/ECA/Modeller/ModellerBase.php

diff --git a/modules/modeller_bpmn/src/ModellerBpmnBase.php b/modules/modeller_bpmn/src/ModellerBpmnBase.php
index cd673f815..32b0f724c 100644
--- a/modules/modeller_bpmn/src/ModellerBpmnBase.php
+++ b/modules/modeller_bpmn/src/ModellerBpmnBase.php
@@ -11,7 +11,7 @@ use Drupal\eca\Entity\Eca;
 use Drupal\eca\Plugin\ECA\Condition\ConditionInterface;
 use Drupal\eca\Plugin\ECA\EcaPluginBase;
 use Drupal\eca\Plugin\ECA\Event\EventInterface;
-use Drupal\eca\Plugin\ECA\Modeller\ModellerBase;
+use Drupal\modeler_api\Plugin\modeler_api\ModelerBase;
 use Drupal\eca\Service\Modellers;
 use Drupal\eca_ui\Service\TokenBrowserService;
 use Drupal\modeler_api\Entity\Model;
@@ -25,7 +25,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *
  * Providing generic functionality which is similar to all such modellers.
  */
-abstract class ModellerBpmnBase extends ModellerBase {
+abstract class ModellerBpmnBase extends ModelerBase {
 
   /**
    * The model data as an XML string.
diff --git a/src/Plugin/ECA/Modeller/Fallback.php b/src/Plugin/ECA/Modeller/Fallback.php
deleted file mode 100644
index be8c586b2..000000000
--- a/src/Plugin/ECA/Modeller/Fallback.php
+++ /dev/null
@@ -1,159 +0,0 @@
-<?php
-
-namespace Drupal\eca\Plugin\ECA\Modeller;
-
-use Drupal\eca\Entity\Eca;
-use Drupal\modeler_api\Entity\Model;
-use Drupal\modeler_api\Plugin\ModelerInterface;
-use Drupal\modeler_api\TargetInterface;
-use Symfony\Component\HttpFoundation\Response;
-
-/**
- * Fallback plugin implementation of the ECA Modeller.
- *
- * @Modeler(
- *   id = "fallback",
- * )
- */
-class Fallback extends ModellerBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function generateId(): string {
-    return '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function createNewModel(string $id, string $model_data, ?string $filename = NULL, bool $save = FALSE): TargetInterface {
-    return $this->eca;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(string $data, ?string $filename = NULL, ?bool $status = NULL): bool {
-    return FALSE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function updateModel(Model $model): bool {
-    return FALSE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function enable(): ModelerInterface {
-    $this->eca
-      ->setStatus(TRUE)
-      ->save();
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function disable(): ModelerInterface {
-    $this->eca
-      ->setStatus(FALSE)
-      ->save();
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function clone(): ?TargetInterface {
-    return $this->eca;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function export(): ?Response {
-    return NULL;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFilename(): string {
-    return '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setModeldata(string $data): ModelerInterface {
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getModeldata(): string {
-    return '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getId(): string {
-    return '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getLabel(): string {
-    return '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTags(): array {
-    return [];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDocumentation(): string {
-    return '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getStatus(): bool {
-    return FALSE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getVersion(): string {
-    return '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function readComponents(TargetInterface $eca): ModelerInterface {
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function exportTemplates(): ModelerInterface {
-    return $this;
-  }
-
-}
diff --git a/src/Plugin/ECA/Modeller/ModellerBase.php b/src/Plugin/ECA/Modeller/ModellerBase.php
deleted file mode 100644
index e1ee0e59f..000000000
--- a/src/Plugin/ECA/Modeller/ModellerBase.php
+++ /dev/null
@@ -1,167 +0,0 @@
-<?php
-
-namespace Drupal\eca\Plugin\ECA\Modeller;
-
-use Drupal\Component\Uuid\UuidInterface;
-use Drupal\Core\Extension\ExtensionPathResolver;
-use Drupal\Core\Logger\LoggerChannelInterface;
-use Drupal\eca\Plugin\ECA\EcaPluginBase;
-use Drupal\eca\Service\Actions;
-use Drupal\eca\Service\Conditions;
-use Drupal\eca\Service\Modellers;
-use Drupal\modeler_api\Plugin\ModelerInterface;
-use Drupal\modeler_api\TargetInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\BinaryFileResponse;
-use Symfony\Component\HttpFoundation\Response;
-
-/**
- * Base class for ECA modeller plugins.
- */
-abstract class ModellerBase extends EcaPluginBase implements ModelerInterface {
-
-  /**
-   * ECA action service.
-   *
-   * @var \Drupal\eca\Service\Actions
-   */
-  protected Actions $actionServices;
-
-  /**
-   * ECA condition service.
-   *
-   * @var \Drupal\eca\Service\Conditions
-   */
-  protected Conditions $conditionServices;
-
-  /**
-   * ECA modeller service.
-   *
-   * @var \Drupal\eca\Service\Modellers
-   */
-  protected Modellers $modellerServices;
-
-  /**
-   * Logger service.
-   *
-   * @var \Drupal\Core\Logger\LoggerChannelInterface
-   */
-  protected LoggerChannelInterface $logger;
-
-  /**
-   * Uuid service.
-   *
-   * @var \Drupal\Component\Uuid\UuidInterface
-   */
-  protected UuidInterface $uuid;
-
-  /**
-   * Extension path resolver service.
-   *
-   * @var \Drupal\Core\Extension\ExtensionPathResolver
-   */
-  protected ExtensionPathResolver $extensionPathResolver;
-
-  /**
-   * The documentation domain. May be NULL if not enabled or specified.
-   *
-   * @var string|null
-   */
-  protected ?string $documentationDomain;
-
-  /**
-   * Target config entity.
-   *
-   * @var \Drupal\modeler_api\TargetInterface
-   */
-  protected TargetInterface $eca;
-
-  /**
-   * Error flag.
-   *
-   * @var bool
-   */
-  protected bool $hasError = FALSE;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
-    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
-    $instance->actionServices = $container->get('eca.service.action');
-    $instance->conditionServices = $container->get('eca.service.condition');
-    $instance->modellerServices = $container->get('eca.service.modeller');
-    $instance->logger = $container->get('logger.channel.eca');
-    $instance->uuid = $container->get('uuid');
-    $instance->extensionPathResolver = $container->get('extension.path.resolver');
-    $instance->documentationDomain = $container->getParameter('eca.default_documentation_domain') ?
-      $container->get('config.factory')->get('eca.settings')->get('documentation_domain') : NULL;
-    return $instance;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  final public function setConfigEntity(TargetInterface $target): ModelerInterface {
-    $this->eca = $target;
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function isEditable(): bool {
-    return FALSE;
-  }
-
-  /**
-   * Prepares a model for export.
-   *
-   * By default, this is doing nothing. But this can be overwritten by
-   * modeller implementations.
-   */
-  protected function prepareForExport(): void {}
-
-  /**
-   * {@inheritdoc}
-   */
-  public function export(): ?Response {
-    $this->prepareForExport();
-    $filename = mb_strtolower($this->getPluginId()) . '-' . mb_strtolower($this->getEca()->id()) . '.tar.gz';
-    $tempFileName = 'temporary://' . $filename;
-    $this->modellerServices->exportArchive($this->eca, $tempFileName);
-    return new BinaryFileResponse($tempFileName, 200, [
-      'Content-Type' => 'application/octet-stream',
-      'Content-Disposition' => 'attachment; filename="' . $filename . '"',
-    ]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function edit(): array {
-    return [];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getEca(): TargetInterface {
-    return $this->eca;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function hasError(): bool {
-    return $this->hasError;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getChangelog(): array {
-    return [];
-  }
-
-}
-- 
GitLab


From dc0e0b3eb04c34de454505334ace3f42a135108d Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sun, 23 Mar 2025 19:15:01 -0400
Subject: [PATCH 15/22] Use the plugin manager from the modeler_api module

---
 eca.services.yml                              |  5 +-
 .../tests/src/Kernel/BpmnBaseModellerTest.php |  4 +-
 .../src/Kernel/PluginConfigValidationTest.php |  4 +-
 modules/ui/src/Controller/EcaController.php   |  2 +-
 src/Entity/EcaTrait.php                       | 12 ++---
 src/PluginManager/Modeller.php                | 47 -------------------
 src/Service/Modellers.php                     | 10 ++--
 7 files changed, 17 insertions(+), 67 deletions(-)
 delete mode 100644 src/PluginManager/Modeller.php

diff --git a/eca.services.yml b/eca.services.yml
index b14f39adb..426dbccac 100644
--- a/eca.services.yml
+++ b/eca.services.yml
@@ -20,9 +20,6 @@ services:
   eca.dynamic_subscriber:
     class: Drupal\eca\EventSubscriber\DynamicSubscriber
 
-  plugin.manager.eca.modeller:
-    class: Drupal\eca\PluginManager\Modeller
-    parent: default_plugin_manager
   plugin.manager.eca.event:
     class: Drupal\eca\PluginManager\Event
     parent: default_plugin_manager
@@ -39,7 +36,7 @@ services:
 
   eca.service.modeller:
     class: Drupal\eca\Service\Modellers
-    arguments: ['@entity_type.manager', '@plugin.manager.eca.modeller', '@plugin.manager.eca.event',  '@eca.service.action',  '@eca.service.condition', '@logger.channel.eca', '@file_system', '@config.storage.export', '@config.factory']
+    arguments: ['@entity_type.manager', '@plugin.manager.modeler_api.modeler', '@plugin.manager.eca.event',  '@eca.service.action',  '@eca.service.condition', '@logger.channel.eca', '@file_system', '@config.storage.export', '@config.factory']
   eca.service.action:
     class: Drupal\eca\Service\Actions
     arguments: ['@plugin.manager.eca.action', '@logger.channel.eca', '@entity_type.manager', '@eca.token_services']
diff --git a/modules/modeller_bpmn/tests/src/Kernel/BpmnBaseModellerTest.php b/modules/modeller_bpmn/tests/src/Kernel/BpmnBaseModellerTest.php
index b5fdb33be..e71e1d41c 100644
--- a/modules/modeller_bpmn/tests/src/Kernel/BpmnBaseModellerTest.php
+++ b/modules/modeller_bpmn/tests/src/Kernel/BpmnBaseModellerTest.php
@@ -45,8 +45,8 @@ class BpmnBaseModellerTest extends Base {
   protected function setUp(): void {
     parent::setUp();
     $this->installEntitySchema('user');
-    /** @var \Drupal\eca\PluginManager\Modeller $modelManager */
-    $modelManager = \Drupal::service('plugin.manager.eca.modeller');
+    /** @var \Drupal\modeler_api\Plugin\ModelerPluginManager $modelManager */
+    $modelManager = \Drupal::service('plugin.manager.modeler_api.modeler');
     /* @noinspection PhpFieldAssignmentTypeMismatchInspection */
     $this->modeller = $modelManager->createInstance('dummy');
     $this->eca = \Drupal::entityTypeManager()->getStorage('eca')->load('eca_test_0011');
diff --git a/modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php b/modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php
index 368e9cb4e..dbd0fa797 100644
--- a/modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php
+++ b/modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php
@@ -27,8 +27,8 @@ class PluginConfigValidationTest extends Base {
    * Tests the saving of an ECA entity and its validation.
    */
   public function testPluginConfigValidation(): void {
-    /** @var \Drupal\eca\PluginManager\Modeller $modelManager */
-    $modelManager = \Drupal::service('plugin.manager.eca.modeller');
+    /** @var \Drupal\modeler_api\Plugin\ModelerPluginManager $modelManager */
+    $modelManager = \Drupal::service('plugin.manager.modeler_api.modeler');
     /** @var \Drupal\eca_test_model_plugin_config_validation\Plugin\ECA\Modeller\DummyModeller $modeller */
     $modeller = $modelManager->createInstance('dummy');
     /** @var \Drupal\modeler_api\Entity\Model $model */
diff --git a/modules/ui/src/Controller/EcaController.php b/modules/ui/src/Controller/EcaController.php
index 8d5abe787..1fab775c0 100644
--- a/modules/ui/src/Controller/EcaController.php
+++ b/modules/ui/src/Controller/EcaController.php
@@ -106,7 +106,7 @@ final class EcaController extends ControllerBase {
     return [
       '#cache' => [
         'tags' => [
-          'eca_modeller_plugins',
+          'modeler_api_plugins',
         ],
       ],
       '#theme' => 'entity_add_list',
diff --git a/src/Entity/EcaTrait.php b/src/Entity/EcaTrait.php
index 4e62ba701..ff9ec767f 100644
--- a/src/Entity/EcaTrait.php
+++ b/src/Entity/EcaTrait.php
@@ -10,7 +10,7 @@ use Drupal\Core\Messenger\MessengerInterface;
 use Drupal\eca\PluginManager\Action;
 use Drupal\eca\PluginManager\Condition;
 use Drupal\eca\PluginManager\Event;
-use Drupal\eca\PluginManager\Modeller;
+use Drupal\modeler_api\Plugin\ModelerPluginManager;
 use Drupal\eca\Service\Actions;
 use Drupal\eca\Service\Conditions;
 use Drupal\eca\Service\DependencyCalculation;
@@ -25,9 +25,9 @@ trait EcaTrait {
   /**
    * ECA modeller plugin manager.
    *
-   * @var \Drupal\eca\PluginManager\Modeller|null
+   * @var \Drupal\modeler_api\Plugin\ModelerPluginManager|null
    */
-  protected ?Modeller $modellerPluginManager;
+  protected ?ModelerPluginManager $modellerPluginManager;
 
   /**
    * ECA event plugin manager.
@@ -116,12 +116,12 @@ trait EcaTrait {
   /**
    * Initializes the modeller plugin manager.
    *
-   * @return \Drupal\eca\PluginManager\Modeller
+   * @return \Drupal\modeler_api\Plugin\ModelerPluginManager
    *   The modeller plugin manager.
    */
-  protected function modellerPluginManager(): Modeller {
+  protected function modellerPluginManager(): ModelerPluginManager {
     if (!isset($this->modellerPluginManager)) {
-      $this->modellerPluginManager = \Drupal::service('plugin.manager.eca.modeller');
+      $this->modellerPluginManager = \Drupal::service('plugin.manager.modeler_api.modeler');
     }
     return $this->modellerPluginManager;
   }
diff --git a/src/PluginManager/Modeller.php b/src/PluginManager/Modeller.php
deleted file mode 100644
index 76cc5d322..000000000
--- a/src/PluginManager/Modeller.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-namespace Drupal\eca\PluginManager;
-
-use Drupal\Component\Plugin\FallbackPluginManagerInterface;
-use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Extension\ModuleHandlerInterface;
-use Drupal\Core\Plugin\DefaultPluginManager;
-use Drupal\modeler_api\Annotation\Modeler;
-use Drupal\modeler_api\Plugin\ModelerInterface;
-
-/**
- * ECA modeller plugin manager.
- */
-class Modeller extends DefaultPluginManager implements FallbackPluginManagerInterface {
-
-  /**
-   * Constructs PluginManager object.
-   *
-   * @param \Traversable $namespaces
-   *   An object that implements \Traversable which contains the root paths
-   *   keyed by the corresponding namespace to look for plugin implementations.
-   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
-   *   Cache backend instance to use.
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   *   The module handler to invoke the alter hook with.
-   */
-  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
-    parent::__construct(
-      'Plugin/ECA/Modeller',
-      $namespaces,
-      $module_handler,
-      ModelerInterface::class,
-      Modeler::class
-    );
-    $this->alterInfo('eca_modeller_info');
-    $this->setCacheBackend($cache_backend, 'eca_modeller_plugins', ['eca_modeller_plugins']);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFallbackPluginId($plugin_id, array $configuration = []): string {
-    return 'fallback';
-  }
-
-}
diff --git a/src/Service/Modellers.php b/src/Service/Modellers.php
index c1b3bf48c..a71ba9d1b 100644
--- a/src/Service/Modellers.php
+++ b/src/Service/Modellers.php
@@ -15,7 +15,7 @@ use Drupal\Core\Logger\LoggerChannelInterface;
 use Drupal\eca\Entity\Eca;
 use Drupal\eca\ErrorHandlerTrait;
 use Drupal\eca\PluginManager\Event;
-use Drupal\eca\PluginManager\Modeller;
+use Drupal\modeler_api\Plugin\ModelerPluginManager;
 use Drupal\modeler_api\Plugin\ModelerInterface;
 
 /**
@@ -43,9 +43,9 @@ class Modellers {
   /**
    * ECA modeller plugin manager.
    *
-   * @var \Drupal\eca\PluginManager\Modeller
+   * @var \Drupal\modeler_api\Plugin\ModelerPluginManager
    */
-  protected Modeller $pluginManagerModeller;
+  protected ModelerPluginManager $pluginManagerModeller;
 
   /**
    * ECA event plugin manager.
@@ -101,7 +101,7 @@ class Modellers {
    *
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity type manager service.
-   * @param \Drupal\eca\PluginManager\Modeller $plugin_manager_modeller
+   * @param \Drupal\modeler_api\Plugin\ModelerPluginManager $plugin_manager_modeller
    *   The ECA modeller plugin manager.
    * @param \Drupal\eca\PluginManager\Event $plugin_manager_event
    *   The ECA event plugin manager.
@@ -121,7 +121,7 @@ class Modellers {
    * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
    * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, Modeller $plugin_manager_modeller, Event $plugin_manager_event, Actions $action_services, Conditions $condition_services, LoggerChannelInterface $logger, FileSystemInterface $file_system, StorageInterface $export_storage, ConfigFactoryInterface $config_factory) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, ModelerPluginManager $plugin_manager_modeller, Event $plugin_manager_event, Actions $action_services, Conditions $condition_services, LoggerChannelInterface $logger, FileSystemInterface $file_system, StorageInterface $export_storage, ConfigFactoryInterface $config_factory) {
     $this->configStorage = $entity_type_manager->getStorage('eca');
     $this->modelStorage = $entity_type_manager->getStorage('modeler_api_model');
     $this->pluginManagerModeller = $plugin_manager_modeller;
-- 
GitLab


From df1d5e158cd9b7b4189e54fedb8b47e89aecad81 Mon Sep 17 00:00:00 2001
From: Benji Fisher <benji@FisherFam.org>
Date: Sun, 23 Mar 2025 20:08:26 -0400
Subject: [PATCH 16/22] Remove the Annotation class for the modeler plugin

On second thought, we should deprecate this class in the 2.1.x branch
and remove it in the 3.0.x branch. This MR targets 3.0.x.
---
 src/Annotation/EcaModeller.php | 41 ----------------------------------
 1 file changed, 41 deletions(-)
 delete mode 100644 src/Annotation/EcaModeller.php

diff --git a/src/Annotation/EcaModeller.php b/src/Annotation/EcaModeller.php
deleted file mode 100644
index f27ac669d..000000000
--- a/src/Annotation/EcaModeller.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-namespace Drupal\eca\Annotation;
-
-use Drupal\Component\Annotation\Plugin;
-
-/**
- * Defines ECA modeller annotation object.
- *
- * @deprecated in eca:3.0.0 and is removed from eca:4.0.0. Use
- * \Drupal\modeler_api\Entity\Model instead.
- *
- * @see https://www.drupal.org/node/TODO
- *
- * @Annotation
- */
-class EcaModeller extends Plugin {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct($values) {
-    parent::__construct($values);
-    @trigger_error(__CLASS__ . ' is deprecated in eca:3.0.0 and is removed from eca:4.0.0. Use Drupal\modeler_api\Entity\Model instead. See https://www.drupal.org/node/TODO', E_USER_DEPRECATED);
-  }
-
-  /**
-   * Label of the modeller.
-   *
-   * @var string
-   */
-  public string $label;
-
-  /**
-   * Description of the modeller.
-   *
-   * @var string
-   */
-  public string $description;
-
-}
-- 
GitLab


From 9e14c0ecccdca31745f51a16d008bb4c999ecc86 Mon Sep 17 00:00:00 2001
From: jurgenhaas <juergen.haas@lakedrops.com>
Date: Fri, 4 Apr 2025 12:05:04 +0200
Subject: [PATCH 17/22] Issue #3513175 by benjifisher, jurgenhaas,
 cosmicdreams: Remove code that is added to the Modeler API module

---
 .gitlab-ci.yml                                |    4 +-
 composer.json                                 |   13 +-
 eca.info.yml                                  |    3 +-
 eca.install                                   |  238 ----
 eca.module                                    |   49 -
 eca.post_update.php                           |   40 -
 eca.services.yml                              |   86 +-
 modules/access/eca_access.info.yml            |    2 +-
 modules/access/eca_access.services.yml        |    3 +-
 modules/base/eca_base.info.yml                |    2 +-
 modules/base/eca_base.install                 |   28 -
 modules/cache/eca_cache.info.yml              |    2 +-
 modules/cache/eca_cache.services.yml          |    6 +-
 .../src/Plugin/Action/CacheInvalidate.php     |    2 +-
 .../src/Plugin/Action/RawCacheInvalidate.php  |    2 +-
 modules/config/eca_config.info.yml            |    2 +-
 modules/content/eca_content.info.yml          |    2 +-
 modules/content/eca_content.services.yml      |   12 +-
 modules/development/eca_development.info.yml  |    3 +-
 .../src/Drush/Commands/DocsCommands.php       |   64 +-
 modules/endpoint/eca_endpoint.info.yml        |    2 +-
 modules/file/eca_file.info.yml                |    2 +-
 modules/form/eca_form.info.yml                |    2 +-
 modules/form/eca_form.install                 |   69 --
 modules/form/eca_form.services.yml            |    3 +-
 modules/language/eca_language.info.yml        |    2 +-
 modules/log/eca_log.info.yml                  |    2 +-
 modules/log/eca_log.services.yml              |    3 +-
 modules/migrate/eca_migrate.info.yml          |    2 +-
 modules/miscellaneous/eca_misc.info.yml       |    2 +-
 modules/miscellaneous/eca_misc.services.yml   |    3 +-
 modules/modeller_bpmn/data/empty.bpmn         |    7 -
 .../modeller_bpmn/eca_modeller_bpmn.info.yml  |    7 -
 .../modeller_bpmn/src/ModellerBpmnBase.php    | 1019 -----------------
 .../config/install/eca.eca.eca_test_0011.yml  |   30 -
 .../install/eca.model.eca_test_0011.yml       |   58 -
 ...st_model_plugin_config_validation.info.yml |   10 -
 .../src/Plugin/Action/DummyValidation.php     |   67 --
 .../src/Plugin/ECA/Modeller/DummyModeller.php |   51 -
 .../tests/src/Kernel/BpmnBaseModellerTest.php |  252 ----
 .../src/Kernel/PluginConfigValidationTest.php |   64 --
 .../eca_project_browser.services.yml          |    3 +-
 modules/queue/eca_queue.info.yml              |    2 +-
 modules/render/eca_render.info.yml            |    2 +-
 .../src/Plugin/ECA/Event/RenderEvent.php      |    2 +-
 modules/ui/eca_ui.info.yml                    |    2 +-
 modules/ui/eca_ui.module                      |  120 --
 modules/ui/eca_ui.routing.yml                 |   70 --
 modules/ui/eca_ui.services.yml                |    3 +-
 modules/ui/src/Controller/EcaController.php   |  267 -----
 .../ui/src/Entity/AccessControlHandler.php    |   31 -
 modules/ui/src/Form/EcaDeleteForm.php         |   19 -
 modules/ui/src/Form/ExportRecipe.php          |  153 ---
 modules/ui/src/Form/Import.php                |  415 -------
 .../Plugin/modeler_api_model_owner/Eca.php    |   65 ++
 modules/user/eca_user.info.yml                |    2 +-
 modules/user/eca_user.services.yml            |    6 +-
 .../src/Plugin/ECA/Condition/UserTrait.php    |    9 +-
 modules/views/eca_views.info.yml              |    2 +-
 modules/views/eca_views.services.yml          |    3 +-
 modules/workflow/eca_workflow.info.yml        |    2 +-
 modules/workflow/eca_workflow.services.yml    |    3 +-
 src/Drush/Commands/EcaCommands.php            |  163 ---
 src/EcaUpdate.php                             |  156 ---
 src/Entity/Eca.php                            |   92 +-
 src/Entity/Model.php                          |  176 ---
 src/Service/Conditions.php                    |   12 +-
 src/Service/Events.php                        |   72 ++
 src/Service/ExportRecipe.php                  |  292 -----
 src/Service/Modellers.php                     |  368 ------
 70 files changed, 306 insertions(+), 4426 deletions(-)
 delete mode 100644 eca.install
 delete mode 100644 eca.post_update.php
 delete mode 100644 modules/base/eca_base.install
 delete mode 100644 modules/modeller_bpmn/data/empty.bpmn
 delete mode 100644 modules/modeller_bpmn/eca_modeller_bpmn.info.yml
 delete mode 100644 modules/modeller_bpmn/src/ModellerBpmnBase.php
 delete mode 100644 modules/modeller_bpmn/tests/modules/plugin_config_validation/config/install/eca.eca.eca_test_0011.yml
 delete mode 100644 modules/modeller_bpmn/tests/modules/plugin_config_validation/config/install/eca.model.eca_test_0011.yml
 delete mode 100644 modules/modeller_bpmn/tests/modules/plugin_config_validation/eca_test_model_plugin_config_validation.info.yml
 delete mode 100644 modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/Action/DummyValidation.php
 delete mode 100644 modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php
 delete mode 100644 modules/modeller_bpmn/tests/src/Kernel/BpmnBaseModellerTest.php
 delete mode 100644 modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php
 delete mode 100644 modules/ui/eca_ui.module
 delete mode 100644 modules/ui/src/Controller/EcaController.php
 delete mode 100644 modules/ui/src/Entity/AccessControlHandler.php
 delete mode 100644 modules/ui/src/Form/EcaDeleteForm.php
 delete mode 100644 modules/ui/src/Form/ExportRecipe.php
 delete mode 100644 modules/ui/src/Form/Import.php
 create mode 100644 modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
 delete mode 100644 src/EcaUpdate.php
 delete mode 100644 src/Entity/Model.php
 create mode 100644 src/Service/Events.php
 delete mode 100644 src/Service/ExportRecipe.php
 delete mode 100644 src/Service/Modellers.php

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9358ad579..12ee5d692 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -7,8 +7,8 @@ include:
       - '/includes/include.drupalci.workflows.yml'
 variables:
   _CSPELL_IGNORE_PATHS: '"**/eca.eca.*.yml", "**/eca.model.*.yml", "**/empty.bpmn"'
-  OPT_IN_TEST_PREVIOUS_MAJOR: 1
-  OPT_IN_TEST_NEXT_MINOR: 1
+  OPT_IN_TEST_PREVIOUS_MAJOR: 0
+  OPT_IN_TEST_NEXT_MINOR: 0
   OPT_IN_TEST_NEXT_MAJOR: 0
   RUN_JOB_UPGRADE_STATUS: 0
 cspell:
diff --git a/composer.json b/composer.json
index 562889d3c..fd4359800 100644
--- a/composer.json
+++ b/composer.json
@@ -7,19 +7,12 @@
     "issues": "https://drupal.org/project/issues/eca",
     "source": "https://drupal.org/project/eca"
   },
-  "repositories": {
-    "webform": {
-      "type": "vcs",
-      "url": "https://git.drupalcode.org/issue/webform-3465838.git"
-    }
-  },
   "require": {
-    "php": ">=8.1",
+    "php": ">=8.3",
     "ext-dom": "*",
     "ext-json": "*",
     "dragonmantank/cron-expression": "^3.1",
-    "drupal/core": "^10.3||^11",
-    "drupal/modeler_api": "dev-3513173-modeler_api",
+    "drupal/core": "^11.2",
     "mtownsend/xml-to-array": "^2.0"
   },
   "require-dev": {
@@ -28,6 +21,6 @@
     "drupal/paragraphs": "^1.18",
     "drupal/project_browser": "^2.0",
     "drupal/token": "^1.15",
-    "drupal/webform": "dev-3465838-drupal-11-compatibility"
+    "drupal/webform": "^6.3"
   }
 }
diff --git a/eca.info.yml b/eca.info.yml
index aa9f4a9b1..48ac5a67c 100644
--- a/eca.info.yml
+++ b/eca.info.yml
@@ -1,9 +1,8 @@
 name: 'ECA Core'
 type: module
 description: 'Core module for ECA framework.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - drupal:system
   - drupal:user
-  - modeler_api:modeler_api
diff --git a/eca.install b/eca.install
deleted file mode 100644
index 04eb70864..000000000
--- a/eca.install
+++ /dev/null
@@ -1,238 +0,0 @@
-<?php
-
-/**
- * @file
- * ECA install file.
- */
-
-use Drupal\Component\Plugin\ConfigurableInterface;
-use Drupal\eca\Plugin\ECA\Condition\ConditionInterface;
-
-/**
- * Update existing schema of eca config entities.
- */
-function eca_update_8001(): void {
-  $storage = \Drupal::entityTypeManager()->getStorage('eca');
-  /** @var \Drupal\eca\Entity\Eca $eca */
-  foreach ($storage->loadMultiple() as $eca) {
-    foreach (['events', 'conditions', 'actions', 'gateways'] as $type) {
-      $items = $eca->get($type) ?? [];
-      foreach ($items as &$item) {
-        if (isset($item['fields'])) {
-          $fields = [];
-          foreach ($item['fields'] as $field) {
-            if (!isset($field['key'])) {
-              // Model already of new structure.
-              break 3;
-            }
-            $fields[$field['key']] = $field['value'];
-          }
-          $item['configuration'] = $fields;
-          unset($item['fields']);
-        }
-      }
-      $eca->set($type, $items);
-    }
-    $eca->save();
-  }
-}
-
-/**
- * Enable eca_ui sub-module which has been moved to its own sub-module.
- */
-function eca_update_8002(): void {
-  \Drupal::service('module_installer')->install(['eca_ui']);
-}
-
-/**
- * Adds the new documentation domain setting with a default value.
- *
- * Export your configuration after this update.
- */
-function eca_update_8003(): void {
-  $config = \Drupal::configFactory()->getEditable('eca.settings');
-  $config->set('documentation_domain', "https://ecaguide.org");
-  $config->save(TRUE);
-}
-
-/**
- * Replace core save actions by one generic save action.
- *
- * Export your configuration after this update.
- */
-function eca_update_8004(): void {
-  /** @var \Drupal\eca\Entity\Eca $eca */
-  foreach (\Drupal::entityTypeManager()->getStorage('eca')->loadMultiple() as $eca) {
-    $items = $eca->get('actions') ?? [];
-    $needs_save = FALSE;
-    foreach ($items as &$item) {
-      $plugin_id = $item['plugin'] ?? '';
-      if (strpos($plugin_id, 'entity:save_action') === 0) {
-        $item['plugin'] = 'eca_save_entity';
-        $needs_save = TRUE;
-      }
-    }
-    if ($needs_save) {
-      $eca->set('actions', $items);
-      $eca->save();
-    }
-  }
-
-  /** @var \Drupal\eca\Entity\Model $eca_model */
-  foreach (\Drupal::entityTypeManager()->getStorage('eca_model')->loadMultiple() as $eca_model) {
-    $model_data = $eca_model->get('modeldata') ?? '';
-    if ($model_data) {
-      $needs_save = FALSE;
-      foreach (array_keys(\Drupal::entityTypeManager()->getDefinitions()) as $entity_type_id) {
-        $core_action = 'entity:save_action:' . $entity_type_id;
-        if (strpos($model_data, $core_action) !== FALSE) {
-          $model_data = str_replace($core_action, 'eca_save_entity', $model_data);
-          $needs_save = TRUE;
-        }
-      }
-      if ($needs_save) {
-        $eca_model->set('modeldata', $model_data);
-        $eca_model->save();
-      }
-    }
-  }
-}
-
-/**
- * Update existing eca config entities to use boolean value for checkboxes.
- */
-function eca_update_8005(): void {
-  $actionPluginManager = \Drupal::service('plugin.manager.action');
-  $conditionPluginManager = \Drupal::service('plugin.manager.eca.condition');
-
-  $storage = \Drupal::entityTypeManager()->getStorage('eca');
-  /** @var \Drupal\eca\Entity\Eca $eca */
-  foreach ($storage->loadMultiple() as $eca) {
-    $changed = FALSE;
-    foreach (['conditions', 'actions'] as $type) {
-      $items = $eca->get($type) ?? [];
-      foreach ($items as &$item) {
-        if (!empty($item['configuration'])) {
-          $defaultConfig = [];
-          if ($type === 'actions') {
-            $plugin = $actionPluginManager()->createInstance($item['plugin'], []);
-            if ($plugin instanceof ConfigurableInterface) {
-              $defaultConfig = $plugin->defaultConfiguration();
-            }
-          }
-          elseif ($type === 'conditions') {
-            $plugin = $conditionPluginManager()->createInstance($item['plugin'], []);
-            if ($plugin instanceof ConditionInterface) {
-              $defaultConfig = $plugin->defaultConfiguration();
-            }
-          }
-          // Convert potential strings from pseudo-checkboxes back to boolean.
-          foreach ($defaultConfig as $key => $value) {
-            if (is_bool($value) && isset($item['configuration'][$key]) && !is_bool($item['configuration'][$key])) {
-              $item['configuration'][$key] = mb_strtolower($item['configuration'][$key]) === 'yes';
-              $changed = TRUE;
-            }
-          }
-        }
-      }
-      $eca->set($type, $items);
-    }
-    if ($changed) {
-      $eca->save();
-    }
-  }
-}
-
-/**
- * Update bpmn ids.
- */
-function eca_update_8006(): void {
-  $storage = \Drupal::entityTypeManager()->getStorage('eca');
-  /** @var \Drupal\eca\Entity\Eca $eca */
-  foreach ($storage->loadMultiple() as $eca) {
-    $model = $eca->getModel();
-    $xml = $model->getModeldata();
-    foreach (['event', 'condition', 'action'] as $type) {
-      $items = $eca->get($type . 's') ?? [];
-      foreach ($items as $item) {
-        $search = 'org.drupal.' . $item['plugin'];
-        $replace = 'org.drupal.' . $type . '.' . $item['plugin'];
-        $xml = str_replace($search, $replace, $xml);
-      }
-    }
-    $model
-      ->setModeldata($xml)
-      ->save();
-  }
-}
-
-/**
- * Update global configuration with new user setting.
- */
-function eca_update_8007(): void {
-  \Drupal::configFactory()
-    ->getEditable('eca.settings')
-    ->set('user', '')
-    ->save();
-}
-
-/**
- * Update global configuration with new dependency calculation settings.
- */
-function eca_update_8008(): void {
-  \Drupal::configFactory()
-    ->getEditable('eca.settings')
-    ->set('dependency_calculation', [
-      'bundle',
-      'field_storage',
-      'field_config',
-      'new_field_config',
-    ])
-    ->save();
-}
-
-/**
- * Writes subscribed ECA events into Drupal state for the new subscriber logic.
- */
-function eca_update_8009(): void {
-  /**
-   * @var \Drupal\eca\Entity\EcaStorage $storage
-   */
-  $storage = \Drupal::entityTypeManager()->getStorage('eca');
-  $storage->rebuildSubscribedEvents();
-}
-
-/**
- * Update global configuration with new service user setting.
- */
-function eca_update_8010(): void {
-  \Drupal::configFactory()
-    ->getEditable('eca.settings')
-    ->set('service_user', '')
-    ->save();
-}
-
-/**
- * Update existing models.
- */
-function eca_update_8011(): void {
-  \Drupal::service('eca.update')->updateAllModels();
-}
-
-/**
- * Replace eca_model config entities with modeler_api_model entities.
- */
-function eca_update_8012(): void {
-  /** @var \Drupal\eca\Entity\Model $eca_model */
-  $model_storage = \Drupal::entityTypeManager()->getStorage('modeler_api_model');
-  foreach (\Drupal::entityTypeManager()->getStorage('eca_model')->loadMultiple() as $eca_model) {
-    if ($model_storage->load($eca_model->id()) !== NULL) {
-      continue;
-    }
-    $data = $eca_model->toArray();
-    unset($data['uuid']);
-    if ($model_storage->create($data)->save()) {
-      $eca_model->delete();
-    }
-  }
-}
diff --git a/eca.module b/eca.module
index 64d820a89..4c7e444a9 100644
--- a/eca.module
+++ b/eca.module
@@ -50,52 +50,3 @@ function _eca_trigger_event(): TriggerEvent {
 function _eca_content_entity_types(): ContentEntityTypes {
   return \Drupal::service('eca.service.content_entity_types');
 }
-
-/**
- * Helper function to rename tokens in existing ECA models.
- *
- * @param array $tokenNames
- *   The list of token names.
- */
-function _eca_post_update_token_rename(array $tokenNames): void {
-  $storage = \Drupal::entityTypeManager()->getStorage('eca');
-  /** @var \Drupal\eca\Entity\Eca $eca */
-  foreach ($storage->loadMultiple() as $eca) {
-    $changed = FALSE;
-    $modelData = $eca->getModel()->getModeldata();
-    foreach (['conditions', 'actions'] as $type) {
-      $items = $eca->get($type) ?? [];
-      foreach ($items as &$item) {
-        if (!empty($item['configuration'])) {
-          foreach ($item['configuration'] as $key => $value) {
-            foreach ($tokenNames as $oldTokenName => $newTokenName) {
-              $oldToken = '[' . $oldTokenName;
-              $newToken = '[' . $newTokenName;
-              $count = 0;
-              $modelData = str_replace($oldToken, $newToken, $modelData, $count);
-              if ($count > 0) {
-                $changed = TRUE;
-              }
-              if (is_array($value)) {
-                foreach ($value as $delta => $subValue) {
-                  $item['configuration'][$key][$delta] = str_replace($oldToken, $newToken, $subValue);
-                }
-              }
-              else {
-                $item['configuration'][$key] = str_replace($oldToken, $newToken, $value);
-              }
-            }
-          }
-        }
-      }
-      unset($item);
-      $eca->set($type, $items);
-    }
-    if ($changed) {
-      if ($modelData !== '') {
-        $eca->getModel()->setModeldata($modelData)->save();
-      }
-      $eca->save();
-    }
-  }
-}
diff --git a/eca.post_update.php b/eca.post_update.php
deleted file mode 100644
index d239658a6..000000000
--- a/eca.post_update.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-/**
- * @file
- * Post update functions for ECA.
- */
-
-/**
- * Rename used tokens in available ECA models for ECA 2.0.0.
- */
-function eca_post_update_rename_tokens_2_0_0(): void {
-  $tokenNames = [
-    'event:content-type' => 'event:content_type',
-    'event:entity-bundle' => 'event:entity_bundle',
-    'event:entity-id' => 'event:entity_id',
-    'event:entity-type' => 'event:entity_type',
-    'event:extra-field-name' => 'event:extra_field_name',
-    'event:field-name' => 'event:field_name',
-    'event:machine-name' => 'event:machine_name',
-    'event:parent-bundle' => 'event:parent_bundle',
-    'event:parent-id' => 'event:parent_id',
-    'event:parent-type' => 'event:parent_type',
-    'event:path-arguments' => 'event:path_arguments',
-    'event:route-parameters' => 'event:route_parameters',
-    'event:view-display' => 'event:view_display',
-    'event:view-id' => 'event:view_id',
-    'form:base-id' => 'form:base_id',
-    'form:num-errors' => 'form:num_errors',
-  ];
-  _eca_post_update_token_rename($tokenNames);
-}
-
-/**
- * Re-run the 2.0.0 post update hook.
- *
- * @see https://www.drupal.org/project/eca/issues/3460491
- */
-function eca_post_update_rename_tokens_2_0_1(): void {
-  eca_post_update_rename_tokens_2_0_0();
-}
diff --git a/eca.services.yml b/eca.services.yml
index 426dbccac..9fced6216 100644
--- a/eca.services.yml
+++ b/eca.services.yml
@@ -1,21 +1,31 @@
 services:
 
-  eca.memory_cache:
-    class: Drupal\Core\Cache\MemoryCache\MemoryCache
   eca.state:
     class: Drupal\eca\EcaState
     parent: state
-    arguments: ['@datetime.time']
+    arguments:
+      - '@datetime.time'
   eca.processor:
     class: Drupal\eca\Processor
-    arguments: ['@entity_type.manager', '@logger.channel.eca', '@event_dispatcher', '@plugin.manager.eca.event', '@state', '%eca.max_recursion_level%']
+    arguments:
+      - '@entity_type.manager'
+      - '@logger.channel.eca'
+      - '@event_dispatcher'
+      - '@plugin.manager.eca.event'
+      - '@state'
+      - '%eca.max_recursion_level%'
   logger.channel.eca:
     parent: logger.channel_base
-    arguments: ['eca']
+    arguments:
+      - 'eca'
   eca.configurable_logger_channel:
     class: Drupal\eca\ConfigurableLoggerChannel
     decorates: logger.channel.eca
-    arguments: ['eca', '@eca.configurable_logger_channel.inner', '@config.factory', '@module_handler']
+    arguments:
+      - 'eca'
+      - '@eca.configurable_logger_channel.inner'
+      - '@config.factory'
+      - '@module_handler'
 
   eca.dynamic_subscriber:
     class: Drupal\eca\EventSubscriber\DynamicSubscriber
@@ -34,32 +44,55 @@ services:
       - [setDecoratedActionManager, ['@plugin.manager.eca.action.inner']]
       - [setEntityTypeManager, ['@entity_type.manager']]
 
-  eca.service.modeller:
-    class: Drupal\eca\Service\Modellers
-    arguments: ['@entity_type.manager', '@plugin.manager.modeler_api.modeler', '@plugin.manager.eca.event',  '@eca.service.action',  '@eca.service.condition', '@logger.channel.eca', '@file_system', '@config.storage.export', '@config.factory']
   eca.service.action:
     class: Drupal\eca\Service\Actions
-    arguments: ['@plugin.manager.eca.action', '@logger.channel.eca', '@entity_type.manager', '@eca.token_services']
+    arguments:
+      - '@plugin.manager.eca.action'
+      - '@logger.channel.eca'
+      - '@entity_type.manager'
+      - '@eca.token_services'
   eca.service.condition:
     class: Drupal\eca\Service\Conditions
-    arguments: ['@plugin.manager.eca.condition', '@logger.channel.eca', '@entity_type.manager', '@language_manager', '@eca.service.token']
+    arguments:
+      - '@plugin.manager.eca.condition'
+      - '@logger.channel.eca'
+      - '@entity_type.manager'
+      - '@language_manager'
+      - '@eca.service.token'
+  eca.service.events:
+    class: Drupal\eca\Service\Events
+    arguments:
+      - '@plugin.manager.eca.event'
+      - '@logger.channel.eca'
   eca.service.yaml_parser:
     class: Drupal\eca\Service\YamlParser
-    arguments: ['@eca.token_services']
+    arguments:
+      - '@eca.token_services'
   eca.service.content_entity_types:
     class: Drupal\eca\Service\ContentEntityTypes
-    arguments: ['@entity_type.manager', '@entity_type.bundle.info']
+    arguments:
+      - '@entity_type.manager'
+      - '@entity_type.bundle.info'
   eca.service.dependency_calculation:
     class: Drupal\eca\Service\DependencyCalculation
-    arguments: ['@entity_type.manager', '@entity_type.bundle.info', '@entity_field.manager', '@eca.token_services', '@config.factory']
+    arguments:
+      - '@entity_type.manager'
+      - '@entity_type.bundle.info'
+      - '@entity_field.manager'
+      - '@eca.token_services'
+      - '@config.factory'
 
   eca.trigger_event:
     class: Drupal\eca\Event\TriggerEvent
-    arguments: ['@plugin.manager.eca.event', '@event_dispatcher']
+    arguments:
+      - '@plugin.manager.eca.event'
+      - '@event_dispatcher'
 
   eca.token_services:
     class: Drupal\eca\Token\TokenServices
-    arguments: ['@eca.service.token', '@token']
+    arguments:
+      - '@eca.service.token'
+      - '@token'
   eca.service.token:
     decorates: token
     parent: token
@@ -71,7 +104,9 @@ services:
       - { name: service_collector, tag: eca.token_data_provider, call: addTokenDataProvider }
   eca.execution.subscriber_parent:
     class: Drupal\eca\EventSubscriber\EcaExecutionSubscriberBase
-    arguments: ['@entity_type.manager', '@eca.service.token']
+    arguments:
+      - '@entity_type.manager'
+      - '@eca.service.token'
     abstract: true
   eca.execution.token_subscriber:
     class: Drupal\eca\EventSubscriber\EcaExecutionTokenSubscriber
@@ -100,7 +135,9 @@ services:
 
   eca.token_data.current_user:
     class: Drupal\eca\Token\CurrentUserDataProvider
-    arguments: ['@current_user', '@entity_type.manager']
+    arguments:
+      - '@current_user'
+      - '@entity_type.manager'
     tags:
       - { name: eca.token_data_provider, priority: -100 }
   eca.token_data.context:
@@ -108,19 +145,6 @@ services:
     tags:
       - { name: eca.token_data_provider, priority: -50 }
 
-  eca.update:
-    class: Drupal\eca\EcaUpdate
-    arguments: ['@entity_type.manager', '@eca.service.modeller', '@messenger', '@eca.service.modeller', '@eca.service.condition', '@eca.service.action']
-
-  eca.export.recipe:
-    class: Drupal\eca\Service\ExportRecipe
-    arguments:
-      - '@config.storage.export'
-      - '@file_system'
-      - '@extension.list.module'
-      - '@eca.service.modeller'
-      - '@messenger'
-
 parameters:
   eca.max_recursion_level: 1
   # Override following parameter as blank to disable documentation links at all.
diff --git a/modules/access/eca_access.info.yml b/modules/access/eca_access.info.yml
index 01067871e..772057d35 100644
--- a/modules/access/eca_access.info.yml
+++ b/modules/access/eca_access.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Access'
 type: module
 description: 'Access events, conditions and actions'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/access/eca_access.services.yml b/modules/access/eca_access.services.yml
index 3794b7661..e66324b8a 100644
--- a/modules/access/eca_access.services.yml
+++ b/modules/access/eca_access.services.yml
@@ -1,4 +1,5 @@
 services:
   eca_access.hook_handler:
     class: Drupal\eca_access\HookHandler
-    arguments: ['@eca.trigger_event']
+    arguments:
+      - '@eca.trigger_event'
diff --git a/modules/base/eca_base.info.yml b/modules/base/eca_base.info.yml
index 4182cf7b8..531f1be73 100644
--- a/modules/base/eca_base.info.yml
+++ b/modules/base/eca_base.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Base'
 type: module
 description: 'Base events, conditions and actions'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/base/eca_base.install b/modules/base/eca_base.install
deleted file mode 100644
index 14dc74ab4..000000000
--- a/modules/base/eca_base.install
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-/**
- * @file
- * ECA base install file.
- */
-
-/**
- * Update last cron run times for existing eca config entities.
- */
-function eca_base_update_8001(): void {
-  $state = \Drupal::service('eca.state');
-  $storage = \Drupal::entityTypeManager()->getStorage('eca');
-  /**
-   * @var \Drupal\eca\Entity\Eca $eca
-   */
-  foreach ($storage->loadMultiple() as $eca) {
-    foreach ($eca->getUsedEvents() as $event) {
-      if ($event->getPlugin()->getPluginId() === 'eca_base:eca_cron') {
-        $id = $event->getId();
-        $lastRun = $state->getTimestamp('cron-' . $id);
-        if (!$lastRun) {
-          $state->setTimestamp('cron-' . $id);
-        }
-      }
-    }
-  }
-}
diff --git a/modules/cache/eca_cache.info.yml b/modules/cache/eca_cache.info.yml
index 25de6438c..371290eca 100644
--- a/modules/cache/eca_cache.info.yml
+++ b/modules/cache/eca_cache.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Cache'
 type: module
 description: 'Cache actions for ECA.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/cache/eca_cache.services.yml b/modules/cache/eca_cache.services.yml
index 0a49dba57..d9b3fa23b 100644
--- a/modules/cache/eca_cache.services.yml
+++ b/modules/cache/eca_cache.services.yml
@@ -1,13 +1,15 @@
 services:
   cache.eca_memory:
     class: Drupal\Core\Cache\MemoryCache\MemoryCache
-    arguments: ['@datetime.time']
+    arguments:
+      - '@datetime.time'
   cache.eca_default:
     class: Drupal\Core\Cache\CacheBackendInterface
     tags:
       - { name: cache.bin }
     factory: ['@cache_factory', 'get']
-    arguments: [eca_default]
+    arguments:
+      - eca_default
   cache.eca_chained:
     class: Drupal\Core\Cache\BackendChain
     calls:
diff --git a/modules/cache/src/Plugin/Action/CacheInvalidate.php b/modules/cache/src/Plugin/Action/CacheInvalidate.php
index c30d665a4..b3bcb1f25 100644
--- a/modules/cache/src/Plugin/Action/CacheInvalidate.php
+++ b/modules/cache/src/Plugin/Action/CacheInvalidate.php
@@ -55,7 +55,7 @@ abstract class CacheInvalidate extends CacheActionBase {
     $tags = $this->getCacheTags();
 
     if (empty($tags)) {
-      $cache->invalidateAll();
+      $cache->deleteAll();
     }
     else {
       Cache::invalidateTags($tags);
diff --git a/modules/cache/src/Plugin/Action/RawCacheInvalidate.php b/modules/cache/src/Plugin/Action/RawCacheInvalidate.php
index 0b1c6bd5f..55672a806 100644
--- a/modules/cache/src/Plugin/Action/RawCacheInvalidate.php
+++ b/modules/cache/src/Plugin/Action/RawCacheInvalidate.php
@@ -35,7 +35,7 @@ class RawCacheInvalidate extends CacheInvalidate {
 
     if (empty($tags)) {
       foreach (Cache::getBins() as $bin) {
-        $bin->invalidateAll();
+        $bin->deleteAll();
       }
     }
     else {
diff --git a/modules/config/eca_config.info.yml b/modules/config/eca_config.info.yml
index 2c820045f..599a21b45 100644
--- a/modules/config/eca_config.info.yml
+++ b/modules/config/eca_config.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Config'
 type: module
 description: 'Config events.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/content/eca_content.info.yml b/modules/content/eca_content.info.yml
index 23db8e86c..948c3504f 100644
--- a/modules/content/eca_content.info.yml
+++ b/modules/content/eca_content.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Content'
 type: module
 description: 'Content entity events, conditions and actions.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/content/eca_content.services.yml b/modules/content/eca_content.services.yml
index 5547e9036..f522061bc 100644
--- a/modules/content/eca_content.services.yml
+++ b/modules/content/eca_content.services.yml
@@ -1,8 +1,16 @@
 services:
   eca_content.hook_handler:
     class: Drupal\eca_content\HookHandler
-    arguments: ['@eca.trigger_event', '@eca.service.content_entity_types']
+    arguments:
+      - '@eca.trigger_event'
+      - '@eca.service.content_entity_types'
 
   eca_content.service.entity_loader:
     class: Drupal\eca_content\Service\EntityLoader
-    arguments: ['@entity_type.manager', '@eca.token_services', '@string_translation', '@eca.service.yaml_parser', '@logger.channel.eca', '@language_manager']
+    arguments:
+      - '@entity_type.manager'
+      - '@eca.token_services'
+      - '@string_translation'
+      - '@eca.service.yaml_parser'
+      - '@logger.channel.eca'
+      - '@language_manager'
diff --git a/modules/development/eca_development.info.yml b/modules/development/eca_development.info.yml
index eecb1374c..019a6f1d8 100644
--- a/modules/development/eca_development.info.yml
+++ b/modules/development/eca_development.info.yml
@@ -1,7 +1,8 @@
 name: 'ECA Development'
 type: module
 description: 'Internal development services only, not for production sites.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 hidden: true
 dependencies:
   - eca:eca
+  - modeler_api:modeler_api
diff --git a/modules/development/src/Drush/Commands/DocsCommands.php b/modules/development/src/Drush/Commands/DocsCommands.php
index d6f59a2e7..d739316af 100644
--- a/modules/development/src/Drush/Commands/DocsCommands.php
+++ b/modules/development/src/Drush/Commands/DocsCommands.php
@@ -15,8 +15,9 @@ use Drupal\eca\Plugin\ECA\Condition\ConditionInterface;
 use Drupal\eca\Plugin\ECA\Event\EventInterface;
 use Drupal\eca\Service\Actions;
 use Drupal\eca\Service\Conditions;
-use Drupal\eca\Service\ExportRecipe;
-use Drupal\eca\Service\Modellers;
+use Drupal\eca\Service\Events;
+use Drupal\modeler_api\Api;
+use Drupal\modeler_api\ExportRecipe;
 use Drush\Attributes\Command;
 use Drush\Attributes\Usage;
 use Drush\Commands\DrushCommands;
@@ -68,11 +69,11 @@ final class DocsCommands extends DrushCommands {
   protected Conditions $conditionServices;
 
   /**
-   * ECA Modeller service.
+   * The ECA events services.
    *
-   * @var \Drupal\eca\Service\Modellers
+   * @var \Drupal\eca\Service\Events
    */
-  protected Modellers $modellerServices;
+  protected Events $eventsServices;
 
   /**
    * File system service.
@@ -117,9 +118,16 @@ final class DocsCommands extends DrushCommands {
   protected TwigEnvironment $twigEnvironment;
 
   /**
-   * The export as recipe service.
+   * The modeler API service.
    *
-   * @var \Drupal\eca\Service\ExportRecipe
+   * @var \Drupal\modeler_api\Api
+   */
+  protected Api $modelerApi;
+
+  /**
+   * The export recipe service.
+   *
+   * @var \Drupal\modeler_api\ExportRecipe
    */
   protected ExportRecipe $exportRecipe;
 
@@ -132,29 +140,32 @@ final class DocsCommands extends DrushCommands {
    *   The ECA action services.
    * @param \Drupal\eca\Service\Conditions $conditionServices
    *   The ECA condition services.
-   * @param \Drupal\eca\Service\Modellers $modellerServices
-   *   The ECA modeller services.
+   * @param \Drupal\eca\Service\Events $eventsServices
+   *   The ECA events services.
    * @param \Drupal\Core\File\FileSystemInterface $fileSystem
    *   The file system service.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
    *   The module handler service.
    * @param \Drupal\Core\Extension\ModuleExtensionList $moduleExtensionList
    *   The module extension list service.
-   * @param \Drupal\eca\Service\ExportRecipe $exportRecipe
-   *   The export as recipe service.
+   * @param \Drupal\modeler_api\Api $modelerApi
+   *   The modeler API service.
+   * @param \Drupal\modeler_api\ExportRecipe $exportRecipe
+   *   The export recipe service.
    */
-  public function __construct(EntityTypeManagerInterface $entityTypeManager, Actions $actionServices, Conditions $conditionServices, Modellers $modellerServices, FileSystemInterface $fileSystem, ModuleHandlerInterface $moduleHandler, ModuleExtensionList $moduleExtensionList, ExportRecipe $exportRecipe) {
+  public function __construct(EntityTypeManagerInterface $entityTypeManager, Actions $actionServices, Conditions $conditionServices, Events $eventsServices, FileSystemInterface $fileSystem, ModuleHandlerInterface $moduleHandler, ModuleExtensionList $moduleExtensionList, Api $modelerApi, ExportRecipe $exportRecipe) {
     parent::__construct();
     $this->entityTypeManager = $entityTypeManager;
     $this->actionServices = $actionServices;
     $this->conditionServices = $conditionServices;
-    $this->modellerServices = $modellerServices;
+    $this->eventsServices = $eventsServices;
     $this->fileSystem = $fileSystem;
     $this->moduleHandler = $moduleHandler;
     $this->twigLoader = new TwigLoader();
     $this->twigEnvironment = new TwigEnvironment($this->twigLoader);
     $this->moduleExtensionList = $moduleExtensionList;
     $this->moduleExtensions = $moduleExtensionList->getList();
+    $this->modelerApi = $modelerApi;
     $this->exportRecipe = $exportRecipe;
   }
 
@@ -172,11 +183,12 @@ final class DocsCommands extends DrushCommands {
       $container->get('entity_type.manager'),
       $container->get('eca.service.action'),
       $container->get('eca.service.condition'),
-      $container->get('eca.service.modeller'),
+      $container->get('eca.service.events'),
       $container->get('file_system'),
       $container->get('module_handler'),
       $container->get('extension.list.module'),
-      $container->get('eca.export.recipe'),
+      $container->get('modeler_api.service'),
+      $container->get('modeler_api.export.recipe'),
     );
   }
 
@@ -190,7 +202,7 @@ final class DocsCommands extends DrushCommands {
     @$this->fileSystem->mkdir('../mkdocs/include/plugins', NULL, TRUE);
     $this->toc['0-ECA']['0-placeholder'] = 'plugins/eca/index.md';
 
-    foreach ($this->modellerServices->events() as $event) {
+    foreach ($this->eventsServices->events() as $event) {
       $this->pluginDoc($event);
     }
     foreach ($this->conditionServices->conditions() as $condition) {
@@ -216,7 +228,8 @@ final class DocsCommands extends DrushCommands {
       ->getStorage('eca')
       ->loadMultiple() as $eca) {
       $this->modelDoc($eca);
-      $this->exportRecipe->doExport($eca, $this->exportRecipe->defaultName($eca), ExportRecipe::DEFAULT_NAMESPACE, '../recipes/' . $eca->id());
+      $owner = $this->modelerApi->findOwner($eca);
+      $this->exportRecipe->doExport($owner, $eca, $this->exportRecipe->defaultName($eca), ExportRecipe::DEFAULT_NAMESPACE, '../recipes/' . $eca->id());
     }
     $this->updateToc('library');
   }
@@ -433,12 +446,11 @@ final class DocsCommands extends DrushCommands {
    *   The ECA config entity for which documentation should be created.
    */
   private function modelDoc(Eca $eca): void {
-    $model = $eca->getModel();
-    $modeller = $eca->getModeller();
-    if ($modeller === NULL) {
+    $modeler = $this->modelerApi->findOwner($eca);
+    if ($modeler === NULL) {
       return;
     }
-    $tags = $model->getTags();
+    $tags = $modeler->getTags($eca);
     if (empty($tags) || (count($tags) === 1 && ($tags[0] === 'untagged' || $tags[0] === ''))) {
       // Do not export models without at least one tag.
       return;
@@ -449,14 +461,14 @@ final class DocsCommands extends DrushCommands {
       'id' => str_replace([':', ' '], '_', mb_strtolower($eca->label())),
       'label' => $eca->label(),
       'version' => $eca->get('version'),
-      'changelog' => $modeller->getChangelog(),
+      'changelog' => $modeler->getChangelog($eca),
       'main_tag' => $tags[0],
       'tags' => $tags,
-      'documentation' => $model->getDocumentation(),
+      'documentation' => $modeler->getDocumentation($eca),
       'events' => [],
       'conditions' => [],
       'actions' => [],
-      'model_filename' => $modeller->getPluginId() . '-' . $eca->id(),
+      'model_filename' => $modeler->getPluginId() . '-' . $eca->id(),
       'library_path' => 'library/' . $tags[0],
       'namespace' => ExportRecipe::DEFAULT_NAMESPACE,
     ];
@@ -499,10 +511,10 @@ final class DocsCommands extends DrushCommands {
     @$this->fileSystem->mkdir('../mkdocs/docs/' . $values['library_path'] . '/' . $values['id'], NULL, TRUE);
 
     $archiveFileName = '../mkdocs/docs/' . $values['library_path'] . '/' . $values['id'] . '/' . $values['model_filename'] . '.tar.gz';
-    $values['dependencies'] = $this->modellerServices->exportArchive($eca, $archiveFileName);
+    $values['dependencies'] = $this->modelerApi->exportArchive($eca, $archiveFileName);
 
     file_put_contents('../mkdocs/docs/' . $values['library_path'] . '/' . $values['id'] . '.md', $this->render(__DIR__ . '/../../../templates/docs/library.md.twig', $values));
-    file_put_contents('../mkdocs/docs/' . $values['library_path'] . '/' . $values['id'] . '/' . $values['model_filename'] . '.xml', $model->getModeldata());
+    file_put_contents('../mkdocs/docs/' . $values['library_path'] . '/' . $values['id'] . '/' . $values['model_filename'] . '.xml', $modeler->getModeldata($eca));
 
     $this->toc[$values['main_tag']][$values['label']] = $values['library_path'] . '/' . $values['id'] . '.md';
   }
diff --git a/modules/endpoint/eca_endpoint.info.yml b/modules/endpoint/eca_endpoint.info.yml
index bfb69da5c..eb9bf9961 100644
--- a/modules/endpoint/eca_endpoint.info.yml
+++ b/modules/endpoint/eca_endpoint.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Endpoint'
 type: module
 description: 'A URL endpoint whose contents are served by ECA.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/file/eca_file.info.yml b/modules/file/eca_file.info.yml
index e71be308e..da5a92769 100644
--- a/modules/file/eca_file.info.yml
+++ b/modules/file/eca_file.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA File'
 type: module
 description: 'Events, conditions and actions related fo files and file entities.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/form/eca_form.info.yml b/modules/form/eca_form.info.yml
index 0d61e5f85..f0010fb84 100644
--- a/modules/form/eca_form.info.yml
+++ b/modules/form/eca_form.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Form'
 type: module
 description: 'Form API events, conditions and actions.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/form/eca_form.install b/modules/form/eca_form.install
index ec0c0b707..36795c678 100644
--- a/modules/form/eca_form.install
+++ b/modules/form/eca_form.install
@@ -5,78 +5,9 @@
  * Install file for the ECA Form submodule.
  */
 
-use Drupal\eca_modeller_bpmn\ModellerBpmnBase;
-
 /**
  * Implements hook_install().
  */
 function eca_form_install(): void {
   module_set_weight('eca_form', 1);
 }
-
-/**
- * Update token "current-form" to "current_form".
- */
-function eca_form_update_8001(): void {
-  $storage = \Drupal::entityTypeManager()->getStorage('eca');
-  /**
-   * @var \Drupal\eca\Entity\Eca $eca
-   */
-  foreach ($storage->loadMultiple() as $eca) {
-    $model = $eca->getModel();
-    if ($eca->getModeller() instanceof ModellerBpmnBase) {
-      $xml = $model->getModeldata();
-      $xml = str_replace('[current-form', '[current_form', $xml);
-      $model
-        ->setModeldata($xml)
-        ->save();
-    }
-    else {
-      foreach (['event', 'condition', 'action'] as $type) {
-        $items = $eca->get($type . 's') ?? [];
-        foreach ($items as &$item) {
-          foreach ($item['configuration'] as $key => $value) {
-            $item[$key] = str_replace('[current-form', '[current_form', $value);
-          }
-        }
-        $eca->set($type . 's', $items);
-      }
-      $eca->save();
-    }
-  }
-}
-
-/**
- * Increases the module weight.
- *
- * This action is necessary to improve compatibility with other projects.
- * Please synchronize your configuration after applying this update.
- */
-function eca_form_update_8002(): void {
-  module_set_weight('eca_form', 1);
-}
-
-/**
- * Create default value for "group" configuration in form fieldsets.
- */
-function eca_form_update_8003(): void {
-  $storage = \Drupal::entityTypeManager()->getStorage('eca');
-  /** @var \Drupal\eca\Entity\Eca $eca */
-  foreach ($storage->loadMultiple() as $eca) {
-    $save_eca = FALSE;
-    $items = $eca->get('actions') ?? [];
-    foreach ($items as &$item) {
-      if ($item['plugin'] !== 'eca_form_add_group_element') {
-        continue;
-      }
-      if (!isset($item['configuration']['group'])) {
-        $item['configuration']['group'] = '';
-        $save_eca = TRUE;
-      }
-    }
-    if ($save_eca) {
-      $eca->set('actions', $items);
-      $eca->save();
-    }
-  }
-}
diff --git a/modules/form/eca_form.services.yml b/modules/form/eca_form.services.yml
index 229d94a7b..9be57df8f 100644
--- a/modules/form/eca_form.services.yml
+++ b/modules/form/eca_form.services.yml
@@ -2,7 +2,8 @@ services:
 
   eca_form.hook_handler:
     class: Drupal\eca_form\HookHandler
-    arguments: ['@eca.trigger_event']
+    arguments:
+      - '@eca.trigger_event'
 
   eca.token_data.current_form:
     class: Drupal\eca_form\Token\CurrentFormDataProvider
diff --git a/modules/language/eca_language.info.yml b/modules/language/eca_language.info.yml
index 929c9860e..0850069e8 100644
--- a/modules/language/eca_language.info.yml
+++ b/modules/language/eca_language.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Language'
 type: module
 description: 'Advanced language handling within ECA.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - drupal:language
diff --git a/modules/log/eca_log.info.yml b/modules/log/eca_log.info.yml
index 6383e5643..85c221391 100644
--- a/modules/log/eca_log.info.yml
+++ b/modules/log/eca_log.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Log'
 type: module
 description: 'Events and actions for Drupal log messages.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/log/eca_log.services.yml b/modules/log/eca_log.services.yml
index 4e979aeb3..d63fc9ee2 100644
--- a/modules/log/eca_log.services.yml
+++ b/modules/log/eca_log.services.yml
@@ -1,6 +1,7 @@
 services:
   logger.eca_log:
     class: Drupal\eca_log\Logger\EcaLog
-    arguments: ['@event_dispatcher']
+    arguments:
+      - '@event_dispatcher'
     tags:
       - { name: logger }
diff --git a/modules/migrate/eca_migrate.info.yml b/modules/migrate/eca_migrate.info.yml
index ba3fb5dba..90254fe9c 100644
--- a/modules/migrate/eca_migrate.info.yml
+++ b/modules/migrate/eca_migrate.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Migrate'
 type: module
 description: 'Migrate events.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - drupal:migrate
diff --git a/modules/miscellaneous/eca_misc.info.yml b/modules/miscellaneous/eca_misc.info.yml
index 4b8a7ae4c..70d08b1b3 100644
--- a/modules/miscellaneous/eca_misc.info.yml
+++ b/modules/miscellaneous/eca_misc.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Miscellaneous'
 type: module
 description: 'Miscellaneous events and conditions from Drupal core and the kernel.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/miscellaneous/eca_misc.services.yml b/modules/miscellaneous/eca_misc.services.yml
index d958eb329..d2df68fb6 100644
--- a/modules/miscellaneous/eca_misc.services.yml
+++ b/modules/miscellaneous/eca_misc.services.yml
@@ -1,6 +1,7 @@
 services:
   eca_misc.subscriber:
     class: Drupal\eca_misc\EventSubscriber\ExceptionSubscriber
-    arguments: ['@event_dispatcher']
+    arguments:
+      - '@event_dispatcher'
     tags:
       - { name: event_subscriber }
diff --git a/modules/modeller_bpmn/data/empty.bpmn b/modules/modeller_bpmn/data/empty.bpmn
deleted file mode 100644
index 5f8df1698..000000000
--- a/modules/modeller_bpmn/data/empty.bpmn
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:qa="http://some-company/schema/bpmn/qa" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn">
-  <bpmn2:process id="IDPLACEHOLDER" isExecutable="true"/>
-  <bpmndi:BPMNDiagram id="SIDPLACEHOLDER1">
-    <bpmndi:BPMNPlane id="SIDPLACEHOLDER2" bpmnElement="IDPLACEHOLDER"/>
-  </bpmndi:BPMNDiagram>
-</bpmn2:definitions>
diff --git a/modules/modeller_bpmn/eca_modeller_bpmn.info.yml b/modules/modeller_bpmn/eca_modeller_bpmn.info.yml
deleted file mode 100644
index d5f4489b7..000000000
--- a/modules/modeller_bpmn/eca_modeller_bpmn.info.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-name: 'ECA BPMN'
-type: module
-description: 'Common functionality for all BPMN based modeller implementations.'
-core_version_requirement: ^10.3 || ^11
-package: ECA
-dependencies:
-  - eca:eca_ui
diff --git a/modules/modeller_bpmn/src/ModellerBpmnBase.php b/modules/modeller_bpmn/src/ModellerBpmnBase.php
deleted file mode 100644
index 32b0f724c..000000000
--- a/modules/modeller_bpmn/src/ModellerBpmnBase.php
+++ /dev/null
@@ -1,1019 +0,0 @@
-<?php
-
-namespace Drupal\eca_modeller_bpmn;
-
-use Drupal\Component\Plugin\PluginInspectionInterface;
-use Drupal\Component\Utility\Random;
-use Drupal\Core\Action\ActionInterface;
-use Drupal\Core\Entity\EntityStorageException;
-use Drupal\Core\Form\FormState;
-use Drupal\eca\Entity\Eca;
-use Drupal\eca\Plugin\ECA\Condition\ConditionInterface;
-use Drupal\eca\Plugin\ECA\EcaPluginBase;
-use Drupal\eca\Plugin\ECA\Event\EventInterface;
-use Drupal\modeler_api\Plugin\modeler_api\ModelerBase;
-use Drupal\eca\Service\Modellers;
-use Drupal\eca_ui\Service\TokenBrowserService;
-use Drupal\modeler_api\Entity\Model;
-use Drupal\modeler_api\Plugin\ModelerInterface;
-use Drupal\modeler_api\TargetInterface;
-use Mtownsend\XmlToArray\XmlToArray;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Abstract class for BPMN modellers.
- *
- * Providing generic functionality which is similar to all such modellers.
- */
-abstract class ModellerBpmnBase extends ModelerBase {
-
-  /**
-   * The model data as an XML string.
-   *
-   * @var string
-   */
-  protected string $modeldata;
-
-  /**
-   * The unserialized model data as an XML object.
-   *
-   * @var array
-   */
-  protected array $xmlModel;
-
-  /**
-   * The filename of the BPMN model, if saved in the file system.
-   *
-   * @var string
-   */
-  protected string $filename;
-
-  /**
-   * The DOM of the XML data for detailed processing.
-   *
-   * @var \DOMDocument
-   */
-  protected \DOMDocument $doc;
-
-  /**
-   * The DOM Xpath object for DOM queries.
-   *
-   * @var \DOMXPath
-   */
-  protected \DOMXPath $xpath;
-
-  /**
-   * ECA token browser service.
-   *
-   * @var \Drupal\eca_ui\Service\TokenBrowserService
-   */
-  protected TokenBrowserService $tokenBrowserService;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
-    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
-    $instance->tokenBrowserService = $container->get('eca_ui.service.token_browser');
-    return $instance;
-  }
-
-  /**
-   * Prepares the data for further updates processes.
-   *
-   * @param string $data
-   *   The serialized data of this model.
-   */
-  protected function prepareForUpdate(string $data): void {
-    $this->modeldata = $data;
-    $this->xmlModel = XmlToArray::convert($this->modeldata);
-    $this->doc = new \DOMDocument();
-    $this->doc->loadXML($this->modeldata);
-    $this->xpath = new \DOMXPath($this->doc);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function prepareForExport(): void {
-    $this->prepareForUpdate($this->eca->getModel()->getModeldata());
-  }
-
-  /**
-   * Return the XML namespace prefix used by the BPMN modeller.
-   *
-   * @return string
-   *   The namespace prefix used by the current modeller.
-   */
-  protected function xmlNsPrefix(): string {
-    return '';
-  }
-
-  /**
-   * Prepares data for a new and empty BPMN model.
-   *
-   * @return string
-   *   The model data.
-   */
-  public function prepareEmptyModelData(string &$id): string {
-    $id = $this->generateId();
-    $emptyBpmn = file_get_contents($this->extensionPathResolver->getPath('module', 'eca_modeller_bpmn') . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'empty.bpmn');
-    return str_replace([
-      'SIDPLACEHOLDER1',
-      'SIDPLACEHOLDER2',
-      'IDPLACEHOLDER',
-    ], [
-      'sid-' . $this->uuid->generate(),
-      'sid-' . $this->uuid->generate(),
-      $id,
-    ], $emptyBpmn);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function generateId(): string {
-    $random = new Random();
-    return 'Process_' . $random->name(7);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function createNewModel(string $id, string $model_data, ?string $filename = NULL, bool $save = FALSE): TargetInterface {
-    $eca = Eca::create(['id' => mb_strtolower($id)]);
-    $eca->getModel()->setModeldata($model_data);
-    $this->setConfigEntity($eca);
-    if ($save) {
-      $this->save($model_data, $filename);
-      $eca = $this->getEca();
-    }
-    return $eca;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(string $data, ?string $filename = NULL, ?bool $status = NULL): bool {
-    $this->prepareForUpdate($data);
-    $this->filename = $filename ?? '';
-    if ($status !== NULL) {
-      $this->xmlModel[$this->xmlNsPrefix() . 'process']['@attributes']['isExecutable'] = $status ? 'true' : 'false';
-    }
-    return $this->modellerServices->saveModel($this);
-  }
-
-  /**
-   * {@inheritdoc}
-   *
-   * @throws \DOMException
-   */
-  public function updateModel(Model $model): bool {
-    $this->prepareForUpdate($this->eca->getModel()->getModeldata());
-    $changed = FALSE;
-    $idxExtension = $this->xmlNsPrefix() . 'extensionElements';
-    foreach ($this->getTemplates() as $template) {
-      foreach ($template['appliesTo'] as $type) {
-        switch ($type) {
-          case 'bpmn:Event':
-            $objects = $this->getStartEvents();
-            break;
-
-          case 'bpmn:SequenceFlow':
-            $objects = $this->getSequenceFlows();
-            break;
-
-          case 'bpmn:Task':
-            $objects = $this->getTasks();
-            break;
-
-          default:
-            $objects = [];
-
-        }
-        foreach ($objects as $object) {
-          if (isset($object['@attributes']['modelerTemplate']) && $template['id'] === $object['@attributes']['modelerTemplate']) {
-            $fields = $this->findFields($object[$idxExtension]);
-            $id = $object['@attributes']['id'];
-            /**
-             * @var \DOMElement|null $element
-             */
-            $element = $this->xpath->query("//*[@id='$id']")->item(0);
-            if ($element) {
-              /**
-               * @var \DOMElement|null $extensions
-               */
-              $extensions = $this->xpath->query("//*[@id='$id']/$idxExtension")
-                ->item(0);
-              if (!$extensions) {
-                $node = $this->doc->createElement($idxExtension);
-                $extensions = $element->appendChild($node);
-              }
-              foreach ($template['properties'] as $property) {
-                switch ($property['binding']['type']) {
-                  case 'camunda:property':
-                    if ($this->findProperty($object[$idxExtension], $property['binding']['name']) !== $property['value']) {
-                      $element->setAttribute($property['binding']['name'], $property['value']);
-                      $changed = TRUE;
-                    }
-                    break;
-
-                  case 'camunda:field':
-                    if (isset($fields[$property['binding']['name']])) {
-                      // Field exists, remove it from the list.
-                      unset($fields[$property['binding']['name']]);
-                    }
-                    else {
-                      $fieldNode = $this->doc->createElement('camunda:field');
-                      $fieldNode->setAttribute('name', $property['binding']['name']);
-                      $valueNode = $this->doc->createElement('camunda:string');
-                      $valueNode->textContent = $property['value'];
-                      $fieldNode->appendChild($valueNode);
-                      $extensions->appendChild($fieldNode);
-                      $changed = TRUE;
-                    }
-                    break;
-                }
-              }
-              // Remove remaining fields from the model.
-              foreach ($fields as $name => $value) {
-                /**
-                 * @var \DOMElement $fieldElement
-                 */
-                if ($fieldElement = $this->xpath->query("//*[@id='$id']/$idxExtension/camunda:field[@name='$name']")
-                  ->item(0)) {
-                  $extensions->removeChild($fieldElement);
-                  $changed = TRUE;
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-    if ($changed) {
-      $this->prepareForUpdate($this->doc->saveXML());
-      $model->setModeldata($this->modeldata);
-    }
-    return $changed;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function enable(): ModelerInterface {
-    $this->prepareForUpdate($this->eca->getModel()->getModeldata());
-    /** @var \DOMElement|null $element */
-    $element = $this->xpath->query("//*[@id='{$this->getId()}']")->item(0);
-    if ($element) {
-      $element->setAttribute('isExecutable', 'true');
-    }
-    try {
-      $this->save($this->doc->saveXML());
-    }
-    catch (\LogicException | EntityStorageException $e) {
-      $this->messenger->addError($e->getMessage());
-    }
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function disable(): ModelerInterface {
-    $this->prepareForUpdate($this->eca->getModel()->getModeldata());
-    /** @var \DOMElement|null $element */
-    $element = $this->xpath->query("//*[@id='{$this->getId()}']")->item(0);
-    if ($element) {
-      $element->setAttribute('isExecutable', 'false');
-    }
-    try {
-      $this->save($this->doc->saveXML());
-    }
-    catch (\LogicException | EntityStorageException $e) {
-      $this->messenger->addError($e->getMessage());
-    }
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function clone(): ?TargetInterface {
-    $this->prepareForUpdate($this->eca->getModel()->getModeldata());
-    $id = $this->generateId();
-    /** @var \DOMElement|null $element */
-    $element = $this->xpath->query("//*[@id='{$this->getId()}']")->item(0);
-    if ($element) {
-      $element->setAttribute('id', $id);
-      $element->setAttribute('name', $this->getLabel() . ' (' . $this->t('clone') . ')');
-    }
-    try {
-      return $this->createNewModel($id, $this->doc->saveXML(), NULL, TRUE);
-    }
-    catch (\LogicException | EntityStorageException $e) {
-      $this->messenger->addError($e->getMessage());
-    }
-    return NULL;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFilename(): string {
-    return $this->filename;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setModeldata(string $data): ModelerInterface {
-    $this->prepareForUpdate($data);
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getModeldata(): string {
-    return $this->modeldata;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getId(): string {
-    return $this->xmlModel[$this->xmlNsPrefix() . 'process']['@attributes']['id'];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getLabel(): string {
-    return $this->xmlModel[$this->xmlNsPrefix() . 'process']['@attributes']['name'] ?? 'noname';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTags(): array {
-    $process = $this->xmlNsPrefix() . 'process';
-    $extensions = $this->xmlNsPrefix() . 'extensionElements';
-    $tags = isset($this->xmlModel[$process][$extensions]) ?
-      explode(',', $this->findProperty($this->xmlModel[$process][$extensions], 'Tags')) :
-      [];
-    array_walk($tags, static function (&$item) {
-      $item = trim((string) $item);
-    });
-    return $tags;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getChangelog(): array {
-    $this->prepareForExport();
-    $process = $this->xmlNsPrefix() . 'process';
-    $extensions = $this->xmlNsPrefix() . 'extensionElements';
-    $changelog = [];
-    if (isset($this->xmlModel[$process][$extensions])) {
-      $v = 1;
-      while ($item = $this->findProperty($this->xmlModel[$process][$extensions], 'Changelog v' . $v)) {
-        $changelog['v' . $v] = $item;
-        $v++;
-      }
-    }
-    return $changelog;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDocumentation(): string {
-    return $this->xmlModel[$this->xmlNsPrefix() . 'process'][$this->xmlNsPrefix() . 'documentation'] ?? '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getStatus(): bool {
-    return mb_strtolower($this->xmlModel[$this->xmlNsPrefix() . 'process']['@attributes']['isExecutable'] ?? 'true') === 'true';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getVersion(): string {
-    return $this->xmlModel[$this->xmlNsPrefix() . 'process']['@attributes']['versionTag'] ?? '';
-  }
-
-  /**
-   * Returns all the startEvent (events) objects from the XML model.
-   *
-   * @return array
-   *   The list of all start events in the model data.
-   */
-  private function getStartEvents(): array {
-    $events = $this->xmlModel[$this->xmlNsPrefix() . 'process'][$this->xmlNsPrefix() . 'startEvent'] ?? [];
-    if (isset($events['@attributes'])) {
-      return [$events];
-    }
-    return $events;
-  }
-
-  /**
-   * Returns all the task objects (actions) from the XML model.
-   *
-   * @return array
-   *   The list of all tasks in the model data.
-   */
-  private function getTasks(): array {
-    $actions = $this->xmlModel[$this->xmlNsPrefix() . 'process'][$this->xmlNsPrefix() . 'task'] ?? [];
-    if (isset($actions['@attributes'])) {
-      return [$actions];
-    }
-    return $actions;
-  }
-
-  /**
-   * Returns all the sequenceFlow objects (condition) from the XML model.
-   *
-   * @return array
-   *   The list of all sequence flows in the model data.
-   */
-  private function getSequenceFlows(): array {
-    $conditions = $this->xmlModel[$this->xmlNsPrefix() . 'process'][$this->xmlNsPrefix() . 'sequenceFlow'] ?? [];
-    if (isset($conditions['@attributes'])) {
-      return [$conditions];
-    }
-    return $conditions;
-  }
-
-  /**
-   * Returns all the gateway objects from the XML model.
-   *
-   * @return array
-   *   The list of all gateways in the model data.
-   */
-  private function getGateways(): array {
-    $types = [
-      $this->conditionServices::GATEWAY_TYPE_EXCLUSIVE => 'exclusiveGateway',
-      $this->conditionServices::GATEWAY_TYPE_PARALLEL => 'parallelGateway',
-      $this->conditionServices::GATEWAY_TYPE_INCLUSIVE => 'inclusiveGateway',
-      $this->conditionServices::GATEWAY_TYPE_COMPLEX => 'complexGateway',
-      $this->conditionServices::GATEWAY_TYPE_EVENTBASED => 'eventBasedGateway',
-    ];
-    $gateways = [];
-    foreach ($types as $key => $type) {
-      $objects = $this->xmlModel[$this->xmlNsPrefix() . 'process'][$this->xmlNsPrefix() . $type] ?? [];
-      if (isset($objects['@attributes'])) {
-        $objects = [$objects];
-      }
-      foreach ($objects as $object) {
-        $object['type'] = $key;
-        $gateways[] = $object;
-      }
-    }
-    return $gateways;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function readComponents(TargetInterface $eca): ModelerInterface {
-    $this->eca = $eca;
-    $this->eca->resetComponents();
-    $idxExtension = $this->xmlNsPrefix() . 'extensionElements';
-
-    $this->hasError = FALSE;
-    $flow = [];
-    foreach ($this->getSequenceFlows() as $sequenceFlow) {
-      if (isset($sequenceFlow[$idxExtension])) {
-        $pluginId = $this->findProperty($sequenceFlow[$idxExtension], 'pluginid');
-        $condition = $this->findAttribute($sequenceFlow, 'id');
-        if (!empty($pluginId) && !empty($condition)) {
-          if (!$eca->addCondition(
-            $condition,
-            $pluginId,
-            $this->findFields($sequenceFlow[$idxExtension])
-          )) {
-            $this->hasError = TRUE;
-          }
-        }
-        else {
-          $condition = '';
-        }
-      }
-      else {
-        $condition = '';
-      }
-      $flow[$this->findAttribute($sequenceFlow, 'sourceRef')][] = [
-        'id' => $this->findAttribute($sequenceFlow, 'targetRef'),
-        'condition' => $condition,
-      ];
-    }
-
-    foreach ($this->getGateways() as $gateway) {
-      $gatewayId = $this->findAttribute($gateway, 'id');
-      $eca->addGateway($gatewayId, $gateway['type'], $flow[$gatewayId] ?? []);
-    }
-
-    foreach ($this->getStartEvents() as $startEvent) {
-      $extension = $startEvent[$idxExtension] ?? [];
-      $pluginId = $this->findProperty($extension, 'pluginid');
-      if (empty($pluginId)) {
-        continue;
-      }
-      if (!$eca->addEvent(
-        $this->findAttribute($startEvent, 'id'),
-        $pluginId,
-        $this->findAttribute($startEvent, 'name'),
-        $this->findFields($extension),
-        $flow[$this->findAttribute($startEvent, 'id')] ?? []
-      )) {
-        $this->hasError = TRUE;
-      }
-    }
-
-    foreach ($this->getTasks() as $task) {
-      $extension = $task[$idxExtension] ?? [];
-      $pluginId = $this->findProperty($extension, 'pluginid');
-      if (empty($pluginId)) {
-        continue;
-      }
-      if (!$eca->addAction(
-        $this->findAttribute($task, 'id'),
-        $pluginId,
-        $this->findAttribute($task, 'name'),
-        $this->findFields($extension),
-        $flow[$this->findAttribute($task, 'id')] ?? []
-      )) {
-        $this->hasError = TRUE;
-      }
-    }
-
-    return $this;
-  }
-
-  /**
-   * Prepares the plugin's configuration form and catches errors.
-   *
-   * @param \Drupal\eca\Plugin\ECA\Event\EventInterface|\Drupal\eca\Plugin\ECA\Condition\ConditionInterface|\Drupal\Core\Action\ActionInterface $plugin
-   *   The plugin.
-   *
-   * @return array
-   *   The configuration form.
-   */
-  protected function buildConfigurationForm(EventInterface|ConditionInterface|ActionInterface $plugin): array {
-    $form_state = new FormState();
-    try {
-      if ($plugin instanceof ActionInterface) {
-        $form = $this->actionServices->getConfigurationForm($plugin, $form_state) ?? [
-          'error_message' => [
-            '#type' => 'checkbox',
-            '#title' => $this->t('Error in configuration form!!!'),
-            '#description' => $this->t('Details can be found in the Drupal error log.'),
-          ],
-        ];
-      }
-      else {
-        $form = $plugin->buildConfigurationForm([], $form_state);
-      }
-    }
-    catch (\Throwable $ex) {
-      // @todo Replace this with some markup when that's supported by bpmn_io.
-      $form['error_message'] = [
-        '#type' => 'checkbox',
-        '#title' => $this->t('Error in configuration form!!!'),
-        '#description' => $ex->getMessage(),
-      ];
-    }
-    return $form;
-  }
-
-  /**
-   * Returns all the templates for the modeller UI.
-   *
-   * This includes templates for events, conditions and actions.
-   *
-   * @return array
-   *   The list of all templates.
-   */
-  protected function getTemplates(): array {
-    $templates = [];
-    foreach ($this->modellerServices->events() as $event) {
-      $templates[] = $this->properties($event, 'event', 'bpmn:Event', $this->buildConfigurationForm($event));
-    }
-    foreach ($this->conditionServices->conditions() as $condition) {
-      $templates[] = $this->properties($condition, 'condition', 'bpmn:SequenceFlow', $this->buildConfigurationForm($condition));
-    }
-    foreach ($this->actionServices->actions() as $action) {
-      $templates[] = $this->properties($action, 'action', 'bpmn:Task', $this->buildConfigurationForm($action));
-    }
-    return $templates;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function exportTemplates(): ModelerInterface {
-    // Nothing to do by default.
-    return $this;
-  }
-
-  /**
-   * Helper function to build a template for an event, condition or action.
-   *
-   * @param \Drupal\Component\Plugin\PluginInspectionInterface $plugin
-   *   The event, condition or action plugin for which the template should
-   *   be build.
-   * @param string $plugin_type
-   *   The string identifying the plugin type, which is one of event, condition
-   *   or action.
-   * @param string $applies_to
-   *   The string to tell the modeller, to which object type the template will
-   *   apply. Valid values are "bpmn:Event", "bpmn:sequenceFlow" or "bpmn:task".
-   * @param array $form
-   *   An array containing the configuration form of the plugin.
-   *
-   * @return array
-   *   The completed template for BPMN modellers for the given plugin and its
-   *   fields.
-   */
-  protected function properties(PluginInspectionInterface $plugin, string $plugin_type, string $applies_to, array $form): array {
-    $properties = [
-      [
-        'label' => 'Plugin ID',
-        'type' => 'Hidden',
-        'value' => $plugin->getPluginId(),
-        'binding' => [
-          'type' => 'camunda:property',
-          'name' => 'pluginid',
-        ],
-      ],
-    ];
-    $extraDescriptions = [];
-    foreach ($this->prepareConfigFields($form, $extraDescriptions) as $field) {
-      if (!isset($field['value'])) {
-        $value = '';
-      }
-      elseif (is_scalar($field['value'])) {
-        $value = (string) $field['value'];
-      }
-      elseif (is_object($field['value']) && method_exists($field['value'], '__toString')) {
-        $value = $field['value']->__toString();
-      }
-      else {
-        $this->logger->error('Found config field %field in %plugin with non-supported value.', [
-          '%field' => $field['label'],
-          '%plugin' => $plugin->getPluginId(),
-        ]);
-        $value = '';
-      }
-      $property = [
-        'label' => $field['label'],
-        'type' => $field['type'],
-        'value' => $value,
-        'editable' => $field['editable'] ?? TRUE,
-        'binding' => [
-          'type' => 'camunda:field',
-          'name' => $field['name'],
-        ],
-      ];
-      if (!empty($field['required'])) {
-        $property['constraints']['notEmpty'] = TRUE;
-      }
-      if (isset($field['description'])) {
-        $property['description'] = (string) $field['description'];
-      }
-      if (isset($field['extras'])) {
-        /* @noinspection SlowArrayOperationsInLoopInspection */
-        $property = array_merge_recursive($property, $field['extras']);
-      }
-      $properties[] = $property;
-    }
-    $extraDescriptions = array_unique($extraDescriptions);
-    $pluginDefinition = $plugin->getPluginDefinition();
-    $template = [
-      'name' => (string) $pluginDefinition['label'],
-      'id' => 'org.drupal.' . $plugin_type . '.' . $plugin->getPluginId(),
-      'category' => [
-        'id' => $pluginDefinition['provider'],
-        'name' => EcaPluginBase::$modules[$pluginDefinition['provider']],
-      ],
-      'appliesTo' => [$applies_to],
-      'properties' => $properties,
-    ];
-    if (isset($pluginDefinition['description']) || $extraDescriptions) {
-      $template['description'] = (string) ($pluginDefinition['description'] ?? '') . ' ' . implode(' ', $extraDescriptions);
-    }
-    if ($doc_url = $this->pluginDocUrl($plugin, $plugin_type)) {
-      $template['documentationRef'] = $doc_url;
-    }
-    return $template;
-  }
-
-  /**
-   * Builds the URL to the offsite documentation for the given plugin.
-   *
-   * @param \Drupal\Component\Plugin\PluginInspectionInterface $plugin
-   *   The plugin for which the documentation URL should be build.
-   * @param string $plugin_type
-   *   The string identifying the plugin type, which is one of event, condition
-   *   or action.
-   *
-   * @return string|null
-   *   The URL to the offsite documentation, or NULL if no URL was generated.
-   */
-  protected function pluginDocUrl(PluginInspectionInterface $plugin, string $plugin_type): ?string {
-    if (!($domain = $this->documentationDomain)) {
-      return NULL;
-    }
-    $provider = $plugin->getPluginDefinition()['provider'];
-    $basePath = (mb_strpos($provider, 'eca_') === 0) ?
-      str_replace('_', '/', $provider) :
-      $provider;
-    return sprintf('%s/plugins/%s/%ss/%s/', $domain, $basePath, $plugin_type, str_replace([':'], '_', $plugin->getPluginId()));
-  }
-
-  /**
-   * Return a property of a given BPMN element.
-   *
-   * @param array $element
-   *   The BPMN element from which the property should be returned.
-   * @param string $property_name
-   *   The name of the property in the BPMN element.
-   *
-   * @return string
-   *   The property's value, default to an empty string.
-   */
-  protected function findProperty(array $element, string $property_name): string {
-    if (isset($element['camunda:properties']['camunda:property'])) {
-      $elements = isset($element['camunda:properties']['camunda:property']['@attributes']) ?
-        [$element['camunda:properties']['camunda:property']] :
-        $element['camunda:properties']['camunda:property'];
-      foreach ($elements as $child) {
-        if ($child['@attributes']['name'] === $property_name) {
-          return $child['@attributes']['value'];
-        }
-      }
-    }
-    return '';
-  }
-
-  /**
-   * Return an attribute of a given BPMN element.
-   *
-   * @param array $element
-   *   The BPMN element from which the attribute should be returned.
-   * @param string $attribute_name
-   *   The name of the attribute in the BPMN element.
-   *
-   * @return string
-   *   The attribute's value, default to an empty string.
-   */
-  protected function findAttribute(array $element, string $attribute_name): string {
-    return $element['@attributes'][$attribute_name] ?? '';
-  }
-
-  /**
-   * Return all the field values of a given BPMN element.
-   *
-   * @param array $element
-   *   The BPMN element from which the field values should be returned.
-   *
-   * @return array
-   *   An array containing all the field values, keyed by the field name.
-   */
-  protected function findFields(array $element): array {
-    $fields = [];
-    if (isset($element['camunda:field'])) {
-      $elements = isset($element['camunda:field']['@attributes']) ? [$element['camunda:field']] : $element['camunda:field'];
-      foreach ($elements as $child) {
-        $fields[$child['@attributes']['name']] = isset($child['camunda:string']) && is_string($child['camunda:string']) ? $child['camunda:string'] : '';
-      }
-    }
-    return $fields;
-  }
-
-  /**
-   * Helper function preparing config fields for events, conditions and actions.
-   *
-   * @param array $form
-   *   The array to which the fields should be added.
-   * @param array $extraDescriptions
-   *   An array receiving all markup "fields" which can be displayed separately
-   *   in the UI.
-   *
-   * @return array
-   *   The prepared config fields.
-   */
-  protected function prepareConfigFields(array $form, array &$extraDescriptions): array {
-    // @todo Add support for nested form fields like e.g. in container/fieldset.
-    $fields = [];
-    foreach ($form as $key => $definition) {
-      if (!is_array($definition)) {
-        continue;
-      }
-      $label = $definition['#title'] ?? Modellers::convertKeyToLabel($key);
-      $description = $definition['#description'] ?? NULL;
-      $value = $definition['#default_value'] ?? '';
-      $weight = $definition['#weight'] ?? 0;
-      $type = 'String';
-      $required = $definition['#required'] ?? FALSE;
-      // @todo Map to more proper property types of bpmn-js.
-      switch ($definition['#type'] ?? 'markup') {
-
-        case 'hidden':
-        case 'actions':
-          // The modellers can't handle these types, so we ignore them for
-          // the templates.
-          continue 2;
-
-        case 'item':
-        case 'markup':
-        case 'container':
-          if (isset($definition['#markup'])) {
-            $extraDescriptions[] = (string) $definition['#markup'];
-          }
-          continue 2;
-
-        case 'textarea':
-          $type = 'Text';
-          break;
-
-        case 'checkbox':
-          $fields[] = $this->checkbox($key, $label, $weight, $description, $value);
-          continue 2;
-
-        case 'checkboxes':
-        case 'radios':
-        case 'select':
-          if (!is_array($value)) {
-            $options = $this->normalizeOptions(form_select_options($definition));
-            $fields[] = $this->optionsField($key, $label, $weight, $description, $options, (string) $value, $required);
-            continue 2;
-          }
-          break;
-
-      }
-      if (is_bool($value)) {
-        $fields[] = $this->checkbox($key, $label, $weight, $description, $value);
-        continue;
-      }
-      if (is_array($value)) {
-        $value = implode(',', $value);
-      }
-      $field = [
-        'name' => $key,
-        'label' => $label,
-        'weight' => $weight,
-        'type' => $type,
-        'value' => $value,
-        'required' => $required,
-      ];
-      if ($description !== NULL) {
-        $field['description'] = $description;
-      }
-      $fields[] = $field;
-    }
-
-    // Sort fields by weight.
-    usort($fields, static function ($f1, $f2) {
-      $l1 = (int) $f1['weight'];
-      $l2 = (int) $f2['weight'];
-      if ($l1 < $l2) {
-        return -1;
-      }
-      if ($l1 > $l2) {
-        return 1;
-      }
-      return 0;
-    });
-
-    return $fields;
-  }
-
-  /**
-   * Normalizes an option list into a flat list of keys and labels.
-   *
-   * This can be called recursively, e.g. for nested option groups.
-   *
-   * @param array $formApiOptions
-   *   The list of options.
-   * @param string $prefix
-   *   An optional prefix which will be prepended to the label.
-   *
-   * @return array
-   *   The flat option list.
-   */
-  protected function normalizeOptions(array $formApiOptions, string $prefix = ''): array {
-    $options = [];
-    foreach ($formApiOptions as $formApiOption) {
-      switch ($formApiOption['type']) {
-        case 'option':
-          $options[$formApiOption['value']] = $prefix . $formApiOption['label'];
-          break;
-
-        case 'optgroup':
-          $options += $this->normalizeOptions($formApiOption['options'], $formApiOption['label'] . ': ');
-          break;
-
-      }
-    }
-    return $options;
-  }
-
-  /**
-   * Prepares a field with options as a drop-down.
-   *
-   * @param string $name
-   *   The field name.
-   * @param string $label
-   *   The field label.
-   * @param int $weight
-   *   The field weight for sorting.
-   * @param string|null $description
-   *   The optional field description.
-   * @param array $options
-   *   Key/value list of available options.
-   * @param string $value
-   *   The default value for the field.
-   * @param bool $required
-   *   The setting, if this field is required to be filled by the user.
-   *
-   * @return array
-   *   Prepared option field.
-   */
-  protected function optionsField(string $name, string $label, int $weight, ?string $description, array $options, string $value, bool $required = FALSE): array {
-    $choices = [];
-    foreach ($options as $optionValue => $optionName) {
-      $choices[] = [
-        'name' => (string) $optionName,
-        'value' => (string) $optionValue,
-      ];
-      if ($required && $value === '') {
-        $value = (string) $optionValue;
-      }
-    }
-    $field = [
-      'name' => $name,
-      'label' => $label,
-      'weight' => $weight,
-      'type' => 'Dropdown',
-      'value' => $value,
-      'required' => $required,
-      'extras' => [
-        'choices' => $choices,
-      ],
-    ];
-    if ($description !== NULL) {
-      $field['description'] = $description;
-    }
-    return $field;
-  }
-
-  /**
-   * Prepares a field as a checkbox.
-   *
-   * @param string $name
-   *   The field name.
-   * @param string $label
-   *   The field label.
-   * @param int $weight
-   *   The field weight for sorting.
-   * @param string|null $description
-   *   The optional field description.
-   * @param bool $value
-   *   The default value for the field.
-   *
-   * @return array
-   *   Prepared checkbox field.
-   */
-  protected function checkbox(string $name, string $label, int $weight, ?string $description, bool $value): array {
-    $field = [
-      'name' => $name,
-      'label' => $label,
-      'weight' => $weight,
-      'type' => 'Dropdown',
-      'value' => $value ? 'yes' : 'no',
-      'extras' => [
-        'choices' => [
-          [
-            'name' => 'no',
-            'value' => 'no',
-          ],
-          [
-            'name' => 'yes',
-            'value' => 'yes',
-          ],
-        ],
-      ],
-    ];
-    if ($description !== NULL) {
-      $field['description'] = $description;
-    }
-    return $field;
-  }
-
-}
diff --git a/modules/modeller_bpmn/tests/modules/plugin_config_validation/config/install/eca.eca.eca_test_0011.yml b/modules/modeller_bpmn/tests/modules/plugin_config_validation/config/install/eca.eca.eca_test_0011.yml
deleted file mode 100644
index 75a0b87f5..000000000
--- a/modules/modeller_bpmn/tests/modules/plugin_config_validation/config/install/eca.eca.eca_test_0011.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  module:
-    - eca_base
-    - eca_test_model_plugin_config_validation
-id: eca_test_0011
-modeller: dummy
-label: 'Validate plugin configuration'
-version: 1.0.0
-weight: null
-events:
-  custom:
-    plugin: 'eca_base:eca_custom'
-    label: 'Custom event'
-    configuration:
-      event_id: ''
-    successors:
-      -
-        id: Dummy
-        condition: ''
-conditions: {  }
-gateways: {  }
-actions:
-  Dummy:
-    plugin: eca_test_model_plugin_config_validation
-    label: Dummy
-    configuration:
-      dummy: correct
-    successors: {  }
diff --git a/modules/modeller_bpmn/tests/modules/plugin_config_validation/config/install/eca.model.eca_test_0011.yml b/modules/modeller_bpmn/tests/modules/plugin_config_validation/config/install/eca.model.eca_test_0011.yml
deleted file mode 100644
index b36819931..000000000
--- a/modules/modeller_bpmn/tests/modules/plugin_config_validation/config/install/eca.model.eca_test_0011.yml
+++ /dev/null
@@ -1,58 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  config:
-    - eca.eca.eca_test_0011
-id: eca_test_0011
-label: 'Validate plugin configuration'
-tags:
-  - untagged
-documentation: ''
-filename: ''
-modeldata: |
-  <?xml version="1.0" encoding="UTF-8"?>
-  <bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:qa="http://some-company/schema/bpmn/qa" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
-    <bpmn2:process id="eca_test_0011" name="Validate plugin configuration" isExecutable="true" camunda:versionTag="1.0.0">
-      <bpmn2:startEvent id="custom" name="Custom event" camunda:modelerTemplate="org.drupal.event.eca_base:eca_custom">
-        <bpmn2:extensionElements>
-          <camunda:properties>
-            <camunda:property name="pluginid" value="eca_base:eca_custom"/>
-          </camunda:properties>
-          <camunda:field name="event_id">
-            <camunda:string/>
-          </camunda:field>
-        </bpmn2:extensionElements>
-        <bpmn2:outgoing>Flow_1opgjzh</bpmn2:outgoing>
-      </bpmn2:startEvent>
-      <bpmn2:task id="Dummy" name="Dummy" camunda:modelerTemplate="org.drupal.action.eca_test_model_plugin_config_validation">
-        <bpmn2:extensionElements>
-          <camunda:properties>
-            <camunda:property name="pluginid" value="eca_test_model_plugin_config_validation"/>
-          </camunda:properties>
-          <camunda:field name="dummy">
-            <camunda:string>correct</camunda:string>
-          </camunda:field>
-        </bpmn2:extensionElements>
-        <bpmn2:incoming>Flow_1opgjzh</bpmn2:incoming>
-      </bpmn2:task>
-      <bpmn2:sequenceFlow id="Flow_1opgjzh" sourceRef="custom" targetRef="Dummy"/>
-    </bpmn2:process>
-    <bpmndi:BPMNDiagram id="sid-0a76329c-a777-4f6b-8942-89034a8dddd8">
-      <bpmndi:BPMNPlane id="sid-95105220-93cf-4cbf-ad85-491d56ec5629" bpmnElement="eca_test_0011">
-        <bpmndi:BPMNEdge id="Flow_1opgjzh_di" bpmnElement="Flow_1opgjzh">
-          <di:waypoint x="228" y="190"/>
-          <di:waypoint x="290" y="190"/>
-        </bpmndi:BPMNEdge>
-        <bpmndi:BPMNShape id="Event_0zvt8vz_di" bpmnElement="custom">
-          <dc:Bounds x="192" y="172" width="36" height="36"/>
-          <bpmndi:BPMNLabel>
-            <dc:Bounds x="177" y="215" width="68" height="14"/>
-          </bpmndi:BPMNLabel>
-        </bpmndi:BPMNShape>
-        <bpmndi:BPMNShape id="Activity_0rt55gb_di" bpmnElement="Dummy">
-          <dc:Bounds x="290" y="150" width="100" height="80"/>
-          <bpmndi:BPMNLabel/>
-        </bpmndi:BPMNShape>
-      </bpmndi:BPMNPlane>
-    </bpmndi:BPMNDiagram>
-  </bpmn2:definitions>
diff --git a/modules/modeller_bpmn/tests/modules/plugin_config_validation/eca_test_model_plugin_config_validation.info.yml b/modules/modeller_bpmn/tests/modules/plugin_config_validation/eca_test_model_plugin_config_validation.info.yml
deleted file mode 100644
index 342e6ca4c..000000000
--- a/modules/modeller_bpmn/tests/modules/plugin_config_validation/eca_test_model_plugin_config_validation.info.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-name: 'ECA model test: Validate plugin configuration'
-type: module
-description: 'This module contains a model test for saving a new entity.'
-package: Testing
-dependencies:
-  - eca:eca_base
-
-config_devel:
-  - eca.eca.eca_test_0011
-  - eca.model.eca_test_0011
diff --git a/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/Action/DummyValidation.php b/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/Action/DummyValidation.php
deleted file mode 100644
index d939be452..000000000
--- a/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/Action/DummyValidation.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-namespace Drupal\eca_test_model_plugin_config_validation\Plugin\Action;
-
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\eca\Plugin\Action\ConfigurableActionBase;
-
-/**
- * Dummy action to test config validation.
- *
- * @Action(
- *   id = "eca_test_model_plugin_config_validation",
- *   label = @Translation("Test: Dummy action to validate configuration"),
- *   nodocs = true
- * )
- */
-class DummyValidation extends ConfigurableActionBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute(mixed $entity = NULL): void {
-    // Nothing to do!
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function defaultConfiguration(): array {
-    return [
-      'dummy' => '',
-    ] + parent::defaultConfiguration();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
-    $form['dummy'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Dummy'),
-      '#default_value' => $this->configuration['dummy'],
-      '#weight' => -10,
-    ];
-    return parent::buildConfigurationForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void {
-    parent::validateConfigurationForm($form, $form_state);
-    $dummy = $form_state->getValue('dummy');
-    if ($dummy === 'wrong') {
-      $form_state->setErrorByName('dummy', 'This value is not allowed.');
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {
-    $this->configuration['dummy'] = $form_state->getValue('dummy');
-    parent::submitConfigurationForm($form, $form_state);
-  }
-
-}
diff --git a/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php b/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php
deleted file mode 100644
index 8ea7d0020..000000000
--- a/modules/modeller_bpmn/tests/modules/plugin_config_validation/src/Plugin/ECA/Modeller/DummyModeller.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-namespace Drupal\eca_test_model_plugin_config_validation\Plugin\ECA\Modeller;
-
-use Drupal\eca_modeller_bpmn\ModellerBpmnBase;
-use Drupal\modeler_api\Plugin\ModelerInterface;
-
-/**
- * Plugin implementation of the ECA Modeller.
- *
- * @Modeler(
- *   id = "dummy",
- *   nodocs = true
- * )
- */
-class DummyModeller extends ModellerBpmnBase {
-
-  /**
-   * A list of all templates.
-   *
-   * @var array
-   */
-  protected array $templates;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function xmlNsPrefix(): string {
-    return 'bpmn2:';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function exportTemplates(): ModelerInterface {
-    $this->templates = $this->getTemplates();
-    return parent::exportTemplates();
-  }
-
-  /**
-   * Prepares and returns the templates for testing.
-   *
-   * @return array
-   *   The list of templates.
-   */
-  public function getTemplatesForTesting(): array {
-    $this->exportTemplates();
-    return $this->templates;
-  }
-
-}
diff --git a/modules/modeller_bpmn/tests/src/Kernel/BpmnBaseModellerTest.php b/modules/modeller_bpmn/tests/src/Kernel/BpmnBaseModellerTest.php
deleted file mode 100644
index e71e1d41c..000000000
--- a/modules/modeller_bpmn/tests/src/Kernel/BpmnBaseModellerTest.php
+++ /dev/null
@@ -1,252 +0,0 @@
-<?php
-
-namespace Drupal\Tests\eca_modeller_bpmn\Kernel\Model;
-
-use Drupal\Tests\eca\Kernel\Model\Base;
-use Drupal\eca\Entity\Eca;
-use Drupal\eca_test_model_plugin_config_validation\Plugin\ECA\Modeller\DummyModeller;
-use Symfony\Component\HttpFoundation\BinaryFileResponse;
-
-/**
- * Testing the BPMN base modeller.
- *
- * @group eca
- * @group eca_modeller_bpmn
- */
-class BpmnBaseModellerTest extends Base {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected static $modules = [
-    'eca_base',
-    'eca_modeller_bpmn',
-    'eca_test_model_plugin_config_validation',
-    'eca_ui',
-  ];
-
-  /**
-   * The dummy BPMN modeller.
-   *
-   * @var \Drupal\eca_test_model_plugin_config_validation\Plugin\ECA\Modeller\DummyModeller|null
-   */
-  protected ?DummyModeller $modeller;
-
-  /**
-   * The dummy ECA config entity.
-   *
-   * @var \Drupal\eca\Entity\Eca|null
-   */
-  protected ?Eca $eca;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-    $this->installEntitySchema('user');
-    /** @var \Drupal\modeler_api\Plugin\ModelerPluginManager $modelManager */
-    $modelManager = \Drupal::service('plugin.manager.modeler_api.modeler');
-    /* @noinspection PhpFieldAssignmentTypeMismatchInspection */
-    $this->modeller = $modelManager->createInstance('dummy');
-    $this->eca = \Drupal::entityTypeManager()->getStorage('eca')->load('eca_test_0011');
-  }
-
-  /**
-   * Test ModellerBpmnBase::createNewModel.
-   */
-  public function testCreateNewModel(): void {
-    // Test with an new, empty model.
-    $id = '';
-    $modelData = $this->modeller->prepareEmptyModelData($id);
-    $eca = $this->modeller->createNewModel($id, $modelData);
-    $this->assertNotSame($id, $eca->id(), "ID of empty ECA should not be $id");
-    $this->assertSame(mb_strtolower($id), $eca->id(), 'ID of empty ECA should be' . mb_strtolower($id));
-    $this->assertTrue($eca->isNew(), 'Empty ECA model should be new.');
-    $this->modeller->save($modelData);
-    $eca = $this->modeller->getEca();
-    $this->assertEmpty($eca->getUsedEvents(), 'Empty ECA should not contain any events.');
-
-    // Test with the data of the existing dummy model.
-    $id = $this->modeller->generateId();
-    $modelData = str_replace('eca_test_0011', $id, $this->eca->getModel()->getModeldata());
-    $eca = $this->modeller->createNewModel($id, $modelData);
-    $this->assertNotSame($id, $eca->id(), "ID of ECA should not be $id");
-    $this->assertSame(mb_strtolower($id), $eca->id(), 'ID of ECA should be' . mb_strtolower($id));
-    $this->assertTrue($eca->isNew(), 'ECA model should be new.');
-    $this->modeller->save($modelData);
-    $eca = $this->modeller->getEca();
-    $this->assertNotEmpty($eca->getUsedEvents(), 'ECA should contain events.');
-  }
-
-  /**
-   * Test ModellerBpmnBase::updateModel.
-   */
-  public function testUpdateModel(): void {
-    $modeller = $this->eca->getModeller();
-    $changed = $modeller->updateModel($this->eca->getModel());
-    $this->assertFalse($changed, 'Model should not have changed during update.');
-  }
-
-  /**
-   * Test ModellerBpmnBase::enable and ModellerBpmnBase::disable.
-   */
-  public function testEnableDisableModel(): void {
-    $modeller = $this->eca->getModeller();
-    $this->assertTrue($modeller->getEca()->status(), 'ECA should initially be enabled.');
-    $modeller->disable();
-    $this->assertFalse($modeller->getEca()->status(), 'ECA should then be disabled.');
-    $modeller->enable();
-    $this->assertTrue($modeller->getEca()->status(), 'ECA should finally be enabled again.');
-  }
-
-  /**
-   * Test ModellerBpmnBase::clone.
-   */
-  public function testCloneModel(): void {
-    $modeller = $this->eca->getModeller();
-    $this->assertSame($this->eca->id(), $modeller->getEca()->id(), 'ECA should be the same.');
-    $eca = $modeller->clone();
-    $this->assertNotNull($eca, 'ECA should not be NULL.');
-    $this->assertNotSame($this->eca->id(), $eca->id(), 'Cloned ECA should not be the same as original ECA.');
-
-    $orgEvents = $this->eca->getUsedEvents();
-    $newEvents = $eca->getUsedEvents();
-    $this->assertCount(count($orgEvents), $newEvents, 'Both ECA entities should have the same number of events.');
-    foreach ($orgEvents as $id => $event) {
-      $this->assertArrayHasKey($id, $newEvents, "Cloned ECA should have the $id event.");
-      $this->assertEquals($event->getConfiguration()['event_id'], $newEvents[$id]->getConfiguration()['event_id'], "Configured event if of the $id events should be the same.");
-    }
-  }
-
-  /**
-   * Test ModellerBpmnBase::export.
-   */
-  public function testExportModel(): void {
-    $modeller = $this->eca->getModeller();
-    $export = $modeller->export();
-    $this->assertInstanceOf(BinaryFileResponse::class, $export, 'Export should be a binary response.');
-    $this->assertEquals('gz', $export->getFile()->getExtension(), "Export file extension should be gz.");
-  }
-
-  /**
-   * Test ModellerBpmnBase::setModeldata.
-   */
-  public function testSetModelData(): void {
-    $modeller = $this->eca->getModeller();
-    $id = $this->modeller->generateId();
-    $modelData = str_replace('eca_test_0011', $id, $this->eca->getModel()->getModeldata());
-    $modeller->setModeldata($modelData);
-    $this->assertEquals($id, $modeller->getId(), "New ID should be $id.");
-  }
-
-  /**
-   * Test the templates.
-   */
-  public function testTemplates(): void {
-    $templates = $this->modeller->getTemplatesForTesting();
-    $this->assertIsArray($templates, 'Templates should be an array');
-    foreach ($templates as $template) {
-      foreach ($template['properties'] as $property) {
-        if ($property['type'] === 'Dropdown') {
-          if (count($property['choices']) === 2 &&
-            in_array($property['choices'][0]['name'], ['yes', 'no'], TRUE)) {
-            $name = $property['binding']['name'];
-            $label = $property['label'];
-            $this->assertEquals(self::getExpectedCheckbox($name, $label, $property['value'], $property['description'] ?? ''),
-              $property, "Checkbox $name for plugin $label should be properly prepared.");
-          }
-          else {
-            $name = $property['binding']['name'];
-            $label = $property['label'];
-            $this->assertEquals(self::getExpectedOptionFields($name, $label, $property['value'], $property['choices'], $property['description'] ?? '', $property['constraints'] ?? NULL),
-              $property, "Option list $name for plugin $label should be properly prepared.");
-          }
-        }
-      }
-    }
-  }
-
-  /**
-   * Gets the expected option fields.
-   *
-   * @param string $name
-   *   The name of the field.
-   * @param string $label
-   *   The label of the field.
-   * @param string $value
-   *   The value of the field.
-   * @param array $choices
-   *   The available options for the field.
-   * @param string $description
-   *   The optional description.
-   * @param array|null $constraints
-   *   The optional constraints.
-   *
-   * @return array
-   *   The expected option field definition.
-   */
-  private static function getExpectedOptionFields(string $name, string $label, string $value, array $choices, string $description, ?array $constraints = NULL): array {
-    $options = [
-      'label' => $label,
-      'type' => 'Dropdown',
-      'value' => $value,
-      'editable' => TRUE,
-      'binding' => [
-        'type' => 'camunda:field',
-        'name' => $name,
-      ],
-      'choices' => $choices,
-    ];
-    if (!empty($description)) {
-      $options['description'] = $description;
-    }
-    if ($constraints !== NULL) {
-      $options['constraints'] = $constraints;
-    }
-    return $options;
-  }
-
-  /**
-   * Gets the expected checkbox.
-   *
-   * @param string $name
-   *   The name of the checkbox.
-   * @param string $label
-   *   The label of the checkbox.
-   * @param string $value
-   *   The condition of the checkbox.
-   * @param string $description
-   *   The optional description.
-   *
-   * @return array
-   *   The expected checkbox field definition.
-   */
-  private static function getExpectedCheckbox(string $name, string $label, string $value, string $description): array {
-    $checkbox = [
-      'label' => $label,
-      'type' => 'Dropdown',
-      'value' => $value,
-      'editable' => TRUE,
-      'binding' => [
-        'type' => 'camunda:field',
-        'name' => $name,
-      ],
-      'choices' => [
-        [
-          'name' => 'no',
-          'value' => 'no',
-        ],
-        [
-          'name' => 'yes',
-          'value' => 'yes',
-        ],
-      ],
-    ];
-    if (!empty($description)) {
-      $checkbox['description'] = $description;
-    }
-    return $checkbox;
-  }
-
-}
diff --git a/modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php b/modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php
deleted file mode 100644
index dbd0fa797..000000000
--- a/modules/modeller_bpmn/tests/src/Kernel/PluginConfigValidationTest.php
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-namespace Drupal\Tests\eca_modeller_bpmn\Kernel\Model;
-
-use Drupal\Tests\eca\Kernel\Model\Base;
-
-/**
- * Model test for saving an ECA entity with config validation.
- *
- * @group eca
- * @group eca_model
- * @group eca_modeller_bpmn
- */
-class PluginConfigValidationTest extends Base {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected static $modules = [
-    'eca_base',
-    'eca_modeller_bpmn',
-    'eca_test_model_plugin_config_validation',
-    'eca_ui',
-  ];
-
-  /**
-   * Tests the saving of an ECA entity and its validation.
-   */
-  public function testPluginConfigValidation(): void {
-    /** @var \Drupal\modeler_api\Plugin\ModelerPluginManager $modelManager */
-    $modelManager = \Drupal::service('plugin.manager.modeler_api.modeler');
-    /** @var \Drupal\eca_test_model_plugin_config_validation\Plugin\ECA\Modeller\DummyModeller $modeller */
-    $modeller = $modelManager->createInstance('dummy');
-    /** @var \Drupal\modeler_api\Entity\Model $model */
-    $model = \Drupal::entityTypeManager()->getStorage('modeler_api_model')->load('eca_test_0011');
-    $data = $model->getModeldata();
-
-    $fieldOrigin = '<camunda:string>correct</camunda:string>';
-    $fieldWrong = '<camunda:string>wrong</camunda:string>';
-    $fieldCorrect = '<camunda:string>my test value</camunda:string>';
-
-    // Test that the model won't be saved with the config value "wrong".
-    $wrongData = str_replace($fieldOrigin, $fieldWrong, $data);
-    $modeller->save($wrongData);
-
-    $this->assertErrorMessages([
-      'action "Test: Dummy action to validate configuration" (Dummy): This value is not allowed.',
-    ]);
-    /** @var \Drupal\modeler_api\Entity\Model $model */
-    $eca = \Drupal::entityTypeManager()->getStorage('eca')->load('eca_test_0011');
-    $actions = $eca->get('actions');
-    $this->assertSame('correct', $actions['Dummy']['configuration']['dummy'], 'The config value "correct" should not have changed.');
-
-    // Test that the model will be saved with the custom value "my test value".
-    $correctData = str_replace($fieldOrigin, $fieldCorrect, $data);
-    $modeller->save($correctData);
-
-    $this->assertErrorMessages([]);
-    $eca = \Drupal::entityTypeManager()->getStorage('eca')->load('eca_test_0011');
-    $actions = $eca->get('actions');
-    $this->assertSame('my test value', $actions['Dummy']['configuration']['dummy'], 'The config value "correct" should have changed to "my test value".');
-  }
-
-}
diff --git a/modules/project_browser/eca_project_browser.services.yml b/modules/project_browser/eca_project_browser.services.yml
index 154bcb797..99218f14e 100644
--- a/modules/project_browser/eca_project_browser.services.yml
+++ b/modules/project_browser/eca_project_browser.services.yml
@@ -1,4 +1,5 @@
 services:
   eca_project_browser.hook_handler:
     class: Drupal\eca_project_browser\HookHandler
-    arguments: ['@eca.trigger_event']
+    arguments:
+       - '@eca.trigger_event'
diff --git a/modules/queue/eca_queue.info.yml b/modules/queue/eca_queue.info.yml
index a9278f1ee..75346e81f 100644
--- a/modules/queue/eca_queue.info.yml
+++ b/modules/queue/eca_queue.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Queue'
 type: module
 description: 'Events, conditions and actions for queued operations'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/render/eca_render.info.yml b/modules/render/eca_render.info.yml
index 608088a69..6ce535dab 100644
--- a/modules/render/eca_render.info.yml
+++ b/modules/render/eca_render.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Render'
 type: module
 description: 'Rendering capabilities for ECA, such as blocks and links.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/render/src/Plugin/ECA/Event/RenderEvent.php b/modules/render/src/Plugin/ECA/Event/RenderEvent.php
index 060fb5764..f5d731cf6 100644
--- a/modules/render/src/Plugin/ECA/Event/RenderEvent.php
+++ b/modules/render/src/Plugin/ECA/Event/RenderEvent.php
@@ -505,7 +505,7 @@ class RenderEvent extends EventBase implements PluginUsageInterface {
       $this->entityFieldManager->clearCachedFieldDefinitions();
     }
     foreach ($this->cacheBackends as $cache) {
-      $cache->invalidateAll();
+      $cache->deleteAll();
     }
   }
 
diff --git a/modules/ui/eca_ui.info.yml b/modules/ui/eca_ui.info.yml
index c6f009198..3a9ae7c17 100644
--- a/modules/ui/eca_ui.info.yml
+++ b/modules/ui/eca_ui.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA UI'
 type: module
 description: 'Provides a user interface for managing ECA models.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 configure: entity.eca.collection
 dependencies:
diff --git a/modules/ui/eca_ui.module b/modules/ui/eca_ui.module
deleted file mode 100644
index 97edc703e..000000000
--- a/modules/ui/eca_ui.module
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-
-/**
- * @file
- * ECA UI module file.
- */
-
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Url;
-use Drupal\eca\Entity\Eca;
-use Drupal\eca\Event\Tag;
-use Drupal\eca_ui\Entity\AccessControlHandler;
-use Drupal\eca_ui\Entity\ListBuilder;
-use Drupal\eca_ui\Form\EcaDeleteForm;
-
-/**
- * Implements hook_help().
- */
-function eca_ui_help(string $route_name): string {
-  if ($route_name === 'help.page.eca') {
-    /** @var \Drupal\eca\PluginManager\Event $event_plugin_manager */
-    $event_plugin_manager = \Drupal::service('plugin.manager.eca.event');
-    $available_event_tags = Tag::getTags();
-
-    $output = '<h3>' . t('About') . '</h3>';
-    $output .= '<p>' . t('The <a href=":project">ECA</a> module provides a processor that gets triggered for every Drupal event. It validates these events against the models (event - condition - action), which are stored in config, and processes them. ECA leverages existing components of Drupal core, i.e. events and actions. For more details and help, see the <a href=":online">online documentation</a>.', [
-      ':online' => 'https://www.drupal.org/docs/contributed-modules/eca-event-condition-action',
-      ':project' => 'https://www.drupal.org/project/eca',
-    ]) . '</p>';
-    $output .= '<p>' . t('ECA makes no decision about the user interface which may be required for building the (business process) models (a.k.a. rules). Instead, it provides a plugin manager with an interface to easily integrate existing tools, that already do that pretty well. Here is a list of integrated modellers:<ul><li><a href=":bpmn_io">BPMN.iO</a>: javascript based implementation, integrated into the Drupal admin UI</li><li><a href=":camunda">Camunda</a>: desktop client</li></ul>', [
-      ':bpmn_io' => 'https://www.drupal.org/project/bpmn_io',
-      ':camunda' => 'https://www.drupal.org/project/camunda',
-    ]) . '</p>';
-    $output .= '<h3>' . t('Available events') . '</h3>';
-    $output .= '<p>' . t('You can react on available events within an <a href=":config_ui">ECA configuration</a>.', [
-      ':config_ui' => '/admin/config/workflow/eca',
-    ]) . '</p>';
-    $output .= '<dl>';
-    $output .= '<dt>' . t('All available events to react on are shown below.') . '</dt>';
-    $output .= '<dd><ul>';
-
-    foreach ($event_plugin_manager->getDefinitions() as $definition) {
-      $tags_info = [];
-      $defined_tags = $definition['tags'] ?? 0;
-      foreach ($available_event_tags as $event_tag => $event_label) {
-        if ($event_tag & $defined_tags) {
-          $tags_info[] = $event_label;
-        }
-      }
-      $output .= '<li>';
-      $output .= '<strong>' . $definition['label'] . '</strong>';
-      if ($tags_info) {
-        $output .= ' <em>(' . t('Characteristics: @tags', ['@tags' => implode(', ', $tags_info)]) . ')</em>';
-      }
-      $output .= '</li>';
-    }
-
-    $output .= '</ul></dd>';
-    $output .= '</dl>';
-    return $output;
-  }
-  return '';
-}
-
-/**
- * Implements hook_entity_type_build().
- */
-function eca_ui_entity_type_build(array &$entity_types): void {
-  /**
-   * @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types
-   */
-  $entity_types['eca']
-    ->setAccessClass(AccessControlHandler::class)
-    ->setListBuilderClass(ListBuilder::class)
-    ->setFormClass('delete', EcaDeleteForm::class)
-    ->setLinkTemplate('edit-form', '/admin/config//workflow/eca/{eca}/edit')
-    ->setLinkTemplate('delete-form', '/admin/config/workflow/eca/{eca}/delete')
-    ->setLinkTemplate('collection', '/admin/config/workflow/eca');
-}
-
-/**
- * Implements hook_entity_operation().
- */
-function eca_ui_entity_operation(EntityInterface $entity): array {
-  $operations = [];
-  if ($entity instanceof Eca) {
-    if (!$entity->status()) {
-      $operations['enable'] = [
-        'title' => t('Enable'),
-        'url' => Url::fromRoute('entity.eca.enable', ['eca' => $entity->id()]),
-        'weight' => 50,
-      ];
-    }
-    else {
-      $operations['disable'] = [
-        'title' => t('Disable'),
-        'url' => Url::fromRoute('entity.eca.disable', ['eca' => $entity->id()]),
-        'weight' => 51,
-      ];
-    }
-    if ($entity->isEditable()) {
-      $operations['clone'] = [
-        'title' => t('Clone'),
-        'url' => Url::fromRoute('entity.eca.clone', ['eca' => $entity->id()]),
-        'weight' => 52,
-      ];
-    }
-    $operations['export'] = [
-      'title' => t('Export'),
-      'url' => Url::fromRoute('entity.eca.export', ['eca' => $entity->id()]),
-      'weight' => 52,
-    ];
-    $operations['export_recipe'] = [
-      'title' => t('Export as recipe'),
-      'url' => Url::fromRoute('entity.eca.export_recipe', ['eca' => $entity->id()]),
-      'weight' => 53,
-    ];
-  }
-  return $operations;
-}
diff --git a/modules/ui/eca_ui.routing.yml b/modules/ui/eca_ui.routing.yml
index d21d9e27f..599069c92 100644
--- a/modules/ui/eca_ui.routing.yml
+++ b/modules/ui/eca_ui.routing.yml
@@ -5,60 +5,6 @@ entity.eca.collection:
     _title: 'Configure ECA - Events, Conditions, Actions'
   requirements:
     _permission: 'administer eca'
-entity.eca.edit_form:
-  path: '/admin/config/workflow/eca/{eca}/edit'
-  defaults:
-    _controller: '\Drupal\eca_ui\Controller\EcaController::edit'
-  requirements:
-    _permission: 'administer eca'
-eca.save:
-  path: '/admin/config/workflow/eca/{modeller_id}/save'
-  defaults:
-    _controller: '\Drupal\eca_ui\Controller\EcaController::save'
-  requirements:
-    _permission: 'administer eca'
-entity.eca.delete_form:
-  path: '/admin/config/workflow/eca/{eca}/delete'
-  defaults:
-    _entity_form: 'eca.delete'
-    _title: 'Delete'
-  requirements:
-    _permission: 'administer eca'
-entity.eca.enable:
-  path: '/admin/config/workflow/eca/{eca}/enable'
-  defaults:
-    _controller: '\Drupal\eca_ui\Controller\EcaController::enable'
-  requirements:
-    _permission: 'administer eca'
-entity.eca.disable:
-  path: '/admin/config/workflow/eca/{eca}/disable'
-  defaults:
-    _controller: '\Drupal\eca_ui\Controller\EcaController::disable'
-  requirements:
-    _permission: 'administer eca'
-entity.eca.clone:
-  path: '/admin/config/workflow/eca/{eca}/clone'
-  defaults:
-    _controller: '\Drupal\eca_ui\Controller\EcaController::clone'
-  requirements:
-    _permission: 'administer eca'
-entity.eca.export:
-  path: '/admin/config/workflow/eca/{eca}/export'
-  defaults:
-    _controller: '\Drupal\eca_ui\Controller\EcaController::export'
-  requirements:
-    _permission: 'administer eca'
-entity.eca.export_recipe:
-  path: '/admin/config/workflow/eca/{eca}/recipe'
-  defaults:
-    _title: 'Export as recipe'
-    _form: '\Drupal\eca_ui\Form\ExportRecipe'
-  options:
-    parameters:
-      eca:
-        type: 'entity:eca'
-  requirements:
-    _permission: 'administer eca'
 
 eca.settings:
   path: '/admin/config/workflow/eca/settings'
@@ -67,19 +13,3 @@ eca.settings:
     _form: '\Drupal\eca_ui\Form\Settings'
   requirements:
     _permission: 'administer eca'
-
-eca.import:
-  path: '/admin/config/workflow/eca/import'
-  defaults:
-    _title: 'Import'
-    _form: '\Drupal\eca_ui\Form\Import'
-  requirements:
-    _permission: 'administer eca'
-
-eca.add:
-  path: '/admin/config/workflow/eca/add'
-  defaults:
-    _title: 'Create new model'
-    _controller: '\Drupal\eca_ui\Controller\EcaController::add'
-  requirements:
-    _permission: 'administer eca'
diff --git a/modules/ui/eca_ui.services.yml b/modules/ui/eca_ui.services.yml
index 78f141228..ced55a328 100644
--- a/modules/ui/eca_ui.services.yml
+++ b/modules/ui/eca_ui.services.yml
@@ -2,4 +2,5 @@ services:
 
   eca_ui.service.token_browser:
     class: Drupal\eca_ui\Service\TokenBrowserService
-    arguments: ['@module_handler']
+    arguments:
+      - '@module_handler'
diff --git a/modules/ui/src/Controller/EcaController.php b/modules/ui/src/Controller/EcaController.php
deleted file mode 100644
index 1fab775c0..000000000
--- a/modules/ui/src/Controller/EcaController.php
+++ /dev/null
@@ -1,267 +0,0 @@
-<?php
-
-namespace Drupal\eca_ui\Controller;
-
-use Drupal\Core\Ajax\AjaxResponse;
-use Drupal\Core\Ajax\MessageCommand;
-use Drupal\Core\Ajax\RedirectCommand;
-use Drupal\Core\Controller\ControllerBase;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Link;
-use Drupal\Core\Url;
-use Drupal\eca\Service\Modellers;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\RedirectResponse;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-
-/**
- * Class Eca.
- *
- * @package Drupal\eca\Controller
- */
-final class EcaController extends ControllerBase {
-
-  /**
-   * Symfony request.
-   *
-   * @var \Symfony\Component\HttpFoundation\Request
-   */
-  protected Request $request;
-
-  /**
-   * ECA modeller service.
-   *
-   * @var \Drupal\eca\Service\Modellers
-   */
-  protected Modellers $modellerServices;
-
-  /**
-   * Entity storage manager.
-   *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
-   */
-  protected EntityStorageInterface $storage;
-
-  /**
-   * ECA controller constructor.
-   *
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The symfony request.
-   * @param \Drupal\eca\Service\Modellers $modeller_services
-   *   The ECA modeller service.
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   The entity type manager service.
-   */
-  public function __construct(Request $request, Modellers $modeller_services, EntityTypeManagerInterface $entity_type_manager) {
-    $this->request = $request;
-    $this->modellerServices = $modeller_services;
-    $this->storage = $entity_type_manager->getStorage('eca');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container): EcaController {
-    return new EcaController(
-      $container->get('request_stack')->getCurrentRequest(),
-      $container->get('eca.service.modeller'),
-      $container->get('entity_type.manager')
-    );
-  }
-
-  /**
-   * Displays add new model links for available modellers.
-   *
-   * Redirects to /admin/config/workflow/eca/add/[type] if only one modeller is
-   * available.
-   *
-   * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
-   *   A render array for a list of modellers that can add new models; however,
-   *   if there is only one modeller available, the function will return a
-   *   RedirectResponse to directly add that model type.
-   */
-  public function add() {
-    $modellers = [];
-    foreach ($this->modellerServices->getModellerDefinitions() as $modellerDefinition) {
-      if (($modeller = $this->modellerServices->getModeller($modellerDefinition['id'])) && $modeller->isEditable()) {
-        $url = Url::fromRoute($modellerDefinition['provider'] . '.add');
-        if ($url->access()) {
-          $label = $modellerDefinition['label'] ?? $modellerDefinition['id'];
-          $description = $modellerDefinition['description'] ?? 'Use ' . $label . ' to create the new model.';
-          $modellers[$modellerDefinition['id']] = [
-            'provider' => $modellerDefinition['provider'],
-            'label' => $label,
-            'description' => $description,
-            'add_link' => Link::fromTextAndUrl($label, $url),
-          ];
-        }
-      }
-    }
-    if (count($modellers) === 1) {
-      $modeller = array_shift($modellers);
-      return $this->redirect($modeller['provider'] . '.add');
-    }
-    return [
-      '#cache' => [
-        'tags' => [
-          'modeler_api_plugins',
-        ],
-      ],
-      '#theme' => 'entity_add_list',
-      '#bundles' => $modellers,
-      '#add_bundle_message' => $this->t('There are no modellers available yet. Install at least one module that integrates a modeller. A list of available integrations can be found on the <a href=":url" target="_blank" rel="nofollow noreferrer">ECA project page</a>.', [
-        ':url' => 'https://www.drupal.org/project/eca#modellers',
-      ]),
-    ];
-  }
-
-  /**
-   * Enable the given ECA entity if disabled.
-   *
-   * @param string $eca
-   *   The ID of the ECA entity to enable.
-   *
-   * @return \Symfony\Component\HttpFoundation\RedirectResponse
-   *   Redirect response to go to the ECA collection page.
-   */
-  public function enable(string $eca): RedirectResponse {
-    /** @var \Drupal\eca\Entity\Eca|null $config */
-    $config = $this->storage->load($eca);
-    if ($config && !$config->status() && $modeller = $config->getModeller()) {
-      $modeller->enable();
-    }
-    return new RedirectResponse(Url::fromRoute('entity.eca.collection')->toString());
-  }
-
-  /**
-   * Disable the given ECA entity if enabled.
-   *
-   * @param string $eca
-   *   The ID of the ECA entity to disable.
-   *
-   * @return \Symfony\Component\HttpFoundation\RedirectResponse
-   *   Redirect response to go to the ECA collection page.
-   */
-  public function disable(string $eca): RedirectResponse {
-    /** @var \Drupal\eca\Entity\Eca|null $config */
-    $config = $this->storage->load($eca);
-    if ($config && $config->status() && $modeller = $config->getModeller()) {
-      $modeller->disable();
-    }
-    return new RedirectResponse(Url::fromRoute('entity.eca.collection')->toString());
-  }
-
-  /**
-   * Clone the given ECA entity and save it as a new one.
-   *
-   * @param string $eca
-   *   The ID of the ECA entity to clone.
-   *
-   * @return \Symfony\Component\HttpFoundation\RedirectResponse
-   *   Redirect response to go to the ECA collection page.
-   */
-  public function clone(string $eca): RedirectResponse {
-    /** @var \Drupal\eca\Entity\Eca|null $config */
-    $config = $this->storage->load($eca);
-    if ($config && $config->isEditable() && $modeller = $config->getModeller()) {
-      $modeller->clone();
-    }
-    return new RedirectResponse(Url::fromRoute('entity.eca.collection')->toString());
-  }
-
-  /**
-   * Export the model from the given ECA entity.
-   *
-   * @param string $eca
-   *   The ID of the ECA entity to export.
-   *
-   * @return \Symfony\Component\HttpFoundation\Response
-   *   Redirect response to go to the ECA collection page.
-   */
-  public function export(string $eca): Response {
-    /** @var \Drupal\eca\Entity\Eca|null $config */
-    $config = $this->storage->load($eca);
-    if ($config && $modeller = $config->getModeller()) {
-      $response = $modeller->export();
-      if ($response) {
-        return $response;
-      }
-    }
-    return new RedirectResponse(Url::fromRoute('entity.eca.collection')->toString());
-  }
-
-  /**
-   * Edit the given ECA entity if the modeller supports that.
-   *
-   * @param string $eca
-   *   The ID of the ECA entity to edit.
-   *
-   * @return array
-   *   The render array for editing the ECA entity.
-   */
-  public function edit(string $eca): array {
-    /** @var \Drupal\eca\Entity\Eca|null $config */
-    $config = $this->storage->load($eca);
-    if ($config && $config->isEditable() && $modeller = $config->getModeller()) {
-      $build = $modeller->edit();
-      $build['#title'] = $this->t('%label ECA Model', ['%label' => $config->label()]);
-      return $build;
-    }
-    return [];
-  }
-
-  /**
-   * Ajax callback to save an ECA model with a given modeller.
-   *
-   * @param string $modeller_id
-   *   The plugin ID of the modeller that's being used for the posted model.
-   *
-   * @return \Drupal\Core\Ajax\AjaxResponse
-   *   An Ajax response object containing the message indicating the success of
-   *   the save operation and if this is a new ECA entity to be saved, also
-   *   containing a redirect instruction to the edit page of that entity.
-   */
-  public function save(string $modeller_id): AjaxResponse {
-    $response = new AjaxResponse();
-    if ($modeller = $this->modellerServices->getModeller($modeller_id)) {
-      try {
-        if ($modeller->save($this->request->getContent())) {
-          $editUrl = Url::fromRoute('entity.eca.edit_form', ['eca' => mb_strtolower($modeller->getId())], ['absolute' => TRUE])->toString();
-          $response->addCommand(new RedirectCommand($editUrl));
-        }
-        if (!$modeller->hasError()) {
-          $message = new MessageCommand('Successfully saved the model.', NULL, [
-            'type' => 'status',
-          ]);
-        }
-        else {
-          $message = new MessageCommand('Model contains error(s) and can not be saved.', NULL, [
-            'type' => 'error',
-          ]);
-        }
-      }
-      catch (\Exception $ex) {
-        // @todo Log details about the exception.
-        $message = new MessageCommand($ex->getMessage(), NULL, [
-          'type' => 'error',
-        ]);
-      }
-    }
-    else {
-      $message = new MessageCommand('Invalid modeller ID.', NULL, [
-        'type' => 'error',
-      ]);
-    }
-    $response->addCommand($message);
-    foreach ($this->messenger()->all() as $type => $messages) {
-      foreach ($messages as $message) {
-        $response->addCommand(new MessageCommand($message, NULL, ['type' => $type], FALSE));
-      }
-    }
-    $this->messenger()->deleteAll();
-    return $response;
-  }
-
-}
diff --git a/modules/ui/src/Entity/AccessControlHandler.php b/modules/ui/src/Entity/AccessControlHandler.php
deleted file mode 100644
index 47ba4b6d2..000000000
--- a/modules/ui/src/Entity/AccessControlHandler.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-namespace Drupal\eca_ui\Entity;
-
-use Drupal\Core\Access\AccessResult;
-use Drupal\Core\Entity\EntityAccessControlHandler;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Session\AccountInterface;
-
-/**
- * Defines the access control handler for ECA config entities.
- *
- * @see \Drupal\eca\Entity\Eca
- */
-class AccessControlHandler extends EntityAccessControlHandler {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
-    /** @var \Drupal\eca\Entity\Eca $entity */
-
-    // Deny access for ECA configurations that cannot be edited.
-    if ($operation === 'update' && !$entity->isEditable()) {
-      return AccessResult::forbidden();
-    }
-
-    return parent::checkAccess($entity, $operation, $account);
-  }
-
-}
diff --git a/modules/ui/src/Form/EcaDeleteForm.php b/modules/ui/src/Form/EcaDeleteForm.php
deleted file mode 100644
index 8160ee323..000000000
--- a/modules/ui/src/Form/EcaDeleteForm.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-
-namespace Drupal\eca_ui\Form;
-
-use Drupal\Core\Entity\EntityDeleteForm;
-
-/**
- * Creates a form to delete an eca config entity.
- */
-class EcaDeleteForm extends EntityDeleteForm {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getQuestion() {
-    return $this->t('Deleting ECA model %label', ['%label' => $this->entity->label()]);
-  }
-
-}
diff --git a/modules/ui/src/Form/ExportRecipe.php b/modules/ui/src/Form/ExportRecipe.php
deleted file mode 100644
index 508c7393d..000000000
--- a/modules/ui/src/Form/ExportRecipe.php
+++ /dev/null
@@ -1,153 +0,0 @@
-<?php
-
-namespace Drupal\eca_ui\Form;
-
-use Drupal\Component\Utility\Html;
-use Drupal\Core\Ajax\AjaxFormHelperTrait;
-use Drupal\Core\Ajax\AjaxResponse;
-use Drupal\Core\Ajax\CloseModalDialogCommand;
-use Drupal\Core\Ajax\MessageCommand;
-use Drupal\Core\File\FileSystem;
-use Drupal\Core\Form\FormBase;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\eca\Entity\Eca;
-use Drupal\eca\Service\ExportRecipe as ExportRecipeService;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Export a model as a recipe.
- */
-class ExportRecipe extends FormBase {
-
-  use AjaxFormHelperTrait;
-
-  /**
-   * The export recipe service.
-   *
-   * @var \Drupal\eca\Service\ExportRecipe
-   */
-  protected ExportRecipeService $exportRecipe;
-
-  /**
-   * The file system.
-   *
-   * @var \Drupal\Core\File\FileSystem
-   */
-  protected FileSystem $fileSystem;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container): static {
-    $form = parent::create($container);
-    $form->exportRecipe = $container->get('eca.export.recipe');
-    $form->fileSystem = $container->get('file_system');
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId(): string {
-    return 'eca_export_recipe';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, ?Eca $eca = NULL): array {
-    if ($eca === NULL) {
-      return $form;
-    }
-    $form['eca'] = ['#type' => 'hidden', '#value' => $eca->id()];
-    $form['name'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Name'),
-      '#default_value' => $this->exportRecipe->defaultName($eca),
-      '#required' => TRUE,
-    ];
-    $form['namespace'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Namespace'),
-      '#default_value' => ExportRecipeService::DEFAULT_NAMESPACE,
-      '#required' => TRUE,
-    ];
-    $form['destination'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Destination'),
-      '#default_value' => ExportRecipeService::DEFAULT_DESTINATION,
-      '#required' => TRUE,
-    ];
-
-    $form['actions']['#type'] = 'actions';
-    $form['actions']['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Export'),
-      '#button_type' => 'primary',
-    ];
-
-    if ($this->isAjax()) {
-      $form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit';
-      // @todo static::ajaxSubmit() requires data-drupal-selector to be the same
-      //   between the various Ajax requests. A bug in
-      //   \Drupal\Core\Form\FormBuilder prevents that from happening unless
-      //   $form['#id'] is also the same. Normally, #id is set to a unique HTML
-      //   ID via Html::getUniqueId(), but here we bypass that in order to work
-      //   around the data-drupal-selector bug. This is okay so long as we
-      //   assume that this form only ever occurs once on a page. Remove this
-      //   workaround in https://www.drupal.org/node/2897377.
-      $form['#id'] = Html::getId($form_state->getBuildInfo()['form_id']);
-    }
-
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state): void {
-    $destination = $form_state->getValue('destination');
-    $configDestination = $destination . '/config';
-    if (!file_exists($configDestination)) {
-      if (!$this->fileSystem->mkdir($configDestination, FileSystem::CHMOD_DIRECTORY, TRUE)) {
-        $form_state->setErrorByName('destination', $this->t('The destination does not exist or is not writable.'));
-      }
-    }
-    elseif (!is_writable($configDestination)) {
-      $form_state->setErrorByName('destination', $this->t('The destination is not writable.'));
-    }
-    parent::validateForm($form, $form_state);
-  }
-
-  /**
-   * Helper function to perform the export.
-   *
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The form state.
-   */
-  protected function doExport(FormStateInterface $form_state): void {
-    /** @var \Drupal\eca\Entity\Eca $eca */
-    $eca = Eca::load($form_state->getValue('eca'));
-    $this->exportRecipe->doExport($eca, $form_state->getValue('name'), $form_state->getValue('namespace'), $form_state->getValue('destination'));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state): void {
-    $this->doExport($form_state);
-    $form_state->setRedirect('entity.eca.collection');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function successfulAjaxSubmit(array $form, FormStateInterface $form_state): AjaxResponse {
-    $this->doExport($form_state);
-    $response = new AjaxResponse();
-    $response->addCommand(new CloseModalDialogCommand());
-    $response->addCommand(new MessageCommand('The model has been exported as a recipe.'));
-    return $response;
-  }
-
-}
diff --git a/modules/ui/src/Form/Import.php b/modules/ui/src/Form/Import.php
deleted file mode 100644
index dc51a3ef1..000000000
--- a/modules/ui/src/Form/Import.php
+++ /dev/null
@@ -1,415 +0,0 @@
-<?php
-
-namespace Drupal\eca_ui\Form;
-
-use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
-use Drupal\Component\Plugin\Exception\PluginNotFoundException;
-use Drupal\Component\Serialization\Yaml;
-use Drupal\Core\Archiver\ArchiveTar;
-use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Config\CachedStorage;
-use Drupal\Core\Config\ConfigException;
-use Drupal\Core\Config\ConfigImporter;
-use Drupal\Core\Config\ConfigManagerInterface;
-use Drupal\Core\Config\FileStorage;
-use Drupal\Core\Config\StorageComparer;
-use Drupal\Core\Config\TypedConfigManager;
-use Drupal\Core\Entity\EntityStorageException;
-use Drupal\Core\Extension\ModuleExtensionList;
-use Drupal\Core\Extension\ModuleHandlerInterface;
-use Drupal\Core\Extension\ModuleInstallerInterface;
-use Drupal\Core\Extension\ThemeExtensionList;
-use Drupal\Core\Extension\ThemeHandler;
-use Drupal\Core\File\FileSystemInterface;
-use Drupal\Core\Form\FormBase;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Lock\LockBackendInterface;
-use Drupal\config\StorageReplaceDataWrapper;
-use Drupal\eca\Entity\Eca;
-use Drupal\eca\Service\Modellers;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
-
-/**
- * Import a model from a previous export.
- */
-class Import extends FormBase {
-
-  /**
-   * ECA modeller service.
-   *
-   * @var \Drupal\eca\Service\Modellers
-   */
-  protected Modellers $modellerService;
-
-  /**
-   * Symfony request.
-   *
-   * @var \Symfony\Component\HttpFoundation\Request
-   */
-  protected Request $request;
-
-  /**
-   * Config manager.
-   *
-   * @var \Drupal\Core\Config\ConfigManagerInterface
-   */
-  protected ConfigManagerInterface $configManager;
-
-  /**
-   * Cached storage.
-   *
-   * @var \Drupal\Core\Config\CachedStorage
-   */
-  protected CachedStorage $configStorage;
-
-  /**
-   * Cache backend.
-   *
-   * @var \Drupal\Core\Cache\CacheBackendInterface
-   */
-  protected CacheBackendInterface $configCache;
-
-  /**
-   * Module handler.
-   *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface
-   */
-  protected ModuleHandlerInterface $moduleHandler;
-
-  /**
-   * Event dispatcher.
-   *
-   * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
-   */
-  protected EventDispatcherInterface $eventDispatcher;
-
-  /**
-   * Lock backend.
-   *
-   * @var \Drupal\Core\Lock\LockBackendInterface
-   */
-  protected LockBackendInterface $lock;
-
-  /**
-   * Typed config manager.
-   *
-   * @var \Drupal\Core\Config\TypedConfigManager
-   */
-  protected TypedConfigManager $configTyped;
-
-  /**
-   * Module installer.
-   *
-   * @var \Drupal\Core\Extension\ModuleInstallerInterface
-   */
-  protected ModuleInstallerInterface $moduleInstaller;
-
-  /**
-   * Theme handler.
-   *
-   * @var \Drupal\Core\Extension\ThemeHandler
-   */
-  protected ThemeHandler $themeHandler;
-
-  /**
-   * The module extension list.
-   *
-   * @var \Drupal\Core\Extension\ModuleExtensionList
-   */
-  protected ModuleExtensionList $moduleExtensionList;
-
-  /**
-   * The theme extension list.
-   *
-   * @var \Drupal\Core\Extension\ThemeExtensionList
-   */
-  protected ThemeExtensionList $themeExtensionList;
-
-  /**
-   * The file system service.
-   *
-   * @var \Drupal\Core\File\FileSystemInterface
-   */
-  protected FileSystemInterface $fileSystem;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container): Import {
-    $form = parent::create($container);
-    $form->modellerService = $container->get('eca.service.modeller');
-    $form->request = $container->get('request_stack')->getCurrentRequest();
-    $form->configManager = $container->get('config.manager');
-    $form->configStorage = $container->get('config.storage');
-    $form->configCache = $container->get('cache.config');
-    $form->moduleHandler = $container->get('module_handler');
-    $form->eventDispatcher = $container->get('event_dispatcher');
-    $form->lock = $container->get('lock');
-    $form->configTyped = $container->get('config.typed');
-    $form->moduleInstaller = $container->get('module_installer');
-    $form->themeHandler = $container->get('theme_handler');
-    $form->moduleExtensionList = $container->get('extension.list.module');
-    $form->themeExtensionList = $container->get('extension.list.theme');
-    $form->fileSystem = $container->get('file_system');
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId(): string {
-    return 'eca_import';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state): array {
-    if ($this->moduleHandler->moduleExists('config')) {
-      $form['model'] = [
-        '#type' => 'file',
-        '#title' => $this->t('File containing the exported XML model or archive containing all dependent config entities.'),
-      ];
-      $form['actions']['#type'] = 'actions';
-      $form['actions']['submit'] = [
-        '#type' => 'submit',
-        '#value' => $this->t('Import'),
-        '#button_type' => 'primary',
-      ];
-    }
-    else {
-      $form['info'] = [
-        '#type' => 'markup',
-        '#markup' => $this->t('Import requires the config module to be enabled.'),
-      ];
-    }
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state): void {
-    parent::validateForm($form, $form_state);
-    $all_files = $this->request->files->get('files', []);
-    if (empty($all_files)) {
-      $form_state->setErrorByName('model', 'No file provided.');
-      return;
-    }
-    /** @var \Symfony\Component\HttpFoundation\File\UploadedFile|bool $file */
-    $file = reset($all_files);
-    if (!$file) {
-      $form_state->setErrorByName('model', 'No file provided.');
-      return;
-    }
-    $filename = $file->getRealPath();
-    if (!file_exists($filename)) {
-      $form_state->setErrorByName('model', 'Something went wrong during upload.');
-      return;
-    }
-    $extension = $file->getClientOriginalExtension();
-    if ($extension === 'xml') {
-      [$name] = explode('.', $file->getClientOriginalName());
-      [$modellerId, $id] = explode('-', $name);
-      $modeller = $this->modellerService->getModeller($modellerId);
-      if (!$modeller) {
-        $form_state->setErrorByName('model', 'The required modeller is not available.');
-        return;
-      }
-
-      // Verify, that ID is not used yet.
-      $data = file_get_contents($filename);
-      $modeller->setModeldata($data);
-      if ($eca = $this->modellerService->loadModel($modeller->getId())) {
-        $form_state->setErrorByName('model', 'Model with that ID already available with the name "' . $eca->label() . '". Delete that first, when you really want to import this file.');
-      }
-
-      // Test import the model and display missing dependencies if it fails.
-      try {
-        $eca = $modeller->createNewModel($modeller->getId(), $data);
-      }
-      catch (\LogicException | EntityStorageException $e) {
-        $this->messenger->addError($e->getMessage());
-        return;
-      }
-      $modeller->readComponents($eca);
-      if ($modeller->hasError()) {
-        // Reading components didn't succeed and recognized errors got pushed to
-        // Drupal's messenger.
-        return;
-      }
-      try {
-        $eca->calculateDependencies();
-      }
-      catch (\LogicException | InvalidPluginDefinitionException | PluginNotFoundException $e) {
-        $form_state->setErrorByName('model', $e->getMessage());
-        return;
-      }
-
-      // Prepare for final import in submit handler.
-      $form_state->setValue('model', [
-        'filename' => $filename,
-        'modellerId' => $modellerId,
-        'id' => $id,
-      ]);
-    }
-    elseif ($extension === 'gz') {
-      $source_storage_dir = $this->fileSystem->tempnam($this->fileSystem->getTempDirectory(), 'eca-import');
-      unlink($source_storage_dir);
-      $this->fileSystem->prepareDirectory($source_storage_dir);
-      try {
-        $archiver = new ArchiveTar($filename, 'gz');
-        $files = [];
-        foreach ($archiver->listContent() as $file) {
-          $files[] = $file['filename'];
-        }
-        $archiver->extractList($files, $source_storage_dir, '', FALSE, FALSE);
-        $this->fileSystem->unlink($filename);
-
-        $dependencyFilename = $source_storage_dir . '/dependencies.yml';
-        if (!file_exists($dependencyFilename)) {
-          $form_state->setErrorByName('model', 'Uploaded archive is not consistent.');
-        }
-        else {
-          $dependencies = Yaml::decode(file_get_contents($dependencyFilename));
-          $this->fileSystem->unlink($dependencyFilename);
-          $missingModules = [];
-          if (isset($dependencies['module']) && is_array($dependencies['module'])) {
-            foreach ($dependencies['module'] as $module) {
-              if (!$this->moduleHandler->moduleExists($module)) {
-                $missingModules[] = $module;
-              }
-            }
-          }
-          if (!empty($missingModules)) {
-            $form_state->setErrorByName('model', 'Can not import archive due to missing module(s): ' . implode(', ', $missingModules));
-          }
-          elseif (empty($dependencies['config']) || !is_array($dependencies['config'])) {
-            $form_state->setErrorByName('model', 'Archive is not consistent.');
-          }
-          else {
-            $missingFiles = [];
-            foreach ($dependencies['config'] as $item) {
-              if (!file_exists($source_storage_dir . '/' . $item . '.yml')) {
-                $missingFiles[] = $item;
-              }
-            }
-            if (!empty($missingFiles)) {
-              $form_state->setErrorByName('model', 'Can not import archive due to missing config file(s): ' . implode(', ', $missingFiles));
-            }
-            else {
-              // Prepare for final import in submit handler.
-              $form_state->setValue('model', $source_storage_dir);
-            }
-          }
-        }
-      }
-      catch (\Exception $e) {
-        $form_state->setErrorByName('model', $e->getMessage());
-      }
-    }
-    else {
-      $form_state->setErrorByName('model', 'Unsupported file extension.');
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state): void {
-    $model = $form_state->getValue('model');
-    if (is_array($model)) {
-      // Import a single XML file.
-      if ($modeller = $this->modellerService->getModeller($model['modellerId'])) {
-        try {
-          $modeller->save(file_get_contents($model['filename']), NULL, FALSE);
-        }
-        catch (\LogicException | EntityStorageException $e) {
-          $this->messenger()->addError($e->getMessage());
-        }
-      }
-    }
-    else {
-      // Import all files from an extracted archive.
-      $source_storage_dir = $model;
-      $source_storage = new FileStorage($source_storage_dir);
-      $active_storage = $this->configStorage;
-      $replacement_storage = new StorageReplaceDataWrapper($active_storage);
-      $id = NULL;
-      foreach ($source_storage->listAll() as $name) {
-        $data = $source_storage->read($name);
-        if (is_array($data)) {
-          if (mb_strpos($name, 'eca.eca.') === 0) {
-            $id = $data['id'];
-          }
-          $replacement_storage->replaceData($name, $data);
-        }
-        else {
-          $this->messenger()->addError($this->t('The contained config entity %name is invalid and got ignored.', [
-            '%name' => $name,
-          ]));
-        }
-      }
-      $source_storage = $replacement_storage;
-
-      $storage_comparer = new StorageComparer($source_storage, $active_storage);
-      if ($id === NULL) {
-        $this->messenger()->addError('This file does not contain any ECA model.');
-      }
-      elseif (!$storage_comparer->createChangelist()->hasChanges()) {
-        $this->messenger()->addStatus('There are no changes to import.');
-      }
-      else {
-        $config_importer = new ConfigImporter(
-          $storage_comparer,
-          $this->eventDispatcher,
-          $this->configManager,
-          $this->lock,
-          $this->configTyped,
-          $this->moduleHandler,
-          $this->moduleInstaller,
-          $this->themeHandler,
-          $this->getStringTranslation(),
-          $this->moduleExtensionList,
-          $this->themeExtensionList
-        );
-        if ($config_importer->alreadyImporting()) {
-          $this->messenger()->addWarning('Another request may be synchronizing configuration already.');
-        }
-        else {
-          try {
-            $config_importer->import();
-            if ($config_importer->getErrors()) {
-              $this->messenger()->addError(implode("\n", $config_importer->getErrors()));
-            }
-            elseif ($eca = Eca::load($id)) {
-              if ($eca->isEditable()) {
-                $this->messenger()->addStatus($this->t('The configuration <a href="@link">@name</a> was imported successfully.', [
-                  '@name' => $eca->label(),
-                  '@link' => $eca->toUrl()->toString(),
-                ]));
-              }
-              else {
-                $this->messenger()->addStatus($this->t('The configuration %name was imported successfully.', [
-                  '%name' => $eca->label(),
-                ]));
-              }
-            }
-            else {
-              $this->messenger()->addError('Unexpected error.');
-            }
-          }
-          catch (ConfigException $e) {
-            $message = 'The import failed due to the following reason: ' . $e->getMessage() . "\n" . implode("\n", $config_importer->getErrors());
-            $this->messenger()->addError($message);
-          }
-        }
-      }
-      $this->fileSystem->deleteRecursive($source_storage_dir);
-    }
-    $form_state->setRedirect('entity.eca.collection');
-  }
-
-}
diff --git a/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php b/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
new file mode 100644
index 000000000..5bb9e3621
--- /dev/null
+++ b/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\eca_ui\Plugin\modeler_api_model_owner;
+
+use Drupal\modeler_api\Plugin\modeler_api_model_owner\ModelOwnerBase;
+
+/**
+ * Model owner plugins implementation for ECA.
+ */
+class Eca extends ModelOwnerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function configEntityTypeId(): string {
+    return 'eca';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function configEntityBasePath(): string {
+    return 'admin/config/workflow/eca';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resetComponents(): void {
+    // @todo Implement resetComponents() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addCondition(string $id, string $plugin_id, array $fields): bool {
+    // @todo Implement addCondition() method.
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addGateway(string $id, int $type, array $successors): bool {
+    // @todo Implement addGateway() method.
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addEvent(string $id, string $plugin_id, string $label, array $fields, array $successors): bool {
+    // @todo Implement addEvent() method.
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addAction(string $id, string $plugin_id, string $label, array $fields, array $successors): bool {
+    // @todo Implement addAction() method.
+    return TRUE;
+  }
+
+}
diff --git a/modules/user/eca_user.info.yml b/modules/user/eca_user.info.yml
index 714a8b4f4..314edb047 100644
--- a/modules/user/eca_user.info.yml
+++ b/modules/user/eca_user.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA User'
 type: module
 description: 'User events, conditions and actions.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - eca:eca
diff --git a/modules/user/eca_user.services.yml b/modules/user/eca_user.services.yml
index 821c0839b..9a2450bc2 100644
--- a/modules/user/eca_user.services.yml
+++ b/modules/user/eca_user.services.yml
@@ -1,7 +1,9 @@
 services:
   eca_user.hook_handler:
     class: Drupal\eca_user\HookHandler
-    arguments: ['@eca.trigger_event']
+    arguments:
+      - '@eca.trigger_event'
   eca_user.account_switcher:
     class: Drupal\eca_user\AccountSwitcher
-    arguments: ['@account_switcher']
+    arguments:
+      - '@account_switcher'
diff --git a/modules/user/src/Plugin/ECA/Condition/UserTrait.php b/modules/user/src/Plugin/ECA/Condition/UserTrait.php
index e2ea5e399..0c8a8693f 100644
--- a/modules/user/src/Plugin/ECA/Condition/UserTrait.php
+++ b/modules/user/src/Plugin/ECA/Condition/UserTrait.php
@@ -43,11 +43,12 @@ trait UserTrait {
       }
     }
     if (is_numeric($account)) {
-      /**
-       * @var \Drupal\user\UserInterface $account
-       */
+      $id = $account;
       try {
-        $account = $this->entityTypeManager->getStorage('user')->load($account);
+        /**
+         * @var \Drupal\user\UserInterface $account
+         */
+        $account = $this->entityTypeManager->getStorage('user')->load($id);
       }
       catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
         $account = NULL;
diff --git a/modules/views/eca_views.info.yml b/modules/views/eca_views.info.yml
index 72986e36b..0d1f2fa69 100644
--- a/modules/views/eca_views.info.yml
+++ b/modules/views/eca_views.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Views'
 type: module
 description: 'Execute and export Views query results within ECA.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - drupal:views
diff --git a/modules/views/eca_views.services.yml b/modules/views/eca_views.services.yml
index 49c7164d3..59dba12dc 100644
--- a/modules/views/eca_views.services.yml
+++ b/modules/views/eca_views.services.yml
@@ -1,4 +1,5 @@
 services:
   eca_views.hook_handler:
     class: Drupal\eca_views\HookHandler
-    arguments: ['@eca.trigger_event']
+    arguments:
+      - '@eca.trigger_event'
diff --git a/modules/workflow/eca_workflow.info.yml b/modules/workflow/eca_workflow.info.yml
index 7252c07de..f9fde633e 100644
--- a/modules/workflow/eca_workflow.info.yml
+++ b/modules/workflow/eca_workflow.info.yml
@@ -1,7 +1,7 @@
 name: 'ECA Workflow'
 type: module
 description: 'Events, conditions and actions for content moderation workflow.'
-core_version_requirement: ^10.3 || ^11
+core_version_requirement: ^11.2
 package: ECA
 dependencies:
   - drupal:content_moderation
diff --git a/modules/workflow/eca_workflow.services.yml b/modules/workflow/eca_workflow.services.yml
index bdb597a44..410c06ae7 100644
--- a/modules/workflow/eca_workflow.services.yml
+++ b/modules/workflow/eca_workflow.services.yml
@@ -1,7 +1,8 @@
 services:
   eca_workflow.hook_handler:
     class: Drupal\eca_workflow\HookHandler
-    arguments: ['@eca.trigger_event']
+    arguments:
+      - '@eca.trigger_event'
     calls:
       - [setContentEntityTypes, ['@eca.service.content_entity_types']]
       - [setModerationInformation, ['@content_moderation.moderation_information']]
diff --git a/src/Drush/Commands/EcaCommands.php b/src/Drush/Commands/EcaCommands.php
index fa46bd9a6..f67652392 100644
--- a/src/Drush/Commands/EcaCommands.php
+++ b/src/Drush/Commands/EcaCommands.php
@@ -2,15 +2,9 @@
 
 namespace Drupal\eca\Drush\Commands;
 
-use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\eca\EcaUpdate;
-use Drupal\eca\Service\ExportRecipe;
-use Drupal\eca\Service\Modellers;
-use Drush\Attributes\Argument;
 use Drush\Attributes\Command;
-use Drush\Attributes\Option;
 use Drush\Attributes\Usage;
 use Drush\Commands\DrushCommands;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -32,9 +26,6 @@ final class EcaCommands extends DrushCommands {
    */
   public function __construct(
     EntityTypeManagerInterface $entityTypeManager,
-    private readonly Modellers $ecaServiceModeller,
-    private readonly ExportRecipe $exportRecipe,
-    private readonly EcaUpdate $ecaUpdate,
   ) {
     parent::__construct();
     $this->configStorage = $entityTypeManager->getStorage('eca');
@@ -52,141 +43,9 @@ final class EcaCommands extends DrushCommands {
   public static function create(ContainerInterface $container): EcaCommands {
     return new EcaCommands(
       $container->get('entity_type.manager'),
-      $container->get('eca.service.modeller'),
-      $container->get('eca.export.recipe'),
-      $container->get('eca.update'),
     );
   }
 
-  /**
-   * Import a single ECA file.
-   */
-  #[Command(name: 'eca:import', aliases: [])]
-  #[Argument(name: 'pluginId', description: 'The id of the modeller plugin.')]
-  #[Argument(name: 'filename', description: 'The file name to import, relative to the Drupal root or absolute.')]
-  #[Usage(name: 'eca:import camunda mymodel.xml', description: 'Import a single ECA file.')]
-  public function import(string $pluginId, string $filename): void {
-    $modeller = $this->ecaServiceModeller->getModeller($pluginId);
-    if ($modeller === NULL) {
-      $this->io()->error('This modeller plugin does not exist.');
-      return;
-    }
-    if (!file_exists($filename)) {
-      $this->io()->error('This file does not exist.');
-      return;
-    }
-    try {
-      $modeller->save(file_get_contents($filename), $filename);
-    }
-    catch (\LogicException | EntityStorageException $e) {
-      $this->io()->error($e->getMessage());
-    }
-  }
-
-  /**
-   * Update all previously imported ECA files.
-   */
-  #[Command(name: 'eca:reimport', aliases: [])]
-  #[Usage(name: 'eca:reimport', description: 'Update all previously imported ECA files.')]
-  public function reimportAll(): void {
-    /** @var \Drupal\eca\Entity\Eca $eca */
-    foreach ($this->configStorage->loadMultiple() as $eca) {
-      $modeller = $this->ecaServiceModeller->getModeller($eca->get('modeller'));
-      if ($modeller === NULL) {
-        $this->logger->error('This modeller plugin ' . $eca->get('modeller') . ' does not exist.');
-        continue;
-      }
-      if ($modeller->isEditable()) {
-        // Editable models have no external files.
-        continue;
-      }
-      $model = $eca->getModel();
-      $filename = $model->getFilename();
-      if (!file_exists($filename)) {
-        $this->logger->error('This file ' . $filename . ' does not exist.');
-        continue;
-      }
-      try {
-        $modeller->save(file_get_contents($filename), $filename);
-      }
-      catch (\LogicException | EntityStorageException $e) {
-        $this->io()->error($e->getMessage());
-      }
-    }
-  }
-
-  /**
-   * Export templates for all ECA modellers.
-   */
-  #[Command(name: 'eca:export:templates', aliases: [])]
-  #[Usage(name: 'eca:export:templates', description: 'Export templates for all ECA modellers.')]
-  public function exportTemplates(): void {
-    foreach ($this->ecaServiceModeller->getModellerDefinitions() as $plugin_id => $definition) {
-      $modeller = $this->ecaServiceModeller->getModeller($plugin_id);
-      if ($modeller === NULL) {
-        $this->io()->error('This modeller plugin does not exist.');
-        continue;
-      }
-      $modeller->exportTemplates();
-    }
-  }
-
-  /**
-   * Updates all existing ECA entities calling ::updateModel in their modeller.
-   *
-   * It is the modeller's responsibility to load all existing plugins and find
-   * out if the model data, which is proprietary to them, needs to be updated.
-   */
-  #[Command(name: 'eca:update', aliases: [])]
-  #[Usage(name: 'eca:update', description: 'Update all models if plugins got changed.')]
-  public function updateAllModels(): void {
-    $this->ecaUpdate->updateAllModels();
-    if ($infos = $this->ecaUpdate->getInfos()) {
-      $this->io()->info(implode(PHP_EOL, $infos));
-    }
-    if ($errors = $this->ecaUpdate->getErrors()) {
-      $this->io()->error(implode(PHP_EOL, $errors));
-    }
-  }
-
-  /**
-   * Disable all existing ECA entities.
-   */
-  #[Command(name: 'eca:disable', aliases: [])]
-  #[Usage(name: 'eca:disable', description: 'Disable all models.')]
-  public function disableAllModels(): void {
-    /** @var \Drupal\eca\Entity\Eca $eca */
-    foreach ($this->configStorage->loadMultiple() as $eca) {
-      $modeller = $this->ecaServiceModeller->getModeller($eca->get('modeller'));
-      if ($modeller === NULL) {
-        $this->logger->error('This modeller plugin ' . $eca->get('modeller') . ' does not exist.');
-        continue;
-      }
-      $modeller
-        ->setConfigEntity($eca)
-        ->disable();
-    }
-  }
-
-  /**
-   * Enable all existing ECA entities.
-   */
-  #[Command(name: 'eca:enable', aliases: [])]
-  #[Usage(name: 'eca:enable', description: 'Enable all models.')]
-  public function enableAllModels(): void {
-    /** @var \Drupal\eca\Entity\Eca $eca */
-    foreach ($this->configStorage->loadMultiple() as $eca) {
-      $modeller = $this->ecaServiceModeller->getModeller($eca->get('modeller'));
-      if ($modeller === NULL) {
-        $this->logger->error('This modeller plugin ' . $eca->get('modeller') . ' does not exist.');
-        continue;
-      }
-      $modeller
-        ->setConfigEntity($eca)
-        ->enable();
-    }
-  }
-
   /**
    * Rebuild the state of subscribed events.
    */
@@ -198,26 +57,4 @@ final class EcaCommands extends DrushCommands {
     $storage->rebuildSubscribedEvents();
   }
 
-  /**
-   * Export a model as a recipe.
-   */
-  #[Command(name: 'eca:model:export', aliases: [])]
-  #[Argument(name: 'id', description: 'The ID of the model.')]
-  #[Option(name: 'namespace', description: 'The namespace of the composer package.')]
-  #[Option(name: 'destination', description: 'The directory where to store the recipe.')]
-  #[Usage(name: 'eca:model:export MODELID', description: 'Export the model with the given ID as a recipe.')]
-  #[Usage(name: 'eca:model:export MODELID --namespace=your-vendor', description: 'Customize the recipe namespace (name prefix in composer.json).')]
-  #[Usage(name: 'eca:model:export MODELID --destination=../recipes/process_abc', description: 'Output the recipe at a custom relative path.')]
-  public function exportModel(string $id, array $options = ['namespace' => self::OPT, 'destination' => self::OPT]): void {
-    /** @var \Drupal\eca\Entity\Eca|null $eca */
-    $eca = $this->configStorage->load($id);
-    if ($eca === NULL) {
-      $this->io()->error('The given ECA model does not exist!');
-      return;
-    }
-    $namespace = $options['namespace'] ?? ExportRecipe::DEFAULT_NAMESPACE;
-    $destination = $options['destination'] ?? ExportRecipe::DEFAULT_DESTINATION;
-    $this->exportRecipe->doExport($eca, NULL, $namespace, $destination);
-  }
-
 }
diff --git a/src/EcaUpdate.php b/src/EcaUpdate.php
deleted file mode 100644
index 7a8c35588..000000000
--- a/src/EcaUpdate.php
+++ /dev/null
@@ -1,156 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\eca;
-
-use Drupal\Core\Entity\EntityStorageException;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Messenger\MessengerInterface;
-use Drupal\eca\Service\Actions;
-use Drupal\eca\Service\Conditions;
-use Drupal\eca\Service\Modellers;
-
-/**
- * Provides methods to update all existing ECA models and to output messages.
- */
-final class EcaUpdate {
-
-  /**
-   * ECA config entity storage manager.
-   *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
-   */
-  protected EntityStorageInterface $configStorage;
-
-  /**
-   * List of errors.
-   *
-   * @var array
-   */
-  protected array $errors;
-
-  /**
-   * List of info messages.
-   *
-   * @var array
-   */
-  protected array $infos;
-
-  /**
-   * Constructs an EcaUpdate object.
-   */
-  public function __construct(
-    private readonly EntityTypeManagerInterface $entityTypeManager,
-    private readonly Modellers $ecaServiceModeller,
-    private readonly MessengerInterface $messenger,
-    private readonly Modellers $modellerServices,
-    private readonly Conditions $conditionServices,
-    private readonly Actions $actionServices,
-  ) {
-    $this->configStorage = $this->entityTypeManager->getStorage('eca');
-  }
-
-  /**
-   * Updates all existing ECA entities calling ::updateModel in their modeller.
-   */
-  public function updateAllModels(): void {
-    $this->errors = [];
-    $this->infos = [];
-    /** @var \Drupal\eca\Entity\Eca $eca */
-    foreach ($this->configStorage->loadMultiple() as $eca) {
-      if ($eca->hasModel()) {
-        $modeller = $this->ecaServiceModeller->getModeller($eca->get('modeller'));
-        if ($modeller === NULL) {
-          $this->errors[] = '[' . $eca->label() . '] This modeller plugin ' . $eca->get('modeller') . ' for this ECA model does not exist.';
-          continue;
-        }
-        $model = $eca->getModel();
-        $modeller->setConfigEntity($eca);
-        if ($modeller->updateModel($model)) {
-          $filename = $model->getFilename();
-          if ($filename && file_exists($filename)) {
-            file_put_contents($filename, $model->getModeldata());
-          }
-          try {
-            $modeller->save($model->getModeldata(), $filename);
-            $this->infos[] = '[' . $eca->label() . '] Model has been updated.';
-          }
-          catch (\LogicException | EntityStorageException $e) {
-            $this->errors[] = '[' . $eca->label() . '] Error while updating this model: ' . $e->getMessage();
-          }
-        }
-        else {
-          $this->infos[] = '[' . $eca->label() . '] Model does not require any updates.';
-        }
-      }
-      else {
-        $allPlugins = [
-          'events' => $this->modellerServices->events(),
-          'conditions' => $this->conditionServices->conditions(),
-          'actions' => $this->actionServices->actions(),
-        ];
-        $changed = FALSE;
-        foreach ($allPlugins as $type => $plugins) {
-          $items = $eca->get($type) ?? [];
-          foreach ($items as &$item) {
-            $result = array_filter($plugins, function ($plugin) use ($item) {
-              return $plugin->getPluginId() === $item['plugin'];
-            });
-            $plugin = reset($result);
-            if ($plugin && method_exists($plugin, 'defaultConfiguration')) {
-              foreach ($plugin->defaultConfiguration() as $key => $value) {
-                if (!isset($item['configuration'][$key])) {
-                  $item['configuration'][$key] = $value;
-                  $changed = TRUE;
-                }
-              }
-            }
-          }
-          $eca->set($type, $items);
-        }
-        if ($changed) {
-          $eca->save();
-          $this->infos[] = '[' . $eca->label() . '] Model has been updated.';
-        }
-        else {
-          $this->infos[] = '[' . $eca->label() . '] Model does not require any updates.';
-        }
-      }
-    }
-  }
-
-  /**
-   * Gets the list of all collected error messages.
-   *
-   * @return array
-   *   The list of all collected error messages.
-   */
-  public function getErrors(): array {
-    return $this->errors;
-  }
-
-  /**
-   * Gets the list of all collected info messages.
-   *
-   * @return array
-   *   The list of all collected info messages.
-   */
-  public function getInfos(): array {
-    return $this->infos;
-  }
-
-  /**
-   * Outputs all messages (info and error) to the user.
-   */
-  public function displayMessages(): void {
-    foreach ($this->infos ?? [] as $info) {
-      $this->messenger->addMessage($info);
-    }
-    foreach ($this->errors ?? [] as $error) {
-      $this->messenger->addError($error);
-    }
-  }
-
-}
diff --git a/src/Entity/Eca.php b/src/Entity/Eca.php
index b3c4a935d..a30ad2893 100644
--- a/src/Entity/Eca.php
+++ b/src/Entity/Eca.php
@@ -3,9 +3,7 @@
 namespace Drupal\eca\Entity;
 
 use Drupal\Component\Plugin\ConfigurableInterface;
-use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
 use Drupal\Component\Plugin\Exception\PluginException;
-use Drupal\Component\Plugin\Exception\PluginNotFoundException;
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Component\Utility\Random;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
@@ -21,9 +19,6 @@ use Drupal\eca\Entity\Objects\EcaGateway;
 use Drupal\eca\Entity\Objects\EcaObject;
 use Drupal\eca\Form\RuntimePluginForm;
 use Drupal\eca\Plugin\PluginUsageInterface;
-use Drupal\modeler_api\Entity\Model;
-use Drupal\modeler_api\TargetInterface;
-use Drupal\modeler_api\Plugin\ModelerInterface;
 use Symfony\Contracts\EventDispatcher\Event;
 
 /**
@@ -66,7 +61,7 @@ use Symfony\Contracts\EventDispatcher\Event;
  *   }
  * )
  */
-class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterface, TargetInterface {
+class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterface {
 
   use EcaTrait;
 
@@ -124,13 +119,6 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
    */
   protected array $actions = [];
 
-  /**
-   * Model config entity for the ECA config entity.
-   *
-   * @var \Drupal\modeler_api\Entity\Model
-   */
-  protected Model $model;
-
   /**
    * Whether this instance s in testing mode.
    *
@@ -190,7 +178,7 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
   /**
    * {@inheritdoc}
    */
-  public function calculateDependencies() {
+  public function calculateDependencies(): static {
     // As ::trustData() states that dependencies are not calculated on save,
     // calculation is skipped when flagged as trusted.
     // @see Drupal\Core\Config\Entity\ConfigEntityInterface::trustData
@@ -237,82 +225,6 @@ class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterfac
     return $request->query->get('eca_validation', '') === 'off';
   }
 
-  /**
-   * Determine if the ECA config entity is editable.
-   *
-   * @return bool
-   *   If the associated modeller supports editing inside the Drupal admin UI,
-   *   return TRUE, FALSE otherwise.
-   */
-  public function isEditable(): bool {
-    if ($modeller = $this->getModeller()) {
-      return $modeller->isEditable();
-    }
-    return FALSE;
-  }
-
-  /**
-   * Provides the modeller plugin associated with this ECA config entity.
-   *
-   * @return \Drupal\modeler_api\Plugin\ModelerInterface|null
-   *   Returns the modeller plugin if possible, NULL otherwise.
-   */
-  public function getModeller(): ?ModelerInterface {
-    try {
-      /**
-       * @var \Drupal\modeler_api\Plugin\ModelerInterface $plugin
-       */
-      $plugin = $this->modellerPluginManager()->createInstance($this->get('modeller'));
-    }
-    catch (PluginException $e) {
-      $this->logger()->error($e->getMessage());
-      return NULL;
-    }
-    $plugin->setConfigEntity($this);
-    return $plugin;
-  }
-
-  /**
-   * Determines if the current ECA model has model data.
-   *
-   * @return bool
-   *   TRUE, if the current ECA has model data, FALSE otherwise.
-   */
-  public function hasModel(): bool {
-    return $this->getModel()->getModeldata() !== '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getModel(): Model {
-    if (!isset($this->model)) {
-      try {
-        $storage = $this->entityTypeManager()->getStorage('modeler_api_model');
-      }
-      catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
-        // @todo Log this exception.
-        // This should be impossible to ever happen, because this module is
-        // providing that storage handler.
-        return $this->model;
-      }
-      /**
-       * @var \Drupal\modeler_api\Entity\Model|null $model
-       */
-      $model = $storage->load($this->id());
-      if ($model === NULL) {
-        /**
-         * @var \Drupal\modeler_api\Entity\Model $model
-         */
-        $model = $storage->create([
-          'id' => $this->id(),
-        ]);
-      }
-      $this->model = $model;
-    }
-    return $this->model;
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/src/Entity/Model.php b/src/Entity/Model.php
deleted file mode 100644
index 9da49a913..000000000
--- a/src/Entity/Model.php
+++ /dev/null
@@ -1,176 +0,0 @@
-<?php
-
-namespace Drupal\eca\Entity;
-
-use Drupal\Core\Config\Entity\ConfigEntityBase;
-use Drupal\modeler_api\Plugin\ModelerInterface;
-
-/**
- * Defines the ECA Model entity type.
- *
- * @deprecated in eca:3.0.0 and is removed from eca:4.0.0. Use
- * \Drupal\modeler_api\Entity\Model instead.
- *
- * @see https://www.drupal.org/node/TODO
- *
- * @ConfigEntityType(
- *   id = "eca_model",
- *   label = @Translation("ECA Model"),
- *   label_collection = @Translation("ECA Models"),
- *   label_singular = @Translation("ECA Model"),
- *   label_plural = @Translation("ECA Models"),
- *   label_count = @PluralTranslation(
- *     singular = "@count ECA Model",
- *     plural = "@count ECA Models",
- *   ),
- *   config_prefix = "model",
- *   entity_keys = {
- *     "id" = "id",
- *     "uuid" = "uuid",
- *     "label" = "label"
- *   },
- *   config_export = {
- *     "id",
- *     "label",
- *     "tags",
- *     "documentation",
- *     "filename",
- *     "modeldata"
- *   }
- * )
- */
-class Model extends ConfigEntityBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(array $values, $entity_type) {
-    parent::__construct($values, $entity_type);
-    @trigger_error(__CLASS__ . ' is deprecated in eca:3.0.0 and is removed from eca:4.0.0. Use Drupal\modeler_api\Entity\Model instead. See https://www.drupal.org/node/TODO', E_USER_DEPRECATED);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function calculateDependencies() {
-    parent::calculateDependencies();
-
-    /** @var \Drupal\eca\Entity\Eca|null $eca */
-    $eca = $this->entityTypeManager()->getStorage('eca')->load($this->id());
-    if ($eca) {
-      $this->addDependency('config', $eca->getConfigDependencyName());
-    }
-
-    return $this;
-  }
-
-  /**
-   * Set the filename or raw data of the model by the modeller.
-   *
-   * @param \Drupal\modeler_api\Plugin\ModelerInterface $modeller
-   *   The modeller instance which handles the model and can provide either the
-   *   filename or the raw data to be stored.
-   *
-   * @return $this
-   */
-  public function setData(ModelerInterface $modeller): Model {
-    $this
-      ->setLabel($modeller->getLabel())
-      ->setTags($modeller->getTags())
-      ->setDocumentation($modeller->getDocumentation())
-      ->setFilename($modeller->getFilename())
-      ->setModeldata($modeller->getModeldata());
-    return $this;
-  }
-
-  /**
-   * Set the label of this model.
-   *
-   * @return $this
-   */
-  public function setLabel(string $label): Model {
-    $this->set('label', $label);
-    return $this;
-  }
-
-  /**
-   * Set the tags of this model.
-   *
-   * @return $this
-   */
-  public function setTags(array $tags): Model {
-    $this->set('tags', empty($tags) ? ['untagged'] : $tags);
-    return $this;
-  }
-
-  /**
-   * Get the tags of this model.
-   *
-   * @return array
-   *   The tags of this model.
-   */
-  public function getTags(): array {
-    return $this->get('tags') ?? [];
-  }
-
-  /**
-   * Set the documentation of this model.
-   *
-   * @return $this
-   */
-  public function setDocumentation(string $documentation): Model {
-    $this->set('documentation', $documentation);
-    return $this;
-  }
-
-  /**
-   * Get the documentation of this model.
-   *
-   * @return string
-   *   The documentation.
-   */
-  public function getDocumentation(): string {
-    return $this->get('documentation') ?? '';
-  }
-
-  /**
-   * Set the external filename of this model.
-   *
-   * @return $this
-   */
-  public function setFilename(string $filename): Model {
-    $this->set('filename', $filename);
-    return $this;
-  }
-
-  /**
-   * Get the external filename of this model.
-   *
-   * @return string
-   *   The external filename.
-   */
-  public function getFilename(): string {
-    return $this->get('filename') ?? '';
-  }
-
-  /**
-   * Set the external filename of this model.
-   *
-   * @return $this
-   */
-  public function setModeldata(string $modeldata): Model {
-    $this->set('modeldata', $modeldata);
-    return $this;
-  }
-
-  /**
-   * Get the raw model data of this model.
-   *
-   * @return string
-   *   The raw model data.
-   */
-  public function getModeldata(): string {
-    return $this->get('modeldata') ?? '';
-  }
-
-}
diff --git a/src/Service/Conditions.php b/src/Service/Conditions.php
index 43b3f11ac..ddd1877fd 100644
--- a/src/Service/Conditions.php
+++ b/src/Service/Conditions.php
@@ -14,18 +14,18 @@ use Drupal\eca\Token\TokenInterface;
 use Symfony\Contracts\EventDispatcher\Event;
 
 /**
- * Service class for Drupal core conditions in ECA.
+ * Service class for condition plugins in ECA.
  */
 class Conditions {
 
   use ErrorHandlerTrait;
   use ServiceTrait;
 
-  public const GATEWAY_TYPE_EXCLUSIVE = 0;
-  public const GATEWAY_TYPE_PARALLEL = 1;
-  public const GATEWAY_TYPE_INCLUSIVE = 2;
-  public const GATEWAY_TYPE_COMPLEX = 3;
-  public const GATEWAY_TYPE_EVENTBASED = 4;
+  public const int GATEWAY_TYPE_EXCLUSIVE = 0;
+  public const int GATEWAY_TYPE_PARALLEL = 1;
+  public const int GATEWAY_TYPE_INCLUSIVE = 2;
+  public const int GATEWAY_TYPE_COMPLEX = 3;
+  public const int GATEWAY_TYPE_EVENTBASED = 4;
 
   /**
    * ECA condition plugin manager.
diff --git a/src/Service/Events.php b/src/Service/Events.php
new file mode 100644
index 000000000..384465d60
--- /dev/null
+++ b/src/Service/Events.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Drupal\eca\Service;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Logger\LoggerChannelInterface;
+use Drupal\eca\ErrorHandlerTrait;
+use Drupal\eca\PluginManager\Event;
+
+/**
+ * Service class for event plugins in ECA.
+ */
+class Events {
+
+  use ErrorHandlerTrait;
+  use ServiceTrait;
+
+  /**
+   * The ECA event plugin manager.
+   *
+   * @var \Drupal\eca\PluginManager\Event
+   */
+  protected Event $eventManager;
+
+  /**
+   * Logger channel service.
+   *
+   * @var \Drupal\Core\Logger\LoggerChannelInterface
+   */
+  protected LoggerChannelInterface $logger;
+
+  /**
+   * Events constructor.
+   *
+   * @param \Drupal\eca\PluginManager\Event $event_manager
+   *   The ECA event plugin manager.
+   * @param \Drupal\Core\Logger\LoggerChannelInterface $logger
+   *   Logger channel service.
+   */
+  public function __construct(Event $event_manager, LoggerChannelInterface $logger) {
+    $this->eventManager = $event_manager;
+    $this->logger = $logger;
+  }
+
+  /**
+   * Returns a sorted list of event plugins.
+   *
+   * @return \Drupal\eca\Plugin\ECA\Event\EventInterface[]
+   *   The sorted list of events.
+   */
+  public function events(): array {
+    static $events;
+    if ($events === NULL) {
+      $this->enableExtendedErrorHandling('Collecting all available events');
+      $events = [];
+      foreach ($this->eventManager->getDefinitions() as $plugin_id => $definition) {
+        try {
+          /** @var \Drupal\eca\Plugin\ECA\Event\EventInterface $plugin */
+          $plugin = $this->eventManager->createInstance($plugin_id);
+          $events[] = $plugin;
+        }
+        catch (PluginException | \Throwable) {
+          // Can be ignored.
+        }
+      }
+      $this->resetExtendedErrorHandling();
+      $this->sortPlugins($events);
+    }
+    return $events;
+  }
+
+}
diff --git a/src/Service/ExportRecipe.php b/src/Service/ExportRecipe.php
deleted file mode 100644
index 6e9fd9f6b..000000000
--- a/src/Service/ExportRecipe.php
+++ /dev/null
@@ -1,292 +0,0 @@
-<?php
-
-namespace Drupal\eca\Service;
-
-use Drupal\Component\Serialization\Yaml;
-use Drupal\Core\Config\ManagedStorage;
-use Drupal\Core\Extension\ModuleExtensionList;
-use Drupal\Core\File\FileExists;
-use Drupal\Core\File\FileSystem;
-use Drupal\Core\File\FileSystemInterface;
-use Drupal\Core\Messenger\Messenger;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\eca\Entity\Eca;
-
-/**
- * Service provides export to recipe functionality for ECA models.
- */
-class ExportRecipe {
-
-  use StringTranslationTrait;
-
-  public const DEFAULT_NAMESPACE = 'drupal-eca-recipe';
-  public const DEFAULT_DESTINATION = 'temporary://recipe';
-
-  /**
-   * Constructs the recipe export service.
-   *
-   * @param \Drupal\Core\Config\ManagedStorage $configStorage
-   *   The config storage.
-   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
-   *   The file system.
-   * @param \Drupal\Core\Extension\ModuleExtensionList $moduleExtensionList
-   *   The module extension list.
-   * @param \Drupal\eca\Service\Modellers $modellerService
-   *   The ECA modeller service.
-   * @param \Drupal\Core\Messenger\Messenger $messenger
-   *   The messenger.
-   */
-  public function __construct(
-    protected readonly ManagedStorage $configStorage,
-    protected readonly FileSystemInterface $fileSystem,
-    protected readonly ModuleExtensionList $moduleExtensionList,
-    protected readonly Modellers $modellerService,
-    protected readonly Messenger $messenger,
-  ) {}
-
-  /**
-   * Exports the given ECA model to a recipe.
-   *
-   * @param \Drupal\eca\Entity\Eca $eca
-   *   The ECA model.
-   * @param string|null $name
-   *   The name of the model.
-   * @param string $namespace
-   *   The namespace to use for composer.
-   * @param string $destination
-   *   The directory, where to store the recipe.
-   */
-  public function doExport(Eca $eca, ?string $name = NULL, string $namespace = self::DEFAULT_NAMESPACE, string $destination = self::DEFAULT_DESTINATION): void {
-    $destination = rtrim($destination, '/');
-    $configDestination = $destination . '/config';
-    $composerJson = $destination . '/composer.json';
-    $recipeYml = $destination . '/recipe.yml';
-    $readmeMd = $destination . '/README.md';
-    if (file_exists($configDestination) && !$this->fileSystem->deleteRecursive($configDestination)) {
-      $this->messenger->addError($this->t('A config directory already exists in the given destination and can not be removed.'));
-      return;
-    }
-    if (file_exists($composerJson) && !$this->fileSystem->unlink($composerJson)) {
-      $this->messenger->addError($this->t('A composer.json already exists in the given destination and can not be removed.'));
-      return;
-    }
-    if (file_exists($recipeYml) && !$this->fileSystem->unlink($recipeYml)) {
-      $this->messenger->addError($this->t('A recipe.yml already exists in the given destination and can not be removed.'));
-      return;
-    }
-    if (file_exists($readmeMd) && !$this->fileSystem->unlink($readmeMd)) {
-      $this->messenger->addError($this->t('A README.md already exists in the given destination and can not be removed.'));
-      return;
-    }
-    if (!$this->fileSystem->mkdir($configDestination, FileSystem::CHMOD_DIRECTORY, TRUE)) {
-      $this->messenger->addError($this->t('The destination does not exist or is not writable.'));
-      return;
-    }
-    if (!is_writable($configDestination)) {
-      $this->messenger->addError($this->t('The destination is not writable.'));
-      return;
-    }
-    $this->fileSystem->prepareDirectory($destination);
-    $this->fileSystem->prepareDirectory($configDestination);
-
-    if ($name === NULL) {
-      $name = $this->defaultName($eca);
-    }
-    $description = $eca->getModel()->getDocumentation();
-    $dependencies = [
-      'config' => [
-        'eca.eca.' . $eca->id(),
-      ],
-      'module' => [],
-    ];
-    $comesWithEcaModel = $this->configStorage->read('modeler_api.model.' . $eca->id());
-    if ($comesWithEcaModel) {
-      $dependencies['config'][] = 'model  er_api.model.' . $eca->id();
-    }
-    $this->modellerService->getNestedDependencies($dependencies, $eca->getDependencies());
-
-    $actions = [];
-    $imports = [];
-    foreach ($dependencies['config'] as $configName) {
-      $config = $this->configStorage->read($configName);
-      if (!$config) {
-        continue;
-      }
-      unset($config['uuid'], $config['_core']);
-      if (str_starts_with($configName, 'user.role.')) {
-        $actions[$configName] = [
-          'ensure_exists' => [
-            'label' => $config['label'],
-          ],
-          'grantPermissions' => $config['permissions'],
-        ];
-      }
-      else {
-        $canBeImported = FALSE;
-        foreach ($config['dependencies']['module'] ?? [] as $module) {
-          if ($this->isProvidedByModule($module, $configName)) {
-            $imports[$module][] = $configName;
-            $canBeImported = TRUE;
-            break;
-          }
-        }
-        if (!$canBeImported) {
-          $this->fileSystem->saveData(Yaml::encode($config), $configDestination . '/' . $configName . '.yml', FileExists::Replace);
-        }
-      }
-    }
-
-    $this->fileSystem->saveData(json_encode($this->getComposer($eca->id(), $namespace, $name, $dependencies['module']), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) . PHP_EOL, $composerJson, FileExists::Replace);
-    $this->fileSystem->saveData(Yaml::encode($this->getRecipe($name, $description, $dependencies['module'], $actions, $imports)), $recipeYml, FileExists::Replace);
-    $this->fileSystem->saveData($this->getReadme($eca->id(), $name, $description, $namespace), $readmeMd, FileExists::Replace);
-  }
-
-  /**
-   * Gets the default name for the recipe.
-   *
-   * @param \Drupal\eca\Entity\Eca $eca
-   *   The ECA model.
-   *
-   * @return string
-   *   The default name for the recipe.
-   */
-  public function defaultName(Eca $eca): string {
-    return (string) $eca->label();
-  }
-
-  /**
-   * Helper function to determine if a config name is provided by given module.
-   *
-   * @param string $module
-   *   The module.
-   * @param string $configName
-   *   The config name.
-   *
-   * @return bool
-   *   TRUE, if that module provides that config, FALSE otherwise.
-   */
-  private function isProvidedByModule(string $module, string $configName): bool {
-    $pathname = $this->fileSystem->dirName($this->moduleExtensionList->getPathname($module));
-    foreach (['install', 'optional'] as $item) {
-      if (file_exists($pathname . '/config/' . $item . '/' . $configName . '.yml')) {
-        return TRUE;
-      }
-    }
-    return FALSE;
-  }
-
-  /**
-   * Builds the content of the composer.json file.
-   *
-   * @param string $id
-   *   The recipe ID.
-   * @param string $namespace
-   *   The namespace.
-   * @param string $description
-   *   The recipe description.
-   * @param array $modules
-   *   The list of required module names.
-   *
-   * @return string[]
-   *   The content of the composer.json file as an array.
-   */
-  protected function getComposer(string $id, string $namespace, string $description, array $modules = []): array {
-    $composer = [
-      'name' => $namespace . '/' . $id,
-      'type' => 'drupal-recipe',
-      'description' => $description,
-      'license' => 'GPL-2.0-or-later',
-    ];
-    if ($modules) {
-      $composer['require'] = [
-        'drupal/core' => '>=10.3',
-      ];
-      $list = $this->moduleExtensionList->getList();
-      foreach ($modules as $module) {
-        $path = $this->moduleExtensionList->getPath($module);
-        if (!str_starts_with($path, 'core/modules')) {
-          foreach ($list[$module]->requires ?? [] as $key => $dependency) {
-            if (str_starts_with($path, $this->moduleExtensionList->getPath($key) . '/')) {
-              $module = $key;
-              break;
-            }
-          }
-          $composer['require']['drupal/' . $module] = '*';
-        }
-      }
-    }
-    return $composer;
-  }
-
-  /**
-   * Builds the content of the recipe file.
-   *
-   * @param string $name
-   *   The recipe name.
-   * @param string $description
-   *   The recipe description.
-   * @param array $modules
-   *   The list of required modules.
-   * @param array $actions
-   *   The list of config actions.
-   * @param array $imports
-   *   The list of config imports keyed by module name.
-   *
-   * @return string[]
-   *   The content of the recipe file as an array.
-   */
-  protected function getRecipe(string $name, string $description, array $modules = [], array $actions = [], array $imports = []): array {
-    $recipe = [
-      'name' => $name,
-      'description' => $description,
-      'type' => 'ECA',
-    ];
-    if ($modules) {
-      $recipe['install'] = $modules;
-    }
-    if ($actions) {
-      $recipe['config']['actions'] = $actions;
-    }
-    if ($imports) {
-      $recipe['config']['import'] = $imports;
-    }
-    return $recipe;
-  }
-
-  /**
-   * Builds the content of the readme file.
-   *
-   * @param string $id
-   *   The ID of the recipe.
-   * @param string $name
-   *   The recipe name.
-   * @param string $description
-   *   The recipe description.
-   * @param string $namespace
-   *   The namespace.
-   *
-   * @return string
-   *   The content of the readme file.
-   */
-  protected function getReadme(string $id, string $name, string $description, string $namespace): string {
-    $description = str_replace(['](/', '.md)'], [
-      '](https://ecaguide.org/',
-      ')',
-    ], $description);
-    return <<<end_of_readme
-## ECA Recipe: $name
-
-ID: $id
-
-$description
-
-### Installation
-
-```shell
-composer require $namespace/$id
-cd web && php core/scripts/drupal recipe ../vendor/$namespace/$id
-```
-end_of_readme;
-  }
-
-}
diff --git a/src/Service/Modellers.php b/src/Service/Modellers.php
deleted file mode 100644
index a71ba9d1b..000000000
--- a/src/Service/Modellers.php
+++ /dev/null
@@ -1,368 +0,0 @@
-<?php
-
-namespace Drupal\eca\Service;
-
-use Drupal\Component\Plugin\Exception\PluginException;
-use Drupal\Component\Serialization\Yaml;
-use Drupal\Core\Archiver\ArchiveTar;
-use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\Config\StorageInterface;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\File\Exception\FileException;
-use Drupal\Core\File\FileSystemInterface;
-use Drupal\Core\Logger\LoggerChannelInterface;
-use Drupal\eca\Entity\Eca;
-use Drupal\eca\ErrorHandlerTrait;
-use Drupal\eca\PluginManager\Event;
-use Drupal\modeler_api\Plugin\ModelerPluginManager;
-use Drupal\modeler_api\Plugin\ModelerInterface;
-
-/**
- * Service class for ECA modellers.
- */
-class Modellers {
-
-  use ErrorHandlerTrait;
-  use ServiceTrait;
-
-  /**
-   * ECA config entity storage manager.
-   *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
-   */
-  protected EntityStorageInterface $configStorage;
-
-  /**
-   * ECA model storage manager.
-   *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
-   */
-  protected EntityStorageInterface $modelStorage;
-
-  /**
-   * ECA modeller plugin manager.
-   *
-   * @var \Drupal\modeler_api\Plugin\ModelerPluginManager
-   */
-  protected ModelerPluginManager $pluginManagerModeller;
-
-  /**
-   * ECA event plugin manager.
-   *
-   * @var \Drupal\eca\PluginManager\Event
-   */
-  protected Event $pluginManagerEvent;
-
-  /**
-   * ECA action services.
-   *
-   * @var \Drupal\eca\Service\Actions
-   */
-  protected Actions $actionServices;
-
-  /**
-   * ECA condition services.
-   *
-   * @var \Drupal\eca\Service\Conditions
-   */
-  protected Conditions $conditionServices;
-
-  /**
-   * Logger channel service.
-   *
-   * @var \Drupal\Core\Logger\LoggerChannelInterface
-   */
-  protected LoggerChannelInterface $logger;
-
-  /**
-   * File system service.
-   *
-   * @var \Drupal\Core\File\FileSystemInterface
-   */
-  protected FileSystemInterface $fileSystem;
-
-  /**
-   * Export storage service.
-   *
-   * @var \Drupal\Core\Config\StorageInterface
-   */
-  protected StorageInterface $exportStorage;
-
-  /**
-   * Config factory service.
-   *
-   * @var \Drupal\Core\Config\ConfigFactoryInterface
-   */
-  protected ConfigFactoryInterface $configFactory;
-
-  /**
-   * Modellers constructor.
-   *
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
-   *   The entity type manager service.
-   * @param \Drupal\modeler_api\Plugin\ModelerPluginManager $plugin_manager_modeller
-   *   The ECA modeller plugin manager.
-   * @param \Drupal\eca\PluginManager\Event $plugin_manager_event
-   *   The ECA event plugin manager.
-   * @param \Drupal\eca\Service\Actions $action_services
-   *   The ECA action service.
-   * @param \Drupal\eca\Service\Conditions $condition_services
-   *   The ECA condition service.
-   * @param \Drupal\Core\Logger\LoggerChannelInterface $logger
-   *   The logger channel service.
-   * @param \Drupal\Core\File\FileSystemInterface $file_system
-   *   The file system service.
-   * @param \Drupal\Core\Config\StorageInterface $export_storage
-   *   The export storage service.
-   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
-   *   The config factory service.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
-   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
-   */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, ModelerPluginManager $plugin_manager_modeller, Event $plugin_manager_event, Actions $action_services, Conditions $condition_services, LoggerChannelInterface $logger, FileSystemInterface $file_system, StorageInterface $export_storage, ConfigFactoryInterface $config_factory) {
-    $this->configStorage = $entity_type_manager->getStorage('eca');
-    $this->modelStorage = $entity_type_manager->getStorage('modeler_api_model');
-    $this->pluginManagerModeller = $plugin_manager_modeller;
-    $this->pluginManagerEvent = $plugin_manager_event;
-    $this->actionServices = $action_services;
-    $this->conditionServices = $condition_services;
-    $this->logger = $logger;
-    $this->fileSystem = $file_system;
-    $this->exportStorage = $export_storage;
-    $this->configFactory = $config_factory;
-  }
-
-  /**
-   * Loads the given Eca config entity by its ID.
-   *
-   * @param string $id
-   *   The ID of an ECA model.
-   *
-   * @return \Drupal\eca\Entity\Eca|null
-   *   The Eca config entity if available, NULL otherwise.
-   */
-  public function loadModel(string $id): ?Eca {
-    /** @var \Drupal\eca\Entity\Eca $eca */
-    $eca = $this->configStorage->load(mb_strtolower($id));
-    return $eca;
-  }
-
-  /**
-   * Save a model as config.
-   *
-   * @param \Drupal\modeler_api\Plugin\ModelerInterface $modeller
-   *   The modeller controlling the ECA config entity.
-   *
-   * @return bool
-   *   Returns TRUE, if a reload of the saved model is required. That's the case
-   *   when this is either a new model or if the label had changed. It returns
-   *   FALSE otherwise, if none of those conditions applies.
-   *
-   * @throws \Drupal\Core\Entity\EntityStorageException
-   * @throws \LogicException
-   */
-  public function saveModel(ModelerInterface $modeller): bool {
-    $id = mb_strtolower($modeller->getId());
-    /** @var \Drupal\eca\Entity\Eca|null $config */
-    $config = $this->configStorage->load($id);
-    if ($config === NULL) {
-      /**
-       * @var \Drupal\eca\Entity\Eca $config
-       */
-      $config = $this->configStorage->create([
-        'id' => $id,
-        'modeller' => $modeller->getPluginId(),
-      ]);
-      $requiresReload = TRUE;
-    }
-    else {
-      $requiresReload = $config->label() !== $modeller->getLabel();
-    }
-    $config
-      ->set('label', $modeller->getLabel())
-      ->set('status', $modeller->getStatus())
-      ->set('version', $modeller->getVersion())
-      ->set('events', [])
-      ->set('conditions', [])
-      ->set('actions', []);
-    $modeller->readComponents($config);
-    if ($modeller->hasError()) {
-      // If the model contains error(s), don't save it and do not ask for a
-      // page reload, because that would cause data loss.
-      return FALSE;
-    }
-    // Only save model if reading its components succeeded without errors.
-    $config->save();
-    $config->getModel()
-      ->setData($modeller)
-      ->save();
-    return $requiresReload;
-  }
-
-  /**
-   * Gets a list of all available modeller plugin definitions.
-   *
-   * @return array
-   *   The list of modeller plugin definitions indexed by their ID.
-   */
-  public function getModellerDefinitions(): array {
-    return $this->pluginManagerModeller->getDefinitions();
-  }
-
-  /**
-   * Returns an instance of the modeller for the given id.
-   *
-   * @param string $plugin_id
-   *   The id of the modeller plugin.
-   *
-   * @return \Drupal\modeler_api\Plugin\ModelerInterface|null
-   *   The modeller instance, or NULL if the plugin doesn't exist.
-   */
-  public function getModeller(string $plugin_id): ?ModelerInterface {
-    try {
-      /**
-       * @var \Drupal\modeler_api\Plugin\ModelerInterface $modeller
-       */
-      $modeller = $this->pluginManagerModeller->createInstance($plugin_id);
-    }
-    catch (PluginException) {
-      return NULL;
-    }
-    return $modeller;
-  }
-
-  /**
-   * Returns a sorted list of event plugins.
-   *
-   * @return \Drupal\eca\Plugin\ECA\Event\EventInterface[]
-   *   The sorted list of events.
-   */
-  public function events(): array {
-    static $events;
-    if ($events === NULL) {
-      $this->enableExtendedErrorHandling('Collecting all available events');
-      $events = [];
-      foreach ($this->pluginManagerEvent->getDefinitions() as $plugin_id => $definition) {
-        try {
-          /** @var \Drupal\eca\Plugin\ECA\Event\EventInterface $plugin */
-          $plugin = $this->pluginManagerEvent->createInstance($plugin_id);
-          $events[] = $plugin;
-        }
-        catch (PluginException | \Throwable) {
-          // Can be ignored.
-        }
-      }
-      $this->resetExtendedErrorHandling();
-      $this->sortPlugins($events);
-    }
-    return $events;
-  }
-
-  /**
-   * Export components for all ECA modellers.
-   */
-  public function exportTemplates(): void {
-    foreach ($this->pluginManagerModeller->getDefinitions() as $plugin_id => $definition) {
-      try {
-        /**
-         * @var \Drupal\modeler_api\Plugin\ModelerInterface $modeller
-         */
-        $modeller = $this->pluginManagerModeller->createInstance($plugin_id);
-        $modeller->exportTemplates();
-      }
-      catch (PluginException) {
-        // Can be ignored.
-      }
-    }
-  }
-
-  /**
-   * Exports the ECA config with all dependencies into an archive.
-   *
-   * @param \Drupal\eca\Entity\Eca $eca
-   *   The ECA config entity.
-   * @param string $archiveFileName
-   *   The fully qualified filename for the archive.
-   *
-   * @return array
-   *   An array with "config" and "module" keys, each containing the list of
-   *   dependencies.
-   */
-  public function exportArchive(Eca $eca, string $archiveFileName): array {
-    $dependencies = [
-      'config' => [
-        'eca.eca.' . $eca->id(),
-      ],
-      'module' => [],
-    ];
-    $comesWithEcaModel = $this->exportStorage->read('modeler_api.model.' . $eca->id());
-    if ($comesWithEcaModel) {
-      $dependencies['config'][] = 'modeler_api.model.' . $eca->id();
-    }
-    $this->getNestedDependencies($dependencies, $eca->getDependencies());
-    if (file_exists($archiveFileName)) {
-      try {
-        @$this->fileSystem->delete($archiveFileName);
-      }
-      catch (FileException) {
-        // Ignore failed deletes.
-      }
-    }
-    $archiver = new ArchiveTar($archiveFileName, 'gz');
-    foreach ($dependencies['config'] as $name) {
-      $config = $this->exportStorage->read($name);
-      if ($config) {
-        unset($config['uuid'], $config['_core']);
-        $archiver->addString("$name.yml", Yaml::encode($config));
-      }
-    }
-    $archiver->addString('dependencies.yml', Yaml::encode($dependencies));
-
-    // Remove "'eca.eca.ID" from the config dependencies.
-    array_shift($dependencies['config']);
-    if ($comesWithEcaModel) {
-      // Also remove "'moder_api.model.ID" from the config dependencies.
-      array_shift($dependencies['config']);
-    }
-    foreach ($dependencies as $type => $values) {
-      if (empty($values)) {
-        unset($dependencies[$type]);
-      }
-      else {
-        sort($dependencies[$type]);
-      }
-    }
-    return $dependencies;
-  }
-
-  /**
-   * Recursively determines config dependencies.
-   *
-   * @param array $allDependencies
-   *   The list of all dependencies.
-   * @param array $dependencies
-   *   The list of dependencies to be added.
-   */
-  public function getNestedDependencies(array &$allDependencies, array $dependencies): void {
-    foreach ($dependencies['module'] ?? [] as $module) {
-      if (!in_array($module, $allDependencies['module'], TRUE)) {
-        $allDependencies['module'][] = $module;
-      }
-    }
-    if (empty($dependencies['config'])) {
-      return;
-    }
-    foreach ($dependencies['config'] as $dependency) {
-      if (!in_array($dependency, $allDependencies['config'], TRUE)) {
-        $allDependencies['config'][] = $dependency;
-        $depConfig = $this->configFactory->get($dependency)->getStorage()->read($dependency);
-        if ($depConfig && isset($depConfig['dependencies'])) {
-          $this->getNestedDependencies($allDependencies, $depConfig['dependencies']);
-        }
-      }
-    }
-  }
-
-}
-- 
GitLab


From f541f650603a7df2f7949f12f0362da514ce8c0b Mon Sep 17 00:00:00 2001
From: jurgenhaas <juergen.haas@lakedrops.com>
Date: Fri, 4 Apr 2025 14:51:38 +0200
Subject: [PATCH 18/22] Issue #3513175 by benjifisher, jurgenhaas,
 cosmicdreams: Remove code that is added to the Modeler API module

---
 modules/ui/eca_ui.links.action.yml            |   5 -
 modules/ui/eca_ui.links.menu.yml              |  15 --
 modules/ui/eca_ui.links.task.yml              |  12 --
 modules/ui/eca_ui.routing.yml                 |  15 --
 modules/ui/eca_ui.services.yml                |   6 -
 modules/ui/src/Entity/ListBuilder.php         | 201 ------------------
 modules/ui/src/Form/Settings.php              |  11 -
 .../Plugin/modeler_api_model_owner/Eca.php    |  35 +--
 .../ui/src/Service/TokenBrowserService.php    |  49 -----
 .../Unit/Service/TokenBrowserServiceTest.php  |  46 ----
 10 files changed, 6 insertions(+), 389 deletions(-)
 delete mode 100644 modules/ui/eca_ui.links.action.yml
 delete mode 100644 modules/ui/eca_ui.links.menu.yml
 delete mode 100644 modules/ui/eca_ui.links.task.yml
 delete mode 100644 modules/ui/eca_ui.routing.yml
 delete mode 100644 modules/ui/eca_ui.services.yml
 delete mode 100644 modules/ui/src/Entity/ListBuilder.php
 delete mode 100644 modules/ui/src/Service/TokenBrowserService.php
 delete mode 100644 modules/ui/tests/src/Unit/Service/TokenBrowserServiceTest.php

diff --git a/modules/ui/eca_ui.links.action.yml b/modules/ui/eca_ui.links.action.yml
deleted file mode 100644
index 71984431b..000000000
--- a/modules/ui/eca_ui.links.action.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-eca.add:
-  route_name: eca.add
-  title: 'Add new model'
-  appears_on:
-    - entity.eca.collection
diff --git a/modules/ui/eca_ui.links.menu.yml b/modules/ui/eca_ui.links.menu.yml
deleted file mode 100644
index 3a99af6b9..000000000
--- a/modules/ui/eca_ui.links.menu.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-entity.eca.collection:
-  title: 'ECA'
-  parent: system.admin_config_workflow
-  description: 'Create, edit and manage ECA models.'
-  route_name: entity.eca.collection
-eca.import:
-  title: 'Import'
-  parent: entity.eca.collection
-  description: 'Import an ECA model.'
-  route_name: eca.import
-eca.settings:
-  title: 'Settings'
-  parent: entity.eca.collection
-  description: 'Configure ECA settings.'
-  route_name: eca.settings
diff --git a/modules/ui/eca_ui.links.task.yml b/modules/ui/eca_ui.links.task.yml
deleted file mode 100644
index 1e0d6601e..000000000
--- a/modules/ui/eca_ui.links.task.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-entity.eca.collection:
-  title: Models
-  route_name: entity.eca.collection
-  base_route: entity.eca.collection
-eca.import:
-  title: Import
-  route_name: eca.import
-  base_route: entity.eca.collection
-eca.settings:
-  title: Settings
-  route_name: eca.settings
-  base_route: entity.eca.collection
diff --git a/modules/ui/eca_ui.routing.yml b/modules/ui/eca_ui.routing.yml
deleted file mode 100644
index 599069c92..000000000
--- a/modules/ui/eca_ui.routing.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-entity.eca.collection:
-  path: '/admin/config/workflow/eca'
-  defaults:
-    _entity_list: 'eca'
-    _title: 'Configure ECA - Events, Conditions, Actions'
-  requirements:
-    _permission: 'administer eca'
-
-eca.settings:
-  path: '/admin/config/workflow/eca/settings'
-  defaults:
-    _title: 'Settings'
-    _form: '\Drupal\eca_ui\Form\Settings'
-  requirements:
-    _permission: 'administer eca'
diff --git a/modules/ui/eca_ui.services.yml b/modules/ui/eca_ui.services.yml
deleted file mode 100644
index ced55a328..000000000
--- a/modules/ui/eca_ui.services.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-services:
-
-  eca_ui.service.token_browser:
-    class: Drupal\eca_ui\Service\TokenBrowserService
-    arguments:
-      - '@module_handler'
diff --git a/modules/ui/src/Entity/ListBuilder.php b/modules/ui/src/Entity/ListBuilder.php
deleted file mode 100644
index a268bf0db..000000000
--- a/modules/ui/src/Entity/ListBuilder.php
+++ /dev/null
@@ -1,201 +0,0 @@
-<?php
-
-namespace Drupal\eca_ui\Entity;
-
-use Drupal\Core\Config\Entity\DraggableListBuilder;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Url;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Defines a class to build a listing of ECA config entities.
- *
- * @see \Drupal\eca\Entity\Eca
- */
-class ListBuilder extends DraggableListBuilder {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $entitiesKey = 'eca_entities';
-
-  /**
-   * The messenger.
-   *
-   * @var \Drupal\Core\Messenger\MessengerInterface
-   */
-  protected $messenger;
-
-  /**
-   * This flag stores the calculated result for ::showModeller().
-   *
-   * @var bool|null
-   */
-  protected ?bool $showModeller;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type): static {
-    $instance = parent::createInstance($container, $entity_type);
-    $instance->messenger = $container->get('messenger');
-    return $instance;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId(): string {
-    return 'eca_collection';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildHeader(): array {
-    $header['model'] = $this->t('Model');
-    if ($this->showModeller()) {
-      $header['modeller'] = $this->t('Modeller');
-    }
-    $header['events'] = $this->t('Events');
-    $header['version'] = $this->t('Version');
-    $header['status'] = $this->t('Enabled');
-    return $header + parent::buildHeader();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildRow(EntityInterface $entity): array {
-    /** @var \Drupal\eca\Entity\Eca $eca */
-    $eca = $entity;
-
-    $row['model'] = ['#markup' => $eca->label() ?: $eca->id()];
-    if ($this->showModeller()) {
-      $row['modeller'] = ['#markup' => (string) $eca->get('modeller')];
-    }
-    $row['events'] = [
-      '#theme' => 'item_list',
-      '#items' => $eca->getEventInfos(),
-      '#attributes' => [
-        'class' => ['eca-event-list'],
-      ],
-    ];
-    $row['version'] = ['#markup' => $eca->get('version') ?: $this->t('undefined')];
-    $row['status'] = [
-      '#markup' => $eca->status() ? $this->t('yes') : $this->t('no'),
-    ];
-
-    foreach (['model', 'events', 'version', 'status'] as $cell) {
-      $row[$cell]['#wrapper_attributes'] = [
-        'data-drupal-selector' => 'models-table-filter-text-source',
-      ];
-    }
-
-    return $row + parent::buildRow($entity);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state): array {
-    $form = parent::buildForm($form, $form_state);
-    $form['actions']['submit']['#value'] = $this->t('Save');
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state): void {
-    parent::submitForm($form, $form_state);
-    $this->messenger->addStatus($this->t('The ordering has been saved.'));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDefaultOperations(EntityInterface $entity): array {
-    $operations = parent::getDefaultOperations($entity);
-
-    if ($entity->access('update')) {
-      $operations['edit'] = [
-        'title' => $this->t('Edit'),
-        'weight' => 10,
-        'url' => Url::fromRoute('entity.eca.edit_form', ['eca' => $entity->id()]),
-      ];
-    }
-
-    return $operations;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function render(): array {
-
-    $list = parent::render();
-
-    $list['#attached']['library'][] = 'eca_ui/eca_ui.listing';
-
-    $list['filters'] = [
-      '#type' => 'container',
-      '#attributes' => [
-        'class' => [
-          'table-filter',
-          'js-show',
-        ],
-      ],
-      '#weight' => -1,
-    ];
-
-    $list['filters']['text'] = [
-      '#type' => 'search',
-      '#title' => $this
-        ->t('Filter'),
-      '#title_display' => 'invisible',
-      '#size' => 60,
-      '#placeholder' => $this
-        ->t('Filter by model name, events or version'),
-      '#attributes' => [
-        'class' => [
-          'models-filter-text',
-        ],
-        'data-table' => '#edit-eca-entities',
-        'autocomplete' => 'off',
-        'title' => $this
-          ->t('Enter a part of the model name, event name or version to filter by.'),
-      ],
-    ];
-
-    return $list;
-  }
-
-  /**
-   * Determines whether the modeller info should be displayed or not.
-   *
-   * @return bool
-   *   Returns TRUE if the modeller info should be displayed, FALSE otherwise.
-   */
-  protected function showModeller(): bool {
-    if (!isset($this->showModeller)) {
-      $modellers = [];
-      /**
-       * @var \Drupal\eca\Entity\Eca $eca
-       */
-      foreach ($this->storage->loadMultiple() as $eca) {
-        if ($eca->get('modeller')) {
-          $modellers[$eca->get('modeller')] = TRUE;
-        }
-        else {
-          $modellers['_none'] = TRUE;
-        }
-      }
-      $this->showModeller = count($modellers) > 1;
-    }
-    return $this->showModeller;
-  }
-
-}
diff --git a/modules/ui/src/Form/Settings.php b/modules/ui/src/Form/Settings.php
index 3338ec3bb..a6e9541fd 100644
--- a/modules/ui/src/Form/Settings.php
+++ b/modules/ui/src/Form/Settings.php
@@ -7,7 +7,6 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Logger\RfcLogLevel;
-use Drupal\eca_ui\Service\TokenBrowserService;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -22,13 +21,6 @@ class Settings extends ConfigFormBase {
    */
   protected ?string $defaultDocumentationDomain;
 
-  /**
-   * Token browser.
-   *
-   * @var \Drupal\eca_ui\Service\TokenBrowserService
-   */
-  protected TokenBrowserService $tokenBrowser;
-
   /**
    * The entity type manager.
    *
@@ -43,7 +35,6 @@ class Settings extends ConfigFormBase {
     /** @var \Drupal\eca_ui\Form\Settings $instance */
     $instance = parent::create($container);
     $instance->defaultDocumentationDomain = $container->getParameter('eca.default_documentation_domain');
-    $instance->tokenBrowser = $container->get('eca_ui.service.token_browser');
     $instance->entityTypeManager = $container->get('entity_type.manager');
     return $instance;
   }
@@ -97,8 +88,6 @@ class Settings extends ConfigFormBase {
       '#default_value' => $config->get('service_user'),
       '#weight' => 5,
     ];
-    $form['token_browser'] = $this->tokenBrowser->getTokenBrowserMarkup();
-    $form['token_browser']['#weight'] = 10;
     $form['dependency_calculation'] = [
       '#type' => 'details',
       '#title' => $this->t('Dependency calculation'),
diff --git a/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php b/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
index 5bb9e3621..c8e0fed12 100644
--- a/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
+++ b/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
@@ -6,6 +6,8 @@ use Drupal\modeler_api\Plugin\modeler_api_model_owner\ModelOwnerBase;
 
 /**
  * Model owner plugins implementation for ECA.
+ *
+ * |label = 'Configure ECA - Events, Conditions, Actions'
  */
 class Eca extends ModelOwnerBase {
 
@@ -26,40 +28,15 @@ class Eca extends ModelOwnerBase {
   /**
    * {@inheritdoc}
    */
-  public function resetComponents(): void {
-    // @todo Implement resetComponents() method.
+  public function permission(): string {
+    return 'administer eca';
   }
 
   /**
    * {@inheritdoc}
    */
-  public function addCondition(string $id, string $plugin_id, array $fields): bool {
-    // @todo Implement addCondition() method.
-    return TRUE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addGateway(string $id, int $type, array $successors): bool {
-    // @todo Implement addGateway() method.
-    return TRUE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addEvent(string $id, string $plugin_id, string $label, array $fields, array $successors): bool {
-    // @todo Implement addEvent() method.
-    return TRUE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addAction(string $id, string $plugin_id, string $label, array $fields, array $successors): bool {
-    // @todo Implement addAction() method.
-    return TRUE;
+  public function settingsForm(): ?string {
+    return '\Drupal\eca_ui\Form\Settings';
   }
 
 }
diff --git a/modules/ui/src/Service/TokenBrowserService.php b/modules/ui/src/Service/TokenBrowserService.php
deleted file mode 100644
index 359d5006e..000000000
--- a/modules/ui/src/Service/TokenBrowserService.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-namespace Drupal\eca_ui\Service;
-
-use Drupal\Core\Extension\ModuleHandlerInterface;
-
-/**
- * Service class for Token Browser in ECA.
- */
-class TokenBrowserService {
-
-  /**
-   * Module handler service.
-   *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface
-   */
-  protected ModuleHandlerInterface $moduleHandler;
-
-  /**
-   * Constructs the token browser service.
-   *
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
-   *   The module handler service.
-   */
-  public function __construct(ModuleHandlerInterface $moduleHandler) {
-    $this->moduleHandler = $moduleHandler;
-  }
-
-  /**
-   * Returns the markup needed for the token browser.
-   *
-   * @return array
-   *   Markup for the token browser.
-   */
-  public function getTokenBrowserMarkup(): array {
-    if (!$this->moduleHandler->moduleExists('token')) {
-      return [];
-    }
-
-    return [
-      'tb' => [
-        '#type' => 'container',
-        '#theme' => 'token_tree_link',
-        '#token_types' => 'all',
-      ],
-    ];
-  }
-
-}
diff --git a/modules/ui/tests/src/Unit/Service/TokenBrowserServiceTest.php b/modules/ui/tests/src/Unit/Service/TokenBrowserServiceTest.php
deleted file mode 100644
index 001178455..000000000
--- a/modules/ui/tests/src/Unit/Service/TokenBrowserServiceTest.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-namespace Drupal\Tests\eca_ui\Unit\Service;
-
-use Drupal\Core\Extension\ModuleHandlerInterface;
-use Drupal\Tests\eca\Unit\EcaUnitTestBase;
-use Drupal\eca_ui\Service\TokenBrowserService;
-
-/**
- * Tests the token browser service.
- *
- * @group eca
- * @group eca_core
- */
-class TokenBrowserServiceTest extends EcaUnitTestBase {
-
-  /**
-   * Tests the token browser markup if contrib token module is not installed.
-   */
-  public function testTokenModuleNotInstalled(): void {
-    $moduleHandler = $this->createMock(ModuleHandlerInterface::class);
-    $tokenBrowserService = new TokenBrowserService($moduleHandler);
-    $this->assertEquals([], $tokenBrowserService->getTokenBrowserMarkup());
-  }
-
-  /**
-   * Tests the method getTokenBrowserMarkup.
-   */
-  public function testGetMarkup(): void {
-    $moduleHandler = $this->createMock(ModuleHandlerInterface::class);
-    $moduleHandler->expects($this->once())->method('moduleExists')
-      ->with('token')->willReturn(TRUE);
-
-    $tokenBrowserService = new TokenBrowserService($moduleHandler);
-
-    $markup = [
-      'tb' => [
-        '#type' => 'container',
-        '#theme' => 'token_tree_link',
-        '#token_types' => 'all',
-      ],
-    ];
-    $this->assertEquals($markup, $tokenBrowserService->getTokenBrowserMarkup());
-  }
-
-}
-- 
GitLab


From e86428e1655bc565e541f72bd234b1b75aa5abfb Mon Sep 17 00:00:00 2001
From: jurgenhaas <juergen.haas@lakedrops.com>
Date: Fri, 4 Apr 2025 15:29:14 +0200
Subject: [PATCH 19/22] Issue #3513175 by benjifisher, jurgenhaas,
 cosmicdreams: Remove code that is added to the Modeler API module

---
 modules/ui/src/Plugin/modeler_api_model_owner/Eca.php | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php b/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
index c8e0fed12..78444f129 100644
--- a/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
+++ b/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
@@ -7,7 +7,11 @@ use Drupal\modeler_api\Plugin\modeler_api_model_owner\ModelOwnerBase;
 /**
  * Model owner plugins implementation for ECA.
  *
- * |label = 'Configure ECA - Events, Conditions, Actions'
+ * @ModelOwner(
+ *   id = "eca",
+ *   label = @Translation("ECA"),
+ *   description = @Translation("Configure ECA - Events, Conditions, Actions")
+ * )
  */
 class Eca extends ModelOwnerBase {
 
-- 
GitLab


From e9fd4d772587c81e78ced9c4624d780873fb20b5 Mon Sep 17 00:00:00 2001
From: jurgenhaas <juergen.haas@lakedrops.com>
Date: Sat, 5 Apr 2025 11:57:35 +0200
Subject: [PATCH 20/22] Issue #3513175 by benjifisher, jurgenhaas,
 cosmicdreams: Remove code that is added to the Modeler API module

---
 eca.services.yml                              |   2 +-
 .../src/Drush/Commands/DocsCommands.php       |   2 +-
 .../Plugin/modeler_api_model_owner/Eca.php    | 123 ++++++++++++++++++
 3 files changed, 125 insertions(+), 2 deletions(-)

diff --git a/eca.services.yml b/eca.services.yml
index 9fced6216..64f2654c8 100644
--- a/eca.services.yml
+++ b/eca.services.yml
@@ -59,7 +59,7 @@ services:
       - '@entity_type.manager'
       - '@language_manager'
       - '@eca.service.token'
-  eca.service.events:
+  eca.service.event:
     class: Drupal\eca\Service\Events
     arguments:
       - '@plugin.manager.eca.event'
diff --git a/modules/development/src/Drush/Commands/DocsCommands.php b/modules/development/src/Drush/Commands/DocsCommands.php
index d739316af..054fe1b27 100644
--- a/modules/development/src/Drush/Commands/DocsCommands.php
+++ b/modules/development/src/Drush/Commands/DocsCommands.php
@@ -183,7 +183,7 @@ final class DocsCommands extends DrushCommands {
       $container->get('entity_type.manager'),
       $container->get('eca.service.action'),
       $container->get('eca.service.condition'),
-      $container->get('eca.service.events'),
+      $container->get('eca.service.event'),
       $container->get('file_system'),
       $container->get('module_handler'),
       $container->get('extension.list.module'),
diff --git a/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php b/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
index 78444f129..2a603b70a 100644
--- a/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
+++ b/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
@@ -2,7 +2,16 @@
 
 namespace Drupal\eca_ui\Plugin\modeler_api_model_owner;
 
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\Action\ActionInterface;
+use Drupal\Core\Form\FormState;
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\eca\Service\Actions;
+use Drupal\eca\Service\Conditions;
+use Drupal\eca\Service\Events;
+use Drupal\modeler_api\Api;
 use Drupal\modeler_api\Plugin\modeler_api_model_owner\ModelOwnerBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Model owner plugins implementation for ECA.
@@ -15,6 +24,54 @@ use Drupal\modeler_api\Plugin\modeler_api_model_owner\ModelOwnerBase;
  */
 class Eca extends ModelOwnerBase {
 
+  public const array SUPPORTED_COMPONENT_TYPES = [
+    Api::COMPONENT_TYPE_START => 'event',
+    Api::COMPONENT_TYPE_CONDITION => 'condition',
+    Api::COMPONENT_TYPE_ELEMENT => 'action',
+  ];
+
+  /**
+   * ECA events service.
+   *
+   * @var \Drupal\eca\Service\Events
+   */
+  protected Events $eventsService;
+
+  /**
+   * ECA conditions service.
+   *
+   * @var \Drupal\eca\Service\Conditions
+   */
+  protected Conditions $conditionsService;
+
+  /**
+   * ECA actions service.
+   *
+   * @var \Drupal\eca\Service\Actions
+   */
+  protected Actions $actionsService;
+
+  /**
+   * The documentation domain.
+   *
+   * @var string|null
+   */
+  protected ?string $documentationDomain;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
+    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
+    $instance->eventsService = $container->get('eca.service.event');
+    $instance->conditionsService = $container->get('eca.service.condition');
+    $instance->actionsService = $container->get('eca.service.action');
+    $instance->documentationDomain = $container->getParameter('eca.default_documentation_domain') ?
+      $container->get('config.factory')->get('eca.settings')->get('documentation_domain') :
+      NULL;
+    return $instance;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -43,4 +100,70 @@ class Eca extends ModelOwnerBase {
     return '\Drupal\eca_ui\Form\Settings';
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function availableOwnerComponents(int $type): array {
+    return match($type) {
+      Api::COMPONENT_TYPE_START => $this->eventsService->events(),
+      Api::COMPONENT_TYPE_CONDITION => $this->conditionsService->conditions(),
+      Api::COMPONENT_TYPE_ELEMENT => $this->actionsService->actions(),
+      default => [],
+    };
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function ownerComponentId(int $type): string {
+    return self::SUPPORTED_COMPONENT_TYPES[$type] ?? 'unsupported';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(PluginInspectionInterface $plugin): array {
+    $form_state = new FormState();
+    try {
+      if ($plugin instanceof ActionInterface) {
+        $form = $this->actionsService->getConfigurationForm($plugin, $form_state) ?? [
+          'error_message' => [
+            '#type' => 'checkbox',
+            '#title' => $this->t('Error in configuration form!!!'),
+            '#description' => $this->t('Details can be found in the Drupal error log.'),
+          ],
+        ];
+      }
+      elseif ($plugin instanceof PluginFormInterface) {
+        $form = $plugin->buildConfigurationForm([], $form_state);
+      }
+      else {
+        $form = [];
+      }
+    }
+    catch (\Throwable $ex) {
+      // @todo Replace this with some markup when that's supported by bpmn_io.
+      $form['error_message'] = [
+        '#type' => 'checkbox',
+        '#title' => $this->t('Error in configuration form!!!'),
+        '#description' => $ex->getMessage(),
+      ];
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function pluginDocUrl(PluginInspectionInterface $plugin, string $pluginType): ?string {
+    if (!($domain = $this->documentationDomain)) {
+      return NULL;
+    }
+    $provider = $plugin->getPluginDefinition()['provider'];
+    $basePath = (mb_strpos($provider, 'eca_') === 0) ?
+      str_replace('_', '/', $provider) :
+      $provider;
+    return sprintf('%s/plugins/%s/%ss/%s/', $domain, $basePath, $pluginType, str_replace([':'], '_', $plugin->getPluginId()));
+  }
+
 }
-- 
GitLab


From 8ebdcbcc0d80a946ddb060f2bcfa80d7f422a4a4 Mon Sep 17 00:00:00 2001
From: jurgenhaas <juergen.haas@lakedrops.com>
Date: Sun, 6 Apr 2025 13:54:09 +0200
Subject: [PATCH 21/22] Issue #3513175 by benjifisher, jurgenhaas,
 cosmicdreams: Remove code that is added to the Modeler API module

---
 .../Plugin/modeler_api_model_owner/Eca.php    | 38 +++++++++++++++++++
 src/Service/Conditions.php                    |  9 -----
 2 files changed, 38 insertions(+), 9 deletions(-)

diff --git a/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php b/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
index 2a603b70a..e96ebe3f5 100644
--- a/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
+++ b/modules/ui/src/Plugin/modeler_api_model_owner/Eca.php
@@ -4,13 +4,17 @@ namespace Drupal\eca_ui\Plugin\modeler_api_model_owner;
 
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Core\Action\ActionInterface;
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\eca\Entity\Eca as EcaModel;
 use Drupal\eca\Service\Actions;
 use Drupal\eca\Service\Conditions;
 use Drupal\eca\Service\Events;
 use Drupal\modeler_api\Api;
+use Drupal\modeler_api\Component;
 use Drupal\modeler_api\Plugin\modeler_api_model_owner\ModelOwnerBase;
+use Drupal\modeler_api\Plugin\modeler_api_model_owner\ModelOwnerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -166,4 +170,38 @@ class Eca extends ModelOwnerBase {
     return sprintf('%s/plugins/%s/%ss/%s/', $domain, $basePath, $pluginType, str_replace([':'], '_', $plugin->getPluginId()));
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function resetComponents(ConfigEntityInterface $model): ModelOwnerInterface {
+    assert($model instanceof EcaModel);
+    $model->resetComponents();
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addComponent(ConfigEntityInterface $model, Component $component): bool {
+    assert($model instanceof EcaModel);
+    $id = $component->getId();
+    $pluginId = $component->getPluginId();
+    $label = $component->getLabel();
+    $configuration = $component->getConfiguration();
+    $successors = [];
+    foreach ($component->getSuccessors() as $successor) {
+      $successors[] = [
+        'id' => $successor->getId(),
+        'condition' => $successor->getConditionId(),
+      ];
+    }
+    return match ($component->getType()) {
+      Api::COMPONENT_TYPE_START => $model->addEvent($id, $pluginId, $label, $configuration, $successors),
+      Api::COMPONENT_TYPE_CONDITION => $model->addCondition($id, $pluginId, $configuration),
+      Api::COMPONENT_TYPE_ELEMENT => $model->addAction($id, $pluginId, $label, $configuration, $successors),
+      Api::COMPONENT_TYPE_LINK => $model->addGateway($id, 0, $successors),
+      default => FALSE,
+    };
+  }
+
 }
diff --git a/src/Service/Conditions.php b/src/Service/Conditions.php
index ddd1877fd..cd77d2b50 100644
--- a/src/Service/Conditions.php
+++ b/src/Service/Conditions.php
@@ -21,12 +21,6 @@ class Conditions {
   use ErrorHandlerTrait;
   use ServiceTrait;
 
-  public const int GATEWAY_TYPE_EXCLUSIVE = 0;
-  public const int GATEWAY_TYPE_PARALLEL = 1;
-  public const int GATEWAY_TYPE_INCLUSIVE = 2;
-  public const int GATEWAY_TYPE_COMPLEX = 3;
-  public const int GATEWAY_TYPE_EVENTBASED = 4;
-
   /**
    * ECA condition plugin manager.
    *
@@ -75,9 +69,6 @@ class Conditions {
    *   The language manager service.
    * @param \Drupal\eca\Token\TokenInterface $token
    *   The ECA token service.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
-   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
    */
   public function __construct(Condition $condition_manager, LoggerChannelInterface $logger, EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager, TokenInterface $token) {
     $this->conditionManager = $condition_manager;
-- 
GitLab


From 79b746f5746ac55fd62c248de1949bb381e054aa Mon Sep 17 00:00:00 2001
From: jurgenhaas <juergen.haas@lakedrops.com>
Date: Sun, 6 Apr 2025 14:07:41 +0200
Subject: [PATCH 22/22] Issue #3513175 by benjifisher, jurgenhaas,
 cosmicdreams: Remove code that is added to the Modeler API module

---
 composer.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/composer.json b/composer.json
index fd4359800..04753b988 100644
--- a/composer.json
+++ b/composer.json
@@ -18,6 +18,7 @@
   "require-dev": {
     "drupal/entity_reference_revisions": "^1.12",
     "drupal/inline_entity_form": "^3.0",
+    "drupal/modeler_api": "^1.0",
     "drupal/paragraphs": "^1.18",
     "drupal/project_browser": "^2.0",
     "drupal/token": "^1.15",
-- 
GitLab