eca-1.0.x-dev/src/Entity/Eca.php

src/Entity/Eca.php
<?php

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;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormState;
use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\eca\Entity\Objects\EcaAction;
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 Symfony\Contracts\EventDispatcher\Event;

/**
 * Defines the ECA entity type.
 *
 * @ConfigEntityType(
 *   id = "eca",
 *   label = @Translation("ECA"),
 *   label_collection = @Translation("ECAs"),
 *   label_singular = @Translation("ECA"),
 *   label_plural = @Translation("ECAs"),
 *   label_count = @PluralTranslation(
 *     singular = "@count ECA",
 *     plural = "@count ECAs",
 *   ),
 *   handlers = {
 *     "storage" = "Drupal\eca\Entity\EcaStorage",
 *   },
 *   config_prefix = "eca",
 *   admin_permission = "administer eca",
 *   entity_keys = {
 *     "id" = "id",
 *     "label" = "label",
 *     "uuid" = "uuid",
 *     "status" = "status",
 *     "weight" = "weight"
 *   },
 *   config_export = {
 *     "id",
 *     "modeller",
 *     "label",
 *     "uuid",
 *     "status",
 *     "version",
 *     "weight",
 *     "events",
 *     "conditions",
 *     "gateways",
 *     "actions"
 *   }
 * )
 */
class Eca extends ConfigEntityBase implements EntityWithPluginCollectionInterface {

  use EcaTrait;

  /**
   * List of action plugins for which validation needs to be avoided.
   *
   * @var string[]
   *
   * @see https://www.drupal.org/project/eca/issues/3278080
   */
  protected static array $ignoreConfigValidationActions = [
    'action_send_email_action',
    'node_assign_owner_action',
  ];

  /**
   * ID of the ECA config entity.
   *
   * @var string
   */
  protected string $id;

  /**
   * Label of the ECA config entity.
   *
   * @var string
   */
  protected string $label;

  /**
   * List of events.
   *
   * @var array
   */
  protected array $events = [];

  /**
   * List of conditions.
   *
   * @var array
   */
  protected array $conditions = [];

  /**
   * List of gateways.
   *
   * @var array|null
   */
  protected ?array $gateways = [];

  /**
   * List of actions.
   *
   * @var array
   */
  protected array $actions = [];

  /**
   * Model config entity for the ECA config entity.
   *
   * @var \Drupal\eca\Entity\Model
   */
  protected Model $model;

  /**
   * Whether this instance s in testing mode.
   *
   * @var bool
   */
  protected static bool $isTesting = FALSE;

