dotCMS / dotCMS/core

Fix PublishDateUpdater republishing manually-unpublished content when previous job run time is unknown

Open
#37,157 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

OKR : Customer Support Team : Maintenance Type : Defect
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.javagetPreviousJobRunTime() (~L114-177) and shouldPublishContent() (~L196-232).

Steps to Reproduce
  1. Create a contentlet on a content type with a publish date field, set that field to a date in the past, and publish it through the normal workflow.
  2. Manually unpublish the contentlet. Its publish date field value is left unchanged (still in the past). The contentlet is now live:false with a stale, past publishDate.
  3. Cause the Quartz PublishDateUpdater job to run at a moment when both JobExecutionContext.getPreviousFireTime() and the cron-based fallback in getPreviousJobRunTime() 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: ...
    
  4. Observe the same job tick: the contentlet from step 2 is republished as the system user. The workflow log shows the content type's default Publish action firing via ESContentletAPIImpl (because no explicit action was set on the checkin) — with no push-publish bundle and no explicit user action behind it.
Acceptance Criteria
  • When previousJobRunTime cannot 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 current live status is false and its publishDate field is in the past.
  • When previousJobRunTime cannot 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 as system on a job run where previousJobRunTime is unavailable (e.g. the first PublishDateUpdater execution after a server restart).
  • Existing behavior is unchanged when previousJobRunTime is available: content is published or skipped exactly as today, based on the publishDate-vs-previousJobRunTime comparison.
  • The PUBLISH_JOB_QUEUE_FORCE_INCLUDE_PAST_CONTENT config property, when set to true, 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

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 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.