custom/plugins/SwagFlowBuilder/src/Core/Content/Flow/Dispatching/Action/CallWebhookAction.php line 70

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. /*
  3.  * (c) shopware AG <info@shopware.com>
  4.  * For the full copyright and license information, please view the LICENSE
  5.  * file that was distributed with this source code.
  6.  */
  7. namespace Swag\FlowBuilderProfessional\Core\Content\Flow\Dispatching\Action;
  8. use Doctrine\DBAL\Connection;
  9. use GuzzleHttp\Client;
  10. use GuzzleHttp\ClientInterface;
  11. use GuzzleHttp\Exception\GuzzleException;
  12. use GuzzleHttp\Exception\RequestException;
  13. use GuzzleHttp\RequestOptions;
  14. use Psr\Log\LoggerInterface;
  15. use Shopware\Core\Content\Flow\Dispatching\Action\FlowAction;
  16. use Shopware\Core\Defaults;
  17. use Shopware\Core\Framework\Adapter\Twig\StringTemplateRenderer;
  18. use Shopware\Core\Framework\Context;
  19. use Shopware\Core\Framework\Event\FlowEvent;
  20. use Shopware\Core\Framework\Event\FlowEventAware;
  21. use Shopware\Core\Framework\Uuid\Uuid;
  22. use Shopware\Core\Framework\Webhook\EventLog\WebhookEventLogDefinition;
  23. use Swag\FlowBuilderProfessional\Core\Content\Flow\Dispatching\Exception\WebhookActionConfigurationException;
  24. use Swag\FlowBuilderProfessional\Core\Framework\Event\WebhookAware;
  25. class CallWebhookAction extends FlowAction
  26. {
  27.     private const TIMEOUT 20;
  28.     private const CONNECT_TIMEOUT 10;
  29.     private ClientInterface $guzzleClient;
  30.     private LoggerInterface $logger;
  31.     private StringTemplateRenderer $templateRenderer;
  32.     private Connection $connection;
  33.     public function __construct(
  34.         Client $guzzleClient,
  35.         StringTemplateRenderer $templateRenderer,
  36.         LoggerInterface $logger,
  37.         Connection $connection
  38.     ) {
  39.         $this->guzzleClient $guzzleClient;
  40.         $this->templateRenderer $templateRenderer;
  41.         $this->logger $logger;
  42.         $this->connection $connection;
  43.     }
  44.     public static function getName(): string
  45.     {
  46.         return 'action.call.webhook';
  47.     }
  48.     public static function getSubscribedEvents(): array
  49.     {
  50.         return [
  51.             self::getName() => 'handle',
  52.         ];
  53.     }
  54.     public function requirements(): array
  55.     {
  56.         return [WebhookAware::class];
  57.     }
  58.     public function handle(FlowEvent $event): void
  59.     {
  60.         $config $event->getConfig();
  61.         if (!$this->validateConfigData($config)) {
  62.             return;
  63.         }
  64.         $options $config['options'] ?? [];
  65.         $options['connect_timeout'] = self::CONNECT_TIMEOUT;
  66.         $options['timeout'] = self::TIMEOUT;
  67.         if (\array_key_exists(RequestOptions::AUTH$options) && !$config['authActive']) {
  68.             unset($options[RequestOptions::AUTH]);
  69.         }
  70.         $sequenceId $event->getFlowState()->sequenceId;
  71.         $event $event->getFlowState()->event;
  72.         $data $this->getAvailableData($event);
  73.         $options $this->buildRequestOptions($options$data$event->getContext());
  74.         $webhookEventId Uuid::randomBytes();
  75.         $timestamp \time();
  76.         $this->connection->executeStatement(
  77.             'INSERT INTO `webhook_event_log` (id, delivery_status, timestamp, webhook_name, event_name, url, request_content, created_at)
  78.             VALUES (:webhookEventId, :deliveryStatus, :timestamp, :webhookName, :eventName, :url, :requestContent, :createdAt)',
  79.             [
  80.                 'webhookEventId' => $webhookEventId,
  81.                 'deliveryStatus' => WebhookEventLogDefinition::STATUS_RUNNING,
  82.                 'timestamp' => $timestamp,
  83.                 'webhookName' => $config['method'] . ': ' $config['baseUrl'],
  84.                 'eventName' => $event->getName(),
  85.                 'url' => $config['baseUrl'],
  86.                 'requestContent' => \json_encode([
  87.                     'method' => $config['method'],
  88.                     'options' => $options,
  89.                 ]),
  90.                 'createdAt' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT),
  91.             ],
  92.         );
  93.         $this->connection->executeStatement(
  94.             'INSERT INTO `swag_sequence_webhook_event_log` (sequence_id, webhook_event_log_id)
  95.             VALUES (:sequenceId, :webhookEventId)',
  96.             [
  97.                 'sequenceId' => Uuid::fromHexToBytes($sequenceId),
  98.                 'webhookEventId' => $webhookEventId,
  99.             ]
  100.         );
  101.         try {
  102.             $response $this->guzzleClient->request($config['method'], $config['baseUrl'], $options);
  103.             $this->connection->executeStatement(
  104.                 'UPDATE `webhook_event_log` SET delivery_status = :deliveryStatus, processing_time = :processingTime,
  105.                         response_content = :responseContent, response_status_code = :responseStatusCode,
  106.                         response_reason_phrase = :responseReasonPhrase
  107.                     WHERE id = :webhookEventId',
  108.                 [
  109.                     'webhookEventId' => $webhookEventId,
  110.                     'deliveryStatus' => WebhookEventLogDefinition::STATUS_SUCCESS,
  111.                     'processingTime' => \time() - $timestamp,
  112.                     'responseContent' => \json_encode([
  113.                         'headers' => $response->getHeaders(),
  114.                         'body' => \json_decode($response->getBody()->getContents(), true),
  115.                     ]),
  116.                     'responseStatusCode' => $response->getStatusCode(),
  117.                     'responseReasonPhrase' => $response->getReasonPhrase(),
  118.                 ],
  119.             );
  120.         } catch (GuzzleException $e) {
  121.             $this->logger->notice(\sprintf('Webhook execution failed to target url "%s".'$config['baseUrl']), [
  122.                 'exceptionMessage' => $e->getMessage(),
  123.                 'statusCode' => $e->getCode(),
  124.             ]);
  125.             $payload = [
  126.                 'webhookEventId' => $webhookEventId,
  127.                 'deliveryStatus' => WebhookEventLogDefinition::STATUS_FAILED,
  128.                 'processingTime' => \time() - $timestamp,
  129.             ];
  130.             if ($e instanceof RequestException && $e->getResponse() !== null) {
  131.                 $response $e->getResponse();
  132.                 $payload \array_merge($payload, [
  133.                     'responseContent' => \json_encode([
  134.                         'headers' => $response->getHeaders(),
  135.                         'body' => \json_decode($response->getBody()->getContents(), true),
  136.                     ]),
  137.                     'responseStatusCode' => $response->getStatusCode(),
  138.                     'responseReasonPhrase' => $response->getReasonPhrase(),
  139.                 ]);
  140.             }
  141.             $this->connection->executeStatement(
  142.                 'UPDATE `webhook_event_log` SET delivery_status = :deliveryStatus, processing_time = :processingTime,
  143.                         response_content = :responseContent, response_status_code = :responseStatusCode,
  144.                         response_reason_phrase = :responseReasonPhrase
  145.                     WHERE id = :webhookEventId',
  146.                 $payload
  147.             );
  148.         }
  149.     }
  150.     private function buildRequestOptions(array $options, array $dataContext $context): array
  151.     {
  152.         /*
  153.          * request headers:
  154.          * $options['headers'] = [
  155.          *     'Content-Type' => 'application/json',
  156.          *     'User-Agent' => 'GuzzleHttp/7',
  157.          * ]
  158.          */
  159.         if (\array_key_exists(RequestOptions::HEADERS$options)) {
  160.             $options[RequestOptions::HEADERS] = $this->resolveOptionParams($options[RequestOptions::HEADERS], $data$context);
  161.         }
  162.         /*
  163.          * request query:
  164.          * $options['query'] = [
  165.          *     'orderNumber' => '{{ order.orderNumber }}',
  166.          *     'message' => 'message test',
  167.          * ]
  168.          */
  169.         if (\array_key_exists(RequestOptions::QUERY$options)) {
  170.             $options[RequestOptions::QUERY] = $this->resolveOptionParams($options[RequestOptions::QUERY], $data$context);
  171.         }
  172.         /*
  173.          * request form params:
  174.          * $options['form_params'] = [
  175.          *     'firstName' => '{{ customer.firstName }}',
  176.          *     'message' => 'Foo',
  177.          * ]
  178.          */
  179.         if (\array_key_exists(RequestOptions::FORM_PARAMS$options)) {
  180.             $options[RequestOptions::FORM_PARAMS] = $this->resolveOptionParams($options[RequestOptions::FORM_PARAMS], $data$context);
  181.         }
  182.         /*
  183.          * request body:
  184.          * $options['body'] = 'Foo bar!'
  185.          * or
  186.          * $options['body'] = '{"chat_id": "332293824", "text": "Hello {{ customer.firstName }}"}'
  187.          */
  188.         if (\array_key_exists(RequestOptions::BODY$options)) {
  189.             $options[RequestOptions::BODY] = $this->resolveParamsData($options[RequestOptions::BODY], $data$context);
  190.         }
  191.         return $options;
  192.     }
  193.     private function resolveOptionParams(array $params, array $dataContext $context): array
  194.     {
  195.         foreach ($params as $key => $value) {
  196.             $params[$key] = $this->resolveParamsData($value$data$context);
  197.         }
  198.         return $params;
  199.     }
  200.     private function resolveParamsData(string $template, array $dataContext $context): ?string
  201.     {
  202.         try {
  203.             return $this->templateRenderer->render($template$data$context);
  204.         } catch (\Throwable $e) {
  205.             $this->logger->error(
  206.                 "Could not render template with error message:\n"
  207.                 $e->getMessage() . "\n"
  208.                 'Error Code:' $e->getCode() . "\n"
  209.                 'Template source:'
  210.                 $template "\n"
  211.                 "Template data: \n"
  212.                 \json_encode($data) . "\n"
  213.             );
  214.             return null;
  215.         }
  216.     }
  217.     private function getAvailableData(FlowEventAware $event): array
  218.     {
  219.         $data = [];
  220.         foreach (\array_keys($event::getAvailableData()->toArray()) as $key) {
  221.             $getter 'get' \ucfirst($key);
  222.             if (!\method_exists($event$getter)) {
  223.                 throw new WebhookActionConfigurationException('Data for ' $key ' not available.'\get_class($event));
  224.             }
  225.             $data[$key] = $event->$getter();
  226.         }
  227.         return $data;
  228.     }
  229.     private function validateConfigData(array $config): bool
  230.     {
  231.         if (!\array_key_exists('method'$config)) {
  232.             $this->logger->error(
  233.                 "Method does not exist in config data:\n"
  234.                 \json_encode($config) . "\n"
  235.             );
  236.             return false;
  237.         }
  238.         if (!\array_key_exists('baseUrl'$config)) {
  239.             $this->logger->error(
  240.                 "Base url does not exist in config data:\n"
  241.                 \json_encode($config) . "\n"
  242.             );
  243.             return false;
  244.         }
  245.         if (!\array_key_exists('authActive'$config)) {
  246.             $this->logger->error(
  247.                 "Auth active does not exist in config data:\n"
  248.                 \json_encode($config) . "\n"
  249.             );
  250.             return false;
  251.         }
  252.         return true;
  253.     }
  254. }