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,51 @@
<?php
namespace Mautic\CampaignBundle\Form\Type;
use Mautic\CampaignBundle\Form\Validator\Constraints\InfiniteLoop;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @extends AbstractType<mixed>
*/
class CampaignEventAddRemoveLeadType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('addTo', CampaignListType::class, [
'label' => 'mautic.campaign.form.addtocampaigns',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'required' => false,
'include_this' => $options['include_this'],
'this_translation' => 'mautic.campaign.form.thiscampaign_restart',
'constraints' => [new InfiniteLoop()],
]);
$builder->add('removeFrom', CampaignListType::class, [
'label' => 'mautic.campaign.form.removefromcampaigns',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'required' => false,
'include_this' => $options['include_this'],
]);
}
public function getBlockPrefix(): string
{
return 'campaignevent_addremovelead';
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'include_this' => false,
]);
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Mautic\CampaignBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\NotBlank;
/**
* @extends AbstractType<mixed>
*/
class CampaignEventJumpToEventType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$jumpProps = $builder->getData();
$selected = $jumpProps['jumpToEvent'] ?? null;
$builder->add(
'jumpToEvent',
ChoiceType::class,
[
'choices' => [],
'multiple' => false,
'label' => 'mautic.campaign.form.jump_to_event',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'data-onload-callback' => 'updateJumpToEventOptions',
'data-selected' => $selected,
],
'constraints' => [
new NotBlank(
[
'message' => 'mautic.core.value.required',
]
),
],
]
);
// Allows additional values (new events) to be selected before persisting
$builder->get('jumpToEvent')->resetViewTransformers();
}
public function getBlockPrefix(): string
{
return 'campaignevent_jump_to_event';
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Mautic\CampaignBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @extends AbstractType<mixed>
*/
final class CampaignImportType extends AbstractType
{
/**
* Build the form fields for importing campaign data.
*
* @param FormBuilderInterface $builder The form builder
* @param array<string, mixed> $options The form options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add(
'campaignFile',
FileType::class,
[
'required' => true,
'mapped' => false,
'attr' => [
'class' => 'form-control',
'accept' => '.zip',
],
]
);
$builder->add(
'start',
SubmitType::class,
[
'label' => 'mautic.campaign.campaign.import.upload.button',
'attr' => [
'class' => 'btn btn-tertiary btn-sm',
'icon' => 'ri-import-line',
],
]
);
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Mautic\CampaignBundle\Form\Type;
use Mautic\CoreBundle\Form\Type\FormButtonsType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
/**
* @extends AbstractType<mixed>
*/
class CampaignLeadSourceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$sourceType = $options['data']['sourceType'];
$sourceChoices = $options['source_choices'] ?? [];
foreach ($sourceChoices as $key => $val) {
$sourceChoices[$key] = $val.' ('.$key.')';
}
switch ($sourceType) {
case 'lists':
$builder->add(
'lists',
ChoiceType::class,
[
'choices' => array_flip($sourceChoices),
'multiple' => true,
'label' => 'mautic.campaign.leadsource.lists',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'constraints' => [
new NotBlank(
[
'message' => 'mautic.core.value.required',
]
),
],
]
);
break;
case 'forms':
$builder->add(
'forms',
ChoiceType::class,
[
'choices' => array_flip($sourceChoices),
'multiple' => true,
'label' => 'mautic.campaign.leadsource.forms',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'constraints' => [
new NotBlank(
[
'message' => 'mautic.core.value.required',
]
),
],
]
);
break;
default:
break;
}
$builder->add('sourceType', HiddenType::class);
$builder->add('droppedX', HiddenType::class);
$builder->add('droppedY', HiddenType::class);
$update = !empty($options['data'][$sourceType]);
if (!empty($update)) {
$btnValue = 'mautic.core.form.update';
$btnIcon = 'ri-edit-line';
} else {
$btnValue = 'mautic.core.form.add';
$btnIcon = 'ri-add-line';
}
$builder->add('buttons', FormButtonsType::class, [
'save_text' => $btnValue,
'save_icon' => $btnIcon,
'save_onclick' => 'Mautic.submitCampaignSource(event)',
'apply_text' => false,
'container_class' => 'bottom-form-buttons',
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setRequired(['source_choices']);
}
public function getBlockPrefix(): string
{
return 'campaign_leadsource';
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Mautic\CampaignBundle\Form\Type;
use Mautic\CampaignBundle\Model\CampaignModel;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @extends AbstractType<mixed>
*/
class CampaignListType extends AbstractType
{
/**
* @var bool
*/
private $canViewOther = false;
public function __construct(
private CampaignModel $model,
protected TranslatorInterface $translator,
CorePermissions $security,
) {
$this->canViewOther = $security->isGranted('campaign:campaigns:viewother');
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(
[
'choices' => function (Options $options): array {
$choices = [];
$campaigns = $this->model->getRepository()->getPublishedCampaigns(null, null, true, $this->canViewOther);
foreach ($campaigns as $campaign) {
$choices[$campaign['name']] = $campaign['id'];
}
// sort by language
ksort($choices);
if ($options['include_this']) {
$choices = [$options['this_translation'] => 'this'] + $choices;
}
return $choices;
},
'placeholder' => false,
'expanded' => false,
'multiple' => true,
'required' => false,
'include_this' => false,
'this_translation' => 'mautic.campaign.form.thiscampaign',
]
);
}
public function getParent(): ?string
{
return ChoiceType::class;
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace Mautic\CampaignBundle\Form\Type;
use Mautic\CampaignBundle\Entity\Campaign;
use Mautic\CategoryBundle\Form\Type\CategoryListType;
use Mautic\CoreBundle\Form\EventListener\CleanFormSubscriber;
use Mautic\CoreBundle\Form\EventListener\FormExitSubscriber;
use Mautic\CoreBundle\Form\Type\FormButtonsType;
use Mautic\CoreBundle\Form\Type\PublishDownDateType;
use Mautic\CoreBundle\Form\Type\PublishUpDateType;
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Mautic\ProjectBundle\Form\Type\ProjectType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @extends AbstractType<Campaign>
*/
class CampaignType extends AbstractType
{
public function __construct(
private CorePermissions $security,
private TranslatorInterface $translator,
) {
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addEventSubscriber(new CleanFormSubscriber(['description' => 'html']));
$builder->addEventSubscriber(new FormExitSubscriber('campaign', $options));
$builder->add('name', TextType::class, [
'label' => 'mautic.core.name',
'label_attr' => ['class' => 'control-label'],
'attr' => ['class' => 'form-control'],
]);
$builder->add('description', TextareaType::class, [
'label' => 'mautic.core.description',
'label_attr' => ['class' => 'control-label'],
'attr' => ['class' => 'form-control editor'],
'required' => false,
]);
$builder->add('allowRestart',
YesNoButtonGroupType::class,
[
'label' => 'mautic.campaign.allow_restart',
'attr' => [
'tooltip' => 'mautic.campaign.allow_restart.tooltip',
],
]
);
// add category
$builder->add('category', CategoryListType::class, [
'bundle' => 'campaign',
]);
$attr = [];
if (!empty($options['data']) && $options['data']->getId()) {
$readonly = !$this->security->isGranted('campaign:campaigns:publish');
$data = $options['data']->isPublished(false);
$attr = [
'onchange' => 'Mautic.showCampaignConfirmation(mQuery(this));',
'data-toggle' => 'confirmation',
'data-message' => $this->translator->trans('mautic.campaign.form.confirmation.message'),
'data-confirm-text' => $this->translator->trans('mautic.campaign.form.confirmation.confirm_text'),
'data-confirm-callback' => 'dismissConfirmation',
'data-cancel-text' => $this->translator->trans('mautic.campaign.form.confirmation.cancel_text'),
'data-cancel-callback' => 'setPublishedButtonToYes',
'class' => 'btn btn-ghost',
];
} elseif (!$this->security->isGranted('campaign:campaigns:publish')) {
$readonly = true;
$data = false;
} else {
$readonly = false;
$data = false;
}
$attr['readonly'] = $readonly;
$builder->add('isPublished', YesNoButtonGroupType::class, [
'data' => $data,
'attr' => $attr,
]);
$builder->add('publishUp', PublishUpDateType::class);
$builder->add('publishDown', PublishDownDateType::class);
$builder->add('sessionId', HiddenType::class, [
'mapped' => false,
]);
if (!empty($options['action'])) {
$builder->setAction($options['action']);
}
$builder->add('projects', ProjectType::class);
$builder->add('buttons', FormButtonsType::class, [
'pre_extra_buttons' => [
[
'name' => 'builder',
'label' => 'mautic.campaign.campaign.launch.builder',
'attr' => [
'class' => 'btn btn-tertiary btn-dnd',
'icon' => 'ri-organization-chart',
'onclick' => 'Mautic.launchCampaignEditor();',
],
],
],
]);
$builder->add('version', HiddenType::class, [
'mapped' => false,
]);
$builder->add('campaignElements', HiddenType::class, [
'mapped' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Campaign::class,
]);
}
}

View File

@@ -0,0 +1,241 @@
<?php
namespace Mautic\CampaignBundle\Form\Type;
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @extends AbstractType<mixed>
*/
class ConfigType extends AbstractType
{
public function __construct(
private TranslatorInterface $translator,
) {
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add(
'campaign_time_wait_on_event_false',
ChoiceType::class,
[
'label' => 'mautic.campaignconfig.campaign_time_wait_on_event_false',
'label_attr' => ['class' => 'control-label'],
'help' => 'mautic.campaignconfig.campaign_time_wait_on_event_false_help',
'data' => $options['data']['campaign_time_wait_on_event_false'],
'choices' => [
'mautic.core.never' => 'null',
'15 mn' => 'PT15M',
'30 mn' => 'PT30M',
'45 mn' => 'PT45M',
'1 h' => 'PT1H',
'2 h' => 'PT2H',
'4 h' => 'PT4H',
'8 h' => 'PT8H',
'12 h' => 'PT12H',
'24 h' => 'PT1D',
'3 days' => 'PT3D',
'5 days' => 'PT5D',
'1 week' => 'PT14D',
'3 months' => 'P3M',
],
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.campaignconfig.campaign_time_wait_on_event_false_tooltip',
],
'required' => false,
]
);
$builder->add(
'campaign_by_range',
YesNoButtonGroupType::class,
[
'label' => 'mautic.campaignconfig.campaign_by_range',
'attr' => [
'tooltip' => 'mautic.campaignconfig.campaign_by_range.tooltip',
],
'data' => (bool) ($options['data']['campaign_by_range'] ?? false),
]
);
$builder->add(
'campaign_use_summary',
YesNoButtonGroupType::class,
[
'label' => 'mautic.campaignconfig.use_summary',
'attr' => [
'tooltip' => 'mautic.campaignconfig.use_summary.tooltip',
],
'data' => (bool) ($options['data']['campaign_use_summary'] ?? false),
]
);
$builder->add(
'campaign_email_stats_enabled',
YesNoButtonGroupType::class,
[
'label' => 'mautic.campaignconfig.campaign_email_stats_enabled',
'label_attr' => ['class' => 'control-label'],
'data' => $options['data']['campaign_email_stats_enabled'] ?? true,
'required' => false,
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.campaignconfig.campaign_email_stats_enabled.tooltip',
],
]
);
$builder->add(
'peak_interaction_timer_best_default_hour_start',
NumberType::class,
[
'label' => 'mautic.config.peak_interaction_timer.best_default_hour_start',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.config.peak_interaction_timer.best_default_hour_start.tooltip',
],
'data' => $options['data']['peak_interaction_timer_best_default_hour_start'] ?? 9,
'constraints' => [
new Range([
'min' => 0,
'max' => 23,
]),
],
]
);
$builder->add(
'peak_interaction_timer_best_default_hour_end',
NumberType::class,
[
'label' => 'mautic.config.peak_interaction_timer.best_default_hour_end',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.config.peak_interaction_timer.best_default_hour_end.tooltip',
],
'data' => $options['data']['peak_interaction_timer_best_default_hour_end'] ?? 12,
'constraints' => [
new Range([
'min' => 0,
'max' => 23,
]),
new Callback(
function ($hourEnd, ExecutionContextInterface $context): void {
$data = $context->getRoot()->getData();
$hourStart = $data['campaignconfig']['peak_interaction_timer_best_default_hour_start'] ?? null;
if (null !== $hourStart && null !== $hourEnd && $hourStart >= $hourEnd) {
$context->buildViolation('mautic.config.peak_interaction_timer.best_default_hour.validation.range')->addViolation();
}
}
),
],
]
);
$builder->add(
'peak_interaction_timer_best_default_days',
ChoiceType::class,
[
'label' => 'mautic.config.peak_interaction_timer.best_default_days',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.config.peak_interaction_timer.best_default_days.tooltip',
],
'choices' => [
'mautic.core.date.monday' => 1,
'mautic.core.date.tuesday' => 2,
'mautic.core.date.wednesday' => 3,
'mautic.core.date.thursday' => 4,
'mautic.core.date.friday' => 5,
'mautic.core.date.saturday' => 6,
'mautic.core.date.sunday' => 7,
],
'data' => $options['data']['peak_interaction_timer_best_default_days'] ?? [2, 3, 4],
'multiple' => true,
'required' => true,
]
);
$builder->add(
'peak_interaction_timer_cache_timeout',
ChoiceType::class,
[
'label' => 'mautic.config.peak_interaction_timer.cache_timeout',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.config.peak_interaction_timer.cache_timeout.tooltip',
],
'choices' => [
'mautic.config.peak_interaction_timer.cache.off' => 0,
'1 '.$this->translator->trans('mautic.campaign.event.intervalunit.d', ['%count%' => 1]) => 1440,
'7 '.$this->translator->trans('mautic.campaign.event.intervalunit.d', ['%count%' => 7]) => 10080,
'1 '.$this->translator->trans('mautic.campaign.event.intervalunit.m', ['%count%' => 1]) => 43800,
],
'data' => $options['data']['peak_interaction_timer_cache_timeout'] ?? 43800,
'constraints' => [
new GreaterThanOrEqual([
'value' => 0,
]),
],
]
);
$builder->add(
'peak_interaction_timer_fetch_interactions_from',
ChoiceType::class,
[
'label' => 'mautic.config.peak_interaction_timer.fetch_interactions_from',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.config.peak_interaction_timer.fetch_interactions_from.tooltip',
],
'choices' => [
'mautic.config.peak_interaction_timer.fetch.from_30_days' => '-30 days',
'mautic.config.peak_interaction_timer.fetch.from_60_days' => '-60 days',
'mautic.config.peak_interaction_timer.fetch.from_90_days' => '-90 days',
],
'data' => $options['data']['peak_interaction_timer_fetch_interactions_from'] ?? '-60 days',
]
);
$builder->add(
'peak_interaction_timer_fetch_limit',
NumberType::class,
[
'label' => 'mautic.config.peak_interaction_timer.fetch_limit',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.config.peak_interaction_timer.fetch_limit.tooltip',
],
'data' => $options['data']['peak_interaction_timer_fetch_limit'] ?? 50,
'constraints' => [
new GreaterThanOrEqual([
'value' => 10,
]),
],
]
);
}
public function getBlockPrefix(): string
{
return 'campaignconfig';
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Mautic\CampaignBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @extends AbstractType<mixed>
*/
class EventCanvasSettingsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('droppedX', HiddenType::class);
$builder->add('droppedY', HiddenType::class);
}
public function getBlockPrefix(): string
{
return 'campaignevent_canvassettings';
}
}

