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,11 @@
<?php
declare(strict_types=1);
namespace Mautic\EmailBundle\Validator;
use Symfony\Component\Validator\Constraint;
class Dsn extends Constraint
{
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Mautic\EmailBundle\Validator;
use Mautic\EmailBundle\Mailer\Transport\TransportFactory;
use Mautic\EmailBundle\Validator\Dsn as DsnConstraint;
use Symfony\Component\Mailer\Exception\ExceptionInterface;
use Symfony\Component\Mailer\Exception\InvalidArgumentException;
use Symfony\Component\Mailer\Exception\UnsupportedSchemeException;
use Symfony\Component\Mailer\Transport\Dsn as MailerDsn;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class DsnValidator extends ConstraintValidator
{
public function __construct(
private TransportFactory $transportFactory,
) {
}
public function validate(mixed $value, Constraint $constraint): void
{
if (!is_string($value)) {
throw new UnexpectedTypeException($value, 'string');
}
if (!$constraint instanceof DsnConstraint) {
throw new UnexpectedTypeException($constraint, DsnConstraint::class);
}
if (!$value) {
return;
}
try {
$dsn = MailerDsn::fromString($value);
} catch (InvalidArgumentException $e) {
$this->context->addViolation($e->getMessage() ?: 'mautic.email.dsn.invalid_dsn');
return;
}
try {
$this->transportFactory->fromDsnObject($dsn);
} catch (UnsupportedSchemeException $e) {
$this->context->addViolation($e->getMessage() ?: 'mautic.email.dsn.unsupported_scheme');
} catch (ExceptionInterface $e) {
$this->context->addViolation($e->getMessage() ?: 'mautic.email.dsn.invalid_dsn');
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Mautic\EmailBundle\Validator;
use Symfony\Component\Validator\Constraint;
final class EmailLists extends Constraint
{
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Mautic\EmailBundle\Validator;
use Mautic\EmailBundle\Entity\Email;
use Mautic\LeadBundle\Entity\LeadList;
use Mautic\LeadBundle\Form\Validator\Constraints\LeadListAccess;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
final class EmailListsValidator extends ConstraintValidator
{
public function validate(mixed $value, Constraint $constraint): void
{
if (!$value instanceof Email) {
throw new UnexpectedTypeException($value, Email::class);
}
if ('list' !== $value->getEmailType() || $value->getTranslationParent()) {
return;
}
$this->validateLists($value);
$this->validateExcludedLists($value);
$this->validateConflictingLists($value);
}
private function validateLists(Email $email): void
{
$violations = $this->context->getValidator()->validate(
$email->getLists(),
[
new LeadListAccess(),
new NotBlank(
[
'message' => 'mautic.lead.lists.required',
]
),
]
);
$this->addViolationsAtPath($violations, 'lists');
}
private function validateExcludedLists(Email $email): void
{
$violations = $this->context->getValidator()->validate(
$email->getExcludedLists(),
[
new LeadListAccess(['allowEmpty' => true]),
]
);
$this->addViolationsAtPath($violations, 'excludedLists');
}
private function validateConflictingLists(Email $email): void
{
$listsIds = $this->getListsIds($email->getLists());
$excludedListsIds = $this->getListsIds($email->getExcludedLists());
$isConflicting = (bool) array_intersect($listsIds, $excludedListsIds);
if ($isConflicting) {
$this->context->buildViolation('mautic.lead.excluded_lists.conflicting')
->atPath('excludedLists')
->addViolation();
}
}
/**
* @param LeadList[] $lists
*
* @return string[]
*/
private function getListsIds(iterable $lists): array
{
$ids = [];
foreach ($lists as $list) {
$ids[] = (string) $list->getId();
}
return $ids;
}
/**
* @param ConstraintViolationInterface[] $violations
*/
private function addViolationsAtPath(iterable $violations, string $path): void
{
foreach ($violations as $violation) {
$this->context->buildViolation($violation->getMessage())
->atPath($path)
->addViolation();
}
}
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Mautic\EmailBundle\Validator;
use Symfony\Component\Validator\Constraint;
final class EmailOrEmailTokenList extends Constraint
{
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Mautic\EmailBundle\Validator;
use Mautic\CoreBundle\Exception\InvalidValueException;
use Mautic\CoreBundle\Exception\RecordException;
use Mautic\CoreBundle\Form\DataTransformer\ArrayStringTransformer;
use Mautic\EmailBundle\Exception\InvalidEmailException;
use Mautic\EmailBundle\Helper\EmailValidator;
use Mautic\LeadBundle\DataObject\ContactFieldToken;
use Mautic\LeadBundle\Exception\InvalidContactFieldTokenException;
use Mautic\LeadBundle\Validator\CustomFieldValidator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
final class EmailOrEmailTokenListValidator extends ConstraintValidator
{
private ArrayStringTransformer $transformer;
public function __construct(
private EmailValidator $emailValidator,
private CustomFieldValidator $customFieldValidator,
) {
$this->transformer = new ArrayStringTransformer();
}
public function validate(mixed $csv, Constraint $constraint): void
{
if (!$constraint instanceof EmailOrEmailTokenList) {
throw new UnexpectedTypeException($constraint, EmailOrEmailTokenList::class);
}
if (null === $csv || '' === $csv) {
return;
}
if (!is_string($csv)) {
throw new UnexpectedTypeException($csv, 'string');
}
array_map(
$this->makeEmailOrEmailTokenValidator(),
$this->transformer->reverseTransform($csv)
);
}
private function makeEmailOrEmailTokenValidator(): callable
{
return function (string $emailOrToken): void {
try {
// Try to validate if the value is an email address.
$this->emailValidator->validate($emailOrToken);
} catch (InvalidEmailException) {
try {
// The token syntax is validated during creation of new ContactFieldToken object.
$contactFieldToken = new ContactFieldToken($emailOrToken);
// Validate that the token default value is a valid email address if set.
if ($contactFieldToken->getDefaultValue()) {
$this->emailValidator->validate($contactFieldToken->getDefaultValue());
}
// Validate that the contact field exists and is type of email.
$this->customFieldValidator->validateFieldType($contactFieldToken->getFieldAlias(), 'email');
} catch (RecordException|InvalidValueException|InvalidContactFieldTokenException $tokenException) {
$this->context->addViolation(
'mautic.email.email_or_token.not_valid',
['%value%' => $emailOrToken, '%details%' => $tokenException->getMessage()]
);
}
}
};
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Mautic\EmailBundle\Validator;
use Symfony\Component\Validator\Constraint;
class MultipleEmailsValid extends Constraint
{
public function getTargets(): string|array
{
return self::PROPERTY_CONSTRAINT;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Mautic\EmailBundle\Validator;
use Mautic\CoreBundle\Form\DataTransformer\ArrayStringTransformer;
use Mautic\EmailBundle\Exception\InvalidEmailException;
use Mautic\EmailBundle\Helper\EmailValidator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class MultipleEmailsValidValidator extends ConstraintValidator
{
public function __construct(
private EmailValidator $emailValidator,
) {
}
/**
* @param string $emailsInString
*/
public function validate($emailsInString, Constraint $constraint): void
{
if (!$emailsInString) {
return;
}
$transformer = new ArrayStringTransformer();
$emails = $transformer->reverseTransform($emailsInString);
foreach ($emails as $email) {
try {
$this->emailValidator->validate($email);
} catch (InvalidEmailException $e) {
$this->context->buildViolation('mautic.email.multiple_emails.not_valid', ['%email%' => $e->getMessage()])
->addViolation();
return;
}
}
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Mautic\EmailBundle\Validator;
use Symfony\Component\Validator\Constraint;
class ScheduleDateRange extends Constraint
{
public string $message = 'mautic.form.date_time_range.invalid_range';
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Mautic\EmailBundle\Validator;
use Mautic\EmailBundle\Entity\Email;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class ScheduleDateRangeValidator extends ConstraintValidator
{
public function validate(mixed $value, Constraint $constraint): void
{
if (!$constraint instanceof ScheduleDateRange) {
throw new UnexpectedTypeException($constraint, ScheduleDateRange::class);
}
// Handle Email entity validation
if ($value instanceof Email) {
// Skip validation if continueSending is false
if (!$value->getContinueSending()) {
return;
}
$publishUp = $value->getPublishUp();
$publishDown = $value->getPublishDown();
$pathPrefix = '';
}
// Handle form data validation
elseif (is_array($value)) {
// Skip validation if continueSending is false
if (!($value['continueSending'] ?? true)) {
return;
}
$publishUp = $value['publishUp'] ?? null;
$publishDown = $value['publishDown'] ?? null;
$pathPrefix = '[publishDown]';
}
if ($publishUp && $publishDown && $publishDown <= $publishUp) {
$this->context->buildViolation($constraint->message)
->atPath($pathPrefix ?: 'publishDown')
->addViolation();
}
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Mautic\EmailBundle\Validator;
use Symfony\Component\Validator\Constraint;
final class TextOnlyDynamicContent extends Constraint
{
public string $message = 'mautic.email.subject.dynamic_content.text_only';
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Mautic\EmailBundle\Validator;
use Mautic\DynamicContentBundle\DynamicContent\TypeList;
use Mautic\DynamicContentBundle\Model\DynamicContentModel;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
final class TextOnlyDynamicContentValidator extends ConstraintValidator
{
public function __construct(private DynamicContentModel $dynamicContentModel)
{
}
public function validate(mixed $value, Constraint $constraint): void
{
// Skip validation for null or empty values
if (null === $value || '' === $value) {
return;
}
if (!is_string($value)) {
throw new UnexpectedTypeException($value, 'string');
}
if (!$constraint instanceof TextOnlyDynamicContent) {
throw new UnexpectedTypeException($constraint, TextOnlyDynamicContent::class);
}
// Pattern to match DWC tokens in the format {dwc=slotname}
preg_match_all('/{dwc=([^}]*)}/', $value, $matches);
foreach ($matches[1] as $slotName) {
// Retrieve DWC item by slot name
$dwcItem = $this->dynamicContentModel->checkEntityBySlotName($slotName, TypeList::HTML);
// Perform the validation against the type
if ($dwcItem) {
$this->context->buildViolation(
$constraint->message, ['%slotName%' => $slotName])
->addViolation();
}
}
}
}