diff --git a/ccm-cms/src/main/java/org/librecms/pages/Page.java b/ccm-cms/src/main/java/org/librecms/pages/Page.java index 88f107b73..5c6a4180b 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/Page.java +++ b/ccm-cms/src/main/java/org/librecms/pages/Page.java @@ -118,6 +118,14 @@ public class Page extends CcmObject implements Serializable { .map(Collections::unmodifiableMap) .orElse(null); } + + public void addProperty(final String key, final String value) { + properties.put(key, value); + } + + public void removeProperty(final String key) { + properties.remove(key); + } public void setProperties(final Map properties) { this.properties = Optional diff --git a/ccm-cms/src/main/java/org/librecms/ui/PageDetailsModel.java b/ccm-cms/src/main/java/org/librecms/ui/PageDetailsModel.java new file mode 100644 index 000000000..8c27a312a --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/PageDetailsModel.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.librecms.ui; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Named; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +@Named("CmsPageDetailsModel") +public class PageDetailsModel { + + private String site; + + private String categoryDomain; + + private String category; + + private Map pageProperties; + + public String getSite() { + return site; + } + + protected void setSite(String site) { + this.site = site; + } + + public String getCategoryDomain() { + return categoryDomain; + } + + protected void setCategoryDomain(final String categoryDomain) { + this.categoryDomain = categoryDomain; + } + + public String getCategory() { + return category; + } + + protected void setCategory(String category) { + this.category = category; + } + + public Map getPageProperties() { + return Collections.unmodifiableMap(pageProperties); + } + + protected void setPageProperties(final Map pageProperties) { + this.pageProperties = new HashMap<>(pageProperties); + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/PageTreeNodeModel.java b/ccm-cms/src/main/java/org/librecms/ui/PageTreeNodeModel.java new file mode 100644 index 000000000..39c391337 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/PageTreeNodeModel.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.librecms.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * @author Jens Pelzetter + */ +public class PageTreeNodeModel { + + private String categoryName; + + private List children; + + private boolean pageAssigned; + + private Map properties; + + public PageTreeNodeModel() { + children = new ArrayList<>(); + properties = new HashMap<>(); + } + + public String getCategoryName() { + return categoryName; + } + + protected void setCategoryName(final String categoryName) { + this.categoryName = categoryName; + } + + public List getChildren() { + return Collections.unmodifiableList(children); + } + + protected void setChildren(final List children) { + this.children = new ArrayList<>(children); + } + + public boolean isPageAssigned() { + return pageAssigned; + } + + public void setPageAssigned(final boolean pageAssigned) { + this.pageAssigned = pageAssigned; + } + + public Map getProperties() { + return Collections.unmodifiableMap(properties); + } + + public void setProperties(final Map properties) { + this.properties = new HashMap<>(properties); + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/PagesController.java b/ccm-cms/src/main/java/org/librecms/ui/PagesController.java index 6d31214a5..152df813c 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/PagesController.java +++ b/ccm-cms/src/main/java/org/librecms/ui/PagesController.java @@ -18,18 +18,38 @@ */ package org.librecms.ui; +import org.libreccm.api.Identifier; +import org.libreccm.api.IdentifierParser; +import org.libreccm.categorization.Category; +import org.libreccm.categorization.CategoryRepository; import org.libreccm.categorization.Domain; +import org.libreccm.categorization.DomainRepository; import org.libreccm.core.CoreConstants; import org.libreccm.security.AuthorizationRequired; import org.libreccm.security.RequiresPrivilege; +import org.libreccm.sites.Site; +import org.libreccm.sites.SiteRepository; +import org.librecms.pages.Page; +import org.librecms.pages.PageManager; +import org.librecms.pages.PageRepository; +import org.librecms.pages.Pages; +import org.librecms.pages.PagesManager; +import org.librecms.pages.PagesRepository; + +import java.util.HashMap; +import java.util.Optional; +import java.util.stream.Collectors; import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; import javax.mvc.Controller; +import javax.mvc.Models; import javax.transaction.Transactional; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; /** * @@ -40,6 +60,42 @@ import javax.ws.rs.Path; @Path("/pages") public class PagesController { + private static final String PAGES_LIST_TEMPLATE + = "org/librecms/ui/cms/pages.xhtml"; + + @Inject + private CategoryRepository categoryRepo; + + @Inject + private DomainRepository domainRepo; + + @Inject + private IdentifierParser identifierParser; + + @Inject + private Models models; + + @Inject + private PagesDetailsModel pagesDetailsModel; + + @Inject + private PagesManager pagesManager; + + @Inject + private PagesRepository pagesRepo; + + @Inject + private PageDetailsModel pageDetailsModel; + + @Inject + private PageRepository pageRepo; + + @Inject + private PageManager pageManager; + + @Inject + private SiteRepository siteRepo; + /** * Shows all available pages instances. * @@ -49,7 +105,7 @@ public class PagesController { @Path("/") @AuthorizationRequired public String getPages() { - return "org/librecms/ui/cms/pages.xhtml"; + return PAGES_LIST_TEMPLATE; } /** @@ -73,18 +129,405 @@ public class PagesController { @FormParam("primaryUrl") final String primaryUrlParam, @FormParam("categoryDomain") final String categoryDomainParam ) { - // ToDo - throw new UnsupportedOperationException(); + final Optional siteResult = siteRepo.findByDomain(siteParam); + if (siteResult.isEmpty()) { + models.put("siteNotFound", true); + models.put("site", siteParam); + } + + final Optional domainResult = domainRepo.findByDomainKey( + categoryDomainParam + ); + if (domainResult.isEmpty()) { + models.put("domainNotFound", true); + models.put("domainKey", categoryDomainParam); + return PAGES_LIST_TEMPLATE; + } + + if (primaryUrlParam == null || primaryUrlParam.isBlank()) { + models.put("primaryUrlNullOrEmpty", true); + return PAGES_LIST_TEMPLATE; + } + + final Site site = siteResult.get(); + final Domain domain = domainResult.get(); + final String primaryUrl = primaryUrlParam; + + pagesManager.createPages(primaryUrl, site, domain); + + return "redirect:/pages"; + } + + @GET + @Path("/{pagesInstance}") + @AuthorizationRequired + @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) + @Transactional(Transactional.TxType.REQUIRED) + public String showPagesDetails( + @PathParam("pagesInstance") final String pagesInstance + ) { + final Optional pagesResult = findPages(pagesInstance); + if (pagesResult.isEmpty()) { + return showPagesNotFound(pagesInstance); + } + + final Pages pages = pagesResult.get(); + + pagesDetailsModel.setCategoryDomain( + pages.getCategoryDomain().getDomainKey() + ); + pagesDetailsModel.setPrimaryUrl(pages.getPrimaryUrl()); + pagesDetailsModel.setSite(pages.getSite().getDomainOfSite()); + + pagesDetailsModel.setPageTreeRoot( + buildPageTreeNodeModel(pages.getCategoryDomain().getRoot()) + ); + + return "org/librecms/ui/cms/pages-details.xhtml"; } - //ToDo: Update Pages instance - //ToDo: Delete Pages instance //ToDo: Show details for Pages instance - //ToDo: Show details for page - //ToDo: Create Page for category - //ToDo: Edit Page for category - //ToDo: Remove page from category - //ToDo: Add page property - //ToDo: Edit page property + @POST + @Path("/{pagesInstance}/@edit") + @AuthorizationRequired + @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) + @Transactional(Transactional.TxType.REQUIRED) + public String updatePagesInstance( + @PathParam("pagesInstance") final String pagesInstance, + @FormParam("primaryUrl") final String primaryUrlParam + ) { + final Optional pagesResult = findPages(pagesInstance); + if (pagesResult.isEmpty()) { + return showPagesNotFound(pagesInstance); + } + + if (primaryUrlParam == null || primaryUrlParam.isBlank()) { + models.put("primaryUrlNullOrEmpty", true); + return PAGES_LIST_TEMPLATE; + } + + final Pages pages = pagesResult.get(); + pages.setPrimaryUrl(primaryUrlParam); + pagesRepo.save(pages); + + return String.format( + "/pages/%s", pagesInstance + ); + } + + @POST + @Path("/{pagesInstance}/@delete") + @AuthorizationRequired + @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) + @Transactional(Transactional.TxType.REQUIRED) + public String deletePagesInstance( + @PathParam("pagesInstance") final String pagesInstance + ) { + final Optional pagesResult = findPages(pagesInstance); + if (pagesResult.isEmpty()) { + return showPagesNotFound(pagesInstance); + } + + final Pages pages = pagesResult.get(); + pagesRepo.delete(pages); + + return String.format( + "/pages/%s", pagesInstance + ); + } + + @POST + @Path("/{pagesInstance}/{category}/@add") + @AuthorizationRequired + @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) + @Transactional(Transactional.TxType.REQUIRED) + public String createPage( + @PathParam("pagesInstance") final String pagesInstance, + @PathParam("category") final String categoryParam + ) { + final Optional pagesResult = findPages(pagesInstance); + if (pagesResult.isEmpty()) { + return showPagesNotFound(pagesInstance); + } + final Pages pages = pagesResult.get(); + + final Optional categoryResult = categoryRepo.findByPath( + pages.getCategoryDomain(), + categoryParam + ); + + if (categoryResult.isEmpty()) { + models.put("categoryNotFound", true); + models.put( + "categoryDomain", pages.getCategoryDomain().getDomainKey() + ); + models.put("category", categoryParam); + return showPagesDetails(pagesInstance); + } + + final Category category = categoryResult.get(); + + pageManager.createPageForCategory(category); + + return String.format( + "redirect:/pages/%s/%s", + pagesInstance, + categoryParam + ); + } + + @GET + @Path("/{pagesInstance}/{category}/@details") + @AuthorizationRequired + @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) + @Transactional(Transactional.TxType.REQUIRED) + public String showPageDetails( + @PathParam("pagesInstance") final String pagesInstance, + @PathParam("category") final String categoryParam + ) { + final Optional pagesResult = findPages(pagesInstance); + if (pagesResult.isEmpty()) { + return showPagesNotFound(pagesInstance); + } + final Pages pages = pagesResult.get(); + + final Optional categoryResult = categoryRepo.findByPath( + pages.getCategoryDomain(), + categoryParam + ); + + if (categoryResult.isEmpty()) { + models.put("categoryNotFound", true); + models.put( + "categoryDomain", pages.getCategoryDomain().getDomainKey() + ); + models.put("category", categoryParam); + return showPagesDetails(pagesInstance); + } + + final Category category = categoryResult.get(); + + final Page page = pageManager.findPageForCategory(category); + pageDetailsModel.setCategory(category.getName()); + pageDetailsModel.setCategoryDomain( + pages.getCategoryDomain().getDomainKey() + ); + pageDetailsModel.setPageProperties(page.getProperties()); + pageDetailsModel.setSite(pages.getSite().getDomainOfSite()); + + return "org/librecms/ui/cms/pages-detail.xhtml"; + } + + @POST + @Path("/{pagesInstance}/{category}/@remove") + @AuthorizationRequired + @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) + @Transactional(Transactional.TxType.REQUIRED) + public String removePage( + @PathParam("pagesInstance") final String pagesInstance, + @PathParam("category") final String categoryParam + ) { + final Optional pagesResult = findPages(pagesInstance); + if (pagesResult.isEmpty()) { + return showPagesNotFound(pagesInstance); + } + final Pages pages = pagesResult.get(); + + final Optional categoryResult = categoryRepo.findByPath( + pages.getCategoryDomain(), + categoryParam + ); + + if (categoryResult.isEmpty()) { + models.put("categoryNotFound", true); + models.put( + "categoryDomain", pages.getCategoryDomain().getDomainKey() + ); + models.put("category", categoryParam); + return showPagesDetails(pagesInstance); + } + + final Category category = categoryResult.get(); + + final Page page = pageManager.findPageForCategory(category); + pageRepo.delete(page); + + return String.format("redirect:/pages/%s", pagesInstance); + } + + @POST + @Path("/{pagesInstance}/{category}/@add-property") + @AuthorizationRequired + @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) + @Transactional(Transactional.TxType.REQUIRED) + public String addPageProperty( + @PathParam("pagesInstance") final String pagesInstance, + @PathParam("category") final String categoryParam, + @FormParam("propertyKey") final String propertyKey, + @FormParam("propertyValue") final String propertyValue + ) { + final Optional pagesResult = findPages(pagesInstance); + if (pagesResult.isEmpty()) { + return showPagesNotFound(pagesInstance); + } + final Pages pages = pagesResult.get(); + + final Optional categoryResult = categoryRepo.findByPath( + pages.getCategoryDomain(), + categoryParam + ); + + if (categoryResult.isEmpty()) { + models.put("categoryNotFound", true); + models.put( + "categoryDomain", pages.getCategoryDomain().getDomainKey() + ); + models.put("category", categoryParam); + return showPagesDetails(pagesInstance); + } + + final Category category = categoryResult.get(); + + if (propertyKey == null || propertyKey.isBlank()) { + models.put("propertyKeyEmpty", true); + return showPageDetails(pagesInstance, categoryParam); + } + + if (propertyValue == null || propertyValue.isBlank()) { + models.put("propertyValueEmpty", true); + return showPageDetails(pagesInstance, categoryParam); + } + + final Page page = pageManager.findPageForCategory(category); + page.addProperty(propertyKey, propertyValue); + pageRepo.save(page); + + return String.format( + "redirect:/pages/%s/%s", + pagesInstance, + categoryParam + ); + } + + @POST + @Path("/{pagesInstance}/{category}/{propertyKey}/@edit") + @AuthorizationRequired + @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) + @Transactional(Transactional.TxType.REQUIRED) + public String updatePageProperty( + @PathParam("pagesInstance") final String pagesInstance, + @PathParam("category") final String categoryParam, + @PathParam("propertyKey") final String propertyKey, + @FormParam("propertyValue") final String propertyValue + ) { + return addPageProperty( + pagesInstance, + categoryParam, + propertyKey, + propertyValue + ); + } + + @POST + @Path("/{pagesInstance}/{category}/{propertyKey}/@remove") + @AuthorizationRequired + @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) + @Transactional(Transactional.TxType.REQUIRED) + public String removePageProperty( + @PathParam("pagesInstance") final String pagesInstance, + @PathParam("category") final String categoryParam, + @PathParam("propertyKey") final String propertyKey + ) { + final Optional pagesResult = findPages(pagesInstance); + if (pagesResult.isEmpty()) { + return showPagesNotFound(pagesInstance); + } + final Pages pages = pagesResult.get(); + + final Optional categoryResult = categoryRepo.findByPath( + pages.getCategoryDomain(), + categoryParam + ); + + if (categoryResult.isEmpty()) { + models.put("categoryNotFound", true); + models.put( + "categoryDomain", pages.getCategoryDomain().getDomainKey() + ); + models.put("category", categoryParam); + return showPagesDetails(pagesInstance); + } + + final Category category = categoryResult.get(); + + if (propertyKey == null || propertyKey.isBlank()) { + models.put("propertyKeyEmpty", true); + return showPageDetails(pagesInstance, categoryParam); + } + + final Page page = pageManager.findPageForCategory(category); + page.removeProperty(propertyKey); + pageRepo.save(page); + + return String.format( + "redirect:/pages/%s/%s", + pagesInstance, + categoryParam + ); + } + //ToDo: Remove page property + private Optional findPages(final String identifierParam) { + final Identifier identifier = identifierParser.parseIdentifier( + identifierParam + ); + switch (identifier.getType()) { + case UUID: + return pagesRepo.findPagesForSite( + identifier.getIdentifier() + ); + case ID: + return pagesRepo.findById( + Long.parseLong(identifier.getIdentifier()) + ); + default: + return pagesRepo.findPagesForSite( + identifier.getIdentifier() + ); + } + } + + private String showPagesNotFound(final String pagesInstance) { + models.put("pagesInstanceNotFound", true); + models.put("pagesInstance", pagesInstance); + return PAGES_LIST_TEMPLATE; + } + + private PageTreeNodeModel buildPageTreeNodeModel( + final Category category + ) { + final Optional pageResult = pageRepo.findPageForCategory( + category + ); + + final PageTreeNodeModel node = new PageTreeNodeModel(); + node.setCategoryName(category.getName()); + node.setPageAssigned(pageResult.isPresent()); + node.setProperties( + pageResult + .map(Page::getProperties) + .orElse(new HashMap<>()) + ); + + node.setChildren( + category + .getSubCategories() + .stream() + .map(this::buildPageTreeNodeModel) + .collect(Collectors.toList()) + ); + + return node; + } + } diff --git a/ccm-cms/src/main/java/org/librecms/ui/PagesDetailsModel.java b/ccm-cms/src/main/java/org/librecms/ui/PagesDetailsModel.java new file mode 100644 index 000000000..7290d51ac --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/PagesDetailsModel.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.librecms.ui; + +import java.util.ArrayList; +import java.util.List; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Named; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +@Named("CmsPagesDetailsModel") +public class PagesDetailsModel { + + private String site; + + private String primaryUrl; + + private String categoryDomain; + + private PageTreeNodeModel pageTreeRoot; + + + public String getSite() { + return site; + } + + protected void setSite(final String site) { + this.site = site; + } + + public String getPrimaryUrl() { + return primaryUrl; + } + + protected void setPrimaryUrl(final String primaryUrl) { + this.primaryUrl = primaryUrl; + } + + public String getCategoryDomain() { + return categoryDomain; + } + + protected void setCategoryDomain(final String categoryDomain) { + this.categoryDomain = categoryDomain; + } + + public PageTreeNodeModel getPageTreeRoot() { + return pageTreeRoot; + } + + protected void setPageTreeRoot(final PageTreeNodeModel pageTreeRoot) { + this.pageTreeRoot = pageTreeRoot; + } + +}