diff --git a/ccm-bundle-devel-wildfly/src/main/resources/log4j2.xml b/ccm-bundle-devel-wildfly/src/main/resources/log4j2.xml index c2eed1bd8..ff0533652 100644 --- a/ccm-bundle-devel-wildfly/src/main/resources/log4j2.xml +++ b/ccm-bundle-devel-wildfly/src/main/resources/log4j2.xml @@ -107,7 +107,11 @@ - + + + diff --git a/ccm-cms-default-theme/package.json b/ccm-cms-default-theme/package.json index fcda5aec9..bff2e6b99 100644 --- a/ccm-cms-default-theme/package.json +++ b/ccm-cms-default-theme/package.json @@ -5,6 +5,7 @@ "build": "npm-run-all build:*", "build:mkdir": "shx mkdir -p target/generated-resources/themes/librecms", "build:theme": "shx cp -r src/main/resources/themes/librecms/* target/generated-resources/themes/librecms", + "build:icons": "shx cp node_modules/bootstrap-icons/bootstrap-icons.svg target/generated-resources/themes/librecms/images/", "build:js": "webpack", "build:css": "npm-run-all build:css:*", "build:css:librecms": "sass src/main/scss/librecms.scss target/generated-resources/themes/librecms/styles/librecms.css", diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/images/libreccm.svg b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/libreccm.svg new file mode 100644 index 000000000..108c91fac --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/libreccm.svg @@ -0,0 +1,803 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/images/librecms.svg b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/librecms.svg new file mode 100644 index 000000000..f73a74cb6 --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/librecms.svg @@ -0,0 +1,722 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/images/placeholder.png b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/placeholder.png new file mode 100644 index 000000000..c09d904b1 Binary files /dev/null and b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/placeholder.png differ diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/images/placeholder_2.png b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/placeholder_2.png new file mode 100644 index 000000000..f131d24da Binary files /dev/null and b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/placeholder_2.png differ diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/images/placeholder_3.png b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/placeholder_3.png new file mode 100644 index 000000000..27b7e2027 Binary files /dev/null and b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/placeholder_3.png differ diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/images/scientificcms.svg b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/scientificcms.svg new file mode 100644 index 000000000..e7c9029bc --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/images/scientificcms.svg @@ -0,0 +1,756 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/assets.html.ftl b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/assets.html.ftl new file mode 100644 index 000000000..087d2b556 --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/assets.html.ftl @@ -0,0 +1,63 @@ +<#macro "org.librecms.assets.AudioAsset" asset> +
+ +
${asset.description}
+
+ + +<#macro "org.librecms.assets.ExternalAudioAsset" asset> +
+ +
${asset.description}
+
+ + +<#macro "org.librecms.assets.ExternalVideoAsset" asset> +
+ +
${asset.description}
+
+ + +<#macro "org.librecms.assets.FileAsset" asset> +

${asset.title}

+

${asset.description}

+ ${asset.mimeType} ${asset.size} Bytes + + +<#macro "org.librecms.assets.Image" asset> +
+ +
${asset.description}
+
+ + + +<#macro "org.librecms.assets.RelatedLink" asset> + <#if asset.externalLink> +
+ ${asset.title} + + + + External link +
+ <#else> + ${asset.title} + + + +<#macro "org.librecms.assets.VideoAsset" asset> +
+ +
${asset.description}
+
+ diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/category-page.html.ftl b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/category-page.html.ftl new file mode 100644 index 000000000..7d63836f3 --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/category-page.html.ftl @@ -0,0 +1,51 @@ +<#import "./main.html.ftl" as main> +<#import "./assets.html.ftl" as assets> + +<@main.librecms> +
+
+
+ <#if CmsPagesContentItemTypeModel.itemClass != ""> + <#assign itemTemplate = .getOptionalTemplate("./contentitems/${CmsPagesContentItemTypeModel.itemClass}.html.ftl")> + <#if itemTemplate.exists> + <#import "./contentitems/${CmsPagesContentItemTypeModel.itemClass}.html.ftl" as contentitem> + <#else> + <#import "./contentitems/default.html.ftl" as contentitem> + + <@contentitem.details /> + +
+
+ <#list CmsPagesCategorizedItemModel.attachmentLists as attachmentList> +

${attachmentList.title}

+

${attachmentList.description}

+ <#list attachmentList.attachments> +
    + <#items as attachment> +
  • + <@.vars["assets"][attachment.asset.type] attachment.asset /> +
  • + +
+ + +
+
+ <#list CmsPagesItemListModel.getItems()> +
+
+
    + <#items as item> +
  • +

    + ${item.title} +

    +

    ${item.description}

    +
  • + +
+
+
+
+ + \ No newline at end of file diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/default.html.ftl b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/default.html.ftl new file mode 100644 index 000000000..0f34ea715 --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/default.html.ftl @@ -0,0 +1,7 @@ +<#macro details> +

${CmsPagesCategorizedItemModel.title}

+

${CmsPagesCategorizedItemModel.description}

+
+ No template for ${CmsPagesContentItemTypeModel.itemClass} available +
+ \ No newline at end of file diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.Article.html.ftl b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.Article.html.ftl new file mode 100644 index 000000000..a9e3b83b9 --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.Article.html.ftl @@ -0,0 +1,17 @@ +<#import "../assets.html.ftl" as assets> + +<#macro details> +

${CmsPagesCategorizedItemModel.title}

+

${CmsPagesCategorizedItemModel.description}

+ +
+ <#list CmsPagesCategorizedItemModel.mediaLists as mediaList> + <#list mediaList.attachments as media> + <@.vars["assets"][media.asset.type] media.asset /> + + +
+
+ ${CmsPagesArticleModel.text} +
+ \ No newline at end of file diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.Event.html.ftl b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.Event.html.ftl new file mode 100644 index 000000000..ef2ce59ed --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.Event.html.ftl @@ -0,0 +1,25 @@ +<#macro details> +
+

+ ${CmsPagesCategorizedItemModel.title} + + ${CmsPagesEventModel.getStartDateTime('yyyy-MM-dd')} + <#if CmsPagesEventModel.getEndDateTime()??> + - ${CmsPagesEventModel.getEndDateTime('yyyy-MM-dd')} + + +

+
+

${CmsPagesCategorizedItemModel.description}

+ +
+ ${CmsPagesEventModel.text} +
+ + <#if CmsPagesEventModel.location??> +

Location

+
+ ${CmsPagesEventModel.location} +
+ + \ No newline at end of file diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.MultiPartArticle.html.ftl b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.MultiPartArticle.html.ftl new file mode 100644 index 000000000..6a5f432f9 --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.MultiPartArticle.html.ftl @@ -0,0 +1,61 @@ +<#macro details> +

${CmsPagesCategorizedItemModel.title}

+

${CmsPagesCategorizedItemModel.description}

+ +

${CmsPagesMultiPartArticleModel.currentSectionTitle}

+
+ + ${CmsPagesMultiPartArticleModel.currentSectionText} + +
+ \ No newline at end of file diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.News.html.ftl b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.News.html.ftl new file mode 100644 index 000000000..8d18d9b4f --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/contentitems/org.librecms.contenttypes.News.html.ftl @@ -0,0 +1,15 @@ +<#macro details> +
+

+ ${CmsPagesCategorizedItemModel.title} + + ${CmsPagesNewsModel.getReleaseDate('yyyy-MM-dd')} + +

+
+

${CmsPagesCategorizedItemModel.description}

+ +
+ ${CmsPagesNewsModel.text} +
+ \ No newline at end of file diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/debug.html.ftl b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/debug.html.ftl new file mode 100644 index 000000000..a8c0dbf69 --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/debug.html.ftl @@ -0,0 +1,21 @@ + + + + + + Default Template + + +
+

This theme works.

+
+
application
+
${application}
+
themeUrl
+
${themeUrl}
+
view
+
${view!""}
+
+
+ + \ No newline at end of file diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/default.html.ftl b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/default.html.ftl index a8c0dbf69..b584c3797 100644 --- a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/default.html.ftl +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/default.html.ftl @@ -1,21 +1,48 @@ - - - - - - Default Template - - -
-

This theme works.

-
-
application
-
${application}
-
themeUrl
-
${themeUrl}
-
view
-
${view!""}
-
+<#import "./main.html.ftl" as main> +<#-- <#import "./contentitems/${CmsPagesContentItemTypeModel.itemClass}.html.ftl" as contentitem> --> + +<@main.librecms> +
+
+
+ <#if CmsPagesContentItemTypeModel.itemClass != ""> + <#assign itemTemplate = .getOptionalTemplate("./contentitems/${CmsPagesContentItemTypeModel.itemClass}.html.ftl")> + <#if itemTemplate.exists> + <#import "./contentitems/${CmsPagesContentItemTypeModel.itemClass}.html.ftl" as contentitem> + <#else> + <#import "./contentitems/default.html.ftl" as contentitem> + + <@contentitem.details /> + +
+
+ +

Notes

+
    +
  • +

    + A side note with some text +

    +

    + Occaecat sit eu ipsum irure. Enim consectetur aute anim proident sint dolor sint ea ex eu adipisicing et. Veniam laborum mollit velit incididunt aliquip do esse officia eu ea nostrud nulla. +

    +
  • +
+

More information

+

+ Consequat occaecat eu ullamco amet id tempor. +

+
    +
  • +

    Anim ex ut reprehenderit in enim id proident duis pariatur est anim do.

    + A related link +
  • +
  • +

    Quis minim deserunt incididunt ea voluptate laboris fugiat elit nulla.

    + Some download link +
  • +
+
- - \ No newline at end of file +
+ \ No newline at end of file diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/index-page.html.ftl b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/index-page.html.ftl index f66d6bc54..f30dbcd68 100644 --- a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/index-page.html.ftl +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/index-page.html.ftl @@ -1,22 +1,173 @@ - - - - - - Default Template - - -
-

Index page

-

This theme works.

-
-
application
-
${application}
-
themeUrl
-
${themeUrl}
-
view
-
${view!""}
-
+<#import "./main.html.ftl" as main> + +<@main.librecms> +
+
+
+
+
+ <#if CmsPagesCategorizedItemModel.itemAvailable> +

+ ${CmsPagesCategorizedItemModel.title} +

+

+ ${CmsPagesCategorizedItemModel.description} +

+ + Find out more + + <#else> +

+ LibreCMS +

+

+ No index item has been defined. +

+ + Find out more + + +
+
+ + <#-- <#if CmsPagesCategorizedItemModel.itemAvailable> + Category has an index item + <#else> + Category has no index item + + +

Index page

+

This theme works.

+
+
application
+
${application}
+
themeUrl
+
${themeUrl}
+
view
+
${view!""}
+
+ +

From ArticleModel

+
+
Title
+
${CmsPagesArticleModel.title}
+
Description
+
${CmsPagesArticleModel.description}
+
Text
+
${CmsPagesArticleModel.text}
+
+
-->
- - \ No newline at end of file + +
+ <#list CmsPagesItemListModel.getItems("newslist")> +
+

News

+
    + <#items as news> +
  • +

    + + <#--
    ${news.getReleaseDate('yyyy-MM-dd')}
    --> +
    ${news.getReleaseDate('dd. MMM yyyy')}
    +

    +

    ${news.description}

    +
  • + +
+
+ + <#list CmsPagesItemListModel.getItems("eventlist")> +
+

Upcoming events

+
    + <#items as event> +
  • +

    + +
    ${event.getStartDate('dd. MMM yyyy HH:mm')}
    +

    +

    ${event.description}

    +
  • + +
+
+ +
+ + + <#--

Item List

+

Item List size: ${CmsPagesItemListModel.listSize}

+
+
Item List size:
+
${CmsPagesItemListModel.listSize}
+
Page size
+
${CmsPagesItemListModel.pageSize}
+
Page
+
${CmsPagesItemListModel.page}
+
Offset
+
${CmsPagesItemListModel.offset}
+
+
    + <#list CmsPagesItemListModel.items as item> +
  • +
    +
    UUID
    +
    ${item.uuid}
    +
    displayName
    +
    ${item.displayName}
    +
    Name
    +
    ${item.name}
    +
    Title
    +
    ${item.title}
    +
    description
    +
    ${item.description}
    +
    Type
    +
    ${item.type}
    +
    +
  • + +
+
+ +

News List

+

News List size: ${CmsPagesItemListModel.getListSize("newslist")}

+
+
Item List size:
+
${CmsPagesItemListModel.getListSize("newslist")}
+
Page size
+
${CmsPagesItemListModel.getPageSize("newslist")}
+
Page
+
${CmsPagesItemListModel.getPage("newslist")}
+
Offset
+
${CmsPagesItemListModel.getOffset("newslist")}
+
+
    + <#list CmsPagesItemListModel.getItems("newslist") as item> +
  • +
    +
    UUID
    +
    ${item.uuid}
    +
    displayName
    +
    ${item.displayName}
    +
    Name
    +
    ${item.name}
    +
    Title
    +
    ${item.title}
    +
    description
    +
    ${item.description}
    +
    Type
    +
    ${item.type}
    +
    +
  • + +
