fix: relation fields require a readable target and hide its post type

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 01:24:51 +02:00
co-authored by Claude Opus 5.5
parent 106a099483
commit d67a1d4fb3
2 changed files with 29 additions and 9 deletions
@@ -137,28 +137,33 @@ final class FieldValidator
: sprintf('Erlaubte Werte: %s.', implode(', ', $allowed));
}
/**
* The target must exist and be readable for the current user; an unreadable target gets
* the same answer as a missing one and its actual post type is never named (no probing).
*/
private function checkRelation(FieldDefinition $field, mixed $value): ?string
{
if (! is_int($value) || $value <= 0) {
return 'ID (positive Ganzzahl) erwartet.';
}
$actual = get_post_type($value);
$readable = $actual !== false && current_user_can('read_post', $value);
$objectType = $field->related['object_type'] ?? '';
if ($objectType === 'media') {
return get_post_type($value) === 'attachment'
return $readable && $actual === 'attachment'
? null
: sprintf('%d ist kein Medium der Mediathek.', $value);
: sprintf('%d ist kein für dich lesbares Medium der Mediathek.', $value);
}
if ($objectType === 'post_type') {
$actual = get_post_type($value);
if ($actual === false) {
return sprintf('Eintrag %d existiert nicht.', $value);
if (! $readable) {
return sprintf('Eintrag %d existiert nicht oder ist für dich nicht lesbar.', $value);
}
$expected = $field->related['name'] ?? null;
if ($expected !== null && $actual !== $expected) {
return sprintf('Eintrag %d ist vom Typ %s, erwartet %s.', $value, $actual, $expected);
return sprintf('Eintrag %d ist kein Eintrag vom Typ %s.', $value, $expected);
}
}