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,205 @@
<?php
namespace Mautic\UserBundle\Form\Type;
use Mautic\ConfigBundle\Form\Type\ConfigFileType;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @extends AbstractType<array<mixed>>
*/
class ConfigType extends AbstractType
{
public function __construct(
protected CoreParametersHelper $parameters,
protected TranslatorInterface $translator,
) {
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$samlEntityIdChoices = ['', rtrim($this->parameters->get('mautic.site_url'), '/')];
if (!empty($this->parameters->get('mautic.subdomain_url'))) {
$samlEntityIdChoices[] = rtrim($this->parameters->get('mautic.subdomain_url'), '/');
}
$builder->add('saml_idp_entity_id', ChoiceType::class,
[
'choices' => array_combine($samlEntityIdChoices, $samlEntityIdChoices),
'label' => 'mautic.user.config.form.saml.idp_entity_id_label',
'label_attr' => ['class' => 'control-label'],
'required' => true,
'multiple' => false,
'attr' => [
'class' => 'form-control',
],
]);
$builder->add(
'saml_idp_metadata',
ConfigFileType::class,
[
'label' => 'mautic.user.config.form.saml.idp.metadata',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.user.config.form.saml.idp.metadata.tooltip',
'rows' => 10,
],
'required' => false,
'constraints' => [
new File(
[
'mimeTypes' => ['text/plain', 'text/xml', 'application/xml'],
'mimeTypesMessage' => 'mautic.core.invalid_file_type',
]
),
],
]
);
$builder->add(
'saml_idp_own_certificate',
ConfigFileType::class,
[
'label' => 'mautic.user.config.form.saml.idp.own_certificate',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.user.config.form.saml.idp.own_certificate.tooltip',
],
'required' => false,
'constraints' => [
new File(
[
'mimeTypes' => ['text/plain'],
'mimeTypesMessage' => 'mautic.core.invalid_file_type',
]
),
],
]
);
$builder->add(
'saml_idp_own_private_key',
ConfigFileType::class,
[
'label' => 'mautic.user.config.form.saml.idp.own_private_key',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.user.config.form.saml.idp.own_private_key.tooltip',
],
'required' => false,
'constraints' => [
new File(
[
'mimeTypes' => ['text/plain'],
'mimeTypesMessage' => 'mautic.core.invalid_file_type',
]
),
],
]
);
$builder->add(
'saml_idp_own_password',
PasswordType::class,
[
'label' => 'mautic.user.config.form.saml.idp.own_password',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'tooltip' => 'mautic.user.config.form.saml.idp.own_password.tooltip',
],
'required' => false,
]
);
$builder->add(
'saml_idp_email_attribute',
TextType::class,
[
'label' => 'mautic.user.config.form.saml.idp.attribute_email',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'empty_data' => 'EmailAddress',
]
);
$builder->add(
'saml_idp_username_attribute',
TextType::class,
[
'label' => 'mautic.user.config.form.saml.idp.attribute_username',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'required' => false,
]
);
$builder->add(
'saml_idp_firstname_attribute',
TextType::class,
[
'label' => 'mautic.user.config.form.saml.idp.attribute_firstname',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'empty_data' => 'FirstName',
]
);
$builder->add(
'saml_idp_lastname_attribute',
TextType::class,
[
'label' => 'mautic.user.config.form.saml.idp.attribute_lastname',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'empty_data' => 'LastName',
]
);
$builder->add(
'saml_idp_default_role',
RoleListType::class,
[
'label' => 'mautic.user.config.form.saml.idp.default_role',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'data-placeholder' => $this->translator->trans('mautic.user.config.form.saml.idp.disable_creation'),
'tooltip' => 'mautic.user.config.form.saml.idp.default_role.tooltip',
],
'required' => false,
'placeholder' => '',
]
);
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$view->vars['entityId'] = $this->parameters->get('mautic.saml_idp_entity_id');
}
public function getBlockPrefix(): string
{
return 'userconfig';
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace Mautic\UserBundle\Form\Type;
use Mautic\CoreBundle\Form\Type\FormButtonsType;
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\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
/**
* @extends AbstractType<array<mixed>>
*/
class ContactType extends AbstractType
{
/**
* @param FormBuilderInterface<array<mixed>|null> $builder
* @param array<string, mixed> $options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add(
'msg_subject',
TextType::class,
[
'label' => 'mautic.email.subject',
'label_attr' => ['class' => 'control-label'],
'attr' => ['class' => 'form-control'],
'constraints' => [
new NotBlank(['message' => 'Subject should not be blank.']),
new Length(['min' => 3]),
],
]
)
->add(
'msg_body',
TextareaType::class,
[
'label' => 'mautic.user.user.contact.message',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'rows' => 10,
],
'constraints' => [
new NotBlank(['message' => 'Message should not be blank.']),
new Length(['min' => 5]),
],
]
)
->add(
'entity',
HiddenType::class,
[
'attr' => [
'autocomplete' => 'off',
],
]
)
->add(
'id',
HiddenType::class,
[
'attr' => [
'autocomplete' => 'off',
],
]
)
->add(
'returnUrl',
HiddenType::class,
[
'attr' => [
'autocomplete' => 'off',
],
]
)
->add('buttons', FormButtonsType::class, [
'save_text' => 'mautic.user.user.contact.send',
'save_icon' => 'ri-send-plane-line',
'apply_text' => false,
]);
if (!empty($options['action'])) {
$builder->setAction($options['action']);
}
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace Mautic\UserBundle\Form\Type;
use Mautic\CoreBundle\Form\EventListener\CleanFormSubscriber;
use Mautic\UserBundle\Form\Validator\Constraints\NotWeak;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @extends AbstractType<array<mixed>>
*/
class PasswordResetConfirmType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addEventSubscriber(new CleanFormSubscriber([]));
$builder->add(
'identifier',
TextType::class,
[
'label' => 'mautic.user.auth.form.loginusername',
'label_attr' => ['class' => 'sr-only'],
'attr' => [
'class' => 'form-control',
'preaddon' => 'ri-user-6-fill',
'placeholder' => 'mautic.user.auth.form.loginusername',
],
'required' => true,
'constraints' => [
new Assert\NotBlank(['message' => 'mautic.user.user.passwordreset.notblank']),
],
]
);
$builder->add(
'plainPassword',
RepeatedType::class,
[
'first_name' => 'password',
'first_options' => [
'label' => 'mautic.core.password',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'placeholder' => 'mautic.user.user.passwordreset.password.placeholder',
'tooltip' => 'mautic.user.user.form.help.passwordrequirements',
'preaddon' => 'ri-lock-fill',
'autocomplete' => 'off',
],
'required' => true,
'error_bubbling' => false,
'constraints' => [
new Assert\NotBlank(['message' => 'mautic.user.user.passwordreset.notblank']),
new Assert\Length([
'min' => 6,
'minMessage' => 'mautic.user.user.password.minlength',
]),
new NotWeak([
'message' => 'mautic.user.user.password.weak',
]),
],
],
'second_name' => 'confirm',
'second_options' => [
'label' => 'mautic.user.user.form.passwordconfirm',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'placeholder' => 'mautic.user.user.passwordreset.confirm.placeholder',
'tooltip' => 'mautic.user.user.form.help.passwordrequirements',
'preaddon' => 'ri-lock-fill',
'autocomplete' => 'off',
],
'required' => true,
'error_bubbling' => false,
'constraints' => [
new Assert\NotBlank(['message' => 'mautic.user.user.passwordreset.notblank']),
],
],
'type' => PasswordType::class,
'invalid_message' => 'mautic.user.user.password.mismatch',
'required' => true,
'error_bubbling' => false,
]
);
$builder->add(
'submit',
SubmitType::class,
[
'attr' => [
'class' => 'btn btn-lg btn-primary btn-block',
],
'label' => 'mautic.user.user.passwordreset.reset',
]
);
if (!empty($options['action'])) {
$builder->setAction($options['action']);
}
}
public function getBlockPrefix(): string
{
return 'passwordresetconfirm';
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Mautic\UserBundle\Form\Type;
use Mautic\CoreBundle\Form\EventListener\CleanFormSubscriber;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @extends AbstractType<array<mixed>>
*/
class PasswordResetType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addEventSubscriber(new CleanFormSubscriber([]));
$builder->add(
'identifier',
TextType::class,
[
'label' => 'mautic.user.auth.form.loginusername',
'label_attr' => ['class' => 'sr-only'],
'attr' => [
'class' => 'form-control',
'preaddon' => 'ri-user-6-fill',
'placeholder' => 'mautic.user.auth.form.loginusername',
],
'constraints' => [
new Assert\NotBlank(['message' => 'mautic.user.user.passwordreset.notblank']),
],
]
);
$builder->add(
'submit',
SubmitType::class,
[
'attr' => [
'class' => 'btn btn-lg btn-primary btn-block',
],
'label' => 'mautic.user.user.passwordreset.reset',
]
);
if (!empty($options['action'])) {
$builder->setAction($options['action']);
}
}
public function getBlockPrefix(): string
{
return 'passwordreset';
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Mautic\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @extends AbstractType<array<mixed>>
*/
class PermissionListType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setRequired(['bundle', 'level']);
$resolver->setDefaults([
'multiple' => true,
'expanded' => true,
'label_attr' => ['class' => 'control-label'],
'attr' => fn (Options $options): array => [
'data-permission' => $options['bundle'].':'.$options['level'],
'onchange' => 'Mautic.onPermissionChange(this, \''.$options['bundle'].'\')',
],
'choices_as_values' => false,
]);
}
public function getParent(): ?string
{
return ChoiceType::class;
}
public function getBlockPrefix(): string
{
return 'permissionlist';
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Mautic\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Valid;
/**
* @extends AbstractType<array<mixed>>
*/
class PermissionsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
foreach ($options['permissionsConfig'] as $bundle => $config) {
$builder->add(
$bundle,
HiddenType::class,
[
'data' => 'newbundle',
'label' => false,
'mapped' => false,
]
);
$config['permissionObject']->buildForm($builder, $options, $config['data']);
}
if (!empty($options['action'])) {
$builder->setAction($options['action']);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'permissionsConfig' => [],
'constraints' => [new Valid()],
]);
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Mautic\UserBundle\Form\Type;
use Mautic\UserBundle\Model\RoleModel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @extends AbstractType<array<mixed>>
*/
class RoleListType extends AbstractType
{
public function __construct(
private RoleModel $roleModel,
) {
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(
[
'choices' => $this->getRoleChoices(),
'expanded' => false,
'multiple' => false,
'required' => false,
'placeholder' => 'mautic.core.form.chooseone',
]
);
}
public function getParent(): ?string
{
return ChoiceType::class;
}
private function getRoleChoices(): array
{
$choices = [];
$roles = $this->roleModel->getRepository()->getEntities(
[
'filter' => [
'force' => [
[
'column' => 'r.isPublished',
'expr' => 'eq',
'value' => true,
],
],
],
]
);
foreach ($roles as $role) {
$choices[$role->getName(true)] = $role->getId();
}
// sort by name
ksort($choices);
return $choices;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Mautic\UserBundle\Form\Type;
use Mautic\CoreBundle\Form\EventListener\CleanFormSubscriber;
use Mautic\CoreBundle\Form\EventListener\FormExitSubscriber;
use Mautic\CoreBundle\Form\Type\FormButtonsType;
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
use Mautic\UserBundle\Entity\Role;
use Symfony\Component\Form\AbstractType;
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\Component\Validator\Constraints\Valid;
/**
* @extends AbstractType<RoleType>
*/
class RoleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addEventSubscriber(new CleanFormSubscriber(['description' => 'html']));
$builder->addEventSubscriber(new FormExitSubscriber('user.role', $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('isAdmin', YesNoButtonGroupType::class, [
'label' => 'mautic.user.role.form.isadmin',
'attr' => [
'onchange' => 'Mautic.togglePermissionVisibility();',
'tooltip' => 'mautic.user.role.form.isadmin.tooltip',
],
]);
// add a normal text field, but add your transformer to it
$hidden = ($options['data']->isAdmin()) ? ' hide' : '';
$builder->add(
'permissions',
PermissionsType::class,
[
'label' => 'mautic.user.role.permissions',
'mapped' => false, // we'll have to manually build the permissions for persisting
'required' => false,
'attr' => [
'class' => $hidden,
],
'permissionsConfig' => $options['permissionsConfig'],
]
);
$builder->add('buttons', FormButtonsType::class);
if (!empty($options['action'])) {
$builder->setAction($options['action']);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Role::class,
'constraints' => [new Valid()],
'permissionsConfig' => [],
]);
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Mautic\UserBundle\Form\Type;
use Mautic\UserBundle\Model\UserModel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @extends AbstractType<array<mixed>>
*/
class UserListType extends AbstractType
{
/**
* @var array<string,int>
*/
private array $choices = [];
public function __construct(
private UserModel $userModel,
) {
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(
[
'choices' => $this->getUserChoices(),
'expanded' => false,
'multiple' => true,
'required' => false,
'placeholder' => 'mautic.core.form.chooseone',
]
);
}
public function getParent(): ?string
{
return ChoiceType::class;
}
/**
* @return array<string,int>
*/
private function getUserChoices(): array
{
if ($this->choices) {
return $this->choices;
}
$users = $this->userModel->getRepository()->getEntities(
[
'filter' => [
'force' => [
[
'column' => 'u.isPublished',
'expr' => 'eq',
'value' => true,
],
],
],
]
);
foreach ($users as $user) {
$this->choices[$user->getName(true).' ('.$user->getId().')'] = $user->getId();
}
// sort by user name
ksort($this->choices);
return $this->choices;
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Mautic\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @extends AbstractType<mixed>
*/
class UserPreferencesType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// Theme
$builder->add(
'theme',
HiddenType::class,
[
'label' => 'mautic.user.preferences.theme',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'required' => false,
]
);
// Reduce Transparency
$builder->add(
'reduce_transparency',
HiddenType::class,
[
'label' => 'mautic.user.preferences.reduce_transparency',
'label_attr' => ['class' => 'control-label'],
'required' => false,
]
);
// Reduce Motion
$builder->add(
'reduce_motion',
HiddenType::class,
[
'label' => 'mautic.user.preferences.reduce_motion',
'label_attr' => ['class' => 'control-label'],
'required' => false,
]
);
// Contrast Borders
$builder->add(
'contrast_borders',
HiddenType::class,
[
'label' => 'mautic.user.preferences.contrast_borders',
'label_attr' => ['class' => 'control-label'],
'required' => false,
]
);
// Enable Underlines
$builder->add(
'enable_underlines',
HiddenType::class,
[
'label' => 'mautic.user.preferences.enable_underlines',
'label_attr' => ['class' => 'control-label'],
'required' => false,
]
);
}
}

View File

@@ -0,0 +1,279 @@
<?php
namespace Mautic\UserBundle\Form\Type;
use Doctrine\Common\Collections\Order;
use Doctrine\ORM\EntityRepository;
use Mautic\CoreBundle\Form\EventListener\CleanFormSubscriber;
use Mautic\CoreBundle\Form\EventListener\FormExitSubscriber;
use Mautic\CoreBundle\Form\Type\FormButtonsType;
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
use Mautic\CoreBundle\Helper\LanguageHelper;
use Mautic\UserBundle\Entity\Role;
use Mautic\UserBundle\Entity\User;
use Mautic\UserBundle\Model\UserModel;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TimezoneType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @extends AbstractType<User>
*/
class UserType extends AbstractType
{
public function __construct(
private TranslatorInterface $translator,
private UserModel $model,
private LanguageHelper $languageHelper,
) {
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addEventSubscriber(new CleanFormSubscriber(['signature' => 'html', 'email' => 'email']));
$builder->addEventSubscriber(new FormExitSubscriber('user.user', $options));
$builder->add(
'username',
TextType::class,
[
'label' => 'mautic.core.username',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'preaddon' => 'ri-user-6-fill',
'autocomplete' => 'off',
],
]
);
$builder->add(
'firstName',
TextType::class,
[
'label' => 'mautic.core.firstname',
'label_attr' => ['class' => 'control-label'],
'attr' => ['class' => 'form-control'],
]
);
$builder->add(
'lastName',
TextType::class,
[
'label' => 'mautic.core.lastname',
'label_attr' => ['class' => 'control-label'],
'attr' => ['class' => 'form-control'],
]
);
$positions = $this->model->getLookupResults('position', null, 0);
$builder->add(
'position',
TextType::class,
[
'label' => 'mautic.core.position',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'data-options' => json_encode($positions),
],
'required' => false,
]
);
$builder->add(
'email',
EmailType::class,
[
'label' => 'mautic.core.type.email',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'preaddon' => 'ri-mail-line',
],
]
);
$existing = (!empty($options['data']) && $options['data']->getId());
$placeholder = ($existing) ?
$this->translator->trans('mautic.user.user.form.passwordplaceholder') : '';
$required = ($existing) ? false : true;
$builder->add(
'plainPassword',
RepeatedType::class,
[
'first_name' => 'password',
'first_options' => [
'label' => 'mautic.core.password',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'placeholder' => $placeholder,
'tooltip' => 'mautic.user.user.form.help.passwordrequirements',
'preaddon' => 'ri-lock-fill',
'autocomplete' => 'off',
],
'required' => $required,
'error_bubbling' => false,
],
'second_name' => 'confirm',
'second_options' => [
'label' => 'mautic.user.user.form.passwordconfirm',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'placeholder' => $placeholder,
'tooltip' => 'mautic.user.user.form.help.passwordrequirements',
'preaddon' => 'ri-lock-fill',
'autocomplete' => 'off',
],
'required' => $required,
'error_bubbling' => false,
],
'type' => PasswordType::class,
'invalid_message' => 'mautic.user.user.password.mismatch',
'required' => $required,
'error_bubbling' => false,
]
);
$builder->add(
'timezone',
TimezoneType::class,
[
'label' => 'mautic.core.timezone',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'multiple' => false,
'placeholder' => 'mautic.user.user.form.defaulttimezone',
]
);
$builder->add(
'locale',
ChoiceType::class,
[
'choices' => $this->getSupportedLanguageChoices(),
'label' => 'mautic.core.language',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'multiple' => false,
'placeholder' => 'mautic.user.user.form.defaultlocale',
]
);
$builder->add(
'preferences',
UserPreferencesType::class,
[
'label' => false,
]
);
$defaultSignature = '';
if (isset($options['data']) && null === $options['data']->getSignature()) {
$defaultSignature = $this->translator->trans('mautic.email.default.signature', ['%from_name%' => '|FROM_NAME|']);
} elseif (isset($options['data'])) {
$defaultSignature = $options['data']->getSignature();
}
$builder->add(
'signature',
TextareaType::class,
[
'label' => 'mautic.email.token.signature',
'label_attr' => ['class' => 'control-label'],
'required' => false,
'attr' => [
'class' => 'form-control',
],
'data' => $defaultSignature,
'help' => 'mautic.user.config.signature.helper',
]
);
if (empty($options['in_profile'])) {
$builder->add(
$builder->create(
'role',
EntityType::class,
[
'label' => 'mautic.user.role',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'class' => Role::class,
'choice_label' => 'name',
'query_builder' => fn (EntityRepository $er) => $er->createQueryBuilder('r')
->where('r.isPublished = true')
->orderBy('r.name', Order::Ascending->value),
]
)
);
$builder->add('isPublished', YesNoButtonGroupType::class);
$builder->add('buttons', FormButtonsType::class);
} else {
$builder->add(
'buttons',
FormButtonsType::class,
[
'save_text' => 'mautic.core.form.apply',
'apply_text' => false,
]
);
}
if (!empty($options['action'])) {
$builder->setAction($options['action']);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(
[
'data_class' => User::class,
'validation_groups' => [
User::class,
'determineValidationGroups',
],
'ignore_formexit' => false,
'in_profile' => false,
]
);
}
private function getSupportedLanguageChoices(): array
{
// Get the list of available languages
$languages = $this->languageHelper->fetchLanguages(false, false);
$choices = [];
foreach ($languages as $code => $langData) {
$choices[$langData['name']] = $code;
}
$choices = array_merge($choices, array_flip($this->languageHelper->getSupportedLanguages()));
// Alpha sort the languages by name
ksort($choices);
return $choices;
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Mautic\UserBundle\Form\Validator\Constraints;
use Mautic\UserBundle\Model\PasswordStrengthEstimatorModel;
use Symfony\Component\Validator\Constraint;
final class NotWeak extends Constraint
{
public const TOO_WEAK = 'f61e730a-284e-11eb-adc1-0242ac120002';
protected const ERROR_NAMES = [
self::TOO_WEAK => 'PASSWORD_TOO_WEAK_ERROR',
];
public string $message = 'This password is too weak. Consider using a stronger password.';
public int $score = PasswordStrengthEstimatorModel::MINIMUM_PASSWORD_STRENGTH_ALLOWED;
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Mautic\UserBundle\Form\Validator\Constraints;
use Mautic\UserBundle\Model\PasswordStrengthEstimatorModel;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
final class NotWeakValidator extends ConstraintValidator
{
public function __construct(private PasswordStrengthEstimatorModel $passwordStrengthEstimatorModel)
{
}
public function validate(mixed $value, Constraint $constraint): void
{
if (!$constraint instanceof NotWeak) {
throw new UnexpectedTypeException($constraint, NotWeak::class);
}
if ($this->passwordStrengthEstimatorModel->validate($value, $constraint->score)) {
return;
}
$this->context->buildViolation($constraint->message)
->setCode(NotWeak::TOO_WEAK)
->addViolation();
}
}