+
--> + + \ No newline at end of file diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/main.html.ftl b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/main.html.ftl new file mode 100644 index 000000000..78f5c7251 --- /dev/null +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/templates/main.html.ftl @@ -0,0 +1,50 @@ +<#macro librecms scripts=[]> + + + + + + Default Template + + +
+ +
+
+ <#nested> +
+ + + + \ No newline at end of file diff --git a/ccm-cms-default-theme/src/main/resources/themes/librecms/theme.json b/ccm-cms-default-theme/src/main/resources/themes/librecms/theme.json index a471bbb4e..f9d2371da 100644 --- a/ccm-cms-default-theme/src/main/resources/themes/librecms/theme.json +++ b/ccm-cms-default-theme/src/main/resources/themes/librecms/theme.json @@ -32,6 +32,30 @@ }, "mvc-templates": { + "category-page": { + "description": { + "values": { + "value": [ + { + "lang": "en", + "value": "Template for category pages." + } + ] + } + }, + "name": "Category Page Template", + "path": "templates/category-page.html.ftl", + "title": { + "values": { + "value": [ + { + "lang": "en", + "value": "Category Page Template" + } + ] + } + } + }, "default": { "description": { "values": { @@ -88,6 +112,7 @@ "@default": "default" }, "pages": { + "category-page": "category-page", "index": "index-page", "default": "default", "@default": "default" diff --git a/ccm-cms-default-theme/src/main/scss/_custom.scss b/ccm-cms-default-theme/src/main/scss/_custom.scss index e69de29bb..ca6725969 100644 --- a/ccm-cms-default-theme/src/main/scss/_custom.scss +++ b/ccm-cms-default-theme/src/main/scss/_custom.scss @@ -0,0 +1,15 @@ +$primary: #0A9793; + +a.navbar-brand { + max-width: 15%; +} + +.imgbox-button { + background-color: transparent; + border: none; + padding: 0; +} + +.item-description { + white-space: pre-wrap; +} \ No newline at end of file diff --git a/ccm-cms/src/main/java/org/librecms/CmsConstants.java b/ccm-cms/src/main/java/org/librecms/CmsConstants.java index 1a3ff9ad1..1c6a28feb 100644 --- a/ccm-cms/src/main/java/org/librecms/CmsConstants.java +++ b/ccm-cms/src/main/java/org/librecms/CmsConstants.java @@ -61,6 +61,8 @@ public class CmsConstants { public static final String FORM_ENCTYPE_MULTIPART = "multipart/form-data"; + public static final String MEDIA_LIST_PREFIX = ".media-"; + /** * Constant string used as key for creating service package as a legacy * application. diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/FolderManager.java b/ccm-cms/src/main/java/org/librecms/contentsection/FolderManager.java index e6ad58d21..db451c04c 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/FolderManager.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/FolderManager.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; @@ -509,13 +510,27 @@ public class FolderManager { } Collections.reverse(tokens); - final String path = String.join("/", tokens); + final String path = tokens + .stream() + .filter(token -> !"/".equals(token)) + .collect( + Collectors.joining("/") + ); + //final String path = String.join("/", tokens); if (withContentSection) { final String sectionName = folder.getSection().getDisplayName(); - return String.format("%s:/%s/", sectionName, path); + if (path.isEmpty()) { + return String.format("%s:/", sectionName); + } else { + return String.format("%s:/%s/", sectionName, path); + } } else { - return String.format("/%s/", path); + if (path.isEmpty()) { + return "/"; + } else { + return String.format("/%s/", path); + } } } diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/rs/AudioMedia.java b/ccm-cms/src/main/java/org/librecms/contentsection/rs/AudioMedia.java index ad2d28f47..4cf68dc7c 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/rs/AudioMedia.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/rs/AudioMedia.java @@ -18,15 +18,6 @@ */ package org.librecms.contentsection.rs; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.librecms.assets.BinaryAssetDataService; -import org.librecms.assets.AudioAsset; -import org.librecms.contentsection.Asset; -import org.librecms.contentsection.AssetRepository; -import org.librecms.contentsection.ContentSection; -import org.librecms.contentsection.ContentSectionRepository; - import java.io.IOException; import java.io.OutputStream; import java.util.Optional; @@ -40,6 +31,15 @@ import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.librecms.assets.AudioAsset; +import org.librecms.assets.BinaryAssetDataService; +import org.librecms.contentsection.Asset; +import org.librecms.contentsection.AssetRepository; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.ContentSectionRepository; + /** * * @author Jens Pelzetter @@ -84,7 +84,7 @@ public class AudioMedia { } @GET - @Path("/{path:^(?!uuid).+$}") + @Path("/{path:.+}") public Response getAudio( @PathParam("content-section") final String sectionName, @PathParam("path") final String path diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/rs/Files.java b/ccm-cms/src/main/java/org/librecms/contentsection/rs/Files.java index 14135e8b0..f44bc6db4 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/rs/Files.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/rs/Files.java @@ -32,6 +32,7 @@ import java.util.Optional; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; +import javax.transaction.Transactional; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -44,7 +45,7 @@ import javax.ws.rs.core.StreamingOutput; * @author Jens Pelzetter */ @RequestScoped -@Path("/{content-section}/files") +@Path("/{content-section}/files/") public class Files { private static final Logger LOGGER = LogManager.getLogger(Files.class); @@ -60,6 +61,7 @@ public class Files { @GET @Path("/uuid-{uuid}") + @Transactional(Transactional.TxType.REQUIRED) public Response getFileByUuid( @PathParam("content-section") final String sectionName, @PathParam("uuid") final String uuid @@ -82,7 +84,9 @@ public class Files { } @GET - @Path("/{path:^(?!uuid).+$}") + //@Path("/{path:^(?!uuid).+$}") + @Path("/{path:.+}") + @Transactional(Transactional.TxType.REQUIRED) public Response getFile( @PathParam("content-section") final String sectionName, @PathParam("path") final String path diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/rs/Images.java b/ccm-cms/src/main/java/org/librecms/contentsection/rs/Images.java index e77275739..69aa2739b 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/rs/Images.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/rs/Images.java @@ -18,14 +18,6 @@ */ package org.librecms.contentsection.rs; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.librecms.assets.Image; -import org.librecms.contentsection.Asset; -import org.librecms.contentsection.AssetRepository; -import org.librecms.contentsection.ContentSection; -import org.librecms.contentsection.ContentSectionRepository; - import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -45,6 +37,14 @@ import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.librecms.assets.Image; +import org.librecms.contentsection.Asset; +import org.librecms.contentsection.AssetRepository; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.ContentSectionRepository; + /** * * @author Jens Pelzetter @@ -139,7 +139,7 @@ public class Images { * @return A {@link Response} containing the scaled image or an error value. */ @GET - @Path("/{path:^(?!uuid).+$}") + @Path("/{path:.+}") public Response getImage( @PathParam("content-section") final String sectionName, @PathParam("path") final String path, diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/rs/Videos.java b/ccm-cms/src/main/java/org/librecms/contentsection/rs/Videos.java index cecd277ac..6453e4f1b 100644 --- a/ccm-cms/src/main/java/org/librecms/contentsection/rs/Videos.java +++ b/ccm-cms/src/main/java/org/librecms/contentsection/rs/Videos.java @@ -18,15 +18,6 @@ */ package org.librecms.contentsection.rs; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.librecms.assets.BinaryAssetDataService; -import org.librecms.assets.VideoAsset; -import org.librecms.contentsection.Asset; -import org.librecms.contentsection.AssetRepository; -import org.librecms.contentsection.ContentSection; -import org.librecms.contentsection.ContentSectionRepository; - import java.io.IOException; import java.io.OutputStream; import java.util.Optional; @@ -40,6 +31,15 @@ import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.librecms.assets.BinaryAssetDataService; +import org.librecms.assets.VideoAsset; +import org.librecms.contentsection.Asset; +import org.librecms.contentsection.AssetRepository; +import org.librecms.contentsection.ContentSection; +import org.librecms.contentsection.ContentSectionRepository; + /** * * @author Jens Pelzetter @@ -84,7 +84,7 @@ public class Videos { } @GET - @Path("/{path:^(?!uuid).+$}") + @Path("/{path:.+}") public Response getVideo( @PathParam("content-section") final String sectionName, @PathParam("path") final String path diff --git a/ccm-cms/src/main/java/org/librecms/pages/PagesController.java b/ccm-cms/src/main/java/org/librecms/pages/PagesController.java index 35e515e58..26decc332 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/PagesController.java +++ b/ccm-cms/src/main/java/org/librecms/pages/PagesController.java @@ -38,6 +38,7 @@ import org.librecms.contentsection.ContentItemVersion; import org.librecms.pages.models.CategoryModel; import org.librecms.pages.models.ContentItemModel; import org.librecms.pages.models.PagePropertiesModel; +import org.librecms.pages.models.PageUrlModel; import org.librecms.pages.models.SiteInfoModel; import java.net.URI; @@ -196,6 +197,9 @@ public class PagesController { @Inject private PagePropertiesModel pagePropertiesModel; + @Inject + private PageUrlModel pageUrlModel; + @Inject private PagesRepository pagesRepo; @@ -242,6 +246,8 @@ public class PagesController { final Versions versions = generateFromPreviewParam(preview); final String language = determineLanguage(category, versions); + initPageUrlModel(uriInfo); + final String indexPage = String.format( "/index.%s.html%s", language, @@ -271,6 +277,8 @@ public class PagesController { final Versions versions = generateFromPreviewParam(preview); final String language = determineLanguage(category, versions); + initPageUrlModel(uriInfo); + final String itemPage = String.format( "/%s.%s.html%s", itemName, @@ -301,6 +309,8 @@ public class PagesController { final Versions versions = generateFromPreviewParam(preview); final String language = determineLanguage(category, itemName, versions); + initPageUrlModel(uriInfo); + final String itemPage = String.format( "/%s.%s.html", itemName, language ); @@ -407,6 +417,8 @@ public class PagesController { final Versions versions = generateFromPreviewParam(preview); final String language = determineLanguage(category, versions); + initPageUrlModel(uriInfo); + final String redirectTo; if (uriInfo.getPath().endsWith("/")) { redirectTo = String.format( @@ -461,6 +473,8 @@ public class PagesController { final Versions versions = generateFromPreviewParam(preview); final String language = determineLanguage(category, versions); + initPageUrlModel(uriInfo); + final String redirectTo; if (uriInfo.getPath().endsWith("/")) { redirectTo = String.format( @@ -535,6 +549,7 @@ public class PagesController { .sorted() .collect(Collectors.toList()) ); + initPageUrlModel(uriInfo); siteInfoModel.setDomain(site.getDomainOfSite()); siteInfoModel.setHost(domain); siteInfoModel.setName( @@ -546,9 +561,16 @@ public class PagesController { categoryModel.init(pages.getCategoryDomain(), category, version); final Page page = pageManager.findPageForCategory(category); pagePropertiesModel.setProperties(page.getProperties()); - return themesMvc.getMvcTemplate( - uriInfo, "pages", page.getDisplayName() - ); + if (itemName.equals("index") || itemName.isBlank()) { + return themesMvc.getMvcTemplate( + uriInfo, "pages", page.getDisplayName() + ); + } else { + return themesMvc.getMvcTemplate( + uriInfo, "pages", "item-page" + ); + } + } private void initSiteInfoModel( @@ -806,6 +828,25 @@ public class PagesController { } } + private void initPageUrlModel(final UriInfo uriInfo) { + pageUrlModel.setHost(uriInfo.getRequestUri().getHost()); + pageUrlModel.setPath(uriInfo.getPath()); + pageUrlModel.setPort(uriInfo.getRequestUri().getPort()); + pageUrlModel.setProtocol(uriInfo.getRequestUri().getScheme()); + pageUrlModel.setQueryParameters( + uriInfo + .getQueryParameters() + .entrySet() + .stream() + .collect( + Collectors.toMap( + entry -> entry.getKey(), + entry -> entry.getValue().get(0) + ) + ) + ); + } + /** * Encapsulate the result of converting the value of the {@code preview} * query parameter. diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/AbstractAssetModelBuilder.java b/ccm-cms/src/main/java/org/librecms/pages/models/AbstractAssetModelBuilder.java index 501676ec3..586ed79b6 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/AbstractAssetModelBuilder.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/AbstractAssetModelBuilder.java @@ -29,31 +29,40 @@ import javax.inject.Inject; * @param * @param */ -public abstract class AbstractAssetModelBuilder -implements AssetModelBuilder { - +public abstract class AbstractAssetModelBuilder + implements AssetModelBuilder { + @Inject private GlobalizationHelper globalizationHelper; - + @Override @SuppressWarnings("unchecked") - public M buildAssetModel(final T asset) { + public M buildAssetModel(final Asset asset) { + if (!asset.getClass().isAssignableFrom(buildsAssetModelFor())) { + throw new IllegalArgumentException( + String.format( + "This builder can only process Assets of type %s.", + buildsAssetModelFor().getName() + ) + ); + } + final M model = buildModel(); model.setDisplayName(asset.getDisplayName()); model.setTitle( globalizationHelper.getValueFromLocalizedString(asset.getTitle()) ); model.setUuid(asset.getUuid()); - - addProperties(asset, model); - + + addProperties((T) asset, model); + return model; } - + protected abstract M buildModel(); - + protected void addProperties(final T asset, final M model) { // Nothing in the default implementation. } - + } diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/AbstractBinaryAssetModelBuilder.java b/ccm-cms/src/main/java/org/librecms/pages/models/AbstractBinaryAssetModelBuilder.java index 735040ab8..af47bf3e7 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/AbstractBinaryAssetModelBuilder.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/AbstractBinaryAssetModelBuilder.java @@ -21,8 +21,8 @@ package org.librecms.pages.models; import org.libreccm.l10n.GlobalizationHelper; import org.librecms.assets.BinaryAsset; import org.librecms.contentsection.AssetManager; +import org.librecms.contentsection.Folder; -import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.transaction.Transactional; @@ -32,7 +32,6 @@ import javax.transaction.Transactional; * @param * @param */ -@RequestScoped public abstract class AbstractBinaryAssetModelBuilder extends AbstractAssetModelBuilder { @@ -48,6 +47,12 @@ public abstract class AbstractBinaryAssetModelBuilder folder.getSection().getDisplayName()) + .orElse("") + ); model.setDescription( globalizationHelper.getValueFromLocalizedString( asset.getDescription() diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/AbstractContentItemListItemModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/AbstractContentItemListItemModel.java index f2af5b253..e4ca2c550 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/AbstractContentItemListItemModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/AbstractContentItemListItemModel.java @@ -18,6 +18,8 @@ */ package org.librecms.pages.models; +import java.util.Locale; + /** * A simplified representation of a content item for use in a list of content * items.Base class for other more specific models. @@ -26,6 +28,12 @@ package org.librecms.pages.models; */ public abstract class AbstractContentItemListItemModel { + /** + * The locale negotiated with the user agent. Useful for example for + * formatting date. Only for internal use. + */ + private Locale locale; + private String uuid; private String displayName; @@ -35,6 +43,14 @@ public abstract class AbstractContentItemListItemModel { private String title; private String description; + + protected Locale getLocale() { + return locale; + } + + protected void setLocale(final Locale locale) { + this.locale = locale; + } public String getUuid() { return uuid; @@ -76,6 +92,10 @@ public abstract class AbstractContentItemListItemModel { this.description = description; } + public String getLang() { + return locale.toString(); + } + /** * Returns the type of the content item. In most cases implementations * should return the fully qualified name of the content item class here. diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/AbstractContentItemListItemModelBuilder.java b/ccm-cms/src/main/java/org/librecms/pages/models/AbstractContentItemListItemModelBuilder.java index 39c58ae8f..0d4494de8 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/AbstractContentItemListItemModelBuilder.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/AbstractContentItemListItemModelBuilder.java @@ -39,16 +39,14 @@ public abstract class AbstractContentItemListItemModelBuilder { - M buildAssetModel(T asset); + M buildAssetModel(Asset asset); Class buildsAssetModelFor(); diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/AttachmentListModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/AttachmentListModel.java new file mode 100644 index 000000000..b4163e259 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/pages/models/AttachmentListModel.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2022 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.pages.models; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * + * @author Jens Pelzetter + */ +public class AttachmentListModel implements Comparable{ + + private long listId; + + private String uuid; + + private String name; + + private long listOrder; + + private String title; + + private String description; + + private List attachments; + + public long getListId() { + return listId; + } + + public void setListId(final long listId) { + this.listId = listId; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(final String uuid) { + this.uuid = uuid; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public long getListOrder() { + return listOrder; + } + + public void setListOrder(final long listOrder) { + this.listOrder = listOrder; + } + + public String getTitle() { + return title; + } + + public void setTitle(final String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public List getAttachments() { + return Collections.unmodifiableList(attachments); + } + + public void setAttachments(final List attachments) { + this.attachments = new ArrayList<>(attachments); + } + + @Override + public int compareTo(final AttachmentListModel other) { + return Comparator + .comparing(AttachmentListModel::getListOrder) + .thenComparing( + AttachmentListModel::getName, String::compareToIgnoreCase + ) + .compare(this, other); + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/AttachmentModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/AttachmentModel.java new file mode 100644 index 000000000..0eb48c58d --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/pages/models/AttachmentModel.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 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.pages.models; + +import java.util.Comparator; + +/** + * + * @author Jens Pelzetter + */ +public class AttachmentModel implements Comparable{ + + private long attachmentId; + + private String uuid; + + private long sortKey; + + private AbstractAssetModel asset; + + public long getAttachmentId() { + return attachmentId; + } + + public void setAttachmentId(final long attachmentId) { + this.attachmentId = attachmentId; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(final String uuid) { + this.uuid = uuid; + } + + public long getSortKey() { + return sortKey; + } + + public void setSortKey(final long sortKey) { + this.sortKey = sortKey; + } + + public AbstractAssetModel getAsset() { + return asset; + } + + public void setAsset(final AbstractAssetModel asset) { + this.asset = asset; + } + + @Override + public int compareTo(final AttachmentModel other) { + return Comparator + .comparing(AttachmentModel::getSortKey) + .thenComparing(AttachmentModel::getAttachmentId) + .compare(this, other); + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/BinaryAssetModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/BinaryAssetModel.java index cc1dcc62b..578e0f81f 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/BinaryAssetModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/BinaryAssetModel.java @@ -27,17 +27,19 @@ import org.librecms.assets.BinaryAsset; public class BinaryAssetModel extends AbstractAssetModel { private String description; - + private String fileName; - + private String mimeType; - + private long size; - + private String binaryAssetUuid; - + + private String contentSection; + private String assetPath; - + @Override public String getType() { return BinaryAsset.class.getName(); @@ -83,6 +85,14 @@ public class BinaryAssetModel extends AbstractAssetModel { this.binaryAssetUuid = binaryAssetUuid; } + public String getContentSection() { + return contentSection; + } + + public void setContentSection(final String contentSection) { + this.contentSection = contentSection; + } + public String getAssetPath() { return assetPath; } @@ -90,9 +100,5 @@ public class BinaryAssetModel extends AbstractAssetModel { public void setAssetPath(final String assetPath) { this.assetPath = assetPath; } - - - - - + } diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemModel.java index 1a475683d..05eb4bc41 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemModel.java @@ -18,28 +18,35 @@ */ package org.librecms.pages.models; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.libreccm.categorization.CategoryManager; import org.libreccm.categorization.CategoryRepository; +import org.libreccm.core.CcmObject; import org.libreccm.l10n.GlobalizationHelper; +import org.librecms.CmsConstants; +import org.librecms.contentsection.Asset; import org.librecms.contentsection.AttachmentList; import org.librecms.contentsection.ContentItem; import org.librecms.contentsection.ContentItemVersion; +import org.librecms.contentsection.ItemAttachment; +import org.librecms.pages.PagesController; +import org.librecms.pages.PagesRouter; import org.librecms.pages.PagesService; import java.time.ZoneId; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.Date; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.inject.Named; -import javax.persistence.EntityManager; -import javax.ws.rs.NotFoundException; +import javax.transaction.Transactional; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; /** * Retrieves a categorized content item for the current category. To work, the @@ -53,204 +60,704 @@ import javax.ws.rs.NotFoundException; @Named("CmsPagesCategorizedItemModel") public class ContentItemModel { - private static final Logger LOGGER = LogManager.getLogger( - ContentItemModel.class - ); - + /** + * Provides access to the builders for asset models. + */ @Inject - private CategoryManager categoryManager; - + private AssetModelBuilders assetModelBuilders; + + /** + * Category repository used to retrieve the current category. + */ @Inject private CategoryRepository categoryRepository; + /** + * Category model used to get the curretn category. + */ @Inject private CategoryModel categoryModel; - @Inject - private EntityManager entityManager; - + /** + * Utility for globalization stuff. + */ @Inject private GlobalizationHelper globalizationHelper; + /** + * Provides some utility methods. + */ @Inject private PagesService pagesService; + /** + * All instances of implementations of the {@link ProcessesContentItem} + * interface. + */ + @Inject + private Instance contentItemProcessingModels; + + /** + * A {@link DateTimeFormatter} instance for converting dates and times to an + * ISO 8601 date/time string. + */ private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.systemDefault()); + /** + * The name of the item to be shown. + */ private String itemName; + /** + * The version of the item to be shown. + */ private ContentItemVersion itemVersion; - private Optional contentItem; + /** + * Data of the current content item, already prepared for retrieving the + * data from a MVC template. If no item with the {@link #itemName} provided + * by the {@link PagesController} is present in the category, the + * {@link Optional} will be empty. + */ + private Optional contentItem; + /** + * Gets the item name provided by the {@link PagesController}. + * + * @return The item name provided by the {@link PagesController}. + */ public String getItemName() { return itemName; } + /** + * Used by the {@link PagesController} to set the name of the requested + * content item. May be used by other controllers. + * + * @param itemName The name of the requested content item. + */ public void setItemName(final String itemName) { this.itemName = itemName; } + /** + * The requested version of the content item. + * + * @return The requested version of the content item. + */ public ContentItemVersion getItemVersion() { return itemVersion; } + /** + * Used by controllers, for example the {@link PagesController} to set the + * requested version of the content item. + * + * @param itemVersion The requested version of the content item. + */ public void setItemVersion(final ContentItemVersion itemVersion) { this.itemVersion = itemVersion; } /** - * Retrieves the current content item. Depending if {@link #itemName} has - * been initalized with a value either the index item of the current - * category or the item in the category identified by {@link #itemName} will - * be retrieved. The item is only received once per request. The method will - * retrieve the item on the first call and store the result in - * {@link #contentItem}. Subsequent calls will return the value of - * {@link #contentItem}. If {@link #itemName} is not {@code null} and there - * is no content item with the requested name in the category this method - * throws a {@link NotFoundException}. + * A convient getter for checking if a content item is available from a + * template. * - * @return The requested categorized item. If {@link #itemName} is - * {@code null}, and the current category has not index item, the - * method will return {@code null}. - * - * @throws NotFoundException If there is no item identified by the name in - * {@link #itemName}. + * @return {@code true} if an item is available, {@code false} if not. */ - public ContentItem getContentItem() { - return getOrRetrieveContentItem().orElse(null); + @Transactional(Transactional.TxType.REQUIRED) + public boolean isItemAvailable() { + init(); + return contentItem.isPresent(); } + /** + * Gets the {@code objectId)} (see {@link CcmObject#objectId} of the current + * item. + * + * @return The {@code objectId} of the current item if there is an item or + * {@code -1L} if there is no item. + */ + @Transactional(Transactional.TxType.REQUIRED) public long getObjectId() { - return getOrRetrieveContentItem() - .map(ContentItem::getObjectId) - .orElse(0L); + init(); + return contentItem + .map(ContentItemModelData::getObjectId) + .orElse(-1L); } + /** + * Get the UUID of the current item (see {@link CcmObject#uuid}. + * + * @return The UUID of the current item if there is an item, an empty string + * if there is not item. + */ + @Transactional(Transactional.TxType.REQUIRED) public String getUuid() { - return getOrRetrieveContentItem() - .map(ContentItem::getUuid) + init(); + return contentItem + .map(ContentItemModelData::getUuid) .orElse(""); } + /** + * Gets the display name of the current item (see + * {@link CcmObject#displayName}. + * + * @return The display name of the current item, or an empty string if there + * is not current item. + */ + @Transactional(Transactional.TxType.REQUIRED) public String getDisplayName() { - return getOrRetrieveContentItem() - .map(ContentItem::getDisplayName) + init(); + return contentItem + .map(ContentItemModelData::getDisplayName) .orElse(""); } + /** + * Gets the item UUID of the current item (see {@link ContentItem#itemUuid}. + * + * @return The item UUID of the current item, or an empty string if the is + * no current item. + */ + @Transactional(Transactional.TxType.REQUIRED) public String getItemUuid() { - return getOrRetrieveContentItem() - .map(ContentItem::getItemUuid) + init(); + return contentItem + .map(ContentItemModelData::getItemUuid) .orElse(""); } + /** + * Gets the name of the current item {@link ContentItem#name}) for the + * current language (see {@link GlobalizationHelper#getNegotiatedLocale()}). + * + * @return The name of the the current item for the current language, or an + * empty string if there is no item. + */ + @Transactional(Transactional.TxType.REQUIRED) public String getName() { - return getOrRetrieveContentItem() - .map(ContentItem::getName) - .map(globalizationHelper::getValueFromLocalizedString) + init(); + return contentItem + .map(ContentItemModelData::getName) .orElse(""); } + /** + * Gets the title of the current item (see {@link ContentItem#title} for the + * current language (see {@link GlobalizationHelper#getNegotiatedLocale()}). + * + * @return The title of the current item for the current language, or an + * empty string if there is no item. + */ + @Transactional(Transactional.TxType.REQUIRED) public String getTitle() { - return getOrRetrieveContentItem() - .map(ContentItem::getTitle) - .map(globalizationHelper::getValueFromLocalizedString) + init(); + return contentItem + .map(ContentItemModelData::getTitle) .orElse(""); } + /** + * Gets the description of the current item (see + * {@link ContentItem#description} for the current language (see + * {@link GlobalizationHelper#getNegotiatedLocale()}). + * + * @return The description of the current item for the current language, or + * an empty string if there is no item. + */ + @Transactional(Transactional.TxType.REQUIRED) public String getDescription() { - return getOrRetrieveContentItem() - .map(ContentItem::getDescription) - .map(globalizationHelper::getValueFromLocalizedString) + init(); + return contentItem + .map(ContentItemModelData::getDescription) .orElse(""); } + @Transactional(Transactional.TxType.REQUIRED) + public List getAttachmentLists() { + init(); + return contentItem + .map(ContentItemModelData::getAttachmentLists) + .orElse(Collections.emptyList()); + } + + @Transactional(Transactional.TxType.REQUIRED) + public List getMediaLists() { + init(); + return contentItem + .map(ContentItemModelData::getMediaLists) + .orElse(Collections.emptyList()); + } + + /** + * Gets the version of the current item (see {@link ContentItem#version}. + * + * @return The version of the current version as a string, or an empty + * string if there is not current item. + */ + @Transactional(Transactional.TxType.REQUIRED) public String getVersion() { - return getOrRetrieveContentItem() - .map(ContentItem::getVersion) - .map(ContentItemVersion::toString) + init(); + return contentItem + .map(ContentItemModelData::getVersion) .orElse(""); } + /** + * Gets the creation date/time of the current item (see + * {@link ContentItem#creationDate}. + * + * @return The creation date/time of the current item as ISO 8601 date/time + * string, or an empty string if there is no item. + */ + @Transactional(Transactional.TxType.REQUIRED) public String getCreationDate() { - return getOrRetrieveContentItem() - .map(ContentItem::getCreationDate) - .map(Date::toInstant) - .map(instant -> instant.atZone(ZoneId.systemDefault())) - .map(ZonedDateTime::toLocalDateTime) - .map(dateTimeFormatter::format) + init(); + return contentItem + .map(ContentItemModelData::getCreationDate) .orElse(""); } + /** + * Gets the date/time of the last modification of the current item (see + * {@link ContentItem#lastModified}. + * + * @return The date/time of the modification of the current item as ISO 8601 + * date/time string, or an empty string of there is not current + * item. + */ + @Transactional(Transactional.TxType.REQUIRED) public String getLastModified() { - return getOrRetrieveContentItem() - .map(ContentItem::getLastModified) - .map(Date::toInstant) - .map(instant -> instant.atZone(ZoneId.systemDefault())) - .map(ZonedDateTime::toLocalDateTime) - .map(dateTimeFormatter::format) + init(); + return contentItem + .map(ContentItemModelData::getLastModified) .orElse(""); } + /** + * Gets the user name of the user that created the item. + * + * @return The user name of the user that created the item. + */ + @Transactional(Transactional.TxType.REQUIRED) public String getCreationUser() { - return getOrRetrieveContentItem() - .map(ContentItem::getCreationUserName) + init(); + return contentItem + .map(ContentItemModelData::getCreationUser) .orElse(""); } + /** + * Gets the user name of the user that did the last modifications on the + * item. + * + * @return The user name of the user that did the last modifications on the + * item. + */ + @Transactional(Transactional.TxType.REQUIRED) public String getLastModifyingUserName() { - return getOrRetrieveContentItem() - .map(ContentItem::getLastModifyingUserName) + init(); + return contentItem + .map(ContentItemModelData::getLastModifyingUserName) .orElse(""); } - public List getAttachments() { - return getContentItem().getAttachments(); - } - - private Optional getOrRetrieveContentItem() { - if (contentItem == null) { - retrieveContentItem(); + /** + * Initialize the this model and all models implementing + * {@link ProcessesContentItem#}. + * + * If this model has not been initalizied already this method retrieves the + * current content item using {@link #retrieveContentItem()}, and + * initializes {@link #contentItem} with an instance of + * {@link ContentItemModelData} build from the item using + * {@link #buildModelData(org.librecms.contentsection.ContentItem)}. + * + * After that, the method will invoke the implementation of + * {@link ProcessesContentItem#init(org.librecms.contentsection.ContentItem)} + * for all available implemetentations of {@link ProcessesContentItem} (see + * {@link #contentItemProcessingModels}). + * + * Models implementing SHOULD call this method in their getters before + * returning a value. There is not need to wrap the invocation of this + * method in an {@code if} statement, the method will only run its logic if + * the item has not been initialized. + * + * If there is no current item, the method will do nothing. + */ + @Transactional(Transactional.TxType.REQUIRED) + public void init() { + if (contentItem != null) { + return; + } + + final Optional item = retrieveContentItem(); + contentItem = item.map(this::buildModelData); + + final Iterator iterator + = contentItemProcessingModels.iterator(); + while (iterator.hasNext()) { + final ProcessesContentItem model = iterator.next(); + model.init(item.orElse(null)); } - return contentItem; } - private void retrieveContentItem() { - if (itemName == null) { - contentItem = pagesService.findIndexItem( + /** + * Helper method for retrieving the current content item. If + * {@link #itemName} is {@code null} or {@code index} the method will return + * if the index item of the current category. If the category has not index + * item, an empty {@link Optional} is returned. + * + * If {@link #itemName} is not {@code null} or {@code index}, the + * method will try to find a content item associated to the category where + * {@link ContentItem#name} matches {@link #itemName}. + * + * @return The current content item if any, or an empyty {@link Optional}. + * + * @see PagesService#findIndexItem(org.libreccm.categorization.Category, + * org.librecms.contentsection.ContentItemVersion) + * @see + * PagesService#findCategorizedItem(org.libreccm.categorization.Category, + * java.lang.String, org.librecms.contentsection.ContentItemVersion) + */ + @Transactional(Transactional.TxType.REQUIRED) + private Optional retrieveContentItem() { + final Optional item; + if (itemName == null || "index".equals(itemName)) { + item = pagesService.findIndexItem( categoryRepository - .findById(categoryModel.getCategory().getCategoryId()) - .orElseThrow( - () -> new RuntimeException( - String.format( - "The category with the ID %d is set as current " - + "category, but not category with that ID " - + "can be found in the database.", - categoryModel.getCategory().getCategoryId() + .findById(categoryModel.getCategory().getCategoryId()) + .orElseThrow( + () -> new RuntimeException( + String.format( + "The category with the ID %d is set as current " + + "category, but not category with that ID " + + "can be found in the database.", + categoryModel.getCategory().getCategoryId() + ) ) - ) - ), + ), itemVersion ); } else { - contentItem = pagesService.findCategorizedItem( + item = pagesService.findCategorizedItem( categoryRepository - .findById(categoryModel.getCategory().getCategoryId()) - .orElseThrow( - () -> new RuntimeException( - String.format( - "The category with the ID %d is set as current " - + "category, but not category with that ID " - + "can be found in the database.", - categoryModel.getCategory().getCategoryId() + .findById(categoryModel.getCategory().getCategoryId()) + .orElseThrow( + () -> new RuntimeException( + String.format( + "The category with the ID %d is set as current " + + "category, but not category with that ID " + + "can be found in the database.", + categoryModel.getCategory().getCategoryId() + ) ) - ) - ), - itemName, + ), + itemName, itemVersion ); } + + return item; } + + /** + * Helper method for building an instance {@link ContentItemModelData} for a + * {@link ContentItem}. + * + * @param item The source content item. + * + * @return An instance of {@link ContentItemModelData} for the provided + * content item. + */ + private ContentItemModelData buildModelData(final ContentItem item) { + final ContentItemModelData data = new ContentItemModelData(); + data.setObjectId(item.getObjectId()); + data.setUuid(item.getUuid()); + data.setDisplayName(item.getDisplayName()); + data.setItemUuid(item.getItemUuid()); + data.setName( + globalizationHelper.getValueFromLocalizedString( + item.getName() + ) + ); + data.setTitle( + globalizationHelper.getValueFromLocalizedString( + item.getTitle() + ) + ); + data.setDescription( + globalizationHelper.getValueFromLocalizedString( + item.getDescription() + ) + ); + data.setVersion(item.getVersion().toString()); + data.setCreationDate( + dateTimeFormatter.format( + item + .getCreationDate() + .toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime() + ) + ); + data.setLastModified( + dateTimeFormatter.format( + item + .getLastModified() + .toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime() + ) + ); + data.setCreationUser(item.getCreationUserName()); + data.setLastModifyingUserName(item.getLastModifyingUserName()); + + data.setAttachmentLists( + item + .getAttachments() + .stream() + .filter( + list -> !list.getName().startsWith( + CmsConstants.MEDIA_LIST_PREFIX + ) + ) + .map(this::buildAttachmentListModel) + .sorted() + .collect(Collectors.toList()) + ); + data.setMediaLists( + item + .getAttachments() + .stream() + .filter( + list -> list.getName().startsWith( + CmsConstants.MEDIA_LIST_PREFIX + ) + ) + .map(this::buildAttachmentListModel) + .sorted() + .collect(Collectors.toList()) + ); + + return data; + } + + private AttachmentListModel buildAttachmentListModel( + final AttachmentList attachmentList + ) { + final AttachmentListModel model = new AttachmentListModel(); + model.setAttachments( + attachmentList + .getAttachments() + .stream() + .map(this::buildAttachmentModel) + .sorted() + .collect(Collectors.toList()) + ); + model.setDescription( + globalizationHelper.getValueFromLocalizedString( + attachmentList.getDescription() + ) + ); + model.setListId(attachmentList.getListId()); + model.setListOrder(attachmentList.getListOrder()); + model.setName(attachmentList.getName()); + model.setTitle( + globalizationHelper.getValueFromLocalizedString( + attachmentList.getTitle() + ) + ); + model.setUuid(attachmentList.getUuid()); + + return model; + } + + private AttachmentModel buildAttachmentModel( + final ItemAttachment attachment + ) { + final AttachmentModel model = new AttachmentModel(); + final Asset asset = attachment.getAsset(); + final AssetModelBuilder assetModelBuilder + = assetModelBuilders + .getModelBuilderFor(asset.getClass()) + .orElseThrow( + () -> new WebApplicationException( + Response + .status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + String.format( + "Unknown asset type %s.", + asset.getClass().getName() + ) + ) + .build() + ) + ); + model.setAsset( + assetModelBuilder.buildAssetModel(attachment.getAsset()) + ); + model.setAttachmentId(attachment.getAttachmentId()); + model.setSortKey(attachment.getSortKey()); + model.setUuid(attachment.getUuid()); + + return model; + } + + /** + * Encapsulates the data of content item provided by this model. To avoid to + * have to many fields in this model class we use an internal class to + * encapsulate the data. The getters of the model class return the values + * from an instance of this class. + */ + private class ContentItemModelData { + + private long objectId; + + private String uuid; + + private String displayName; + + private String itemUuid; + + private String name; + + private String title; + + private String description; + + private String version; + + private String creationDate; + + private String lastModified; + + private String creationUser; + + private String lastModifyingUserName; + + private List attachmentLists; + + private List mediaLists; + + public long getObjectId() { + return objectId; + } + + public void setObjectId(final long objectId) { + this.objectId = objectId; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(final String uuid) { + this.uuid = uuid; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(final String displayName) { + this.displayName = displayName; + } + + public String getItemUuid() { + return itemUuid; + } + + public void setItemUuid(final String itemUuid) { + this.itemUuid = itemUuid; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getTitle() { + return title; + } + + public void setTitle(final String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public String getVersion() { + return version; + } + + public void setVersion(final String version) { + this.version = version; + } + + public String getCreationDate() { + return creationDate; + } + + public void setCreationDate(final String creationDate) { + this.creationDate = creationDate; + } + + public String getLastModified() { + return lastModified; + } + + public void setLastModified(final String lastModified) { + this.lastModified = lastModified; + } + + public String getCreationUser() { + return creationUser; + } + + public void setCreationUser(final String creationUser) { + this.creationUser = creationUser; + } + + public String getLastModifyingUserName() { + return lastModifyingUserName; + } + + public void setLastModifyingUserName( + final String lastModifyingUserName + ) { + this.lastModifyingUserName = lastModifyingUserName; + } + + public List getAttachmentLists() { + return Collections.unmodifiableList(attachmentLists); + } + + public void setAttachmentLists( + final List attachmentLists + ) { + this.attachmentLists = new ArrayList<>(attachmentLists); + } + + public List getMediaLists() { + return Collections.unmodifiableList(mediaLists); + } + + public void setMediaLists(final List mediaLists) { + this.mediaLists = new ArrayList<>(mediaLists); + } + + } + } diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemTypeModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemTypeModel.java index e7d6914aa..16da29dde 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemTypeModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemTypeModel.java @@ -18,16 +18,16 @@ */ package org.librecms.pages.models; -import org.libreccm.l10n.GlobalizationHelper; -import org.librecms.contentsection.ContentItem; -import org.librecms.contentsection.ContentType; - import java.util.Optional; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; -import javax.swing.text.AbstractDocument.Content; +import javax.transaction.Transactional; + +import org.libreccm.l10n.GlobalizationHelper; +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentType; /** * MVC model for retrieving information about the content type of the current @@ -43,7 +43,7 @@ import javax.swing.text.AbstractDocument.Content; */ @RequestScoped @Named("CmsPagesContentItemTypeModel") -public class ContentItemTypeModel { +public class ContentItemTypeModel implements ProcessesContentItem { @Inject private ContentItemModel contentItemModel; @@ -51,55 +51,150 @@ public class ContentItemTypeModel { @Inject private GlobalizationHelper globalizationHelper; - private Optional contentType; - - public ContentType getContentType() { - return getOrRetrieveContentType().orElse(null); - } + private Optional contentType; public long getContentTypeId() { - return getOrRetrieveContentType() - .map(ContentType::getObjectId) + contentItemModel.init(); + + return contentType + .map(ContentItemTypeModelData::getTypeId) .orElse(0L); } public String getUuid() { - return getOrRetrieveContentType() - .map(ContentType::getUuid) + contentItemModel.init(); + + return contentType + .map(ContentItemTypeModelData::getUuid) + .orElse(""); + } + + public String getItemClass() { + contentItemModel.init(); + + return contentType + .map(ContentItemTypeModelData::getItemClass) .orElse(""); } public String getDisplayName() { - return getOrRetrieveContentType() - .map(ContentType::getDisplayName) + contentItemModel.init(); + + return contentType + .map(ContentItemTypeModelData::getDisplayName) .orElse(""); } public String getLabel() { - return getOrRetrieveContentType() - .map(ContentType::getLabel) - .map(globalizationHelper::getValueFromLocalizedString) + contentItemModel.init(); + + return contentType + .map(ContentItemTypeModelData::getLabel) .orElse(""); } public String getDescription() { - return getOrRetrieveContentType() - .map(ContentType::getDescription) - .map(globalizationHelper::getValueFromLocalizedString) + contentItemModel.init(); + + return contentType + .map(ContentItemTypeModelData::getDescription) .orElse(""); } - private Optional getOrRetrieveContentType() { - if (contentType == null) { - retrieveContentType(); + @Transactional(Transactional.TxType.REQUIRED) + public void init(final ContentItem contentItem) { + if (contentType != null) { + return; } - return contentType; + + contentType = Optional + .ofNullable(contentItem) + .map(ContentItem::getContentType) + .map(this::buildModelData); } - private void retrieveContentType() { - contentType = Optional - .ofNullable(contentItemModel.getContentItem()) - .map(ContentItem::getContentType); + @Transactional(Transactional.TxType.REQUIRED) + private ContentItemTypeModelData buildModelData(final ContentType type) { + final ContentItemTypeModelData data = new ContentItemTypeModelData(); + data.setDescription( + globalizationHelper.getValueFromLocalizedString( + type.getDescription() + ) + ); + data.setDisplayName(type.getDisplayName()); + data.setItemClass(type.getContentItemClass()); + data.setLabel( + globalizationHelper.getValueFromLocalizedString( + type.getLabel() + ) + ); + data.setTypeId(type.getObjectId()); + data.setUuid(type.getUuid()); + + return data; + } + + private class ContentItemTypeModelData { + + private long typeId; + + private String uuid; + + private String itemClass; + + private String displayName; + + private String label; + + private String description; + + public long getTypeId() { + return typeId; + } + + public void setTypeId(final long typeId) { + this.typeId = typeId; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(final String uuid) { + this.uuid = uuid; + } + + public String getItemClass() { + return itemClass; + } + + public void setItemClass(final String itemClass) { + this.itemClass = itemClass; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(final String displayName) { + this.displayName = displayName; + } + + public String getLabel() { + return label; + } + + public void setLabel(final String label) { + this.label = label; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } } diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/EventListItemModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/EventListItemModel.java index fa2f31f5b..5e5b2dff7 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/EventListItemModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/EventListItemModel.java @@ -21,6 +21,8 @@ package org.librecms.pages.models; import org.librecms.contenttypes.Event; import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; /** * @@ -43,6 +45,13 @@ public class EventListItemModel extends AbstractContentItemListItemModel { return startDate; } + public String getStartDate(final String pattern) { + return DateTimeFormatter + .ofPattern(pattern) + .withZone(ZoneId.systemDefault()) + .format(startDate); + } + public void setStartDate(final LocalDateTime startDate) { this.startDate = startDate; } @@ -50,6 +59,13 @@ public class EventListItemModel extends AbstractContentItemListItemModel { public LocalDateTime getEndDate() { return endDate; } + + public String getEndDate(final String pattern) { + return DateTimeFormatter + .ofPattern(pattern) + .withZone(ZoneId.systemDefault()) + .format(endDate); + } public void setEndDate(final LocalDateTime endDate) { this.endDate = endDate; diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/EventListItemModelBuilder.java b/ccm-cms/src/main/java/org/librecms/pages/models/EventListItemModelBuilder.java index f1e1636ef..3b3e2c7de 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/EventListItemModelBuilder.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/EventListItemModelBuilder.java @@ -22,6 +22,7 @@ import org.libreccm.l10n.GlobalizationHelper; import org.librecms.contenttypes.Event; import java.time.LocalDateTime; +import java.time.ZoneId; import javax.inject.Inject; @@ -50,13 +51,21 @@ public class EventListItemModelBuilder final Event event, final EventListItemModel model ) { super.addProperties(event, model); - model.setEndDate(LocalDateTime.from(event.getEndDate().toInstant())); + model.setEndDate( + LocalDateTime.from( + event.getEndDate().toInstant().atZone(ZoneId.systemDefault()) + ) + ); model.setLocation( globalizationHelper.getValueFromLocalizedString( event.getLocation() ) ); - model.setStartDate(LocalDateTime.from(event.getStartDate().toInstant())); + model.setStartDate( + LocalDateTime.from( + event.getStartDate().toInstant().atZone(ZoneId.systemDefault()) + ) + ); } } diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/EventModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/EventModel.java index 0c7f2cde6..ad9bc92d7 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/EventModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/EventModel.java @@ -18,18 +18,19 @@ */ package org.librecms.pages.models; -import org.libreccm.l10n.GlobalizationHelper; -import org.librecms.contentsection.ContentItem; -import org.librecms.contenttypes.Event; - +import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.Optional; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Response; +import javax.transaction.Transactional; + +import org.libreccm.l10n.GlobalizationHelper; +import org.librecms.contentsection.ContentItem; +import org.librecms.contenttypes.Event; /** * @@ -37,7 +38,7 @@ import javax.ws.rs.core.Response; */ @RequestScoped @Named("CmsPagesEventModel") -public class EventModel { +public class EventModel implements ProcessesContentItem { @Inject private ContentItemModel contentItemModel; @@ -45,55 +46,202 @@ public class EventModel { @Inject private GlobalizationHelper globalizationHelper; + private boolean initialized; + + private String title; + + private String text; + + private LocalDateTime startDateTime; + + private LocalDateTime endDateTime; + + private String eventDate; + + private String location; + + private String eventType; + + public EventModel() { + initialized = false; + } + + @Transactional(Transactional.TxType.REQUIRED) public String getTitle() { - return globalizationHelper.getValueFromLocalizedString( - getEvent().getTitle() - ); + contentItemModel.init(); + + return title; } + @Transactional(Transactional.TxType.REQUIRED) public String getText() { - return globalizationHelper.getValueFromLocalizedString( - getEvent().getText() - ); + contentItemModel.init(); + + return text; } - public String getStartDateTime() { - return DateTimeFormatter.ISO_DATE_TIME - .withZone(ZoneId.systemDefault()) - .format(getEvent().getStartDate().toInstant()); + @Transactional(Transactional.TxType.REQUIRED) + public LocalDateTime getStartDateTime() { + contentItemModel.init(); + + return startDateTime; } - public String getEndDateTime() { - return DateTimeFormatter.ISO_DATE_TIME - .withZone(ZoneId.systemDefault()) - .format(getEvent().getEndDate().toInstant()); + /** + * Returns the start date of the event represented by this model formatted + * using the provided pattern. The pattern MUST be a valid pattern for + * {@link DateTimeFormatter#ofPattern(java.lang.String) }. + * + * @param pattern The pattern to use for formatting the release date. + * + * @return The formatted release date. + */ + public String getStartDateTime(final String pattern) { + contentItemModel.init(); + + return Optional + .ofNullable(startDateTime) + .map( + dateTime -> DateTimeFormatter + .ofPattern( + pattern, + globalizationHelper.getNegotiatedLocale() + ) + .withZone(ZoneId.systemDefault()) + .format(dateTime) + ) + .orElse(""); } + public String getStartDateTimeAsString() { + contentItemModel.init(); + + return Optional + .ofNullable(endDateTime) + .map( + dateTime -> DateTimeFormatter.ISO_DATE_TIME + .withZone(ZoneId.systemDefault()) + .format(dateTime) + ) + .orElse(""); + } + + @Transactional(Transactional.TxType.REQUIRED) + public LocalDateTime getEndDateTime() { + contentItemModel.init(); + + return endDateTime; + } + + /** + * Returns the end date of the event represented by this model formatted + * using the provided pattern. The pattern MUST be a valid pattern for + * {@link DateTimeFormatter#ofPattern(java.lang.String) }. + * + * @param pattern The pattern to use for formatting the release date. + * + * @return The formatted release date. + */ + public String getEndDateTime(final String pattern) { + contentItemModel.init(); + + return Optional + .ofNullable(endDateTime) + .map( + dateTime -> DateTimeFormatter + .ofPattern( + pattern, + globalizationHelper.getNegotiatedLocale() + ) + .withZone(ZoneId.systemDefault()) + .format(dateTime) + ) + .orElse(""); + } + + public String getEndDateTimeAsString() { + contentItemModel.init(); + + return Optional + .ofNullable(endDateTime) + .map( + dateTime -> DateTimeFormatter.ISO_DATE_TIME + .withZone(ZoneId.systemDefault()) + .format(dateTime) + ) + .orElse(""); + } + + @Transactional(Transactional.TxType.REQUIRED) public String getEventDate() { - return globalizationHelper.getValueFromLocalizedString( - getEvent().getEventDate() - ); + contentItemModel.init(); + + return eventDate; } + @Transactional(Transactional.TxType.REQUIRED) public String getLocation() { - return globalizationHelper.getValueFromLocalizedString( - getEvent().getLocation() - ); + contentItemModel.init(); + + return location; } - protected Event getEvent() { - final ContentItem contentItem = contentItemModel.getContentItem(); - if (contentItem instanceof Event) { - return (Event) contentItem; - } else { - throw new WebApplicationException( - "Current content item is not an event", - Response - .status(Response.Status.INTERNAL_SERVER_ERROR) - .entity("Current content item is not an event.") - .build() - ); + @Transactional(Transactional.TxType.REQUIRED) + public String getEventType() { + contentItemModel.init(); + + return eventType; + } + + @Override + @Transactional(Transactional.TxType.REQUIRED) + public void init(final ContentItem contentItem) { + if (initialized) { + return; } + + if (contentItem instanceof Event) { + final Event event = (Event) contentItem; + + title = Optional + .ofNullable(event.getTitle()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(""); + text = Optional + .ofNullable(event.getText()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(""); + startDateTime = Optional + .ofNullable(event.getStartDate()) + .map( + startDate -> LocalDateTime.from( + startDate.toInstant().atZone(ZoneId.systemDefault()) + ) + ) + .orElse(null); + endDateTime = Optional + .ofNullable(event.getEndDate()) + .map( + endDate -> LocalDateTime.from( + endDate.toInstant().atZone(ZoneId.systemDefault()) + ) + ) + .orElse(null); + eventDate = Optional + .ofNullable(event.getEventDate()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(""); + location = Optional + .ofNullable(event.getLocation()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(""); + eventType = Optional + .ofNullable(event.getEventType()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(""); + } + + initialized = true; } } diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/ImageModelBuilder.java b/ccm-cms/src/main/java/org/librecms/pages/models/ImageModelBuilder.java index 9da299934..f60006c01 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/ImageModelBuilder.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/ImageModelBuilder.java @@ -50,9 +50,12 @@ public class ImageModelBuilder protected void addProperties(final Image image, final ImageModel model) { super.addProperties(image, model); model.setHeight(image.getHeight()); - model.setLegalMetadata( - legalMetadataModelBuilder.buildAssetModel(image.getLegalMetadata()) - ); + if (image.getLegalMetadata() != null) { + model.setLegalMetadata( + legalMetadataModelBuilder.buildAssetModel( + image.getLegalMetadata()) + ); + } model.setWidth(image.getWidth()); } diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/ItemListModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/ItemListModel.java index 9850fb9cb..84f8bb9c4 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/ItemListModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/ItemListModel.java @@ -18,26 +18,6 @@ */ package org.librecms.pages.models; -import com.arsdigita.kernel.KernelConfig; - -import org.libreccm.categorization.Categorization; -import org.libreccm.categorization.Category; -import org.libreccm.categorization.CategoryRepository; -import org.libreccm.configuration.ConfigurationManager; -import org.libreccm.core.UnexpectedErrorException; -import org.libreccm.l10n.GlobalizationHelper; -import org.libreccm.security.Permission; -import org.libreccm.security.PermissionChecker; -import org.libreccm.security.Role; -import org.libreccm.security.RoleManager; -import org.libreccm.security.Shiro; -import org.libreccm.security.User; -import org.libreccm.security.UserRepository; -import org.librecms.contentsection.ContentItem; -import org.librecms.contentsection.ContentItemL10NManager; -import org.librecms.contentsection.ContentItemVersion; -import org.librecms.contentsection.privileges.ItemPrivileges; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -59,6 +39,29 @@ import javax.transaction.Transactional; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; +import org.libreccm.categorization.Categorization; +import org.libreccm.categorization.Category; +import org.libreccm.categorization.CategoryRepository; +import org.libreccm.configuration.ConfigurationManager; +import org.libreccm.core.UnexpectedErrorException; +import org.libreccm.l10n.GlobalizationHelper; +import org.libreccm.security.Permission; +import org.libreccm.security.PermissionChecker; +import org.libreccm.security.Role; +import org.libreccm.security.RoleManager; +import org.libreccm.security.Shiro; +import org.libreccm.security.User; +import org.libreccm.security.UserRepository; +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentItemL10NManager; +import org.librecms.contentsection.ContentItemVersion; +import org.librecms.contentsection.privileges.ItemPrivileges; + +import com.arsdigita.kernel.KernelConfig; + +import java.util.Arrays; +import java.util.stream.Stream; + /** * * @author Jens Pelzetter @@ -67,6 +70,26 @@ import javax.ws.rs.core.Response; @Named("CmsPagesItemListModel") public class ItemListModel { + private static final String ITEM_LIST_SETTINGS_PREFIX = "itemlist"; + + private static final String LIMIT_TO_TYPE_SETTING = "limitToType"; + + private static final String LIST_ORDER_SETTING = "listOrder"; + + private static final String PAGE_SIZE_SETTING = "pageSize"; + + private static final boolean DESCENDING_DEFAULT = false; + + private static final String LIMIT_TO_TYPE_DEFAULT = ContentItem.class + .getName(); + + private static final List LIST_ORDER_DEFAULT = List + .of("displayName"); + + private static final int PAGE_SIZE_DEFAULT = 20; + + private static final String PAGE_PARAM_NAME = "page"; + @Inject private CategoryModel categoryModel; @@ -100,71 +123,73 @@ public class ItemListModel { @Inject private Shiro shiro; + @Inject + private PagePropertiesModel pagePropertiesModel; + @Inject private PageUrlModel pageUrlModel; - private boolean descending; - - private String limitToType; - - private List listOrder; - - private int pageSize; - - private List itemList; - - public ItemListModel() { - descending = false; - limitToType = ContentItem.class.getName(); - listOrder = List.of("displayName"); - pageSize = 20; + @Transactional(Transactional.TxType.REQUIRED) + public int getListSize() { + return getListSize(""); } - public boolean isDescending() { - return descending; - } - - public void setDescending(final boolean descending) { - this.descending = descending; - } - - public String getLimitToType() { - return limitToType; - } - - public void setLimitToType(final String limitToType) { - this.limitToType = limitToType; - } - - public List getListOrder() { - return Collections.unmodifiableList(listOrder); - } - - public void setListOrder(final List listOrder) { - this.listOrder = new ArrayList<>(listOrder); + @Transactional(Transactional.TxType.REQUIRED) + public int getListSize(final String listName) { + return getItems(listName).size(); } public int getPageSize() { - return pageSize; + return getPageSize(""); } - public void setPageSize(final int pageSize) { - this.pageSize = pageSize; + public int getPageSize(final String listName) { + return getPageSizeSetting(listName); } - public int getFirstItem() { - return getOffset(pageSize); + public int getOffset() { + return getOffset(""); } - public int getListSize() { - return getItems().size(); + public int getOffset(final String listName) { + return getOffset(listName, getPageSize(listName)); + } + + public int getPage() { + return getPage(""); + } + + public int getPage(final String listName) { + final String pageParamName = Stream + .of(listName, PAGE_PARAM_NAME) + .filter(token -> token != null && !token.isBlank()) + .collect( + Collectors.joining(".") + ); + + if (pageUrlModel.getQueryParameters().containsKey(pageParamName)) { + return parsePageParam(pageParamName); + } else if (pageUrlModel.getQueryParameters() + .containsKey(PAGE_PARAM_NAME)) { + return parsePageParam(pageParamName); + } else { + return 0; + } } @Transactional(Transactional.TxType.REQUIRED) public List getItems() { -// if (itemList == null) { + return getItems(""); + } + + @Transactional(Transactional.TxType.REQUIRED) + public List getItems( + final String listName + ) { + final List items + = Collections.unmodifiableList( buildList( - buildLimitToType(limitToType), + buildLimitToType(getLimitToTypeSetting(listName)), collectCategories( categoryRepository .findById(categoryModel.getCategory().getCategoryId()) @@ -179,19 +204,21 @@ public class ItemListModel { ) ) ), - listOrder, - pageSize - ); -// } - - return Collections.unmodifiableList(itemList); + getListOrderSetting(listName), + getPageSizeSetting(listName), + getOffset(listName, getPageSizeSetting(listName)) + ) + ); + + return items; } - private void buildList( + private List buildList( final Class limitToType, final List categories, final List listOrder, - final int pageSize + final int pageSize, + final int offset ) { final CriteriaBuilder criteriaBuilder = entityManager .getCriteriaBuilder(); @@ -209,7 +236,7 @@ public class ItemListModel { .where( criteriaBuilder.and( catJoin.get("category").in(categories), - criteriaBuilder.equal(catJoin.get("indexObject"), false), + criteriaBuilder.isFalse(catJoin.get("indexObject")), criteriaBuilder.isNull(catJoin.get("type")), criteriaBuilder.equal( from.get("version"), ContentItemVersion.LIVE @@ -227,7 +254,7 @@ public class ItemListModel { .collect(Collectors.toList()) ); - itemList = entityManager + return entityManager .createQuery(criteriaQuery) .getResultList() .stream() @@ -236,7 +263,7 @@ public class ItemListModel { item, globalizationHelper.getNegotiatedLocale() ) ) - .skip(getOffset(pageSize)) + .skip(offset) .limit(pageSize) .map(this::buildListItemModel) .collect(Collectors.toList()); @@ -386,21 +413,92 @@ public class ItemListModel { } } - private int getOffset(final int pageSize) { - if (pageUrlModel.getQueryParameters().containsKey("page")) { - final String value = pageUrlModel.getQueryParameters().get("page"); - if (value.matches("\\d*")) { - final int page = Integer.valueOf(value); + private int getOffset(final String listName, final int pageSize) { + return getPage(listName) * pageSize; + } - return page * pageSize; - } else { - return 0; - } + private int parsePageParam(final String pageParamName) { + final String value = pageUrlModel + .getQueryParameters() + .get(pageParamName); + if (value.matches("\\d+")) { + return Integer.parseUnsignedInt(value); } else { return 0; } } + private String getLimitToTypeSetting(final String listName) { + return Optional + .ofNullable( + pagePropertiesModel + .getProperties() + .get( + buildSettingKey( + listName, + LIMIT_TO_TYPE_SETTING + ) + ) + ) + .orElse(LIMIT_TO_TYPE_DEFAULT); + } + + private List getListOrderSetting(final String listName) { + return Optional + .ofNullable( + pagePropertiesModel + .getProperties() + .get( + buildSettingKey( + listName, + LIST_ORDER_SETTING + ) + ) + ) + .map(value -> value.split(",")) + .map( + values -> Arrays + .asList(values) + .stream() + .map(String::strip) + .collect(Collectors.toList()) + ) + .orElse(LIST_ORDER_DEFAULT); + } + + private int getPageSizeSetting(final String listName) { + return Optional + .ofNullable( + pagePropertiesModel + .getProperties() + .get( + buildSettingKey( + listName, + PAGE_SIZE_SETTING + ) + ) + ) + .filter(value -> value.matches("\\d+")) + .map(value -> Integer.parseUnsignedInt(value)) + .orElse(PAGE_SIZE_DEFAULT); + } + + private String buildSettingKey( + final String listName, + final String settingKey + ) { + return Stream + .of( + ITEM_LIST_SETTINGS_PREFIX, + listName, + settingKey + ) + .filter(token -> token != null && !token.isBlank()) + .collect( + Collectors.joining(".") + ); + } + private AbstractContentItemListItemModel buildListItemModel( final ContentItem contentItem ) { diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/MultiPartArticleModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/MultiPartArticleModel.java index 5d0ad91fe..2d5e5f683 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/MultiPartArticleModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/MultiPartArticleModel.java @@ -18,27 +18,33 @@ */ package org.librecms.pages.models; -import org.libreccm.l10n.GlobalizationHelper; -import org.librecms.contentsection.ContentItem; -import org.librecms.contenttypes.MultiPartArticle; -import org.librecms.contenttypes.MultiPartArticleSection; - +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; +import javax.transaction.Transactional; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; +import org.libreccm.l10n.GlobalizationHelper; +import org.librecms.contentsection.ContentItem; +import org.librecms.contenttypes.MultiPartArticle; +import org.librecms.contenttypes.MultiPartArticleSection; + /** * * @author Jens Pelzetter */ @RequestScoped @Named("CmsPagesMultiPartArticleModel") -public class MultiPartArticleModel { +public class MultiPartArticleModel implements ProcessesContentItem { + + private static final String MPA_SECTION_QUERY_PARAM = "mpa_section"; @Inject private ContentItemModel contentItemModel; @@ -46,106 +52,191 @@ public class MultiPartArticleModel { @Inject private GlobalizationHelper globalizationHelper; + private boolean initialized; + @Inject private PageUrlModel pageUrlModel; + private String title; + + private String summary; + + private List sectionTitles; + + private List sectionLinks; + + private String prevSectionLink; + + private String nextSectionLink; + + private int currentSection; + + private String currentSectionTitle; + + private String currentSectionText; + + private List sections; + + public MultiPartArticleModel() { + initialized = false; + } + + @Transactional(Transactional.TxType.REQUIRED) public String getTitle() { - return globalizationHelper.getValueFromLocalizedString( - getMultiPartArticle().getTitle() - ); + contentItemModel.init(); + + return title; } + @Transactional(Transactional.TxType.REQUIRED) public String getSummary() { - return globalizationHelper.getValueFromLocalizedString( - getMultiPartArticle().getSummary() - ); + contentItemModel.init(); + + return summary; } + @Transactional(Transactional.TxType.REQUIRED) public List getSectionTitles() { - return getMultiPartArticle() - .getSections() - .stream() - .map(MultiPartArticleSection::getTitle) - .map(globalizationHelper::getValueFromLocalizedString) - .collect(Collectors.toList()); + contentItemModel.init(); + + return Collections.unmodifiableList(sectionTitles); } + @Transactional(Transactional.TxType.REQUIRED) + public List getSectionLinks() { + contentItemModel.init(); + + return Collections.unmodifiableList(sectionLinks); + } + + @Transactional(Transactional.TxType.REQUIRED) + public String getPrevSectionLink() { + contentItemModel.init(); + + return prevSectionLink; + } + + @Transactional(Transactional.TxType.REQUIRED) + public String getNextSectionLink() { + contentItemModel.init(); + + return nextSectionLink; + } + + @Transactional(Transactional.TxType.REQUIRED) + public int getCurrentSection() { + contentItemModel.init(); + + return currentSection; + } + + @Transactional(Transactional.TxType.REQUIRED) public String getCurrentSectionTitle() { - final int currentSection = readCurrentSection(); - if (getMultiPartArticle().getSections().size() > currentSection) { - throw new WebApplicationException( - Response - .status(Response.Status.NOT_FOUND) - .entity( - String.format( - "MultiPartArticle %s has not section %d.", - getMultiPartArticle().getDisplayName(), - currentSection - ) - ) - .build() - ); - } + contentItemModel.init(); - return globalizationHelper.getValueFromLocalizedString( - getMultiPartArticle().getSections().get(currentSection).getTitle() - ); + return currentSectionTitle; } + @Transactional(Transactional.TxType.REQUIRED) public String getCurrentSectionText() { - final int currentSection = readCurrentSection(); - if (getMultiPartArticle().getSections().size() > currentSection) { - throw new WebApplicationException( - Response - .status(Response.Status.NOT_FOUND) - .entity( - String.format( - "MultiPartArticle %s has not section %d.", - getMultiPartArticle().getDisplayName(), - currentSection - ) - ) - .build() - ); - } + contentItemModel.init(); - return globalizationHelper.getValueFromLocalizedString( - getMultiPartArticle().getSections().get(currentSection).getText() - ); + return currentSectionText; } + @Transactional(Transactional.TxType.REQUIRED) public List getSections() { - return getMultiPartArticle() - .getSections() - .stream() - .map(this::buildSectionModel) - .collect(Collectors.toList()); + contentItemModel.init(); + + return Collections.unmodifiableList(sections); } - protected MultiPartArticle getMultiPartArticle() { - final ContentItem contentItem = contentItemModel.getContentItem(); - if (contentItem instanceof MultiPartArticle) { - return (MultiPartArticle) contentItem; - } else { - throw new WebApplicationException( - "Current content item is not an MultiPartArticle", - Response - .status(Response.Status.INTERNAL_SERVER_ERROR) - .entity("Current content item is not an MultiPartArticle.") - .build() - ); + @Override + @Transactional(Transactional.TxType.REQUIRED) + public void init(final ContentItem contentItem) { + if (initialized) { + return; } + + if (contentItem instanceof MultiPartArticle) { + final MultiPartArticle mpa = (MultiPartArticle) contentItem; + + title = Optional + .ofNullable(mpa.getTitle()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(""); + summary = Optional + .ofNullable(mpa.getSummary()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(null); + + sectionTitles = Optional + .ofNullable(mpa.getSections()) + .map( + mpaSections -> mpaSections + .stream() + .map(MultiPartArticleSection::getTitle) + .map(globalizationHelper::getValueFromLocalizedString) + .collect(Collectors.toList()) + ) + .orElse(Collections.emptyList()); + + currentSection = readCurrentSection(); + + if (mpa.getSections().size() < currentSection) { + throw new WebApplicationException( + Response + .status(Response.Status.NOT_FOUND) + .entity( + String.format( + "MultiPartArticle %s has not section %d.", + mpa.getDisplayName(), + currentSection + ) + ) + .build() + ); + } + + if (mpa.getSections().isEmpty()) { + currentSectionTitle = ""; + currentSectionText = ""; + } else { + currentSectionTitle = Optional + .ofNullable(mpa.getSections().get(currentSection).getTitle()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(""); + + currentSectionText = Optional + .ofNullable(mpa.getSections().get(currentSection).getText()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(""); + } + + sectionLinks = buildSectionLinks(); + sections = mpa + .getSections() + .stream() + .map(this::buildSectionModel) + .collect(Collectors.toList()); + } + + initialized = true; } private int readCurrentSection() { - if (pageUrlModel.getPath().matches(".*/@sections/[0-9]*$")) { - final String[] tokens = pageUrlModel.getPath().split("/"); - return Integer.valueOf(tokens[tokens.length - 1]) - 1; - } else { - return 0; - } + return Optional + .ofNullable( + pageUrlModel.getQueryParameters().get( + MPA_SECTION_QUERY_PARAM + ) + ) + .filter(value -> value.matches("\\d*")) + .map(value -> Integer.parseInt(value)) + .orElse(0); } + @Transactional(Transactional.TxType.REQUIRED) private MultiPartArticleSectionModel buildSectionModel( final MultiPartArticleSection fromSection ) { @@ -165,4 +256,52 @@ public class MultiPartArticleModel { return model; } + @Transactional(Transactional.TxType.REQUIRED) + private List buildSectionLinks() { + final int size = sectionTitles.size(); + final List sectionLinksList = new ArrayList<>(); + for (int i = 0; i < size; i++) { + sectionLinksList.add(buildSectionLink(i)); + } + + if (currentSection == 0) { + prevSectionLink = ""; + } else { + prevSectionLink = buildSectionLink(currentSection - 1); + } + + if (currentSection < sectionLinksList.size() - 1) { + nextSectionLink = buildSectionLink(currentSection + 1); + } else { + nextSectionLink = ""; + } + + return sectionLinksList; + } + + private String buildSectionLink(final int section) { + return String.format( + "?%s&%s=%d", + pageUrlModel + .getQueryParameters() + .entrySet() + .stream() + .filter( + entry + -> !MPA_SECTION_QUERY_PARAM + .equals( + entry.getKey() + ) + ) + .map( + entry -> String.format( + "%s=%s", entry.getKey(), entry.getValue() + ) + ) + .collect(Collectors.joining("&")), + MPA_SECTION_QUERY_PARAM, + section + ); + } + } diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/NewsListItemModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/NewsListItemModel.java index b61ddac57..ecf84bb3f 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/NewsListItemModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/NewsListItemModel.java @@ -36,6 +36,22 @@ public class NewsListItemModel extends AbstractContentItemListItemModel { return releaseDate; } + /** + * Returns the release date of the news represeted by this model formatted + * using the provided pattern. The pattern MUST be a valid pattern for + * {@link DateTimeFormatter#ofPattern(java.lang.String) }. + * + * @param pattern The pattern to use for formatting the release date. + * + * @return The formatted release date. + */ + public String getReleaseDate(final String pattern) { + return DateTimeFormatter + .ofPattern(pattern, getLocale()) + .withZone(ZoneId.systemDefault()) + .format(releaseDate); + } + public String getReleaseDateAsString() { return DateTimeFormatter.ISO_DATE_TIME .withZone(ZoneId.systemDefault()) diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/NewsListItemModelBuilder.java b/ccm-cms/src/main/java/org/librecms/pages/models/NewsListItemModelBuilder.java index f16346c99..2f478b5c7 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/NewsListItemModelBuilder.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/NewsListItemModelBuilder.java @@ -21,6 +21,7 @@ package org.librecms.pages.models; import org.librecms.contenttypes.News; import java.time.LocalDateTime; +import java.time.ZoneId; import javax.enterprise.context.RequestScoped; @@ -48,7 +49,9 @@ public class NewsListItemModelBuilder ) { super.addProperties(news, model); model.setReleaseDate( - LocalDateTime.from(news.getReleaseDate().toInstant()) + LocalDateTime.from( + news.getReleaseDate().toInstant().atZone(ZoneId.systemDefault()) + ) ); } diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/NewsModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/NewsModel.java index 1c3e963cc..a4359e327 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/NewsModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/NewsModel.java @@ -18,20 +18,19 @@ */ package org.librecms.pages.models; -import org.libreccm.l10n.GlobalizationHelper; -import org.librecms.contentsection.ContentItem; -import org.librecms.contenttypes.News; - import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import java.util.Date; +import java.util.Optional; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Response; +import javax.transaction.Transactional; + +import org.libreccm.l10n.GlobalizationHelper; +import org.librecms.contentsection.ContentItem; +import org.librecms.contenttypes.News; /** * @@ -39,7 +38,7 @@ import javax.ws.rs.core.Response; */ @RequestScoped @Named("CmsPagesNewsModel") -public class NewsModel { +public class NewsModel implements ProcessesContentItem { @Inject private ContentItemModel contentItemModel; @@ -47,46 +46,130 @@ public class NewsModel { @Inject private GlobalizationHelper globalizationHelper; + private boolean initialized; + + private final DateTimeFormatter isoDateTimeFormatter; + + private String title; + + private String description; + + private String text; + + private LocalDateTime releaseDateTime; + + private boolean homepage; + + public NewsModel() { + initialized = false; + isoDateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME + .withZone(ZoneId.systemDefault()); + } + + @Transactional(Transactional.TxType.REQUIRED) public String getTitle() { - return globalizationHelper - .getValueFromLocalizedString(getNews().getTitle()); + contentItemModel.init(); + + return title; } + @Transactional(Transactional.TxType.REQUIRED) public String getDescription() { - return globalizationHelper - .getValueFromLocalizedString(getNews().getDescription()); + contentItemModel.init(); + + return description; } + @Transactional(Transactional.TxType.REQUIRED) public String getText() { - return globalizationHelper - .getValueFromLocalizedString(getNews().getText()); + contentItemModel.init(); + + return text; } - public String getReleaseDateTime() { - return DateTimeFormatter.ISO_DATE_TIME - .withZone(ZoneId.systemDefault()) - .format( - LocalDateTime.from(getNews().getReleaseDate().toInstant()) - ); + @Transactional(Transactional.TxType.REQUIRED) + public LocalDateTime getReleaseDateTime() { + contentItemModel.init(); + + return releaseDateTime; } - + + /** + * Returns the release date of the news represented by this model formatted + * using the provided pattern. The pattern MUST be a valid pattern for + * {@link DateTimeFormatter#ofPattern(java.lang.String) }. + * + * @param pattern The pattern to use for formatting the release date. + * + * @return The formatted release date. + */ + public String getReleaseDate(final String pattern) { + return Optional + .ofNullable(releaseDateTime) + .map( + dateTime -> DateTimeFormatter + .ofPattern( + pattern, + globalizationHelper.getNegotiatedLocale() + ) + .withZone(ZoneId.systemDefault()) + .format(dateTime) + ) + .orElse(""); + } + + public String getReleaseDateTimeAsString() { + return Optional + .ofNullable(releaseDateTime) + .map( + dateTime -> DateTimeFormatter.ISO_DATE_TIME + .withZone(ZoneId.systemDefault()) + .format(dateTime) + ) + .orElse(""); + } + + @Transactional(Transactional.TxType.REQUIRED) public boolean getHomepage() { - return getNews().isHomepage(); + contentItemModel.init(); + + return homepage; } - protected News getNews() { - final ContentItem contentItem = contentItemModel.getContentItem(); - if (contentItem instanceof News) { - return (News) contentItem; - } else { - throw new WebApplicationException( - "Current content item is not a news item.", - Response - .status(Response.Status.INTERNAL_SERVER_ERROR) - .entity("Current content item is not a new item.") - .build() - ); + @Override + @Transactional(Transactional.TxType.REQUIRED) + public void init(final ContentItem contentItem) { + if (initialized) { + return; } + + if (contentItem instanceof News) { + final News news = (News) contentItem; + + title = Optional + .ofNullable(news.getTitle()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(""); + description = Optional + .ofNullable(news.getDescription()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(""); + text = Optional + .ofNullable(news.getText()) + .map(globalizationHelper::getValueFromLocalizedString) + .orElse(""); + releaseDateTime = Optional + .ofNullable(news.getReleaseDate()) + .map( + date -> LocalDateTime.from( + date.toInstant().atZone(ZoneId.systemDefault()) + ) + ) + .orElse(null); + homepage = news.isHomepage(); + } + + initialized = true; } } diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/PageUrlModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/PageUrlModel.java index 3a9509812..6afc685e2 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/PageUrlModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/PageUrlModel.java @@ -21,12 +21,13 @@ package org.librecms.pages.models; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; import javax.enterprise.context.RequestScoped; import javax.inject.Named; /** - * Model initalized by the Pages application containing information about the + * Model initialized by the Pages application containing information about the * URL requested. * * @author Jens Pelzetter @@ -93,4 +94,20 @@ public class PageUrlModel { this.queryParameters = new HashMap<>(queryParameters); } + public String getQueryString() { + return queryParameters + .entrySet() + .stream() + .map( + entry -> String.format( + "%s=%s", + entry.getKey(), + entry.getValue() + ) + ) + .collect( + Collectors.joining("&", "?", "") + ); + } + } diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/ProcessesContentItem.java b/ccm-cms/src/main/java/org/librecms/pages/models/ProcessesContentItem.java new file mode 100644 index 000000000..9d3714e29 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/pages/models/ProcessesContentItem.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 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.pages.models; + +import org.hibernate.LazyInitializationException; +import org.librecms.contentsection.ContentItem; + +import javax.transaction.Transactional; + +/** + * Models that provide data for the current content item (either the index item + * of a category or a selected content item) SHOULD implement this interface. + * + * This {@link ContentItemModel} collects all instances of classes implementing + * this interface and calls their implemententation of the + * {@link #init(org.librecms.contentsection.ContentItem)} method. + * + * For an example of an implementation please look at + * {@link ContentItemTypeModel}, {@link ArticleModel} or + * at {@link MultiPartArticleModel} for a more complex model. + * + * @author Jens Pelzetter + */ +public interface ProcessesContentItem { + + /** + * Initalizes a model providing the data of a content item for MVC + * templates. + * + * If the implementing model only provides data for specific sub classes of + * {@link ContentItem} the implementation MUST check the correct type using + * the {@code instanceof} operator. If the item is not a the correct type a + * implementation MUST gracefully ignore the item. + * + * To avoid a {@link LazyInitializationException} implementations SHOULD be + * annotated with {@link Transactional} and the + * {@link Transactional.TxType#REQUIRED}. + * + * @param item The item. + */ + void init(ContentItem item); + +} 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 61f500282..e3d7e741f 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/PagesController.java +++ b/ccm-cms/src/main/java/org/librecms/ui/PagesController.java @@ -515,7 +515,7 @@ public class PagesController { return showPageDetails(pagesInstance, categoryParam); } - if (!propertyKey.matches("^([a-z0-9-_]*)$")) { + if (!propertyKey.matches("^([a-zA-Z0-9-_\\.]*)$")) { models.put("propertyKeyInvalid", true); return showPageDetails(pagesInstance, categoryParam); } diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/CategoriesController.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/CategoriesController.java index 71db5db33..9dfe35103 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contentsections/CategoriesController.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/CategoriesController.java @@ -18,8 +18,6 @@ */ package org.librecms.ui.contentsections; -import com.arsdigita.cms.ui.authoring.multipartarticle.SectionPreviewPanel; - import org.libreccm.api.Identifier; import org.libreccm.api.IdentifierParser; import org.libreccm.categorization.Categorization; @@ -33,8 +31,12 @@ import org.libreccm.core.CcmObject; import org.libreccm.l10n.GlobalizationHelper; import org.libreccm.security.AuthorizationRequired; import org.libreccm.security.PermissionChecker; +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentItemManager; +import org.librecms.contentsection.ContentItemVersion; import org.librecms.contentsection.ContentSection; import org.librecms.contentsection.ContentSectionRepository; +import org.librecms.contentsection.Folder; import org.librecms.contentsection.privileges.AdminPrivileges; import java.time.ZoneId; @@ -87,6 +89,12 @@ public class CategoriesController { @Inject private CategorySystemModel categorySystemModel; + /** + * The {@link ContentItemManager}. + */ + @Inject + private ContentItemManager itemManager; + /** * The {@link ContentSectionModel} which stores the data of the current * content section for the view. @@ -252,7 +260,7 @@ public class CategoriesController { "sectionIdentifier", sectionIdentifier ); } - + final Optional domainResult = section .getDomains() .stream() @@ -812,18 +820,59 @@ public class CategoriesController { category.setAbstractCategory(Objects.equals("true", isAbstract)); categoryRepo.save(category); +// return String.format( +// "redirect:/%s/categorysystems/%s/categories/%s/%s", +// sectionIdentifier, +// context, +// categoryManager.getCategoryPath(category.getParentCategory()), +// categoryName +// ); return String.format( - "redirect:/%s/categorysystems/%s/categories/%s/%s", + "redirect:/%s/categorysystems/%s/categories/%s", sectionIdentifier, context, - categoryManager.getCategoryPath(category.getParentCategory()), - categoryName + categoryManager.getCategoryPath(category) ); } else { return result.getFailedResponseTemplate(); } } + /** + * Sets the index element of the root category. + * + * @param sectionIdentifier The identifier of the current + * {@link ContentSection}. + * @param context The mapping context of the assigned category + * system. + * @param indexElementUuid The UUID of the new index element. + * + * @return A redirect to the category page or the template for generating an + * error view. + */ + @POST + @Path( + "/{context}/categories/@index-element/{indexElementUuid}") + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String setIndexElement( + @PathParam("sectionIdentifier") final String sectionIdentifier, + @PathParam("context") final String context, + @PathParam("indexElementUuid") final String indexElementUuid + ) { + final RetrieveResult result = retrieveCategory( + sectionIdentifier, context, "/" + ); + + return setIndexObject( + result, + sectionIdentifier, + context, + context, + indexElementUuid + ); + } + /** * Sets the index element of a category. * @@ -837,7 +886,7 @@ public class CategoriesController { * @return A redirect to the category page or the template for generating an * error view. */ - @GET + @POST @Path( "/{context}/categories/{categoryPath:(.+)?}/@index-element/{indexElementUuid}") @AuthorizationRequired @@ -851,41 +900,95 @@ public class CategoriesController { final RetrieveResult result = retrieveCategory( sectionIdentifier, context, categoryPath ); + + return setIndexObject( + result, + sectionIdentifier, + context, + categoryPath, + indexElementUuid + ); +// if (result.isSuccessful()) { +// final Category category = result.getResult(); +// final Optional categorizationResult = category +// .getObjects() +// .stream() +// .filter( +// categorization -> Objects.equals( +// categorization.getUuid(), indexElementUuid +// ) +// ).findAny(); +// if (categorizationResult.isPresent()) { +// final CcmObject object = categorizationResult +// .get() +// .getCategorizedObject(); +// try { +// categoryManager.setIndexObject(category, object); +// if (object instanceof ContentItem) { +// final ContentItem item = (ContentItem) object; +// final ContentItem live = itemManager.getDraftVersion( +// item, +// ContentItem.class +// ); +// categoryManager.setIndexObject(category, live); +// } +// } catch (ObjectNotAssignedToCategoryException ex) { +// models.put("sectionIdentifier", sectionIdentifier); +// models.put("context", context); +// models.put("categoryPath", categoryPath); +// models.put("categorizationUuid", indexElementUuid); +// return "org/librecms/ui/contentsection/categorysystems/categorization-not-found.xhtml"; +// } +// } else { +// models.put("sectionIdentifier", sectionIdentifier); +// models.put("context", context); +// models.put("categoryPath", categoryPath); +// models.put("categorizationUuid", indexElementUuid); +// return "org/librecms/ui/contentsection/categorysystems/categorization-not-found.xhtml"; +// } +// return String.format( +// "redirect:/%s/categorysystems/%s/categories/%s#objects-sections", +// sectionIdentifier, +// context, +// categoryPath +// ); +// } else { +// return result.getFailedResponseTemplate(); +// } + } + + /** + * Resets the index object of the root category. The object is still + * associated with the category after this, but no longer the index object + * of the category. + * + * @param sectionIdentifier The identifier of the current + * {@link ContentSection}. + * @param context The mapping context of the assigned category + * system. + * + * @return A redirect to the category page or the template for generating an + * error view. + */ + @POST + @Path("/{context}/categories/@index-element/reset") + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String resetIndexElement( + @PathParam("sectionIdentifier") final String sectionIdentifier, + @PathParam("context") final String context + ) { + final RetrieveResult result = retrieveCategory( + sectionIdentifier, context, "/" + ); + if (result.isSuccessful()) { final Category category = result.getResult(); - final Optional categorizationResult = category - .getObjects() - .stream() - .filter( - categorization -> Objects.equals( - categorization.getUuid(), indexElementUuid - ) - ).findAny(); - if (categorizationResult.isPresent()) { - final CcmObject object = categorizationResult - .get() - .getCategorizedObject(); - try { - categoryManager.setIndexObject(category, object); - } catch (ObjectNotAssignedToCategoryException ex) { - models.put("sectionIdentifier", sectionIdentifier); - models.put("context", context); - models.put("categoryPath", categoryPath); - models.put("categorizationUuid", indexElementUuid); - return "org/librecms/ui/contentsection/categorysystems/categorization-not-found.xhtml"; - } - } else { - models.put("sectionIdentifier", sectionIdentifier); - models.put("context", context); - models.put("categoryPath", categoryPath); - models.put("categorizationUuid", indexElementUuid); - return "org/librecms/ui/contentsection/categorysystems/categorization-not-found.xhtml"; - } + categoryManager.resetIndexObject(category); return String.format( - "redirect:/%s/categorysystems/%s/categories/%s#objects-sections", + "redirect:/%s/categorysystems/%s/categories/", sectionIdentifier, - context, - categoryPath + context ); } else { return result.getFailedResponseTemplate(); @@ -893,7 +996,9 @@ public class CategoriesController { } /** - * Rests (removes) the index element of a category. + * Resets the index object of a category. The object is still associated + * with the category after this, but no longer the index object of the + * category. * * @param sectionIdentifier The identifier of the current * {@link ContentSection}. @@ -904,7 +1009,7 @@ public class CategoriesController { * @return A redirect to the category page or the template for generating an * error view. */ - @GET + @POST @Path("/{context}/categories/{categoryPath:(.+)?}/@index-element/reset") @AuthorizationRequired @Transactional(Transactional.TxType.REQUIRED) @@ -920,7 +1025,7 @@ public class CategoriesController { final Category category = result.getResult(); categoryManager.resetIndexObject(category); return String.format( - "redirect:/%s/categorysystems/%s/categories/%s#objects-sections", + "redirect:/%s/categorysystems/%s/categories/%s", sectionIdentifier, context, categoryPath @@ -1220,7 +1325,47 @@ public class CategoriesController { } /** - * Reorders subcategories. + * Reorders objects assigned to the root category. + * + * @param sectionIdentifier The identifier of the current + * {@link ContentSection}. + * @param context The mapping context of the assigned category + * system. + * @param objectIdentifier The identifier of the object to move. + * @param direction The direction of the move. + * + * @return A redirect to the details page of the parent category or the + * template for building an error message. + */ + @POST + @Path( + "/{context}/categories/@objects/{objectIdentifier}/order" + ) + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String orderObjects( + @PathParam("sectionIdentifier") final String sectionIdentifier, + @PathParam("context") final String context, + @PathParam("objectIdentifier") final String objectIdentifier, + @FormParam("direction") final String direction + ) { + final RetrieveResult result = retrieveCategory( + sectionIdentifier, + context, + "/" + ); + + return orderObjects( + result, + sectionIdentifier, + context, + objectIdentifier, + direction + ); + } + + /** + * Reorders objects assigned to a category. * * @param sectionIdentifier The identifier of the current * {@link ContentSection}. @@ -1246,9 +1391,147 @@ public class CategoriesController { @FormParam("direction") final String direction ) { final RetrieveResult result = retrieveCategory( - sectionIdentifier, context, categoryPath + sectionIdentifier, + context, + categoryPath ); + return orderObjects( + result, + sectionIdentifier, + context, + objectIdentifier, + direction + ); +// +// if (result.isSuccessful()) { +// final Category category = result.getResult(); +// +// final Optional categorizationResult = category +// .getObjects() +// .stream() +// .filter( +// categorization -> Objects.equals( +// categorization.getUuid(), objectIdentifier +// ) +// ).findAny(); +// if (categorizationResult.isPresent()) { +// final CcmObject object = categorizationResult +// .get() +// .getCategorizedObject(); +// try { +// switch (direction) { +// case "DECREASE": +// categoryManager.decreaseObjectOrder( +// object, category +// ); +// break; +// case "INCREASE": +// categoryManager.increaseObjectOrder( +// object, category +// ); +// break; +// default: +// // Nothing +// break; +// } +// } catch (ObjectNotAssignedToCategoryException ex) { +// return String.format( +// "redirect:/%s/categorysystems/%s/categories/%s#objects-sections", +// sectionIdentifier, +// context, +// categoryManager.getCategoryPath(category) +// ); +// } +// } +// +// return String.format( +// "redirect:/%s/categorysystems/%s/categories/%s", +// sectionIdentifier, +// context, +// categoryManager.getCategoryPath(category) +// ); +// } else { +// return result.getFailedResponseTemplate(); +// } + } + + private String setIndexObject( + final RetrieveResult result, + @PathParam("sectionIdentifier") final String sectionIdentifier, + @PathParam("context") final String context, + @PathParam("categoryPath") final String categoryPath, + @PathParam("indexElementUuid") final String indexElementUuid + ) { + if (result.isSuccessful()) { + final Category category = result.getResult(); + final List categorizationResult = category + .getObjects() + .stream() + .filter( + categorization -> Objects.equals( + categorization.getCategorizedObject().getUuid(), + indexElementUuid + ) + ) + .collect(Collectors.toList()); + if (!categorizationResult.isEmpty()) { + for (final Categorization categorization : categorizationResult) { + final CcmObject object = categorization + .getCategorizedObject(); + try { + categoryManager.setIndexObject(category, object); + if (object instanceof ContentItem) { + final ContentItem item = (ContentItem) object; + final ContentItem live = itemManager + .getDraftVersion( + item, + ContentItem.class + ); + categoryManager.setIndexObject(category, live); + } + } catch (ObjectNotAssignedToCategoryException ex) { + models.put("sectionIdentifier", sectionIdentifier); + models.put("context", context); + models.put("categoryPath", categoryPath); + models.put("categorizationUuid", indexElementUuid); + return "org/librecms/ui/contentsection/categorysystems/categorization-not-found.xhtml"; + } + } + } else { + models.put("sectionIdentifier", sectionIdentifier); + models.put("context", context); + models.put("categoryPath", categoryPath); + models.put("categorizationUuid", indexElementUuid); + return "org/librecms/ui/contentsection/categorysystems/categorization-not-found.xhtml"; + } + if ("/".equals(categoryPath) + || category.getParentCategory() == null) { + return String.format( + "redirect:/%s/categorysystems/%s/categories/", + sectionIdentifier, + context + ); + } else { + return String.format( + "redirect:/%s/categorysystems/%s/categories/%s", + sectionIdentifier, + context, + categoryPath + ); + } + } else { + return result.getFailedResponseTemplate(); + } + } + + private String orderObjects( + final RetrieveResult result, + final String sectionIdentifier, + final String context, + final String objectIdentifier, + final String direction + ) { if (result.isSuccessful()) { final Category category = result.getResult(); @@ -1358,7 +1641,7 @@ public class CategoriesController { ); } final ContentSection section = sectionResult.get(); - if (permissionChecker.isPermitted( + if (!permissionChecker.isPermitted( AdminPrivileges.ADMINISTER_CATEGORIES, section )) { return RetrieveResult.failed( @@ -1477,6 +1760,15 @@ public class CategoriesController { category .getObjects() .stream() + .filter( + categorization -> categorization + .getCategorizedObject() instanceof ContentItem + ) + .filter( + categorization -> ((ContentItem) categorization + .getCategorizedObject()) + .getVersion() == ContentItemVersion.DRAFT + ) .map(this::buildCategorizedObjectModel) .collect(Collectors.toList()) ); diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/CategoryModel.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/CategoryModel.java index 4e4f258e3..f8f6cbf55 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contentsections/CategoryModel.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/CategoryModel.java @@ -223,7 +223,7 @@ public class CategoryModel { } public void setObjects(final List objects) { - this.objects = new ArrayList<>(); + this.objects = new ArrayList<>(objects); } public long getCategoryOrder() { diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/DocumentController.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/DocumentController.java index 1d165edce..80ed0c96c 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/DocumentController.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/DocumentController.java @@ -815,7 +815,7 @@ public class DocumentController { } return String.format( - "redirect:/%s/documentfolders/%s", + "redirect:/%s/documentfolders%s", sectionIdentifier, folderPath ); diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/media/MediaStep.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/media/MediaStep.java index 66b0866dc..eb79ab20b 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/media/MediaStep.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/media/MediaStep.java @@ -23,6 +23,7 @@ import org.libreccm.api.IdentifierParser; import org.libreccm.l10n.GlobalizationHelper; import org.libreccm.security.PermissionChecker; import org.libreccm.ui.BaseUrl; +import org.librecms.CmsConstants; import org.librecms.assets.AssetTypesManager; import org.librecms.contentsection.Asset; import org.librecms.contentsection.AssetManager; @@ -63,6 +64,8 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.Context; +import static org.librecms.CmsConstants.MEDIA_LIST_PREFIX; + /** * * @author Jens Pelzetter @@ -83,8 +86,6 @@ public class MediaStep extends AbstractMvcAuthoringStep { */ static final String PATH_FRAGMENT = "media"; - protected static final String MEDIA_LIST_PREFIX = ".media-"; - /** * {@link AssetManager} instance of managing {@link Asset}s. */ @@ -186,7 +187,11 @@ public class MediaStep extends AbstractMvcAuthoringStep { getDocument() .getAttachments() .stream() - .filter(list -> list.getName().startsWith(MEDIA_LIST_PREFIX)) + .filter( + list -> list.getName().startsWith( + CmsConstants.MEDIA_LIST_PREFIX + ) + ) .map(this::buildMediaListDto) .collect(Collectors.toList()) ); diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/media/MediaStepService.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/media/MediaStepService.java index 0b15ee0c7..32f15cd44 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/media/MediaStepService.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/media/MediaStepService.java @@ -18,6 +18,7 @@ */ package org.librecms.ui.contentsections.documents.media; +import org.librecms.CmsConstants; import org.librecms.contentsection.AttachmentList; import org.librecms.contentsection.AttachmentListRepository; import org.librecms.contentsection.ContentItem; @@ -102,7 +103,10 @@ public class MediaStepService { .getAttachments() .stream() .filter( - list -> list.getName().startsWith(MediaStep.MEDIA_LIST_PREFIX)) + list -> list.getName().startsWith( + CmsConstants.MEDIA_LIST_PREFIX + ) + ) .collect(Collectors.toList()); final List attachmentListsOrder = order .getMediaListsOrder(); @@ -147,7 +151,7 @@ public class MediaStepService { .stream() .filter( list -> list.getName().startsWith( - MediaStep.MEDIA_LIST_PREFIX + CmsConstants.MEDIA_LIST_PREFIX ) ) .filter(list -> attachmentsOrder.getKey().equals(list.getUuid())) diff --git a/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/cms/page-details.xhtml b/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/cms/page-details.xhtml index 8fe2df6a2..7c703d971 100644 --- a/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/cms/page-details.xhtml +++ b/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/cms/page-details.xhtml @@ -155,7 +155,7 @@ class="form-control" id="property-key" name="propertyKey" - pattern="^([a-zA-Z0-9-_]*)$" + pattern="^([a-zA-Z0-9-_\.]*)$" required="true" type="text" /> - - + - + + value="true" />