Skip to content

Commit 853dae5

Browse files
committed
- (Lava) Updated Dynamic Shortcode processing to only enter a child scope if indicated by the newly-added Shortcode Scope Behavior. Also updated the Workflow Activate Lava block to no longer enter a child scope while preserving any provided merge field values that use reserved keys. Added unit and integration tests to support these changes.
1 parent e237181 commit 853dae5

File tree

11 files changed

+347
-196
lines changed

11 files changed

+347
-196
lines changed

Rock.Lava.Fluid/FluidRenderContext.cs

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ public class FluidRenderContext : LavaRenderContextBase
3232
private TemplateContext _context;
3333
private const string _InternalFieldKeyPrefix = "$_";
3434
private const string _InternalFieldKeyEnabledCommands = "EnabledCommands";
35-
private const string _InternalFieldKeyIsChildScope = "_isChildScope";
3635

3736
#region Constructors
3837

@@ -180,9 +179,7 @@ public override void SetEnabledCommands( IEnumerable<string> commands )
180179
{
181180
var commandString = commands == null ? string.Empty : commands.JoinStrings( "," );
182181

183-
SetInternalField( _InternalFieldKeyEnabledCommands,
184-
commandString,
185-
LavaContextRelativeScopeSpecifier.Local );
182+
SetInternalField( _InternalFieldKeyEnabledCommands, commandString );
186183
}
187184

188185
/// <summary>
@@ -191,23 +188,7 @@ public override void SetEnabledCommands( IEnumerable<string> commands )
191188
/// </summary>
192189
public override void EnterChildScope()
193190
{
194-
// In Fluid, the ChildScope has the following behavior:
195-
// 1. GetValue(var) gets the value of "var" from the innermost scope in which the variable is currently defined.
196-
// 2. SetValue(var) sets the value of "var" for the innermost scope.
197-
// ... and the ForLoopScope has the following behavior:
198-
// 1. GetValue(var) gets the value of "var" from the innermost scope in which the variable is currently defined.
199-
// 2. SetValue(var) sets the value of "var" for the parent scope.
200-
// 3. SetOwnValue(var) sets the value of "var" for the innermost scope.
201-
202-
// In Lava, the child scope has the following behavior:
203-
// 1. GetValue(var) gets the value of "var" from the innermost scope in which the variable is currently defined.
204-
// 2. SetValue(var) sets the value of "var" in the parent scope if it is already defined;
205-
// if not, the variable is created in the child scope.
206-
207-
_context.EnterForLoopScope();
208-
209-
var localScope = _contextScopeInternalField.GetValue( _context ) as Scope;
210-
localScope.SetOwnValue( _InternalFieldKeyIsChildScope, FluidValue.Create( true, _context.Options ) );
191+
_context.EnterChildScope();
211192
}
212193

213194
/// <summary>
@@ -279,19 +260,9 @@ private void SetFieldPrivate( string key, object value, bool allowInternalFieldA
279260
value = new FluidRawValueProxy( value );
280261
}
281262

282-
if ( scope == LavaContextRelativeScopeSpecifier.Local )
263+
if ( scope == LavaContextRelativeScopeSpecifier.Default )
283264
{
284-
var isChildScope = localScope.Properties.Contains( _InternalFieldKeyIsChildScope );
285-
if ( isChildScope )
286-
{
287-
// We are using the Fluid "ForLoopScope", which would set the value in the parent scope by default.
288-
// Here we must specify that we want to set the value in the current child scope.
289-
localScope.SetOwnValue( key, FluidValue.Create( value, _context.Options ) );
290-
}
291-
else
292-
{
293-
_context.SetValue( key, value );
294-
}
265+
_context.SetValue( key, value );
295266
}
296267
else if ( scope == LavaContextRelativeScopeSpecifier.Parent )
297268
{

Rock.Lava.Shared/Core/DynamicShortcodeDefinition.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
using System;
1818
using System.Collections.Generic;
1919

20+
using Rock.Enums.Cms;
21+
2022
namespace Rock.Lava
2123
{
2224
/// <summary>
@@ -62,6 +64,11 @@ public DynamicShortcodeDefinition()
6264
/// </summary>
6365
public LavaShortcodeTypeSpecifier ElementType { get; set; }
6466

67+
/// <summary>
68+
/// The scope behavior for variables that are defined within this shortcode.
69+
/// </summary>
70+
public ShortcodeScopeBehavior ShortcodeScopeBehavior { get; set; }
71+
6572
#endregion
6673
}
6774
}

Rock.Lava.Shared/Core/ILavaRenderContext.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ public enum LavaContextRelativeScopeSpecifier
4141
/// <summary>
4242
/// The current local scope.
4343
/// </summary>
44+
[Obsolete( "[v19] This scope specifier is no longer used." )]
4445
Local = 3,
4546

46-
[Obsolete("The behavior of this setting is equivalent to Default. (v16.1).")]
47+
[Obsolete( "[v16.1] The behavior of this setting is equivalent to Default." )]
4748
Root = 2
4849
}
4950

Rock.Lava.Shared/Rock.Lava.Shared.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,8 @@
1313
</Compile>
1414
</ItemGroup>
1515

16+
<ItemGroup>
17+
<ProjectReference Include="..\Rock.Enums\Rock.Enums.csproj" />
18+
</ItemGroup>
19+
1620
</Project>

Rock.Lava/Core/Shortcodes/DynamicShortcode.cs

Lines changed: 9 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ public override void OnRender( ILavaRenderContext context, TextWriter result )
192192
// Parameters declared on child elements can be referenced in the shortcode template as <childElementName>.<paramName>.
193193

194194
string residualMarkup = string.Empty;
195-
var keysToCleanup = new List<string>();
196195

197196
if ( parms["processchilditems"].ToString().AsBoolean() )
198197
{
@@ -210,7 +209,6 @@ public override void OnRender( ILavaRenderContext context, TextWriter result )
210209
// Add the collections of child to the set of parameters that will be passed to the shortcode template.
211210
foreach ( var item in childElements )
212211
{
213-
keysToCleanup.Add( item.Key );
214212
parms.AddOrReplace( item.Key, item.Value );
215213
}
216214
}
@@ -277,15 +275,11 @@ public override void OnRender( ILavaRenderContext context, TextWriter result )
277275
}
278276
}
279277

280-
// This is a partial fix for issue #6470. Due to variable leakage between
281-
// shortcode executions, some shortcodes were failing if you used multiple
282-
// instances of the same shortcode on a page with different parameters.
283-
// In the future we will fix the EnterChildScope() implementation to
284-
// completely isolate the merge fields of the child scope from the parent.
285-
var savedMergeFields = SaveOriginalMergeFields( context, keysToCleanup );
286-
287-
// Render the shortcode template in a child scope that includes the shortcode parameters.
288-
context.EnterChildScope();
278+
var isIsolatedScope = _shortcode.ShortcodeScopeBehavior == Enums.Cms.ShortcodeScopeBehavior.Isolated;
279+
if ( isIsolatedScope )
280+
{
281+
context.EnterChildScope();
282+
}
289283

290284
LavaRenderResult results;
291285
try
@@ -312,11 +306,12 @@ public override void OnRender( ILavaRenderContext context, TextWriter result )
312306
}
313307
finally
314308
{
315-
context.ExitChildScope();
309+
if ( isIsolatedScope )
310+
{
311+
context.ExitChildScope();
312+
}
316313
}
317314

318-
RestoreOriginalMergeFields( context, savedMergeFields );
319-
320315
// Reset the original parameters in the context
321316
context.SetMergeField( "blockContent", originalBlockContent );
322317
context.SetMergeField( "RecursionDepth", originalRecursionDepth );
@@ -513,40 +508,5 @@ private void AssertShortcodeIsInitialized()
513508
throw new Exception( $"Shortcode configuration error. \"{_tagName}\" is not initialized." );
514509
}
515510
}
516-
517-
/// <summary>
518-
/// Saves the current merge field values for the specified keys.
519-
/// </summary>
520-
/// <param name="context">The lava context to get the values from.</param>
521-
/// <param name="keys">The keys whose values will be returned.</param>
522-
/// <returns>A dictionary of keys and values that can be later restored.</returns>
523-
private Dictionary<string, object> SaveOriginalMergeFields( ILavaRenderContext context, IEnumerable<string> keys )
524-
{
525-
var saved = new Dictionary<string, object>();
526-
527-
foreach ( var key in keys )
528-
{
529-
saved[key] = context.GetMergeField( key, null );
530-
}
531-
532-
return saved;
533-
}
534-
535-
/// <summary>
536-
/// Restores the saved merge fields to the specified context. This
537-
/// is used to make a best effor to reset the context to its original
538-
/// state after rendering a shortcode. The shortcode will have modified
539-
/// the merge fields in order to pass the parameters to the shortcode
540-
/// template. We attempt to undo that.
541-
/// </summary>
542-
/// <param name="context">The lava context to restore the values on.</param>
543-
/// <param name="savedMergeFields">The saved merge fields.</param>
544-
private void RestoreOriginalMergeFields( ILavaRenderContext context, Dictionary<string, object> savedMergeFields )
545-
{
546-
foreach ( var kvp in savedMergeFields )
547-
{
548-
context.SetMergeField( kvp.Key, kvp.Value );
549-
}
550-
}
551511
}
552512
}

