You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2020/08/27 21:04:08 UTC

[sling-org-apache-sling-app-cms] branch SLING-8913-multiple-instance-types updated (c115e06 -> 707241d)

This is an automated email from the ASF dual-hosted git repository.

dklco pushed a change to branch SLING-8913-multiple-instance-types
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git.


    from c115e06  Updating to clean up the code around publishable resources including pages and files
     new 2828550  Fixing an issue where the page editor buttons weren't opening in a modal
     new ec1affb  Resolving issues where the security filter was not taking publication status into consideration and made it more configurable
     new 6ef9918  Fixing error when retrieving a publishable resource with an unset / invalid publication type
     new 3a3a691  Adding the new publication UI
     add 41c0943  Updating badges for org-apache-sling-app-cms
     add cb407e4  Adding sonarqube badges as per SLING-9682
     new 707241d  Merge branch 'master' of github.com:apache/sling-org-apache-sling-app-cms into SLING-8913-multiple-instance-types

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 README.md                                          |   4 +-
 .../filters/CMSSecurityConfigInstance.java         |  81 +++++++++
 .../core/internal/filters/CMSSecurityFilter.java   |  93 +++++------
 .../internal/models/PublishableResourceImpl.java   |  18 +-
 .../PublishPostOperation.java}                     |  43 ++---
 .../UnpublishPostOperation.java}                   |  43 ++---
 .../internal/filters/CMSSecurityFilterTest.java    |  59 +++----
 .../core/publication/PublishPostOperationTest.java |  74 +++++++++
 .../publication/UnpublishPostOperationTest.java    |  74 +++++++++
 core/src/test/resources/content.json               |  10 ++
 .../apps/reference/components/pages/base/edit.json | 173 +++++++++----------
 .../apps/reference/components/pages/post/edit.json | 185 +++++++++------------
 ui/src/main/frontend/scss/cms.scss                 |  13 ++
 ui/src/main/resources/jcr_root/conf/global.json    |   9 +-
 .../components/cms/columns/publish/publish.jsp     |   5 +-
 .../components/cms/contentgrid/contentgrid.jsp     |  78 ++++-----
 .../components/cms/pageeditbar/actions/actions.jsp |  30 +---
 .../cms/publicationstatus/publicationstatus.jsp    |  53 ++++++
 .../editor/fields/{file.json => publication.json}  |   3 +-
 .../components/editor/fields/publication/field.jsp |  73 ++++++++
 .../libs/sling-cms/content/shared/publish.json     |  18 +-
 .../libs/sling-cms/content/shared/unpublish.json   |  18 +-
 .../resources/jcr_root/libs/sling-cms/i18n.json    |  25 +++
 23 files changed, 749 insertions(+), 433 deletions(-)
 create mode 100644 core/src/main/java/org/apache/sling/cms/core/internal/filters/CMSSecurityConfigInstance.java
 copy core/src/main/java/org/apache/sling/cms/core/{internal/operations/ChangePasswordOperation.java => publication/PublishPostOperation.java} (63%)
 copy core/src/main/java/org/apache/sling/cms/core/{internal/operations/ChangePasswordOperation.java => publication/UnpublishPostOperation.java} (63%)
 create mode 100644 core/src/test/java/org/apache/sling/cms/core/publication/PublishPostOperationTest.java
 create mode 100644 core/src/test/java/org/apache/sling/cms/core/publication/UnpublishPostOperationTest.java
 create mode 100644 ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/publicationstatus/publicationstatus.jsp
 copy ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/{file.json => publication.json} (64%)
 create mode 100644 ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/publication/field.jsp


[sling-org-apache-sling-app-cms] 01/05: Fixing an issue where the page editor buttons weren't opening in a modal

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch SLING-8913-multiple-instance-types
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git

commit 282855073490a2e3c27c29615589489daf4ea635
Author: Dan Klco <dk...@apache.org>
AuthorDate: Thu Aug 27 08:45:16 2020 -0400

    Fixing an issue where the page editor buttons weren't opening in a modal
---
 .../components/cms/pageeditbar/actions/actions.jsp | 30 ++++------------------
 1 file changed, 5 insertions(+), 25 deletions(-)

diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/pageeditbar/actions/actions.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/pageeditbar/actions/actions.jsp
index 91042ca..907c96b 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/pageeditbar/actions/actions.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/pageeditbar/actions/actions.jsp
@@ -17,29 +17,9 @@
  * under the License.
  */ --%>
 <%@include file="/libs/sling-cms/global.jsp"%>
-<c:forEach var="actionConfig" items="${sling:listChildren(resource)}">
-    <c:choose>
-        <c:when test="${actionConfig.valueMap.modal}">
-            <div class="control">
-                <a class="button Fetch-Modal" data-title="${sling:encode(actionConfig.valueMap.title,'HTML_ATTR')}" data-path="${actionConfig.valueMap.ajaxPath != null ? actionConfig.valueMap.ajaxPath : '.Main-Content form'}" href="${sling:encode(actionConfig.valueMap.prefix,'HTML_ATTR')}${sling:encode(slingRequest.requestPathInfo.suffix,'HTML_ATTR')}" title="${sling:encode(actionConfig.valueMap.title,'HTML_ATTR')}">
-                    <span class="jam jam-${actionConfig.valueMap.icon}">
-                        <span class="is-vhidden">
-                            ${sling:encode(actionConfig.valueMap.title,'HTML')}
-                        </span>
-                    </span>
-                </a>
-            </div>
-        </c:when>
-        <c:otherwise>
-            <div class="control">
-                <a class="button" ${actionConfig.valueMap.new != false ? 'target="_blank"' : ''} href="${sling:encode(actionConfig.valueMap.prefix,'HTML_ATTR')}${sling:encode(slingRequest.requestPathInfo.suffix,'HTML_ATTR')}" title="${sling:encode(actionConfig.valueMap.title,'HTML_ATTR')}">
-                    <span class="jam jam-${actionConfig.valueMap.icon}">
-                        <span class="is-vhidden">
-                            ${sling:encode(actionConfig.valueMap.title,'HTML')}
-                        </span>
-                    </span>
-                </a>
-            </div>
-        </c:otherwise>
-    </c:choose>
+<c:forEach var="actionCfg" items="${sling:listChildren(resource)}">
+    <c:set var="actionConfig" value="${actionCfg}" scope="request" />
+    <div class="control">
+        <sling:include path="${request.requestPathInfo.suffix}" resourceType="${actionConfig.resourceType}" />
+    </div>
 </c:forEach>
\ No newline at end of file


[sling-org-apache-sling-app-cms] 03/05: Fixing error when retrieving a publishable resource with an unset / invalid publication type

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch SLING-8913-multiple-instance-types
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git

commit 6ef991849f48af1fa1bcb4992acfb8c9e3029995
Author: Dan Klco <dk...@apache.org>
AuthorDate: Thu Aug 27 17:02:19 2020 -0400

    Fixing error when retrieving a publishable resource with an unset / invalid publication type
---
 .../core/internal/models/PublishableResourceImpl.java  | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/models/PublishableResourceImpl.java b/core/src/main/java/org/apache/sling/cms/core/internal/models/PublishableResourceImpl.java
index bd1366f..043646e 100644
--- a/core/src/main/java/org/apache/sling/cms/core/internal/models/PublishableResourceImpl.java
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/models/PublishableResourceImpl.java
@@ -31,6 +31,8 @@ import org.apache.sling.cms.SiteManager;
 import org.apache.sling.cms.publication.PublicationType;
 import org.apache.sling.models.annotations.Model;
 import org.apache.sling.models.annotations.injectorspecific.Self;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Implementation of the publishable resource interface and adaptable from a
@@ -39,6 +41,10 @@ import org.apache.sling.models.annotations.injectorspecific.Self;
 @Model(adaptables = Resource.class, adapters = PublishableResource.class)
 public class PublishableResourceImpl implements PublishableResource {
 
+    private static final Logger log = LoggerFactory.getLogger(PublishableResourceImpl.class);
+
+    public static final String LEGACY_PUBLISHED_PROPERTY = "published";
+
     private final Resource contentResource;
 
     private final Calendar created;
@@ -72,7 +78,8 @@ public class PublishableResourceImpl implements PublishableResource {
             this.lastPublication = properties.get(CMSConstants.PN_LAST_PUBLICATION, Calendar.class);
             this.lastPublicationBy = properties.get(NodeTypeConstants.JCR_LASTMODIFIEDBY, String.class);
             this.lastPublicationType = properties.get(CMSConstants.PN_LAST_PUBLICATION_TYPE, String.class);
-            this.published = properties.get(CMSConstants.PN_PUBLISHED, false);
+            this.published = properties.get(CMSConstants.PN_PUBLISHED,
+                    properties.get(LEGACY_PUBLISHED_PROPERTY, false));
         } else {
             this.lastModified = null;
             this.lastModifiedBy = null;
@@ -110,7 +117,14 @@ public class PublishableResourceImpl implements PublishableResource {
 
     @Override
     public PublicationType getLastPublicationType() {
-        return PublicationType.valueOf(lastPublicationType);
+        if (lastPublicationType != null) {
+            try {
+                return PublicationType.valueOf(lastPublicationType);
+            } catch (IllegalArgumentException iae) {
+                log.warn("Invalid publication type: {}", lastPublicationType, iae);
+            }
+        }
+        return null;
     }
 
     @Override


[sling-org-apache-sling-app-cms] 05/05: Merge branch 'master' of github.com:apache/sling-org-apache-sling-app-cms into SLING-8913-multiple-instance-types

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch SLING-8913-multiple-instance-types
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git

commit 707241d486c01c71e9719d07f9ce38cbb57f544f
Merge: 3a3a691 cb407e4
Author: Dan Klco <dk...@apache.org>
AuthorDate: Thu Aug 27 17:03:31 2020 -0400

    Merge branch 'master' of github.com:apache/sling-org-apache-sling-app-cms into SLING-8913-multiple-instance-types

 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)


[sling-org-apache-sling-app-cms] 02/05: Resolving issues where the security filter was not taking publication status into consideration and made it more configurable

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch SLING-8913-multiple-instance-types
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git

commit ec1affbe693f97761cafd05a3613c2b70e422776
Author: Dan Klco <dk...@apache.org>
AuthorDate: Thu Aug 27 17:01:32 2020 -0400

    Resolving issues where the security filter was not taking publication status into consideration and made it more configurable
---
 .../filters/CMSSecurityConfigInstance.java         | 81 +++++++++++++++++++
 .../core/internal/filters/CMSSecurityFilter.java   | 93 ++++++++++------------
 .../internal/filters/CMSSecurityFilterTest.java    | 59 ++++++--------
 3 files changed, 148 insertions(+), 85 deletions(-)

diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/filters/CMSSecurityConfigInstance.java b/core/src/main/java/org/apache/sling/cms/core/internal/filters/CMSSecurityConfigInstance.java
new file mode 100644
index 0000000..377cf43
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/filters/CMSSecurityConfigInstance.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.internal.filters;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.metatype.annotations.Designate;
+
+@Component(service = { CMSSecurityConfigInstance.class })
+@Designate(ocd = CMSSecurityFilterConfig.class, factory = true)
+public class CMSSecurityConfigInstance {
+
+    private CMSSecurityFilterConfig config;
+    private final List<Pattern> patterns = new ArrayList<>();
+
+    @Modified
+    @Activate
+    public void activate(CMSSecurityFilterConfig config) {
+        this.config = config;
+
+        if (config.allowedPatterns() != null) {
+            for (String p : config.allowedPatterns()) {
+                patterns.add(Pattern.compile(p));
+            }
+        }
+
+    }
+
+    public boolean applies(HttpServletRequest request) {
+        return ArrayUtils.isEmpty(config.hostDomains())
+                || ArrayUtils.contains(config.hostDomains(), request.getServerName());
+    }
+
+    /**
+     * @return the config
+     */
+    public CMSSecurityFilterConfig getConfig() {
+        return config;
+    }
+
+    public String getGroupName() {
+        return config.group();
+    }
+
+    /**
+     * @param config the config to set
+     */
+    public void setConfig(CMSSecurityFilterConfig config) {
+        this.config = config;
+    }
+
+    /**
+     * @return the patterns
+     */
+    public List<Pattern> getPatterns() {
+        return patterns;
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/filters/CMSSecurityFilter.java b/core/src/main/java/org/apache/sling/cms/core/internal/filters/CMSSecurityFilter.java
index 19fe002..f1f87de 100644
--- a/core/src/main/java/org/apache/sling/cms/core/internal/filters/CMSSecurityFilter.java
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/filters/CMSSecurityFilter.java
@@ -17,8 +17,6 @@
 package org.apache.sling.cms.core.internal.filters;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.regex.Pattern;
@@ -33,7 +31,6 @@ import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.user.Authorizable;
@@ -41,11 +38,13 @@ import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.sling.api.SlingHttpServletRequest;
-import org.osgi.service.component.annotations.Activate;
+import org.apache.sling.cms.PublishableResource;
+import org.apache.sling.cms.publication.PUBLICATION_MODE;
+import org.apache.sling.cms.publication.PublicationManagerFactory;
 import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.ConfigurationPolicy;
-import org.osgi.service.component.annotations.Modified;
-import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -53,33 +52,16 @@ import org.slf4j.LoggerFactory;
  * Checks to ensure that the user is logged in for requests which otherwise
  * would be allowed when accessing through a CMS-specific domain name.
  */
-@Component(service = { Filter.class }, property = {
-        "sling.filter.scope=request" }, configurationPolicy = ConfigurationPolicy.REQUIRE)
-@Designate(ocd = CMSSecurityFilterConfig.class)
+@Component(service = { Filter.class }, property = { "sling.filter.scope=request" }, immediate = true)
 public class CMSSecurityFilter implements Filter {
 
     private static final Logger log = LoggerFactory.getLogger(CMSSecurityFilter.class);
 
-    private CMSSecurityFilterConfig config;
+    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policyOption = ReferencePolicyOption.GREEDY)
+    private List<CMSSecurityConfigInstance> securityConfigInstances;
 
-    private List<Pattern> patterns = new ArrayList<>();
-
-    @Modified
-    @Activate
-    public void activate(CMSSecurityFilterConfig config) {
-        if (config.hostDomains() != null && config.hostDomains().length > 0) {
-            if (log.isInfoEnabled()) {
-                log.info("Applying CMS Security Filter for domains {}", Arrays.toString(config.hostDomains()));
-            }
-            this.config = config;
-            for (String p : config.allowedPatterns()) {
-                patterns.add(Pattern.compile(p));
-            }
-        } else {
-            this.config = null;
-            log.info("No host domains supplied, CMS Security Filter not enabled");
-        }
-    }
+    @Reference
+    private PublicationManagerFactory pubMgrFactory;
 
     @Override
     public void init(FilterConfig filterConfig) throws ServletException {
@@ -90,26 +72,33 @@ public class CMSSecurityFilter implements Filter {
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
             throws IOException, ServletException {
 
-        SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;
-        if (config != null && ArrayUtils.contains(config.hostDomains(), request.getServerName())) {
-            boolean allowed = checkAllowed(request, slingRequest);
-
-            // permission checked failed, so return an unauthorized error
-            if (!allowed) {
-                log.trace("Request to {} not allowed for user {}", slingRequest.getRequestURI(),
-                        slingRequest.getResourceResolver().getUserID());
-                ((HttpServletResponse) response).sendError(401);
-                return;
+        if (pubMgrFactory.getPublicationMode() == PUBLICATION_MODE.STANDALONE) {
+            SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;
+            for (CMSSecurityConfigInstance securityConfig : securityConfigInstances) {
+                log.trace("Checking to see if security config {} applies to request", securityConfig);
+                if (securityConfig.applies(slingRequest)) {
+                    boolean allowed = checkAllowed(securityConfig, slingRequest);
+                    // permission checked failed, so return an unauthorized error
+                    if (!allowed) {
+                        log.trace("Request to {} not allowed for user {}", slingRequest.getRequestURI(),
+                                slingRequest.getResourceResolver().getUserID());
+                        ((HttpServletResponse) response).sendError(401);
+                        return;
+                    }
+                }
             }
+        } else {
+            log.trace("Publication mode {} is not standalone", pubMgrFactory.getPublicationMode());
         }
+
         chain.doFilter(request, response);
     }
 
-    private boolean checkAllowed(ServletRequest request, SlingHttpServletRequest slingRequest) {
-        log.trace("Filtering requests to host {}", request.getServerName());
+    private boolean checkAllowed(CMSSecurityConfigInstance securityConfig, SlingHttpServletRequest slingRequest) {
+        log.trace("Filtering requests to host {}", slingRequest.getServerName());
         String uri = slingRequest.getRequestURI();
         boolean allowed = false;
-        for (Pattern p : this.patterns) {
+        for (Pattern p : securityConfig.getPatterns()) {
             if (p.matcher(uri).matches()) {
                 log.trace("Allowing request matching pattern {}", p);
                 allowed = true;
@@ -117,15 +106,19 @@ public class CMSSecurityFilter implements Filter {
             }
         }
 
+        PublishableResource publishableResource = slingRequest.getResource().adaptTo(PublishableResource.class);
+        if (publishableResource.isPublished()) {
+            allowed = true;
+        }
+
         // the uri isn't allowed automatically, so check user permissions
         if (!allowed) {
             log.trace("Request to {} not allowed, checking user permissions", uri);
             // check to see if the user is a member of the specified group
-            if (StringUtils.isNotBlank(config.group())) {
-                allowed = checkGroupMembership(slingRequest);
-
-                // just check to make sure the user is logged in
+            if (StringUtils.isNotBlank(securityConfig.getGroupName())) {
+                allowed = checkGroupMembership(securityConfig, slingRequest);
             } else {
+                // just check to make sure the user is logged in
                 if (!"anonymous".equals(slingRequest.getResourceResolver().getUserID())) {
                     allowed = true;
                 }
@@ -136,7 +129,8 @@ public class CMSSecurityFilter implements Filter {
         return allowed;
     }
 
-    private boolean checkGroupMembership(SlingHttpServletRequest slingRequest) {
+    private boolean checkGroupMembership(CMSSecurityConfigInstance securityConfig,
+            SlingHttpServletRequest slingRequest) {
         boolean allowed = false;
         try {
             Session session = slingRequest.getResourceResolver().adaptTo(Session.class);
@@ -157,10 +151,11 @@ public class CMSSecurityFilter implements Filter {
                 return false;
             }
 
-            log.trace("Checking to see if user {} is in required group {}", auth.getID(), config.group());
+            log.trace("Checking to see if user {} is in required group {}", auth.getID(),
+                    securityConfig.getGroupName());
             Iterator<Group> groups = ((User) auth).memberOf();
             while (groups.hasNext()) {
-                if (groups.next().getID().equals(config.group())) {
+                if (groups.next().getID().equals(securityConfig.getGroupName())) {
                     allowed = true;
                     break;
                 }
diff --git a/core/src/test/java/org/apache/sling/cms/core/internal/filters/CMSSecurityFilterTest.java b/core/src/test/java/org/apache/sling/cms/core/internal/filters/CMSSecurityFilterTest.java
index 793e1cc..7edc430 100644
--- a/core/src/test/java/org/apache/sling/cms/core/internal/filters/CMSSecurityFilterTest.java
+++ b/core/src/test/java/org/apache/sling/cms/core/internal/filters/CMSSecurityFilterTest.java
@@ -26,9 +26,9 @@ import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
 
-import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.cms.core.helpers.SlingCMSTestHelper;
-import org.apache.sling.servlethelpers.MockSlingHttpServletRequest;
+import org.apache.sling.cms.publication.PUBLICATION_MODE;
+import org.apache.sling.cms.publication.PublicationManagerFactory;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.junit.Before;
 import org.junit.Rule;
@@ -44,48 +44,31 @@ public class CMSSecurityFilterTest {
     @Before
     public void init() throws UnsupportedRepositoryOperationException, RepositoryException, IOException {
         SlingCMSTestHelper.initAuthContext(context);
-        securityFilter = new CMSSecurityFilter();
     }
 
     @Test
-    public void testNotMatchingDomain() throws IOException, ServletException {
-        context.request().setServerName("localhost");
-        securityFilter.activate(new CMSSecurityFilterConfig() {
+    public void testNotEnabled() throws IOException, ServletException {
 
-            @Override
-            public Class<? extends Annotation> annotationType() {
-                return null;
-            }
+        PublicationManagerFactory factory = Mockito.mock(PublicationManagerFactory.class);
+        Mockito.when(factory.getPublicationMode()).thenReturn(PUBLICATION_MODE.CONTENT_DISTRIBUTION);
 
-            @Override
-            public String[] hostDomains() {
-                return new String[] { "cms.danklco.com" };
-            }
+        context.registerService(PublicationManagerFactory.class, factory);
 
-            @Override
-            public String[] allowedPatterns() {
-                return new String[0];
-            }
+        securityFilter = context.registerInjectActivateService(new CMSSecurityFilter());
 
-            @Override
-            public String group() {
-                return null;
-            }
-
-        });
         securityFilter.doFilter(context.request(), context.response(), Mockito.mock(FilterChain.class));
         assertEquals(200, context.response().getStatus());
     }
 
     @Test
-    public void testMatchingDomain() throws IOException, ServletException {
-        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
-        Mockito.when(resolver.getUserID()).thenReturn("anonymous");
-        MockSlingHttpServletRequest request = new MockSlingHttpServletRequest(resolver);
-        request.setServerName("cms.danklco.com");
-        request.setServletPath("/content/apache/sling-apache/org.html");
+    public void testNotMatchingDomain() throws IOException, ServletException {
+
+        PublicationManagerFactory factory = Mockito.mock(PublicationManagerFactory.class);
+        Mockito.when(factory.getPublicationMode()).thenReturn(PUBLICATION_MODE.STANDALONE);
+        context.registerService(PublicationManagerFactory.class, factory);
 
-        securityFilter.activate(new CMSSecurityFilterConfig() {
+        CMSSecurityConfigInstance config = new CMSSecurityConfigInstance();
+        config.activate(new CMSSecurityFilterConfig() {
 
             @Override
             public Class<? extends Annotation> annotationType() {
@@ -94,12 +77,12 @@ public class CMSSecurityFilterTest {
 
             @Override
             public String[] hostDomains() {
-                return new String[] { "cms.danklco.com" };
+                return new String[] { "cms.apache.org" };
             }
 
             @Override
             public String[] allowedPatterns() {
-                return new String[0];
+                return null;
             }
 
             @Override
@@ -108,8 +91,12 @@ public class CMSSecurityFilterTest {
             }
 
         });
-        securityFilter.doFilter(request, context.response(), Mockito.mock(FilterChain.class));
-        assertEquals(401, context.response().getStatus());
-    }
+        context.registerService(CMSSecurityConfigInstance.class, config);
+
+        securityFilter = context.registerInjectActivateService(new CMSSecurityFilter());
 
+        context.request().setRemoteHost("www.apache.org");
+        securityFilter.doFilter(context.request(), context.response(), Mockito.mock(FilterChain.class));
+        assertEquals(200, context.response().getStatus());
+    }
 }
\ No newline at end of file


[sling-org-apache-sling-app-cms] 04/05: Adding the new publication UI

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch SLING-8913-multiple-instance-types
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git

commit 3a3a691ee4f018b6396408955ec4c01287ce6044
Author: Dan Klco <dk...@apache.org>
AuthorDate: Thu Aug 27 17:03:07 2020 -0400

    Adding the new publication UI
---
 .../cms/core/publication/PublishPostOperation.java |  68 ++++++++
 .../core/publication/UnpublishPostOperation.java   |  68 ++++++++
 .../core/publication/PublishPostOperationTest.java |  74 +++++++++
 .../publication/UnpublishPostOperationTest.java    |  74 +++++++++
 core/src/test/resources/content.json               |  10 ++
 .../apps/reference/components/pages/base/edit.json | 173 +++++++++----------
 .../apps/reference/components/pages/post/edit.json | 185 +++++++++------------
 ui/src/main/frontend/scss/cms.scss                 |  13 ++
 ui/src/main/resources/jcr_root/conf/global.json    |   9 +-
 .../components/cms/columns/publish/publish.jsp     |   5 +-
 .../components/cms/contentgrid/contentgrid.jsp     |  78 ++++-----
 .../cms/publicationstatus/publicationstatus.jsp    |  53 ++++++
 .../components/editor/fields/publication.json      |   5 +
 .../components/editor/fields/publication/field.jsp |  73 ++++++++
 .../libs/sling-cms/content/shared/publish.json     |  18 +-
 .../libs/sling-cms/content/shared/unpublish.json   |  18 +-
 .../resources/jcr_root/libs/sling-cms/i18n.json    |  25 +++
 17 files changed, 683 insertions(+), 266 deletions(-)

diff --git a/core/src/main/java/org/apache/sling/cms/core/publication/PublishPostOperation.java b/core/src/main/java/org/apache/sling/cms/core/publication/PublishPostOperation.java
new file mode 100644
index 0000000..ad1672b
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/publication/PublishPostOperation.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.publication;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.cms.PublishableResource;
+import org.apache.sling.cms.publication.PublicationManagerFactory;
+import org.apache.sling.servlets.post.Modification;
+import org.apache.sling.servlets.post.PostOperation;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A Sling Post Operation to publish content
+ */
+@Component(immediate = true, service = { PostOperation.class }, property = PostOperation.PROP_OPERATION_NAME
+        + "=publish")
+public class PublishPostOperation implements PostOperation {
+
+    private static final Logger log = LoggerFactory.getLogger(PublishPostOperation.class);
+
+    @Reference
+    private PublicationManagerFactory publicationManagerFactory;
+
+    @Override
+    public void run(SlingHttpServletRequest request, PostResponse response, SlingPostProcessor[] processors) {
+        final List<Modification> changes = new ArrayList<>();
+        try {
+            publicationManagerFactory.getPublicationManager()
+                    .publish(request.getResource().adaptTo(PublishableResource.class));
+
+            if (processors != null) {
+                for (SlingPostProcessor processor : processors) {
+                    processor.process(request, changes);
+                }
+            }
+
+            request.getResourceResolver().commit();
+
+            response.setPath(request.getResource().getPath());
+            response.onModified(request.getResource().getPath());
+        } catch (Exception e) {
+            log.error("Failed to publish", e);
+            response.setError(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/sling/cms/core/publication/UnpublishPostOperation.java b/core/src/main/java/org/apache/sling/cms/core/publication/UnpublishPostOperation.java
new file mode 100644
index 0000000..1dff941
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/publication/UnpublishPostOperation.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.publication;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.cms.PublishableResource;
+import org.apache.sling.cms.publication.PublicationManagerFactory;
+import org.apache.sling.servlets.post.Modification;
+import org.apache.sling.servlets.post.PostOperation;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A Sling Post Operation to unpublish content
+ */
+@Component(immediate = true, service = { PostOperation.class }, property = PostOperation.PROP_OPERATION_NAME
+        + "=unpublish")
+public class UnpublishPostOperation implements PostOperation {
+
+    private static final Logger log = LoggerFactory.getLogger(UnpublishPostOperation.class);
+
+    @Reference
+    private PublicationManagerFactory publicationManagerFactory;
+
+    @Override
+    public void run(SlingHttpServletRequest request, PostResponse response, SlingPostProcessor[] processors) {
+        final List<Modification> changes = new ArrayList<>();
+        try {
+            publicationManagerFactory.getPublicationManager()
+                    .unpublish(request.getResource().adaptTo(PublishableResource.class));
+
+            if (processors != null) {
+                for (SlingPostProcessor processor : processors) {
+                    processor.process(request, changes);
+                }
+            }
+
+            request.getResourceResolver().commit();
+
+            response.setPath(request.getResource().getPath());
+            response.onModified(request.getResource().getPath());
+        } catch (Exception e) {
+            log.error("Failed to publish", e);
+            response.setError(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/sling/cms/core/publication/PublishPostOperationTest.java b/core/src/test/java/org/apache/sling/cms/core/publication/PublishPostOperationTest.java
new file mode 100644
index 0000000..38b4d2d
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/cms/core/publication/PublishPostOperationTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.publication;
+
+import org.apache.sling.cms.core.helpers.SlingCMSTestHelper;
+import org.apache.sling.cms.publication.PublicationException;
+import org.apache.sling.cms.publication.PublicationManager;
+import org.apache.sling.cms.publication.PublicationManagerFactory;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class PublishPostOperationTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext();
+
+    @Before
+    public void init() {
+        SlingCMSTestHelper.initContext(context);
+        context.currentResource("/content/apache/sling-apache-org/index");
+    }
+
+    @Test
+    public void testPublish() throws PublicationException {
+        PublicationManagerFactory publicationManagerFactory = Mockito.mock(PublicationManagerFactory.class);
+        PublicationManager publicationManager = Mockito.mock(PublicationManager.class);
+        Mockito.when(publicationManagerFactory.getPublicationManager()).thenReturn(publicationManager);
+        context.registerService(PublicationManagerFactory.class, publicationManagerFactory);
+        PublishPostOperation publishPostOperation = context.registerInjectActivateService(new PublishPostOperation());
+        PostResponse response = Mockito.mock(PostResponse.class);
+
+        publishPostOperation.run(context.request(), response, null);
+
+        Mockito.verify(publicationManager).publish(Mockito.any());
+
+        Mockito.verify(response, Mockito.never()).setError(Mockito.any());
+        Mockito.verify(response).setPath(context.currentResource().getPath());
+    }
+
+    @Test
+    public void testPublishFailure() throws PublicationException {
+        PublicationManagerFactory publicationManagerFactory = Mockito.mock(PublicationManagerFactory.class);
+        PublicationManager publicationManager = Mockito.mock(PublicationManager.class);
+        PublicationException ex = new PublicationException("OH NO MR BILL");
+        Mockito.doThrow(ex).when(publicationManager).publish(Mockito.any());
+        Mockito.when(publicationManagerFactory.getPublicationManager()).thenReturn(publicationManager);
+        context.registerService(PublicationManagerFactory.class, publicationManagerFactory);
+        PublishPostOperation publishPostOperation = context.registerInjectActivateService(new PublishPostOperation());
+        PostResponse response = Mockito.mock(PostResponse.class);
+
+        publishPostOperation.run(context.request(), response, null);
+
+        Mockito.verify(response).setError(ex);
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/sling/cms/core/publication/UnpublishPostOperationTest.java b/core/src/test/java/org/apache/sling/cms/core/publication/UnpublishPostOperationTest.java
new file mode 100644
index 0000000..28b87e3
--- /dev/null
+++ b/core/src/test/java/org/apache/sling/cms/core/publication/UnpublishPostOperationTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.publication;
+
+import org.apache.sling.cms.core.helpers.SlingCMSTestHelper;
+import org.apache.sling.cms.publication.PublicationException;
+import org.apache.sling.cms.publication.PublicationManager;
+import org.apache.sling.cms.publication.PublicationManagerFactory;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class UnpublishPostOperationTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext();
+
+    @Before
+    public void init() {
+        SlingCMSTestHelper.initContext(context);
+        context.currentResource("/content/apache/sling-apache-org/index");
+    }
+
+    @Test
+    public void testUnpublish() throws PublicationException {
+        PublicationManagerFactory publicationManagerFactory = Mockito.mock(PublicationManagerFactory.class);
+        PublicationManager publicationManager = Mockito.mock(PublicationManager.class);
+        Mockito.when(publicationManagerFactory.getPublicationManager()).thenReturn(publicationManager);
+        context.registerService(PublicationManagerFactory.class, publicationManagerFactory);
+        UnpublishPostOperation unpublishPostOperation = context.registerInjectActivateService(new UnpublishPostOperation());
+        PostResponse response = Mockito.mock(PostResponse.class);
+
+        unpublishPostOperation.run(context.request(), response, null);
+
+        Mockito.verify(publicationManager).unpublish(Mockito.any());
+
+        Mockito.verify(response, Mockito.never()).setError(Mockito.any());
+        Mockito.verify(response).setPath(context.currentResource().getPath());
+    }
+
+    @Test
+    public void testUnpublishFailure() throws PublicationException {
+        PublicationManagerFactory publicationManagerFactory = Mockito.mock(PublicationManagerFactory.class);
+        PublicationManager publicationManager = Mockito.mock(PublicationManager.class);
+        PublicationException ex = new PublicationException("OH NO MR BILL");
+        Mockito.doThrow(ex).when(publicationManager).unpublish(Mockito.any());
+        Mockito.when(publicationManagerFactory.getPublicationManager()).thenReturn(publicationManager);
+        context.registerService(PublicationManagerFactory.class, publicationManagerFactory);
+        UnpublishPostOperation unpublishPostOperation = context.registerInjectActivateService(new UnpublishPostOperation());
+        PostResponse response = Mockito.mock(PostResponse.class);
+
+        unpublishPostOperation.run(context.request(), response, null);
+
+        Mockito.verify(response).setError(ex);
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/test/resources/content.json b/core/src/test/resources/content.json
index 7acf199..7622552 100644
--- a/core/src/test/resources/content.json
+++ b/core/src/test/resources/content.json
@@ -55,6 +55,16 @@
             "jcr:language": "en",
             "sling:url": "https://sling.apache.org",
             "jcr:created": "Wed May 15 2019 12:40:00 GMT-0400",
+            "unpublished": {
+                "jcr:primaryType": "sling:Page",
+                "jcr:content": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "jcr:title": "Not Published",
+                    "sling:template": "/conf/global/site/templates/base-page",
+                    "sling:resourceType": "reference/components/pages/base",
+                    "sling:published": false
+                }
+            },
             "index": {
                 "jcr:primaryType": "sling:Page",
                 "jcr:mixinTypes": [
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/pages/base/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/pages/base/edit.json
index 19d4ae2..65a4295 100644
--- a/reference/src/main/resources/jcr_root/apps/reference/components/pages/base/edit.json
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/pages/base/edit.json
@@ -1,97 +1,78 @@
- {
-	"jcr:primaryType": "nt:unstructured",
-	"sling:resourceType": "sling-cms/components/editor/slingform",
-	"button": "Save Page",
-	"fields": {
-		"jcr:primaryType": "nt:unstructured",
-		"sling:resourceType": "sling-cms/components/general/container",
-		"title": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/text",
-			"label": "Title",
-			"name": "jcr:title",
-			"required": true
-		},
-		"description": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/textarea",
-			"label": "Description",
-			"name": "jcr:description"
-		},
-		"taxonomy": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/taxonomy",
-			"label": "Keywords",
-			"name": "sling:taxonomy"
-		},
-		"taxonomyTypeHint": {
-			"jcr:primaryType": "nt:unstructured",
-			"name": "jcr:content/sling:taxonomy@TypeHint",
-			"value": "String[]",
-			"sling:resourceType": "sling-cms/components/editor/fields/hidden"
-		},
-		"canonical": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/path",
-			"basePath": "/content",
-			"type": "page",
-			"label": "Canonical",
-			"name": "canonical"
-		},
-		"published": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/select",
-			"label": "Published",
-			"name": "published",
-			"options": {
-				"yes": {
-					"jcr:primaryType": "nt:unstructured",
-					"label": "Yes",
-					"value": "true"
-				},
-				"no": {
-					"jcr:primaryType": "nt:unstructured",
-					"label": "No",
-					"value": "false"
-				}
-			}
-		},
-		"publishedTypeHint": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/hidden",
-			"name": "published@TypeHint",
-			"value": "Boolean"
-		},
-		"hideInSitemap": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/select",
-			"label": "Hide in Sitemap",
-			"name": "hideInSitemap",
-			"options": {
-				"no": {
-					"jcr:primaryType": "nt:unstructured",
-					"label": "No",
-					"value": "false"
-				},
-				"yes": {
-					"jcr:primaryType": "nt:unstructured",
-					"label": "Yes",
-					"value": "true"
-				}
-			}
-		},
-		"hideInSitemapTypeHint": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/hidden",
-			"name": "hideInSitemap@TypeHint",
-			"value": "Boolean"
-		},
-		"template": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/text",
-			"label": "Template",
-			"name": "sling:template",
-			"disabled": true
-		}
-	}
+{
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save Page",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "title": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Title",
+            "name": "jcr:title",
+            "required": true
+        },
+        "description": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/textarea",
+            "label": "Description",
+            "name": "jcr:description"
+        },
+        "taxonomy": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/taxonomy",
+            "label": "Keywords",
+            "name": "sling:taxonomy"
+        },
+        "taxonomyTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "name": "jcr:content/sling:taxonomy@TypeHint",
+            "value": "String[]",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden"
+        },
+        "canonical": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/path",
+            "basePath": "/content",
+            "type": "page",
+            "label": "Canonical",
+            "name": "canonical"
+        },
+        "publication": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/publication",
+            "label": "Publication"
+        },
+        "hideInSitemap": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/select",
+            "label": "Hide in Sitemap",
+            "name": "hideInSitemap",
+            "options": {
+                "no": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "label": "No",
+                    "value": "false"
+                },
+                "yes": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "label": "Yes",
+                    "value": "true"
+                }
+            }
+        },
+        "hideInSitemapTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "hideInSitemap@TypeHint",
+            "value": "Boolean"
+        },
+        "template": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Template",
+            "name": "sling:template",
+            "disabled": true
+        }
+    }
 }
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/pages/post/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/pages/post/edit.json
index c568024..425f97b 100644
--- a/reference/src/main/resources/jcr_root/apps/reference/components/pages/post/edit.json
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/pages/post/edit.json
@@ -1,104 +1,85 @@
  {
-	"jcr:primaryType": "nt:unstructured",
-	"sling:resourceType": "sling-cms/components/editor/slingform",
-	"button": "Save Page",
-	"fields": {
-		"jcr:primaryType": "nt:unstructured",
-		"sling:resourceType": "sling-cms/components/general/container",
-		"title": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/text",
-			"label": "Title",
-			"name": "jcr:title",
-			"required": true
-		},
-		"description": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/textarea",
-			"label": "Description",
-			"name": "jcr:description"
-		},
-		"taxonomy": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/taxonomy",
-			"label": "Keywords",
-			"name": "sling:taxonomy"
-		},
-		"taxonomyTypeHint": {
-			"jcr:primaryType": "nt:unstructured",
-			"name": "jcr:content/sling:taxonomy@TypeHint",
-			"value": "String[]",
-			"sling:resourceType": "sling-cms/components/editor/fields/hidden"
-		},
-		"canonical": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/path",
-			"basePath": "/content",
-			"type": "page",
-			"label": "Canonical",
-			"name": "canonical"
-		},
-		"published": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/select",
-			"label": "Published",
-			"name": "published",
-			"options": {
-				"yes": {
-					"jcr:primaryType": "nt:unstructured",
-					"label": "Yes",
-					"value": "true"
-				},
-				"no": {
-					"jcr:primaryType": "nt:unstructured",
-					"label": "No",
-					"value": "false"
-				}
-			}
-		},
-		"publishedTypeHint": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/hidden",
-			"name": "published@TypeHint",
-			"value": "Boolean"
-		},
-		"publishDate": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/text",
-			"label": "Published Date",
-			"name": "publishDate",
-			"type": "date"
-		},
-		"hideInSitemap": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/select",
-			"label": "Hide in Sitemap",
-			"name": "hideInSitemap",
-			"options": {
-				"no": {
-					"jcr:primaryType": "nt:unstructured",
-					"label": "No",
-					"value": "false"
-				},
-				"yes": {
-					"jcr:primaryType": "nt:unstructured",
-					"label": "Yes",
-					"value": "true"
-				}
-			}
-		},
-		"hideInSitemapTypeHint": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/hidden",
-			"name": "hideInSitemap@TypeHint",
-			"value": "Boolean"
-		},
-		"template": {
-			"jcr:primaryType": "nt:unstructured",
-			"sling:resourceType": "sling-cms/components/editor/fields/text",
-			"label": "Template",
-			"name": "sling:template",
-			"disabled": true
-		}
-	}
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save Page",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "title": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Title",
+            "name": "jcr:title",
+            "required": true
+        },
+        "description": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/textarea",
+            "label": "Description",
+            "name": "jcr:description"
+        },
+        "taxonomy": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/taxonomy",
+            "label": "Keywords",
+            "name": "sling:taxonomy"
+        },
+        "taxonomyTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "name": "jcr:content/sling:taxonomy@TypeHint",
+            "value": "String[]",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden"
+        },
+        "canonical": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/path",
+            "basePath": "/content",
+            "type": "page",
+            "label": "Canonical",
+            "name": "canonical"
+        },
+        "published": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/publication",
+            "label": "Published"
+        },
+        "publishDate": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Published Date",
+            "name": "publishDate",
+            "type": "date"
+        },
+        "hideInSitemap": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/select",
+            "label": "Hide in Sitemap",
+            "name": "hideInSitemap",
+            "options": {
+                "no": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "label": "No",
+                    "value": "false"
+                },
+                "yes": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "label": "Yes",
+                    "value": "true"
+                }
+            }
+        },
+        "hideInSitemapTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "hideInSitemap@TypeHint",
+            "value": "Boolean"
+        },
+        "template": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Template",
+            "name": "sling:template",
+            "disabled": true
+        }
+    }
 }
\ No newline at end of file
diff --git a/ui/src/main/frontend/scss/cms.scss b/ui/src/main/frontend/scss/cms.scss
index ede12e8..75dc2ed 100644
--- a/ui/src/main/frontend/scss/cms.scss
+++ b/ui/src/main/frontend/scss/cms.scss
@@ -37,6 +37,15 @@ body.cms {
   }
 }
 
+.card-footer-group {
+  display: flex !important;
+  flex-direction: column;
+}
+
+.card-footer-group small {
+  font-size: 0.75em;
+}
+
 dl {
   padding: 1em 0;
 }
@@ -49,6 +58,10 @@ dt {
   font-weight: bold;
 }
 
+fieldset {
+  min-inline-size: auto;
+}
+
 h1 {
   font-size: 200%;
 }
diff --git a/ui/src/main/resources/jcr_root/conf/global.json b/ui/src/main/resources/jcr_root/conf/global.json
index 622b168..1089ded 100644
--- a/ui/src/main/resources/jcr_root/conf/global.json
+++ b/ui/src/main/resources/jcr_root/conf/global.json
@@ -58,16 +58,9 @@
                     },
                     "published": {
                         "jcr:primaryType": "nt:unstructured",
-                        "options": "Yes=true, No=false",
                         "name": "jcr:content/published",
                         "label": "Published",
-                        "sling:resourceType": "sling-cms/components/editor/fields/select"
-                    },
-                    "publishedTypeHint": {
-                        "jcr:primaryType": "nt:unstructured",
-                        "name": "jcr:content/published@TypeHint",
-                        "value": "Boolean",
-                        "sling:resourceType": "sling-cms/components/editor/fields/hidden"
+                        "sling:resourceType": "sling-cms/components/editor/fields/publication"
                     },
                     "filemetadata": {
                         "jcr:primaryType": "nt:unstructured",
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/columns/publish/publish.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/columns/publish/publish.jsp
index c74c4a6..6edc01e 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/columns/publish/publish.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/columns/publish/publish.jsp
@@ -17,9 +17,10 @@
  * under the License.
  */ --%>
 <%@include file="/libs/sling-cms/global.jsp"%>
-<td data-value="${sling:getRelativeResource(resource,'jcr:content').valueMap.published ? 0 : 1}">
+<sling:adaptTo adaptable="${resource}" adaptTo="org.apache.sling.cms.PublishableResource" var="publishableResource" />
+<td data-value="${publishableResource.published ? 0 : 1}">
     <c:choose>
-        <c:when test="${sling:getRelativeResource(resource,'jcr:content').valueMap.published}">
+        <c:when test="${publishableResource.published}">
             <a class="button is-success is-outlined Fetch-Modal" href="/cms/shared/unpublish.html${resource.path}" title="Content Published" data-title="Unpublish" data-path=".Main-Content form">
                 <i class="jam jam-check">
                     <span class="is-vhidden">Content Published</span>
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentgrid/contentgrid.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentgrid/contentgrid.jsp
index 7678f2c..8b2d6a8 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentgrid/contentgrid.jsp
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/contentgrid/contentgrid.jsp
@@ -28,6 +28,18 @@
             </c:forEach>
             <c:if test="${showCard}">
                 <div class="tile is-parent is-3 contentnav__item">
+                        <sling:getResource base="${resource}" path="types/${child.valueMap['jcr:primaryType']}/columns/name" var="nameConfig" />
+                        <c:choose>
+                            <c:when test="${not empty child.valueMap['jcr:content/jcr:title']}">
+                                <c:set var="title" value="${child.valueMap['jcr:content/jcr:title']}" />
+                            </c:when>
+                            <c:when test="${not empty child.valueMap['jcr:title']}">
+                                <c:set var="title" value="${child.valueMap['jcr:title']}" />
+                            </c:when>
+                            <c:otherwise>
+                                <c:set var="title" value="${child.name}" />
+                            </c:otherwise>
+                        </c:choose>
                         <div class="tile is-child">
                             <div class="card is-linked" title="${sling:encode(child.name,'HTML_ATTR')}" data-value="${child.path}">
                                 <div class="card-image">
@@ -62,57 +74,47 @@
                                         <sling:getResource base="${resource}" path="types/${child.valueMap['jcr:primaryType']}/columns/actions" var="colConfig" />
                                         <c:forEach var="ac" items="${sling:listChildren(colConfig)}">
                                             <c:set var="actionConfig" value="${ac}" scope="request" />
-                                            <!-- Resource: ${resource} -->
-                                            <!-- Action Config: ${actionConfig} -->
                                             <sling:include path="${child.path}" resourceType="${actionConfig.resourceType}" />
                                         </c:forEach>
                                     </div>
                                 </div>
                                 <footer class="card-footer">
-                                    <sling:getResource base="${resource}" path="types/${child.valueMap['jcr:primaryType']}/columns/name" var="nameConfig" />
-                                    <c:choose>
-                                        <c:when test="${not empty child.valueMap['jcr:content/jcr:title']}">
-                                            <c:set var="title" value="${child.valueMap['jcr:content/jcr:title']}" />
-                                        </c:when>
-                                        <c:when test="${not empty child.valueMap['jcr:title']}">
-                                            <c:set var="title" value="${child.valueMap['jcr:title']}" />
-                                        </c:when>
-                                        <c:otherwise>
-                                            <c:set var="title" value="${child.name}" />
-                                        </c:otherwise>
-                                    </c:choose>
-                                    <div class="card-footer-item">
+                                    <div class="card-footer-item card-footer-group">
                                         <c:choose>
                                             <c:when test="${child.resourceType == 'sling:Site' || child.resourceType == 'sling:OrderedFolder' || child.resourceType == 'sling:Folder' || child.resourceType == 'nt:folder' || child.resourceType == 'sling:Page'}">
-                                                <a href="${nameConfig.valueMap.prefix}${child.path}" class="item-link">${sling:encode(title,'HTML')}</a><br/>
+                                                <a href="${nameConfig.valueMap.prefix}${child.path}" class="item-link">${sling:encode(title,'HTML')}</a>
                                             </c:when>
                                             <c:otherwise>
-                                                ${sling:encode(title,'HTML')}<br/>
+                                                <span>${sling:encode(title,'HTML')}</span>
                                             </c:otherwise>
                                         </c:choose>
-                                        <small>
-                                          <c:choose>
-                                              <c:when test="${sling:getRelativeResource(child,'jcr:content').valueMap.published}">
-                                                  <a class="Fetch-Modal has-text-success" href="/cms/shared/unpublish.html${child.path}" title="Content Published" data-title="Unpublish" data-path=".Main-Content form">
-                                                      <em class="jam jam-check">
-                                                          <span class="is-vhidden">Content Published</span>
-                                                      </em>
-                                                  </a>
-                                              </c:when>
-                                              <c:otherwise>
-                                                  <a class="has-text-warning Fetch-Modal" href="/cms/shared/publish.html${child.path}" title="Content Not Published" data-title="Publish" data-path=".Main-Content form">
-                                                      <em class="jam jam-close">
-                                                          <span class="is-vhidden">Content Not Published</span>
-                                                      </em>
-                                                  </a>
-                                              </c:otherwise>
-                                          </c:choose>
-                                          <c:catch var="ex">
-                                            <fmt:formatDate type="both" dateStyle="long" timeStyle="long" value = "${child.valueMap['jcr:content/jcr:lastModified'].time}" />
-                                          </c:catch>
-                                        </small>
+                                        <c:catch var="ex">
+                                            <small>
+                                                <fmt:formatDate type="both" dateStyle="long" timeStyle="long" value = "${child.valueMap['jcr:content/jcr:lastModified'].time}" />
+                                            </small>
+                                        </c:catch>
                                     </div>
                                 </footer>
+                                <footer class="card-footer">
+                                    <sling:adaptTo adaptable="${child}" adaptTo="org.apache.sling.cms.PublishableResource" var="publishableResource" />
+                                    <c:if test="${child.resourceType == 'sling:Page' || child.resourceType == 'sling:File' || child.resourceType == 'nt:file'}">
+                                        <c:choose>
+                                            <c:when test="${publishableResource.published}">
+                                                <a class="Fetch-Modal card-footer-item" href="/cms/shared/unpublish.html${child.path}" title="Content Published" data-title="Unpublish" data-path=".Main-Content form">
+                                                    Unpublish
+                                                </a>
+                                            </c:when>
+                                            <c:otherwise>
+                                                <a class="Fetch-Modal card-footer-item" href="/cms/shared/publish.html${child.path}" title="Content Not Published" data-title="Publish" data-path=".Main-Content form">
+                                                    Publish
+                                                </a>
+                                            </c:otherwise>
+                                        </c:choose>
+                                    </c:if>
+                                     <c:if test="${child.resourceType == 'sling:Site' || child.resourceType == 'sling:OrderedFolder' || child.resourceType == 'sling:Folder' || child.resourceType == 'nt:folder' || child.resourceType == 'sling:Page'}">
+                                        <a href="${nameConfig.valueMap.prefix}${child.path}" class="card-footer-item">Open</a>
+                                    </c:if>
+                                </footer>
                             </div>
                         </div>
                     </div>
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/publicationstatus/publicationstatus.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/publicationstatus/publicationstatus.jsp
new file mode 100644
index 0000000..ddb9a49
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/publicationstatus/publicationstatus.jsp
@@ -0,0 +1,53 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+<%@include file="/libs/sling-cms/global.jsp"%>
+<sling:adaptTo adaptable="${slingRequest.requestPathInfo.suffixResource}" adaptTo="org.apache.sling.cms.PublishableResource" var="publishableResource" />
+<dl class="my-3">
+    <dt>
+        <fmt:message key="slingcms.published" />
+    </dt>
+    <dd>
+        <sling:encode value="${publishableResource.published}"  mode="HTML" />
+    </dd>
+    <c:if test="${publishableResource.lastPublication.time != null}">
+        <dt>
+            <fmt:message key="slingcms.lastpublication" />
+        </dt>
+        <dd>
+            <fmt:formatDate value="${publishableResource.lastPublication.time}" type="both" />
+        </dd>
+    </c:if>
+    <c:if test="${not empty publishableResource.lastPublicationBy}">
+        <dt>
+            <fmt:message key="slingcms.lastpublicationby" />
+        </dt>
+        <dd>
+            <sling:encode value="${publishableResource.lastPublicationBy}"  mode="HTML" />
+        </dd>
+    </c:if>
+    <c:if test="${not empty publishableResource.lastPublicationType}">
+        <dt>
+            <fmt:message key="slingcms.lastpublicationtype" />
+        </dt>
+        <dd>
+            <sling:encode value="${publishableResource.lastPublicationType}"  mode="HTML" />
+        </dd>
+    </c:if>
+</dl>
+<hr/>
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/publication.json b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/publication.json
new file mode 100644
index 0000000..b40ab8f
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/publication.json
@@ -0,0 +1,5 @@
+{
+	"jcr:primaryType": "sling:Component",
+	"sling:resourceSuperType" : "sling-cms/components/editor/fields/base",
+    "jcr:title": "Sling CMS - Publication Field"
+}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/publication/field.jsp b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/publication/field.jsp
new file mode 100644
index 0000000..c267eb3
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/fields/publication/field.jsp
@@ -0,0 +1,73 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<sling:adaptTo adaptable="${slingRequest.requestPathInfo.suffixResource.parent}" adaptTo="org.apache.sling.cms.PublishableResource" var="publishableResource" />
+
+<div class="columns">
+    <div class="column">
+        <dl>
+            <dt>
+                <fmt:message key="slingcms.published" />
+            </dt>
+            <dd>
+                <sling:encode value="${publishableResource.published}"  mode="HTML" />
+            </dd>
+            <c:if test="${publishableResource.lastPublication.time != null}">
+                <dt>
+                    <fmt:message key="slingcms.lastpublication" />
+                </dt>
+                <dd>
+                    <fmt:formatDate value="${publishableResource.lastPublication.time}" type="both" />
+                </dd>
+            </c:if>
+            <c:if test="${not empty publishableResource.lastPublicationBy}">
+                <dt>
+                    <fmt:message key="slingcms.lastpublicationby" />
+                </dt>
+                <dd>
+                    <sling:encode value="${publishableResource.lastPublicationBy}"  mode="HTML" />
+                </dd>
+            </c:if>
+            <c:if test="${not empty publishableResource.lastPublicationType}">
+                <dt>
+                    <fmt:message key="slingcms.lastpublicationtype" />
+                </dt>
+                <dd>
+                    <sling:encode value="${publishableResource.lastPublicationType}"  mode="HTML" />
+                </dd>
+            </c:if>
+        </dl>
+    </div>
+    <div class="column">
+        <c:choose>
+            <c:when test="${publishableResource.published}">
+                <a class="Fetch-Modal button is-success is-outlined" href="/cms/shared/unpublish.html${sling:encode(slingRequest.requestPathInfo.suffixResource.parent.path,'HTML_ATTR')}" title="Content Published" data-title="Unpublish" data-path=".Main-Content form">
+                    Unpublish
+                </a>
+            </c:when>
+            <c:otherwise>
+                <a class="Fetch-Modal button is-warning is-outlined" href="/cms/shared/publish.html${sling:encode(slingRequest.requestPathInfo.suffixResource.parent.path,'HTML_ATTR')}" title="Content Not Published" data-title="Publish" data-path=".Main-Content form">
+                    Publish
+                </a>
+            </c:otherwise>
+        </c:choose>
+    </div>
+</div>
+
+
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/shared/publish.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/shared/publish.json
index bb76104..04bf291 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/content/shared/publish.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/shared/publish.json
@@ -2,11 +2,15 @@
     "jcr:primaryType": "sling:Page",
     "jcr:content": {
         "sling:resourceType": "sling-cms/components/pages/modal",
-        "jcr:title": "Publish Content",
+        "jcr:title": "Publication Status",
         "jcr:primaryType": "nt:unstructured",
         "container": {
             "jcr:primaryType": "nt:unstructured",
             "sling:resourceType": "sling-cms/components/general/container",
+            "publicationstatus": {
+                "jcr:primaryType": "nt:unstructured",
+                "sling:resourceType": "sling-cms/components/cms/publicationstatus"
+            },
             "slingform": {
                 "jcr:primaryType": "nt:unstructured",
                 "sling:resourceType": "sling-cms/components/editor/slingform",
@@ -19,17 +23,11 @@
                         "sling:resourceType": "sling-cms/components/editor/fields/suffixlabel",
                         "label": "Do you want to publish:"
                     },
-                    "published": {
-                        "jcr:primaryType": "nt:unstructured",
-                        "sling:resourceType": "sling-cms/components/editor/fields/hidden",
-                        "name": "jcr:content/published",
-                        "value": "true"
-                    },
-                    "publishedTypeHint": {
+                    "operation": {
                         "jcr:primaryType": "nt:unstructured",
                         "sling:resourceType": "sling-cms/components/editor/fields/hidden",
-                        "name": "jcr:content/published@TypeHint",
-                        "value": "Boolean"
+                        "name": ":operation",
+                        "value": "publish"
                     }
                 }
             }
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/content/shared/unpublish.json b/ui/src/main/resources/jcr_root/libs/sling-cms/content/shared/unpublish.json
index a17a473..06e44a4 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/content/shared/unpublish.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/content/shared/unpublish.json
@@ -2,11 +2,15 @@
     "jcr:primaryType": "sling:Page",
     "jcr:content": {
         "sling:resourceType": "sling-cms/components/pages/modal",
-        "jcr:title": "Unpublish Content",
+        "jcr:title": "Publication Status",
         "jcr:primaryType": "nt:unstructured",
         "container": {
             "jcr:primaryType": "nt:unstructured",
             "sling:resourceType": "sling-cms/components/general/container",
+            "publicationstatus": {
+                "jcr:primaryType": "nt:unstructured",
+                "sling:resourceType": "sling-cms/components/cms/publicationstatus"
+            },
             "slingform": {
                 "jcr:primaryType": "nt:unstructured",
                 "sling:resourceType": "sling-cms/components/editor/slingform",
@@ -19,17 +23,11 @@
                         "sling:resourceType": "sling-cms/components/editor/fields/suffixlabel",
                         "label": "Do you want to unpublish:"
                     },
-                    "published": {
-                        "jcr:primaryType": "nt:unstructured",
-                        "sling:resourceType": "sling-cms/components/editor/fields/hidden",
-                        "name": "jcr:content/published",
-                        "value": "false"
-                    },
-                    "publishedTypeHint": {
+                    "operation": {
                         "jcr:primaryType": "nt:unstructured",
                         "sling:resourceType": "sling-cms/components/editor/fields/hidden",
-                        "name": "jcr:content/published@TypeHint",
-                        "value": "Boolean"
+                        "name": ":operation",
+                        "value": "unpublish"
                     }
                 }
             }
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/i18n.json b/ui/src/main/resources/jcr_root/libs/sling-cms/i18n.json
index 5a136bd..5441116 100644
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/i18n.json
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/i18n.json
@@ -95,6 +95,31 @@
             "sling:message": "Job Properties",
             "sling:key": "slingcms.jobs.properties"
         },
+        "slingcms-publicationstatus": {
+            "jcr:primaryType": "sling:MessageEntry",
+            "sling:message": "Publication Status",
+            "sling:key": "slingcms.publicationstatus"
+        },
+        "slingcms-published": {
+            "jcr:primaryType": "sling:MessageEntry",
+            "sling:message": "Published",
+            "sling:key": "slingcms.published"
+        },
+        "slingcms-lastpublication": {
+            "jcr:primaryType": "sling:MessageEntry",
+            "sling:message": "Last Publication",
+            "sling:key": "slingcms.lastpublication"
+        },
+        "slingcms-lastpublicationby": {
+            "jcr:primaryType": "sling:MessageEntry",
+            "sling:message": "Last Publication By",
+            "sling:key": "slingcms.lastpublicationby"
+        },
+        "slingcms-lastpublicationtype": {
+            "jcr:primaryType": "sling:MessageEntry",
+            "sling:message": "Last Publication Type",
+            "sling:key": "slingcms.lastpublicationtype"
+        },
         "slingcms-recentcontent": {
             "jcr:primaryType": "sling:MessageEntry",
             "sling:message": "Recent Content",