From 559d7ade56e3e7fe1f5488c3907c6fb4a1e4c85d Mon Sep 17 00:00:00 2001 From: Mark Hamstra Date: Sat, 3 Dec 2022 19:48:14 +0100 Subject: [PATCH] Fix non-existent snippet tags being broken up by an @ tag and work on adding tests for property set tags --- _build/test/Tests/Model/modParserTest.php | 168 ++++++++++++++++++++++ core/src/Revolution/modElement.php | 21 ++- core/src/Revolution/modTag.php | 20 +-- 3 files changed, 183 insertions(+), 26 deletions(-) diff --git a/_build/test/Tests/Model/modParserTest.php b/_build/test/Tests/Model/modParserTest.php index 278c8b828f1..b5ca433fe76 100644 --- a/_build/test/Tests/Model/modParserTest.php +++ b/_build/test/Tests/Model/modParserTest.php @@ -12,6 +12,9 @@ namespace MODX\Revolution\Tests\Model; +use MODX\Revolution\modElementPropertySet; +use MODX\Revolution\modPropertySet; +use MODX\Revolution\modSnippet; use MODX\Revolution\modX; use MODX\Revolution\MODxTestCase; use MODX\Revolution\MODxTestHarness; @@ -646,7 +649,172 @@ public function providerProcessElementTags() { 'depth' => 0 ] ], + // #16318 parsing tags with @ in the value causes it to break the tag + [ + [ + 'processed' => 1, + 'content' => "aaa +[[nonExistentSnippet? &x=`bbb@ccc`]] +ddd +eee" + ], + "[[+empty_content:empty=`aaa +[[nonExistentSnippet? &x=`bbb@ccc`]] +ddd +`]]eee", + [ + 'parentTag' => '', + 'processUncacheable' => true, + 'removeUnprocessed' => false, + 'prefix' => '[[', + 'suffix' => ']]', + 'tokens' => [], + 'depth' => 0 + ] + ], + ]; + } + + /** + * @dataProvider providerPropertySetCall + * @param $content + * @param $expected + * @param $propertySet + * @param $params + * @return void + */ + public function testPropertySetCall($content, $expected, $propertySet, $params) + { + /** @var modPropertySet $set */ + $set = $this->modx->newObject(modPropertySet::class); + $set->set('name', 'propset_' . bin2hex(random_bytes(4))); + $set->setProperties($propertySet); + self::assertTrue($set->save()); + + /** @var modSnippet $set */ + $snippet = $this->modx->newObject(modSnippet::class); + $snippet->set('name', 'snippet_' . bin2hex(random_bytes(4))); + $snippet->set('content', 'save()); + + $join = $this->modx->newObject(modElementPropertySet::class); + $join->fromArray([ + 'element' => $snippet->get('id'), + 'element_class' => $snippet->_class, + 'property_set' => $set->get('id'), + ], '', true); + $join->save(); + self::assertTrue($join->save()); + + $content = str_replace('propSetName', $set->get('name'), $content); + $content = str_replace('snippetName', $snippet->get('name'), $content); + + $c = $content; + + $this->modx->parser->processElementTags( + $params['parentTag'], + $content, + $params['processUncacheable'], + $params['removeUnprocessed'], + $params['prefix'], + $params['suffix'], + $params['tokens'], + $params['depth'] + ); + + $set->remove(); + $snippet->remove(); + $join->remove(); + + $this->assertEquals($expected, $content, "Did not get expected results from parsing {$c}."); + } + + public function providerPropertySetCall() + { + // In this test, snippetName and propSetName are replaced with a random string + // for each run + return [ + [ + '[[snippetName? &prop=`123`]]', + '123', + [], + [ + 'parentTag' => '', + 'processUncacheable' => true, + 'removeUnprocessed' => false, + 'prefix' => '[[', + 'suffix' => ']]', + 'tokens' => [], + 'depth' => 10 + ] + ], + [ + '[[snippetName@propSetName]]', + '123', + [ + 'prop' => '123', + ], + [ + 'parentTag' => '', + 'processUncacheable' => true, + 'removeUnprocessed' => false, + 'prefix' => '[[', + 'suffix' => ']]', + 'tokens' => [], + 'depth' => 10 + ] + ], + [ + '[[snippetName@propSetName? &otherProp=`foo`]]', + '789', + [ + 'prop' => '789', + ], + [ + 'parentTag' => '', + 'processUncacheable' => true, + 'removeUnprocessed' => false, + 'prefix' => '[[', + 'suffix' => ']]', + 'tokens' => [], + 'depth' => 10 + ] + ], + [ + '[[snippetName@propSetName? &prop=`123`]]', + '123', + [ + 'prop' => '456', // needs to be ignored because &prop is specified as override + ], + [ + 'parentTag' => '', + 'processUncacheable' => true, + 'removeUnprocessed' => false, + 'prefix' => '[[', + 'suffix' => ']]', + 'tokens' => [], + 'depth' => 10 + ] + ], + [ + 'This is a [[snippetName@propSetName:default=`default value`]]', + 'This is a default value', + [ + 'otherProp' => 'not this one', // needs to be ignored because &prop is specified as override + ], + [ + 'parentTag' => '', + 'processUncacheable' => true, + 'removeUnprocessed' => false, + 'prefix' => '[[', + 'suffix' => ']]', + 'tokens' => [], + 'depth' => 10 + ] + ], ]; + } /** diff --git a/core/src/Revolution/modElement.php b/core/src/Revolution/modElement.php index 55d9a3b62c6..62c85d8a956 100644 --- a/core/src/Revolution/modElement.php +++ b/core/src/Revolution/modElement.php @@ -726,19 +726,14 @@ public function getPropertySet($setName = null) { $propertySet = null; $name = $this->get('name'); - if (strpos($name, '@') !== false) { - $psName = ''; - $split = xPDO:: escSplit('@', $name); - if ($split && isset($split[1])) { - $name = $split[0]; - $psName = $split[1]; - $filters = xPDO:: escSplit(':', $setName); - if ($filters && isset($filters[1]) && !empty($filters[1])) { - $psName = $filters[0]; - $name .= ':' . $filters[1]; - } - $this->set('name', $name); - } + + $nameSplit = explode(':', $name); + $tagName = array_shift($nameSplit); + $remainingTag = implode(':', $nameSplit); + if (strpos($tagName, '@') !== false) { + $split = xPDO:: escSplit('@', $tagName); + $psName = $split[1]; + $this->set('name', $split[0] . $remainingTag); if (!empty($psName)) { $psObj = $this->xpdo->getObjectGraph(modPropertySet::class, '{"Elements":{}}', [ 'Elements.element' => $this->id, diff --git a/core/src/Revolution/modTag.php b/core/src/Revolution/modTag.php index 6120511f557..edc67889d4f 100644 --- a/core/src/Revolution/modTag.php +++ b/core/src/Revolution/modTag.php @@ -511,19 +511,13 @@ public function getPropertySet($setName = null) { $propertySet = null; $name = $this->get('name'); - if (strpos($name, '@') !== false) { - $psName = ''; - $split = xPDO:: escSplit('@', $name); - if ($split && isset($split[1])) { - $name = $split[0]; - $psName = $split[1]; - $filters = xPDO:: escSplit(':', $setName); - if ($filters && isset($filters[1]) && !empty($filters[1])) { - $psName = $filters[0]; - $name .= ':' . $filters[1]; - } - $this->set('name', $name); - } + $nameSplit = explode(':', $name); + $tagName = array_shift($nameSplit); + $remainingTag = implode(':', $nameSplit); + if (strpos($tagName, '@') !== false) { + $split = xPDO:: escSplit('@', $tagName); + $psName = $split[1]; + $this->set('name', $split[0] . $remainingTag); if (!empty($psName)) { $psObj = $this->modx->getObject(modPropertySet::class, ['name' => $psName]); if ($psObj) {