Rock.Lava/Rock.Lava.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454

5555
<ItemGroup>
5656
<ProjectReference Include="..\Rock.Common\Rock.Common.csproj" />
57+
<ProjectReference Include="..\Rock.Enums\Rock.Enums.csproj" />
5758
<ProjectReference Include="..\Rock.Lava.Shared\Rock.Lava.Shared.csproj" />
5859
</ItemGroup>
5960

Rock.Tests.Integration/Core/Lava/Shortcodes/ShortcodeCodeTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public void Shortcode_WithMergeFieldAsParameter_CorrectlyResolvesParameters()
7373
}
7474

7575
[TestMethod]
76+
[TestCategory( "ShortcodeScopeBehavior" )]
7677
public void Shortcode_ReferencingItemFromParentScope_CorrectlyResolvesItem()
7778
{
7879
var shortcodeTemplate = @"
@@ -274,6 +275,56 @@ public void ScripturizeShortcode_WithInvalidLandingSite_ProducesErrorMessage()
274275

275276
#endregion
276277

278+
#region Workflow Activate
279+
280+
[TestMethod]
281+
[DataRow( "Workflow", "some workflow" )]
282+
[DataRow( "Activity", "some activity" )]
283+
public void WorkflowActivate_WithPreexistingReservedMergeField_SavesAndRestoresMergeFieldValue( string mergeFieldKey, string mergeFieldValue )
284+
{
285+
var input = $@"
286+
{{% workflowactivate workflowtype:'51FE9641-FB8F-41BF-B09E-235900C3E53E' %}}
287+
{{% endworkflowactivate %}}
288+
Restored Value: {{{{{mergeFieldKey}}}}}";
289+
290+
var expectedOutput = $"Restored Value: {mergeFieldValue}";
291+
292+
TestHelper.ExecuteForActiveEngines( ( engine ) =>
293+
{
294+
var options = new LavaTestRenderOptions()
295+
.WithContextVariable( mergeFieldKey, mergeFieldValue )
296+
.WithEnabledCommands( "workflowactivate" );
297+
298+
var output = TestHelper.GetTemplateOutput( engine, input, options );
299+
300+
Assert.Contains( expectedOutput, output, $"Reserved merge field with key '{mergeFieldKey}' was not restored." );
301+
} );
302+
}
303+
304+
[TestMethod]
305+
public void WorkflowActivate_WithPreexistingErrorMergeField_SavesAndRestoresMergeFieldValue()
306+
{
307+
var input = @"
308+
{% workflowactivate workflowtype:'some-invalid-workflow-type-guid' %}
309+
{% endworkflowactivate %}
310+
Restored Value: {{Error}}";
311+
312+
var expectedOutput = $"Restored Value: some error";
313+
314+
TestHelper.ExecuteForActiveEngines( ( engine ) =>
315+
{
316+
var options = new LavaTestRenderOptions()
317+
.WithContextVariable( "Error", "some error" )
318+
.WithEnabledCommands( "workflowactivate" );
319+
320+
var output = TestHelper.GetTemplateOutput( engine, input, options );
321+
322+
Assert.Contains( expectedOutput, output, $"Reserved merge field with key 'Error' was not restored." );
323+
} );
324+
}
325+
326+
#endregion
327+
277328
/// <summary>
278329
/// Verify that an invalid shortcode name correctly throws a shortcode parsing error when embedded in an if/endif block.
279330
/// </summary>

Rock.Tests.Integration/Issues/Issues_v16.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ Did you see those comments ^^^
172172
}
173173

174174
[TestMethod]
175+
[TestCategory( "ShortcodeScopeBehavior" )]
175176
public void Issue5102_VariableScopingInWorkflowActivateTag()
176177
{
177178
/* The WorkflowActivate tag does not allow persistent changes to variables declared outside the block in Fluid.

0 commit comments

Comments
 (0)