mirror of
				https://github.com/alchemy-fr/Phraseanet.git
				synced 2025-10-27 11:53:12 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			293 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| /*
 | |
|  * This file is part of phrasea-4.1.
 | |
|  *
 | |
|  * (c) Alchemy <info@alchemy.fr>
 | |
|  *
 | |
|  * For the full copyright and license information, please view the LICENSE
 | |
|  * file that was distributed with this source code.
 | |
|  */
 | |
| 
 | |
| namespace Alchemy\Phrasea\Webhook;
 | |
| 
 | |
| use Alchemy\Phrasea\Model\Entities\ApiApplication;
 | |
| use Alchemy\Phrasea\Model\Entities\WebhookEvent;
 | |
| use Alchemy\Phrasea\Model\Entities\WebhookEventDelivery;
 | |
| use Alchemy\Phrasea\Model\Entities\WebhookEventPayload;
 | |
| use Alchemy\Phrasea\Model\Manipulator\WebhookEventDeliveryManipulator;
 | |
| use Alchemy\Phrasea\Model\Manipulator\WebhookEventManipulator;
 | |
| use Alchemy\Phrasea\Model\Repositories\ApiApplicationRepository;
 | |
| use Alchemy\Phrasea\Model\Repositories\WebhookEventDeliveryRepository;
 | |
| use Alchemy\Phrasea\Model\Repositories\WebhookEventPayloadRepository;
 | |
| use Alchemy\Phrasea\Model\Repositories\WebhookEventRepository;
 | |
| use Guzzle\Common\Event;
 | |
| use Guzzle\Http\Client;
 | |
| use Guzzle\Http\Message\EntityEnclosingRequestInterface;
 | |
| use Guzzle\Http\Message\Request;
 | |
| use Guzzle\Http\Message\Response;
 | |
| use Guzzle\Plugin\Backoff\BackoffPlugin;
 | |
| use Guzzle\Plugin\Backoff\CallbackBackoffStrategy;
 | |
| use Guzzle\Plugin\Backoff\CurlBackoffStrategy;
 | |
| use Guzzle\Plugin\Backoff\TruncatedBackoffStrategy;
 | |
| use Psr\Log\LoggerAwareInterface;
 | |
| use Psr\Log\LoggerInterface;
 | |
| use Psr\Log\NullLogger;
 | |
| 
 | |
| /**
 | |
|  * Class WebhookInvoker invokes remote endpoints with webhook event data
 | |
|  * @package Alchemy\Phrasea\Webhook
 | |
|  */
 | |
| class WebhookInvoker implements LoggerAwareInterface
 | |
