Skip to content

bug(content-type): changing a content type's publish/expire date field recalculates identifiers with the OLD mapping #38031

Open

Description

@fabrizzio-dotCMS

Problem Statement

When a content type's publish date or expire date field mapping changes, ContentTypeAPIImpl.transactionalSave fires IdentifierDateJob to recalculate SysPublishDate / SysExpireDate on the identifiers of every existing contentlet of that type. It passes the type as it was before the save:

// ContentTypeAPIImpl.java:1179-1183
final ContentType oldOldType = oldType;   // read from the DB before contentTypeFactory.save(...)
if (fireUpdateIdentifiers(oldType.expireDateVar(), contentTypeToSave.expireDateVar())
        || fireUpdateIdentifiers(oldType.publishDateVar(), contentTypeToSave.publishDateVar())) {
    IdentifierDateJob.triggerJobImmediately(oldOldType, user);
}

IdentifierDateJob.updateIdentifierDates reads the date fields from that type (IdentifierDateJob.java:164-174, type.publishDateVar() / type.expireDateVar(), commented as "the new … Date Var"). So the recalculation applies the previous mapping, not the one just saved:

  • Field assigned where there was none → old expireDateVar() is null → the job writes SysExpireDate = null on every identifier of the type. Existing content never auto-unpublishes (same for publish date / auto-publish).
  • Field switched A → B → identifiers keep the dates of field A.
  • Field removed → identifiers keep the stale dates and keep auto-publishing / unpublishing on a field that is no longer the date field.

Newly saved content is unaffected: a normal save writes the identifier dates directly (ESContentletAPIImpl.java:6687-6699). The defect hits content that existed before the mapping change. Engine-independent (ES and OS alike). The old type has been passed since at least 83ed01e (2019).

Why tests don't catch it: PublisherTest.autoUnpublishContent saves the type and then calls IdentifierDateJob.triggerJobImmediately(contentType, …) itself with the new type (PublisherTest.java:484, added in 6f0b8cf as a flakiness fix). The test passes on that manual run and never exercises the automatic trigger. The two jobs also race: if the automatic one (old type, writes null) finishes last, it overwrites the correct value — a plausible contributor to the flake tracked in #38006 (not verified).

Steps to Reproduce

  1. Create a content type with a Date/Time field expires and no expire date field set.
  2. Create and publish a contentlet with expires = yesterday.
  3. Edit the content type and set expires as its Expire Date Field; save.
  4. Wait for IdentifierDateJob to run, then check the identifier: SELECT syspublish_date, sysexpire_date FROM identifier WHERE id = '<identifier>';

Expected: sysexpire_date = the contentlet's expires value, and the content is unpublished by the next PublishDateUpdater run.
Actual: sysexpire_date is NULL; the content stays live.

Acceptance Criteria

  • Changing a content type's publish/expire date field recalculates existing identifiers with the newly saved mapping (pass contentTypeToSave, not oldOldType).
  • Assigning, switching and removing the date field each leave identifier dates matching the new mapping.
  • PublisherTest.autoUnpublishContent relies on the automatic trigger — the manual triggerJobImmediately call is removed so the real path is tested.

dotCMS Version

main (3a6eaa8042); present since at least 2019.

Severity

Medium - Some functionality impacted

Links

Metadata

Metadata

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions