Skip to content

Commit e1330e8

Browse files
dario-dazafabrizzio-dotCMS
authored andcommitted
Retrieve styleProperties per contentlet (#34176)
This PR is for retrieve the Contentlet Style properties in the **PageAPI** and **GraphQL**, this functionality is under the feature flag `FEATURE_FLAG_UVE_STYLE_EDITOR`. Also there was an enhancement to support objects when we try to save complex style properties. ### Proposed Changes * `Define_Contentlets_StyleProperties.postman_collection.json`: New collection to test the Definition and Retrieve Style Properties. * `GraphQL.Tests.json`: New folder to test GraphQL request that retrieve Style Properties. * `PageResourceTest.json`: Move style properties test to the new collection `Define_Contentlets_StyleProperties`. * `PageRenderUtil.java`: Add addStyles() method to enrich the contentlet with style properties retrieved from the Multitree database. * `PageContainerForm.java`: Add support to the Style Properties deserializer to save objects and arrays. * `MultiTree.java`: Make style properties field immutable. * `PersonalizedContentlet.java`: Add getStyleProperties() to retrieve styleProperties. This PR fixes: #34091 This PR fixes: #33696 --------- Co-authored-by: Fabrizzio Araya <[email protected]>
1 parent 160331b commit e1330e8

7 files changed

Lines changed: 1911 additions & 257 deletions

File tree

dotCMS/src/main/java/com/dotcms/rendering/velocity/services/PageRenderUtil.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ private List<ContainerRaw> populateContainers() throws DotDataException, DotSecu
321321
this.widgetPreExecute(contentlet);
322322
this.addAccrueTags(contentlet);
323323
this.addRelationships(contentlet);
324+
this.addStyles(contentlet, personalizedContentlet);
324325

325326
if (personalizedContentlet.getPersonalization().equals(includeContentFor)) {
326327

@@ -525,6 +526,28 @@ private void widgetPreExecute(final Contentlet contentlet) {
525526
}
526527
}
527528

529+
/**
530+
* Only applies when the FEATURE_FLAG_UVE_STYLE_EDITOR is enabled.
531+
* Adds style properties from the MultiTree to the contentlet's data map.
532+
* This ensures that contentlet styling metadata is properly scoped to its specific
533+
* personalization and variant context.
534+
*
535+
* @param contentlet The {@link Contentlet} to add style properties to
536+
* @param personalizedContentlet The {@link PersonalizedContentlet} containing the style
537+
* properties from the MultiTree relationship
538+
*/
539+
private void addStyles(Contentlet contentlet, PersonalizedContentlet personalizedContentlet) {
540+
if (!Config.getBooleanProperty("FEATURE_FLAG_UVE_STYLE_EDITOR", false)) {
541+
return;
542+
}
543+
544+
final Map<String, Object> styleProperties = personalizedContentlet.getStyleProperties();
545+
546+
if (UtilMethods.isSet(styleProperties) && !styleProperties.isEmpty()) {
547+
contentlet.getMap().put("styleProperties", styleProperties);
548+
}
549+
}
550+
528551
private boolean needParseContainerPrefix(final Container container, final String uniqueId) {
529552
return !ParseContainer.isParserContainerUUID(uniqueId) &&
530553
(templateLayout == null || !templateLayout.existsContainer(container, uniqueId));

dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageContainerForm.java

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -84,35 +84,70 @@ public PageContainerForm deserialize(final JsonParser jsonParser,
8484

8585
// Parse styleProperties for each contentlet (optional field)
8686
final JsonNode stylePropertiesNode = jsonElement.get(STYLE_PROPERTIES_ATTRIBUTE_NAME);
87-
if (stylePropertiesNode != null && stylePropertiesNode.isObject()) {
88-
stylePropertiesNode.fields().forEachRemaining(entry -> {
89-
final String contentletId = entry.getKey();
90-
final JsonNode styleProps = entry.getValue();
91-
if (styleProps != null && styleProps.isObject()) {
92-
final Map<String, Object> propsMap = new HashMap<>();
93-
styleProps.fields().forEachRemaining(prop -> {
94-
final JsonNode propValue = prop.getValue();
95-
// Handle different JSON value types
96-
if (propValue.isTextual()) {
97-
propsMap.put(prop.getKey(), propValue.asText());
98-
} else if (propValue.isNumber()) {
99-
propsMap.put(prop.getKey(), propValue.numberValue());
100-
} else if (propValue.isBoolean()) {
101-
propsMap.put(prop.getKey(), propValue.asBoolean());
102-
} else {
103-
propsMap.put(prop.getKey(), propValue.toString());
104-
}
105-
});
106-
containerEntry.setStyleProperties(contentletId, propsMap);
107-
}
108-
});
87+
if (UtilMethods.isSet(stylePropertiesNode) && stylePropertiesNode.isObject()) {
88+
processStyleProperties(stylePropertiesNode, containerEntry);
10989
}
11090

11191
entries.add(containerEntry);
11292
}
11393

11494
return new PageContainerForm(entries, jsonNode.toString());
11595
}
96+
97+
/**
98+
* Processes the style properties for the container entry.
99+
* It converts the JSON node to a Map<String, Object> and sets it to the container entry.
100+
* @param stylePropertiesNode The JSON node containing the style properties.
101+
* @param containerEntry The container entry to set the style properties.
102+
*/
103+
private void processStyleProperties(final JsonNode stylePropertiesNode, final ContainerEntry containerEntry) {
104+
stylePropertiesNode.fields().forEachRemaining(entry -> {
105+
final String contentletId = entry.getKey();
106+
final JsonNode styleProps = entry.getValue();
107+
if (styleProps != null && styleProps.isObject()) {
108+
final Map<String, Object> propsMap = new HashMap<>();
109+
styleProps.fields().forEachRemaining(prop -> {
110+
final JsonNode propValue = prop.getValue();
111+
propsMap.put(prop.getKey(), convertJsonNodeToObject(propValue));
112+
});
113+
containerEntry.setStyleProperties(contentletId, propsMap);
114+
}
115+
});
116+
}
117+
118+
/**
119+
* Recursively converts a JsonNode to its corresponding Java object type.
120+
* Handles all JSON types: primitives, objects, arrays, and null.
121+
*
122+
* @param node The JsonNode to convert
123+
* @return The converted Java object (String, Number, Boolean, Map, List, or null)
124+
*/
125+
private Object convertJsonNodeToObject(final JsonNode node) {
126+
if (node == null || node.isNull()) {
127+
return null;
128+
} else if (node.isBoolean()) {
129+
return node.asBoolean();
130+
} else if (node.isInt()) {
131+
return node.asInt();
132+
} else if (node.isLong()) {
133+
return node.asLong();
134+
} else if (node.isDouble() || node.isFloat()) {
135+
return node.asDouble();
136+
} else if (node.isArray()) {
137+
final List<Object> list = new ArrayList<>();
138+
node.forEach(element -> list.add(convertJsonNodeToObject(element)));
139+
return list;
140+
} else if (node.isObject()) {
141+
final Map<String, Object> map = new HashMap<>();
142+
node.fields().forEachRemaining(entry ->
143+
map.put(entry.getKey(), convertJsonNodeToObject(entry.getValue()))
144+
);
145+
return map;
146+
} else {
147+
// Fallback for any other type and String values
148+
return node.asText();
149+
}
150+
}
116151
}
117152

118153
/**

dotCMS/src/main/java/com/dotmarketing/beans/MultiTree.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.dotmarketing.portlets.containers.model.Container;
88
import com.dotmarketing.portlets.contentlet.model.Contentlet;
99
import com.dotmarketing.portlets.htmlpageasset.model.HTMLPageAsset;
10+
import com.dotmarketing.util.UtilMethods;
1011
import com.fasterxml.jackson.annotation.JsonIgnore;
1112
import java.util.Map;
1213
import org.apache.commons.lang.builder.EqualsBuilder;
@@ -75,7 +76,7 @@ public MultiTree(final String htmlPage,
7576
this.treeOrder = Math.max(treeOrder, 0);
7677
this.personalization = personalization;
7778
this.variantId = variantId;
78-
this.styleProperties = styleProperties;
79+
this.styleProperties = UtilMethods.isSet(styleProperties) ? Map.copyOf(styleProperties) : null;
7980
}
8081

8182
/** full constructor */

dotCMS/src/main/java/com/dotmarketing/factories/PersonalizedContentlet.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.dotmarketing.factories;
22

3+
import com.dotmarketing.util.UtilMethods;
34
import java.io.Serializable;
45
import java.util.Map;
56
import java.util.Objects;
@@ -22,14 +23,14 @@ public PersonalizedContentlet(final String contentletId, final String personaliz
2223
this.contentletId = contentletId;
2324
this.personalization = personalization;
2425
this.treeOrder = treeOrder;
25-
this.styleProperties = styleProperties;
26+
this.styleProperties = UtilMethods.isSet(styleProperties) ? Map.copyOf(styleProperties) : Map.of();
2627
}
2728

2829
public PersonalizedContentlet(final String contentletId, final String personalization) {
2930
this.contentletId = contentletId;
3031
this.personalization = personalization;
3132
this.treeOrder = 0;
32-
this.styleProperties = null;
33+
this.styleProperties = Map.of();
3334
}
3435

3536
public String getContentletId() {
@@ -44,6 +45,10 @@ public Object getTreeOrder() {
4445
return treeOrder;
4546
}
4647

48+
public Map<String, Object> getStyleProperties() {
49+
return styleProperties;
50+
}
51+
4752
@Override
4853
public boolean equals(Object o) {
4954
if (this == o) return true;

0 commit comments

Comments
 (0)