diff --git a/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentFactoryImpl.java b/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentFactoryImpl.java index 61de303f746e..8f18a2353ee6 100644 --- a/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentFactoryImpl.java +++ b/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentFactoryImpl.java @@ -1231,7 +1231,12 @@ protected Contentlet findContentletByIdentifier(final String identifier, final l throws DotDataException { final String variant = UtilMethods.isSet(variantId) ? variantId : DEFAULT_VARIANT.name(); - + // Perhaps we should exclude here contents with only a working version and no live version whatsoever + // To match what we did in the old time machine days + // This logic is tied with a fragment of VTL code in ContainerLoader.java check it out + // Since it basically iterates over the results of this method checks the sysPublishDate and sysExpireDate + // and then adds the contentlet to the container if a live version is found and the dates are correct + // But for the new time machine we allow the user to see the working version dropped on the container final String query = "SELECT c.*, ci.owner \n" + "FROM contentlet c\n" + "INNER JOIN identifier i ON i.id = c.identifier\n" diff --git a/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/ContainerLoader.java b/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/ContainerLoader.java index a27f10601996..bd8ce004664c 100644 --- a/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/ContainerLoader.java +++ b/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/ContainerLoader.java @@ -4,6 +4,7 @@ import com.dotcms.contenttype.exception.NotFoundInDbException; import com.dotcms.contenttype.model.type.ContentType; import com.dotcms.exception.ExceptionUtil; +import com.dotcms.rest.api.v1.page.PageResource; import com.dotmarketing.beans.ContainerStructure; import com.dotmarketing.beans.Host; import com.dotmarketing.business.APILocator; @@ -260,52 +261,65 @@ private InputStream buildVelocity(final Container container, final String uuid, velocityCodeBuilder.append(editWrapperDiv); velocityCodeBuilder.append("#end"); } + // WARNING: Make no changes to the variable names used here without having checked first its usage + // e.g. ContentletDetail.java uses _show_working_ + + //Let's find out if the time-machine attribute is set + velocityCodeBuilder.append("#set($_timeMachineOn=false)") + .append("#if($UtilMethods.isSet($request.getSession(false)) && $request.session.getAttribute(\""+PageResource.TM_DATE+"\"))") + .append("#set($_timeMachineOn=true)") + .append("#set($_tmdate=$date.toDate($webapi.parseLong($request.session.getAttribute(\""+PageResource.TM_DATE+"\"))))") + .append("#end") + .append("#if($request.getAttribute(\""+PageResource.TM_DATE+"\"))") + .append("#set($_timeMachineOn=true)") + .append("#set($_tmdate=$date.toDate($webapi.parseLong($request.getAttribute(\""+PageResource.TM_DATE+"\"))))") + .append("#end"); // FOR LOOP - - velocityCodeBuilder.append("#foreach ($contentletId in $CONTENTLETS )"); - - velocityCodeBuilder.append("#set($dotContentMap=$dotcontent.load($contentletId))"); - velocityCodeBuilder.append("#set($_show_working_=false)"); - - //Time-machine block begin - velocityCodeBuilder.append("#if($UtilMethods.isSet($request.getSession(false)) && $request.session.getAttribute(\"tm_date\"))"); - - velocityCodeBuilder.append("#set($_tmdate=$date.toDate($webapi.parseLong($request.session.getAttribute(\"tm_date\"))))"); - velocityCodeBuilder.append("#set($_ident=$webapi.findIdentifierById($contentletId))"); - // if the content has expired we rewrite the identifier so it isn't loaded - velocityCodeBuilder.append("#if($UtilMethods.isSet($_ident.sysExpireDate) && $_tmdate.after($_ident.sysExpireDate))"); - velocityCodeBuilder.append("#set($contentletId='')"); + velocityCodeBuilder + .append("#foreach ($contentletId in $CONTENTLETS )") + .append("#set($dotContentMap=$dotcontent.load($contentletId))") + .append("#set($_show_working_=false)"); + + //Time-machine block begin + velocityCodeBuilder.append("#if($_timeMachineOn) "); + + velocityCodeBuilder + .append("#set($_ident=$webapi.findIdentifierById($contentletId))"); + + // if the content has expired we rewrite the identifier, so it isn't loaded + velocityCodeBuilder + .append("#if($UtilMethods.isSet($_ident.sysExpireDate) && $_tmdate.after($_ident.sysExpireDate))") + .append("#set($contentletId='')") + .append("#end"); + + // if the content should be published then force to show the working version + velocityCodeBuilder + .append("#if($UtilMethods.isSet($_ident.sysPublishDate) && ($_tmdate.equals($_ident.sysPublishDate) || $_tmdate.after($_ident.sysPublishDate)))") + .append("#set($_show_working_=true)") + .append("#end"); + + velocityCodeBuilder.append("#if(!$UtilMethods.isSet($user)) ") + .append("#set($user = $cmsuser.getLoggedInUser($request)) ") + .append("#end"); + //end of time-machine block velocityCodeBuilder.append("#end"); - // if the content should be published then force to show the working version - velocityCodeBuilder.append("#if($UtilMethods.isSet($_ident.sysPublishDate) && ($_tmdate.equals($_ident.sysPublishDate) || $_tmdate.after($_ident.sysPublishDate)))"); - velocityCodeBuilder.append("#set($_show_working_=true)"); - velocityCodeBuilder.append("#end"); - - velocityCodeBuilder.append("#if(! $webapi.contentHasLiveVersion($contentletId) && ! $_show_working_)") - .append("#set($contentletId='')") // working contentlet still not published - .append("#end"); - - velocityCodeBuilder.append("#if(!$UtilMethods.isSet($user)) ") - .append("#set($user = $cmsuser.getLoggedInUser($request)) ") - .append("#end"); - //end of time-machine block - velocityCodeBuilder.append("#end"); - - velocityCodeBuilder.append("#set($CONTENT_INODE = '')"); - velocityCodeBuilder.append("#set($CONTENT_BASE_TYPE = '')"); - velocityCodeBuilder.append("#set($CONTENT_LANGUAGE = '')"); - velocityCodeBuilder.append("#set($ContentletTitle = '')"); - velocityCodeBuilder.append("#set($CONTENT_TYPE_ID = '')"); - velocityCodeBuilder.append("#set($CONTENT_TYPE = '')"); - velocityCodeBuilder.append("#set($CONTENT_VARIANT = '')"); - velocityCodeBuilder.append("#set($ON_NUMBER_OF_PAGES = '')"); - - // read in the content - velocityCodeBuilder.append("#if($contentletId != '')"); - velocityCodeBuilder.append("#contentDetail($contentletId)"); - velocityCodeBuilder.append("#end"); + velocityCodeBuilder + .append("#set($CONTENT_INODE = '')") + .append("#set($CONTENT_BASE_TYPE = '')") + .append("#set($CONTENT_LANGUAGE = '')") + .append("#set($ContentletTitle = '')") + .append("#set($CONTENT_TYPE_ID = '')") + .append("#set($CONTENT_TYPE = '')") + .append("#set($CONTENT_VARIANT = '')") + .append("#set($ON_NUMBER_OF_PAGES = '')"); + + // read in the content + velocityCodeBuilder + .append("#if($contentletId != '')") + .append("#contentDetail($contentletId)") + .append("#end"); velocityCodeBuilder.append("#set($HAVE_A_VERSION=($CONTENT_INODE != ''))"); @@ -354,13 +368,13 @@ private InputStream buildVelocity(final Container container, final String uuid, for (int i = 0; i < containerContentTypeList.size(); i++) { ContainerStructure cs = containerContentTypeList.get(i); - String ifelse = (i == 0) ? "if" : "elseif"; - velocityCodeBuilder.append("#").append(ifelse) + String ifElse = (i == 0) ? "if" : "elseif"; + velocityCodeBuilder.append("#").append(ifElse) .append("($ContentletStructure ==\"") .append(cs.getStructureId()).append("\")"); velocityCodeBuilder.append(cs.getCode()); } - if (containerContentTypeList.size() > 0) { + if (!containerContentTypeList.isEmpty()) { velocityCodeBuilder.append("#end"); } @@ -375,11 +389,11 @@ private InputStream buildVelocity(final Container container, final String uuid, velocityCodeBuilder.append("#end"); // end content dot-data-content - if (mode == PageMode.EDIT_MODE) { - velocityCodeBuilder.append(""); - } + if (mode == PageMode.EDIT_MODE) { + velocityCodeBuilder.append(""); + } - velocityCodeBuilder.append("#set($dotContentMap='')"); + velocityCodeBuilder.append("#set($dotContentMap='')"); // ##End of foreach loop velocityCodeBuilder.append("#end"); diff --git a/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/ContentletLoader.java b/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/ContentletLoader.java index 4c461cb591dc..b48ff0fcf06c 100644 --- a/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/ContentletLoader.java +++ b/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/ContentletLoader.java @@ -18,6 +18,7 @@ import com.dotcms.contenttype.model.field.TimeField; import com.dotcms.contenttype.model.type.BaseContentType; import com.dotcms.contenttype.model.type.ContentType; +import static com.dotcms.util.FunctionUtils.getOrDefault; import com.dotcms.util.JsonUtil; import com.dotcms.variant.business.web.VariantWebAPI.RenderContext; import com.dotmarketing.business.APILocator; @@ -42,8 +43,6 @@ import com.liferay.portal.model.User; import com.liferay.util.FileUtil; import io.vavr.control.Try; -import org.apache.velocity.exception.ResourceNotFoundException; - import java.io.IOException; import java.io.InputStream; import java.util.Collections; @@ -53,6 +52,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import org.apache.velocity.exception.ResourceNotFoundException; /** * Provides the Velocity Engine with the objects that are or can be used when rendering content. @@ -634,36 +634,35 @@ public void invalidate(Structure structure) throws DotDataException, DotSecurity public InputStream writeObject(final VelocityResourceKey key) throws DotStateException, DotDataException, DotSecurityException { - long language = Long.valueOf(key.language); + final long language = Long.parseLong(key.language); final RenderContext renderContext = WebAPILocator.getVariantWebAPI() .getRenderContext(language, key.id1, key.mode, APILocator.systemUser()); Optional info = APILocator.getVersionableAPI().getContentletVersionInfo(key.id1, renderContext.getCurrentLanguageId(), renderContext.getCurrentVariantKey()); - Contentlet contentlet = (key.mode.showLive) - ? APILocator.getContentletAPI().find(info.get().getLiveInode(), - APILocator.systemUser(), false) - : APILocator.getContentletAPI().find(info.get().getWorkingInode(), + if(info.isEmpty()){ + throw new ResourceNotFoundException("cannot find content version info for key: " + key); + } + + final ContentletVersionInfo contentletVersionInfo = info.get(); + final String inode = (key.mode.showLive) + ? contentletVersionInfo.getLiveInode() + : contentletVersionInfo.getWorkingInode(); + + final Contentlet contentlet = + APILocator.getContentletAPI().find(inode, APILocator.systemUser(), false); - Logger.debug(this, "DotResourceLoader:\tWriting out contentlet inode = " + contentlet.getInode()); + final String liveWorkingLabel = getOrDefault(key.mode.showLive,()->"live",()->"working"); + + Logger.debug(this, String.format("DotResourceLoader:\tWriting out contentlet \"%s\" inode = %s", liveWorkingLabel, inode)); if (null == contentlet) { throw new ResourceNotFoundException("cannot find content for: " + key); } return buildVelocity(contentlet, key.mode, key.path); } - private boolean shouldCheckForVersionInfoInDefaultLanguage(long language) { - return language != defaultLang && APILocator.getLanguageAPI() - .canDefaultContentToDefaultLanguage(); - } - - private boolean isLiveVersionNotAvailable(VelocityResourceKey key, ContentletVersionInfo info) { - return info == null || key.mode.showLive && !UtilMethods.isSet(info.getLiveInode()); - } - - @Override public void invalidate(Object obj, PageMode mode) { Contentlet asset = (Contentlet) obj; diff --git a/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/PageRenderUtil.java b/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/PageRenderUtil.java index d1daac3664c2..339d0c2dcd63 100644 --- a/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/PageRenderUtil.java +++ b/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/PageRenderUtil.java @@ -252,6 +252,12 @@ private List populateContainers() throws DotDataException, DotSecu final HttpServletRequest request = HttpServletRequestThreadLocal.INSTANCE.getRequest(); final Date timeMachineDate = timeMachineDate(request).orElseGet(()->null); + if(null != timeMachineDate){ + Logger.debug(this, "Time Machine date found cache must be evicted: " + timeMachineDate); + // for future time machine we need to invalidate the cache for there can be precalculated content + // that was loaded before without using time machine date see https://github.com/dotCMS/core/issues/31061 + new PageLoader().invalidate(htmlPage, mode); + } final boolean live = this.isLive(request); final String currentVariantId = WebAPILocator.getVariantWebAPI().currentVariantId(); final Table> pageContents = this.multiTreeAPI @@ -412,7 +418,8 @@ private void addRelationships(final Contentlet contentlet) { } private Contentlet getContentletByVariantFallback(final String currentVariantId, - final PersonalizedContentlet personalizedContentlet, final Date timeMachineDate) { + final PersonalizedContentlet personalizedContentlet, final Date timeMachineDate) + throws DotSecurityException { final Contentlet contentlet = this.getContentlet(personalizedContentlet, currentVariantId, timeMachineDate); @@ -539,7 +546,7 @@ private boolean needParseContainerPrefix(final Container container, final String * Personalized Contentlet object. */ private Contentlet getContentlet(final PersonalizedContentlet personalizedContentlet, - final String variantName, final Date timeMachineDate) { + final String variantName, final Date timeMachineDate) throws DotSecurityException { try { return Config.getBooleanProperty("DEFAULT_CONTENT_TO_DEFAULT_LANGUAGE", false) ? @@ -551,7 +558,7 @@ private Contentlet getContentlet(final PersonalizedContentlet personalizedConten // Page that is holding it without any problems return limitedUserPermissionFallback(personalizedContentlet.getContentletId()); } - throw new DotStateException(se); + throw se; } } diff --git a/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/content/util/ContentUtils.java b/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/content/util/ContentUtils.java index 292dd663c0e1..273094484b95 100644 --- a/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/content/util/ContentUtils.java +++ b/dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/content/util/ContentUtils.java @@ -83,7 +83,7 @@ public ContentUtils getInstance() */ public static Contentlet find(final String inodeOrIdentifier, final User user, final boolean EDIT_OR_PREVIEW_MODE, final long sessionLang){ final Optional timeMachineDate = TimeMachineUtil.getTimeMachineDate(); - return find(inodeOrIdentifier,user,timeMachineDate.orElse(null),EDIT_OR_PREVIEW_MODE, sessionLang); + return find(inodeOrIdentifier, user, timeMachineDate.orElse(null), EDIT_OR_PREVIEW_MODE, sessionLang); } private static Contentlet fixRecurringDates(Contentlet contentlet, String[] recDates) { diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java index f839f82b1e84..796824b8f03c 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java @@ -483,6 +483,7 @@ private void setUpTimeMachineIfPresent(final PageRenderParams renderParams) { session.setAttribute(TM_DATE, timeMachineEpochMillis); session.setAttribute(TM_LANG, renderParams.languageId()); session.setAttribute(DOT_CACHE, "refresh"); + session.setAttribute(TM_HOST, host.get()); } else { request.setAttribute(TM_DATE, timeMachineEpochMillis); request.setAttribute(TM_LANG, renderParams.languageId()); diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/page/HTMLPageAssetRenderedBuilder.java b/dotCMS/src/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/page/HTMLPageAssetRenderedBuilder.java index 20523d9a2cf5..1a4c22a796b5 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/page/HTMLPageAssetRenderedBuilder.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/page/HTMLPageAssetRenderedBuilder.java @@ -154,12 +154,11 @@ public PageView build(final boolean rendered, final PageMode mode) throws DotDat // (unless host is specified in the dotParse) github 14624 final RenderParams params=new RenderParams(user,language, site, mode); request.setAttribute(RenderParams.RENDER_PARAMS_ATTRIBUTE, params); - final User systemUser = APILocator.getUserAPI().getSystemUser(); final boolean canEditTemplate = this.permissionAPI.doesUserHavePermission(template, PermissionLevel.EDIT.getType(), user); final boolean canCreateTemplates = layoutAPI.doesUserHaveAccessToPortlet("templates", user); final PageRenderUtil pageRenderUtil = new PageRenderUtil( - this.htmlPageAsset, systemUser, mode, language.getId(), this.site); + this.htmlPageAsset, user, mode, language.getId(), this.site); final Optional urlContentletOpt = this.findUrlContentlet (request); diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/page/PageResourceTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/page/PageResourceTest.java index 96e5bb8c55a0..ff37d2e99e01 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/page/PageResourceTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/page/PageResourceTest.java @@ -36,6 +36,7 @@ import com.dotcms.datagen.TemplateDataGen; import com.dotcms.datagen.TemplateLayoutDataGen; import com.dotcms.datagen.TestDataUtils; +import com.dotcms.datagen.TestUserUtils; import com.dotcms.datagen.UserDataGen; import com.dotcms.rendering.velocity.viewtools.content.util.ContentUtils; import com.dotcms.repackage.org.apache.struts.config.ModuleConfig; @@ -52,7 +53,10 @@ import com.dotmarketing.beans.Host; import com.dotmarketing.beans.Identifier; import com.dotmarketing.beans.MultiTree; +import com.dotmarketing.beans.Permission; import com.dotmarketing.business.APILocator; +import com.dotmarketing.business.PermissionAPI; +import com.dotmarketing.business.Permissionable; import com.dotmarketing.business.VersionableAPI; import com.dotmarketing.business.web.HostWebAPI; import com.dotmarketing.exception.DotDataException; @@ -101,6 +105,7 @@ import java.time.Instant; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Date; @@ -121,7 +126,6 @@ import javax.servlet.http.HttpSession; import javax.ws.rs.core.Response; import org.elasticsearch.action.search.SearchResponse; -import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -168,10 +172,16 @@ public static void prepare() throws Exception { @Before public void init() throws DotSecurityException, DotDataException, SystemException, PortalException { + user = APILocator.getUserAPI().loadByUserByEmail("admin@dotcms.com", APILocator.getUserAPI().getSystemUser(), false); + hostName = "my.host.com" + System.currentTimeMillis(); + host = new SiteDataGen().name(hostName).nextPersisted(); + initWith(user, host); + } + private void initWith(User user, Host host) + throws DotDataException, DotSecurityException, PortalException, SystemException { pageName = "index" + System.currentTimeMillis(); folderName = "about-us" + System.currentTimeMillis(); - hostName = "my.host.com" + System.currentTimeMillis(); pagePath = String.format("/%s/%s", folderName, pageName); // Collection to store attributes keys/values @@ -182,7 +192,6 @@ public void init() final ModuleConfig moduleConfig = mock(ModuleConfig.class); initDataObject = mock(InitDataObject.class); - user = APILocator.getUserAPI().loadByUserByEmail("admin@dotcms.com", APILocator.getUserAPI().getSystemUser(), false); final PageResourceHelper pageResourceHelper = mock(PageResourceHelper.class); final WebResource webResource = mock(WebResource.class); @@ -195,7 +204,7 @@ public void init() when(webResource.init(false, request, true)).thenReturn(initDataObject); when(webResource.init(any(WebResource.InitBuilder.class))).thenReturn(initDataObject); when(initDataObject.getUser()).thenReturn(user); - host = new SiteDataGen().name(hostName).nextPersisted(); + hostWebAPI = mock(HostWebAPI.class); when(hostWebAPI.getCurrentHost(request, user)).thenReturn(host); when(hostWebAPI.getCurrentHost(request)).thenReturn(host); @@ -247,7 +256,6 @@ public void init() return null; }).when(request).setAttribute(anyString(), Mockito.any()); - Folder aboutUs = APILocator.getFolderAPI().findFolderByPath(String.format("/%s/",folderName), host, APILocator.systemUser(), false); if(null == aboutUs || !UtilMethods.isSet(aboutUs.getIdentifier())) { aboutUs = new FolderDataGen().site(host).name(folderName).nextPersisted(); @@ -1717,4 +1725,151 @@ public static Optional findNode(final JsonNode currentNode, final Stri return Optional.empty(); // Node not found } + /** + * Create a page with a container and a blog contentlet that has a publish-date set but isn't published + * @param title + * @param publishDate + * @return + * @throws DotDataException + * @throws DotSecurityException + * @throws WebAssetException + */ + PageInfo createPageWithWorkingContentAndPublishDateSet(String title, Date publishDate) throws DotDataException, DotSecurityException, WebAssetException { + final User systemUser = APILocator.getUserAPI().getSystemUser(); + final long languageId = 1L; + final ContentType blogLikeContentType = TestDataUtils.getBlogLikeContentType(); + + final Structure structure = new StructureDataGen().nextPersisted(); + final Container myContainer = new ContainerDataGen() + .withStructure(structure, "") + .friendlyName("container-friendly-name" + System.currentTimeMillis()) + .title("container-title") + .site(host) + .nextPersisted(); + + ContainerDataGen.publish(myContainer); + + final TemplateLayout templateLayout = TemplateLayoutDataGen.get() + .withContainer(myContainer.getIdentifier()) + .next(); + + final Template newTemplate = new TemplateDataGen() + .drawedBody(templateLayout) + .withContainer(myContainer.getIdentifier()) + .nextPersisted(); + + final VersionableAPI versionableAPI = APILocator.getVersionableAPI(); + versionableAPI.setWorking(newTemplate); + versionableAPI.setLive(newTemplate); + + final String myFolderName = "folder-" + System.currentTimeMillis(); + final Folder myFolder = new FolderDataGen().name(myFolderName).site(host).nextPersisted(); + final String myPageName = "my-future-tm-test-page-working-saved-content-" + System.currentTimeMillis(); + final HTMLPageAsset myPage = new HTMLPageDataGen(myFolder, newTemplate) + .languageId(languageId) + .pageURL(myPageName) + .title(myPageName) + .nextPersisted(); + + final ContentletAPI contentletAPI = APILocator.getContentletAPI(); + contentletAPI.publish(myPage, systemUser, false); + // These are the blogs that will be shown in the widget + // if it's published then it'll show immediately otherwise it'll show in the future + // if it's set to show then we need to pass the time machine date to show it + //Our blog content type has to have a publishDate field set otherwise it will never make it properly into the index + assertNotNull(blogLikeContentType.publishDateVar()); + final Contentlet blog = new ContentletDataGen(blogLikeContentType.id()) + .languageId(languageId) + .host(host) + .setProperty("title", title) + .setProperty("body", TestDataUtils.BLOCK_EDITOR_DUMMY_CONTENT) + .setPolicy(IndexPolicy.WAIT_FOR) + .languageId(languageId) + .setProperty(Contentlet.IS_TEST_MODE, true) + .setProperty("publishDate", publishDate) + .nextPersisted(); + + final MultiTreeAPI multiTreeAPI = APILocator.getMultiTreeAPI(); + final MultiTree multiTree = new MultiTree(myPage.getIdentifier(), + myContainer.getIdentifier(), blog.getIdentifier(), "1", 1); + multiTreeAPI.saveMultiTree(multiTree); + return new PageInfo(String.format("/%s/%s", myFolderName, myPageName), blog.getIdentifier(), Set.of(blog.getInode())); + } + + /** + * Initialize the test + * @throws SystemException + * @throws DotDataException + * @throws DotSecurityException + * @throws PortalException + */ + private void overrideInitWithLimitedUser() + throws SystemException, DotDataException, DotSecurityException, PortalException { + user = new UserDataGen().roles(TestUserUtils.getFrontendRole()).nextPersisted(); + hostName = "my.host.com" + System.currentTimeMillis(); + host = new SiteDataGen().name(hostName).nextPersisted(); + initWith(user, host); + } + + /** + * Add permission to a user + * @param permissionable + * @param limitedUser + * @param permissionType + * @param permissions + * @throws Exception + */ + private static void addPermission(final Permissionable permissionable, + final User limitedUser, final String permissionType, final int... permissions) throws Exception { + final int permission = Arrays.stream(permissions).sum(); + final Permission permissionObject = new Permission(permissionType, + permissionable.getPermissionId(), + APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(), + permission, true); + + APILocator.getPermissionAPI().save(permissionObject, permissionable, + APILocator.systemUser(), false); + + } + + /** + * Given scenario: A page with a container and a contentlet is created. The contentlet is set to be published in the future. + * But it is saved not published. Therefor it only has a working version. + * Now we use a limited user to render the page. The limited user only has READ permissions on the contentlet. + * But as this contentlet is in working state we should not allow the user to see it. + * Expected result: A Security exception should be thrown resulting in a 403 status code. + * @throws Exception a Security exception should be thrown + */ + @Test(expected = DotSecurityException.class) + public void Test_Rendering_Working_Content_Using_Limited_User() throws Exception{ + overrideInitWithLimitedUser(); + final TimeZone defaultZone = TimeZone.getDefault(); + try { + final TimeZone utc = TimeZone.getTimeZone("UTC"); + TimeZone.setDefault(utc); + final Instant instant = LocalDateTime.now().plusDays(4).atZone(utc.toZoneId()).toInstant(); + final String matchingFutureIso8601 = instant.toString(); + final Date publishDate = Date.from(instant); + final PageInfo pageInfo = createPageWithWorkingContentAndPublishDateSet("Blog in working state with a publish-date set", publishDate); + + HttpServletResponseThreadLocal.INSTANCE.setResponse(this.response); + HttpServletRequestThreadLocal.INSTANCE.setRequest(this.request); + + //This param is required to be live to behave correctly when building the query + when(request.getAttribute(WebKeys.PAGE_MODE_PARAMETER)).thenReturn(PageMode.LIVE); + when(request.getAttribute(com.liferay.portal.util.WebKeys.USER)).thenReturn(user); + addPermission(host, user, PermissionAPI.INDIVIDUAL_PERMISSION_TYPE, PermissionAPI.PERMISSION_READ); + + final Response endpointResponse = pageResource + .loadJson(this.request, this.response, pageInfo.pageUri, PageMode.LIVE.name(), null, + "1", null, matchingFutureIso8601); + + RestUtilTest.verifySuccessResponse(endpointResponse); + + } finally { + TimeZone.setDefault(defaultZone); + } + + } + } diff --git a/test-karate/src/test/java/graphql/ftm/newContent.feature b/test-karate/src/test/java/graphql/ftm/newContent.feature index 64affd0890be..968593603a9e 100644 --- a/test-karate/src/test/java/graphql/ftm/newContent.feature +++ b/test-karate/src/test/java/graphql/ftm/newContent.feature @@ -8,8 +8,10 @@ Background: * def title = __arg.title * def publishDate = __arg.publishDate * def expiresOn = __arg.expiresOn + * def action = __arg.action ? __arg.action : 'PUBLISH' + # Default to PUBLISH if action is not provided - Given url baseUrl + '/api/v1/workflow/actions/default/fire/PUBLISH?indexPolicy=WAIT_FOR' + Given url baseUrl + '/api/v1/workflow/actions/default/fire/' + action + '?indexPolicy=WAIT_FOR' And headers commonHeaders * def requestPayload = buildContentRequestPayload (contentTypeId, title, publishDate, expiresOn) diff --git a/test-karate/src/test/java/graphql/ftm/publishPage.feature b/test-karate/src/test/java/graphql/ftm/publishPage.feature index 68b9b9923732..674ab757fde8 100644 --- a/test-karate/src/test/java/graphql/ftm/publishPage.feature +++ b/test-karate/src/test/java/graphql/ftm/publishPage.feature @@ -2,8 +2,7 @@ Feature: Add pieces of content then Publish the Page Background: * def page_id = __arg.page_id - * def content1_id = __arg.content1_id - * def content2_id = __arg.content2_id + * def content_ids = __arg.content_ids * def container_id = __arg.container_id Scenario: Create a new version of a piece of content @@ -13,7 +12,7 @@ Feature: Add pieces of content then Publish the Page """ [ { - "contentletsId": ["#(content1_id)", "#(content2_id)"], + "contentletsId": "#(content_ids)", "identifier": "#(container_id)", "uuid": "1" } diff --git a/test-karate/src/test/java/graphql/ftm/setup.feature b/test-karate/src/test/java/graphql/ftm/setup.feature index 1d8b6a2f5628..1415993a67b7 100644 --- a/test-karate/src/test/java/graphql/ftm/setup.feature +++ b/test-karate/src/test/java/graphql/ftm/setup.feature @@ -36,7 +36,14 @@ Feature: Setting up the Future Time Machine Test * def newContentPiceOneVersion2 = callonce read('classpath:graphql/ftm/newContentVersion.feature') { contentTypeId: '#(contentTypeId)', identifier: '#(contentPieceOneId)', title: 'test 1 v2 (This ver will be publshed in the future)', publishDate: '#(formattedFutureDateTime)' } * def newContentPiceTwoVersion2 = callonce read('classpath:graphql/ftm/newContentVersion.feature') { contentTypeId: '#(contentTypeId)', identifier: '#(contentPieceTwoId)', title: 'test 2 v2' } - * def pageUrl = 'ftm-test-page' + Math.floor(Math.random() * 10000) + # Lets create a new non-published piece of content wiht a publish date in the future + * def nonPublishedPieceResult = callonce read('classpath:graphql/ftm/newContent.feature') { contentTypeId: '#(contentTypeId)', title: 'Working version Only! with publish date', publishDate: '#(formattedFutureDateTime)', action: 'NEW' } + * def nonPublishedPiece = nonPublishedPieceResult.response.entity.results + * def nonPublishedPieceId = nonPublishedPiece.map(result => Object.keys(result)[0]) + * def nonPublishedPieceId = nonPublishedPieceId[0] + + * def suffix = Math.floor(Math.random() * 10000) + * def pageUrl = 'ftm-test-page' + suffix # Finally lets create a new page * def createPageResult = callonce read('classpath:graphql/ftm/newPage.feature') { pageUrl:'#(pageUrl)' ,title: 'Future Time Machine Test page', templateId:'#(templateId)' } @@ -45,7 +52,8 @@ Feature: Setting up the Future Time Machine Test * def pageId = pages.map(result => Object.keys(result)[0]) * def pageId = pageId[0] - * def publishPageResult = callonce read('classpath:graphql/ftm/publishPage.feature') { page_id: '#(pageId)', content1_id: '#(contentPieceOneId)', content2_id: '#(contentPieceTwoId)', container_id: '#(containerId)' } + # Now lets add the pieces of content to the page + * def publishPageResult = callonce read('classpath:graphql/ftm/publishPage.feature') { page_id: '#(pageId)', content_ids: ['#(contentPieceOneId)', '#(contentPieceTwoId)', '#(nonPublishedPieceId)'], container_id: '#(containerId)' } * karate.log('Page created and Published ::', pageUrl) diff --git a/test-karate/src/test/java/tests/graphql/ftm/CheckingTimeMachine.feature b/test-karate/src/test/java/tests/graphql/ftm/CheckingTimeMachine.feature index 2a3210c9d9bd..5f84444a0526 100644 --- a/test-karate/src/test/java/tests/graphql/ftm/CheckingTimeMachine.feature +++ b/test-karate/src/test/java/tests/graphql/ftm/CheckingTimeMachine.feature @@ -8,7 +8,7 @@ Feature: Test Time Machine functionality * callonce read('classpath:graphql/ftm/setup.feature') - @smoke @positive + @smoke @positive @ftm Scenario: Test Time Machine functionality when no publish date is provided Given url baseUrl + '/api/v1/page/render/'+pageUrl+'?language_id=1&mode=LIVE' And headers commonHeaders @@ -18,23 +18,33 @@ Feature: Test Time Machine functionality * def titles = pageContents.map(x => x.title) # This is the first version of the content, test 1 v2 as the title says it will be published in the future * match titles contains CONTENTLET_ONE_V1 - # This is the second version of the content, Thisone is already published therefore it should be displayed + # This is the second version of the content, This one is already published therefore it should be displayed * match titles contains CONTENTLET_TWO_V2 + * karate.log('pageContents:', pageContents) + # Check the rendered page. The same items included as contentlets should be displayed here too + * def rendered = response.entity.page.rendered + * karate.log('rendered:', rendered) + * match rendered contains CONTENTLET_ONE_V1 + * match rendered contains CONTENTLET_TWO_V2 - @positive + @positive @ftm Scenario: Test Time Machine functionality when a publish date is provided expect the future content to be displayed Given url baseUrl + '/api/v1/page/render/'+pageUrl+'?language_id=1&mode=LIVE&publishDate='+formattedFutureDateTime And headers commonHeaders When method GET Then status 200 - * def rendered = response.entity.page.rendered - * match rendered contains CONTENTLET_ONE_V2 + * karate.log('response:: ', response) * def pageContents = extractContentlets (response) * def titles = pageContents.map(x => x.title) + * match titles contains CONTENTLET_ONE_V2 * match titles contains CONTENTLET_TWO_V2 + * def rendered = response.entity.page.rendered + # Check the rendered page. The same items included as contentlets should be displayed here too + * match rendered contains CONTENTLET_ONE_V2 + * match rendered contains CONTENTLET_TWO_V2 - @smoke @positive + @smoke @positive @graphql @ftm Scenario: Send GraphQL query to fetch page details no publish date is sent * def graphQLRequestPayLoad = buildGraphQLRequestPayload (pageUrl) Given url baseUrl + '/api/v1/graphql' @@ -48,7 +58,7 @@ Feature: Test Time Machine functionality * match contentlets contains CONTENTLET_ONE_V1 * match contentlets contains CONTENTLET_TWO_V2 - @smoke @positive + @smoke @positive @graphql @ftm Scenario: Send GraphQL query to fetch page details, publish date is sent expect the future content to be displayed * def graphQLRequestPayLoad = buildGraphQLRequestPayload (pageUrl, formattedFutureDateTime) Given url baseUrl + '/api/v1/graphql'