filled('key')) { $query->where('key', $request->query('key')); } $settings = $query->get()->map(fn (Setting $setting) => [ 'id' => $setting->id, 'key' => $setting->key, 'value' => $setting->value, ]); return response()->json($settings); } public function store(Request $request): JsonResponse { $user = $request->user(); if (!$user || !$user->roles()->where('name', 'ROLE_ADMIN')->exists()) { return response()->json(['message' => 'Forbidden'], 403); } $data = $request->validate([ 'key' => ['required', 'string', 'max:191'], 'value' => ['nullable', 'string'], ]); $value = $data['value'] ?? ''; if ($data['key'] === 'system.php_binary') { $validationError = $this->validatePhpBinarySetting($value); if ($validationError !== null) { return response()->json(['message' => $validationError], 422); } } $setting = Setting::updateOrCreate( ['key' => $data['key']], ['value' => $value] ); return response()->json([ 'id' => $setting->id, 'key' => $setting->key, 'value' => $setting->value, ]); } public function bulkStore(Request $request): JsonResponse { $user = $request->user(); if (!$user || !$user->roles()->where('name', 'ROLE_ADMIN')->exists()) { return response()->json(['message' => 'Forbidden'], 403); } $data = $request->validate([ 'settings' => ['required', 'array'], 'settings.*.key' => ['required', 'string', 'max:191'], 'settings.*.value' => ['nullable', 'string'], ]); $updated = []; foreach ($data['settings'] as $entry) { if (($entry['key'] ?? '') === 'system.php_binary') { $validationError = $this->validatePhpBinarySetting($entry['value'] ?? ''); if ($validationError !== null) { return response()->json(['message' => $validationError], 422); } } $setting = Setting::updateOrCreate( ['key' => $entry['key']], ['value' => $entry['value'] ?? ''] ); $updated[] = [ 'id' => $setting->id, 'key' => $setting->key, 'value' => $setting->value, ]; } return response()->json($updated); } public function validateSystemPhpBinary(Request $request): JsonResponse { $user = $request->user(); if (!$user || !$user->roles()->where('name', 'ROLE_ADMIN')->exists()) { return response()->json(['message' => 'Forbidden'], 403); } $data = $request->validate([ 'value' => ['required', 'string'], ]); $validationError = $this->validatePhpBinarySetting($data['value']); if ($validationError !== null) { return response()->json(['message' => $validationError], 422); } return response()->json([ 'message' => 'PHP interpreter is valid.', ]); } private function validatePhpBinarySetting(string $value): ?string { $binary = trim($value); if ($binary === '' || $binary === 'php') { return null; } if ($binary === 'keyhelp-php-domain') { return '`keyhelp-php-domain` is disabled. Use a concrete binary (e.g. keyhelp-php84).'; } $resolved = null; if (str_contains($binary, '/')) { if (!is_executable($binary)) { return "Configured PHP binary '{$binary}' is not executable."; } $resolved = $binary; } else { $escapedBinary = escapeshellarg($binary); $process = new Process(['sh', '-lc', "command -v {$escapedBinary}"]); $process->setTimeout(5); $process->run(); if (!$process->isSuccessful()) { return "Configured PHP binary '{$binary}' was not found in PATH."; } $resolved = trim($process->getOutput()); if ($resolved === '') { return "Configured PHP binary '{$binary}' was not found in PATH."; } } $phpCheck = new Process([$resolved, '-r', 'echo PHP_VERSION;']); $phpCheck->setTimeout(5); $phpCheck->run(); if (!$phpCheck->isSuccessful() || trim($phpCheck->getOutput()) === '') { return "Configured binary '{$binary}' is not a working PHP CLI executable."; } return null; } }