Skip to content

Language Variable content type is not marked system and can be deleted, breaking i18n site-wide #36958

Description

@wezell

Problem

The Languagevariable content type — which the entire i18n subsystem depends on — is not marked as a system content type, so nothing prevents it from being deleted.

Task04210CreateDefaultLanguageVariable creates it with:

// Task04210CreateDefaultLanguageVariable.java:66-67
final boolean isSystem = Boolean.FALSE;
final boolean isFixed  = Boolean.TRUE;

fixed = true protects the type's fields from being modified. It does not protect the type itself. The only guard against deletion is the system flag:

// ContentTypeFactoryImpl.dbDelete
if (type.defaultType()) {
    throw new DotDataException("contenttype.delete.cannot.delete.default.type");
}
if (type.system()) {
    throw new DotDataException("contenttype.delete.cannot.delete.system.type");
}

Since system = false, that guard never fires. Languagevariable is also absent from ContentTypeAPI.reservedStructureVars and reservedStructureNames (which cover host, folder, file, forms, htmlpage, menulink, container, template, user, calendarEvent).

Impact

Anyone with permission to delete content types can remove it, with no warning and no guard.

Once it is gone, LanguageVariableAPIImpl looks it up unconditionally and throws:

APILocator.getContentTypeAPI(user).find(LANGUAGEVARIABLE_VAR_NAME);
// -> com.dotcms.contenttype.exception.NotFoundInDbException:
//    Content Type with id:'Languagevariable' not found

So the failure mode is site-wide i18n breakage, and it degrades quietly — language-variable resolution falls back to emitting the raw key rather than the translated value.

Note that ContentTypeAPIImpl deletes asynchronously by default (DELETE_CONTENT_TYPE_ASYNC), so the deletion can land some time after the request that triggered it, making the connection between cause and symptom harder to see.

How this surfaced

Observed in CI on #36943. Within a single integration-test shard the content type existed early in the run (a test at position 2 did contentTypeApi.find("Languagevariable") successfully) and was gone by position 21:

ContentletAjaxTest:216   NotFoundInDbException: Content Type with id:'Languagevariable' not found
FieldUtilTest:129        expected:<test[]> but was:<test[1786122699489]>   <- i18n fell back to the raw key
FieldUtilTest:80         ConditionTimeout waiting for "test"

ContentTypeDataGen later logged Content type Languagevariable not found Creating language variable content type and recreated it — after the failures.

Had the type been marked system, whatever deleted it would have thrown instead of silently succeeding, and this class of failure would be impossible.

Proposed fix

  1. Add a startup task setting the flag on existing installations — changing the run-once task alone does not help anyone already upgraded:
    UPDATE structure SET system = true WHERE velocity_var_name = 'Languagevariable';
  2. Create it as system = true going forward.
  3. Consider adding languagevariable to reservedStructureVars / reservedStructureNames, so a user cannot create or rename another type into that slot.

Known consequence to plan for

Task240306MigrateLegacyLanguageVariablesTest.removeLanguageVariableContentType() deliberately deletes this content type to verify that executeUpgrade() recreates it. Once the type is protected, that delete will throw and the test will fail.

That test scenario needs rework as part of this change — either dropping the type via direct SQL to set up the scenario, or reconsidering whether "recreate after deletion" is still a scenario worth covering once deletion is impossible through the API.

Not established

Which specific operation removed the type in the CI run above. The defect stands on its own regardless: the type is deletable when it should not be.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    New

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions