Skip to content
Draft
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 @@ -57,8 +57,10 @@ export class DotCustomEventHandlerService {
this.dotPropertiesService
.getKeys([FeaturedFlags.FEATURE_FLAG_CONTENT_EDITOR2_ENABLED])
.subscribe((response) => {
const contentEditorFeatureFlag =
response[FeaturedFlags.FEATURE_FLAG_CONTENT_EDITOR2_ENABLED] === 'true';
// Accept native boolean true (current backend) or the legacy string 'true'
// (N-1 backend during a rollback window).
const val = response[FeaturedFlags.FEATURE_FLAG_CONTENT_EDITOR2_ENABLED];
const contentEditorFeatureFlag = val === true || val === 'true';

if (!this.handlers) {
this.handlers = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,11 @@ public interface FeatureFlagName {
String FEATURE_FLAG_OPEN_SEARCH_PHASE = "FEATURE_FLAG_OPEN_SEARCH_PHASE";

String FEATURE_FLAG_NEW_BLOCK_EDITOR = "FEATURE_FLAG_NEW_BLOCK_EDITOR";

/**
* Enables the new content editor (Edit Content v2).
* Also checked in content-type metadata to opt individual types out.
* Frontend equivalent: {@code FeaturedFlags.FEATURE_FLAG_CONTENT_EDITOR2_ENABLED}.
*/
String FEATURE_FLAG_CONTENT_EDITOR2_ENABLED = "CONTENT_EDITOR2_ENABLED";
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.dotcms.rest.WebResource;
import com.dotmarketing.business.Role;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.Logger;
import org.glassfish.jersey.server.JSONP;
import com.dotcms.rest.ResponseEntityView;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand Down Expand Up @@ -58,14 +59,42 @@
public class ConfigurationResource implements Serializable {

private final ConfigurationHelper helper;
private final WebResource webResource;
private static final String REPORT_ISSUE_INCLUDE_USER_PII = "REPORT_ISSUE_INCLUDE_USER_PII";

/**
* Feature flag keys in WHITE_LIST that must be serialised as native JSON booleans.
* All other WHITE_LIST entries (strings, numbers, lists) are left as-is.
*
* <p><b>Maintenance rule:</b> every key added here MUST also be present in WHITE_LIST.
* The wire format is the normalised lowercase string {@code "true"} or {@code "false"} —
* frontend callers should compare with {@code === 'true'}. Adding a key here without
* also adding it to WHITE_LIST will silently exclude it from the response.
*/
private static final Set<String> BOOLEAN_FEATURE_FLAGS = ImmutableSet.of(
FeatureFlagName.FEATURE_FLAG_EXPERIMENTS,
FeatureFlagName.DOTFAVORITEPAGE_FEATURE_ENABLE,
FeatureFlagName.FEATURE_FLAG_TEMPLATE_BUILDER_2,
FeatureFlagName.FEATURE_FLAG_SEO_IMPROVEMENTS,
FeatureFlagName.FEATURE_FLAG_SEO_PAGE_TOOLS,
FeatureFlagName.FEATURE_FLAG_EDIT_URL_CONTENT_MAP,
FeatureFlagName.FEATURE_FLAG_NEW_BINARY_FIELD,
FeatureFlagName.FEATURE_FLAG_ANNOUNCEMENTS,
FeatureFlagName.FEATURE_FLAG_NEW_EDIT_PAGE,
FeatureFlagName.FEATURE_FLAG_UVE_PREVIEW_MODE,
FeatureFlagName.FEATURE_FLAG_UVE_TOGGLE_LOCK,
FeatureFlagName.FEATURE_FLAG_UVE_STYLE_EDITOR,
FeatureFlagName.FEATURE_FLAG_PAGE_SCANNER,
FeatureFlagName.FEATURE_FLAG_UVE_LEGACY_SCRIPT_INJECTION,
FeatureFlagName.FEATURE_FLAG_NEW_BLOCK_EDITOR,
FeatureFlagName.FEATURE_FLAG_CONTENT_EDITOR2_ENABLED);

private static final Set<String> WHITE_LIST = ImmutableSet.copyOf(
Config.getStringArrayProperty("CONFIGURATION_WHITE_LIST",
new String[] {"EMAIL_SYSTEM_ADDRESS", "WYSIWYG_IMAGE_URL_PATTERN", "CHARSET","CONTENT_PALETTE_HIDDEN_CONTENT_TYPES", "DEFAULT_CONTAINER",
FeatureFlagName.FEATURE_FLAG_EXPERIMENTS, FeatureFlagName.DOTFAVORITEPAGE_FEATURE_ENABLE, FeatureFlagName.FEATURE_FLAG_TEMPLATE_BUILDER_2,
"SHOW_VIDEO_THUMBNAIL", "EXPERIMENTS_MIN_DURATION", "EXPERIMENTS_MAX_DURATION", "EXPERIMENTS_DEFAULT_DURATION", FeatureFlagName.FEATURE_FLAG_SEO_IMPROVEMENTS,
FeatureFlagName.FEATURE_FLAG_SEO_PAGE_TOOLS, FeatureFlagName.FEATURE_FLAG_EDIT_URL_CONTENT_MAP, "CONTENT_EDITOR2_ENABLED", "CONTENT_EDITOR2_CONTENT_TYPE",
FeatureFlagName.FEATURE_FLAG_SEO_PAGE_TOOLS, FeatureFlagName.FEATURE_FLAG_EDIT_URL_CONTENT_MAP, FeatureFlagName.FEATURE_FLAG_CONTENT_EDITOR2_ENABLED, "CONTENT_EDITOR2_CONTENT_TYPE",
FeatureFlagName.FEATURE_FLAG_NEW_BINARY_FIELD, FeatureFlagName.FEATURE_FLAG_ANNOUNCEMENTS, FeatureFlagName.FEATURE_FLAG_NEW_EDIT_PAGE,
FeatureFlagName.FEATURE_FLAG_UVE_PREVIEW_MODE, FeatureFlagName.FEATURE_FLAG_UVE_TOGGLE_LOCK, FeatureFlagName.FEATURE_FLAG_UVE_STYLE_EDITOR,
FeatureFlagName.FEATURE_FLAG_PAGE_SCANNER,
Expand All @@ -83,6 +112,15 @@ private boolean isOnBlackList(final String key) {
*/
public ConfigurationResource() {
this.helper = ConfigurationHelper.INSTANCE;
this.webResource = new WebResource();
}

/**
* Test constructor — allows injecting a mock {@link WebResource}.
*/
ConfigurationResource(final WebResource webResource) {
this.helper = ConfigurationHelper.INSTANCE;
this.webResource = webResource;
}

/**
Expand All @@ -100,10 +138,9 @@ public ConfigurationResource() {
@Produces({MediaType.APPLICATION_JSON, "application/javascript"})
public final Response getConfigVariables(@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
@QueryParam("keys") final String keysQuery)
throws IOException {
@QueryParam("keys") final String keysQuery) {

new WebResource.InitBuilder(request, response)
new WebResource.InitBuilder(webResource)
.requiredBackendUser(true)
.requestAndResponse(request, response)
.rejectWhenNoUser(true)
Expand Down Expand Up @@ -147,9 +184,45 @@ private Object recoveryFromConfig (final String key) {
return Config.getIntProperty(key.replace("number:", StringPool.BLANK), 0);
}

if (BOOLEAN_FEATURE_FLAGS.contains(key)) {
return parseBooleanFlag(key);
}

return Config.getStringProperty(key, "NOT_FOUND");
}

/**
* Normalises a feature flag property to the canonical lowercase string {@code "true"} or
* {@code "false"}, preserving the existing string wire format so that consumers built
* against the pre-existing contract continue to work without changes.
* Returns the sentinel {@code "NOT_FOUND"} when the key is not defined anywhere
* (no .properties entry, no DOT_* env override).
*
* <p>Accepted truthy values (case-insensitive, whitespace-trimmed): {@code "true"}, {@code "1"}.
* Accepted falsy values: {@code "false"}, {@code "0"}, {@code ""}.
* Unrecognised values are logged as WARN and normalise to {@code "false"}.
*/
private static Object parseBooleanFlag(final String key) {
final String rawValue = Config.getStringProperty(key, null);
if (rawValue == null) {
return "NOT_FOUND";
}
final String normalized = rawValue.trim().toLowerCase(Locale.ROOT);
switch (normalized) {
case "true":
case "1":
return "true";
case "false":
case "0":
case "":
return "false";
default:
Logger.warn(ConfigurationResource.class,
() -> "Feature flag '" + key + "' has unrecognized value '" + rawValue + "'; treating as false.");
return "false";
}
}

/**
* Returns the list of system properties that are set through the dotCMS
* configuration files.
Expand Down
Loading
Loading