diff --git a/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java b/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java index 28c7feb8543..abca007cffc 100644 --- a/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java +++ b/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java @@ -6796,6 +6796,11 @@ private void updateTemplateInAllLanguageVersions(final Contentlet contentlet, fi if (contentlet.getBoolProperty(DO_NOT_UPDATE_TEMPLATES)) { return; } + // Template propagation is irrelevant for archived pages. Returning early here also guards + // against a DotDataException when an already-archived page version is checked in directly. + if (contentlet.isArchived()) { + return; + } if (UtilMethods.isSet(contentlet.getIdentifier())) { final Optional templateField = contentlet @@ -6821,9 +6826,10 @@ private void updateTemplateInAllLanguageVersions(final Contentlet contentlet, fi "Contentlet with ID '%s' has not been found: ", contentlet.getIdentifier())); } else if (contentletByIdentifierAnyLanguageArchived.isArchived()) { - throw new DotDataException(String.format( - "Contentlet is currently marked as 'Archived'.", - contentlet.getIdentifier())); + Logger.warn(ESContentletAPIImpl.class, String.format( + "Skipping template propagation: all existing versions of Contentlet" + + " '%s' are archived.", contentlet.getIdentifier())); + return; } else { return; } diff --git a/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImplTest.java b/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImplTest.java index df8e6b79727..7e6d849ff2d 100644 --- a/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImplTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImplTest.java @@ -721,27 +721,24 @@ public void testGetArchivedRelatedChild() /** * Method to test: {@link ESContentletAPIImpl#checkin(Contentlet, User, boolean)} - * Given Scenario: Try to check-in a version of an archived page with {@link Contentlet#DONT_VALIDATE_ME} flag on - * ExpectedResult: The method should not throw a {@link NullPointerException}, but a {@link DotDataException} because - * we are trying to check-in a version of an archived content + * Given Scenario: Check-in a new version of an archived page with {@link Contentlet#DONT_VALIDATE_ME} flag on. + * ExpectedResult: The check-in should succeed without throwing. Template propagation is skipped for + * archived pages (an archived page is not served, so its template does not need to be synced across + * language versions). This previously threw a {@link DotDataException}, which aborted push-publishing + * bundles containing archived multi-language HTML pages - see #36051. It must also not throw a + * {@link NullPointerException}. */ @Test - public void testCheckInArchivedPageShouldThrowDotDataException() { + public void testCheckInArchivedPageSkipsTemplatePropagation() { final Contentlet contentlet = TestDataUtils .getPageContent(true, APILocator.getLanguageAPI().getDefaultLanguage().getId()); ContentletDataGen.archive(contentlet); final Contentlet newVersion = ContentletDataGen.checkout(contentlet); newVersion.setBoolProperty(Contentlet.DONT_VALIDATE_ME, true); - try { - ContentletDataGen.checkin(newVersion); - fail(); - } catch (Exception e){ - if (!(e.getCause() instanceof DotDataException)){ - fail(); - } - } + final Contentlet result = ContentletDataGen.checkin(newVersion); + assertNotNull(result); } /** diff --git a/dotcms-integration/src/test/java/com/dotcms/publisher/business/PublisherTest.java b/dotcms-integration/src/test/java/com/dotcms/publisher/business/PublisherTest.java index e8d96757d91..ffff5463166 100644 --- a/dotcms-integration/src/test/java/com/dotcms/publisher/business/PublisherTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/publisher/business/PublisherTest.java @@ -37,6 +37,7 @@ 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.templates.model.Template; import com.dotmarketing.portlets.structure.model.Relationship; import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; @@ -286,6 +287,99 @@ public void testPushArchivedAndMultiLanguageContent() throws Exception { } } + /** + * Method to test: {@link com.dotcms.content.elasticsearch.business.ESContentletAPIImpl#updateTemplateInAllLanguageVersions(Contentlet, User)} + * exercised through push publishing on the receiver. + * Given Scenario: + * - Create an HTML Page (which has a template field) with two language versions (default + Spanish) + * - Archive both language versions + * - Push publish the page; the receiver processes each language version sequentially, archiving + * the first before the second is checked in + * Should: The bundle is processed successfully (no {@code DotDataException: Contentlet is currently + * marked as 'Archived'.}) and both language versions are archived on the receiver. Reproduces #36051. + */ + @Test + public void testPushArchivedMultiLanguageHTMLPage() throws Exception { + + final User systemUser = APILocator.getUserAPI().getSystemUser(); + PPBean ppBean = null; + Contentlet resultContentlet = null; + Template template = null; + + final Language defaultLanguage = APILocator.getLanguageAPI().getDefaultLanguage(); + final Language spanishLanguage = TestDataUtils.getSpanishLanguage(); + + try { + + final User adminUser = APILocator.getUserAPI().loadByUserByEmail("admin@dotcms.com", + systemUser, false); + final Host host = APILocator.getHostAPI().findDefaultHost(systemUser, false); + + ppBean = createPushPublishEnv(adminUser); + assertPPBean(ppBean); + + // Create a template + folder + HTML page in the default language + template = new Template(); + template.setTitle("template-36051-" + System.currentTimeMillis()); + template.setBody(" I'm mostly empty "); + template = APILocator.getTemplateAPI().saveTemplate(template, host, systemUser, false); + + final Folder folder = PublisherTestUtil.createFolder( + "folder-36051-" + System.currentTimeMillis()); + final HTMLPageAsset page = new HTMLPageDataGen(folder, template) + .languageId(defaultLanguage.getId()).nextPersisted(); + + // Create the Spanish language version of the same page + final Contentlet spanishPage = ContentletDataGen.checkout(page); + spanishPage.setLanguageId(spanishLanguage.getId()); + ContentletDataGen.checkin(spanishPage); + + // Archive both language versions + ContentletDataGen.archive(page); + ContentletDataGen.archive(spanishPage); + + final String pageIdentifier = page.getIdentifier(); + + // Generate the bundle from the archived multi-language page + final Map bundleData = generateContentBundle( + "archived-multi-language-htmlpage-test-1", adminUser, ppBean, page); + assertNotNull(bundleData); + assertNotNull(bundleData.get(PublisherTestUtil.FILE)); + + // Remove the page so the receiver re-creates every version from the bundle + APILocator.getContentletAPI().destroy(page, adminUser, false); + + // Publish the bundle on the receiver - this used to fail with + // DotDataException: Contentlet is currently marked as 'Archived'. + final PublisherConfig publisherConfig = publishContentBundle( + (File) bundleData.get(PublisherTestUtil.FILE), ppBean.endPoint); + assertNotNull(publisherConfig); + assertEquals(((File) bundleData.get(PublisherTestUtil.FILE)).getName(), + publisherConfig.getId()); + + // Both language versions must exist and be archived on the receiver + resultContentlet = APILocator.getContentletAPI() + .findContentletByIdentifier(pageIdentifier, false, defaultLanguage.getId(), + adminUser, false); + assertNotNull(resultContentlet); + assertTrue(resultContentlet.isArchived()); + + final Contentlet spanishResult = APILocator.getContentletAPI() + .findContentletByIdentifier(pageIdentifier, false, spanishLanguage.getId(), + adminUser, false); + assertNotNull(spanishResult); + assertTrue(spanishResult.isArchived()); + + } finally { + + deleteTestPPData(systemUser, null, ppBean, resultContentlet); + if (UtilMethods.isSet(template) && UtilMethods.isSet(template.getInode())) { + APILocator.getTemplateAPI().delete(template, systemUser, false); + } + + } + } + /** * Method to test: {@link com.dotcms.enterprise.publishing.PublishDateUpdater#updatePublishExpireDates(Date)} * When: