fix(audit M1, L2): Meta-Schlüssel-Optionen gegen alle Schleifen der Vorfahrenkette prüfen; queryType wie sanitize_key normalisieren, terms/users als Term-/Benutzer-Schleife
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
52208764f0
commit
522fc8ff4d
@@ -119,6 +119,37 @@ final class LoopContextTest extends TestCase
|
||||
self::assertSame('repeater_team', $inner->queryType());
|
||||
}
|
||||
|
||||
/** Audit M1: a meta key option must fit EVERY enabled loop of the chain (an empty inner loop hands over to the outer). */
|
||||
public function testMetaKeyOptionsMustFitEveryLoopOfTheChain(): void
|
||||
{
|
||||
$posts = self::loop(['enable' => 'on', 'queryType' => 'post_types']);
|
||||
$terms = self::loop(['enable' => 'on', 'queryType' => 'post_taxonomies']);
|
||||
$repeater = self::loop(['enable' => 'on', 'queryType' => 'repeater_team']);
|
||||
$meta = 'loop_post_meta_key_manual_custom_field';
|
||||
|
||||
$postInTerm = LoopContext::fromChain([$terms, $posts]);
|
||||
self::assertSame([LoopScope::POST, LoopScope::TERM], $postInTerm->kinds());
|
||||
self::assertFalse($postInTerm->fits($meta));
|
||||
self::assertTrue($postInTerm->fits('loop_post_title'), 'other options keep the innermost rule');
|
||||
self::assertTrue(LoopContext::fromChain([$posts, $posts])->fits($meta));
|
||||
self::assertFalse(LoopContext::fromChain([$repeater, $posts])->fits($meta), 'unsupported outer loop');
|
||||
self::assertTrue(LoopContext::fromChain([self::loop([]), $posts])->fits($meta), 'a disabled loop is no loop');
|
||||
}
|
||||
|
||||
/** Audit L2: Divi sanitize_key()s queryType and knows the aliases terms/users. */
|
||||
public function testQueryTypeIsNormalisedLikeSanitizeKey(): void
|
||||
{
|
||||
$kind = static fn (string $q): ?string => LoopContext::ownScope(self::loop(['enable' => 'on', 'queryType' => $q]))
|
||||
?->kind();
|
||||
|
||||
self::assertSame(LoopScope::TERM, $kind('Post_Taxonomies'));
|
||||
self::assertSame(LoopScope::TERM, $kind(' post_taxonomies '));
|
||||
self::assertSame(LoopScope::TERM, $kind('terms'));
|
||||
self::assertSame(LoopScope::USER, $kind('users'));
|
||||
self::assertSame(LoopScope::POST, $kind("POST_TYPES\n"));
|
||||
self::assertNull($kind('repeater_team'));
|
||||
}
|
||||
|
||||
private static function page(): NodeIndex
|
||||
{
|
||||
$loop = static fn (string $queryType): string => '{"module":{"advanced":{"loop":{"desktop":{"value":'
|
||||
|
||||
@@ -19,6 +19,8 @@ final class LoopScopeTest extends TestCase
|
||||
'taxonomies' => ['post_taxonomies', LoopScope::TERM, 'Terme'],
|
||||
'user roles' => ['user_roles', LoopScope::USER, 'Benutzer'],
|
||||
'menus' => ['menus', LoopScope::MENU, 'Menü'],
|
||||
'terms alias (audit L2)' => ['terms', LoopScope::TERM, 'Terme'],
|
||||
'users alias (audit L2)' => ['users', LoopScope::USER, 'Benutzer'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -50,7 +52,7 @@ final class LoopScopeTest extends TestCase
|
||||
*/
|
||||
public function testOtherQueryTypesAreALoopWithoutKind(): void
|
||||
{
|
||||
foreach (['repeater_team', 'terms', 'users', '', 'Post_Types', 'post_types '] as $queryType) {
|
||||
foreach (['repeater_team', '', 'Post_Types', 'post_types '] as $queryType) {
|
||||
$scope = LoopScope::ofQueryType($queryType);
|
||||
|
||||
self::assertFalse($scope->active(), $queryType);
|
||||
|
||||
@@ -373,4 +373,70 @@ final class DiviLoopWriteTest extends DiviWriteAbilityTestCase
|
||||
]);
|
||||
self::assertStringContainsString('außerhalb jeder Divi-Schleife', implode(' | ', $off['warnings']));
|
||||
}
|
||||
|
||||
private function useChainPage(string $outerType): void
|
||||
{
|
||||
$meta = VariableRef::serialize('content', 'loop_post_meta_key_manual_custom_field', ['loop_meta_key' => 'telefon']);
|
||||
$loop = static fn (string $type): array => self::loop(['enable' => 'on', 'queryType' => $type]);
|
||||
$this->original = '<!-- wp:divi/placeholder -->' . self::block('section', $loop($outerType), self::block(
|
||||
'row',
|
||||
$loop('post_types'),
|
||||
self::block('column', [], self::text('<p>Fest</p>') . self::text($meta))
|
||||
)) . '<!-- /wp:divi/placeholder -->';
|
||||
$this->post->post_content = $this->original;
|
||||
}
|
||||
|
||||
/** Audit M1: a post loop inside a term loop may be empty; the term loop would then read post meta by term ID. */
|
||||
public function testPostMetaInAPostLoopInsideATermLoopIsRefusedOnInsertAndUpdate(): void
|
||||
{
|
||||
$this->useChainPage('post_taxonomies');
|
||||
$meta = VariableRef::serialize('content', 'loop_post_meta_key_manual_custom_field', ['loop_meta_key' => 'telefon']);
|
||||
|
||||
$insert = $this->insertInto('s0.r0.c0', ['nodes' => [['type' => 'text', 'content' => $meta]]]);
|
||||
self::assertStringContainsString('passt nicht zur Schleife', self::problems($insert));
|
||||
$update = $this->changeAll([['id' => 's0.r0.c0.m0', 'set' => ['content' => $meta]]]);
|
||||
self::assertStringContainsString('passt nicht zur Schleife', self::problems($update));
|
||||
self::assertSame([], $this->saved);
|
||||
}
|
||||
|
||||
public function testPostMetaInAPostLoopInsideAPostLoopIsAccepted(): void
|
||||
{
|
||||
$this->useChainPage('post_types');
|
||||
$meta = VariableRef::serialize('content', 'loop_post_meta_key_manual_custom_field', ['loop_meta_key' => 'telefon']);
|
||||
|
||||
$insert = $this->insertInto('s0.r0.c0', ['nodes' => [['type' => 'text', 'content' => $meta]]]);
|
||||
self::assertTrue($insert['ok'], (string) json_encode($insert));
|
||||
$update = $this->changeAll([['id' => 's0.r0.c0.m0', 'set' => ['content' => $meta]]]);
|
||||
self::assertTrue($update['ok'], (string) json_encode($update));
|
||||
}
|
||||
|
||||
/** Audit M1: the update warning follows the chain, not only the innermost loop. */
|
||||
public function testUpdateWarnsWhenAnOuterLoopTurnsIntoAnotherKindUnderMetaContent(): void
|
||||
{
|
||||
$this->useChainPage('post_types');
|
||||
|
||||
$result = $this->changeAll([
|
||||
['id' => 's0', 'attrs' => ['module.advanced.loop' => ['desktop' => self::TERM_LOOP]]],
|
||||
]);
|
||||
|
||||
self::assertTrue($result['ok'], (string) json_encode($result));
|
||||
self::assertStringContainsString('loop_post_meta_key_manual_custom_field', implode(' | ', $result['warnings']));
|
||||
}
|
||||
|
||||
/** Audit L2: "terms" is a term loop for Divi, an unsupported target "can show data of another object". */
|
||||
public function testUpdateWarningNamesTheRealKindOfAnAliasAndNeverCallsAnUnsupportedLoopEmpty(): void
|
||||
{
|
||||
$alias = $this->changeAll([
|
||||
['id' => 's0.r0.c0', 'attrs' => ['module.advanced.loop' => ['desktop' => ['enable' => 'on', 'queryType' => 'Terms']]]],
|
||||
]);
|
||||
self::assertStringContainsString('Typ: Terme (vorher: Beiträge)', implode(' | ', $alias['warnings']));
|
||||
|
||||
$this->post->post_content = $this->original;
|
||||
$other = $this->changeAll([
|
||||
['id' => 's0.r0.c0', 'attrs' => ['module.advanced.loop' => ['desktop' => ['enable' => 'on', 'queryType' => 'repeater_x']]]],
|
||||
]);
|
||||
$text = implode(' | ', $other['warnings']);
|
||||
self::assertStringContainsString('kann Daten eines anderen Objekts zeigen', $text);
|
||||
self::assertStringNotContainsString('zeigt sie dort leer', $text);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user