'on', 'queryType' => 'post_types']; private const TERM_LOOP = ['enable' => 'on', 'queryType' => 'post_taxonomies']; protected function setUp(): void { parent::setUp(); $this->original = self::page(); $this->post->post_content = $this->original; $registry = self::registry(); $this->writer = DiviWriterFixture::writer( $this->saved, registry: new DynamicContentMap(static fn (int $postId): array => $registry) ); } /** DiviFixtures' registry plus the loop options these tests name (Divi 5.15.1 groups and fields). */ private static function registry(): array { $field = static fn (string $type): array => ['label' => 'x', 'type' => $type, 'default' => '']; $common = ['before' => $field('text'), 'after' => $field('text'), 'loop_position' => $field('text')]; $option = static fn (string $id, string $group, array $fields): array => [ 'id' => $id, 'label' => $id, 'type' => 'text', 'custom' => false, 'group' => $group, 'fields' => $fields, ]; return DiviFixtures::dynamicContentRegistry() + [ 'loop_post_date' => $option('loop_post_date', 'Loop', $common + [ 'date_format' => $field('select'), 'custom_date_format' => $field('text'), ]), 'loop_term_name' => $option('loop_term_name', 'Loop Terms', $common), ]; } /** @param array $attrs */ private static function block(string $name, array $attrs, string $inner = ''): string { $json = (string) json_encode($attrs + ['builderVersion' => '5.2.1'], JSON_UNESCAPED_UNICODE); return sprintf('%s', $name, $json, $inner, $name); } /** @param array $loop */ private static function loop(array $loop): array { return ['module' => ['advanced' => ['loop' => ['desktop' => ['value' => $loop]]]]]; } private static function text(string $value): string { return self::block('text', ['content' => ['innerContent' => ['desktop' => ['value' => $value]]]]); } private static function page(): string { $title = VariableRef::serialize('content', 'loop_post_title', ['before' => '', 'after' => '']); $term = VariableRef::serialize('content', 'loop_term_name', ['before' => '', 'after' => '']); $own = self::block('text', self::loop(self::TERM_LOOP) + [ 'content' => ['innerContent' => ['desktop' => ['value' => $term]]], ]); return '' . self::block('section', [], self::block('row', [], implode('', [ self::block('column', self::loop(self::POST_LOOP), self::text($title) . self::text('

Fest

') . $own), self::block('column', self::loop(self::TERM_LOOP), self::text('

Term

')), self::block('column', [], self::text('

Ohne

')), ]))) . ''; } /** @param array $input */ private function insertInto(string $parent, array $input): mixed { return $this->insert()->execute( ['post_id' => 7, 'tree_rev' => $this->currentRev(), 'parent' => $parent] + $input ); } /** @param list> $changes */ private function changeAll(array $changes): mixed { return $this->update()->execute(['post_id' => 7, 'tree_rev' => $this->currentRev(), 'changes' => $changes]); } /** @param array $input */ private function moveNode(array $input): mixed { return $this->move()->execute(['post_id' => 7, 'tree_rev' => $this->currentRev()] + $input); } /** tree_rev of what the page holds now (the saver puts every saved content on the post). */ private function currentRev(): string { return Rev::of((string) $this->post->post_content); } private static function problems(mixed $error): string { self::assertInstanceOf(WP_Error::class, $error); return implode(' | ', self::errorFields($error)); } public function testInsertUsesTheLoopOfTheTargetParent(): void { $result = $this->insertInto('s0.r0.c0', ['nodes' => [ ['type' => 'text', 'content' => '{{loop:post_date}}'], ['type' => 'text', 'content' => '{{loop:post_title}}'], ]]); self::assertTrue($result['ok'], (string) json_encode($result)); $date = VariableRef::serialize('content', 'loop_post_date', ['before' => '', 'after' => '']); self::assertStringContainsString(str_replace('"', '\\' . 'u0022', $date), $this->saved[0]); $term = $this->insertInto('s0.r0.c1', ['nodes' => [['type' => 'text', 'content' => '{{loop:term_name}}']]]); self::assertTrue($term['ok'], (string) json_encode($term)); } public function testInsertRefusesLoopContentOutsideALoopOrOfAnotherKind(): void { $outside = $this->insertInto('s0.r0.c2', ['nodes' => [['type' => 'text', 'content' => '{{loop:post_date}}']]]); self::assertStringContainsString('braucht eine Divi-Schleife', self::problems($outside)); $term = $this->insertInto('s0.r0.c1', ['nodes' => [['type' => 'text', 'content' => '{{loop:post_date}}']]]); $wrongKind = self::problems($term); self::assertStringContainsString('passt nicht zur Schleife dieses Elements (Typ: Terme)', $wrongKind); $topLevel = $this->insert()->execute([ 'post_id' => 7, 'tree_rev' => $this->currentRev(), 'nodes' => [['type' => 'section', 'modules' => [['type' => 'text', 'content' => '{{loop:post_date}}']]]], ]); self::assertStringContainsString('braucht eine Divi-Schleife', self::problems($topLevel)); self::assertSame([], $this->saved); } /** * Ruling X3, fail closed: a loop a new node of this call switches on counts only once it is saved – loop * content in the same call is refused even where the parent's loop would let it pass (a term loop inside the * post loop c0 would otherwise get post content: Divi reads the post with the term's ID). */ public function testANewLoopInTheSameCallIsRefusedWithLoopContent(): void { $ownLoop = ['module.advanced.loop' => ['desktop' => self::TERM_LOOP]]; foreach (['s0.r0.c0' => '{{loop:post_title}}', 's0.r0.c2' => '{{loop:term_name}}'] as $parent => $content) { $refused = $this->insertInto($parent, ['nodes' => [ ['type' => 'text', 'attrs' => $ownLoop, 'content' => $content], ]]); self::assertStringContainsString('erst nach dem Speichern', self::problems($refused), $parent); } self::assertSame([], $this->saved); $withoutLoopContent = $this->insertInto('s0.r0.c2', ['nodes' => [ ['type' => 'text', 'attrs' => $ownLoop, 'content' => '

Liste

'], ]]); self::assertTrue($withoutLoopContent['ok'], (string) json_encode($withoutLoopContent)); } }