  /**
   * Set the instance into testing mode.
   *
   * This will prevent dependency calculation which would fail during test setup
   * if not all dependant config entities were available from the test module
   * itself.
   *
   * Problem is, that we can't add all the config dependencies to the test
   * modules, because that would fail if we enable the test modules in a real
   * Drupal instance, as some of those config entities already exist from
   * core modules.
   */
  public static function setTesting(): void {
    static::$isTesting = TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public static function postLoad(EntityStorageInterface $storage, array &$entities): void {
    parent::postLoad($storage, $entities);
    /** @var \Drupal\eca\Entity\Eca $entity */
    foreach ($entities as $entity) {
      if ($entity->get('weight') === NULL) {
        $entity->set('weight', 0);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function preSave(EntityStorageInterface $storage): void {
    parent::preSave($storage);
    foreach ([
      'events' => $this->eventPluginManager(),
      'conditions' => $this->conditionPluginManager(),
      'actions' => $this->actionPluginManager(),
    ] as $plugins => $manager) {
      foreach ($this->{$plugins} as $id => $pluginDef) {
        $plugin = $manager->createInstance($pluginDef['plugin'], $pluginDef['configuration']);
        // Allows ECA plugins to react upon being added to an ECA entity.
        if ($plugin instanceof PluginUsageInterface) {
          $plugin->pluginUsed($this, $id);
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    // As ::trustData() states that dependencies are not calculated on save,
    // calculation is skipped when flagged as trusted.
    // @see Drupal\Core\Config\Entity\ConfigEntityInterface::trustData
    if (static::$isTesting || $this->trustedData) {
      return $this;
    }
    parent::calculateDependencies();
    foreach ($this->dependencyCalculation()->calculateDependencies($this) as $type => $names) {
      foreach ($names as $name) {
        $this->addDependency($type, $name);
      }
    }
    return $this;
  }

  /**
   * Builds the cache ID for an ID inside this ECA config entity.
   *
   * @param string $id
   *   An idea for which a cache ID inside this ECA config entity is needed.
   *
   * @return string
   *   The cache ID.
   */
  protected function buildCacheId(string $id): string {
    return "eca:$this->id:$id";
  }

  /**
   * Determines if ECA validation is being disabled for the current request.
   *
   * @return bool
   *   TRUE, if the current request has a query argument eca_validation set to
   *   off, FALSE otherwise.
   */
  protected function isValidationDisabled(): bool {
    $request = $this->request();
    // @noinspection StrContainsCanBeUsedInspection
    $isAjax = mb_strpos($request->get(MainContentViewSubscriber::WRAPPER_FORMAT, ''), 'drupal_ajax') !== FALSE;
    if ($isAjax && ($referer = $request->headers->get('referer')) && $query = parse_url($referer, PHP_URL_QUERY)) {
      // @noinspection StrContainsCanBeUsedInspection
      return mb_strpos($query, 'eca_validation=off') !== FALSE;
    }
    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\eca\Plugin\ECA\Modeller\ModellerInterface|null
   *   Returns the modeller plugin if possible, NULL otherwise.
   */
  public function getModeller(): ?ModellerInterface {
    try {
      /**
       * @var \Drupal\eca\Plugin\ECA\Modeller\ModellerInterface $plugin
       */
      $plugin = $this->modellerPluginManager()->createInstance($this->get('modeller'));
    }
    catch (PluginException $e) {
      $this->logger()->error($e->getMessage());
      return NULL;
    }
    $plugin->setConfigEntity($this);
    return $plugin;
  }

  /**
   * Provides the ECA model entity storing the data for this ECA config entity.
   *
   * @return \Drupal\eca\Entity\Model
   *   The ECA model entity.
   */
  public function getModel(): Model {
    if (!isset($this->model)) {
      try {
        $storage = $this->entityTypeManager()->getStorage('eca_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\eca\Entity\Model|null $model
       */
      $model = $storage->load($this->id());
      if ($model === NULL) {
        /**
         * @var \Drupal\eca\Entity\Model $model
         */
        $model = $storage->create([
          'id' => $this->id(),
        ]);
      }
      $this->model = $model;
    }
    return $this->model;
  }

  /**
   * 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.
   */
  public function resetComponents(): void {
    $this->events = [];
    $this->conditions = [];
    $this->actions = [];
    $this->gateways = [];
  }

  /**
   * 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.
   */
  public function addCondition(string $id, string $plugin_id, array $fields): bool {
    $plugin = $this->conditionPluginManager()->createInstance($plugin_id, []);
    if (($plugin instanceof PluginFormInterface) && !$this->validatePlugin($plugin, $fields, 'condition', $plugin_id, $id)) {
      return FALSE;
    }

    $this->conditions[$id] = [
      'plugin' => $plugin_id,
      'configuration' => $fields,
    ];
    return TRUE;
  }

  /**
   * 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.
   */
  public function addGateway(string $id, int $type, array $successors): bool {
    $this->gateways[$id] = [
      'type' => $type,
      'successors' => $successors,
    ];
    return TRUE;
  }

  /**
   * 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.
   */
  public function addEvent(string $id, string $plugin_id, string $label, array $fields, array $successors): bool {
    $plugin = $this->eventPluginManager()->createInstance($plugin_id, []);
    if (($plugin instanceof PluginFormInterface) && !$this->validatePlugin($plugin, $fields, 'event', $plugin_id, $label)) {
      return FALSE;
    }

    if (empty($label)) {
      $label = $id;
    }
    $this->events[$id] = [
      'plugin' => $plugin_id,
      'label' => $label,
      'configuration' => $fields,
      'successors' => $successors,
    ];
    return TRUE;
  }

  /**
   * 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.
   */
  public function addAction(string $id, string $plugin_id, string $label, array $fields, array $successors): bool {
    $plugin = $this->actionPluginManager()->createInstance($plugin_id, []);
    if (($plugin instanceof PluginFormInterface) && !$this->validatePlugin($plugin, $fields, 'action', $plugin_id, $label)) {
      return FALSE;
    }

    if (empty($label)) {
      $label = $id;
    }
    $this->actions[$id] = [
      'plugin' => $plugin_id,
      'label' => $label,
      'configuration' => $fields,
      'successors' => $successors,
    ];
    return TRUE;
  }

  /**
   * Validate the configuration of an event, condition or action plugin.
   *
   * @param \Drupal\Core\Plugin\PluginFormInterface $plugin
   *   The plugin to be validated.
   * @param array $fields
   *   The configuration values to be validated.
   * @param string $type
   *   The plugin type, either event, condition or action.
   * @param string $plugin_id
   *   The plugin id.
   * @param string $label
   *   The label in the model.
   *
   * @return bool
   *   TRUE, if configuration form validation has no errors. FALSE otherwise.
   */
  protected function validatePlugin(PluginFormInterface $plugin, array &$fields, string $type, string $plugin_id, string $label): bool {
    $replaced_fields = [];
    $eca_validation_error = FALSE;
    $messenger = $this->messenger();
    $plugin_label = $plugin instanceof PluginInspectionInterface ?
      $plugin->getPluginDefinition()['label'] : 'unknown';

    if ($plugin instanceof ConfigurableInterface) {
      foreach ($plugin->defaultConfiguration() + ['replace_tokens' => FALSE] as $key => $value) {
        // Convert potential strings from pseudo-checkboxes (for example a
        // dropdown with "yes" or "no" options).
        if (is_bool($value) &&
          isset($fields[$key]) &&
          is_string($fields[$key]) &&
          in_array(mb_strtolower($fields[$key]), ['yes', 'no'], TRUE)
        ) {
          if (mb_strtolower($fields[$key]) === 'yes') {
            $fields[$key] = TRUE;
          }
          else {
            // Unset from the fields array. An unchecked checkbox is like
            // no value is provided on form submission.
            unset($fields[$key]);
            // When plugin configuration is being used on form building,
            // the default value will be used. This makes sure, that the
            // default value is treated like an unchecked checkbox.
            $plugin->setConfiguration([$key => FALSE] + $plugin->getConfiguration());
          }
        }
      }

      // Identify number or email fields and replace them with a valid value if
      // the field is configured with a token. This is important to get those
      // fields through form validation without issues.
      // @todo Add support for nested form fields like e.g. in container/fieldset.
      $form = [];
      $form_state = new FormState();
      foreach ($plugin->buildConfigurationForm($form, $form_state) as $key => $form_field) {
        if (!empty($form_field['#eca_token_reference']) &&
          isset($fields[$key]) &&
          $this->valueIsToken($fields[$key])
        ) {
          $eca_validation_error = TRUE;
          $errorMsg = sprintf('%s "%s" (%s): %s', $type, $plugin_label, $label, 'This field requires a token name, not a token; please remove the brackets.');
          $messenger->addError($errorMsg);
        }
        if (!empty($form_field['#eca_token_select_option']) && isset($form_field['#options']) && is_array($form_field['#options']) && ($fields[$key] === '_eca_token' || $fields[$key] === '')) {
          // Remember the original configuration value.
          $replaced_fields[$key] = $fields[$key];
          $fields[$key] = array_key_first($form_field['#options']);
        }
        if (isset($form_field['#type'], $fields[$key]) &&
          in_array($form_field['#type'], ['number', 'email', 'machine_name'], TRUE) &&
          $this->valueIsToken($fields[$key])
        ) {
          // Remember the original configuration value.
          $replaced_fields[$key] = $fields[$key];

          switch ($form_field['#type']) {

            case 'number':
              // Set a valid value for the form element type 'number'
              // to pass the validation. Also if the field is required
              // the value "0" would cause a form error, let's use "1" instead.
              $fields[$key] = $form_field['#min'] ?? 1;
              break;

            case 'email':
              // Set a valid value for the form element type 'email'
              // to pass validation.
              $fields[$key] = 'lorem@eca.local';
              break;

            case 'machine_name':
              // Set a valid value for the form element type 'machine_name'
              // to pass validation. Needs to append a random value, so that
              // it passes "exists" callbacks.
              $fields[$key] = 'eca_' . mb_strtolower((new Random())->name(8, TRUE));
              break;

          }
        }
        if (isset($form_field['#type'], $fields[$key]) && $form_field['#type'] === 'machine_name') {
          // Remember the original configuration value.
          $replaced_fields[$key] = $fields[$key];
          $fields[$key] = str_replace('][', '', $fields[$key]);
        }
      }
    }

    // Simulate filling and submitting a form for configuring the plugin.
    $form_state = new FormState();
    $form_state->setProgrammed();
    $form_state->setSubmitted();

    if (!in_array($plugin_id, self::$ignoreConfigValidationActions, TRUE)) {
      // Build a runtime form for validating the plugin.
      $form_object = new RuntimePluginForm($plugin);

      // Runtime plugin form uses a subform state for the plugin configuration.
      $form_state->setUserInput(['configuration' => $fields]);
      $form_state->setValues(['configuration' => $fields]);

      // Keep the currently stored list of messages in mind.
      // The form build will add messages to the messenger, which we want
      // to clear from the runtime.
      $messages_by_type = $messenger->all();

      // Keep the current "has any errors" flag in mind, and reset this flag
      // for the scope of this operation.
      $any_errors = FormState::hasAnyErrors();
      $form_state->clearErrors();

      // Building the form also submits the form, if no errors are there.
      $form = $this->formBuilder()->buildForm($form_object, $form_state);

      // Now re-add the previously fetched messages.
      $messenger->deleteAll();
      foreach ($messages_by_type as $messageType => $messages) {
        foreach ($messages as $message) {
          $messenger->addMessage($message, $messageType);
        }
      }

      // Check for errors.
      if ($errors = $form_state->getErrors()) {
        foreach ($errors as $error) {
          $errorMsg = sprintf('%s "%s" (%s): %s', $type, $plugin_label, $label, $error);
          $messenger->addError($errorMsg);
        }
        return FALSE;
      }

      if ($any_errors) {
        // Make sure that the form state will have the any errors flag restored.
        (new FormState())->setErrorByName('');
      }
    }
    else {
      // Build and execute submit handlers. This makes sure that submit handlers
      // have properly set configuration values.
      $form_state->setUserInput($fields);
      $form_state->setValues($fields);
      $form = $plugin->buildConfigurationForm([], $form_state);
      $plugin->submitConfigurationForm($form, $form_state);
    }

    // If there have been any ECA specific validation errors but no other form
    // error, we end up here but won't proceed, as the model is not valid.
    if ($eca_validation_error) {
      return FALSE;
    }

    // Collect the resulting form field values.
    $fields = ($plugin instanceof ConfigurableInterface ? $plugin->getConfiguration() : []) + $fields;

    // Restore tokens for numeric configuration fields.
    foreach ($replaced_fields as $key => $original_value) {
      $fields[$key] = $original_value;
    }
    return TRUE;
  }

  /**
   * Returns a list of info strings about included events in this ECA model.
   *
   * @return array
   *   A list of info strings about included events in this ECA model.
   */
  public function getEventInfos(): array {
    $events = [];
    foreach ($this->getUsedEvents() as $used_event) {
      $events[] = $this->getEventInfo($used_event);
    }
    return $events;
  }

  /**
   * Returns an info string about the ECA event.
   *
   * @return string
   *   The info string.
   */
  public function getEventInfo(EcaEvent $ecaEvent): string {
    $plugin = $ecaEvent->getPlugin();
    $event_info = $plugin->getPluginDefinition()['label'];
    // If available, additionally display the first config value of the event.
    if ($event_config = $ecaEvent->getConfiguration()) {
      $first_key = key($event_config);
      $first_value = current($event_config);
      $form = $plugin->buildConfigurationForm([], new FormState());
      if (isset($form[$first_key]['#options'][$first_value])) {
        $first_value = $form[$first_key]['#options'][$first_value];
      }
      $event_info .= ' (' . $first_value . ')';
    }
    return $event_info;
  }

  /**
   * Provides a list of all used events by this ECA config entity.
   *
   * @param array|null $ids
   *   (optional) When set, only the subset of given event object IDs are being
   *   returned.
   *
   * @return \Drupal\eca\Entity\Objects\EcaEvent[]
   *   The list of used events.
   */
  public function getUsedEvents(?array $ids = NULL): array {
    $events = [];
    $ids = $ids ?? array_keys($this->events);
    foreach ($ids as $id) {
      if (!isset($this->events[$id])) {
        continue;
      }
      $def = &$this->events[$id];
      /** @var \Drupal\eca\Entity\Objects\EcaEvent|null $event */
      $event = $this->getEcaObject('event', $def['plugin'], $id, $def['label'] ?? 'noname', $def['configuration'] ?? [], $def['successors'] ?? []);
      if ($event) {
        $events[$id] = $event;
      }
      unset($def);
    }
    return $events;
  }

  /**
   * Get a single ECA event object.
   *
   * @param string $id
   *   The ID of the event object within this ECA configuration.
   *
   * @return \Drupal\eca\Entity\Objects\EcaEvent|null
   *   The ECA event object, or NULL if not found.
   */
  public function getEcaEvent(string $id): ?EcaEvent {
    return current($this->getUsedEvents([$id])) ?: NULL;
  }

  /**
   * Get the used conditions.
   *
   * @return array
   *   List of used conditions.
   */
  public function getConditions(): array {
    return $this->conditions;
  }

  /**
   * Get the used actions.
   *
   * @return array
   *   List of used action.
   */
  public function getActions(): array {
    return $this->actions;
  }

  /**
   * Provides a list of valid successors to any ECA item in a given context.
   *
   * @param \Drupal\eca\Entity\Objects\EcaObject $eca_object
   *   The ECA item, for which the successors are requested.
   * @param \Symfony\Contracts\EventDispatcher\Event $event
   *   The originally triggered event in which context to determine the list
   *   of valid successors.
   * @param array $context
   *   A list of tokens from the current context to be used for meaningful
   *   log messages.
   *
   * @return \Drupal\eca\Entity\Objects\EcaObject[]
   *   The list of valid successors.
   */
  public function getSuccessors(EcaObject $eca_object, Event $event, array $context): array {
    $successors = [];
    foreach ($eca_object->getSuccessors() as $successor) {
      $context['%successorid'] = $successor['id'];
      if ($action = $this->actions[$successor['id']] ?? FALSE) {
        $context['%successorlabel'] = $action['label'] ?? 'noname';
        $this->logger()->debug('Check action successor %successorlabel (%successorid) from ECA %ecalabel (%ecaid) for event %event.', $context);
        if ($successorObject = $this->getEcaObject('action', $action['plugin'], $successor['id'], $action['label'] ?? 'noname', $action['configuration'] ?? [], $action['successors'] ?? [], $eca_object->getEvent())) {
          if ($this->conditionServices()->assertCondition($event, $successor['condition'], $this->conditions[$successor['condition']] ?? NULL, $context)) {
            $successors[] = $successorObject;
          }
        }
        else {
          $this->logger()->error('Invalid action successor %successorlabel (%successorid) from ECA %ecalabel (%ecaid) for event %event.', $context);
        }
      }
      elseif ($gateway = $this->gateways[$successor['id']] ?? FALSE) {
        $context['%successorlabel'] = $gateway['label'] ?? 'noname';
        $this->logger()->debug('Check gateway successor %successorlabel (%successorid) from ECA %ecalabel (%ecaid) for event %event.', $context);
        $successorObject = new EcaGateway($this, $successor['id'], $gateway['label'] ?? 'noname', $eca_object->getEvent(), $gateway['type']);
        $successorObject->setSuccessors($gateway['successors']);
        if ($this->conditionServices()->assertCondition($event, $successor['condition'], $this->conditions[$successor['condition']] ?? NULL, $context)) {
          $successors[] = $successorObject;
        }
      }
      else {
        $this->logger()->error('Non existent successor (%successorid) from ECA %ecalabel (%ecaid) for event %event.', $context);
      }
    }
    return $successors;
  }

  /**
   * Provides an ECA item build from given properties.
   *
   * @param string $type
   *   The ECA object type. Can bei either "event" or "action".
   * @param string $plugin_id
   *   The plugin ID.
   * @param string $id
   *   The item ID given by the modeller.
   * @param string $label
   *   The label.
   * @param array $fields
   *   The configuration of the item.
   * @param array $successors
   *   The list of associated successors.
   * @param \Drupal\eca\Entity\Objects\EcaEvent|null $event
   *   The original ECA event object, if looking for an action, NULL otherwise.
   *
   * @return \Drupal\eca\Entity\Objects\EcaObject|null
   *   The ECA object if available, NULL otherwise.
   */
  private function getEcaObject(string $type, string $plugin_id, string $id, string $label, array $fields, array $successors, ?EcaEvent $event = NULL): ?EcaObject {
    $ecaObject = NULL;
    switch ($type) {
      case 'event':
        try {
          /**
           * @var \Drupal\eca\Plugin\ECA\Event\EventInterface $plugin
           */
          $plugin = $this->eventPluginManager()->createInstance($plugin_id, $fields);
        }
        catch (PluginException $e) {
          // This can be ignored.
        }
        if (isset($plugin)) {
          $ecaObject = new EcaEvent($this, $id, $label, $plugin);
        }
        break;

      case 'action':
        if ($event !== NULL) {
          try {
            /**
             * @var \Drupal\Core\Action\ActionInterface $plugin
             */
            $plugin = $this->actionPluginManager()->createInstance($plugin_id, $fields);
          }
          catch (PluginException $e) {
            // This can be ignored.
          }
          if (isset($plugin)) {
            $ecaObject = new EcaAction($this, $id, $label, $event, $plugin);
          }
        }
        break;

    }
    if ($ecaObject !== NULL) {
      foreach ($fields as $key => $value) {
        $ecaObject->setConfiguration($key, $value);
      }
      $ecaObject->setSuccessors($successors);
      return $ecaObject;
    }
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getPluginCollections(): array {
    $collections = [];
    if ($this->isValidationDisabled()) {
      return $collections;
    }
    if (!empty($this->events)) {
      foreach ($this->events as $id => $info) {
        if (empty($info['plugin'])) {
          continue;
        }
        $collections['events.' . $id] = new DefaultSingleLazyPluginCollection($this->eventPluginManager(), $info['plugin'], $info['configuration'] ?? []);
      }
    }
    if (!empty($this->conditions)) {
      foreach ($this->conditions as $id => $info) {
        if (empty($info['plugin'])) {
          continue;
        }
        $collections['conditions.' . $id] = new DefaultSingleLazyPluginCollection($this->conditionPluginManager(), $info['plugin'], $info['configuration'] ?? []);
      }
    }
    if (!empty($this->actions)) {
      foreach ($this->actions as $id => $info) {
        if (empty($info['plugin'])) {
          continue;
        }
        $collections['actions.' . $id] = new DefaultSingleLazyPluginCollection($this->actionPluginManager(), $info['plugin'], $info['configuration'] ?? []);
      }
    }
    return $collections;
  }

  /**
   * Adds a dependency that could only be calculated on runtime.
   *
   * After adding a dependency on runtime, this configuration should be saved.
   *
   * @param string $type
   *   Type of dependency being added: 'module', 'theme', 'config', 'content'.
   * @param string $name
   *   If $type is 'module' or 'theme', the name of the module or theme. If
   *   $type is 'config' or 'content', the result of
   *   EntityInterface::getConfigDependencyName().
   *
   * @see \Drupal\Core\Entity\EntityInterface::getConfigDependencyName()
   *
   * @return static
   *   The ECA config itself.
   */
  public function addRuntimeDependency(string $type, string $name): Eca {
    $this->addDependency($type, $name);
    return $this;
  }

  /**
   * Checks if a given value has the patterns of a token.
   *
   * @param string $value
   *   The field value.
   *
   * @return bool
   *   Wether TRUE or FALSE based on the pattern.
   */
  protected function valueIsToken($value): bool {
    return (mb_substr((string) $value, 0, 1) === '[') &&
    (mb_substr((string) $value, -1, 1) === ']') &&
    (mb_strlen((string) $value) <= 255);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc