Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,23 @@

import com.dotcms.business.WrapInTransaction;
import com.dotcms.content.elasticsearch.constants.ESMappingConstants;
import com.dotcms.contenttype.model.type.ContentType;
import com.dotcms.enterprise.LicenseUtil;
import com.dotcms.enterprise.license.LicenseLevel;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.common.db.DotConnect;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UtilMethods;
import com.liferay.portal.model.User;
import graphql.VisibleForTesting;
import org.apache.commons.lang3.StringUtils;
import org.quartz.CronExpression;

import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -88,14 +93,115 @@ public static List<String> getContentTypeVariableWithPublishField() throws DotDa
.collect(Collectors.toList());
}

/**
* Calculates the previous job run time based on the cron expression configuration.
* This is used to determine if content should have been published in a previous job run.
*
* @param currentFireTime The current job execution time
* @return The previous fire time, or null if it cannot be calculated
*/
@VisibleForTesting
public static Date getPreviousJobRunTime(final Date currentFireTime) {
try {
final String cronExpressionStr = Config.getStringProperty(
"PUBLISHER_QUEUE_THREAD_CRON_EXPRESSION", "0 0/1 * * * ?");

final CronExpression cronExpression = new CronExpression(cronExpressionStr);

final Date nextAfterCurrent = cronExpression.getNextValidTimeAfter(currentFireTime);

if (nextAfterCurrent == null) {
Logger.warn(PublishDateUpdater.class,
"Cannot calculate next execution time from cron expression");
return null;
}

// Calculate the interval
final long intervalMillis = nextAfterCurrent.getTime() - currentFireTime.getTime();

// Go back slightly more than the interval to find the previous execution
final long searchBackMillis = (long) (intervalMillis * 1.1);
final Date searchTime = new Date(currentFireTime.getTime() - searchBackMillis);

final Date previousTime = cronExpression.getNextValidTimeAfter(searchTime);

if (previousTime != null && previousTime.before(currentFireTime)) {
return previousTime;
}

Logger.warn(PublishDateUpdater.class,
"Could not determine previous job run time");
return null;

} catch (ParseException e) {
Logger.warn(PublishDateUpdater.class,
"Failed to parse cron expression: " + e.getMessage());
return null;
}
}

/**
* Determines if content should be published based on whether it should have been
* processed in the previous job run.
* This prevents automatic republishing of content that was manually unpublished after its
* scheduled publish date.
*
* Logic:
* - Calculate when the job last ran based on the cron expression
* - If the content's publishDate is BEFORE the last job run time, it means the job
* should have already processed it
* - If it's currently unpublished (live:false) but should have been published,
* it was likely manually unpublished → DON'T republish
*
* @param contentlet The contentlet to check
* @param previousJobRunTime The previous job run time
* @return true if the content should be published, false if it should be skipped
*/
@VisibleForTesting
public static boolean shouldPublishContent(final Contentlet contentlet, final Date previousJobRunTime) {
try {
final ContentType contentType = contentlet.getContentType();
final String publishDateVar = contentType.publishDateVar();

final Date contentPublishDate = (Date) contentlet.get(publishDateVar);

if (!UtilMethods.isSet(previousJobRunTime)) {
Logger.debug(PublishDateUpdater.class,
"Cannot determine previous job run time - will publish contentlet " +
contentlet.getIdentifier());
return true;
}

// If the content's publish date is BEFORE or EQUAL to the previous job run time,
// it means the job should have already processed it in a previous run.
// If it's unpublished now, it was manually unpublished → DON'T republish
if (!contentPublishDate.after(previousJobRunTime)) {
Logger.debug(PublishDateUpdater.class,
String.format("Contentlet %s publishDate (%s) is before previous job run (%s) - skipping republish",
contentlet.getIdentifier(), contentPublishDate, previousJobRunTime));
return false;
}

Logger.debug(PublishDateUpdater.class,
String.format("Contentlet %s publishDate (%s) is after previous job run (%s) - will publish",
contentlet.getIdentifier(), contentPublishDate, previousJobRunTime));
return true;

} catch (Exception e) {
Logger.warn(PublishDateUpdater.class,
"Error checking if content should be published for " + contentlet.getIdentifier() +
" - defaulting to publish: " + e.getMessage(), e);
return true;
}
}

