librecms-default-theme #20

Merged
jensp merged 46 commits from librecms-default-theme into master 2022-03-14 19:12:32 +01:00
65 changed files with 5286 additions and 633 deletions

View File

@ -107,7 +107,11 @@
<Logger name="org.libreccm.ui.admin.applications.ApplicationsPage"
level="debug">
</Logger>
<Logger name="org.librecms.ui.ContentSectionController" level="debug">
<Logger name="org.librecms.ui.ContentSectionController"
level="debug">
</Logger>
<Logger name="org.libreccm.categorization.CategoryManager"
level="debug">
</Logger>
</Loggers>
</Configuration>

View File

@ -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",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 175 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 180 KiB

View File

@ -0,0 +1,63 @@
<#macro "org.librecms.assets.AudioAsset" asset>
<figure>
<audio controls
src="/content-sections/${asset.contentSection}/audiomedia${asset.assetPath}"
width="240"></audio>
<figcaption>${asset.description}</figcaption>
</figure>
</#macro>
<#macro "org.librecms.assets.ExternalAudioAsset" asset>
<figure>
<audio controls src="${asset.url}" width="240"></audio>
<figcaption>${asset.description}</figcaption>
</figure>
</#macro>
<#macro "org.librecms.assets.ExternalVideoAsset" asset>
<figure>
<video controls src="${asset.url}" width="240"></video>
<figcaption>${asset.description}</figcaption>
</figure>
</#macro>
<#macro "org.librecms.assets.FileAsset" asset>
<h3><a href="/content-sections/${asset.contentSection}/files${asset.assetPath}">${asset.title}</a></h3>
<p>${asset.description}</p>
<small>${asset.mimeType} ${asset.size} Bytes</small>
</#macro>
<#macro "org.librecms.assets.Image" asset>
<figure>
<img src="/content-sections/${asset.contentSection}/images${asset.assetPath}"
width="240" />
<figcaption>${asset.description}</figcaption>
</figure>
</#macro>
<#macro "org.librecms.assets.RelatedLink" asset>
<#if asset.externalLink>
<div>
<a href="${asset.targetUrl}">${asset.title}</a>
<svg class="bi"
fill="current-color"
height="1em"
width="1em">
<use xlink:href="${themeUrl}/images/bootstrap-icons.svg#globe" />
</svg>
<span class="visually-hidden">External link</span>
</div>
<#else>
<a href="${asset.targetItemPath}">${asset.title}</a>
</#if>
</#macro>
<#macro "org.librecms.assets.VideoAsset" asset>
<figure>
<video controls
src="/content-sections/${asset.contentSection}/videos${asset.assetPath}"
width="240"></video>
<figcaption>${asset.description}</figcaption>
</figure>
</#macro>

View File

@ -0,0 +1,51 @@
<#import "./main.html.ftl" as main>
<#import "./assets.html.ftl" as assets>
<@main.librecms>
<div class="container">
<div class="row align-items-start justify-content-center">
<div class="col-lg-8">
<#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>
</#if>
<@contentitem.details />
</#if>
</div>
<div class="col-lg-4">
<#list CmsPagesCategorizedItemModel.attachmentLists as attachmentList>
<h2>${attachmentList.title}</h2>
<p class="item-description">${attachmentList.description}</p>
<#list attachmentList.attachments>
<ul class="list-group">
<#items as attachment>
<li class="list-group-item">
<@.vars["assets"][attachment.asset.type] attachment.asset />
</li>
</#items>
</ul>
</#list>
</#list>
</div>
</div>
<#list CmsPagesItemListModel.getItems()>
<div class="row align-items-start">
<div class="col-12">
<ul class="list-group">
<#items as item>
<li class="list-group-item">
<h2>
<a href="/pages/${CmsPagesCategoryModel.category.path}${item.name}.${negotiatedLocale}.html${CmsPagesPageUrlModel.queryString}">${item.title}</a>
</h2>
<p class="item-description">${item.description}</p>
</li>
</#items>
</ul>
</div>
</div>
</div>
</#list>
</@main.librecms>

View File

@ -0,0 +1,7 @@
<#macro details>
<h1>${CmsPagesCategorizedItemModel.title}</h1>
<p class="item-description">${CmsPagesCategorizedItemModel.description}</p>
<div class="alert alert-warning">
No template for ${CmsPagesContentItemTypeModel.itemClass} available
</div>
</#macro>

View File

@ -0,0 +1,17 @@
<#import "../assets.html.ftl" as assets>
<#macro details>
<h1>${CmsPagesCategorizedItemModel.title}</h1>
<p class="item-description">${CmsPagesCategorizedItemModel.description}</p>
<div class="float-end">
<#list CmsPagesCategorizedItemModel.mediaLists as mediaList>
<#list mediaList.attachments as media>
<@.vars["assets"][media.asset.type] media.asset />
</#list>
</#list>
</div>
<div>
${CmsPagesArticleModel.text}
</div>
</#macro>

View File

@ -0,0 +1,25 @@
<#macro details>
<div class="d-flex">
<h1>
${CmsPagesCategorizedItemModel.title}
<span class="badge bg-secondary rounded-pill">
${CmsPagesEventModel.getStartDateTime('yyyy-MM-dd')}
<#if CmsPagesEventModel.getEndDateTime()??>
- ${CmsPagesEventModel.getEndDateTime('yyyy-MM-dd')}
</#if>
</span>
</h1>
</div>
<p class="item-description">${CmsPagesCategorizedItemModel.description}</p>
<div>
${CmsPagesEventModel.text}
</div>
<#if CmsPagesEventModel.location??>
<h2>Location</h2>
<div>
${CmsPagesEventModel.location}
</div>
</#if>
</#macro>

View File

