What is Legacy Lava:

"Legacy Lava" is when you use attributes as if they were a property.

*record skip*

"Whaaaat? What in the world does that mean?" you may ask. I'm glad you asked.

It is acceptable if you have lava that looks like this: {{ Person.PropertyName }}
It is no longer acceptable if you have lava like this: {{ Person.AttributeName }}

While properties can still be called as in the first line, attributes must now be called like this:
{{ Person | Attribute:'AttributeName' }} This new method uses an attribute filter instead.

"Properties... Attributes... Wait... What's the difference?"

 

A Quick Lingo Lesson

Let's learn some basic lingo to get started.

Entity: The word "entity" is used to describe the classification unit of different types of data in Rock. For instance, People, Groups, Financial Transactions, Locations and Pages are all entities in Rock. If you’re familiar with databases, entities are very similar to tables. In fact, most entities in Rock have an associated table in the database.

Property: Entities have Properties. If an Entity is a database table, the properties are the columns in that table that are storing data. Look at the "Admin Tools - Power Tools - Model Map" and you can find a complete list of every property an entity has. For example in the CRM section of the Model Map we see the Person Entity. If you click on Person on the left, you see FirstName, LastName, Email, PhotoId... and many others, which are properties of the Person Entity. This means they can be called by using lava such as {{ Person.FirstName }}.

Attribute: Everything stored in Rock can have attributes added to it. For example, we can add numerous attributes to a person according to what's important to your organization. If you wanted to track if a person had a pet zebra, you could add a "HasZebra" Person Attribute under the "Admin Tools - General Settings - Person Attributes" section. But since this is an attribute, you have to call it as {{ Person | Attribute:'HasZebra' }} now.

 

Why does having legacy lava matter?

Rock was changed to call attributes differently because it wasn't sustainable for the growth of a Rock install. As attributes are added for various reasons, over time, the code that loops through checking entity properties has to check through all the attributes each time as well. This can mean drastically slower performance. But if we can tell Rock to only loop through a smaller "dictionary" of code and we can call it to look for specific attributes as needed, it speeds things up considerably. So while this old method was supported in v5, it was changed in v6, and may be fully obsolete and ignored by v11. If you check your "Admin Tools - General Settings - Global Attributes" you will find an entry called "Lava Support Level" which has one of 3 values (depending on your version): Legacy, LegacyWithWarning, or NoLegacy.

Legacy: Operate like early versions of Rock. This is deprecated and removed in v10.
LegacyWithWarning: Operate like early versions of Rock, but put a warning in the "Admin Tools - System Settings - Exception List" alerting you to the existence of legacy lava that needs updated. This may be deprecated and removed in v11.
NoLegacy: Any legacy lava in the code will be ignored and not function properly and there will be no exceptions logged. Having this setting will result in faster site performance than the other two options.

 

How do I clean up legacy lava?

1) If you aren't yet on "LegacyWithWarning" you should change to that now to be alerted of where it is being encountered on your site. Go to "Admin Tools - General Settings - Global Attributes" and set the "Lava Support Level" to "LegacyWithWarning".

2) Check your exceptions in the "Admin Tools - System Settings - Exception List" for errors. They will be logged starting with "Warning: Legacy Lava Syntax Detected:" and then tell you what was logged. Most of them are probably in old (even core) workflows like the contact form. Here is an example of one on our site:

legacylava1.PNG

3) If you click the Exception in the Exception List, you will see a list of all occurrences of legacy lava being found on the site. This may not be everywhere it exists, but it is everywhere it has been loaded since logging was enabled. We're going to focus on this example still:

legacylava2.PNG

4) If you click on the Exception occurrence in the step above, you will see specifics about that event, including the page it was detected on. This is where we must go to fix this particular issue as it is HTML in a block causing our problem. If it were in a workflow or other area, you may need to visit that workflow setup, and not the page it was detected on, to fix the issue.

legacylava3.PNG

5) Fix the code. If your exception shows you it was on a certain page or in a certain workflow, find that code and edit it to change the legacy attribute reference into the correct format.

Our example above used a Person attribute of "Student20182019Status" but to keep it simple, let's look at this other example of legacy lava where a user has made "HasZebra" as an attribute on a person record:

	{% if Person.HasZebra != empty %}
		{{ Person.HasZebra }} 
	{% endif %}

This will throw an error as "Person.HasZebra" is legacy lava trying to call an attribute written as if it were a property. It would have to be written as {{ Person | Attribute:'HasZebra' }} to be valid. However, in this case, it is important to know you can never use filters inside of an IF statement and {{ Person | Attribute:'HasZebra' }} is filtering by the attribute of HasZebra. So you can't use {% if {{ Person | Attribute:'HasZebra' }} != empty %} as that won't work.

Instead, you would re-write it as such, assigning a variable named whatever you'd like to that attribute filter (I'd encourage a more descriptive name, but for our example let's use "butterbeer" for funsies), and then running your IF against that variable name:

	{% assign butterbeer = Person | Attribute:'HasZebra' %}
	{% if butterbeer != empty %}
		{{ butterbeer }}
	{% endif %}

