dotCMS / dotCMS/core

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

Open
#36,958 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Type : Defect
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Task04210CreateDefaultLanguageVariable.java and ContentTypeFactoryImpl.dbDelete to trace how the Languagevariable type is created and protected. Then inspect the startup migration, ContentTypeAPIImpl deletion behavior, and Task240306MigrateLegacyLanguageVariablesTest.removeLanguageVariableContentType(). Done means existing and newly created Languagevariable types cannot be deleted through the API and the affected upgrade test reflects that behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, sql
Domain
backend, database, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.