Fix PublishDateUpdater republishing manually-unpublished content when previous job run time is unknown
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Problem Statement
PublishDateUpdater's safeguard against republishing manually-unpublished content — shouldPublishContent() — is silently bypassed whenever the job cannot determine the previous run time. When previousJobRunTime is null, shouldPublishContent() unconditionally returns true, so any contentlet matching the raw "due to publish" Lucene query (live:false AND publishDate <= now AND expireDate >= now) gets auto-published as the system user — with no push-publish bundle and no explicit workflow trail — even if it was deliberately unpublished long after its scheduled publish date already passed.
This surfaced in a production customer environment (dotCMS 26.05.22-01): a content piece with publishDate = 4/7/2020 that had been manually unpublished was silently republished as system during a PublishDateUpdater job run that fired immediately after a server restart. Support had no push-publish record to point to, because none exists — the republish happened entirely through this local fallback path.
Impact: any content type with a publish-date field is at risk of having deliberately-unpublished content silently reappear live on a server restart whenever the job's previous-run-time lookup fails (getPreviousJobRunTime() returning null), with zero audit trail explaining why. The affected job run processed 12 of 29 matching contentlets in this instance, so this is not limited to a single piece of content per occurrence.
Source: PublishDateUpdater.java — getPreviousJobRunTime() (~L114-177) and shouldPublishContent() (~L196-232).
Steps to Reproduce
- Create a contentlet on a content type with a
publish datefield, set that field to a date in the past, and publish it through the normal workflow. - Manually unpublish the contentlet. Its
publish datefield value is left unchanged (still in the past). The contentlet is nowlive:falsewith a stale, pastpublishDate. - Cause the Quartz
PublishDateUpdaterjob to run at a moment when bothJobExecutionContext.getPreviousFireTime()and the cron-based fallback ingetPreviousJobRunTime()fail to resolve a value. In production this is observed right after a server restart, logged as:WARN publishing.PublishDateUpdater - Could not determine previous job run time for currentFireTime: ... - Observe the same job tick: the contentlet from step 2 is republished as the
systemuser. The workflow log shows the content type's defaultPublishaction firing viaESContentletAPIImpl(because no explicit action was set on the checkin) — with no push-publish bundle and no explicit user action behind it.
Acceptance Criteria
- When
previousJobRunTimecannot be determined, a contentlet matching the publish query that has an existing live-version history (has been published at least once before) is not auto-published, even though its currentlivestatus isfalseand itspublishDatefield is in the past. - When
previousJobRunTimecannot be determined, a contentlet matching the same publish query that has never had a live version (a genuine first-time scheduled publish) is still auto-published normally — first-run/new-instance behavior is not delayed or broken by this fix. - When a contentlet is skipped under this fallback, a log entry (contentlet identifier + reason, e.g. "has prior publish history, previousJobRunTime unknown — skipping auto-publish") is written, so a future occurrence is diagnosable directly from logs.
- Original bug scenario no longer reproduces: a contentlet with a past
publishDate,live:false, and prior publish history is not republished assystemon a job run wherepreviousJobRunTimeis unavailable (e.g. the firstPublishDateUpdaterexecution after a server restart). - Existing behavior is unchanged when
previousJobRunTimeis available: content is published or skipped exactly as today, based on thepublishDate-vs-previousJobRunTimecomparison. - The
PUBLISH_JOB_QUEUE_FORCE_INCLUDE_PAST_CONTENTconfig property, when set totrue, continues to force-publish all matching content regardless of the history check (existing override behavior is preserved).
dotCMS Version
26.05.22-01 (Cloud)
Severity
Medium - Some functionality impacted
Links
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in dotCMS/src/enterprise/java/com/dotcms/enterprise/publishing/PublishDateUpdater.java, reading getPreviousJobRunTime() and shouldPublishContent() and the raw due-to-publish query. Reproduce the unknown-previous-run case, then verify prior published content is skipped and never-published content still runs, while logs, the force-include override, and normal known-time behavior remain unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100