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,35 @@
<?php
declare(strict_types=1);
namespace Mautic\FormBundle\Event;
use Mautic\FormBundle\Collection\FieldCollection;
use Mautic\FormBundle\Crate\FieldCrate;
use Symfony\Contracts\EventDispatcher\Event;
final class FieldCollectEvent extends Event
{
private FieldCollection $fields;
public function __construct(
private string $object,
) {
$this->fields = new FieldCollection();
}
public function getObject(): string
{
return $this->object;
}
public function appendField(FieldCrate $field): void
{
$this->fields->append($field);
}
public function getFields(): FieldCollection
{
return $this->fields;
}
}

View File

@@ -0,0 +1,223 @@
<?php
namespace Mautic\FormBundle\Event;
use Mautic\CoreBundle\Event\ComponentValidationTrait;
use Mautic\CoreBundle\Exception\BadConfigurationException;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
use Symfony\Contracts\EventDispatcher\Event;
use Symfony\Contracts\Translation\TranslatorInterface;
class FormBuilderEvent extends Event
{
use ComponentValidationTrait;
private array $actions = [];
private array $fields = [];
private array $validators = [];
public function __construct(
private TranslatorInterface $translator,
) {
}
/**
* Adds a submit action to the list of available actions.
*
* @param string $key a unique identifier; it is recommended that it be namespaced i.e. lead.action
* @param array $action can contain the following keys:
* $action = [
* 'group' => (required) Label of the group to add this action to
* 'label' => (required) what to display in the list
* 'eventName' => (required) Event dispatched to execute action; it will receive a SubmissionEvent object
* 'formType' => (required) name of the form type SERVICE for the action
* 'allowCampaignForm' => (optional) true to allow this action for campaign forms; defaults to false
* 'description' => (optional) short description of event
* 'template' => (optional) template to use for the action's HTML in the form builder; eg AcmeMyBundle:FormAction:theaction.html.twig
* 'formTypeOptions' => (optional) array of options to pass to formType
* 'formTheme' => (optional theme for custom form views
* ]
*
* @throws BadConfigurationException
*/
public function addSubmitAction(string $key, array $action): void
{
if (array_key_exists($key, $this->actions)) {
throw new \InvalidArgumentException("The key, '$key' is already used by another action. Please use a different key.");
}
// check for required keys and that given functions are callable
$this->verifyComponent(
['group', 'label', 'formType', 'eventName'],
$action
);
$action['label'] = $this->translator->trans($action['label']);
$action['description'] ??= '';
$this->actions[$key] = $action;
}
/**
* Get submit actions.
*
* @return array
*/
public function getSubmitActions()
{
uasort(
$this->actions,
fn ($a, $b): int => strnatcasecmp(
$a['label'],
$b['label']
)
);
return $this->actions;
}
/**
* Get submit actions by groups.
*/
public function getSubmitActionGroups(): array
{
$actions = $this->getSubmitActions();
$groups = [];
foreach ($actions as $key => $action) {
$groups[$action['group']][$key] = $action;
}
return $groups;
}
/**
* Adds a form field to the list of available fields in the form builder.
*
* @param string $key unique identifier; it is recommended that it be namespaced i.e. leadbundle.myfield
* @param array $field can contain the following key/values
* $field = [
* 'label' => (required) what to display in the list
* 'formType' => (required) name of the form type SERVICE for the field's property column
* 'template' => (required) template to use for the field's HTML eg AcmeMyBundle:FormField:thefield.html.twig
* 'formTypeOptions' => (optional) array of options to pass to formType
* 'formTheme' => (optional) theme for custom form view
* 'valueFilter' => (optional) the filter to use to clean the input as supported by InputHelper or a callback;
* should accept arguments FormField $field and $filteredValue
* 'builderOptions' => (optional) array of options
* [
* 'addHelpMessage' => (bool) show help message inputs
* 'addShowLabel' => (bool) show label input
* 'addDefaultValue' => (bool) show default value input
* 'addLabelAttributes' => (bool) show label attribute input
* 'addInputAttributes' => (bool) show input attribute input
* 'addIsRequired' => (bool) show is required toggle
* ]
* ]
*
* @throws \InvalidArgumentException
* @throws BadConfigurationException
*/
public function addFormField($key, array $field): void
{
if (array_key_exists($key, $this->fields)) {
throw new \InvalidArgumentException("The key, '$key' is already used by another field. Please use a different key.");
}
$callbacks = [];
// Only validate valueFilter if it's not a InputHelper method
if (isset($field['valueFilter'])
&& (!is_string($field['valueFilter'])
|| !is_callable(
[\Mautic\CoreBundle\Helper\InputHelper::class, $field['valueFilter']]
))
) {
$callbacks = ['valueFilter'];
}
$this->verifyComponent(['label', 'formType', 'template'], $field, $callbacks);
$this->fields[$key] = $field;
}
/**
* Get form fields.
*
* @return mixed
*/
public function getFormFields()
{
return $this->fields;
}
/**
* Add a field validator.
*
* $validator = [
* 'eventName' => (required) Event name to dispatch to validate the form; it will recieve a ValidationEvent object
* 'fieldType' => (optional) Optional filter to validate only a specific type of field; otherwise every field
* will be sent through the validation event
* ]
*/
public function addValidator($key, array $validator): void
{
if (array_key_exists($key, $this->fields)) {
throw new \InvalidArgumentException("The key, '$key' is already used by another validator. Please use a different key.");
}
// check for required keys and that given functions are callable
$this->verifyComponent(['eventName'], $validator);
$this->validators[$key] = $validator;
}
/**
* @param FormInterface<object> $form
*/
public function addValidatorsToBuilder(FormInterface $form): void
{
if (!empty($this->validators)) {
$validationData = $form->getData()['validation'] ?? [];
foreach ($this->validators as $validator) {
if (isset($validator['formType']) && isset($validator['fieldType']) && $validator['fieldType'] == $form->getData()['type']) {
$form->add(
'validation',
$validator['formType'],
[
'label' => false,
'data' => $validationData,
]
);
}
}
}
}
/**
* Returns validators organized by ['form' => [], 'fieldType' => [], ...
*/
public function getValidators(): array
{
// Organize by field
$fieldValidators = [
'form' => [],
];
foreach ($this->validators as $validator) {
if (isset($validator['fieldType'])) {
if (!isset($fieldValidators[$validator['fieldType']])) {
$fieldValidators[$validator['fieldType']] = [];
}
$fieldValidators[$validator['fieldType']] = $validator['eventName'];
} else {
$fieldValidators['form'] = $validator['eventName'];
}
}
return $fieldValidators;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Mautic\FormBundle\Event;
use Mautic\CoreBundle\Event\CommonEvent;
use Mautic\FormBundle\Entity\Form;
class FormEvent extends CommonEvent
{
/**
* @param bool $isNew
*/
public function __construct(Form &$form, $isNew = false)
{
$this->entity = &$form;
$this->isNew = $isNew;
}
/**
* Returns the Form entity.
*
* @return Form
*/
public function getForm()
{
return $this->entity;
}
/**
* Sets the Form entity.
*/
public function setForm(Form $form): void
{
$this->entity = $form;
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Mautic\FormBundle\Event;
use Mautic\FormBundle\Entity\Field;
use Symfony\Contracts\EventDispatcher\Event;
final class FormFieldEvent extends Event
{
public function __construct(
private Field $entity,
private bool $isNew = false,
) {
}
public function getField(): Field
{
return $this->entity;
}
public function isNew(): bool
{
return $this->isNew;
}
public function setField(Field $field): void
{
$this->entity = $field;
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Mautic\FormBundle\Event;
use Mautic\CoreBundle\Event\CommonEvent;
use Mautic\FormBundle\Collection\ObjectCollection;
use Mautic\FormBundle\Crate\ObjectCrate;
final class ObjectCollectEvent extends CommonEvent
{
private ObjectCollection $objects;
public function __construct()
{
$this->objects = new ObjectCollection();
}
public function appendObject(ObjectCrate $object): void
{
$this->objects->append($object);
}
public function getObjects(): ObjectCollection
{
return $this->objects;
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace Mautic\FormBundle\Event\Service;
use Mautic\FormBundle\Entity\Field;
use Mautic\FormBundle\Event\SubmissionEvent;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
class FieldValueTransformer
{
private array $contactFieldsToUpdate = [];
private array $tokensToUpdate = [];
private bool $isTransformed = false;
public function __construct(
private RouterInterface $router,
) {
}
public function transformValuesAfterSubmit(SubmissionEvent $submissionEvent): void
{
if (true === $this->isTransformed) {
return;
}
$fields = $submissionEvent->getForm()->getFields();
$contactFieldMatches = $submissionEvent->getContactFieldMatches();
$tokens = $submissionEvent->getTokens();
/** @var Field $field */
foreach ($fields as $field) {
switch ($field->getType()) {
case 'file':
$newValue = $this->router->generate(
'mautic_form_file_download',
[
'submissionId' => $submissionEvent->getSubmission()->getId(),
'field' => $field->getAlias(),
],
UrlGeneratorInterface::ABSOLUTE_URL
);
$tokenAlias = "{formfield={$field->getAlias()}}";
if (!empty($tokens[$tokenAlias])) {
$this->tokensToUpdate[$tokenAlias] = $tokens[$tokenAlias] = $newValue;
}
$contactFieldAlias = $field->getMappedField();
if ('contact' === $field->getMappedObject() && !empty($contactFieldMatches[$contactFieldAlias])) {
$this->contactFieldsToUpdate[$contactFieldAlias] = $contactFieldMatches[$contactFieldAlias] = $newValue;
}
break;
}
}
$submissionEvent->setTokens($tokens);
$submissionEvent->setContactFieldMatches($contactFieldMatches);
$this->isTransformed = true;
}
/**
* @return array
*/
public function getContactFieldsToUpdate()
{
return $this->contactFieldsToUpdate;
}
/**
* @return array
*/
public function getTokensToUpdate()
{
return $this->tokensToUpdate;
}
}

View File

@@ -0,0 +1,323 @@
<?php
namespace Mautic\FormBundle\Event;
use Mautic\CoreBundle\Event\CommonEvent;
use Mautic\FormBundle\Entity\Action;
use Mautic\FormBundle\Entity\Submission;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ServerBag;
class SubmissionEvent extends CommonEvent
{
/**
* Cleaned post results.
*/
private array $results = [];
/**
* Form fields.
*/
private array $fields = [];
/**
* Results converted to tokens.
*/
private array $tokens = [];
/**
* Callback for post form submit.
*
* @var array<string, mixed[]>
*/
private array $callbacks = [];
/**
* @var mixed[]
*/
private array $callbackResponses = [];
/**
* @var mixed[]
*/
private array $contactFieldMatches = [];
/**
* Array to hold information set by other actions that may be useful to subsequent actions.
*
* @var mixed[]
*/
private array $feedback = [];
private ?Action $action = null;
private ?string $context = null;
/**
* @var array|Response|null
*/
private $postSubmitResponse;
/**
* @var array<mixed>
*/
private ?array $postSubmitPayload = null;
/**
* @param mixed[] $post raw POST results
* @param mixed[]|array|ServerBag $server
*/
public function __construct(
Submission $submission,
private $post,
private $server,
private Request $request,
) {
$this->entity = $submission;
}
public function getSubmission(): Submission
{
return $this->entity;
}
/**
* @return array
*/
public function getPost()
{
return $this->post;
}
/**
* @return array
*/
public function getServer()
{
return $this->server;
}
/**
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* @return \Mautic\FormBundle\Entity\Form
*/
public function getForm()
{
return $this->entity->getForm();
}
/**
* @return array
*/
public function getResults()
{
return $this->results;
}
/**
* @param array $results
*
* @return SubmissionEvent
*/
public function setResults($results)
{
$this->results = $results;
return $this;
}
/**
* @return array
*/
public function getFields()
{
return $this->fields;
}
/**
* @param array $fields
*
* @return SubmissionEvent
*/
public function setFields($fields)
{
$this->fields = $fields;
return $this;
}
/**
* @return array
*/
public function getTokens()
{
return $this->tokens;
}
/**
* @param array $tokens
*
* @return SubmissionEvent
*/
public function setTokens($tokens)
{
$this->tokens = $tokens;
return $this;
}
/**
* @return array
*/
public function getContactFieldMatches()
{
return $this->contactFieldMatches;
}
/**
* @param array $contactFieldMatches
*
* @return SubmissionEvent
*/
public function setContactFieldMatches($contactFieldMatches)
{
$this->contactFieldMatches = $contactFieldMatches;
return $this;
}
public function setActionFeedback($key, $feedback): void
{
$this->feedback[$key] = $feedback;
}
/**
* Get feedback injected by another action.
*
* @return array|bool|mixed
*/
public function getActionFeedback($key = null)
{
if (null === $key) {
return $this->feedback;
} elseif (isset($this->feedback[$key])) {
return $this->feedback[$key];
}
return false;
}
public function checkContext(string $context): bool
{
return $this->context === $context;
}
public function setContext(string $context): void
{
$this->context = $context;
}
public function setAction(?Action $action = null): void
{
$this->action = $action;
if (!is_null($action)) {
$this->setContext($action->getType());
}
}
public function getAction(): ?Action
{
return $this->action;
}
public function getActionConfig(): array
{
return $this->action ? $this->action->getProperties() : [];
}
/**
* Set an post submit callback - include $callback['eventName' => '', 'anythingElse' ...].
*
* @param string $key
*/
public function setPostSubmitCallback($key, array $callback): void
{
if (!array_key_exists('eventName', $callback)) {
throw new \InvalidArgumentException('eventName required');
}
$this->callbacks[$key] = $callback;
}
/**
* @return mixed
*/
public function getPostSubmitCallback($key = null)
{
return (null === $key) ? $this->callbacks : $this->callbacks[$key];
}
public function hasPostSubmitCallbacks(): bool
{
return count($this->callbacks) || count($this->callbackResponses);
}
/**
* @return mixed
*/
public function getPostSubmitCallbackResponse($key = null)
{
return (null === $key) ? $this->callbackResponses : $this->callbackResponses[$key];
}
/**
* @param mixed $callbackResponse
*
* @return SubmissionEvent
*/
public function setPostSubmitCallbackResponse($key, $callbackResponse)
{
$this->callbackResponses[$key] = $callbackResponse;
return $this;
}
public function hasPostSubmitResponse(): bool
{
return null !== $this->postSubmitResponse;
}
public function getPostSubmitResponse()
{
return $this->postSubmitResponse;
}
public function setPostSubmitResponse($response): void
{
$this->postSubmitResponse = $response;
}
/**
* @return mixed[]
*/
public function getPostSubmitPayload(): array
{
return $this->postSubmitPayload;
}
/**
* @param mixed[] $postSubmitPayload
*/
public function setPostSubmitPayload(array $postSubmitPayload): void
{
$this->postSubmitPayload = $postSubmitPayload;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Mautic\FormBundle\Event;
use Mautic\CoreBundle\Event\CommonEvent;
use Mautic\FormBundle\Entity\Field;
class ValidationEvent extends CommonEvent
{
private bool $valid = true;
private string $invalidReason = '';
/**
* @param mixed $value
*/
public function __construct(
private Field $field,
private $value,
) {
}
/**
* @return Field
*/
public function getField()
{
return $this->field;
}
/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}
public function failedValidation($reason): void
{
$this->valid = false;
$this->invalidReason = $reason;
$this->stopPropagation();
}
/**
* Is the field valid.
*/
public function isValid(): bool
{
return $this->valid;
}
/**
* Get the reason this field was invalidated.
*/
public function getInvalidReason(): string
{
return $this->invalidReason;
}
}