@ -0,0 +1,61 @@
<#macro details>
<h1>${CmsPagesCategorizedItemModel.title}</h1>
<p class="item-description">${CmsPagesCategorizedItemModel.description}</p>
<h2>${CmsPagesMultiPartArticleModel.currentSectionTitle}</h2>
<div>
<nav>
<ul class="flex-column float-end nav">
<#list CmsPagesMultiPartArticleModel.sectionTitles as section>
<li class="nav-item">
<#if section?index == CmsPagesMultiPartArticleModel.currentSection>
<a aria-current="page"
class="active nav-link"
href="${CmsPagesMultiPartArticleModel.sectionLinks[section?index]}">
${section}
</a>
<#else>
<a class="nav-link"
href="${CmsPagesMultiPartArticleModel.sectionLinks[section?index]}">
${section}
</a>
</#if>
</li>
</#list>
</ul>
</nav>
${CmsPagesMultiPartArticleModel.currentSectionText}
<nav>
<ul class="pagination">
<#if CmsPagesMultiPartArticleModel.prevSectionLink != "">
<li class="list-item">
<a class="page-link"
href="${CmsPagesMultiPartArticleModel.prevSectionLink}">
<svg class="bi"
fill="current-color"
height="1em"
width="1em">
<use xlink:href="${themeUrl}/images/bootstrap-icons.svg#caret-left-fill" />
</svg>
<span class="visually-hidden">Previous</span>
</a>
</li>
</#if>
<#if CmsPagesMultiPartArticleModel.nextSectionLink != "">
<li class="list-item">
<a class="page-link"
href="${CmsPagesMultiPartArticleModel.nextSectionLink}">
<svg class="bi"
fill="current-color"
height="1em"
width="1em">
<use xlink:href="${themeUrl}/images/bootstrap-icons.svg#caret-right-fill" />
</svg>
<span class="visually-hidden">Next</span>
</a>
</li>
</#if>
</ul>
</nav>
</div>
</#macro>

View File

@ -0,0 +1,15 @@
<#macro details>
<div class="d-flex">
<h1>
${CmsPagesCategorizedItemModel.title}
<span class="badge bg-secondary rounded-pill">
${CmsPagesNewsModel.getReleaseDate('yyyy-MM-dd')}
</span>
</h1>
</div>
<p class="item-description">${CmsPagesCategorizedItemModel.description}</p>
<div>
${CmsPagesNewsModel.text}
</div>
</#macro>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<link href="${themeUrl}/styles/librecms.css" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Default Template</title>
</head>
<body>
<div class="container">
<p>This theme works.</p>
<dl>
<dt>application</dt>
<dd>${application}</dd>
<dt>themeUrl</dt>
<dd>${themeUrl}</dd>
<dt>view</dt>
<dd>${view!""}</dd>
</dl>
</div>
</body>
</html>

View File

@ -1,21 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<link href="${themeUrl}/styles/librecms.css" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Default Template</title>
</head>
<body>
<#import "./main.html.ftl" as main>
<#-- <#import "./contentitems/${CmsPagesContentItemTypeModel.itemClass}.html.ftl" as contentitem> -->
<@main.librecms>
<div class="container">
<p>This theme works.</p>
<dl>
<dt>application</dt>
<dd>${application}</dd>
<dt>themeUrl</dt>
<dd>${themeUrl}</dd>
<dt>view</dt>
<dd>${view!""}</dd>
</dl>
<div class="row align-items-start justify-content-center">
<div class="col-lg-8">
<#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>
</#if>
<@contentitem.details />
</#if>
</div>
</body>
</html>
<div class="col-lg-4">
<!-- Attachments -->
<h2>Notes</h2>
<ul class="list-group mb-4">
<li class="list-group-item">
<p>
A side note with some text
</p>
<p>
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.
</p>
</li>
</ul>
<h2>More information</h2>
<p>
Consequat occaecat eu ullamco amet id tempor.
</p>
<ul class="list-group mb-4">
<li class="list-group-item">
<p>Anim ex ut reprehenderit in enim id proident duis pariatur est anim do.</p>
<a href="https://example.com">A related link</a>
</li>
<li class="list-group-item">
<p>Quis minim deserunt incididunt ea voluptate laboris fugiat elit nulla.</p>
<a href="https://example.com">Some download link</a>
</li>
</ul>
</div>
</div>
</div>
</@main.librecms>

View File

