Hotfix: Vorschau im Inhalte-Editor und Seitennavigation der Medienbibliothek

- AllowPortalFraming: frame-ancestors 'self' + Portal statt X-Frame-Options SAMEORIGIN,
  damit portal.b2in.eu die Website im Vorschau-iframe zeigen darf
- Medienbibliothek und Hub-Verwaltung: WithPagination – ohne zeigten die Seitenlinks nach
  dem ersten Livewire-Klick auf den Update-Endpunkt (405); Suche/Filter springen auf Seite 1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-10-09 09:36:16 +00:00
parent a661a68913
commit 6f1b27371b
5 changed files with 92 additions and 2 deletions

View file

@ -0,0 +1,36 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Erlaubt die Live-Vorschau im Inhalte-Editor des Portals (09.10.2026).
*
* Der Editor auf portal.b2in.eu zeigt die Website in einem iframe. Der Webserver schickt
* `X-Frame-Options: SAMEORIGIN`, das jede andere Domain ausschließt – also auch das Portal.
* `Content-Security-Policy: frame-ancestors` löst X-Frame-Options ab: Browser werten dann nur
* noch diese Liste aus. Erlaubt sind die eigene Domain und das Portal, alle anderen Seiten
* dürfen die Website weiterhin nicht einbetten.
*/
class AllowPortalFraming
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
if ($response->headers->has('Content-Security-Policy')) {
return $response;
}
$portal = rtrim((string) config('domains.domain_portal_url'), '/');
$ancestors = trim("'self' {$portal}");
$response->headers->set('Content-Security-Policy', "frame-ancestors {$ancestors}");
$response->headers->remove('X-Frame-Options');
return $response;
}
}

View file

@ -42,6 +42,9 @@ return Application::configure(basePath: dirname(__DIR__))
// global, weil 404 vor dem Routing entsteht
$middleware->append(ReportErrorSignals::class);
// Inhalte-Editor im Portal zeigt die Website im iframe: frame-ancestors statt SAMEORIGIN
$middleware->append(\App\Http\Middleware\AllowPortalFraming::class);
// BasicAuth ganz am Ende, nach Session-Middleware
if (env('BASIC_AUTH_ENABLED', true)) {
$middleware->append(\App\Http\Middleware\BasicAuthMiddleware::class);

View file

@ -4,7 +4,10 @@ use Flux\Flux;
use FluxCms\Core\Models\CmsMedia;
use FluxCms\Core\Services\MediaConversionService;
use Illuminate\Support\Facades\Storage;
use function Livewire\Volt\{layout, title, state, computed, on};
use Livewire\WithPagination;
use function Livewire\Volt\{layout, title, state, computed, on, updated, uses};
uses([WithPagination::class]);
layout('components.layouts.app');
title('Medienbibliothek');
@ -40,6 +43,12 @@ $media = computed(
->paginate(48),
);
updated([
'search' => fn () => $this->resetPage(),
'filterType' => fn () => $this->resetPage(),
'filterCollection' => fn () => $this->resetPage(),
]);
$collections = computed(fn () => CmsMedia::query()->whereNotNull('collection')->where('collection', '!=', '')->distinct()->pluck('collection')->sort()->values()->toArray());
$profiles = computed(fn () => config('flux-cms.media.profiles', []));

View file

@ -1,6 +1,9 @@
<?php
use function Livewire\Volt\{state, mount, computed};
use Livewire\WithPagination;
use function Livewire\Volt\{state, mount, computed, updated, uses};
uses([WithPagination::class]);
state([
'hubId' => null,
@ -23,6 +26,8 @@ state([
'csvFile' => null,
]);
updated(['zipSearch' => fn () => $this->resetPage()]);
mount(function ($hubId = null) {
if ($hubId) {
$hub = \App\Models\Hub::findOrFail($hubId);

View file

@ -0,0 +1,37 @@
<?php
use App\Models\User;
use FluxCms\Core\Models\CmsMedia;
use Livewire\Volt\Volt;
/**
* Hotfixes 09.10.2026: Website im Vorschau-iframe des Portals, Seitennavigation der Medienbibliothek.
*/
it('lets the portal frame the website but no other site', function () {
config()->set('domains.domain_portal_url', 'https://portal.b2in.eu');
$this->get('/?cms-preview=0')
->assertSuccessful()
->assertHeader('Content-Security-Policy', "frame-ancestors 'self' https://portal.b2in.eu")
->assertHeaderMissing('X-Frame-Options');
});
it('keeps the media library pagination on the page after a livewire request', function () {
$this->actingAs(User::factory()->create());
foreach (range(1, 50) as $i) {
CmsMedia::create([
'filename' => "bild-{$i}.jpg", 'disk' => 'public', 'path' => "cms/media/originals/{$i}.jpg",
'type' => 'image', 'mime_type' => 'image/jpeg', 'file_size' => 1, 'conversions' => [],
]);
}
$component = Volt::test('admin.cms.media-index')
->call('startEdit', CmsMedia::query()->first()->id)
->call('gotoPage', 2)
->assertSet('paginators.page', 2);
expect($component->html())->not->toContain('livewire/update?page=');
$component->set('search', 'bild-1')->assertSet('paginators.page', 1);
});