View File

@@ -0,0 +1,343 @@
<?php
namespace Mautic\CampaignBundle\Form\Type;
use Mautic\CampaignBundle\Executioner\Scheduler\Mode\Optimized as OptimizedScheduler;
use Mautic\CoreBundle\Form\EventListener\CleanFormSubscriber;
use Mautic\CoreBundle\Form\Type\ButtonGroupType;
use Mautic\CoreBundle\Form\Type\FormButtonsType;
use Mautic\CoreBundle\Form\Type\PropertiesTrait;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @extends AbstractType<mixed>
*/
class EventType extends AbstractType
{
use PropertiesTrait;
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$masks = [];
$builder->add(
'name',
TextType::class,
[
'label' => 'mautic.core.name',
'label_attr' => ['class' => 'control-label'],
'attr' => ['class' => 'form-control'],
'required' => false,
]
);
$builder->add(
'anchor',
HiddenType::class,
[
'label' => false,
]
);
if (in_array($options['data']['eventType'], ['action', 'condition'])) {
$label = 'mautic.campaign.form.type';
$choices = [
'immediate' => 'mautic.campaign.form.type.immediate',
'interval' => 'mautic.campaign.form.type.interval',
'date' => 'mautic.campaign.form.type.date',
];
if (in_array($options['data']['type'], OptimizedScheduler::AVAILABLE_FOR_EVENTS)) {
$choices['optimized'] = 'mautic.campaign.form.type.optimized';
}
if (isset($options['data']['anchor']) && isset($options['data']['anchorEventType'])
&& 'no' === $options['data']['anchor']
&& 'condition' !== $options['data']['anchorEventType']
&& 'condition' !== $options['data']['eventType']) {
$label .= '_inaction';
unset($choices['immediate']);
$choices['interval'] = $choices['interval'].'_inaction';
$choices['date'] = $choices['date'].'_inaction';
}
$default = array_key_first($choices);
$triggerMode = (empty($options['data']['triggerMode'])) ? $default : $options['data']['triggerMode'];
$builder->add(
'triggerMode',
ButtonGroupType::class,
[
'choices' => array_flip($choices),
'expanded' => true,
'multiple' => false,
'label_attr' => ['class' => 'control-label'],
'label' => $label,
'placeholder' => false,
'required' => false,
'attr' => [
'onchange' => 'Mautic.campaignToggleTimeframes();',
'tooltip' => 'mautic.campaign.form.type.help',
],
'data' => $triggerMode,
]
);
$builder->add(
'triggerDate',
DateTimeType::class,
[
'label' => false,
'attr' => [
'class' => 'form-control',
'preaddon' => 'ri-calendar-line',
'data-toggle' => 'datetime',
],
'widget' => 'single_text',
'html5' => false,
'format' => 'yyyy-MM-dd HH:mm',
'data' => $this->getTimeValue($options['data'], 'triggerDate'),
]
);
$data = (!isset($options['data']['triggerInterval']) || '' === $options['data']['triggerInterval']
|| null === $options['data']['triggerInterval']) ? 1 : (int) $options['data']['triggerInterval'];
$builder->add(
'triggerInterval',
IntegerType::class,
[
'label' => false,
'attr' => [
'class' => 'form-control',
'preaddon' => 'symbol-hashtag',
],
'data' => $data,
]
);
$data = (!empty($options['data']['triggerIntervalUnit'])) ? $options['data']['triggerIntervalUnit'] : 'd';
$builder->add(
'triggerIntervalUnit',
ChoiceType::class,
[
'choices' => [
'mautic.campaign.event.intervalunit.choice.i' => 'i',
'mautic.campaign.event.intervalunit.choice.h' => 'h',
'mautic.campaign.event.intervalunit.choice.d' => 'd',
'mautic.campaign.event.intervalunit.choice.m' => 'm',
'mautic.campaign.event.intervalunit.choice.y' => 'y',
],
'multiple' => false,
'label_attr' => ['class' => 'control-label'],
'label' => false,
'attr' => [
'class' => 'form-control',
],
'placeholder' => false,
'required' => false,
'data' => $data,
]
);
// I could not get Doctrine TimeType does not play well with Symfony TimeType so hacking this workaround
$data = $this->getTimeValue($options['data'], 'triggerHour');
$builder->add(
'triggerHour',
TextType::class,
[
'label' => false,
'attr' => [
'class' => 'form-control',
'data-toggle' => 'time',
'data-format' => 'H:i',
'autocomplete' => 'off',
],
'data' => ($data) ? $data->format('H:i') : $data,
]
);
$data = $this->getTimeValue($options['data'], 'triggerRestrictedStartHour');
$builder->add(
'triggerRestrictedStartHour',
TextType::class,
[
'label' => false,
'attr' => [
'class' => 'form-control',
'data-toggle' => 'time',
'data-format' => 'H:i',
'autocomplete' => 'off',
],
'data' => ($data) ? $data->format('H:i') : $data,
]
);
$data = $this->getTimeValue($options['data'], 'triggerRestrictedStopHour');
$builder->add(
'triggerRestrictedStopHour',
TextType::class,
[
'label' => false,
'attr' => [
'class' => 'form-control',
'data-toggle' => 'time',
'data-format' => 'H:i',
'autocomplete' => 'off',
],
'data' => ($data) ? $data->format('H:i') : $data,
]
);
$builder->add(
'triggerRestrictedDaysOfWeek',
ChoiceType::class,
[
'label' => true,
'attr' => [
'data-toggle' => 'time',
'data-format' => 'H:i',
],
'choices' => [
'mautic.report.schedule.day.monday' => 1,
'mautic.report.schedule.day.tuesday' => 2,
'mautic.report.schedule.day.wednesday' => 3,
'mautic.report.schedule.day.thursday' => 4,
'mautic.report.schedule.day.friday' => 5,
'mautic.report.schedule.day.saturday' => 6,
'mautic.report.schedule.day.sunday' => 0,
'mautic.report.schedule.day.week_days' => -1,
],
'expanded' => true,
'multiple' => true,
'required' => false,
]
);
$builder->add(
'triggerWindow',
ChoiceType::class,
[
'label' => false,
'choices' => [
'mautic.campaign.form.type.trigger_window_day' => OptimizedScheduler::OPTIMIZED_TIME,
'mautic.campaign.form.type.trigger_window_week' => OptimizedScheduler::OPTIMIZED_DAY_AND_TIME,
],
'data' => $options['data']['triggerWindow'] ?? 0,
'required' => false,
'expanded' => true,
'placeholder' => false,
]
);
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event): void {
$data = $event->getData();
$triggerMode = $data['triggerMode'] ?? 'immediate';
// Do not set any trigger window when optimized mode is not used
if ('optimized' !== $triggerMode) {
$data['triggerWindow'] = null;
$event->setData($data);
}
});
}
if (!empty($options['settings']['formType'])) {
$this->addPropertiesType($builder, $options, $masks);
}
$builder->add('type', HiddenType::class);
$builder->add('eventType', HiddenType::class);
$builder->add(
'anchorEventType',
HiddenType::class,
[
'mapped' => false,
'data' => $options['data']['anchorEventType'] ?? '',
]
);
$builder->add(
'canvasSettings',
EventCanvasSettingsType::class,
[
'label' => false,
]
);
$update = !empty($options['data']['properties']);
if (!empty($update)) {
$btnValue = 'mautic.core.form.update';
$btnIcon = 'ri-edit-line';
} else {
$btnValue = 'mautic.core.form.add';
$btnIcon = 'ri-add-line';
}
$builder->add(
'buttons',
FormButtonsType::class,
[
'save_text' => $btnValue,
'save_icon' => $btnIcon,
'save_onclick' => 'Mautic.submitCampaignEvent(event)',
'cancel_onclick' => 'Mautic.cancelCampaignEvent(event)',
'apply_text' => false,
'container_class' => 'bottom-form-buttons',
]
);
$builder->add(
'campaignId',
HiddenType::class,
[
'mapped' => false,
]
);
$builder->addEventSubscriber(new CleanFormSubscriber($masks));
if (!empty($options['action'])) {
$builder->setAction($options['action']);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setRequired(['settings']);
}
/**
* @return \DateTime|mixed|null
*/
private function getTimeValue(array $data, $name)
{
if (empty($data[$name])) {
return null;
}
if ($data[$name] instanceof \DateTime) {
return $data[$name];
}
if (is_array($data[$name]) && array_key_exists('date', $data[$name])) {
return new \DateTime($data[$name]['date']);
} elseif (is_string($data[$name])) {
return new \DateTime($data[$name]);
}
}
public function getBlockPrefix(): string
{
return 'campaignevent';
}
}

View File

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

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Mautic\CampaignBundle\Form\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
final class InfiniteLoopValidator extends ConstraintValidator
{
public function validate(mixed $value, Constraint $constraint): void
{
if (!$constraint instanceof InfiniteLoop) {
throw new UnexpectedTypeException($constraint, InfiniteLoop::class);
}
$data = $this->context->getRoot()->getData();
$this->validateEvent($this->context, $data['triggerMode'] ?? '', $value, (int) ($data['triggerInterval'] ?? 0), $data['triggerIntervalUnit'] ?? '');
}
/**
* @param string[] $addTo
*/
public function validateEvent(ExecutionContextInterface $context, string $triggerMode, array $addTo, int $triggerInterval, string $triggerIntervalUnit): void
{
if (!in_array('this', $addTo)) {
return;
}
if ('immediate' === $triggerMode) {
$context->buildViolation('mautic.campaign.infiniteloop.immediate')->addViolation();
return;
}
if ('interval' === $triggerMode && 'i' === $triggerIntervalUnit && $triggerInterval < 30) {
$context->buildViolation('mautic.campaign.infiniteloop.interval')
->setParameter('%count%', (string) $triggerInterval)
->addViolation();
}
}
}