| {
 | |
| 
 | |
|     /**
 | |
|      * @var ApiApplicationRepository
 | |
|      */
 | |
|     private $applicationRepository;
 | |
| 
 | |
|     /**
 | |
|      * @var WebhookEventDeliveryManipulator
 | |
|      */
 | |
|     private $eventDeliveryManipulator;
 | |
| 
 | |
|     /**
 | |
|      * @var WebhookEventDeliveryRepository
 | |
|      */
 | |
|     private $eventDeliveryRepository;
 | |
| 
 | |
|     /**
 | |
|      * @var Client
 | |
|      */
 | |
|     private $client;
 | |
| 
 | |
|     /**
 | |
|      * @var LoggerInterface
 | |
|      */
 | |
|     private $logger;
 | |
| 
 | |
|     /**
 | |
|      * @var EventProcessorFactory
 | |
|      */
 | |
|     private $processorFactory;
 | |
| 
 | |
|     /**
 | |
|      * @var WebhookEventRepository
 | |
|      */
 | |
|     private $eventRepository;
 | |
| 
 | |
|     /**
 | |
|      * @var WebhookEventManipulator
 | |
|      */
 | |
|     private $eventManipulator;
 | |
| 
 | |
|     /**
 | |
|      * @var WebhookEventPayloadRepository
 | |
|      */
 | |
|     private $eventPayloadRepository;
 | |
| 
 | |
|     /**
 | |
|      * @param ApiApplicationRepository $applicationRepository
 | |
|      * @param EventProcessorFactory $processorFactory
 | |
|      * @param WebhookEventRepository $eventRepository
 | |
|      * @param WebhookEventManipulator $eventManipulator
 | |
|      * @param WebhookEventDeliveryRepository $eventDeliveryRepository
 | |
|      * @param WebhookEventDeliveryManipulator $eventDeliveryManipulator
 | |
|      * @param WebhookEventPayloadRepository $eventPayloadRepository
 | |
|      * @param Client $client
 | |
|      *
 | |
|      * @todo Extract classes to reduce number of required dependencies
 | |
|      */
 | |
|     public function __construct(
 | |
|         ApiApplicationRepository $applicationRepository,
 | |
|         EventProcessorFactory $processorFactory,
 | |
|         WebhookEventRepository $eventRepository,
 | |
|         WebhookEventManipulator $eventManipulator,
 | |
|         WebhookEventDeliveryRepository $eventDeliveryRepository,
 | |
|         WebhookEventDeliveryManipulator $eventDeliveryManipulator,
 | |
|         WebhookEventPayloadRepository $eventPayloadRepository,
 | |
|         Client $client = null
 | |
|     ) {
 | |
|         $this->applicationRepository = $applicationRepository;
 | |
|         $this->processorFactory = $processorFactory;
 | |
|         $this->eventRepository = $eventRepository;
 | |
|         $this->eventManipulator = $eventManipulator;
 | |
|         $this->eventDeliveryManipulator = $eventDeliveryManipulator;
 | |
|         $this->eventDeliveryRepository = $eventDeliveryRepository;
 | |
|         $this->eventPayloadRepository = $eventPayloadRepository;
 | |
| 
 | |
|         $this->client = $client ?: new Client();
 | |
|         $this->logger = new NullLogger();
 | |
| 
 | |
|         $this->configureClient();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets a logger instance on the object.
 | |
|      *
 | |
|      * @param LoggerInterface $logger
 | |
|      *
 | |
|      * @return null
 | |
|      */
 | |
|     public function setLogger(LoggerInterface $logger)
 | |
|     {
 | |
|         $this->logger = $logger;
 | |
|     }
 | |
| 
 | |
|     public function invoke(WebhookEvent $event)
 | |
|     {
 | |
|         $this->doInvoke($event, $this->applicationRepository->findWithDefinedWebhookCallback());
 | |
|     }
 | |
| 
 | |
|     public function invokeUnprocessedEvents()
 | |
|     {
 | |
|         $targetApplications = $this->applicationRepository->findWithDefinedWebhookCallback();
 | |
| 
 | |
|         foreach ($this->eventRepository->getUnprocessedEventIterator() as $row) {
 | |
|             /** @var WebhookEvent $event */
 | |
|             $event = $row[0];
 | |
| 
 | |
|             $this->doInvoke($event, $targetApplications);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param WebhookEvent $event
 | |
|      * @param ApiApplication[] $targets
 | |
|      */
 | |
|     private function doInvoke(WebhookEvent $event, array $targets)
 | |
|     {
 | |
|         $this->eventManipulator->processed($event);
 | |
|         $this->logger->info(sprintf('Processing event "%s" with id %d', $event->getName(), $event->getId()));
 | |
| 
 | |
|         // send requests
 | |
|         $this->doHttpDelivery($event, $targets);
 | |
|     }
 | |
| 
 | |
|     private function configureClient()
 | |
|     {
 | |
|         $this->client->getEventDispatcher()->addListener('request.error', function (Event $event) {
 | |
|             // Override guzzle default behavior of throwing exceptions
 | |
|             // when 4xx & 5xx responses are encountered
 | |
|             $event->stopPropagation();
 | |
|         }, -254);
 | |
| 
 | |
|         // Set callback which logs success or failure
 | |
|         $subscriber = new CallbackBackoffStrategy(function ($retries, Request $request, $response, $e) {
 | |
|             $retry = true;
 | |
|             if ($response && (null !== $deliverId = parse_url($request->getUrl(), PHP_URL_FRAGMENT))) {
 | |
|                 $delivery = $this->eventDeliveryRepository->find($deliverId);
 | |
| 
 | |
|                 $logContext = ['host' => $request->getHost()];
 | |
| 
 | |
|                 if ($response->isSuccessful()) {
 | |
|                     $this->eventDeliveryManipulator->deliverySuccess($delivery);
 | |
| 
 | |
|                     $logType = 'info';
 | |
|                     $logEntry = sprintf('Deliver success event "%d:%s" for app "%s"',
 | |
|                         $delivery->getWebhookEvent()->getId(), $delivery->getWebhookEvent()->getName(),
 | |
|                         $delivery->getThirdPartyApplication()->getName()
 | |
|                     );
 | |
| 
 | |
|                     $retry = false;
 | |
|                 } else {
 | |
|                     $this->eventDeliveryManipulator->deliveryFailure($delivery);
 | |
| 
 | |
|                     $logType = 'error';
 | |
|                     $logEntry = sprintf('Deliver failure event "%d:%s" for app "%s"',
 | |
|                         $delivery->getWebhookEvent()->getId(), $delivery->getWebhookEvent()->getName(),
 | |
|                         $delivery->getThirdPartyApplication()->getName()
 | |
|                     );
 | |
|                 }
 | |
| 
 | |
|                 $this->logger->log($logType, $logEntry, $logContext);
 | |
| 
 | |
|                 return $retry;
 | |
|             }
 | |
|         }, true, new CurlBackoffStrategy());
 | |
| 
 | |
|         // Set max retries
 | |
|         $subscriber = new TruncatedBackoffStrategy(WebhookEventDelivery::MAX_DELIVERY_TRIES, $subscriber);
 | |
|         $subscriber = new BackoffPlugin($subscriber);
 | |
| 
 | |
|         $this->client->addSubscriber($subscriber);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param WebhookEvent $event
 | |
|      * @param ApiApplication[] $targets
 | |
|      */
 | |
|     private function doHttpDelivery(
 | |
|         WebhookEvent $event,
 | |
|         array $targets
 | |
|     ) {
 | |
|         if (count($targets) === 0) {
 | |
|             $this->logger->info(sprintf('No applications defined to listen for webhook events'));
 | |
| 
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Format event data
 | |
|         $eventProcessor = $this->processorFactory->getProcessor($event);
 | |
|         $data = $eventProcessor->process($event);
 | |
| 
 | |
|         foreach ($targets as $thirdPartyApplication) {
 | |
|             $delivery = $this->eventDeliveryManipulator->create($thirdPartyApplication, $event);
 | |
|             // Append delivery id as url anchor
 | |
|             $uniqueUrl = $this->buildUrl($thirdPartyApplication, $delivery);
 | |
| 
 | |
|             // Create http request with data as request body
 | |
|             $request = $this->client->createRequest('POST', $uniqueUrl, [
 | |
|                 'Content-Type' => 'application/vnd.phraseanet.event+json'
 | |
|             ], json_encode($data));
 | |
| 
 | |
|             $requestBody = $request instanceof EntityEnclosingRequestInterface ? $request->getBody() : '';
 | |
| 
 | |
|             try {
 | |
|                 $response = $request->send();
 | |
| 
 | |
|                 $responseBody = $response->getBody(true);
 | |
|                 $statusCode = $response->getStatusCode();
 | |
|                 $headers = $this->extractResponseHeaders($response);
 | |
|             }
 | |
|             catch (\Exception $exception) {
 | |
|                 $responseBody = $exception->getMessage();
 | |
|                 $statusCode = -1;
 | |
|                 $headers = '';
 | |
|             }
 | |
| 
 | |
|             $deliveryPayload = new WebhookEventPayload(
 | |
|                 $delivery,
 | |
|                 $requestBody,
 | |
|                 $responseBody,
 | |
|                 $statusCode,
 | |
|                 $headers
 | |
|             );
 | |
| 
 | |
|             $this->eventPayloadRepository->save($deliveryPayload);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param ApiApplication $application
 | |
|      * @param WebhookEventDelivery $delivery
 | |
|      * @return string
 | |
|      */
 | |
|     private function buildUrl(ApiApplication $application, WebhookEventDelivery $delivery)
 | |
|     {
 | |
|         return sprintf('%s#%s', $application->getWebhookUrl(), $delivery->getId());
 | |
|     }
 | |
| 
 | |
|     private function extractResponseHeaders(Response $response)
 | |
|     {
 | |
|         $headerCollection = $response->getHeaders()->toArray();
 | |
|         $headers = '';
 | |
| 
 | |
|         foreach ($headerCollection as $name => $value) {
 | |
|             $headers .= sprintf('%s: %s', $name, $value) . PHP_EOL;
 | |
|         }
 | |
| 
 | |
|         return trim($headers);
 | |
|     }
 | |
| }
 | 
