<?php
namespace App\EventSubscriber\Webhooks;
use App\Entity\VOD\Operator;
use App\Entity\VOD\OperatorCountry;
use App\Entity\VOD\OperatorWebhook;
use App\Entity\VOD\OperatorWebhookLog;
use App\Entity\VOD\Order;
use App\Entity\VOD\OrderCountry;
use App\Entity\VOD\OrderDetail;
use App\Entity\VOD\Title;
use App\Entity\VOD\TitleExcludedOperator;
use App\Entity\VOD\TitleLocalizedText;
use App\Entity\VOD\TitleMediaFormat;
use App\Enum\PressSite\DomainLanguageEnum;
use App\Event\VOD\WebhookEvent;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Enqueue\Util\JSON;
use Ramsey\Uuid\Uuid;
class MetaSubscriber extends BaseWebhookSubscriber
{
/**
* @var Title
*/
protected $entity;
/**
* @var Order|null
*/
private $order;
private $ordersOnTitle = [];
public function __construct(EntityManagerInterface $entityManager)
{
parent::__construct($entityManager);
}
public function process(WebhookEvent $event): void
{
parent::process($event);
$this->order = $event->getOrder();
$operatorRepo = $this->entityManager->getRepository(Operator::class);
if (!($this->entity instanceof Title)) {
return;
}
$message = [];
$message['messageID'] = Uuid::uuid4()->toString();
$message['topic'] = 'metadata-operation';
$message['entityLabel'] = $this->getEntityLabel();
$message['mediaIDs'] = $this->getMediaIds();
$message['formats'] = $this->getMediaFormats();
$message['eventName'] = $this->getEventName();
$message['timestamp'] = (new DateTime())->format(DateTime::ISO8601);
$operators = $operatorRepo->findEligibleOperatorsbyTitle($this->entity);
$this->ordersOnTitle = $this->entityManager->createQueryBuilder()
->select('o')
->from(Order::class, 'o')
->where('o.title = :title')
->andWhere('o.closed = true')
->setParameter('title', $this->entity)
->getQuery()
->getResult();
switch ($this->event->getAction()) {
case 'create':
$operators = array_unique(
array_merge(
$this->filterOperatorsOnCreate($operators),
$operatorRepo->findBy(['id' => $event->getOperators()])
)
);
$this->addToQueue($operators, $message);
break;
case 'update':
$operators = array_unique(
array_merge(
$this->filterOperatorsOnUpdate($operators),
$operatorRepo->findBy(['id' => $event->getOperators()])
)
);
$this->addToQueue($operators, $message);
break;
case 'delete':
$operators = array_unique(
array_merge(
$this->filterOperatorsOnDelete($operators),
$operatorRepo->findBy(['id' => $event->getOperators()])
)
);
$this->addToQueue($operators, $message);
break;
default:
return;
}
$event->stopPropagation();
}
protected function filterOperatorsOnCreate(array $operators): array
{
// If the title got created, through an order, extract the operators and languages here.
if (!empty($this->order)) {
$this->ordersOnTitle[] = $this->order;
// Remove all operators. They are useless, and the affected operators are set in the event class.
$operators = [];
/** @var OrderCountry $country */
foreach ($this->order->getCountries() as $country) {
$language = DomainLanguageEnum::getLanguageByCountry($country->getCountry());
$this->affected_countries[$language] = $country->getCountry();
}
return $operators;
}
// This will catch any operator, that is being _removed_ from the exclude list.
$included_operators = $this->entityManager->getUnitOfWork()->getScheduledEntityDeletions();
foreach ($included_operators as $included_operator) {
if ($included_operator instanceof TitleExcludedOperator) {
foreach ($included_operator->getCountries() as $country) {
if (in_array($country, $this->entity->getAllApprovedCountriesForExport())) {
$this->affected_countries[DomainLanguageEnum::getLanguageByCountry($country)] = $country;
}
}
}
}
// If the metadata of the VOD title was created through enabling a language, find the localized languages that
// are enabled.
/** @var TitleLocalizedText $localizedText */
foreach ($this->entity->getLocalizedTexts() as $localizedText) {
$change_set = $this->entityManager->getUnitOfWork()->getEntityChangeSet($localizedText);
$country = DomainLanguageEnum::getCountryByLanguage($localizedText->getLanguage());
if (!empty($change_set)) {
if ($localizedText->isExportApproved()) {
$this->affected_countries[$localizedText->getLanguage()] = $country;
}
}
}
// As a last resort, return an unmodified list of operators.
return $operators;
}
protected function filterOperatorsOnUpdate(array $operators): array
{
// If the VOD title it-self was not update, start calculating what languages that was affected.
if (empty($this->entityManager->getUnitOfWork()->getEntityChangeSet($this->entity))) {
// Check if the individual operators has the changed language available. If not, remove them from the list.
$country_updated = [];
foreach ($this->entity->getLocalizedTexts() as $localizedText) {
$change_set = $this->entityManager->getUnitOfWork()->getEntityChangeSet($localizedText);
$country = DomainLanguageEnum::getCountryByLanguage($localizedText->getLanguage());
if (!empty($change_set)) {
$country_updated[] = $country;
if ($localizedText->isExportApproved()) {
$this->affected_countries[$localizedText->getLanguage()] = $country;
}
}
}
if (!empty($country_updated)) {
/** @var Operator $operator */
foreach ($operators as $index => $operator) {
// Run through all the languages that was updated and make sure that the operator is supporting the
// language in question.
$operator_eligible_for_deleted_language = false;
foreach ($country_updated as $country) {
if ($operator->hasCountryByCode($country)) {
$operator_eligible_for_deleted_language = true;
break;
}
}
if (!$operator_eligible_for_deleted_language) {
unset($operators[$index]);
}
}
return $operators;
}
}
// If the metadata of the VOD title was updated, find the localized languages that are enabled.
/** @var TitleLocalizedText $localizedText */
foreach ($this->entity->getLocalizedTexts() as $localizedText) {
if ($localizedText->isExportApproved()) {
$country = DomainLanguageEnum::getCountryByLanguage($localizedText->getLanguage());
$this->affected_countries[$localizedText->getLanguage()] = $country;
}
}
// As a last resort, return an unmodified list of operators.
return $operators;
}
protected function filterOperatorsOnDelete(array $operators): array
{
// In cases where we are dealing with a localized text that has been disabled, find out which language that
// was removed and save it for later
$country_disabled = [];
foreach ($this->entity->getLocalizedTexts() as $localizedText) {
$change_set = $this->entityManager->getUnitOfWork()->getEntityChangeSet($localizedText);
$country = DomainLanguageEnum::getCountryByLanguage($localizedText->getLanguage());
if (!empty($change_set['exportApproved']) &&
$change_set['exportApproved'][0] === true &&
!in_array($country, $country_disabled, false)) {
$country_disabled[] = $country;
$this->affected_countries[$localizedText->getLanguage()] = $country;
}
}
// If $country_disabled is not empty it means that we are dealing with a deleted localized text.
// Not a deleted title.
if (!empty($country_disabled)) {
/** @var Operator $operator */
foreach ($operators as $index => $operator) {
// Run through all the languages that was deleted and make sure that the operator is supporting the
// language in question.
$operator_eligible_for_deleted_language = false;
foreach ($country_disabled as $country) {
if ($operator->hasCountryByCode($country)) {
$operator_eligible_for_deleted_language = true;
break;
}
}
if (!$operator_eligible_for_deleted_language) {
unset($operators[$index]);
}
}
return $operators;
}
// If the title got deleted, through an order, extract the operators and languages here.
if (!empty($this->order)) {
$this->ordersOnTitle[] = $this->order;
// Remove all operators. They are useless, and the affected operators are set in the event class.
$operators = [];
/** @var OrderCountry $country */
foreach ($this->order->getCountries() as $country) {
$language = DomainLanguageEnum::getLanguageByCountry($country->getCountry());
$this->affected_countries[$language] = $country->getCountry();
}
return $operators;
}
// If the operator was excluded from the VOD title, fetch all the available languages from the title.
$excluded_operators = $this->entityManager->getUnitOfWork()->getScheduledEntityInsertions();
foreach ($excluded_operators as $excluded_operator) {
if ($excluded_operator instanceof TitleExcludedOperator) {
foreach ($excluded_operator->getCountries() as $country) {
if (in_array($country, $this->entity->getAllApprovedCountriesForExport())) {
$this->affected_countries[DomainLanguageEnum::getLanguageByCountry($country)] = $country;
}
}
}
}
// As a last resort, return an unmodified list of operators.
return $operators;
}
protected function getMediaFormats(): array
{
return $this->entity->getMediaFormats()->map(fn($format) => $format->getFormat())->toArray();
}
protected function getEntityLabel(): ?string
{
return $this->entity->getOriginalTitle();
}
protected function getEventName(): string
{
return 'metadata-' . $this->event->getAction() . 'd';
}
protected function getMediaIds(): array
{
return $this->entity->getMediaFormats()->map(fn(TitleMediaFormat $format) => $format->getId())->toArray();
}
protected function addToQueue($operators, $message): void
{
/** @var Operator $operator */
foreach ($operators as $operator) {
if (!empty($operator->getWebhooks()->toArray())) {
$personalized_message = $this->personalizeMessage($operator, $message);
// Remove languages from the message, if there is no closed order for that language.
if (!empty($personalized_message['country'])) {
foreach ($personalized_message['country'] as $index => $country) {
$order_country_exists = false;
/** @var Order $order */
foreach ($this->ordersOnTitle as $order) {
/** @var OrderDetail $order_detail */
foreach ($order->getDetails() as $order_detail) {
if ($order_detail->getOperator() !== null) {
if ($order_detail->getOperator()->getId() === $operator->getId()) {
foreach ($order->getCountriesAsArray() as $order_country) {
if ($order_country === $country) {
$order_country_exists = true;
break;
}
}
}
}
}
}
if (!$order_country_exists) {
unset($personalized_message['country'][$index], $personalized_message['language'][$index]);
}
}
}
$personalized_message['country'] = array_values($personalized_message['country']);
$personalized_message['language'] = array_values($personalized_message['language']);
// Only continue, if there is a language set in the personalized message.
// If the country is missing this message is no longer relevant to the operator.
if (!empty($personalized_message['country'])) {
/* @var OperatorWebhook $webhook */
foreach ($operator->getWebhooks() as $webhook) {
// Add header key and Value to the logs if they exist in their
// fields on the operator's tab. VOD -> Operators -> Webhook tab
if ($webhook->getHeaderKey() && $webhook->getHeaderValue()) {
$headerKey = $webhook->getHeaderKey();
$headerValue = $webhook->getHeaderValue();
$personalized_message[$headerKey] = $headerValue;
}
if (in_array($message['topic'], $webhook->getTopics(), true)) {
$personalized_message_json = JSON::encode($personalized_message);
if (empty($this->messagesSent[$operator->getId()][$personalized_message['messageID']])) {
$this->messagesSent[$operator->getId()][$personalized_message['messageID']] =
$personalized_message_json;
$now = (new \DateTime())->format('Y-m-d H:i:s');
// We run this entity insert around Doctrine to avoid infinite loops from the
// event listeners.
$sql = "INSERT INTO `vod_operator_webhook_log`"
." (`created_at`, `webhook_id`, `request_body`) "
."VALUES ('{$now}',{$webhook->getId()},'{$personalized_message_json}')";
$this->entityManager->getConnection()->executeQuery($sql);
}
}
}
}
}
}
}
}