Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
package org.broadleafcommerce.common.web.dialect;

import org.broadleafcommerce.common.web.BroadleafRequestContext;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.element.AbstractElementTagProcessor;
Expand All @@ -29,6 +30,8 @@
import java.util.HashSet;
import java.util.Set;

import jakarta.servlet.http.HttpServletRequest;

/**
* @author apazzolini
*
Expand Down Expand Up @@ -75,6 +78,26 @@ protected void addToModel(IElementTagStructureHandler structureHandler, String k
structureHandler.setLocalVariable(key, value);
}
Comment on lines 78 to 79

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Other self-closing processors may also need the addToRequest pattern

Several other subclasses of AbstractModelVariableModifierProcessor still use addToModel (local variable scoping) and may also be used as self-closing elements whose values are consumed by sibling markup. For example:

  • ConfigVariableProcessor.java:60 sets resultVar via addToModel
  • BreadcrumbProcessor.java:66 sets resultVar via addToModel
  • DataDrivenEnumerationProcessor.java:91 sets enumValues via addToModel
  • RatingsProcessor.java:66,75 sets rating data via addToModel
  • RelatedProductProcessor.java:85-88 sets product data via addToModel

If any of these are used as self-closing elements (e.g., <blc:config ... />), the local variable will have no body to scope into and the value will be invisible to siblings. This is the same problem this PR aims to fix for NamedOrderProcessor and GoogleAnalyticsProcessor. Each processor's usage pattern in templates would need to be audited.

(Refers to lines 74-77)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid follow-up. This PR is deliberately scoped to the two processors whose javadoc explicitly documents self-closing + sibling usage:

  • NamedOrderProcessor: <blc:named_order orderVar="wishlist" .../> then <span th:text="${wishlist...}"/>
  • GoogleAnalyticsProcessor: <blc:googleanalytics/> then <script th:utext="${analytics}"/>

The base-class addToRequest() helper is now available for any others confirmed to need it. The remaining candidates split two ways:

  • ConfigVariableProcessor, BreadcrumbProcessor, DataDrivenEnumerationProcessor live in the common module, outside this task's broadleaf-framework-web scope.
  • RatingsProcessor, RelatedProductProcessor are in broadleaf-framework-web, but their javadocs don't document a self-closing+sibling pattern, and the prior merged fix intentionally kept them on the body-scoped removeTags() approach.

Confirming which are actually used as self-closing elements requires the demo-site templates, which aren't in this repo, so I'd recommend a dedicated template-usage audit as the follow-up rather than blindly switching them. Note addToRequest is strictly broader than a body-local (a request attribute is visible both inside the body and to siblings), but it persists for the whole request, so local-variable scoping remains preferable wherever a body actually consumes the value.


/**
* Adds a value to the current request so it is visible to the remainder of the page, including
* sibling elements. {@link #addToModel} contributes a Thymeleaf 3 local variable which is scoped
* to the host element's body; processors whose element is self-closing and whose value is consumed
* by following sibling markup must use this method instead so the value survives outside the body.
* Thymeleaf 3's web context resolves ${var} from the request attributes, so setting the attribute
* on the current request makes the value visible to the rest of the page. Outside of a web request
* there is no request scope, so the value is not added.
*
* @param key the key to add to the request
* @param value the value represented by the key
*/
protected void addToRequest(String key, Object value) {
BroadleafRequestContext brc = BroadleafRequestContext.getBroadleafRequestContext();
HttpServletRequest request = (brc != null) ? brc.getRequest() : null;
if (request != null) {
request.setAttribute(key, value);
}
}

@SuppressWarnings("unchecked")
protected <T> void addCollectionToExistingSet(ITemplateContext context, IElementTagStructureHandler structureHandler, String key, Collection<T> value) {
Set<T> items = (Set<T>) context.getVariable(key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ protected void modifyModelAttributes(ITemplateContext context, IProcessableEleme
if (orderNumber != null) {
order = orderService.findOrderByOrderNumber(orderNumber);
}
addToModel(structureHandler, "analytics", analytics(getWebPropertyId(), order));
// This element is self-closing and "analytics" is consumed by a sibling <script>, so the value
// must be request-scoped (a body-scoped local variable would not be visible outside this element).
addToRequest("analytics", analytics(getWebPropertyId(), order));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,12 @@ protected void modifyModelAttributes(ITemplateContext context, IProcessableEleme
String orderName = tag.getAttributeValue("orderName");

Order order = orderService.findNamedOrderForCustomer(orderName, customer);
// This element is self-closing and orderVar is consumed by sibling markup, so the value must be
// request-scoped (a body-scoped local variable would not be visible outside this element).
if (order != null) {
addToModel(structureHandler, orderVar, order);
addToRequest(orderVar, order);
} else {
addToModel(structureHandler, orderVar, new NullOrderImpl());
addToRequest(orderVar, new NullOrderImpl());
}
}
}