['label' => 'Texte libre', 'rule' => ['string', 'max:120']], 'phone' => ['label' => 'Téléphone', 'rule' => ['string', 'max:25', 'regex:/^[0-9 +().\-]{6,25}$/']], ]; /** @return array{fields: array, reply_variant: ?string, reply_variant_default: string} */ public function profile(DirectoryUser $user): array { $editable = $this->editableFields($user); $overrides = $user->overrides()->pluck('value', 'field'); $fields = []; foreach ($editable as $f) { $column = VariableMap::FIELDS[$f->field]['column'] ?? null; if (! $column) { continue; } $fields[] = [ 'key' => $f->field, 'label' => $f->label, 'type' => $f->validation_rule ?: 'text', 'directory_value' => $user->getAttribute($column), 'override' => $overrides->has($f->field) ? $overrides[$f->field] : null, 'overridden' => $overrides->has($f->field), ]; } return [ 'fields' => $fields, 'reply_variant' => $user->preference?->reply_variant, 'reply_variant_default' => config('signature.default_reply_variant', 'short'), ]; } /** * @param array $values clé de variable => valeur (null = rétablir l'annuaire) * * @throws \Illuminate\Validation\ValidationException */ public function update(DirectoryUser $user, array $values, ?string $replyVariant, bool $replyVariantProvided, string $actor): void { $editable = $this->editableFields($user)->keyBy('field'); $forbidden = array_diff(array_keys($values), $editable->keys()->all()); if ($forbidden) { throw ValidationException::withMessages( collect($forbidden)->mapWithKeys(fn ($k) => ["fields.{$k}" => 'Ce champ n\'est pas modifiable.'])->all() ); } $rules = ['reply_variant' => ['nullable', Rule::in(['short', 'full', 'none'])]]; foreach (array_keys($values) as $key) { $type = $editable[$key]->validation_rule ?: 'text'; $rules["fields.{$key}"] = array_merge(['nullable'], self::TYPES[$type]['rule'] ?? self::TYPES['text']['rule']); } // Chaîne vide = masquer le champ : rien à valider pour elle. Validator::make( ['fields' => array_filter($values, fn ($v) => $v !== ''), 'reply_variant' => $replyVariant], $rules, ['fields.*.regex' => 'Numéro invalide (chiffres, espaces, + . - ( ) uniquement).', 'fields.*.max' => 'Valeur trop longue.'], )->validate(); DB::transaction(function () use ($user, $values, $replyVariant, $replyVariantProvided, $actor) { $changes = []; foreach ($values as $key => $value) { $value = $value === null ? null : trim((string) $value); $current = UserOverride::where('directory_user_id', $user->id)->where('field', $key)->first(); if ($value === null) { if ($current) { $changes[$key] = [$current->value, '(annuaire)']; $current->delete(); } continue; } if ($current?->value === $value) { continue; } $changes[$key] = [$current?->value ?? '(annuaire)', $value === '' ? '(masqué)' : $value]; UserOverride::updateOrCreate(['directory_user_id' => $user->id, 'field' => $key], ['value' => $value]); } if ($replyVariantProvided) { $pref = UserPreference::firstOrNew(['directory_user_id' => $user->id]); if ($pref->reply_variant !== $replyVariant) { $changes['reply_variant'] = [$pref->reply_variant ?? '(défaut)', $replyVariant ?? '(défaut)']; $pref->reply_variant = $replyVariant; $pref->save(); } } if ($changes) { AuditLog::record('agent', $actor, 'profile.update', $user, $changes); } }); } private function editableFields(DirectoryUser $user) { return EditableField::where('tenant_id', $user->tenant_id)->where('enabled', true) ->whereIn('field', array_keys(VariableMap::FIELDS)) ->orderBy('id')->get(); } }