diff --git a/system/View/Parser.php b/system/View/Parser.php index 6600b0178369..8a26cf825d3f 100644 --- a/system/View/Parser.php +++ b/system/View/Parser.php @@ -254,20 +254,39 @@ protected function parse(string $template, array $data = [], ?array $options = n // it can potentially modify any template between its tags. $template = $this->parsePlugins($template); - // loop over the data variables, replacing - // the content as we go. + // Parse stack for each parse type (Single and Pairs) + $replaceSingleStack = []; + $replacePairsStack = []; + + // loop over the data variables, saving regex and data + // for later replacement. foreach ($data as $key => $val) { $escape = true; if (is_array($val)) { - $escape = false; - $replace = $this->parsePair($key, $val, $template); + $escape = false; + $replacePairsStack[] = [ + 'replace' => $this->parsePair($key, $val, $template), + 'escape' => $escape, + ]; } else { - $replace = $this->parseSingle($key, (string) $val); + $replaceSingleStack[] = [ + 'replace' => $this->parseSingle($key, (string) $val), + 'escape' => $escape, + ]; } + } + + // Merge both stacks, pairs first + single stacks + // This allows for nested data with the same key to be replaced properly + $replace = array_merge($replacePairsStack, $replaceSingleStack); - foreach ($replace as $pattern => $content) { - $template = $this->replaceSingle($pattern, $content, $template, $escape); + // Loop over each replace array item which + // holds all the data to be replaced + foreach ($replace as $replaceItem) { + // Loop over the actual data to be replaced + foreach ($replaceItem['replace'] as $pattern => $content) { + $template = $this->replaceSingle($pattern, $content, $template, $replaceItem['escape']); } } diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index 9a9f1fd8d0ac..b3c99a2872ce 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -1061,4 +1061,44 @@ public function testChangeConditionalDelimitersWorkWithJavaScriptCode(): void EOL; $this->assertSame($expected, $this->parser->renderString($template)); } + + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/9245 + */ + public function testParseSameArrayKeyName(): void + { + $data = [ + 'type' => 'Super Powers', + 'powers' => [ + [ + 'type' => 'invisibility', + ], + ], + ]; + + $template = '{type} like {powers}{type}{/powers}'; + + $this->parser->setData($data); + $this->assertSame('Super Powers like invisibility', $this->parser->renderString($template)); + } + + public function testParseSameArrayKeyNameNested(): void + { + $data = [ + 'title' => 'My title', + 'similar' => [ + ['items' => [ + [ + 'title' => 'My similar title', + ], + ], + ], + ], + ]; + + $template = '{title} with similar item {similar}{items}{title}{/items}{/similar}'; + + $this->parser->setData($data); + $this->assertSame('My title with similar item My similar title', $this->parser->renderString($template)); + } } diff --git a/user_guide_src/source/changelogs/v4.5.6.rst b/user_guide_src/source/changelogs/v4.5.6.rst index b982f54b38f7..c2f396627442 100644 --- a/user_guide_src/source/changelogs/v4.5.6.rst +++ b/user_guide_src/source/changelogs/v4.5.6.rst @@ -33,6 +33,8 @@ Bugs Fixed - **Validation:** Fixed the `getValidated()` method that did not return valid data when validation rules used multiple asterisks. +- **Parser:** Fixed bug that caused equal key names to be replaced by the key name defined first. + See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed.