Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 31141 Automated Unpublishing Fails for Content with Inactive/Deleted Last Modified Users #31308

Merged
Original file line number Diff line number Diff line change
@@ -1,16 +1,52 @@
/*

* Licensed to dotCMS LLC under the dotCMS Enterprise License (the
* “Enterprise License”) found below
*
* Copyright (c) 2023 dotCMS Inc.
*
* With regard to the dotCMS Software and this code:
*
* This software, source code and associated documentation files (the
* "Software") may only be modified and used if you (and any entity that
* you represent) have:
*
* 1. Agreed to and are in compliance with, the dotCMS Subscription Terms
* of Service, available at https://www.dotcms.com/terms (the “Enterprise
* Terms”) or have another agreement governing the licensing and use of the
* Software between you and dotCMS. 2. Each dotCMS instance that uses
* enterprise features enabled by the code in this directory is licensed
* under these agreements and has a separate and valid dotCMS Enterprise
* server key issued by dotCMS.
*
* Copyright (c) 2025 dotCMS LLC
* Use of this software is governed by the Business Source License included
* in the LICENSE file found at in the root directory of software.
* SPDX-License-Identifier: BUSL-1.1
* Subject to these terms, you are free to modify this Software and publish
* patches to the Software if you agree that dotCMS and/or its licensors
* (as applicable) retain all right, title and interest in and to all such
* modifications and/or patches, and all such modifications and/or patches
* may only be used, copied, modified, displayed, distributed, or otherwise
* exploited with a valid dotCMS Enterprise license for the correct number
* of dotCMS instances. You agree that dotCMS and/or its licensors (as
* applicable) retain all right, title and interest in and to all such
* modifications. You are not granted any other rights beyond what is
* expressly stated herein. Subject to the foregoing, it is forbidden to
* copy, merge, publish, distribute, sublicense, and/or sell the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* For all third party components incorporated into the dotCMS Software,
* those components are licensed under the original license provided by the
* owner of the applicable component.

*/

package com.dotcms.enterprise.publishing;

import static com.dotcms.content.elasticsearch.business.ESMappingAPIImpl.publishExpireESDateTimeFormat;

import com.dotcms.business.WrapInTransaction;
import com.dotcms.content.elasticsearch.constants.ESMappingConstants;
import com.dotcms.enterprise.LicenseUtil;
Expand All @@ -21,20 +57,16 @@
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
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 com.liferay.util.StringUtil;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import graphql.VisibleForTesting;
import org.apache.commons.lang3.StringUtils;
import static com.dotcms.content.elasticsearch.business.ESMappingAPIImpl.publishExpireESDateTimeFormat;


public class PublishDateUpdater {
Expand Down Expand Up @@ -77,7 +109,7 @@ public static void updatePublishExpireDates(final Date fireTime) throws DotDataE

for (final Contentlet contentlet : contentletToPublish) {
try {
APILocator.getContentletAPI().publish(contentlet, locker(contentlet), false);
APILocator.getContentletAPI().publish(contentlet, systemUser, false);
} catch (Exception e) {
Logger.debug(PublishDateUpdater.class,
"content failed to publish: " + e.getMessage());
Expand All @@ -93,10 +125,10 @@ public static void updatePublishExpireDates(final Date fireTime) throws DotDataE

for(final Contentlet contentlet : contentletToUnPublish) {
try {
APILocator.getContentletAPI().unpublish(contentlet, locker(contentlet), false);
APILocator.getContentletAPI().unpublish(contentlet, systemUser, false);
}
catch(Exception e){
Logger.debug(PublishDateUpdater.class, "content failed to publish: " + e.getMessage());
Logger.debug(PublishDateUpdater.class, "content failed to unpublish: " + e.getMessage());
}
}
}
Expand All @@ -120,26 +152,6 @@ private static String getLuceneQuery(final String luceneQueryTemplate, final Obj
return String.format(luceneQueryTemplate, parameters );
}

/**
*
* @param contentlet
* @return
* @throws DotDataException
*/
private static User locker(final Contentlet contentlet) throws DotDataException {

User locker = APILocator.getUserAPI().getSystemUser();
try {
User modUser = APILocator.getUserAPI()
.loadUserById(contentlet.getModUser(), locker, false);
if (APILocator.getContentletAPI().canLock(contentlet, locker)) {
locker = modUser;
}
} catch (Exception userEx) {
Logger.error(PublishDateUpdater.class, userEx.getMessage(), userEx);
}

return locker;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,18 @@
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.beans.Host;
import com.dotmarketing.beans.Permission;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.PermissionAPI;
import com.dotmarketing.business.*;
import com.dotmarketing.exception.AlreadyExistException;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.contentlet.business.ContentletAPI;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.portlets.contentlet.model.IndexPolicy;
import com.dotmarketing.portlets.folders.business.FolderAPI;
import com.dotmarketing.portlets.folders.model.Folder;
import com.dotmarketing.portlets.htmlpageasset.model.HTMLPageAsset;
import com.dotmarketing.portlets.languagesmanager.model.Language;
import com.dotmarketing.portlets.structure.model.Relationship;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.UtilMethods;
import com.google.common.collect.ImmutableMap;
Expand All @@ -51,6 +53,7 @@
import java.io.IOException;
import java.util.*;

import org.jetbrains.annotations.NotNull;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
Expand All @@ -60,7 +63,7 @@
import java.util.concurrent.ExecutionException;

import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;

/**
* Test the PushedAssetsAPI
Expand Down Expand Up @@ -333,6 +336,136 @@ public void dontAutoPublishContentWithoutPublishDate() throws DotDataException,
}
}

/**
* Method to test: {@link com.dotcms.enterprise.publishing.PublishDateUpdater#updatePublishExpireDates(Date)}
* When:
* - Create a ContentType with expire date field
* - Create a {@link Contentlet} with a expire date set, publish it
* Should: The {@link Contentlet} should be unpublished and system user should be the one executing the action
*
* @throws DotDataException
* @throws DotSecurityException
*/
@Test
public void autoUnpublishContent() throws DotDataException, DotSecurityException, InterruptedException {

//Create a datetime field to be used as expire field
final Field expiresField = new FieldDataGen().defaultValue(null)
.type(DateTimeField.class).next();

//Create Content Type without Expire Field set
ContentType contentType = new ContentTypeDataGen()
.field(expiresField)
.nextPersisted();

Contentlet contentlet = null;

try {
//Create a date to be used as expire date value
final Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -1);
final Date expireDate = calendar.getTime();

//Create Contentlet
contentlet = new ContentletDataGen(contentType)
.setProperty(expiresField.variable(), expireDate)
.nextPersisted();

ContentletDataGen.publish(contentlet);

assertTrue(contentlet.isLive());

//Add the Expire Field at content type level
final ContentTypeBuilder builder = ContentTypeBuilder.builder(contentType);
builder.expireDateVar(expiresField.variable());
contentType = APILocator.getContentTypeAPI(APILocator.systemUser()).save(builder.build());

//To give some time to the system to update the identifier (IdenfierDateJob)
Thread.sleep(1000);

//Check if the content type has the expire field
assertTrue(contentType.expireDateVar().equals(expiresField.variable()));

//Run the function to auto publish and expire content
PublishDateUpdater.updatePublishExpireDates(new Date());

//Check if the contentlet was unpublished
contentlet = APILocator.getContentletAPI().checkout(contentlet.getInode(), APILocator.systemUser(), false);
assertFalse(contentlet.isLive());

} finally {

ContentletDataGen.remove(contentlet);
ContentTypeDataGen.remove(contentType);

}
}

/**
* Method to test: {@link com.dotcms.enterprise.publishing.PublishDateUpdater#updatePublishExpireDates(Date)}
* When:
* - Create a ContentType with publish date field
* - Create a {@link Contentlet} with a publish date set
* Should: The {@link Contentlet} should be publish and system user should be the one executing the action
*
* @throws DotDataException
* @throws DotSecurityException
*/
@Test
public void autoPublishContent() throws DotDataException, DotSecurityException, InterruptedException {

//Create a datetime field to be used as publish field
final Field publishField = new FieldDataGen().defaultValue(null)
.type(DateTimeField.class).next();

//Create Content Type without publish field set
ContentType contentType = new ContentTypeDataGen()
.field(publishField)
.nextPersisted();

Contentlet contentlet = null;

try {
//Create a date to be used as publish date value
final Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -1);
final Date publishDate = calendar.getTime();

//Create Contentlet
contentlet = new ContentletDataGen(contentType)
.setProperty(publishField.variable(), publishDate)
.nextPersisted();


assertFalse(contentlet.isLive());

//Add the publish Field at content type level
final ContentTypeBuilder builder = ContentTypeBuilder.builder(contentType);
builder.publishDateVar(publishField.variable());
contentType = APILocator.getContentTypeAPI(APILocator.systemUser()).save(builder.build());

//To give some time to the system to update the identifier (IdenfierDateJob)
Thread.sleep(1000);

//Check if the content type has the publish field
assertTrue(contentType.publishDateVar().equals(publishField.variable()));

//Run the function to auto publish and expire content
PublishDateUpdater.updatePublishExpireDates(new Date());

//Check if the contentlet was published
contentlet = APILocator.getContentletAPI().search(contentlet.getIdentifier(), 0, -1, null, APILocator.systemUser(), false).get(0);

assertTrue(contentlet.isLive());

} finally {

ContentletDataGen.remove(contentlet);
ContentTypeDataGen.remove(contentType);

}
}

private FolderPage createNewPage (final FolderPage folderPage, final User user) throws Exception {

final HTMLPageAsset page = PublisherTestUtil.createPage(folderPage.folder, user);
Expand Down Expand Up @@ -590,4 +723,6 @@ private static void createFilter() {
"Reviewer,dotcms.org.2789");
APILocator.getPublisherAPI().addFilterDescriptor(filterDescriptor);
}
}


}
Loading