From b8f033f1e8fa27b60e0b985b113379e4daec7655 Mon Sep 17 00:00:00 2001 From: Mathieu Ducrot Date: Wed, 20 Mar 2024 17:35:19 +0100 Subject: [PATCH] Better Smart Parameter type management and value validation --- CHANGELOG.md | 41 ++++- composer.json | 5 +- config/bundles.php | 1 + config/services.yaml | 9 + src/Admin/ParameterAdmin.php | 140 ++++++++++++++- src/DependencyInjection/Configuration.php | 2 + src/Entity/Parameter.php | 159 +++++++++++++++++- src/Entity/ParameterInterface.php | 33 ++++ src/Enum/ParameterTypeEnum.php | 35 ++++ src/Loader/ParameterLoader.php | 4 + src/Provider/ParameterProvider.php | 12 +- .../parameter_admin/render_value.html.twig | 18 ++ translations/admin.en.xlf | 16 ++ translations/admin.fr.xlf | 16 ++ translations/messages.en.xlf | 32 ++++ translations/messages.fr.xlf | 32 ++++ translations/validators.en.xlf | 12 ++ translations/validators.fr.xlf | 12 ++ 18 files changed, 560 insertions(+), 19 deletions(-) create mode 100644 src/Entity/ParameterInterface.php create mode 100644 src/Enum/ParameterTypeEnum.php create mode 100644 templates/admin/parameter_admin/render_value.html.twig diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e082e2..03a5ea0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ -CHANGELOG for 1.x +CHANGELOG =================== +## v2.1.0 - (2024-03-17) + +### Uprade guide + +**The upgrade to this version needs some extra steps from your part to work properly. Please do the following :** +- Run a doctrine migration for the new properties added to the Smart Parameter Entity if you use them. +- Also add the following templates on your project if you use our ParameterAdmin +```twig +{# templates/admin/parameter_admin/list_value.html.twig #} +{% extends '@SonataAdmin/CRUD/base_list_field.html.twig' %} +{% block field %} + {% include '@SmartSonata/admin/parameter_admin/render_value.html.twig' %} +{% endblock %} + +{# templates/admin/parameter_admin/timeline_history_field.html.twig #} +{% extends '@SmartSonata/admin/base_field/timeline_history_field.html.twig' %} +{% block render_value %} + {% include '@SmartSonata/admin/parameter_admin/render_value.html.twig' %} +{% endblock %} +``` +- Once this is done, the ParameterAmin should work back as before. Update your smart_sonata.parameters types if you need to and you are good to go. + +_Now back to what have changes on this version ..._ + +### Added +- `ParameterInterface` for a better following on Parameter method and type evolution + - It extends the `HistorizableInterface` which add the history field to the entity + - So when upgrading to this version make sure to run a doctrine migration to have your updated values properly logged. +- `ParameterInterface::type` Parameter can now have a type (text by default) which impact the validation and the return type of the getValue +- `ParameterInterface::getArrayValue` for list values and email chain, this method return the value as a proper array type +- `ParameterInterface::regex` used for value validation for text and list parameter type +- Add `yokai/enum-bundle` composer requirement for the ParameterTypeEnum + +### Changed +- `ParameterProvider::getValue` now handle every new type added to the `ParameterInterface` +- `ParameterAdmin` impact of new type and regex property added to the `ParameterInterface` + - Changes made to the parameter value are now logged in the history of the Parameter + - The help field is now visible on the show/form only if it's not null + ## v2.0.1 - (2024-02-22) ### Fixed - Fix clear cache error on `EmailProvider` locale -> can be nullable and check `$requestStack->getCurrentRequest()` is not null. diff --git a/composer.json b/composer.json index 10edfda..173e955 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "yokai/security-token-bundle": "^3.3", "sentry/sentry-symfony": "^4.1", "symfony/expression-language": "^4.4 || ^5.4 || ^6.0", - "smartbooster/core-bundle": "^1.0" + "smartbooster/core-bundle": "^1.0", + "yokai/enum-bundle": "^3.3 || ^4.1" }, "require-dev": { "smartbooster/standard-bundle": "^1.0", @@ -49,7 +50,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.1.x-dev" }, "symfony": { "endpoint": ["https://api.github.com/repos/smartbooster/standard-bundle/contents/recipes.json"] diff --git a/config/bundles.php b/config/bundles.php index 3727c51..1967f78 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -21,4 +21,5 @@ Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['prod' => true], Yokai\SecurityTokenBundle\YokaiSecurityTokenBundle::class => ['all' => true], Sonata\BlockBundle\SonataBlockBundle::class => ['all' => true], + Yokai\EnumBundle\YokaiEnumBundle::class => ['all' => true], ]; diff --git a/config/services.yaml b/config/services.yaml index e516a6e..c6ecda3 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -88,6 +88,8 @@ services: - ~ - Smart\SonataBundle\Entity\Parameter - ~ + - '@Smart\SonataBundle\Enum\ParameterTypeEnum' + - '@Smart\SonataBundle\Logger\HistoryLogger' tags: - { name: sonata.admin, manager_type: orm, label: dashboard.label_parameter } @@ -155,3 +157,10 @@ services: # The "Symfony\Component\DependencyInjection\ContainerInterface" autowiring alias is deprecated. Define it explicitly in your app if you want to keep using it. # It's not a good way, but it's practical for the moment. It's used in AbstractAdmin::get. Need to replace all get call by injection Symfony\Component\DependencyInjection\ContainerInterface: '@service_container' + + # Enum + Smart\SonataBundle\Enum\ParameterTypeEnum: + arguments: + - '@translator' + tags: + - {name: yokai_enum.enum} diff --git a/src/Admin/ParameterAdmin.php b/src/Admin/ParameterAdmin.php index d732731..c53fcc7 100644 --- a/src/Admin/ParameterAdmin.php +++ b/src/Admin/ParameterAdmin.php @@ -2,17 +2,48 @@ namespace Smart\SonataBundle\Admin; +use Doctrine\ORM\UnitOfWork; +use Smart\CoreBundle\Validator\Constraints\EmailChain; +use Smart\SonataBundle\Entity\Log\HistorizableInterface; +use Smart\SonataBundle\Entity\ParameterInterface; +use Smart\SonataBundle\Enum\ParameterTypeEnum; +use Smart\SonataBundle\Logger\HistoryLogger; use Sonata\AdminBundle\Datagrid\DatagridMapper; use Sonata\AdminBundle\Datagrid\ListMapper; +use Sonata\AdminBundle\FieldDescription\FieldDescriptionInterface; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Route\RouteCollectionInterface; use Sonata\AdminBundle\Show\ShowMapper; +use Sonata\DoctrineORMAdminBundle\Filter\ChoiceFilter; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Validator\Constraints\Email; +use Yokai\EnumBundle\Form\Type\EnumType; /** * @author Mathieu Ducrot + * @method ParameterInterface getSubject() */ class ParameterAdmin extends AbstractAdmin { + private ParameterTypeEnum $typeEnum; + private HistoryLogger $historyLogger; + private array $updateInitialData = []; + + public function __construct( + string $code, + ?string $class, + string $baseControllerName, + ParameterTypeEnum $typeEnum, + HistoryLogger $historyLogger, + ) { + parent::__construct($code, $class, $baseControllerName); + $this->typeEnum = $typeEnum; + $this->historyLogger = $historyLogger; + } + protected function configureRoutes(RouteCollectionInterface $collection): void { $collection @@ -27,8 +58,16 @@ protected function configureListFields(ListMapper $list): void $list ->addIdentifier('id', null, ['label' => 'field.label_id']) ->addIdentifier('code', null, ['label' => 'field.label_code']) + ->add('type', FieldDescriptionInterface::TYPE_CHOICE, [ + 'label' => 'field.label_type', + 'choices' => array_flip($this->typeEnum->getChoices()), + 'catalog' => false, // enum is self translated + ]) ->add('help', null, ['label' => 'field.label_help']) - ->add('value', null, ['label' => 'field.label_value']) + ->add('value', null, [ + 'label' => 'field.label_value', + 'template' => 'admin/parameter_admin/list_value.html.twig' + ]) ; } @@ -39,36 +78,108 @@ protected function configureDatagridFilters(DatagridMapper $filter): void 'show_filter' => true, 'label' => 'field.label_code' ]) + ->add('type', ChoiceFilter::class, [ + 'field_type' => EnumType::class, + 'field_options' => ['enum' => ParameterTypeEnum::class], + 'show_filter' => true, + 'label' => 'field.label_type', + ]) ; } protected function configureShowFields(ShowMapper $show): void { + $parameter = $this->getSubject(); $show ->with('fieldset.label_general', ['label' => 'fieldset.label_general']) ->add('id', null, ['label' => 'field.label_id']) ->add('code', null, ['label' => 'field.label_code']) - ->add('help', null, ['label' => 'field.label_help']) - ->add('value', null, ['label' => 'field.label_value']) + ->add('type', FieldDescriptionInterface::TYPE_CHOICE, [ + 'label' => 'field.label_type', + 'choices' => array_flip($this->typeEnum->getChoices()), + 'catalog' => false, // enum is self translated + ]) + ; + if ($parameter->getHelp()) { + $show->add('help', null, ['label' => 'field.label_help']); + } + if ($parameter->getRegex() !== null) { + $show->add('regex', null, ['label' => 'field.label_regex']); + } + $valueType = null; + if ($parameter->isBooleanType()) { + $valueType = FieldDescriptionInterface::TYPE_BOOLEAN; + } elseif ($parameter->isTextareaType()) { + $valueType = FieldDescriptionInterface::TYPE_HTML; + } + $show + ->add('value', $valueType, ['label' => 'field.label_value']) + ->end() + ->with('fieldset.label_history', ['class' => 'col-md-12', 'label' => 'fieldset.label_history']) + ->add('history', null, ['template' => 'admin/parameter_admin/timeline_history_field.html.twig']) ->end() ; } protected function configureFormFields(FormMapper $form): void { + $parameter = $this->getSubject(); $form ->with('fieldset.label_general', ['label' => 'fieldset.label_general']) ->add('code', null, [ 'label' => 'field.label_code', 'disabled' => true ]) + ->add('type', EnumType::class, [ + 'label' => 'field.label_type', + 'enum' => ParameterTypeEnum::class, + 'disabled' => true, + ]) + ; + + if ($parameter->getHelp()) { + $form ->add('help', null, [ 'label' => 'field.label_help', 'disabled' => true ]) - ->add('value', null, ['label' => 'field.label_value']) - ->end() - ; + ; + } + if ($parameter->getRegex() !== null) { + $form->add('regex', null, [ + 'label' => 'field.label_regex', + 'disabled' => true, + ]); + } + + $valueType = TextType::class; + $valueOptions = []; + if ($parameter->isBooleanType()) { + $valueType = CheckboxType::class; + $valueOptions = ['required' => false]; + } elseif ($parameter->isEmailChainType() || $parameter->isListType() || $parameter->isTextareaType()) { + $valueType = TextareaType::class; + $valueOptions = ['attr' => ['rows' => 10]]; + if (!$parameter->isTextareaType()) { + $valueOptions['help'] = 'field.help_parameter_array_values'; + } + if ($parameter->isEmailChainType()) { + $valueOptions['constraints'] = [new EmailChain(separator: PHP_EOL)]; + } + } elseif ($parameter->isFloatType() || $parameter->isIntegerType()) { + $valueType = NumberType::class; + $valueOptions = ['html5' => true]; + if ($parameter->isIntegerType()) { + $valueOptions['scale'] = 0; + } + } elseif ($parameter->isEmailType()) { + // MDT Should be converted with Assert\When for when we drop support for SF 5.4 + $valueOptions['constraints'] = [new Email()]; + } + $form->add('value', $valueType, [ + 'label' => 'field.label_value', + ...$valueOptions, + ])->end(); } public function getExportFormats(): array @@ -84,4 +195,21 @@ protected function configureExportFields(): array $this->trans('field.label_help') => 'help', ]; } + + protected function preUpdate(object $object): void + { + /** @var UnitOfWork $uow @phpstan-ignore-next-line */ + $uow = $this->getModelManager()->getEntityManager($this->getClass())->getUnitOfWork(); + $this->updateInitialData = [ + 'value' => $uow->getOriginalEntityData($object)['value'], + ]; + } + + protected function postUpdate(object $object): void + { + /** @var HistorizableInterface $object */ + $this->historyLogger->logDiff($object, $this->updateInitialData, [ + 'author' => $this->getUser()->getFullName(), // @phpstan-ignore-line + ]); + } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 03990e7..a16dcc3 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -44,7 +44,9 @@ private function getParametersNode(): ArrayNodeDefinition ->useAttributeAsKey('name') ->arrayPrototype() ->children() + ->scalarNode('type')->defaultValue('text')->end() ->scalarNode('value')->isRequired()->end() + ->scalarNode('regex')->end() ->scalarNode('help')->end() ->end() ->end() diff --git a/src/Entity/Parameter.php b/src/Entity/Parameter.php index 7c3a225..ed1b9d7 100644 --- a/src/Entity/Parameter.php +++ b/src/Entity/Parameter.php @@ -4,14 +4,22 @@ use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Smart\CoreBundle\Utils\ArrayUtils; +use Smart\SonataBundle\Entity\Log\HistorizableTrait; +use Smart\SonataBundle\Enum\ParameterTypeEnum; +use Sonata\Exporter\Exception\InvalidMethodCallException; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\Constraints as Assert; /** * @author Mathieu Ducrot */ #[ORM\Table(name: "smart_parameter")] #[ORM\Entity(repositoryClass: "Smart\SonataBundle\Repository\ParameterRepository")] -class Parameter +class Parameter implements ParameterInterface { + use HistorizableTrait; + #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] @@ -21,16 +29,110 @@ class Parameter private string $code; #[ORM\Column(name: "value", type: Types::TEXT, nullable: false)] - private string $value; + private string|bool|null $value = null; #[ORM\Column(name: "help", type: Types::TEXT, nullable: true)] private ?string $help = null; + #[ORM\Column(length: 15, nullable: false, options: ['default' => 'text'])] + private string $type = ParameterTypeEnum::TEXT; + + /** + * MDT We don't need to set the start/end delimiter when setting the regex, it is automatically added when the validate callback is triggered + */ + #[ORM\Column(length: 100, nullable: true)] + #[Assert\Length(max: 100)] + private ?string $regex = null; + + /** + * @param mixed $payload + */ + #[Assert\Callback] + public function validate(ExecutionContextInterface $context, $payload): void + { + // MDT Should be converted with Assert\When for when we drop support for SF 5.4 + if (!$this->isBooleanType() && $this->getValue() === null || $this->getValue() === '') { + $context->buildViolation("This value should not be blank.") + ->atPath('value') + ->addViolation(); + } + + $regex = $this->getRegex(); + if ($regex !== null && $this->isTextType() && !preg_match('/' . $regex . '/', (string) $this->getValue())) { + $context->buildViolation("smart_parameter.regex_error") + ->atPath('value') + ->addViolation(); + } elseif ($regex !== null && $this->isListType()) { + foreach ($this->getArrayValue() as $rowValue) { + if (!preg_match('/' . $regex . '/', (string) $rowValue)) { + $unvalidRowValues[] = $rowValue; + } + } + if (!empty($unvalidRowValues)) { + $context->buildViolation("smart_parameter.list_regex_error", [ + '%errors%' => implode(', ', $unvalidRowValues) + ]) + ->atPath('value') + ->addViolation(); + } + } + } + public function __toString() { return $this->getCode(); } + public function isArrayValue(): bool + { + return $this->isListType() || $this->isEmailChainType(); + } + + public function isTextType(): bool + { + return $this->getType() === ParameterTypeEnum::TEXT; + } + + public function isEmailType(): bool + { + return $this->getType() === ParameterTypeEnum::EMAIL; + } + + public function isEmailChainType(): bool + { + return $this->getType() === ParameterTypeEnum::EMAIL_CHAIN; + } + + public function isBooleanType(): bool + { + return $this->getType() === ParameterTypeEnum::BOOLEAN; + } + + public function isListType(): bool + { + return $this->getType() === ParameterTypeEnum::LIST; + } + + public function isTextareaType(): bool + { + return $this->getType() === ParameterTypeEnum::TEXTAREA; + } + + public function isIntegerType(): bool + { + return $this->getType() === ParameterTypeEnum::INTEGER; + } + + public function isFloatType(): bool + { + return $this->getType() === ParameterTypeEnum::FLOAT; + } + + public function getAttributsForHistoryDiff(): array + { + return ['value']; + } + public function getId(): ?int { return $this->id; @@ -53,18 +155,41 @@ public function setCode(string $code): void } /** - * @return string + * @return string|bool|float|int|null */ - public function getValue(): string + public function getValue(): string|bool|float|int|null { - return $this->value; + $value = $this->value; + if ($this->isBooleanType()) { + $toReturn = (bool) $value; + } elseif ($this->isFloatType()) { + $toReturn = (float) $value; + } elseif ($this->isIntegerType()) { + $toReturn = (int) $value; + } else { + $toReturn = $this->value; + } + + return $toReturn; + } + + public function getArrayValue(): array + { + if (!$this->isArrayValue()) { + throw new InvalidMethodCallException("You can't use this method for a parameter that isn't a list or an email chain."); + } + + return ArrayUtils::getArrayFromTextarea((string) $this->getValue()); } /** - * @param string $value + * @param string|bool|null $value */ - public function setValue(string $value): void + public function setValue(string|bool|null $value): void { + if ($this->isBooleanType() && $value === false) { + $value = '0'; + } $this->value = $value; } @@ -83,4 +208,24 @@ public function setHelp(?string $help): void { $this->help = $help; } + + public function getType(): string + { + return $this->type; + } + + public function setType(string $type): void + { + $this->type = $type; + } + + public function getRegex(): ?string + { + return $this->regex; + } + + public function setRegex(?string $regex): void + { + $this->regex = $regex; + } } diff --git a/src/Entity/ParameterInterface.php b/src/Entity/ParameterInterface.php new file mode 100644 index 0000000..14fdce8 --- /dev/null +++ b/src/Entity/ParameterInterface.php @@ -0,0 +1,33 @@ + + */ +interface ParameterInterface extends HistorizableInterface +{ + public function getId(): ?int; + public function getCode(): string; + public function setCode(string $code): void; + public function getValue(): string|bool|float|int|null; + public function getArrayValue(): array; + public function isArrayValue(): bool; + public function setValue(string|bool|null $value): void; + public function getHelp(): ?string; + public function setHelp(?string $help): void; + public function getType(): string; + public function isTextType(): bool; + public function isEmailType(): bool; + public function isEmailChainType(): bool; + public function isBooleanType(): bool; + public function isListType(): bool; + public function isTextareaType(): bool; + public function isIntegerType(): bool; + public function isFloatType(): bool; + public function setType(string $type): void; + public function getRegex(): ?string; + public function setRegex(?string $regex): void; +} diff --git a/src/Enum/ParameterTypeEnum.php b/src/Enum/ParameterTypeEnum.php new file mode 100644 index 0000000..f242a3a --- /dev/null +++ b/src/Enum/ParameterTypeEnum.php @@ -0,0 +1,35 @@ + + */ +class ParameterTypeEnum extends TranslatedEnum +{ + public const TEXT = 'text'; + public const EMAIL = 'email'; + public const EMAIL_CHAIN = 'email_chain'; + public const BOOLEAN = 'boolean'; + public const LIST = 'list'; + public const TEXTAREA = 'textarea'; + public const INTEGER = 'integer'; + public const FLOAT = 'float'; + + public function __construct(TranslatorInterface $translator) + { + parent::__construct([ + self::TEXT, + self::EMAIL, + self::EMAIL_CHAIN, + self::BOOLEAN, + self::LIST, + self::TEXTAREA, + self::INTEGER, + self::FLOAT, + ], $translator, 'enum.parameter_type.%s'); + } +} diff --git a/src/Loader/ParameterLoader.php b/src/Loader/ParameterLoader.php index 58d7994..519694d 100644 --- a/src/Loader/ParameterLoader.php +++ b/src/Loader/ParameterLoader.php @@ -99,10 +99,14 @@ protected function handleParametersToInsert(string $code, array $data, int $i): { $parameter = new Parameter(); $parameter->setCode($code); + $parameter->setType($data['type']); $parameter->setValue($data['value']); if (isset($data['help'])) { $parameter->setHelp($data['help']); } + if (isset($data['regex'])) { + $parameter->setRegex($data['regex']); + } $this->entityManager->persist($parameter); $this->logs["nb_inserted"]++; diff --git a/src/Provider/ParameterProvider.php b/src/Provider/ParameterProvider.php index e9b6079..1766e3d 100644 --- a/src/Provider/ParameterProvider.php +++ b/src/Provider/ParameterProvider.php @@ -5,6 +5,7 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityNotFoundException; use Smart\SonataBundle\Entity\Parameter; +use Smart\SonataBundle\Entity\ParameterInterface; /** * @author Mathieu Ducrot @@ -25,7 +26,7 @@ public function __construct(EntityManagerInterface $entityManager) * * @throws EntityNotFoundException */ - public function get(string $code): Parameter + public function get(string $code): ParameterInterface { if (isset($this->parameters[$code])) { return $this->parameters[$code]; @@ -44,8 +45,13 @@ public function get(string $code): Parameter /** * Get a Parameter value */ - public function getValue(string $code): string + public function getValue(string $code): array|bool|float|int|string { - return $this->get($code)->getValue(); + $parameter = $this->get($code); + if ($parameter->isArrayValue()) { + return $parameter->getArrayValue(); + } else { + return $parameter->getValue(); + } } } diff --git a/templates/admin/parameter_admin/render_value.html.twig b/templates/admin/parameter_admin/render_value.html.twig new file mode 100644 index 0000000..0f23d56 --- /dev/null +++ b/templates/admin/parameter_admin/render_value.html.twig @@ -0,0 +1,18 @@ +{% trans_default_domain 'SonataAdminBundle' %} + +{% set type = object.type %} +{% if type == constant('Smart\\SonataBundle\\Enum\\ParameterTypeEnum::BOOLEAN') %} + {% if value == 1 %} + {{ 'label_type_yes'|trans }} + {% else %} + {{ 'label_type_no'|trans }} + {% endif %} +{% elseif type == constant('Smart\\SonataBundle\\Enum\\ParameterTypeEnum::EMAIL_CHAIN') or + type == constant('Smart\\SonataBundle\\Enum\\ParameterTypeEnum::LIST') +%} + {{ value|nl2br }} +{% elseif type == constant('Smart\\SonataBundle\\Enum\\ParameterTypeEnum::TEXTAREA') %} + {{ value|raw }} +{% else %} + {{ value }} +{% endif %} diff --git a/translations/admin.en.xlf b/translations/admin.en.xlf index 5ff2ea0..d313c92 100644 --- a/translations/admin.en.xlf +++ b/translations/admin.en.xlf @@ -62,6 +62,10 @@ fieldset.label_log Log + + fieldset.label_history + History + field.label_id @@ -83,6 +87,18 @@ field.label_value Value + + field.help_parameter_array_values + Separate each value with a line return. + + + field.label_type + Type + + + field.label_regex + Regex + form.label_first_name Firstname diff --git a/translations/admin.fr.xlf b/translations/admin.fr.xlf index 75d7cae..129f845 100644 --- a/translations/admin.fr.xlf +++ b/translations/admin.fr.xlf @@ -79,6 +79,10 @@ fieldset.label_log Log + + fieldset.label_history + Historique + field.label_id @@ -100,6 +104,18 @@ field.label_value Valeur + + field.help_parameter_array_values + Séparer chaque valeur par un retour ligne. + + + field.label_type + Type + + + field.label_regex + Regex + form.label_first_name Prénom diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 7437a06..9f86572 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -11,6 +11,38 @@ impersonate.exit_message Back to previous user. + + enum.parameter_type.text + Text + + + enum.parameter_type.email + Email + + + enum.parameter_type.email_chain + Email list + + + enum.parameter_type.boolean + Boolean + + + enum.parameter_type.list + List + + + enum.parameter_type.textarea + Textarea + + + enum.parameter_type.integer + Integer + + + enum.parameter_type.float + Decimal + \ No newline at end of file diff --git a/translations/messages.fr.xlf b/translations/messages.fr.xlf index e5857c6..11164a8 100644 --- a/translations/messages.fr.xlf +++ b/translations/messages.fr.xlf @@ -11,6 +11,38 @@ impersonate.exit_message Retour à l'utilisateur d'origine. + + enum.parameter_type.text + Texte + + + enum.parameter_type.email + Email + + + enum.parameter_type.email_chain + Liste d'emails + + + enum.parameter_type.boolean + Boolean + + + enum.parameter_type.list + Liste + + + enum.parameter_type.textarea + Textarea + + + enum.parameter_type.integer + Entier + + + enum.parameter_type.float + Décimal + \ No newline at end of file diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf index 92a237a..b4871eb 100644 --- a/translations/validators.en.xlf +++ b/translations/validators.en.xlf @@ -23,6 +23,18 @@ is_password_safe.missing_number_error It must contain at least one number. + + smart_parameter.regex_error + The value does not respect the regex. + + + smart_parameter.list_regex_error + The following values in the list do not respect the regex: %errors% + + + email_chain.format_error + Contains invalid emails, please correct them. + diff --git a/translations/validators.fr.xlf b/translations/validators.fr.xlf index 5b87f01..a027185 100644 --- a/translations/validators.fr.xlf +++ b/translations/validators.fr.xlf @@ -23,6 +23,18 @@ is_password_safe.missing_number_error Il doit contenir au moins un chiffre. + + smart_parameter.regex_error + La valeur ne respecte pas la regex. + + + smart_parameter.list_regex_error + Les valeurs suivantes de la liste ne respectent pas la regex : %errors% + + + email_chain.format_error + Contient des emails invalides, veuillez les corriger. +