Choose menu anchors, link targets in plugin settings and existing post terms

A menu entry picks its anchor from the linked page, link settings of plugins and own menu
links get page and PDF choice next to the address, and the post settings offer existing
categories and tags to click.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-22 09:56:07 +02:00
1 parent 3ea28a03fc
commit a976c146de
14 files changed
+306 -7

No files matched your search

+52
View File
@@ -706,6 +706,58 @@ final class EditorSecurityTest extends WebTestCase
self::assertSame(['umbau' => 'Umbau'], $choices['tags']);
}
public function testMenuAnchorsAndPostTermsAreOfferedForChoosing(): void
{
$media = new MediaLibrary($this->db, $this->path.'-media');
$pages = new PageRepository($this->db, new DocumentValidator(), new SiteSettings($this->db, $media));
$document = DemoContent::starter('Leistungen');
$document['schemaVersion'] = 21;
$document['blocks'][] = ['id' => 'kontakt-sektion', 'type' => 'section', 'anchor' => 'kontakt', 'children' => [
['id' => 'kontakt-titel', 'type' => 'heading', 'props' => ['text' => 'So erreichen Sie uns', 'level' => 2]],
]];
$id = $pages->createPage('leistungen', null, 'page', $document);
// Die Sprungziele einer Seite, beschriftet mit ihrer Überschrift - nur für Rollen, die
// Menüs bearbeiten.
$client = $this->login('editor');
$client->request('GET', '/admin/api/sprungziele/'.$id);
self::assertResponseStatusCodeSame(403);
$client = $this->login('admin', $client);
$client->request('GET', '/admin/api/sprungziele/'.$id);
self::assertResponseIsSuccessful();
self::assertSame([['anchor' => 'kontakt', 'label' => 'So erreichen Sie uns (#kontakt)']], json_decode($client->getResponse()->getContent(), true)['items']);
$client->request('GET', '/admin/api/sprungziele/gibt-es-nicht');
self::assertResponseStatusCodeSame(404);
// Vorhandene Kategorien und Schlagwörter stehen am Feld, damit man sie wählt statt neu tippt.
$post = $pages->createPage('beitrag', null, 'post', DemoContent::pages()['home']);
(new PostRepository($this->db, $media, new SiteSettings($this->db, $media)))->saveMetadata($post, [], ['Projekte'], ['Umbau'], $pages->findForEditor($post)['version']);
$crawler = $client->request('GET', '/admin/posts');
self::assertSame(['Projekte'], json_decode($crawler->filter('input[name="categories"]')->attr('data-term-input'), true));
self::assertSame(['Umbau'], json_decode($crawler->filter('input[name="tags"]')->attr('data-term-input'), true));
self::assertCount(1, $crawler->filter('script[src^="/assets/term-chips.js"]'));
}
public function testPluginSettingsForLinksAndImagesAreChosenNotTyped(): void
{
// Das Feld einer Plugin-Einstellung, direkt aus dem Makro der Plugin-Seite: ein Link
// bleibt ein Textfeld mit Knöpfen daneben, ein Bild wird das Bildfeld der Mediathek.
self::bootKernel();
$twig = static::getContainer()->get('twig');
$render = static fn (array $schema, string $value): string => $twig->createTemplate(
"{% import 'admin/plugins.html.twig' as f %}{{ f.feld('probe', 'ziel', schema, value) }}",
)->render(['schema' => $schema, 'value' => $value]);
$link = $render(['kind' => 'url', 'label' => 'Ziel', 'maxLength' => 200, 'help' => ''], '/kontakt');
self::assertStringContainsString('data-link-field', $link);
self::assertMatchesRegularExpression('#<input id="plugin-probe-ziel" name="settings\[ziel\]" value="/kontakt"[^>]*data-link-input#', $link);
$bild = $render(['kind' => 'media', 'label' => 'Bild', 'help' => ''], '');
self::assertStringContainsString('data-media-field', $bild);
self::assertStringContainsString('<input type="hidden" id="plugin-probe-ziel" name="settings[ziel]" value="">', $bild);
self::assertStringNotContainsString('Kennung aus der Mediathek', $bild);
}
public function testStylePresetsNeedAnEditorRoleACsrfTokenAndAValidStyle(): void
{
$client = $this->login('editor');