Skip to content
Merged
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 @@ -25,13 +25,15 @@
import com.dotcms.contenttype.exception.NotFoundInDbException;
import com.dotcms.contenttype.model.field.BinaryField;
import com.dotcms.contenttype.model.field.CategoryField;
import com.dotcms.contenttype.model.field.CheckboxField;
import com.dotcms.contenttype.model.field.ColumnField;
import com.dotcms.contenttype.model.field.ConstantField;
import com.dotcms.contenttype.model.field.DataTypes;
import com.dotcms.contenttype.model.field.FieldVariable;
import com.dotcms.contenttype.model.field.HostFolderField;
import com.dotcms.contenttype.model.field.JSONField;
import com.dotcms.contenttype.model.field.LineDividerField;
import com.dotcms.contenttype.model.field.MultiSelectField;
import com.dotcms.contenttype.model.field.RelationshipField;
import com.dotcms.contenttype.model.field.RowField;
import com.dotcms.contenttype.model.field.TabDividerField;
Expand Down Expand Up @@ -6213,11 +6215,14 @@ private Contentlet setDefaultValues(final Contentlet contentlet) {

final List<com.dotcms.contenttype.model.field.Field> fields = Try.of(()->contentlet.getContentType().fields()).getOrElse(Collections.emptyList());
final Map<String, Object> map = contentlet.getMap();
final Set<String> nullProperties = contentlet.getNullProperties();
Logger.debug(this, ()-> "Setting default values for the contentlet: " + contentlet.getIdentifier());
// check default values for fields not coming on the map
for (final com.dotcms.contenttype.model.field.Field field : fields) {

if (!map.containsKey(field.variable()) && UtilMethods.isSet(field.defaultValue())) {
if (!map.containsKey(field.variable())
&& !isClearedSelectionField(field, nullProperties)
&& UtilMethods.isSet(field.defaultValue())) {

try {
this.setContentletProperty(contentlet, field, field.defaultValue());
Expand All @@ -6233,6 +6238,31 @@ private Contentlet setDefaultValues(final Contentlet contentlet) {
return contentlet;
}

/**
* Tells whether the user deliberately emptied a multi-value selection field, rather than never
* submitting it at all.
* <p>
* {@code ContentletHashMap#put} removes a key outright when its value is null -- it extends
* {@link java.util.concurrent.ConcurrentHashMap}, which forbids null values -- so an absent key
* alone cannot tell those two cases apart. An explicit clear is recorded in the contentlet's
* null-properties set, which {@link #validateContentlet} already consults.
* <p>
* This only covers fields whose value is a set of selected options, where the empty set is
* itself a valid choice -- an unchecked "Show on Menu" on a new page, for instance. For a text
* or numeric field a null still means "not provided" and its default value is applied as
* before, which
* {@code ContentletJsonAPITest#Initialize_Fields_With_Default_Value_Test} relies on.
*
* @param field the Content Type field being considered
* @param nullProperties the contentlet's explicitly-nulled property names
* @return {@code true} when this is a selection field the user cleared on purpose
*/
private boolean isClearedSelectionField(final com.dotcms.contenttype.model.field.Field field,
final Set<String> nullProperties) {
return (field instanceof CheckboxField || field instanceof MultiSelectField)
&& nullProperties.contains(field.variable());
}

private static boolean hasUniqueField(ContentType contentType) {
return contentType.fields().stream().anyMatch(field -> field.unique());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package com.dotmarketing.portlets.contentlet.business;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.dotcms.contenttype.model.field.CheckboxField;
import com.dotcms.contenttype.model.field.DataTypes;
import com.dotcms.contenttype.model.field.Field;
import com.dotcms.contenttype.model.field.FieldBuilder;
import com.dotcms.contenttype.model.field.RelationshipField;
import com.dotcms.contenttype.model.field.TextField;
import com.dotcms.contenttype.model.type.ContentType;
import com.dotcms.contenttype.model.type.ContentTypeBuilder;
import com.dotcms.contenttype.model.type.SimpleContentType;
import com.dotcms.datagen.ContentTypeDataGen;
import com.dotcms.datagen.ContentletDataGen;
import com.dotcms.datagen.FieldDataGen;
import com.dotcms.rest.AnonymousAccess;
import com.dotcms.util.CollectionsUtils;
import com.dotmarketing.beans.Host;
Expand All @@ -29,6 +34,7 @@
import com.dotmarketing.portlets.structure.model.ContentletRelationships;
import com.dotmarketing.portlets.structure.model.Relationship;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.UtilMethods;
import com.dotmarketing.util.WebKeys;
import com.dotmarketing.util.WebKeys.Relationship.RELATIONSHIP_CARDINALITY;
import com.liferay.portal.model.User;
Expand Down Expand Up @@ -71,7 +77,42 @@ public void checkin_content_anonymously () throws Exception {
assertTrue("the contentlet title was saved", newCon.getTitle().equals(con.getTitle()));

assertTrue("contentlet is not live", newCon.isWorking() && !newCon.hasLiveVersion());


}

/**
* Method to Test: {@link ContentletAPI#checkin(Contentlet, User, boolean)}
* When: A new Contentlet is checked in with a Checkbox field the user explicitly cleared, on a
* Content Type whose field declares a default value of "true"
* Should: Keep the field empty -- an explicit clear must not be mistaken for "never submitted"
* and overwritten with the Content Type's default value
*
* @see <a href="https://github.com/dotCMS/core/issues/35416">Issue #35416</a>
*/
@Test
public void checkin_new_content_with_cleared_checkbox_does_not_apply_default_value()
throws Exception {

final ContentType contentType = new ContentTypeDataGen().nextPersisted();
final Field checkboxField = new FieldDataGen()
.contentTypeId(contentType.id())
.type(CheckboxField.class)
.dataType(DataTypes.TEXT)
.values("|true")
.defaultValue("true")
.nextPersisted();

final Contentlet contentlet = new ContentletDataGen(contentType.id()).next();
// Clearing a checkbox sets a null, and ContentletHashMap#put drops the key from the map
// entirely -- the same state the edit form produces when the box is left unchecked.
contentlet.setProperty(checkboxField.variable(), null);

final Contentlet checkedIn = contentletAPI.checkin(contentlet, user, false);
final String persisted = checkedIn.getStringProperty(checkboxField.variable());

// Mirrors HTMLPageAsset#isShowOnMenu, which reads the raw value with contains("true")
assertFalse("A cleared checkbox must not be restored from the field's default value",
UtilMethods.isSet(persisted) && persisted.contains("true"));
}

/**
Expand Down
Loading