Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
<?php
namespace Mautic\WebhookBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Mautic\ApiBundle\Serializer\Driver\ApiMetadataDriver;
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
class Event
{
/**
* @var int
*/
private $id;
/**
* @var Webhook
*/
private $webhook;
/**
* @var ArrayCollection<int, WebhookQueue>
*/
private $queues;
/**
* @var string
*/
private $eventType;
public function __construct()
{
$this->queues = new ArrayCollection();
}
public static function loadMetadata(ORM\ClassMetadata $metadata): void
{
$builder = new ClassMetadataBuilder($metadata);
$builder->setTable('webhook_events')
->setCustomRepositoryClass(EventRepository::class);
$builder->addId();
$builder->createManyToOne('webhook', 'Webhook')
->inversedBy('events')
->cascadeDetach()
->cascadeMerge()
->addJoinColumn('webhook_id', 'id', false, false, 'CASCADE')
->build();
$builder->createOneToMany('queues', 'WebhookQueue')
->mappedBy('event')
->cascadeDetach()
->cascadeMerge()
->fetchExtraLazy()
->build();
$builder->createField('eventType', 'string')
->columnName('event_type')
->length(50)
->build();
}
/**
* Prepares the metadata for API usage.
*/
public static function loadApiMetadata(ApiMetadataDriver $metadata): void
{
$metadata->setGroupPrefix('event')
->addListProperties(
[
'eventType',
]
)
->build();
}
/**
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* @return Webhook
*/
public function getWebhook()
{
return $this->webhook;
}
/**
* @return $this
*/
public function setWebhook(Webhook $webhook)
{
$this->webhook = $webhook;
return $this;
}
/**
* @return mixed
*/
public function getEventType()
{
return $this->eventType;
}
/**
* @param mixed $eventType
*/
public function setEventType($eventType)
{
$this->eventType = $eventType;
return $this;
}
/**
* @param ArrayCollection $queues
*
* @return self
*/
public function setQueues($queues)
{
$this->queues = $queues;
return $this;
}
/**
* @return ArrayCollection
*/
public function getQueues()
{
return $this->queues;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Mautic\WebhookBundle\Entity;
use Mautic\CoreBundle\Entity\CommonRepository;
/**
* @extends CommonRepository<Event>
*/
class EventRepository extends CommonRepository
{
/**
* @return array
*/
public function getEntitiesByEventType($type)
{
$alias = $this->getTableAlias();
$q = $this->createQueryBuilder($alias)
->leftJoin($alias.'.webhook', 'u');
$q->where(
$q->expr()->eq($alias.'.eventType', ':type')
)->setParameter('type', $type);
// only find published webhooks
$q->andWhere($q->expr()->eq('u.isPublished', ':published'))
->setParameter('published', 1);
return $q->getQuery()->getResult();
}
}

View File

@@ -0,0 +1,159 @@
<?php
namespace Mautic\WebhookBundle\Entity;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping\ClassMetadata;
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
class Log
{
/**
* @var int
*/
private $id;
/**
* @var Webhook
*/
private $webhook;
/**
* @var string
*/
private $statusCode;
/**
* @var \DateTimeInterface
*/
private $dateAdded;
/**
* @var float|null
*/
private $runtime;
private ?string $note = null;
public static function loadMetadata(ClassMetadata $metadata): void
{
$builder = new ClassMetadataBuilder($metadata);
$builder->setTable('webhook_logs')
->setCustomRepositoryClass(LogRepository::class)
->addIndex(['webhook_id', 'date_added'], 'webhook_id_date_added')
->addId();
$builder->createManyToOne('webhook', 'Webhook')
->inversedBy('logs')
->addJoinColumn('webhook_id', 'id', false, false, 'CASCADE')
->build();
$builder->createField('statusCode', Types::STRING)
->columnName('status_code')
->length(50)
->build();
$builder->addNullableField('dateAdded', Types::DATETIME_MUTABLE, 'date_added');
$builder->addNullableField('note', Types::STRING);
$builder->addNullableField('runtime', Types::FLOAT);
}
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return Webhook
*/
public function getWebhook()
{
return $this->webhook;
}
/**
* @return Log
*/
public function setWebhook(Webhook $webhook)
{
$this->webhook = $webhook;
return $this;
}
/**
* @return mixed
*/
public function getStatusCode()
{
return $this->statusCode;
}
/**
* @param mixed $statusCode
*
* @return Log
*/
public function setStatusCode($statusCode)
{
$this->statusCode = $statusCode;
return $this;
}
/**
* @return \DateTimeInterface
*/
public function getDateAdded()
{
return $this->dateAdded;
}
/**
* @return Log
*/
public function setDateAdded(\DateTime $dateAdded)
{
$this->dateAdded = $dateAdded;
return $this;
}
public function getNote(): ?string
{
return $this->note;
}
/**
* Strips tags and keeps first 191 characters so it would fit in the varchar 191 limit.
*/
public function setNote(?string $note): self
{
$this->note = $note ? substr(strip_tags(iconv('UTF-8', 'UTF-8//IGNORE', $note)), 0, 190) : $note;
return $this;
}
/**
* @return float
*/
public function getRuntime()
{
return $this->runtime;
}
/**
* @param float $runtime
*
* @return Log
*/
public function setRuntime($runtime)
{
$this->runtime = round($runtime, 2);
return $this;
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace Mautic\WebhookBundle\Entity;
use Doctrine\DBAL\ParameterType;
use Mautic\CoreBundle\Entity\CommonRepository;
/**
* @extends CommonRepository<Log>
*/
class LogRepository extends CommonRepository
{
private const LOG_DELETE_BATCH_SIZE = 5000;
/**
* @return int[]
*/
public function getWebhooksBasedOnLogLimit(int $logMaxLimit): array
{
$qb = $this->_em->getConnection()->createQueryBuilder();
$qb->select('webhook_id')
->from(MAUTIC_TABLE_PREFIX.'webhook_logs', $this->getTableAlias())
->groupBy('webhook_id')
->having('count(id) > :logMaxLimit')
->setParameter('logMaxLimit', $logMaxLimit);
return array_map(
static fn ($row): int => (int) $row['webhook_id'],
$qb->executeQuery()->fetchAllAssociative()
);
}
/**
* Retains a rolling number of log records for a webhook id.
*/
public function removeLimitExceedLogs(int $webHookId, int $logMax): int
{
$deletedLogs = 0;
$table_name = $this->getTableName();
$conn = $this->getEntityManager()->getConnection();
$id = $conn->createQueryBuilder()
->select('id')
->from($table_name)
->where('webhook_id = '.$webHookId)
->orderBy('id', 'DESC')
->setMaxResults(1)
->setFirstResult($logMax) // if log max limit is 1000 then it will fetch id of 1001'th record from last and we will delete all log which have id less than or equal to this id.
->executeQuery()->fetchOne();
if ($id) {
$sql = "DELETE FROM {$table_name} WHERE webhook_id = (?) and id <= (?) LIMIT ".self::LOG_DELETE_BATCH_SIZE;
while ($rows = $conn->executeStatement($sql, [$webHookId, $id], [ParameterType::INTEGER, ParameterType::INTEGER])) {
$deletedLogs += $rows;
}
}
return $deletedLogs;
}
/**
* Lets assume that all HTTP status codes 2** are a success.
* This method will count the latest success codes until the $limit
* and divide them with the all requests until the limit.
*
* 0 = 100% responses failed
* 1 = 100% responses are successful
* null = no log rows yet
*
* @param int $webhookId
* @param int $limit
*
* @return float|null
*/
public function getSuccessVsErrorStatusCodeRatio($webhookId, $limit)
{
// Generate query to select last X = $limit rows
$selectqb = $this->_em->getConnection()->createQueryBuilder();
$selectqb->select('*')
->from(MAUTIC_TABLE_PREFIX.'webhook_logs', $this->getTableAlias())
->where($this->getTableAlias().'.webhook_id = :webhookId')
->setFirstResult(0)
->setMaxResults($limit)
->orderBy($this->getTableAlias().'.date_added', 'DESC');
// Count all responses
$countAllQb = $this->_em->getConnection()->createQueryBuilder();
$countAllQb->select('COUNT('.$this->getTableAlias().'.id) AS thecount')
->from(sprintf('(%s)', $selectqb->getSQL()), $this->getTableAlias())
->setParameter('webhookId', $webhookId);
$result = $countAllQb->executeQuery()->fetchAssociative();
if (isset($result['thecount'])) {
$allCount = (int) $result['thecount'];
} else {
return null;
}
// Count successful responses
$countSuccessQb = $this->_em->getConnection()->createQueryBuilder();
$countSuccessQb->select('COUNT('.$this->getTableAlias().'.id) AS thecount')
->from(sprintf('(%s)', $selectqb->getSQL()), $this->getTableAlias())
->andWhere($countSuccessQb->expr()->gte($this->getTableAlias().'.status_code', 200))
->andWhere($countSuccessQb->expr()->lt($this->getTableAlias().'.status_code', 300))
->setParameter('webhookId', $webhookId);
$result = $countSuccessQb->executeQuery()->fetchAssociative();
if (isset($result['thecount'])) {
$successCount = (int) $result['thecount'];
}
if (!empty($allCount) && isset($successCount)) {
return $successCount / $allCount;
}
return null;
}
}

View File

@@ -0,0 +1,636 @@
<?php
namespace Mautic\WebhookBundle\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Mautic\ApiBundle\Serializer\Driver\ApiMetadataDriver;
use Mautic\CategoryBundle\Entity\Category;
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
use Mautic\CoreBundle\Entity\FormEntity;
use Mautic\CoreBundle\Entity\SkipModifiedInterface;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Mapping\ClassMetadata;
#[ApiResource(
shortName: 'Webhooks',
operations: [
new GetCollection(uriTemplate: '/webhooks', security: "is_granted('webhook:webhooks:viewown')"),
new Post(uriTemplate: '/webhooks', security: "is_granted('webhook:webhooks:create')"),
new Get(uriTemplate: '/webhooks/{id}', security: "is_granted('webhook:webhooks:viewown')"),
new Put(uriTemplate: '/webhooks/{id}', security: "is_granted('webhook:webhooks:editown')"),
new Patch(uriTemplate: '/webhooks/{id}', security: "is_granted('webhook:webhooks:editother')"),
new Delete(uriTemplate: '/webhooks/{id}', security: "is_granted('webhook:webhooks:deleteown')"),
],
normalizationContext: [
'groups' => ['webhook:read'],
'swagger_definition_name' => 'Read',
'api_included' => ['category'],
],
denormalizationContext: [
'groups' => ['webhook:write'],
'swagger_definition_name' => 'Write',
]
)]
class Webhook extends FormEntity implements SkipModifiedInterface
{
public const LOGS_DISPLAY_LIMIT = 100;
/**
* @var ?int
*/
#[Groups(['webhook:read'])]
private $id;
/**
* @var ?string
*/
#[Groups(['webhook:read', 'webhook:write'])]
private $name;
/**
* @var string|null
*/
#[Groups(['webhook:read', 'webhook:write'])]
private $description;
/**
* @var ?string
*/
#[Groups(['webhook:read', 'webhook:write'])]
private $webhookUrl;
/**
* @var ?string
*/
#[Groups(['webhook:read', 'webhook:write'])]
private $secret;
/**
* @var Category|null
**/
#[Groups(['webhook:read', 'webhook:write'])]
private $category;
/**
* @var Collection<int, Event>
*/
#[Groups(['webhook:read', 'webhook:write'])]
private $events;
/**
* @var ArrayCollection<int, Log>
*/
private $logs;
/**
* @var array
*/
private $removedEvents = [];
/**
* @var mixed[]
*/
#[Groups(['webhook:read', 'webhook:write'])]
private $payload;
/**
* Holds a simplified array of events, just an array of event types.
* It's used for API serializaiton.
*
* @var array
*/
#[Groups(['webhook:read', 'webhook:write'])]
private $triggers = [];
/**
* ASC or DESC order for fetching order of the events when queue mode is on.
* Null means use the global default.
*
* @var string|null
*/
#[Groups(['webhook:read', 'webhook:write'])]
private $eventsOrderbyDir;
private ?\DateTimeImmutable $markedUnhealthyAt = null;
private ?\DateTimeImmutable $unHealthySince = null;
private ?\DateTimeImmutable $lastNotificationSentAt = null;
public function __construct()
{
$this->events = new ArrayCollection();
$this->logs = new ArrayCollection();
}
public static function loadMetadata(ORM\ClassMetadata $metadata): void
{
$builder = new ClassMetadataBuilder($metadata);
$builder->setTable('webhooks')
->setCustomRepositoryClass(WebhookRepository::class);
$builder->addIdColumns();
$builder->addCategory();
$builder->createOneToMany('events', 'Event')
->orphanRemoval()
->setIndexBy('eventType')
->mappedBy('webhook')
->cascadePersist()
->cascadeMerge()
->cascadeDetach()
->build();
$builder->createOneToMany('logs', 'Log')->setOrderBy(['dateAdded' => Order::Descending->value])
->fetchExtraLazy()
->mappedBy('webhook')
->cascadePersist()
->cascadeMerge()
->cascadeDetach()
->build();
$builder->addNamedField('webhookUrl', Types::TEXT, 'webhook_url');
$builder->addField('secret', Types::STRING);
$builder->addNullableField('eventsOrderbyDir', Types::STRING, 'events_orderby_dir');
$builder->addNullableField('markedUnhealthyAt', Types::DATETIME_IMMUTABLE, 'marked_unhealthy_at');
$builder->addNullableField('unHealthySince', Types::DATETIME_IMMUTABLE, 'unhealthy_since');
$builder->addNullableField('lastNotificationSentAt', Types::DATETIME_IMMUTABLE, 'last_notification_sent_at');
}
/**
* Prepares the metadata for API usage.
*/
public static function loadApiMetadata(ApiMetadataDriver $metadata): void
{
$metadata->setGroupPrefix('hook')
->addListProperties(
[
'id',
'name',
'description',
'webhookUrl',
'secret',
'eventsOrderbyDir',
'category',
'triggers',
]
)
->build();
}
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
$metadata->addPropertyConstraint(
'name',
new NotBlank(
[
'message' => 'mautic.core.name.required',
]
)
);
$metadata->addPropertyConstraint(
'webhookUrl',
new Assert\Url(
[
'message' => 'mautic.core.valid_url_required',
]
)
);
$metadata->addPropertyConstraint(
'webhookUrl',
new NotBlank(
[
'message' => 'mautic.core.valid_url_required',
]
)
);
$metadata->addPropertyConstraint(
'eventsOrderbyDir',
new Assert\Choice(
[
null,
Order::Ascending->value,
Order::Descending->value,
]
)
);
}
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @param string $name
*
* @return Webhook
*/
public function setName($name)
{
$this->isChanged('name', $name);
$this->name = $name;
return $this;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $description
*
* @return Webhook
*/
public function setDescription($description)
{
$this->isChanged('description', $description);
$this->description = $description;
return $this;
}
/**
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* @param string $webhookUrl
*
* @return Webhook
*/
public function setWebhookUrl($webhookUrl)
{
$this->isChanged('webhookUrl', $webhookUrl);
$this->webhookUrl = $webhookUrl;
return $this;
}
/**
* @return string
*/
public function getWebhookUrl()
{
return $this->webhookUrl;
}
/**
* @param ?string $secret
*
* @return Webhook
*/
public function setSecret($secret)
{
$this->isChanged('secret', $secret);
$this->secret = $secret;
return $this;
}
/**
* @return ?string
*/
public function getSecret()
{
return $this->secret;
}
/**
* @return Webhook
*/
public function setCategory(?Category $category = null)
{
$this->isChanged('category', $category);
$this->category = $category;
return $this;
}
/**
* @return Category
*/
public function getCategory()
{
return $this->category;
}
/**
* @return Collection<int, Event>
*/
public function getEvents()
{
return $this->events;
}
/**
* @param Collection<int, Event> $events
*
* @return $this
*/
public function setEvents($events)
{
$this->isChanged('events', $events);
$this->events = $events;
foreach ($events as $event) {
$event->setWebhook($this);
}
return $this;
}
/**
* This builds a simple array with subscribed events.
*/
public function buildTriggers(): void
{
foreach ($this->events as $event) {
$this->triggers[] = $event->getEventType();
}
}
/**
* Takes the array of triggers and builds events from them if they don't exist already.
*/
public function setTriggers(array $triggers): void
{
foreach ($triggers as $key) {
$this->addTrigger($key);
}
}
/**
* Takes a trigger (event type) and builds the Event object form it if it doesn't exist already.
*
* @param string $key
*/
public function addTrigger($key): bool
{
if ($this->eventExists($key)) {
return false;
}
$event = new Event();
$event->setEventType($key);
$event->setWebhook($this);
$this->addEvent($event);
return true;
}
/**
* Check if an event exists comared to its type.
*
* @param string $key
*/
public function eventExists($key): bool
{
foreach ($this->events as $event) {
if ($event->getEventType() === $key) {
return true;
}
}
return false;
}
/**
* @return $this
*/
public function addEvent(Event $event)
{
$this->isChanged('events', $event);
$this->events[] = $event;
return $this;
}
/**
* @return $this
*/
public function removeEvent(Event $event)
{
$this->isChanged('events', $event);
$this->removedEvents[] = $event;
$this->events->removeElement($event);
return $this;
}
/**
* @param string $eventsOrderbyDir
*/
public function setEventsOrderbyDir($eventsOrderbyDir)
{
$this->isChanged('eventsOrderbyDir', $eventsOrderbyDir);
$this->eventsOrderbyDir = $eventsOrderbyDir;
return $this;
}
/**
* @return string
*/
public function getEventsOrderbyDir()
{
return $this->eventsOrderbyDir;
}
/**
* Get log entities.
*
* @return ArrayCollection<int,Log>
*/
public function getLogs()
{
return $this->logs;
}
/**
* @return Collection<int,Log>
*/
public function getLimitedLogs(): Collection
{
$criteria = Criteria::create()
->setMaxResults(self::LOGS_DISPLAY_LIMIT);
return $this->logs->matching($criteria);
}
/**
* @param ArrayCollection<int,Log> $logs
*
* @return $this
*/
public function addLogs($logs)
{
$this->logs = $logs;
/** @var Log $log */
foreach ($logs as $log) {
$log->setWebhook($this);
}
return $this;
}
/**
* @return $this
*/
public function addLog(Log $log)
{
$this->logs[] = $log;
return $this;
}
/**
* @return $this
*/
public function removeLog(Log $log)
{
$this->logs->removeElement($log);
return $this;
}
/**
* @return array
*/
public function getPayload()
{
return $this->payload;
}
/**
* @return Webhook
*/
public function setPayload($payload)
{
$this->payload = $payload;
return $this;
}
public function wasModifiedRecently(): bool
{
$dateModified = $this->getDateModified();
if (null === $dateModified) {
return false;
}
$aWhileBack = (new \DateTime())->modify('-2 days');
if ($dateModified < $aWhileBack) {
return false;
}
return true;
}
/**
* @param string $prop
*/
protected function isChanged($prop, $val)
{
$getter = 'get'.ucfirst($prop);
$current = $this->$getter();
if ('category' == $prop) {
$currentId = ($current) ? $current->getId() : '';
$newId = ($val) ? $val->getId() : null;
if ($currentId != $newId) {
$this->changes[$prop] = [$currentId, $newId];
}
} elseif ('events' == $prop) {
$this->changes[$prop] = [];
} elseif ($current != $val) {
$this->changes[$prop] = [$current, $val];
} else {
parent::isChanged($prop, $val);
}
}
public function getMarkedUnhealthyAt(): ?\DateTimeImmutable
{
return $this->markedUnhealthyAt;
}
public function setMarkedUnhealthyAt(?\DateTimeImmutable $markedUnhealthyAt): Webhook
{
$this->isChanged('markedUnhealthyAt', $markedUnhealthyAt);
$this->markedUnhealthyAt = $markedUnhealthyAt;
return $this;
}
public function getUnHealthySince(): ?\DateTimeImmutable
{
return $this->unHealthySince;
}
public function setUnHealthySince(?\DateTimeImmutable $unHealthySince): self
{
$this->unHealthySince = $unHealthySince;
return $this;
}
public function getLastNotificationSentAt(): ?\DateTimeImmutable
{
return $this->lastNotificationSentAt;
}
public function setLastNotificationSentAt(?\DateTimeImmutable $lastNotificationSentAt): self
{
$this->lastNotificationSentAt = $lastNotificationSentAt;
return $this;
}
/**
* Do not update modified_by and date_modified fields if only DNC or manipulator was changed.
* Avoid unnecessary update queries.
*/
public function shouldSkipSettingModifiedProperties(): bool
{
$changes = $this->changes;
unset($changes['markedUnhealthyAt']);
unset($changes['unHealthySince']);
unset($changes['lastNotificationSentAt']);
return 0 === count($changes);
}
}

View File

@@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
namespace Mautic\WebhookBundle\Entity;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
class WebhookQueue
{
public const TABLE_NAME = 'webhook_queue';
private ?string $id = null;
private ?Webhook $webhook;
private ?\DateTime $dateAdded;
private ?\DateTimeImmutable $dateModified; // @phpstan-ignore-line (BC: plain payload is fetched by ORM)
/**
* @var string|resource|null
*/
private $payloadCompressed;
private ?Event $event;
private int $retries = 0;
public static function loadMetadata(ORM\ClassMetadata $metadata): void
{
$builder = new ClassMetadataBuilder($metadata);
$builder->setTable(WebhookQueue::TABLE_NAME)
->setCustomRepositoryClass(WebhookQueueRepository::class);
$builder->addBigIntIdField();
$builder->createManyToOne('webhook', 'Webhook')
->addJoinColumn('webhook_id', 'id', false, false, 'CASCADE')
->build();
$builder->addNullableField('dateAdded', Types::DATETIME_MUTABLE, 'date_added');
$builder->addNullableField('dateModified', Types::DATETIME_IMMUTABLE, 'date_modified');
$builder->createField('payloadCompressed', Types::BLOB)
->columnName('payload_compressed')
->nullable()
->length(MySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB)
->build();
$builder->createManyToOne('event', 'Event')
->inversedBy('queues')
->addJoinColumn('event_id', 'id', false, false, 'CASCADE')
->build();
$builder->createField('retries', Types::SMALLINT)
->columnName('retries')
->option('unsigned', true)
->option('default', 0)
->build();
}
/**
* @return string|null
*/
public function getId()
{
return $this->id;
}
/**
* @return Webhook|null
*/
public function getWebhook()
{
return $this->webhook;
}
/**
* @param Webhook|null $webhook
*
* @return WebhookQueue
*/
public function setWebhook($webhook)
{
$this->webhook = $webhook;
return $this;
}
/**
* @return \DateTimeInterface|null
*/
public function getDateAdded()
{
return $this->dateAdded;
}
/**
* @param \DateTime|null $dateAdded
*
* @return WebhookQueue
*/
public function setDateAdded($dateAdded)
{
$this->dateAdded = $dateAdded;
return $this;
}
/**
* @return string|null
*/
public function getPayload()
{
if (null === $this->payloadCompressed) {
// no payload is set
return null;
}
$payloadCompressed = $this->payloadCompressed;
if (is_resource($payloadCompressed)) {
// compressed payload is fetched by ORM
$payloadCompressed = stream_get_contents($this->payloadCompressed);
}
return gzuncompress($payloadCompressed);
}
/**
* @param string $payload
*
* @return WebhookQueue
*/
public function setPayload($payload)
{
$this->payloadCompressed = gzcompress($payload, 9);
return $this;
}
/**
* @return Event|null
*/
public function getEvent()
{
return $this->event;
}
/**
* @param Event|null $event
*
* @return WebhookQueue
*/
public function setEvent($event)
{
$this->event = $event;
return $this;
}
public function getRetries(): int
{
return $this->retries;
}
public function setRetries(int $retries): WebhookQueue
{
$this->retries = $retries;
return $this;
}
public function getDateModified(): ?\DateTimeImmutable
{
return $this->dateModified;
}
public function setDateModified(?\DateTimeImmutable $dateModified): WebhookQueue
{
$this->dateModified = $dateModified;
return $this;
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Mautic\WebhookBundle\Entity;
use Doctrine\DBAL\ArrayParameterType;
use Mautic\CoreBundle\Entity\CommonRepository;
use Mautic\CoreBundle\Helper\DateTimeHelper;
/**
* @extends CommonRepository<WebhookQueue>
*/
class WebhookQueueRepository extends CommonRepository
{
/**
* Deletes all the webhook queues by ID.
*
* @param $idList array of webhookqueue IDs
*/
public function deleteQueuesById(array $idList): void
{
// don't process the list if there are no items in it
if (!count($idList)) {
return;
}
$qb = $this->_em->getConnection()->createQueryBuilder();
$qb->delete(MAUTIC_TABLE_PREFIX.'webhook_queue')
->where(
$qb->expr()->in('id', $idList)
)
->executeStatement();
}
/**
* @param array<int> $idList
*/
public function incrementRetryCount(array $idList): void
{
if (!count($idList)) {
return;
}
$qb = $this->_em->getConnection()->createQueryBuilder();
$qb->update(MAUTIC_TABLE_PREFIX.'webhook_queue')
->where(
$qb->expr()->in('id', ':ids')
)
->set('retries', 'retries + 1')
->set('date_modified', ':date_modified')
->setParameter('ids', $idList, ArrayParameterType::INTEGER)
->setParameter('date_modified', (new \DateTimeImmutable())->format(DateTimeHelper::FORMAT_DB))
->executeStatement();
}
/**
* Check if there is webhook to process.
*/
public function exists(int $id): bool
{
$qb = $this->_em->getConnection()->createQueryBuilder();
$result = $qb->select($this->getTableAlias().'.id')
->from(MAUTIC_TABLE_PREFIX.'webhook_queue', $this->getTableAlias())
->where($this->getTableAlias().'.webhook_id = :id')
->setParameter('id', $id)
->setMaxResults(1)
->executeQuery()
->fetchOne();
return (bool) $result;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Mautic\WebhookBundle\Entity;
use Mautic\CoreBundle\Entity\CommonRepository;
/**
* @extends CommonRepository<Webhook>
*/
class WebhookRepository extends CommonRepository
{
/**
* @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q
*/
protected function addCatchAllWhereClause($q, $filter): array
{
return $this->addStandardCatchAllWhereClause($q, $filter, ['e.name']);
}
/**
* @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q
*/
protected function addSearchCommandWhereClause($q, $filter): array
{
return $this->addStandardSearchCommandWhereClause($q, $filter);
}
/**
* @return string[]
*/
public function getSearchCommands(): array
{
return $this->getStandardSearchCommands();
}
/**
* @return array<array<string>>
*/
protected function getDefaultOrder(): array
{
return [
[$this->getTableAlias().'.name', 'ASC'],
];
}
}