@ -1,13 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<link href="${themeUrl}/styles/librecms.css" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Default Template</title>
</head>
<body>
<#import "./main.html.ftl" as main>
<@main.librecms>
<div class="container">
<h1>Index page</h1>
<div class="row align-items-center">
<div class="col">
<div class="bg-light mb-4 rounded-3">
<div class="container-fluid py-5">
<#if CmsPagesCategorizedItemModel.itemAvailable>
<h1 class="display-5 fw-bold">
${CmsPagesCategorizedItemModel.title}
</h1>
<p>
${CmsPagesCategorizedItemModel.description}
</p>
<a class="btn btn-primary btn-lg"
href="#">
Find out more
</a>
<#else>
<h1 class="display-5 fw-bold">
LibreCMS
</h1>
<p>
No index item has been defined.
</p>
<a class="btn btn-primary btn-lg"
href="https://www.libreccm.org">
Find out more
</a>
</#if>
</div>
</div>
<#-- <#if CmsPagesCategorizedItemModel.itemAvailable>
Category has an index item
<#else>
Category has no index item
</#if>
<h2>Index page</h2>
<p>This theme works.</p>
<dl>
<dt>application</dt>
@ -17,6 +48,126 @@
<dt>view</dt>
<dd>${view!""}</dd>
</dl>
<h2>From <code>ArticleModel</code></h2>
<dl>
<dt>Title</dt>
<dd>${CmsPagesArticleModel.title}</dd>
<dt>Description</dt>
<dd>${CmsPagesArticleModel.description}</dd>
<dt>Text</dt>
<dd>${CmsPagesArticleModel.text}</dd>
</dl>
</div> -->
</div>
</body>
</html>
<div class="row align-items-start justify-content-center">
<#list CmsPagesItemListModel.getItems("newslist")>
<div class="col">
<h2>News</h2>
<ul class="list-group">
<#items as news>
<li class="list-group-item">
<h3 class="d-flex w-100 justify-content-between">
<div>
<a href="/pages${CmsPagesCategoryModel.category.path}${news.name}.${negotiatedLocale}.html${CmsPagesPageUrlModel.queryString}">${news.title}</a>
</div>
<#-- <div>${news.getReleaseDate('yyyy-MM-dd')}</div> -->
<div>${news.getReleaseDate('dd. MMM yyyy')}</div>
</h3>
<p class="item-description">${news.description}</p>
</li>
</#items>
</ul>
</div>
</#list>
<#list CmsPagesItemListModel.getItems("eventlist")>
<div class="col">
<h2>Upcoming events</h2>
<ul class="list-group">
<#items as event>
<li class="list-group-item">
<h3 class="d-flex w-100 justify-content-between">
<div>
<a href="/pages${CmsPagesCategoryModel.category.path}${event.name}.${negotiatedLocale}.html${CmsPagesPageUrlModel.queryString}">${event.title}</a>
</div>
<div>${event.getStartDate('dd. MMM yyyy HH:mm')}</div>
</h3>
<p class="item-description">${event.description}</p>
</li>
</#items>
</ul>
</div>
</#list>
</div>
<#-- <h2>Item List</h2>
<p>Item List size: ${CmsPagesItemListModel.listSize}</p>
<dl>
<dt>Item List size:</dt>
<dd>${CmsPagesItemListModel.listSize}</dd>
<dt>Page size</dt>
<dd>${CmsPagesItemListModel.pageSize}</dd>
<dt>Page</dt>
<dd>${CmsPagesItemListModel.page}</dd>
<dt>Offset</dt>
<dd>${CmsPagesItemListModel.offset}</dd>
</dl>
<ul>
<#list CmsPagesItemListModel.items as item>
<li>
<dl>
<dt>UUID</dt>
<dd>${item.uuid}</dd>
<dt>displayName</dt>
<dd>${item.displayName}</dd>
<dt>Name</dt>
<dd>${item.name}</dd>
<dt>Title</dt>
<dd>${item.title}</dd>
<dt>description</dt>
<dd>${item.description}</dd>
<dt>Type</dt>
<dd>${item.type}</dd>
</dl>
</li>
</#list>
</ul>
</div>
<h2>News List</h2>
<p>News List size: ${CmsPagesItemListModel.getListSize("newslist")}</p>
<dl>
<dt>Item List size:</dt>
<dd>${CmsPagesItemListModel.getListSize("newslist")}</dd>
<dt>Page size</dt>
<dd>${CmsPagesItemListModel.getPageSize("newslist")}</dd>
<dt>Page</dt>
<dd>${CmsPagesItemListModel.getPage("newslist")}</dd>
<dt>Offset</dt>
<dd>${CmsPagesItemListModel.getOffset("newslist")}</dd>
</dl>
<ul>
<#list CmsPagesItemListModel.getItems("newslist") as item>
<li>
<dl>
<dt>UUID</dt>
<dd>${item.uuid}</dd>
<dt>displayName</dt>
<dd>${item.displayName}</dd>
<dt>Name</dt>
<dd>${item.name}</dd>
<dt>Title</dt>
<dd>${item.title}</dd>
<dt>description</dt>
<dd>${item.description}</dd>
<dt>Type</dt>
<dd>${item.type}</dd>
</dl>
</li>
</#list>
</ul>
</div> -->
</@main.librecms>

View File

@ -0,0 +1,50 @@
<#macro librecms scripts=[]>
<!DOCTYPE html>
<html>
<head>
<link href="${themeUrl}/styles/librecms.css" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Default Template</title>
</head>
<body>
<header>
<nav class="navbar navbar-expand-lg navbar-expand navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand"
href="/pages">
<img class="theme-logo img-fluid"
src="${themeUrl}/images/librecms.svg" />
</a>
<button aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
class="navbar-toggler"
data-bs-toggle="collapse"
data-bs-target="#navbar-items"
type="button">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse"
id="navbar-items">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<#list CmsPagesCategoryModel.categoryTree.subCategories as category>
<li ${category.selected?then("aria-selected=\"page\"","")}
class="nav-item ${category.selected?then( "active","")}">
<a class="nav-link"
href="/pages${category.categoryPath}/index.${negotiatedLocale}.html${CmsPagesPageUrlModel.queryString}">
${category.title}
</a>
</li>
</#list>
</ul>
</div>
</div>
</nav>
</header>
<main>
<#nested>
</main>
<script src="${themeUrl}/scripts/librecms.js"></script>
</body>
</html>
</#macro>

View File

@ -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"

View File

@ -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;
}

View File

@ -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.

View File

