@@ -65,10 +65,8 @@ public function process(
6565 name: 'ORGANIZATION_NAME ' ,
6666 resolver: fn (PlaceholderContext $ ctx ) => $ this ->config ->getValue (ConfigOption::OrganisationName) ?? ''
6767 );
68-
69- foreach ($ this ->placeholderResolvers as $ placeholderResolver ) {
70- $ resolver ->register ($ placeholderResolver ->name (), $ placeholderResolver );
71- }
68+
69+ $ this ->registerNestedResolvers ($ resolver );
7270
7371 foreach ($ this ->patternResolvers as $ patternResolver ) {
7472 $ resolver ->registerPattern ($ patternResolver ->pattern (), $ patternResolver );
@@ -136,4 +134,57 @@ private function registerAttributeResolvers(
136134 );
137135 }
138136 }
137+
138+ private function maskFooterPlaceholders (string $ value , array &$ placeholderMap ): string
139+ {
140+ $ placeholderMap = [];
141+ $ index = 0 ;
142+
143+ return preg_replace_callback (
144+ '/\[FOOTER(?:%%[^\]]+)?\]/i ' ,
145+ function (array $ matches ) use (&$ placeholderMap , &$ index ): string {
146+ $ token = sprintf ('__PHPLIST_FOOTER_TOKEN_%d__ ' , $ index ++);
147+ $ placeholderMap [$ token ] = $ matches [0 ];
148+
149+ return $ token ;
150+ },
151+ $ value
152+ ) ?? $ value ;
153+ }
154+
155+ /** @param array<string, string> $placeholderMap */
156+ private function restoreFooterPlaceholders (string $ value , array $ placeholderMap ): string
157+ {
158+ if ($ placeholderMap === []) {
159+ return $ value ;
160+ }
161+
162+ return strtr ($ value , $ placeholderMap );
163+ }
164+
165+ private function registerNestedResolvers (PlaceholderResolver $ resolver ): void
166+ {
167+ foreach ($ this ->placeholderResolvers as $ placeholderResolver ) {
168+ if (strtoupper ($ placeholderResolver ->name ()) !== 'FOOTER ' ) {
169+ $ resolver ->register ($ placeholderResolver ->name (), $ placeholderResolver );
170+ continue ;
171+ }
172+
173+ $ resolver ->register (
174+ $ placeholderResolver ->name (),
175+ function (PlaceholderContext $ ctx ) use ($ placeholderResolver , $ resolver ): string {
176+ $ footer = (string ) $ placeholderResolver ($ ctx );
177+ if (!str_contains ($ footer , '[ ' )) {
178+ return $ footer ;
179+ }
180+
181+ $ placeholderMap = [];
182+ $ maskedFooter = $ this ->maskFooterPlaceholders ($ footer , $ placeholderMap );
183+ $ resolvedFooter = $ resolver ->resolve ($ maskedFooter , $ ctx );
184+
185+ return $ this ->restoreFooterPlaceholders ($ resolvedFooter , $ placeholderMap );
186+ }
187+ );
188+ }
189+ }
139190}
0 commit comments