6) Test your new lava code to ensure it all still works as desired! If so, congratulations on fixing your legacy lava! Now monitor your Exception Log regularly to fix other instances that pop up. Once you are no longer seeing legacy lava errors with new timestamps, feel free to flip the "Admin Tools - General Settings - Global Attributes" setting for "Lava Support Level" to "NoLegacy" and enjoy the extra site speed! Before you do this however, you may want to do a little more probing to ensure you don't have any extra use of that same legacy lava that just hasn't been stumbled on yet. Using the tool below, you can take some of the ones you did log, and search for them in other areas of the site.

 

A Handy Weapon

Chris Rea has written a handy SQL script that can help you hunt down legacy lava as well instead of just waiting for it to appear on your exception log. If you go to "Admin Tools - Power Tools - SQL Command" you can paste this script in and change the ".HasZebra" text to whatever legacy lava tag you found in your exception log and when you run it, you will be given a report of every page on your site that contains that specific legacy lava. You can then export that result into Excel for a better view of the results. It shows the Location, Entity ID, Location Name, Workflow Type ID, Attribute Name, Found Value, and even the line of code that was found! It's pretty incredible at helping track down where things need updated. To be clear, this SQL doesn't FIX it for you, and no changes are made to your data using it. It just outputs a list of results.

-- Production
Declare @filterValues varchar(max) = '.HasZebra'

--Declare @filterValues varchar(500) = null --'Value A,Value B,Value C,Value D,Value E'

Declare @includeWorkflowTypeIds varchar(500) = null --'86,87'
Declare @excludeWorkflowtypeIds varchar(500) = null --'55,57'

Select *
From (
	Select
		'Workflow' as [Location]
		, wt.Id as [EntityId]
		, wt.Name as [LocationName]
		, wt.Id as [WorkflowTypeId]
		, a.Name as [AttributeName]
		, y.FoundValue as [FoundValue]
		, a.DefaultValue as [Value]
	From [WorkflowType] wt
		Join [Attribute] a on a.EntityTypeQualifierValue = wt.Id
			and a.EntityTypeQualifierColumn = 'WorkflowTypeId'
		Join [EntityType] et on et.Id = a.EntityTypeId
			and et.FriendlyName = 'Workflow'
		Join (
			Select '%' + x.value + '%' as [LikeArgument], x.value as [FoundValue]
			From string_split( @filterValues, ',' ) x
		) y on a.DefaultValue like y.LikeArgument

	Union All

	Select
		'Activity' as [Location]
		, wact.Id as [EntityId]
		, wt.Name + ' > ' + wact.Name as [LocationName]
		, wt.Id as [WorkflowTypeId]
		, a.Name as [AttributeName]
		, y.FoundValue as [FoundValue]
		, a.DefaultValue as [Value]
	From [WorkflowActivityType] wact
		Join [WorkflowType] wt on wt.Id = wact.WorkflowTypeId
		Join [Attribute] a on a.EntityTypeQualifierValue = wact.Id
			and a.EntityTypeQualifierColumn = 'ActivityTypeId'
		Join [EntityType] et on et.Id = a.EntityTypeId
			and et.FriendlyName = 'Workflow Activity'
		Join (
			Select '%' + x.value + '%' as [LikeArgument], x.value as [FoundValue]
			From string_split( @filterValues, ',' ) x
		) y on a.DefaultValue like y.LikeArgument

	Union All

	Select
		'Action' as [Location]
		, wactiont.Id as [EntityId]
		, wt.Name + ' > ' + wact.Name + ' > ' + wactiont.Name as [LocationName]
		, wt.Id as [WorkflowTypeId]
		, a.Name as [AttributeName]
		, y.FoundValue as [FoundValue]
		, av.value as [Value]
	From [WorkflowActionType] wactiont
		Join [WorkflowActivityType] wact on wact.Id = wactiont.ActivityTypeId
		Join [WorkflowType] wt on wt.Id = wact.WorkflowTypeId
		Join [AttributeValue] av on av.EntityId = wactiont.Id
		Join [Attribute] a on a.Id = av.AttributeId
			and a.EntityTypeQualifierColumn = 'EntityTypeId'
		Join [EntityType] et on et.Id = a.EntityTypeId
			and et.FriendlyName = 'Workflow Action Type'
		Join (
			Select '%' + x.value + '%' as [LikeArgument], x.value as [FoundValue]
			From string_split( @filterValues, ',' ) x
		) y on av.Value like y.LikeArgument
	) x
Where (
		1 =
		Case
			When 
				x.WorkflowTypeId in (
					Select cast( x.Value as int )
					From string_split( @includeWorkflowTypeIds, ',' ) x
				) 
				Then 1
			When 
				@excludeWorkflowtypeIds is not null and
				x.WorkflowTypeId not in (
						Select cast( x.Value as int )
						From string_split( @excludeWorkflowtypeIds, ',' ) x
					)
				Then 1
			When @includeWorkflowTypeIds is null and @excludeWorkflowtypeIds is null
				Then 1
			Else 0
		End
	)
Order by 3 asc