@ -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,15 +510,29 @@ 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();
if (path.isEmpty()) {
return String.format("%s:/", sectionName);
} else {
return String.format("%s:/%s/", sectionName, path);
}
} else {
if (path.isEmpty()) {
return "/";
} else {
return String.format("/%s/", path);
}
}
}
/**
* Creates list with a parent folders of the provided folder.

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
@ -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

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@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

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
@ -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,

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
@ -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

View File

@ -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());
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.

View File

@ -37,7 +37,16 @@ implements AssetModelBuilder<T, M> {
@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(
@ -45,7 +54,7 @@ implements AssetModelBuilder<T, M> {
);
model.setUuid(asset.getUuid());
addProperties(asset, model);
addProperties((T) asset, model);
return model;
}

View File

@ -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 <T>
* @param <M>
*/
@RequestScoped
public abstract class AbstractBinaryAssetModelBuilder<T extends BinaryAsset, M extends BinaryAssetModel>
extends AbstractAssetModelBuilder<T, M> {
@ -48,6 +47,12 @@ public abstract class AbstractBinaryAssetModelBuilder<T extends BinaryAsset, M e
super.addProperties(asset, model);
model.setAssetPath(assetManager.getAssetPath(asset));
model.setBinaryAssetUuid(asset.getUuid());
model.setContentSection(
assetManager
.getAssetFolder(asset)
.map(folder -> folder.getSection().getDisplayName())
.orElse("")
);
model.setDescription(
globalizationHelper.getValueFromLocalizedString(
asset.getDescription()

View File

@ -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;
@ -36,6 +44,14 @@ public abstract class AbstractContentItemListItemModel {
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.

View File

@ -39,16 +39,14 @@ public abstract class AbstractContentItemListItemModelBuilder<T extends ContentI
@SuppressWarnings("unchecked")
public M buildListItemModel(final ContentItem contentItem) {
final M model = buildModel();
model.setLocale(globalizationHelper.getNegotiatedLocale());
model.setDescription(
globalizationHelper.getValueFromLocalizedString(
contentItem.getDescription()
)
);
model.setName(
globalizationHelper.getValueFromLocalizedString(
contentItem.getName()
)
);
model.setName(contentItem.getDisplayName());
model.setDisplayName(contentItem.getDisplayName());
model.setTitle(
globalizationHelper.getValueFromLocalizedString(

View File

@ -22,11 +22,12 @@ import org.libreccm.l10n.GlobalizationHelper;
import org.librecms.contentsection.ContentItem;
import org.librecms.contenttypes.Article;
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;
/**
* Model for getting the special properties of an {@link Article}. For general
@ -36,7 +37,7 @@ import javax.ws.rs.core.Response;
*/
@RequestScoped
@Named("CmsPagesArticleModel")
public class ArticleModel {
public class ArticleModel implements ProcessesContentItem {
@Inject
private ContentItemModel contentItemModel;
@ -44,34 +45,63 @@ public class ArticleModel {
@Inject
private GlobalizationHelper globalizationHelper;
private boolean initialized;
private String title;
private String description;
private String text;
public ArticleModel() {
initialized = false;
}
@Transactional(Transactional.TxType.REQUIRED)
public String getTitle() {
return globalizationHelper
.getValueFromLocalizedString(getArticle().getTitle());
contentItemModel.init();
return title;
}
@Transactional(Transactional.TxType.REQUIRED)
public String getDescription() {
return globalizationHelper
.getValueFromLocalizedString(getArticle().getDescription());
contentItemModel.init();
return description;
}
@Transactional(Transactional.TxType.REQUIRED)
public String getText() {
return globalizationHelper
.getValueFromLocalizedString(getArticle().getText());
contentItemModel.init();
return text;
}
@Transactional(Transactional.TxType.REQUIRED)
@Override
public void init(final ContentItem contentItem) {
if (initialized) {
return;
}
protected Article getArticle() {
final ContentItem contentItem = contentItemModel.getContentItem();
if (contentItem instanceof Article) {
return (Article) contentItem;
} else {
throw new WebApplicationException(
"Current content item is not an article.",
Response
.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Current content item is not an article.")
.build()
);
final Article article = (Article) contentItem;
title = Optional
.ofNullable(article.getText())
.map(globalizationHelper::getValueFromLocalizedString)
.orElse("");
description = Optional
.ofNullable(article.getDescription())
.map(globalizationHelper::getValueFromLocalizedString)
.orElse("");
text = Optional
.ofNullable(article.getText())
.map(globalizationHelper::getValueFromLocalizedString)
.orElse("");
}
initialized = true;
}
}

View File

@ -28,7 +28,7 @@ import org.librecms.contentsection.Asset;
*/
public interface AssetModelBuilder<T extends Asset, M extends AbstractAssetModel> {
M buildAssetModel(T asset);
M buildAssetModel(Asset asset);
Class<T> buildsAssetModelFor();

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class AttachmentListModel implements Comparable<AttachmentListModel>{
private long listId;
private String uuid;
private String name;
private long listOrder;
private String title;
private String description;
private List<AttachmentModel> 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<AttachmentModel> getAttachments() {
return Collections.unmodifiableList(attachments);
}
public void setAttachments(final List<AttachmentModel> 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);
}
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class AttachmentModel implements Comparable<AttachmentModel>{
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);
}
}

View File

@ -36,6 +36,8 @@ public class BinaryAssetModel extends AbstractAssetModel {
private String binaryAssetUuid;
private String contentSection;
private String assetPath;
@Override
@ -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;
}
@ -91,8 +101,4 @@ public class BinaryAssetModel extends AbstractAssetModel {
this.assetPath = assetPath;
}
}

View File

@ -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,173 +60,373 @@ 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<ProcessesContentItem> 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> 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<ContentItemModelData> 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<AttachmentListModel> getAttachmentLists() {
init();
return contentItem
.map(ContentItemModelData::getAttachmentLists)
.orElse(Collections.emptyList());
}
@Transactional(Transactional.TxType.REQUIRED)
public List<AttachmentListModel> 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<AttachmentList> getAttachments() {
return getContentItem().getAttachments();
/**
* 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;
}
private Optional<ContentItem> getOrRetrieveContentItem() {
if (contentItem == null) {
retrieveContentItem();
final Optional<ContentItem> item = retrieveContentItem();
contentItem = item.map(this::buildModelData);
final Iterator<ProcessesContentItem> 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 <b>not</b> {@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<ContentItem> retrieveContentItem() {
final Optional<ContentItem> item;
if (itemName == null || "index".equals(itemName)) {
item = pagesService.findIndexItem(
categoryRepository
.findById(categoryModel.getCategory().getCategoryId())
.orElseThrow(
@ -235,7 +442,7 @@ public class ContentItemModel {
itemVersion
);
} else {
contentItem = pagesService.findCategorizedItem(
item = pagesService.findCategorizedItem(
categoryRepository
.findById(categoryModel.getCategory().getCategoryId())
.orElseThrow(
@ -252,5 +459,305 @@ public class ContentItemModel {
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<AttachmentListModel> attachmentLists;
private List<AttachmentListModel> 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<AttachmentListModel> getAttachmentLists() {
return Collections.unmodifiableList(attachmentLists);
}
public void setAttachmentLists(
final List<AttachmentListModel> attachmentLists
) {
this.attachmentLists = new ArrayList<>(attachmentLists);
}
public List<AttachmentListModel> getMediaLists() {
return Collections.unmodifiableList(mediaLists);
}
public void setMediaLists(final List<AttachmentListModel> mediaLists) {
this.mediaLists = new ArrayList<>(mediaLists);
}
}
}

View File

@ -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> contentType;
public ContentType getContentType() {
return getOrRetrieveContentType().orElse(null);
}
private Optional<ContentItemTypeModelData> 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<ContentType> getOrRetrieveContentType() {
if (contentType == null) {
retrieveContentType();
}
return contentType;
@Transactional(Transactional.TxType.REQUIRED)
public void init(final ContentItem contentItem) {
if (contentType != null) {
return;
}
private void retrieveContentType() {
contentType = Optional
.ofNullable(contentItemModel.getContentItem())
.map(ContentItem::getContentType);
.ofNullable(contentItem)
.map(ContentItem::getContentType)
.map(this::buildModelData);
}
@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;
}
}

View File

@ -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;
}
@ -51,6 +60,13 @@ public class EventListItemModel extends AbstractContentItemListItemModel {
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;
}

View File

@ -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())
)
);
}
}

View File

@ -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
@Transactional(Transactional.TxType.REQUIRED)
public LocalDateTime getStartDateTime() {
contentItemModel.init();
return startDateTime;
}
/**
* 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(getEvent().getStartDate().toInstant());
.format(dateTime)
)
.orElse("");
}
public String getEndDateTime() {
return DateTimeFormatter.ISO_DATE_TIME
public String getStartDateTimeAsString() {
contentItemModel.init();
return Optional
.ofNullable(endDateTime)
.map(
dateTime -> DateTimeFormatter.ISO_DATE_TIME
.withZone(ZoneId.systemDefault())
.format(getEvent().getEndDate().toInstant());
.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;
}
@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;
}
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()
);
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;
}
}

View File

@ -50,9 +50,12 @@ public class ImageModelBuilder
protected void addProperties(final Image image, final ImageModel model) {
super.addProperties(image, model);
model.setHeight(image.getHeight());
if (image.getLegalMetadata() != null) {
model.setLegalMetadata(
legalMetadataModelBuilder.buildAssetModel(image.getLegalMetadata())
legalMetadataModelBuilder.buildAssetModel(
image.getLegalMetadata())
);
}
model.setWidth(image.getWidth());
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
@ -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<String> 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<String> listOrder;
private int pageSize;
private List<? extends AbstractContentItemListItemModel> 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<String> getListOrder() {
return Collections.unmodifiableList(listOrder);
}
public void setListOrder(final List<String> 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<? extends AbstractContentItemListItemModel> getItems() {
// if (itemList == null) {
return getItems("");
}
@Transactional(Transactional.TxType.REQUIRED)
public List<? extends AbstractContentItemListItemModel> getItems(
final String listName
) {
final List<? extends AbstractContentItemListItemModel> items
= Collections.unmodifiableList(
buildList(
buildLimitToType(limitToType),
buildLimitToType(getLimitToTypeSetting(listName)),
collectCategories(
categoryRepository
.findById(categoryModel.getCategory().getCategoryId())
@ -179,19 +204,21 @@ public class ItemListModel {
)
)
),
listOrder,
pageSize
getListOrderSetting(listName),
getPageSizeSetting(listName),
getOffset(listName, getPageSizeSetting(listName))
)
);
// }
return Collections.unmodifiableList(itemList);
return items;
}
private void buildList(
private List<? extends AbstractContentItemListItemModel> buildList(
final Class<? extends ContentItem> limitToType,
final List<Category> categories,
final List<String> 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,19 +413,90 @@ 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;
private int parsePageParam(final String pageParamName) {
final String value = pageUrlModel
.getQueryParameters()
.get(pageParamName);
if (value.matches("\\d+")) {
return Integer.parseUnsignedInt(value);
} else {
return 0;
}
} 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<String> 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(

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@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,40 +52,145 @@ public class MultiPartArticleModel {
@Inject
private GlobalizationHelper globalizationHelper;
private boolean initialized;
@Inject
private PageUrlModel pageUrlModel;
private String title;
private String summary;
private List<String> sectionTitles;
private List<String> sectionLinks;
private String prevSectionLink;
private String nextSectionLink;
private int currentSection;
private String currentSectionTitle;
private String currentSectionText;
private List<MultiPartArticleSectionModel> 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<String> getSectionTitles() {
return getMultiPartArticle()
.getSections()
contentItemModel.init();
return Collections.unmodifiableList(sectionTitles);
}
@Transactional(Transactional.TxType.REQUIRED)
public List<String> 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() {
contentItemModel.init();
return currentSectionTitle;
}
@Transactional(Transactional.TxType.REQUIRED)
public String getCurrentSectionText() {
contentItemModel.init();
return currentSectionText;
}
@Transactional(Transactional.TxType.REQUIRED)
public List<MultiPartArticleSectionModel> getSections() {
contentItemModel.init();
return Collections.unmodifiableList(sections);
}
@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());
}
.collect(Collectors.toList())
)
.orElse(Collections.emptyList());
public String getCurrentSectionTitle() {
final int currentSection = readCurrentSection();
if (getMultiPartArticle().getSections().size() > currentSection) {
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.",
getMultiPartArticle().getDisplayName(),
mpa.getDisplayName(),
currentSection
)
)
@ -87,65 +198,45 @@ public class MultiPartArticleModel {
);
}
return globalizationHelper.getValueFromLocalizedString(
getMultiPartArticle().getSections().get(currentSection).getTitle()
);
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("");
}
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()
);
}
return globalizationHelper.getValueFromLocalizedString(
getMultiPartArticle().getSections().get(currentSection).getText()
);
}
public List<MultiPartArticleSectionModel> getSections() {
return getMultiPartArticle()
sectionLinks = buildSectionLinks();
sections = mpa
.getSections()
.stream()
.map(this::buildSectionModel)
.collect(Collectors.toList());
}
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()
);
}
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<String> buildSectionLinks() {
final int size = sectionTitles.size();
final List<String> 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
);
}
}

View File

@ -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())

View File

@ -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())
)
);
}

View File

@ -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
@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(
LocalDateTime.from(getNews().getReleaseDate().toInstant())
);
.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;
}
@Override
@Transactional(Transactional.TxType.REQUIRED)
public void init(final ContentItem contentItem) {
if (initialized) {
return;
}
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()
);
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;
}
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
@ -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("&", "?", "")
);
}
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
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);
}

View File

@ -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);
}

View File

@ -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.
@ -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<Category> 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<Category> result = retrieveCategory(
sectionIdentifier, context, categoryPath
);
if (result.isSuccessful()) {
final Category category = result.getResult();
final Optional<Categorization> 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";
}
return String.format(
"redirect:/%s/categorysystems/%s/categories/%s#objects-sections",
return setIndexObject(
result,
sectionIdentifier,
context,
categoryPath
categoryPath,
indexElementUuid
);
// if (result.isSuccessful()) {
// final Category category = result.getResult();
// final Optional<Categorization> 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<Category> result = retrieveCategory(
sectionIdentifier, context, "/"
);
if (result.isSuccessful()) {
final Category category = result.getResult();
categoryManager.resetIndexObject(category);
return String.format(
"redirect:/%s/categorysystems/%s/categories/",
sectionIdentifier,
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<Category> 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<Category> 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<Categorization> 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<Category> 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<Categorization> 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<Category> 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())
);

View File

@ -223,7 +223,7 @@ public class CategoryModel {
}
public void setObjects(final List<CategorizedObjectModel> objects) {
this.objects = new ArrayList<>();
this.objects = new ArrayList<>(objects);
}
public long getCategoryOrder() {

View File

@ -815,7 +815,7 @@ public class DocumentController {
}
return String.format(
"redirect:/%s/documentfolders/%s",
"redirect:/%s/documentfolders%s",
sectionIdentifier,
folderPath
);

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
@ -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())
);

View File

@ -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<String> 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()))

View File

@ -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" />
<span class="form-text text-muted"

View File

@ -154,18 +154,21 @@
pattern="[\\w-.]*"
required="true"
value="#{CategorySystemModel.selectedCategory.uniqueId}" />
<bootstrap:formCheck label="#{CmsAdminMessages['contentsecton.categorysystems.category.properties.edit.dialog.enabled.label']}"
<bootstrap:formCheck checked="#{CategorySystemModel.selectedCategory.enabled ? 'checked' : ''}"
label="#{CmsAdminMessages['contentsecton.categorysystems.category.properties.edit.dialog.enabled.label']}"
inputId="isEnabled"
name="isEnabled"
value="#{CategorySystemModel.selectedCategory.enabled}" />
<bootstrap:formCheck label="#{CmsAdminMessages['contentsecton.categorysystems.category.properties.edit.dialog.visible.label']}"
value="true" />
<bootstrap:formCheck checked="#{CategorySystemModel.selectedCategory.visible ? 'checked' : ''}"
label="#{CmsAdminMessages['contentsecton.categorysystems.category.properties.edit.dialog.visible.label']}"
inputId="isvisible"
name="isVisible"
value="#{CategorySystemModel.selectedCategory.visible}" />
<bootstrap:formCheck label="#{CmsAdminMessages['contentsecton.categorysystems.category.properties.edit.dialog.abstract_category.label']}"
value="true" />
<bootstrap:formCheck checked="#{CategorySystemModel.selectedCategory.abstractCategory ? 'checked' : ''}"
label="#{CmsAdminMessages['contentsecton.categorysystems.category.properties.edit.dialog.abstract_category.label']}"
inputId="isAbstract"
name="isAbstract"
value="#{CategorySystemModel.selectedCategory.abstractCategory}" />
value="true" />
</div>
<div class="modal-footer">
<button class="btn btn-danger"
@ -451,23 +454,15 @@
<th scope="col">
#{CmsAdminMessages['contentsections.categorystems.category.objects.name']}
</th>
</tr>
<tr>
<th scope="col">
#{CmsAdminMessages['contentsections.categorystems.category.objects.title']}
</th>
</tr>
<tr>
<th scope="col">
#{CmsAdminMessages['contentsections.categorystems.category.objects.type']}
</th>
</tr>
<tr>
<th scope="col">
#{CmsAdminMessages['contentsections.categorystems.category.objects.index']}
</th>
</tr>
<tr>
<th colspan="3">
#{CmsAdminMessages['contentsections.categorystems.category.objects.actions']}
</th>
@ -498,7 +493,7 @@
</td>
<td class="actions-order-col">
<c:if test="#{object.objectOrder != 1}">
<form action="#"
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/categorysystems/#{CategorySystemModel.selectedCategorySystem.context}/categories#{category.path}/@objects/#{object.objectId}/order"
method="post">
<input name="direction"
value="DECREASE"
@ -513,7 +508,7 @@
</td>
<td class="actions-order-col">
<c:if test="#{object.objectOrder lt CategorySystemModel.selectedCategory.objects.size()}">
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/categorysystems/#{CategorySystemModel.selectedCategorySystem.context}/@objects/#{object.objectId}/order"
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/categorysystems/#{CategorySystemModel.selectedCategorySystem.context}/categories#{category.path}/@objects/#{object.objectId}/order"
method="post">
<input name="direction"
value="INCREASE"
@ -529,18 +524,24 @@
<td class="actions-setindex-col">
<c:choose>
<c:when test="#{object.indexObject}">
<a class="btn btn-danger"
href="#{mvc.basePath}/#{ContentSectionModel.sectionName}/categorysystems/#{CategorySystemModel.selectedCategorySystem.context}/@index-element/#{object.objectUuid}">
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/categorysystems/#{CategorySystemModel.selectedCategorySystem.context}/categories#{CategorySystemModel.selectedCategory.path}/@index-element/reset"
method="post">
<button class="btn btn-danger"
type="submit">
<bootstrap:svgIcon icon="bookmark-x" />
<span>#{CmsAdminMessages['contentsections.categorystems.category.objects.index_object.reset']}</span>
</a>
</button>
</form>
</c:when>
<c:otherwise>
<a class="btn btn-info"
href="#{mvc.basePath}/#{ContentSectionModel.sectionName}/categorysystems/#{CategorySystemModel.selectedCategorySystem.context}/@index-element/reset">
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/categorysystems/#{CategorySystemModel.selectedCategorySystem.context}/categories#{CategorySystemModel.selectedCategory.path}/@index-element/#{object.objectUuid}"
method="post">
<button class="btn btn-info"
type="submit">
<bootstrap:svgIcon icon="bookmark-star" />
<span>#{CmsAdminMessages['contentsections.categorystems.category.objects.index_object.set']}</span>
</a>
</button>
</form>
</c:otherwise>
</c:choose>
</td>

View File

@ -999,7 +999,7 @@ pages.errors.primaryurl_null_or_empty=The primary URL of a page tree can't be nu
pages.errors.pages_instance_already_existing=There is already a page tree with the primary URL {1} for site {0}.
pages.errors.primaryurl_invalid=The primary URL "{0}" contains invalid characters. The primary URL of a page tree must only contain the letters a to z and A to Z, numbers, the dash ("-") and the underscore ("_").
pages.page.details.properties.error.key_empty=The name/key of a property can't be null or blank.
pages.page.details.properties.error.key_invalid=The name of a property must only contain the letters A to Z, a to z, numbers, the dash ("-") and the underscore ("_").
pages.page.details.properties.error.key_invalid=The name of a property must only contain the letters A to Z, a to z, numbers, the dash ("-"), the underscore ("_") and the dot (".").
pages.page.details.properties.error.value_empty=The value of a property must not be empty.
pages.page.details.dialog.title=Page Details
pages.page.details.dialog.close=Close

View File

@ -1000,7 +1000,7 @@ pages.errors.primaryurl_null_or_empty=Die prim\u00e4re URL eines Seitenbaumes da
pages.errors.pages_instance_already_existing=Es gibt bereits einen Seitenbaum mit der prim\u00e4ren URL {1} f\u00fcr die Site {0}.
pages.errors.primaryurl_invalid=Die prim\u00e4re URL "{0}" enth\u00e4lt ung\u00fcltige Zeichen. Die prim\u00e4re URL eines Seitenbaumes darf nur die Buchstaben a bis z, A bis Z, Ziffern, den Bindestrich ("-") und den Unterstrich ("_") enthalten.
pages.page.details.properties.error.key_empty=Der Name eines Eigenschaft darf nicht leer sein.
pages.page.details.properties.error.key_invalid=Der Name einer Eigenschaft darf nur die Buchstaben A bis Z, a bis z, Ziffern, den Bindestrich ("-") und den Unterstrich ("_") enthalten.
pages.page.details.properties.error.key_invalid=Der Name einer Eigenschaft darf nur die Buchstaben A bis Z, a bis z, Ziffern, den Bindestrich ("-"), den Unterstrich ("_") und den Punkt (".") enthalten.
pages.page.details.properties.error.value_empty=Der Wert einer Eigenschaft darf nicht leer sein.
pages.page.details.dialog.title=Details Seite
pages.page.details.dialog.close=Schlie\u00dfen

View File

@ -83,8 +83,10 @@ import javax.persistence.Table;
+ "AND c.type = :type"),
@NamedQuery(
name = "Categorization.findIndexObject",
query = "SELECT c.categorizedObject FROM Categorization c "
+ "WHERE c.category = :category "
query = "SELECT c.categorizedObject "
+ "FROM Categorization c "
+ "JOIN c.category a "
+ "WHERE a.uuid = :catuuid "
+ "AND c.indexObject = TRUE"),
@NamedQuery(
name = "Categorization.findIndexObjectCategorization",

View File

@ -45,6 +45,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* The {@code CategoryManager} provides several helper methods for managing
@ -58,7 +59,8 @@ public class CategoryManager implements Serializable {
private static final long serialVersionUID = -3354487547729008811L;
private static final Logger LOGGER = LogManager.getLogger(
CategoryManager.class);
CategoryManager.class
);
@Inject
private CcmObjectRepository ccmObjectRepo;
@ -982,16 +984,13 @@ public class CategoryManager implements Serializable {
*/
@Transactional(Transactional.TxType.REQUIRED)
public List<CcmObject> getIndexObject(final Category category) {
// if (hasIndexObject(category)) {
final TypedQuery<CcmObject> query = entityManager.createNamedQuery(
"Categorization.findIndexObject", CcmObject.class);
query.setParameter("category", category);
return query.getResultList();
// return Optional.of(query.getSingleResult());
// } else {
// return Optional.empty();
// }
final long start = System.currentTimeMillis();
return category
.getObjects()
.stream()
.filter(Categorization::isIndexObject)
.map(Categorization::getCategorizedObject)
.collect(Collectors.toList());
}
/**

View File

@ -113,7 +113,8 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
@SuppressWarnings("unchecked")
public EntityGraph<E> createEntityGraph(final String entityGraphName) {
return (EntityGraph<E>) entityManager.createEntityGraph(
entityGraphName);
entityGraphName
);
}
/**
@ -153,16 +154,18 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
*/
@Transactional(Transactional.TxType.REQUIRED)
public Optional<E> findById(final K entityId) {
Objects.requireNonNull(entityId);
return Optional.ofNullable(entityManager.find(getEntityClass(),
entityId));
return Optional.ofNullable(
entityManager.find(
getEntityClass(),
entityId
)
);
}
@Transactional(Transactional.TxType.REQUIRED)
public Optional<E> findById(final K entityId, final String entityGraphName) {
Objects.requireNonNull(entityId);
Objects.requireNonNull(entityGraphName);
@ -175,15 +178,18 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
@Transactional(Transactional.TxType.REQUIRED)
public Optional<E> findById(final K entityId,
final EntityGraph<E> entityGraph) {
Objects.requireNonNull(entityId);
Objects.requireNonNull(entityGraph);
final Map<String, Object> hints = new HashMap<>();
hints.put(FETCH_GRAPH_HINT_KEY, entityGraph);
return Optional.ofNullable(entityManager.find(getEntityClass(),
return Optional.ofNullable(
entityManager.find(
getEntityClass(),
entityId,
hints));
hints
)
);
}
/**
@ -206,16 +212,18 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
Objects.requireNonNull(entityId);
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
final CriteriaQuery<E> criteriaQuery = builder
.createQuery(getEntityClass());
final CriteriaQuery<E> criteriaQuery = builder.createQuery(
getEntityClass()
);
final Root<E> from = criteriaQuery.from(getEntityClass());
criteriaQuery.from(getEntityClass());
for (final String fetchJoin : fetchJoins) {
from.fetch(fetchJoin);
}
criteriaQuery
.where(builder.equal(from.get(getIdAttributeName()), entityId));
criteriaQuery.where(
builder.equal(from.get(getIdAttributeName()), entityId)
);
final TypedQuery<E> query = entityManager.createQuery(criteriaQuery);
try {
@ -229,10 +237,15 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
public E reload(final E entity, final String... fetchJoins) {
return findById(getIdOfEntity(entity), fetchJoins)
.orElseThrow(() -> new IllegalArgumentException(String
.format("No Entity of type \"%s\" with ID %s in the database.",
.orElseThrow(
() -> new IllegalArgumentException(
String.format(
"No Entity of type \"%s\" with ID %s in the database.",
getEntityClass().getName(),
Objects.toString(getIdOfEntity(entity)))));
Objects.toString(getIdOfEntity(entity))
)
)
);
}
/**
@ -273,7 +286,8 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
final CriteriaBuilder criteriaBuilder = entityManager
.getCriteriaBuilder();
final CriteriaQuery<E> criteriaQuery = criteriaBuilder.createQuery(
getEntityClass());
getEntityClass()
);
final Root<E> root = criteriaQuery.from(getEntityClass());
return criteriaQuery.select(root);
}
@ -303,8 +317,7 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
final String graphName) {
@SuppressWarnings("unchecked")
final EntityGraph<E> entityGraph = (EntityGraph< E>) entityManager
.getEntityGraph(
graphName);
.getEntityGraph(graphName);
return executeCriteriaQuery(criteriaQuery, entityGraph);
}
@ -334,7 +347,6 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
*/
@Transactional(Transactional.TxType.REQUIRED)
public void save(final E entity) {
Objects.requireNonNull(entity, "Can't save null.");
if (isNew(entity)) {
@ -363,12 +375,11 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
*/
@Transactional(Transactional.TxType.REQUIRED)
public void delete(final E entity) {
Objects.requireNonNull(
entity, "Can't delete a null entity."
);
Objects.requireNonNull(entity,
"Can't delete a null entity.");
//We need to make sure we use a none detached entity, therefore the merge
entityManager.remove(entityManager.merge(entity));
entityManager.remove(entity);
}
}

View File

@ -33,6 +33,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.libreccm.core.CoreConstants;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.sites.Site;
import org.libreccm.sites.SiteRepository;
import org.libreccm.theming.ThemeInfo;
@ -51,6 +52,9 @@ public class ThemesMvc {
public static final String DEFAULT_THEME_PARAM = "--DEFAULT--";
@Inject
private GlobalizationHelper globalizationHelper;
@Inject
private Models models;
@ -146,6 +150,10 @@ public class ThemesMvc {
models.put("application", application);
models.put("contextPath", servletContext.getContextPath());
models.put(
"negotiatedLocale",
globalizationHelper.getNegotiatedLocale().toString()
);
models.put("themeName", themeInfo.getName());
models.put("themeVersion", themeInfo.getVersion());
models.put(

View File

@ -6,6 +6,8 @@ import * as fsPromises from "fs/promises";
import * as mime from "mime-types";
import { fileTypeFromFile, FileTypeResult } from "file-type";
const AMBIGOUS_FILE_TYPES = ["application/xml"];
console.log("Static Theme Builder");
if (process.argv.length < 3) {
@ -143,7 +145,10 @@ function mapFileTypeResultToMimeType(
fileTypeResult: FileTypeResult | undefined
): string {
if (fileTypeResult) {
if (fileTypeResult.mime) {
if (
fileTypeResult.mime &&
!AMBIGOUS_FILE_TYPES.includes(fileTypeResult.mime)
) {
return fileTypeResult.mime;
} else {
const result = mime.lookup(name);