eca-1.0.x-dev/src/Plugin/ECA/Event/EventBase.php
src/Plugin/ECA/Event/EventBase.php
<?php
namespace Drupal\eca\Plugin\ECA\Event;
use Drupal\Core\Form\FormStateInterface;
use Drupal\eca\Attribute\Token;
use Drupal\eca\EcaEvents;
use Drupal\eca\Entity\Objects\EcaEvent;
use Drupal\eca\Plugin\DataType\DataTransferObject;
use Drupal\eca\Plugin\ECA\EcaPluginBase;
use Drupal\eca\Plugin\ECA\PluginFormTrait;
use Drupal\eca\Token\TokenInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Base class for ECA event plugins.
*/
abstract class EventBase extends EcaPluginBase implements EventInterface {
use PluginFormTrait;
/**
* According system event, if available.
*
* @var \Symfony\Contracts\EventDispatcher\Event|null
*/
protected ?Event $event = NULL;
/**
* An instance holding event data accessible as Token.
*
* @var \Drupal\eca\Plugin\DataType\DataTransferObject|null
*/
protected ?DataTransferObject $eventData = NULL;
/**
* The list of tokens that the event provides when dispatched.
*
* @var \Drupal\eca\Attribute\Token[]|null
*/
protected ?array $tokens = NULL;
/**
* The event dispatcher service.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected EventDispatcherInterface $eventDispatcher;
/**
* ECA token service.
*
* @var \Drupal\eca\Token\TokenInterface
*/
protected TokenInterface $token;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->setConfiguration($configuration);
$instance->eventDispatcher = $container->get('event_dispatcher');
$instance->token = $container->get('eca.service.token');
return $instance;
}
/**
* {@inheritdoc}
*/
final public function eventClass(): string {
return $this->pluginDefinition['event_class'];
}
/**
* {@inheritdoc}
*/
final public function eventName(): string {
return $this->pluginDefinition['event_name'];
}
/**
* {@inheritdoc}
*/
final public function subscriberPriority(): int {
return $this->pluginDefinition['subscriber_priority'];
}
/**
* {@inheritdoc}
*/
public function generateWildcard(string $eca_config_id, EcaEvent $ecaEvent): string {
// By default return a small wildcard that should match up for every event
// that is of the class as returned by ::drupalEventClass.
return '*';
}
/**
* {@inheritdoc}
*
* @throws \InvalidArgumentException
* When the given wildcard does not match up with the expected pattern as
* generated by ::generateWildcard.
*/
public static function appliesForWildcard(Event $event, string $event_name, string $wildcard): bool {
// This default implementation should not be called as a parent method
// from a child class. When a child class implements this method on its own,
// it must contain solely the concrete appliance logic of the plugin itself.
if ($wildcard !== '*') {
throw new \InvalidArgumentException('The given wildcard is different than expected. If the plugin implements its own appliance logic, the parent method must not be invoked.');
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function setEvent(Event $event): EventInterface {
$this->event = $event;
return $this;
}
/**
* {@inheritdoc}
*/
public function getEvent(): ?Event {
return $this->event;
}
/**
* {@inheritdoc}
*/
public function getTokens(): array {
if ($this->tokens === NULL) {
$this->getSupportedTokens();
}
return $this->tokens;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration(): array {
return [];
}
/**
* {@inheritdoc}
*/
public function getConfiguration(): array {
return $this->configuration;
}
/**
* {@inheritdoc}
*/
public function setConfiguration(array $configuration): void {
$this->configuration = $configuration + $this->defaultConfiguration();
}
/**
* Helper function to get token info.
*/
private function getSupportedTokens(): void {
$sources = $this->eventDispatcher->getListeners(EcaEvents::BEFORE_INITIAL_EXECUTION);
array_unshift($sources, [$this::class, 'buildEventData']);
array_unshift($sources, [$this::class, 'getData']);
foreach ($this->token->getDataProviders() as $dataProvider) {
array_unshift($sources, [$dataProvider::class, 'buildEventData']);
array_unshift($sources, [$dataProvider::class, 'getData']);
}
$eventClass = $this->eventClass();
$tokens = [];
foreach ($sources as $source) {
[$class, $methodName] = $source;
try {
$reflection = new \ReflectionMethod($class, $methodName);
}
catch (\ReflectionException) {
continue;
}
do {
foreach ($reflection->getAttributes() as $attribute) {
if ($attribute->getName() === 'Drupal\eca\Attribute\Token') {
/** @var \Drupal\eca\Attribute\Token $token */
$token = $attribute->newInstance();
if ($this->getSupportedProperties($eventClass, $token)) {
$tokens[] = $token;
}
}
}
try {
$reflection = $reflection->getPrototype();
}
catch (\ReflectionException) {
$reflection = NULL;
}
} while ($reflection !== NULL);
}
$this->tokens = $tokens;
}
/**
* Recursive helper function to get token property info.
*
* @param string $eventClass
* The event class.
* @param \Drupal\eca\Attribute\Token $token
* The token.
*
* @return bool
* TRUE, if the token has any attributes, FALSE otherwise.
*/
private function getSupportedProperties(string $eventClass, Token $token): bool {
if ($this->isClassSupported($eventClass, $token->classes)) {
$properties = [];
foreach ($token->properties as $property) {
if ($this->getSupportedProperties($eventClass, $property)) {
$properties[] = $property;
}
}
$token->properties = $properties;
return TRUE;
}
return FALSE;
}
/**
* Determines if the event class is covered by the list of classes.
*
* @param string $eventClass
* The event class.
* @param array $classes
* The list of classes.
*
* @return bool
* TRUE, if either the list of classes if empty, or if the event class is
* an instance of one of the given classes; FALSE otherwise.
*/
private function isClassSupported(string $eventClass, array $classes): bool {
if (empty($classes)) {
return TRUE;
}
foreach ($classes as $class) {
if (is_a($eventClass, $class, TRUE)) {
return TRUE;
}
}
return FALSE;
}
/**
* Helper function to render a supported token and its properties.
*
* @param \Drupal\eca\Attribute\Token $token
* The token.
* @param string $prefix
* The prefix.
*
* @return array
* The list of rendered tokens and properties.
*/
private function renderToken(Token $token, string $prefix = ''): array {
$tokens = [];
$name = $prefix . $token->name;
$tokens[] = '<p><strong>[' . $name . ']:</strong> ' . $token->description . ($token->aliases ? '<br/>(Alias: ' . implode(', ', $token->aliases) . ')' : '') . '</p>';
foreach ($token->properties as $property) {
$tokens += $this->renderToken($property, $name);
}
return $tokens;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
$i = 0;
$tokens = $this->getTokens();
if ($tokens) {
$form['eca_token_header'] = [
'#markup' => '<h3>' . $this->t('Provided Tokens') . '</h3>',
];
foreach ($tokens as $token) {
foreach ($this->renderToken($token) as $item) {
$i++;
$form['eca_token_' . $i] = [
'#markup' => $item,
'#weight' => 900 + $i,
];
}
}
}
return $this->updateConfigurationForm($form);
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void {}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {}
/**
* {@inheritdoc}
*/
public function getData(string $key): mixed {
if ($key === 'event') {
if (!isset($this->eventData)) {
$this->eventData = DataTransferObject::create($this->buildEventData());
}
return $this->eventData;
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function hasData(string $key): bool {
return $this->getData($key) !== NULL;
}
/**
* {@inheritdoc}
*/
public function handleExceptions(): bool {
return FALSE;
}
/**
* Builds up associative data of the "event" token.
*
* At least this should provide a "machine_name" property, so that this may
* be accessible as "[event:machine_name]" token.
*
* @return array
* The associative data for the "event" token.
*/
#[Token(
name: 'event',
description: 'The event.',
properties: [
new Token(name: 'machine_name', description: 'The machine name of the ECA event.'),
],
)]
protected function buildEventData(): array {
return [
'machine_name' => $this->eventName(),
];
}
}
