fix: Task 6 review round 1 - admin label overlap, heading default level, structure deny-list, row widths (S41)
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:
@@ -46,6 +46,8 @@ final class Composer
|
||||
|
||||
private const BACKGROUND = 'module.decoration.background';
|
||||
|
||||
private const DEFAULT_LEVEL = 'h2';
|
||||
|
||||
/** Advice of the S40 overlap error for new nodes. */
|
||||
private const ONE_FORM = 'für diesen Pfad nur Kurzform oder attrs angeben';
|
||||
|
||||
@@ -122,8 +124,13 @@ final class Composer
|
||||
}
|
||||
|
||||
$set = $change['set'] ?? null;
|
||||
$short = is_array($set) ? $this->shortPaths($block->shortName(), $set, 'set.') : [];
|
||||
if (array_key_exists('label', $change)) {
|
||||
// Ruling S41: the admin label is a short-form value too.
|
||||
$short['label'] = self::LABEL_PATH;
|
||||
}
|
||||
$this->overlaps(
|
||||
is_array($set) ? $this->shortPaths($block->shortName(), $set, 'set.') : [],
|
||||
$short,
|
||||
$change['attrs'] ?? null,
|
||||
$field,
|
||||
'bitte als zwei Änderungen senden'
|
||||
@@ -156,10 +163,7 @@ final class Composer
|
||||
foreach (array_keys($full) as $path) {
|
||||
$path = (string) $path;
|
||||
foreach ($short as $name => $shortPath) {
|
||||
if (
|
||||
$path === $shortPath || str_starts_with($path, $shortPath . '.')
|
||||
|| str_starts_with($shortPath, $path . '.')
|
||||
) {
|
||||
if (AttrMap::overlaps($path, $shortPath)) {
|
||||
$this->errors[$field . '.attrs.' . $path] = sprintf(
|
||||
'derselbe Attributpfad wird auch über %s geändert; %s',
|
||||
$name,
|
||||
@@ -171,6 +175,47 @@ final class Composer
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<array-key, mixed> $full "attrs" input. */
|
||||
private function touches(array $full, string $path): bool
|
||||
{
|
||||
foreach (array_keys($full) as $given) {
|
||||
if (AttrMap::overlaps((string) $given, $path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruling S41: the default level of a new heading stays when attrs replaced the desktop font
|
||||
* value it lives in (attrs replace a breakpoint value whole) – unless attrs name a level.
|
||||
*
|
||||
* @param array<string, mixed> $attrs
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function defaultLevel(string $short, array $attrs): array
|
||||
{
|
||||
$field = ShortformMap::fields($short)['level'] ?? null;
|
||||
if ($field === null || $field['sub'] === null) {
|
||||
return $attrs;
|
||||
}
|
||||
|
||||
$value = AttrMap::desktop($attrs, (string) $field['path']);
|
||||
if ($value !== null && ! is_array($value)) {
|
||||
return $attrs;
|
||||
}
|
||||
|
||||
if (is_array($value) && array_key_exists((string) $field['sub'], $value)) {
|
||||
return $attrs;
|
||||
}
|
||||
|
||||
$current = AttrMap::get($attrs, (string) $field['path'] . '.desktop.value');
|
||||
$merged = array_merge([(string) $field['sub'] => self::DEFAULT_LEVEL], is_array($current) ? $current : []);
|
||||
|
||||
return AttrMap::set($attrs, (string) $field['path'] . '.desktop.value', $merged);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paths of the short-form fields a client gave, keyed by their input name ($prefix . field).
|
||||
*
|
||||
@@ -346,8 +391,9 @@ final class Composer
|
||||
// of the same name (contact-field type, social network label) go through lit-divi-update "set".
|
||||
$values = array_intersect_key($spec, array_flip(array_diff($fields, self::COMMON)));
|
||||
$this->overlaps($this->shortPaths($short, $values, ''), $spec['attrs'] ?? null, $path, self::ONE_FORM);
|
||||
if ($name === 'divi/heading' && ! array_key_exists('level', $values)) {
|
||||
$values['level'] = 'h2';
|
||||
$defaultLevel = $name === 'divi/heading' && ! array_key_exists('level', $values);
|
||||
if ($defaultLevel) {
|
||||
$values['level'] = self::DEFAULT_LEVEL;
|
||||
}
|
||||
|
||||
$attrs = ['builderVersion' => $this->builderVersion];
|
||||
@@ -357,7 +403,12 @@ final class Composer
|
||||
$this->errors += $result['errors'];
|
||||
}
|
||||
|
||||
$block = Block::create($name, $this->common($name, $attrs, $spec, $path, $this->autoLabel($name, $context)));
|
||||
$attrs = $this->common($name, $attrs, $spec, $path, $this->autoLabel($name, $context));
|
||||
if ($defaultLevel) {
|
||||
$attrs = $this->defaultLevel($short, $attrs);
|
||||
}
|
||||
|
||||
$block = Block::create($name, $attrs);
|
||||
if ($hasItems) {
|
||||
$this->children($block, $spec['items'] ?? [], $path . '.items', $context);
|
||||
} elseif ($name === 'divi/group') {
|
||||
@@ -377,6 +428,14 @@ final class Composer
|
||||
*/
|
||||
private function common(string $name, array $attrs, array $spec, string $path, ?string $autoLabel): array
|
||||
{
|
||||
// Ruling S41: "label" and attrs on the admin label is an error; attrs alone replace the auto label.
|
||||
$full = $spec['attrs'] ?? null;
|
||||
if (array_key_exists('label', $spec) && $spec['label'] !== null) {
|
||||
$this->overlaps(['label' => self::LABEL_PATH], $full, $path, self::ONE_FORM);
|
||||
} elseif (is_array($full) && $this->touches($full, self::LABEL_PATH)) {
|
||||
$autoLabel = null;
|
||||
}
|
||||
|
||||
$attrs = $this->presetAndAttrs($name, $attrs, $spec, $path);
|
||||
$label = array_key_exists('label', $spec) && $spec['label'] !== null ? $spec['label'] : $autoLabel;
|
||||
if ($label === null || $label === '') {
|
||||
@@ -574,6 +633,31 @@ final class Composer
|
||||
return $widths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruling S41: the desktop flexType values of all columns of a row (in order), checked with
|
||||
* the rules of "columns" – valid Divi widths that fill the row exactly. Null when valid,
|
||||
* else the German problem.
|
||||
*
|
||||
* @param list<mixed> $flexTypes
|
||||
*/
|
||||
public function rowWidthsError(array $flexTypes): ?string
|
||||
{
|
||||
$saved = $this->errors;
|
||||
$this->errors = [];
|
||||
$valid = $flexTypes !== [] && array_filter(
|
||||
$flexTypes,
|
||||
static fn (mixed $width): bool => is_string($width) && preg_match('/^\d{1,2}_\d{1,2}$/D', $width) === 1
|
||||
) === $flexTypes;
|
||||
if ($valid) {
|
||||
$this->widths(implode(',', $flexTypes), 'w');
|
||||
}
|
||||
|
||||
$error = $valid ? ($this->errors['w'] ?? null) : 'Spaltenbreiten wie "8_24" erwartet';
|
||||
$this->errors = $saved;
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the fractions add up to exactly 1: numerators scaled to the least common
|
||||
* denominator (denominators are divisors of 24 or 5, 7, 9, 10, 11 – at most 27720).
|
||||
|
||||
Reference in New Issue
Block a user