fix: TB dry runs hide uneditable live templates, off-switch despite draft (rulings Q20, Q21)

- ThemeBuilderAccess::editableLiveTemplates() shared by tb-list and the tb-assign/tb-activate dry runs;
  uneditable live templates only as a count (hidden_overlaps, hidden_live)
- tb-activate enabled:false works while the Theme Builder has a draft, with a warning
- possible_overlaps skips identical conditions; reattach records the previous enabled state;
  exclude_from must be a list; tb-activate live from the read-back

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 20:55:53 +02:00
co-authored by Claude Opus 5.5
parent 64fc439cb2
commit a31d4340f9
11 changed files with 355 additions and 37 deletions
@@ -164,8 +164,11 @@ final class Template
}
$conditions = [];
foreach ($other->useOn as $theirs) {
if (in_array($theirs, $useOn, true)) {
continue; // identical: overlaps()
}
foreach ($useOn as $ours) {
if ($theirs !== $ours && self::mayOverlap($theirs, $ours)) {
if (self::mayOverlap($theirs, $ours)) {
$conditions[] = $theirs;
break;
}
@@ -179,6 +182,38 @@ final class Template
return $overlaps;
}
/**
* The overlap details of a dry run (tb-assign, tb-activate) for template $exceptId with conditions $useOn:
* identical and possible overlaps with the live templates the user may edit, and only the number of
* overlapping live templates the user may not edit (ruling Q21, Q8/B11).
*
* @param array{templates: list<Template>, hidden: list<Template>} $live ThemeBuilderAccess::editableLiveTemplates()
* @param list<string> $useOn
* @return array{overlaps: list<array{id: int, title: string, conditions: list<string>}>,
* possible_overlaps: list<array{id: int, title: string, conditions: list<string>}>, hidden_overlaps: int}
*/
public static function overlapDetails(array $live, int $exceptId, array $useOn): array
{
$hidden = array_merge(
self::overlaps($live['hidden'], $exceptId, $useOn),
self::possibleOverlaps($live['hidden'], $exceptId, $useOn)
);
return [
'overlaps' => self::overlaps($live['templates'], $exceptId, $useOn),
'possible_overlaps' => self::possibleOverlaps($live['templates'], $exceptId, $useOn),
'hidden_overlaps' => count(array_unique(array_column($hidden, 'id'))),
];
}
/** The sentence for hidden_overlaps > 0 (never names them). */
public static function hiddenOverlapsNote(int $count): string
{
return $count > 0
? sprintf(' Weitere aktive Vorlagen ohne Berechtigung, die sich überschneiden können: %d.', $count)
: '';
}
private static function mayOverlap(string $a, string $b): bool
{
foreach (self::scopesOf($a) as $x) {