fix: enforce mutability checks for all fields
This commit is contained in:
@@ -21,6 +21,32 @@ use App\Form\Service\Contract\ParticipantFieldHandlerInterface;
|
||||
*/
|
||||
class ParticipantFieldHandlerRegistry
|
||||
{
|
||||
private const MUTABILITY_ADDITIONAL_SERVICES = 'additional_services';
|
||||
private const MUTABILITY_TRANSPORTATION = 'transportation';
|
||||
private const MUTABILITY_PICKUPS = 'pickups';
|
||||
|
||||
/**
|
||||
* Maps handler names to mutability categories for edit-mode enforcement.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private const HANDLER_MUTABILITY_CATEGORY_MAP = [
|
||||
'additionalServices' => self::MUTABILITY_ADDITIONAL_SERVICES,
|
||||
'courses' => self::MUTABILITY_ADDITIONAL_SERVICES,
|
||||
'board' => self::MUTABILITY_ADDITIONAL_SERVICES,
|
||||
'veg' => self::MUTABILITY_ADDITIONAL_SERVICES,
|
||||
'skiPass' => self::MUTABILITY_ADDITIONAL_SERVICES,
|
||||
'rentals' => self::MUTABILITY_ADDITIONAL_SERVICES,
|
||||
'rentalInsurance' => self::MUTABILITY_ADDITIONAL_SERVICES,
|
||||
'transportationOutbound' => self::MUTABILITY_TRANSPORTATION,
|
||||
'transportationInbound' => self::MUTABILITY_TRANSPORTATION,
|
||||
'parking' => self::MUTABILITY_TRANSPORTATION,
|
||||
'licensePlate' => self::MUTABILITY_TRANSPORTATION,
|
||||
'transportationDiscountReplacement' => self::MUTABILITY_TRANSPORTATION,
|
||||
'pickup' => self::MUTABILITY_PICKUPS,
|
||||
'dropOff' => self::MUTABILITY_PICKUPS,
|
||||
];
|
||||
|
||||
/** @var array<string, ParticipantFieldHandlerInterface> Registered handlers indexed by field name */
|
||||
private array $handlers = [];
|
||||
|
||||
@@ -121,6 +147,10 @@ class ParticipantFieldHandlerRegistry
|
||||
// Process each handler across all participants before moving to the next handler
|
||||
// This ensures booking-level state (like family booking detection) is accurate
|
||||
foreach ($sortedHandlerNames as $handlerName) {
|
||||
if (false === $this->isHandlerMutableForBooking($handlerName, $bookingDto)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$handler = $this->handlers[$handlerName];
|
||||
|
||||
// Apply this handler to all participants
|
||||
@@ -158,6 +188,10 @@ class ParticipantFieldHandlerRegistry
|
||||
|
||||
// Apply each handler to the specified participant
|
||||
foreach ($sortedHandlerNames as $handlerName) {
|
||||
if (false === $this->isHandlerMutableForBooking($handlerName, $bookingDto)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$handler = $this->handlers[$handlerName];
|
||||
|
||||
// Let each handler decide if it should process this participant's data
|
||||
@@ -224,8 +258,11 @@ class ParticipantFieldHandlerRegistry
|
||||
'courses',
|
||||
'additionalServices',
|
||||
'board',
|
||||
'veg',
|
||||
'pickup',
|
||||
'dropOff',
|
||||
'parking',
|
||||
'licensePlate',
|
||||
];
|
||||
|
||||
foreach ($serviceFields as $fieldName) {
|
||||
@@ -521,6 +558,25 @@ class ParticipantFieldHandlerRegistry
|
||||
return $this->convertSingleValueToSubmittedFormat($dtoValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a field handler is allowed to run based on edit-mode mutability flags.
|
||||
*/
|
||||
private function isHandlerMutableForBooking(string $handlerName, BookingDto $bookingDto): bool
|
||||
{
|
||||
if (BookingDto::MODE_CREATE === $bookingDto->getMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$mutabilityCategory = self::HANDLER_MUTABILITY_CATEGORY_MAP[$handlerName] ?? null;
|
||||
|
||||
return match ($mutabilityCategory) {
|
||||
self::MUTABILITY_ADDITIONAL_SERVICES => $bookingDto->travel->additionalServicesMutable,
|
||||
self::MUTABILITY_TRANSPORTATION => $bookingDto->travel->transportationServicesMutable,
|
||||
self::MUTABILITY_PICKUPS => $bookingDto->travel->pickupsMutable,
|
||||
default => true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single DTO value to submitted form format.
|
||||
*
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
{%- for child in form %}
|
||||
{% set choiceData = form.vars.choices[loop.index0] is defined ? form.vars.choices[loop.index0].data : null %}
|
||||
{% set description = child.vars.attr['data-description']|default(null) %}
|
||||
{% set isReadonly = child.vars.attr.readonly is defined %}
|
||||
{% set fieldReadonly = form.vars.attr.readonly is defined %}
|
||||
{% set isReadonly = fieldReadonly or child.vars.attr.readonly is defined %}
|
||||
{%- set child_attr = {} -%}
|
||||
{%- if attr['hx-trigger'] is defined -%}
|
||||
{%- set child_attr = {
|
||||
@@ -51,6 +52,9 @@
|
||||
'hx-swap': attr['hx-swap']
|
||||
} -%}
|
||||
{%- endif -%}
|
||||
{%- if fieldReadonly and child.vars.attr.readonly is not defined -%}
|
||||
{%- set child_attr = child_attr|merge({'readonly': true}) -%}
|
||||
{%- endif -%}
|
||||
<tr>
|
||||
<td class="border border-primary-bg p-2 align-top">
|
||||
<div>{{- child.vars.label -}}</div>
|
||||
|
||||
@@ -205,7 +205,9 @@
|
||||
<div{% if tooltip is not null %} {{ stimulus_controller('tooltip', { 'content': tooltip }) }}{% endif %}>
|
||||
{%- if attr.readonly is defined %}
|
||||
<div class="cursor-not-allowed">
|
||||
{{ parent() }}
|
||||
<div class="pointer-events-none">
|
||||
{{ parent() }}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ parent() }}
|
||||
@@ -223,6 +225,9 @@
|
||||
'hx-swap': attr['hx-swap']
|
||||
}) -%}
|
||||
{%- endif -%}
|
||||
{%- if attr.readonly is defined and child.vars.attr.readonly is not defined -%}
|
||||
{%- set child_attr = child_attr|merge({'readonly': true}) -%}
|
||||
{%- endif -%}
|
||||
{%- if child.vars.attr['data-tooltip'] is defined -%}
|
||||
{%- set child_attr = child_attr|merge({'data-tooltip': child.vars.attr['data-tooltip']}) -%}
|
||||
{%- endif -%}
|
||||
@@ -358,4 +363,3 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user