fix: Insert entscheidet fail closed auf den komponierten Blöcken statt auf Client-Text; Update warnt bei Wechsel der Schleifen-Art unter unveränderten Kindern (X3)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
dadc1f3ec4
commit
5b52e18a31
@@ -163,7 +163,12 @@ final class DiviLoopWriteTest extends DiviWriteAbilityTestCase
|
||||
$refused = $this->insertInto($parent, ['nodes' => [
|
||||
['type' => 'text', 'attrs' => $ownLoop, 'content' => $content],
|
||||
]]);
|
||||
self::assertStringContainsString('erst nach dem Speichern', self::problems($refused), $parent);
|
||||
// c2 has no loop: the composer refuses the loop content itself (the call is refused either way).
|
||||
self::assertStringContainsString(
|
||||
$parent === 's0.r0.c0' ? 'erst nach dem Speichern' : 'braucht eine Divi-Schleife',
|
||||
self::problems($refused),
|
||||
$parent
|
||||
);
|
||||
}
|
||||
self::assertSame([], $this->saved);
|
||||
|
||||
@@ -258,4 +263,114 @@ final class DiviLoopWriteTest extends DiviWriteAbilityTestCase
|
||||
self::assertStringNotContainsString('Schleife', implode(' | ', $result['warnings']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix round 1 (critical): the fail-closed decision reads the composed blocks, not the client's text. The writers
|
||||
* strip control characters and trim values (S35), so these forms become canonical loop content.
|
||||
*
|
||||
* @return array<string, array{0: string}>
|
||||
*/
|
||||
public static function obfuscatedLoopContent(): array
|
||||
{
|
||||
$ref = VariableRef::serialize('content', 'loop_post_title', ['before' => '', 'after' => '']);
|
||||
|
||||
return [
|
||||
'control char inside the placeholder' => ["{\x01{loop:post_title}}"],
|
||||
'leading space before the full form' => [' ' . $ref],
|
||||
'leading control char before the full form' => ["\x01" . $ref],
|
||||
'plain placeholder' => ['{{loop:post_title}}'],
|
||||
];
|
||||
}
|
||||
|
||||
/** @dataProvider obfuscatedLoopContent */
|
||||
public function testANewLoopWithObfuscatedLoopContentIsRefused(string $content): void
|
||||
{
|
||||
$refused = $this->insertInto('s0.r0.c0', ['nodes' => [[
|
||||
'type' => 'text',
|
||||
'attrs' => ['module.advanced.loop' => ['desktop' => self::TERM_LOOP]],
|
||||
'content' => $content,
|
||||
]]]);
|
||||
|
||||
// Whichever check catches it (fail closed on the composed blocks, or the writer's own D9 refusal): refused.
|
||||
self::assertNotSame('', self::problems($refused));
|
||||
self::assertSame([], $this->saved);
|
||||
}
|
||||
|
||||
/** Loop content through attrs and in a sibling of the loop node: the whole call fails closed (ruling X3). */
|
||||
public function testLoopContentViaAttrsOrInASiblingOfANewLoopIsRefused(): void
|
||||
{
|
||||
$ownLoop = ['module.advanced.loop' => ['desktop' => self::TERM_LOOP]];
|
||||
$ref = VariableRef::serialize('content', 'loop_post_title', ['before' => '', 'after' => '']);
|
||||
|
||||
$viaAttrs = $this->insertInto('s0.r0.c0', ['nodes' => [[
|
||||
'type' => 'text',
|
||||
'attrs' => $ownLoop + ['content.innerContent' => ['desktop' => $ref]],
|
||||
]]]);
|
||||
self::assertStringContainsString('erst nach dem Speichern', self::problems($viaAttrs));
|
||||
|
||||
$sibling = $this->insertInto('s0.r0.c0', ['nodes' => [
|
||||
['type' => 'text', 'attrs' => $ownLoop, 'content' => '<p>Liste</p>'],
|
||||
['type' => 'text', 'content' => "{\x01{loop:post_title}}"],
|
||||
]]);
|
||||
self::assertStringContainsString('erst nach dem Speichern', self::problems($sibling));
|
||||
self::assertSame([], $this->saved);
|
||||
}
|
||||
|
||||
/** Loop content in a descendant of the new loop node (a section with a module) is refused, nothing saved. */
|
||||
public function testLoopContentBelowANewLoopContainerIsRefused(): void
|
||||
{
|
||||
$refused = $this->insert()->execute([
|
||||
'post_id' => 7,
|
||||
'tree_rev' => $this->currentRev(),
|
||||
'nodes' => [[
|
||||
'type' => 'section',
|
||||
'attrs' => ['module.advanced.loop' => ['desktop' => self::POST_LOOP]],
|
||||
'modules' => [['type' => 'text', 'content' => "{\x01{loop:post_title}}"]],
|
||||
]],
|
||||
]);
|
||||
|
||||
self::assertInstanceOf(WP_Error::class, $refused);
|
||||
self::assertSame([], $this->saved);
|
||||
}
|
||||
|
||||
/** The update path decides on the composed attrs too: a control-char placeholder gets the scope of its node. */
|
||||
public function testUpdateChecksObfuscatedLoopContentInTheNodesScope(): void
|
||||
{
|
||||
$ok = $this->changeAll([['id' => 's0.r0.c0.m1', 'set' => ['content' => "{\x01{loop:post_date}}"]]]);
|
||||
self::assertTrue($ok['ok'], (string) json_encode($ok));
|
||||
|
||||
$refused = $this->changeAll([['id' => 's0.r0.c2.m0', 'set' => ['content' => "{\x01{loop:post_date}}"]]]);
|
||||
self::assertStringContainsString('braucht eine Divi-Schleife', self::problems($refused));
|
||||
}
|
||||
|
||||
/** Fix round 1 (important): a loop whose kind changes under unchanged loop_* children warns, as move does. */
|
||||
public function testUpdateWarnsWhenTheLoopKindChangesUnderUnchangedChildren(): void
|
||||
{
|
||||
$result = $this->changeAll([
|
||||
['id' => 's0.r0.c0', 'attrs' => ['module.advanced.loop' => ['desktop' => self::TERM_LOOP]]],
|
||||
]);
|
||||
|
||||
self::assertTrue($result['ok'], (string) json_encode($result));
|
||||
$warnings = implode(' | ', $result['warnings']);
|
||||
self::assertStringContainsString('s0.r0.c0:', $warnings);
|
||||
self::assertStringContainsString('loop_post_title', $warnings);
|
||||
self::assertStringContainsString('Typ: Terme (vorher: Beiträge)', $warnings);
|
||||
self::assertStringNotContainsString('loop_term_name', $warnings, 'a descendant with its own loop keeps it');
|
||||
}
|
||||
|
||||
public function testUpdateSwitchingTheLoopOffWarnsAndAKeptKindDoesNot(): void
|
||||
{
|
||||
$same = $this->changeAll([
|
||||
['id' => 's0.r0.c0', 'attrs' => ['module.advanced.loop' => ['desktop' => [
|
||||
'enable' => 'on', 'queryType' => 'current_page',
|
||||
]]]],
|
||||
]);
|
||||
self::assertTrue($same['ok'], (string) json_encode($same));
|
||||
self::assertSame([], $same['warnings']);
|
||||
|
||||
$off = $this->changeAll([
|
||||
['id' => 's0.r0.c0', 'attrs' => ['module.advanced.loop' => ['desktop' => ['enable' => 'off']]]],
|
||||
]);
|
||||
self::assertStringContainsString('außerhalb jeder Divi-Schleife', implode(' | ', $off['warnings']));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user