Files
KanbanGit/app/Http/Requests/ResetProjectTemplateRequest.php
T
Nico Schröder 9895fc36db
CI/CD / test_and_check_app (push) Failing after 3m8s
CI/CD / Publish immutable image (push) Skipped
CI/CD / test_and_check_app (pull_request) Failing after 3m8s
CI/CD / Publish immutable image (pull_request) Skipped
fix: make project template inheritance granular and safe
2026-09-08 10:25:52 +00:00

108 lines
3.7 KiB
PHP

<?php
namespace App\Http\Requests;
use App\Actions\Projects\DuplicateProjectConfiguration;
use App\Models\Project;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Validator;
class ResetProjectTemplateRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
/** @var Project $project */
$project = $this->route('project');
return [
'areas' => ['sometimes', 'array', 'min:1'],
'areas.*' => ['required', 'string', Rule::in(DuplicateProjectConfiguration::copyableAreas())],
'confirmation_key' => [
'required_without:areas',
'string',
Rule::in([$project->key]),
],
'status_replacements' => ['sometimes', 'array'],
'status_replacements.*' => [
'required',
'integer',
Rule::exists('ticket_statuses', 'id')->where('project_id', $project->id),
],
];
}
/** @return array<int, callable(Validator): void> */
public function after(): array
{
return [
function (Validator $validator): void {
if ($validator->errors()->isNotEmpty()) {
return;
}
/** @var Project $project */
$project = $this->route('project');
$validated = $validator->validated();
$areas = array_values(Arr::get(
$validated,
'areas',
DuplicateProjectConfiguration::copyableAreas(),
));
if (! in_array(DuplicateProjectConfiguration::COPY_TICKET_STATUSES, $areas, true)) {
return;
}
$project->loadMissing('projectTemplate');
$configuration = app(DuplicateProjectConfiguration::class)->normalizeConfiguration(
$project->projectTemplate?->configuration_snapshot ?? [],
);
$templateStatuses = Arr::get(
$configuration,
DuplicateProjectConfiguration::COPY_TICKET_STATUSES,
[],
);
$statuses = $project->ticketStatuses()->withCount('tickets')->get()->values();
$allowedReplacementIds = $statuses->take(count($templateStatuses))->pluck('id');
$replacements = Arr::get($validated, 'status_replacements', []);
foreach ($statuses->slice(count($templateStatuses)) as $status) {
if ($status->tickets_count === 0) {
continue;
}
$replacementId = (int) Arr::get($replacements, (string) $status->id, 0);
if (! $allowedReplacementIds->contains($replacementId)) {
$validator->errors()->add(
"status_replacements.{$status->id}",
"Choose a replacement status for {$status->name}.",
);
}
}
},
];
}
/**
* @return array<string, string>
*/
public function messages(): array
{
return [
'confirmation_key.in' => 'Enter the exact project key to reset this project to its template baseline.',
'areas.*.in' => 'Choose a valid template area to reset.',
];
}
}