@WrapInTransaction
public static void updatePublishExpireDates(final Date fireTime) throws DotDataException, DotSecurityException {

if(LicenseUtil.getLevel()< LicenseLevel.PROFESSIONAL.level){
return;
}


final User systemUser = APILocator.getUserAPI().getSystemUser();

final List<String> contentTypeVariableWithPublishField = getContentTypeVariableWithPublishField();
Expand All @@ -107,9 +213,17 @@ public static void updatePublishExpireDates(final Date fireTime) throws DotDataE
.search(luceneQueryToPublish, 0, 0,
null, systemUser, false);

Date previousJobRunTime = getPreviousJobRunTime(fireTime);

for (final Contentlet contentlet : contentletToPublish) {
try {
APILocator.getContentletAPI().publish(contentlet, systemUser, false);
if (shouldPublishContent(contentlet, previousJobRunTime)) {
APILocator.getContentletAPI().publish(contentlet, systemUser, false);
} else {
Logger.debug(PublishDateUpdater.class,
"Skipping publish for contentlet " + contentlet.getIdentifier() +
" - content was already auto-published and manually unpublished");
}
} catch (Exception e) {
Logger.debug(PublishDateUpdater.class,
"content failed to publish: " + e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,81 @@ public void testPushPublishWithUniqueField() throws Exception {

}



/**
* Method to test: {@link com.dotcms.enterprise.publishing.PublishDateUpdater#shouldPublishContent(Contentlet, Date)}
*
* Given Scenario: Content with a publish date in the past
* Expected Result: Should be auto-published or not depending on the content publish date field
*
* @throws Exception
*/
@Test
public void test_shouldNotRepublish() throws Exception {


final Field publishField = new FieldDataGen().defaultValue(null)
.type(DateTimeField.class).next();

ContentType contentType = new ContentTypeDataGen()
.field(publishField)
.publishDateFieldVarName(publishField.variable())
.nextPersisted();

Contentlet contentlet = null;

try {
// Create a publish date 10 minutes in the past (simulating content that should have been published)
final Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, -10);
final Date publishDateInPast = calendar.getTime();

// Create contentlet with publish date in the past
contentlet = new ContentletDataGen(contentType)
.setProperty(publishField.variable(), publishDateInPast)
.nextPersisted();


assertFalse("Content should not be live initially", contentlet.isLive());


final Date currentFireTime = new Date();


final Date previousJobRunTime = PublishDateUpdater.getPreviousJobRunTime(currentFireTime);
assertNotNull("Previous job run time should be calculated", previousJobRunTime);
assertTrue("Previous run time should be before current time",
previousJobRunTime.before(currentFireTime));


final boolean shouldPublish = PublishDateUpdater.shouldPublishContent(contentlet, previousJobRunTime);

assertFalse("Content with publish date before previous job run should NOT be republished",
shouldPublish);

final long timeBetween = previousJobRunTime.getTime() +
((currentFireTime.getTime() - previousJobRunTime.getTime()) / 2);
final Date recentPublishDate = new Date(timeBetween);

final Contentlet recentContentlet = new ContentletDataGen(contentType)
.setProperty(publishField.variable(), recentPublishDate)
.nextPersisted();

final boolean shouldPublishRecent = PublishDateUpdater.shouldPublishContent(
recentContentlet, previousJobRunTime);

assertTrue("Content with publish date between previous job run and now SHOULD be published",
shouldPublishRecent);

ContentletDataGen.remove(recentContentlet);

} finally {
ContentletDataGen.remove(contentlet);
ContentTypeDataGen.remove(contentType);
}
}

private PushResult pushFolderPage(final String bundleName, final FolderPage folderPage, final User user, final PPBean ppBean)
throws DotDataException, DotPublisherException, InstantiationException, IllegalAccessException, DotSecurityException {

Expand Down