diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java index cbaa589879..1bc880b674 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java @@ -377,11 +377,15 @@ public Map getRules() { */ protected final Map getRulesForResource(Resource resource) { String[] VALID_RULES = new String[] { "description", "enabled", "enum", "enumNames", - "exclusiveMaximum", "exclusiveMinimum", "label", "maximum", "minimum", + "exclusiveMaximum", "exclusiveMinimum", "label", "maximum", "minimum", "properties", "readOnly", "required", "value", "visible" }; - Predicate> isRuleNameValid = obj -> Arrays.stream(VALID_RULES).anyMatch(validKey -> validKey.equals(obj - .getKey())); + // A rule target may be one of the fixed editable properties (which includes the whole + // `properties` bag), or a granular custom-variable target keyed `properties.` (e.g. + // properties.employer or properties.employer.category) that the runtime routes to the + // PropertiesManager per key. Preserve both the bag and the per-key targets. + Predicate> isRuleNameValid = obj -> obj.getKey().startsWith("properties.") + || Arrays.stream(VALID_RULES).anyMatch(validKey -> validKey.equals(obj.getKey())); Predicate> isRuleValid = isEntryNonEmpty.and(isRuleNameValid); diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImplTest.java index 4d93a7c50e..8cf5db4f03 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImplTest.java @@ -53,6 +53,7 @@ public class AbstractFormComponentImplTest { private static final String PATH_COMPONENT_WITH_NO_VALIDATION_STATUS = CONTENT_ROOT + "/datepicker2"; private static final String PATH_COMPONENT_WITH_INVALID_VALIDATION_STATUS = CONTENT_ROOT + "/datepicker3"; private static final String PATH_COMPONENT_WITH_NO_RULE = CONTENT_ROOT + "/numberinput"; + private static final String PATH_COMPONENT_WITH_PROPERTIES_RULE = CONTENT_ROOT + "/textinputWithPropertiesRule"; private static final String PATH_COMPONENT_WITH_DISABLED_XFA_SCRIPTS = CONTENT_ROOT + "/xfacomponent"; private static final String PATH_COMPONENT_WITH_INVALID_XFA_SCRIPTS = CONTENT_ROOT + "/xfacomponentinvalid"; private static final String PATH_COMPONENT_WITH_NO_XFA_SCRIPTS = CONTENT_ROOT + "/xfacomponentnone"; @@ -147,6 +148,39 @@ public void testRulesExcludedWhenPublishViewAttributeSetOnAuthor() { "fd:rules should not appear when publish view attribute is set even in author WCMMode"); } + @Test + public void testGranularPropertiesRulesPreserved() { + // A rule keyed `properties.` is a granular custom-variable target routed by the + // runtime to the PropertiesManager; the exporter must not drop flat or nested ones. + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(PATH_COMPONENT_WITH_PROPERTIES_RULE); + Map rules = abstractFormComponentImpl.getRules(); + assertEquals("some property expression", rules.get("properties.employer"), + "flat properties. rule should be preserved"); + assertEquals("some nested property expression", rules.get("properties.employer.category"), + "nested properties. rule should be preserved"); + } + + @Test + public void testBarePropertiesAndWhitelistedRulesPreserved() { + // The whole `properties` bag is an editable property, and standard whitelisted targets + // (e.g. visible) must keep working alongside the granular properties. targets. + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(PATH_COMPONENT_WITH_PROPERTIES_RULE); + Map rules = abstractFormComponentImpl.getRules(); + assertEquals("some expression", rules.get("visible")); + assertEquals("merge($field.properties, {a: 1})", rules.get("properties"), + "the whole properties bag rule should be preserved"); + } + + @Test + public void testUnknownRulesDropped() { + // Keys that are neither whitelisted nor a genuine properties. target are filtered + // out, including a `properties`-prefixed key without the dot separator. + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(PATH_COMPONENT_WITH_PROPERTIES_RULE); + Map rules = abstractFormComponentImpl.getRules(); + assertNull(rules.get("notARule"), "non-whitelisted, non-properties rule keys should be dropped"); + assertNull(rules.get("propertiesFoo"), "a properties-prefixed key without a dot is not a valid target"); + } + @Test public void testIsAuthorModeUtility() { assertFalse(ComponentUtils.isAuthorMode(null), "null request should not be author mode"); diff --git a/bundles/af-core/src/test/resources/form/componentswithrule/test-content.json b/bundles/af-core/src/test/resources/form/componentswithrule/test-content.json index f833afe238..82fbe830ff 100644 --- a/bundles/af-core/src/test/resources/form/componentswithrule/test-content.json +++ b/bundles/af-core/src/test/resources/form/componentswithrule/test-content.json @@ -15,6 +15,21 @@ "sling:resourceType": "forms-components-examples/components/form/numberinput", "fieldType": "number-input" }, + "textinputWithPropertiesRule": { + "jcr:primaryType": "nt:unstructured", + "name": "name", + "sling:resourceType": "forms-components-examples/components/form/textinput", + "fieldType": "text-input", + "fd:rules": { + "jcr:primaryType": "nt:unstructured", + "visible": "some expression", + "properties": "merge($field.properties, {a: 1})", + "properties.employer": "some property expression", + "properties.employer.category": "some nested property expression", + "notARule": "should be dropped", + "propertiesFoo": "should be dropped" + } + }, "datepicker": { "jcr:primaryType": "nt:unstructured", "name": "datepicker1666939496905", diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/base/v1/base/_cq_styleConfig/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/base/v1/base/_cq_styleConfig/.content.xml index 47f418b74d..77c344fd3b 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/base/v1/base/_cq_styleConfig/.content.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/base/v1/base/_cq_styleConfig/.content.xml @@ -11,32 +11,6 @@ longTitle="Field" propertySheet="/mnt/overlay/fd/af/components/stylePropertySheet/common"> - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +