View source
<?php
namespace Drupal\webhooks;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\webhooks\Entity\WebhookConfig;
use Drupal\webhooks\Event\WebhookEvents;
use Drupal\webhooks\Event\ReceiveEvent;
use Drupal\webhooks\Event\SendEvent;
use Drupal\webhooks\Exception\WebhookIncomingEndpointNotFoundException;
use GuzzleHttp\Client;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\HttpFoundation\RequestStack;
class WebhooksService implements WebhookDispatcherInterface, WebhookReceiverInterface, WebhookSerializerInterface {
use StringTranslationTrait;
protected $client;
protected $logger;
protected $eventDispatcher;
protected $webhookStorage;
protected $serializer;
protected $uuid;
protected $config;
protected $queue;
protected $requestStack;
public function __construct(Client $client, LoggerChannelFactoryInterface $logger_factory, RequestStack $request_stack, EventDispatcherInterface $event_dispatcher, EntityTypeManagerInterface $entity_type_manager, Serializer $serializer, UuidInterface $uuid, ConfigFactoryInterface $config_factory, QueueFactory $queueFactory) {
$this->client = $client;
$this->logger = $logger_factory
->get('webhooks');
$this->requestStack = $request_stack;
$this->eventDispatcher = $event_dispatcher;
$this->webhookStorage = $entity_type_manager
->getStorage('webhook_config');
$this->serializer = $serializer;
$this->uuid = $uuid;
$this->config = $config_factory
->get('webhooks.settings');
$this->queue = $queueFactory
->get('webhooks_dispatcher', $this->config
->get('reliable'));
}
public function loadMultipleByEvent($event, $type = 'outgoing') {
$query = $this->webhookStorage
->getQuery()
->condition('status', 1)
->condition('events', $event, 'CONTAINS')
->condition('type', $type, '=');
$ids = $query
->execute();
return $this->webhookStorage
->loadMultiple($ids);
}
public function triggerEvent(Webhook $webhook, $event) {
$webhook_configs = $this
->loadMultipleByEvent($event);
foreach ($webhook_configs as $webhook_config) {
$this
->send($webhook_config, $webhook);
}
}
public function send(WebhookConfig $webhook_config, Webhook $webhook) {
$webhook
->setUuid($this->uuid
->generate());
$this->eventDispatcher
->dispatch(WebhookEvents::SEND, new SendEvent($webhook_config, $webhook));
$body = $this
->encode($webhook
->getPayload(), $webhook
->getMimeSubType());
if ($secret = $webhook_config
->getSecret()) {
$webhook
->setSecret($secret);
$webhook
->setSignature($body);
}
try {
$this->client
->post($webhook_config
->getPayloadUrl(), [
'headers' => $webhook
->getHeaders(),
'body' => $body,
'timeout' => strpos($webhook_config
->getPayloadUrl(), $this->requestStack
->getCurrentRequest()
->getHost()) ? 0.1 : 0,
]);
} catch (\Exception $e) {
$this->logger
->error('Dispatch Failed. Subscriber %subscriber on Webhook %uuid for Event %event: @message', [
'%subscriber' => $webhook_config
->id(),
'%uuid' => $webhook
->getUuid(),
'%event' => $webhook
->getEvent(),
'@message' => $e
->getMessage(),
'link' => Link::createFromRoute($this
->t('Edit Webhook'), 'entity.webhook_config.edit_form', [
'webhook_config' => $webhook_config
->id(),
])
->toString(),
]);
$webhook
->setStatus(FALSE);
}
$this->logger
->info('Webhook Dispatched. Subscriber %subscriber on Webhook %uuid for Event %event. Payload: @payload', [
'%subscriber' => $webhook_config
->id(),
'%uuid' => $webhook
->getUuid(),
'%event' => $webhook
->getEvent(),
'@payload' => $this
->encode($webhook
->getPayload(), $webhook
->getMimeSubType()),
'link' => Link::createFromRoute($this
->t('Edit Webhook'), 'entity.webhook_config.edit_form', [
'webhook_config' => $webhook_config
->id(),
])
->toString(),
]);
}
public function receive($name) {
$query = $this->webhookStorage
->getQuery()
->condition('id', $name)
->condition('type', 'incoming')
->condition('status', 1);
$ids = $query
->execute();
if (!array_key_exists($name, $ids)) {
throw new WebhookIncomingEndpointNotFoundException($name);
}
$request = $this->requestStack
->getCurrentRequest();
$payload = $this
->decode($request
->getContent(), $request
->getContentType());
$webhook = new Webhook($payload, $request->headers
->all(), '', $request->headers
->get('Content-Type'));
$webhook
->setUuid($request->headers
->get('X-Drupal-Delivery'));
$webhook
->setEvent($request->headers
->get('X-Drupal-Event'));
$webhook_config = $this->webhookStorage
->load($name);
if ($webhook_config
->getSecret() || $webhook
->getSignature()) {
Webhook::verify($webhook_config
->getSecret(), $request
->getContent(), $webhook
->getSignature());
}
elseif ($webhook_config
->getSecret() || $webhook
->getToken()) {
Webhook::verifyToken($webhook_config
->getToken(), $webhook
->getToken());
}
if ($webhook_config
->isNonBlocking()) {
$this->queue
->createItem([
'id' => $name,
'webhook' => $webhook,
]);
}
else {
$this->eventDispatcher
->dispatch(WebhookEvents::RECEIVE, new ReceiveEvent($webhook_config, $webhook));
}
if (!$webhook
->getStatus()) {
$this->logger
->warning('Processing Failure. Subscriber %subscriber on Webhook %uuid for Event %event. Payload: @payload', [
'%subscriber' => $webhook_config
->id(),
'%uuid' => $webhook
->getUuid(),
'%event' => $webhook
->getEvent(),
'@payload' => $this
->encode($webhook
->getPayload(), $webhook
->getMimeSubType()),
'link' => Link::createFromRoute($this
->t('Edit Webhook'), 'entity.webhook_config.edit_form', [
'webhook_config' => $webhook_config
->id(),
])
->toString(),
]);
}
return $webhook;
}
public function setSerializer(Serializer $serializer) {
$this->serializer = $serializer;
}
public function encode($data, $format, array $context = []) {
return $this->serializer
->encode($data, $format);
}
public function supportsEncoding($format, array $context = []) {
return $this->serializer
->supportsEncoding($format, $context);
}
public function decode($data, $format, array $context = []) {
return $this->serializer
->decode($data, $format);
}
public function supportsDecoding($format, array $context = []) {
return $this->serializer
->supportsDecoding($format, $context);
}
}