api: Bildupload v1 unter Formular v2 (Pflichtfelder, Kennung des Wortlauts, Bestätigung mit Token), Legacy-Bildupload abgeschaltet, API-Doku (Bildkennzeichnung Block C)
- StorePressReleaseImageRequest: copyright, rights_confirmed, confirm_text_version Pflicht; 422 mit aktueller Kennung; 16 MiB
- PressReleaseImageController über den gemeinsamen Uploader, Löschen über den Remover; Resource mit Portalkennzeichnung, Kundenangabe, Herkunft, Bestätigung und warnings[]
- POST /service/pressreleases/{id}/images antwortet 410 mit Hinweis auf v1 (E-B4)
- API-Seite und Sprachdateien: Felder und Rechtehinweis mit aktiver Textkennung; Paritätsdokument ergänzt
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
3d3ddc2dc8
commit
24184f1dec
9 changed files with 411 additions and 30 deletions
|
|
@ -25,6 +25,15 @@ class LegacyPressReleaseImageController extends Controller
|
|||
|
||||
public function store(Request $request, int $pressRelease): JsonResponse
|
||||
{
|
||||
// Bildkennzeichnung E-B4 (22.09.2026): Der Alt-Upload kennt weder
|
||||
// Bildnachweis noch Rechtebestätigung und wird nicht genutzt — mit
|
||||
// Formular v2 abgeschaltet. Wer Bilder liefern will, nimmt v1.
|
||||
if (config('images.rights_v2')) {
|
||||
return $this->legacyError(410, [
|
||||
'Image upload via the legacy service API has been discontinued. Use POST /api/v1/press-releases/{id}/images with copyright, ai_declaration, rights_confirmed and confirm_text_version.',
|
||||
]);
|
||||
}
|
||||
|
||||
abort_unless($request->user()->tokenCan('press-release-images:write'), 403);
|
||||
|
||||
$pressRelease = PressRelease::withoutGlobalScope(PortalScope::class)->find($pressRelease);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ use App\Models\PressRelease;
|
|||
use App\Models\PressReleaseImage;
|
||||
use App\Scopes\PortalScope;
|
||||
use App\Services\Image\ImageService;
|
||||
use App\Services\PressRelease\Images\ImageSubmission;
|
||||
use App\Services\PressRelease\Images\ImageUploadActor;
|
||||
use App\Services\PressRelease\Images\ImageUploadException;
|
||||
use App\Services\PressRelease\Images\PressReleaseImageRemover;
|
||||
use App\Services\PressRelease\Images\PressReleaseImageUploader;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
|
@ -20,7 +25,11 @@ use RuntimeException;
|
|||
|
||||
class PressReleaseImageController extends Controller
|
||||
{
|
||||
public function __construct(private readonly ImageService $imageService) {}
|
||||
public function __construct(
|
||||
private readonly ImageService $imageService,
|
||||
private readonly PressReleaseImageUploader $uploader,
|
||||
private readonly PressReleaseImageRemover $remover,
|
||||
) {}
|
||||
|
||||
public function index(Request $request, int $pressRelease): AnonymousResourceCollection
|
||||
{
|
||||
|
|
@ -49,6 +58,13 @@ class PressReleaseImageController extends Controller
|
|||
/** @var UploadedFile $file */
|
||||
$file = $request->file('image');
|
||||
|
||||
// Formular v2 (Bildkennzeichnung Block C): derselbe Ablauf wie im
|
||||
// Kundenformular — Original, Herkunftsprüfung, Bestätigung mit
|
||||
// Token-ID. Ein zweites Bild wird abgelehnt (ein Titelbild je PM).
|
||||
if (config('images.rights_v2')) {
|
||||
return $this->storeUnderRightsV2($request, $pressRelease, $validated, $file);
|
||||
}
|
||||
|
||||
try {
|
||||
$stored = $this->imageService->storePressReleaseImage($file, $pressRelease->id);
|
||||
} catch (RuntimeException $exception) {
|
||||
|
|
@ -65,10 +81,8 @@ class PressReleaseImageController extends Controller
|
|||
'disk' => $stored['disk'],
|
||||
'path' => $stored['path'],
|
||||
'variants' => $stored['variants'],
|
||||
// v1 verlangt die KI-Kennzeichnung je Bild (FormRequest);
|
||||
// Legacy-/service-Calls ohne Feld gelten als Originalfoto
|
||||
// (Kompatibilitäts-Default, Entscheidung 10.07.2026).
|
||||
'ai_declaration' => $validated['ai_declaration'] ?? AiImageDeclaration::Original->value,
|
||||
// v1 verlangt die KI-Kennzeichnung je Bild (FormRequest).
|
||||
'ai_declaration' => $validated['ai_declaration'],
|
||||
'title' => $validated['title'] ?? null,
|
||||
'description' => $validated['description'] ?? null,
|
||||
'copyright' => $validated['copyright'] ?? null,
|
||||
|
|
@ -102,12 +116,63 @@ class PressReleaseImageController extends Controller
|
|||
], 409);
|
||||
}
|
||||
|
||||
$this->imageService->deletePressReleaseImage($image->disk, $image->path, $image->variants);
|
||||
$image->delete();
|
||||
if (config('images.rights_v2')) {
|
||||
$this->remover->remove($image);
|
||||
} else {
|
||||
$this->imageService->deletePressReleaseImage($image->disk, $image->path, $image->variants);
|
||||
$image->delete();
|
||||
}
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
private function storeUnderRightsV2(StorePressReleaseImageRequest $request, PressRelease $pressRelease, array $validated, UploadedFile $file): JsonResponse
|
||||
{
|
||||
$focus = isset($validated['focus_x'], $validated['focus_y'])
|
||||
? ['x' => (int) $validated['focus_x'], 'y' => (int) $validated['focus_y']]
|
||||
: null;
|
||||
|
||||
$submission = new ImageSubmission(
|
||||
copyright: (string) $validated['copyright'],
|
||||
aiDeclaration: AiImageDeclaration::from($validated['ai_declaration']),
|
||||
rightsConfirmed: (bool) $validated['rights_confirmed'],
|
||||
legalTextKey: (string) $validated['confirm_text_version'],
|
||||
title: $validated['title'] ?? null,
|
||||
description: $validated['description'] ?? null,
|
||||
licenseUrl: $validated['license_url'] ?? null,
|
||||
sourceUrl: $validated['source_url'] ?? null,
|
||||
focus: $focus,
|
||||
noCrop: (bool) ($validated['no_crop'] ?? false),
|
||||
);
|
||||
|
||||
$actor = ImageUploadActor::api(
|
||||
$request->user(),
|
||||
$request->user()->currentAccessToken()?->getKey(),
|
||||
$request->ip(),
|
||||
$request->userAgent(),
|
||||
);
|
||||
|
||||
try {
|
||||
$image = $this->uploader->store($file, $pressRelease, $submission, $actor);
|
||||
} catch (ImageUploadException $exception) {
|
||||
return response()->json([
|
||||
'message' => $exception->getMessage(),
|
||||
'errors' => [$exception->field => [$exception->getMessage()]],
|
||||
], 422);
|
||||
} catch (RuntimeException $exception) {
|
||||
return response()->json([
|
||||
'message' => $exception->getMessage(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
return PressReleaseImageResource::make($image->load('latestRightsConfirmation'))
|
||||
->response()
|
||||
->setStatusCode(201);
|
||||
}
|
||||
|
||||
private function findOwnedPressRelease(int $pressReleaseId, Request $request): ?PressRelease
|
||||
{
|
||||
return PressRelease::withoutGlobalScope(PortalScope::class)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue