diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 8cefb3587..c23b63b01 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -20,11 +20,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- - name: Set up JDK 21
- uses: actions/setup-java@v4
+ - uses: actions/checkout@v5
+ - name: Set up JDK 25
+ uses: actions/setup-java@v5
with:
- java-version: '21'
+ java-version: '25-ea'
distribution: 'temurin'
cache: maven
- name: Build with Maven
diff --git a/.gitignore b/.gitignore
index 0b3f1f96a..6393afa36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,9 @@ test-server/hosts/marx-software
test-server/hosts/demo/modules_data/search-module/index
test-server/hosts/demo/modules_data/ui-module
test-server/hosts/demo/temp/
+test-server/hosts/demo/data/
+test-server/hosts/demo_spa/data/
+test-server/hosts/demo_de/data/
test-server/hosts/features/modules_data/
test-server/hosts/features/temp
test-server/hosts/features_de/modules_data/
@@ -65,3 +68,7 @@ test-server/modules/markedj-module
test-server/cms.pid
test-server/hosts/features/data/
.DS_Store
+test-server/mail-server/fake-smtp-server-2.4.2.jar
+modules/api-module/src/test/resources/site/data
+cms-content/src/test/resources/site/data
+integration-tests/hosts/test/data
\ No newline at end of file
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 7dbfe07fb..12fbe1e90 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -16,4 +16,4 @@
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
-distributionUrl=http://book-build:8081/nexus/content/groups/public/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a3aff0a4f..92c04fe26 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,11 @@ see wiki for more information: [wiki](https://github.com/thmarx/cms/wiki)
# changelog
-## 7.9.0
+## 8.0.0
+
+* **BREAKING CHANGE** Sorted sections now use the _layout.order_ meta attribute for sorting
+* **BREAKING CHANGE** ShortCodes are renamed to tags
+* **BREAKING CHANGE** The default value for published switched from _true_ to _false_
* **BUGFIX** TemplateEngine should use cache only if activated [456](https://github.com/CondationCMS/cms-server/issues/456)
* **MAINTENANCE** multiple dependencies updated
@@ -17,9 +21,12 @@ see wiki for more information: [wiki](https://github.com/thmarx/cms/wiki)
* **FEATURE** Aliases for content [442](https://github.com/CondationCMS/cms-server/issues/442)
* **FEATURE** Add redirect support for aliases [454](https://github.com/CondationCMS/cms-server/issues/454)
* **FEATURE** Signature for modules and themes [471](https://github.com/CondationCMS/cms-server/issues/471)
-* **FEATURE** Switch password has to secure algorithm [472](https://github.com/CondationCMS/cms-server/issues/472)
+* **FEATURE** Switch password hash to secure algorithm [472](https://github.com/CondationCMS/cms-server/issues/472)
* **FEATURE** Simple http api for basic use cases [479](https://github.com/CondationCMS/cms-server/issues/479)
* **FEATURE** Add spa mode for sites [476](https://github.com/CondationCMS/cms-server/issues/476)
+* **FEATURE** Custom repository urls for modules, extensions and themes [466](https://github.com/CondationCMS/cms-server/issues/466)
+* **FEATURE** UI to manage content [PR-446](https://github.com/CondationCMS/cms-server/pull/446)
+* **FEATURE** Introduce server modules [503](https://github.com/CondationCMS/cms-server/issues/503)
### Developer experience
diff --git a/cms-api/pom.xml b/cms-api/pom.xml
index 4b344617e..d0c3846cc 100644
--- a/cms-api/pom.xml
+++ b/cms-api/pom.xml
@@ -4,11 +4,24 @@
com.condation.cms
cms-parent
- 7.8.0
+ 8.0.0
cms-api
jar
-
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.14.0
+
+
+ --enable-preview
+
+
+
+
+
org.projectlombok
@@ -27,6 +40,10 @@
org.yaml
snakeyaml
+
+ com.google.code.gson
+ gson
+
org.eclipse.jetty
jetty-http
@@ -40,9 +57,19 @@
org.slf4j
slf4j-api
+
com.google.inject
guice
+ classes
+
+
+ org.ow2.asm
+ asm
+ 9.8
+
+ 25
+
diff --git a/cms-api/src/main/java/com/condation/cms/api/Constants.java b/cms-api/src/main/java/com/condation/cms/api/Constants.java
index 0ffb647d3..c8d2c4ef9 100644
--- a/cms-api/src/main/java/com/condation/cms/api/Constants.java
+++ b/cms-api/src/main/java/com/condation/cms/api/Constants.java
@@ -56,6 +56,8 @@ public static class MetaFields {
public static final String MENU_POSITION = "position";
public static final String MENU_TITLE = "title";
+ public static final String LAYOUT_ORDER = "layout.order";
+
public static final String REDIRECT_STATUS = "redirect.status";
public static final String REDIRECT_LOCATION = "redirect.location";
@@ -65,6 +67,8 @@ public static class MetaFields {
public static final String ALIASES = "aliases";
public static final String ALIASES_REDIRECT = "aliases_redirect";
+
+ public static final String TRANSLATIONS = "translations";
}
public static class Folders {
@@ -97,13 +101,13 @@ public static class ContentTypes {
return Pattern.compile("%s\\.(?[a-zA-Z0-9-]+)\\.md".formatted(Pattern.quote(fileName)));
};
- public static final Pattern SECTION_ORDERED_PATTERN = Pattern.compile("[\\w-]+\\.(?[a-zA-Z0-9-]+)\\.(?\\d+)\\.md");
+ public static final Pattern SECTION_NAMED_PATTERN = Pattern.compile("[\\w-]+\\.(?[a-zA-Z0-9-]+)\\.(?[\\w-]+)\\.md");
- public static final Function SECTION_ORDERED_OF_PATTERN = (fileName) -> {
- return Pattern.compile("%s\\.[a-zA-Z0-9-]+\\.[0-9]+\\.md".formatted(Pattern.quote(fileName)));
+ public static final Function SECTION_NAMED_OF_PATTERN = (fileName) -> {
+ return Pattern.compile("%s\\.[a-zA-Z0-9-]+\\.[a-zA-Z0-9-]+\\.md".formatted(Pattern.quote(fileName)));
};
- public static final int DEFAULT_SECTION_ORDERED_INDEX = 0;
+ public static final int DEFAULT_SECTION_LAYOUT_ORDER = 0;
public static final double DEFAULT_MENU_POSITION = 1000f;
public static final boolean DEFAULT_MENU_VISIBILITY = true;
public static final int DEFAULT_EXCERPT_LENGTH = 200;
@@ -111,18 +115,24 @@ public static class ContentTypes {
public static final int DEFAULT_PAGE_SIZE = 5;
public static final String DEFAULT_CONTENT_TYPE = ContentTypes.HTML;
- public static final List DEFAULT_CONTENT_PIPELINE = List.of("markdown", "shortcode");
+ public static final List DEFAULT_CONTENT_PIPELINE = List.of("markdown", "tags");
public static final int DEFAULT_REDIRECT_STATUS = 301;
public static final String DEFAULT_CACHE_ENGINE = "local";
public static final boolean DEFAULT_CONTENT_CACHE_ENABLED = false;
- public static final String DEFAULT_MODULE_NAMESPACE = "mod";
+ public static class TemplateNamespaces {
+ public static final String DEFAULT_MODULE_NAMESPACE = "mod";
+ public static final String CMS = "cms";
+ public static final String NODE = "node";
+ public static final String SITE = "site";
+ }
public static class Taxonomy {
public static final String DEFAULT_TEMPLATE = "taxonomy.html";
public static final String DEFAULT_SINGLE_TEMPLATE = "taxonomy.single.html";
}
+ public static final String REQUEST_CONTEXT_ATTRIBUTE_NAME = "_requestContext";
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ServerProperties.java b/cms-api/src/main/java/com/condation/cms/api/ServerProperties.java
index 7bbe13498..697dfd580 100644
--- a/cms-api/src/main/java/com/condation/cms/api/ServerProperties.java
+++ b/cms-api/src/main/java/com/condation/cms/api/ServerProperties.java
@@ -51,4 +51,8 @@ public interface ServerProperties {
public List themeRepositories ();
public List extensionRepositories ();
+
+ String secret();
+
+ public List activeModules();
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/SiteProperties.java b/cms-api/src/main/java/com/condation/cms/api/SiteProperties.java
index a66c8d807..706823f96 100644
--- a/cms-api/src/main/java/com/condation/cms/api/SiteProperties.java
+++ b/cms-api/src/main/java/com/condation/cms/api/SiteProperties.java
@@ -38,6 +38,8 @@ public interface SiteProperties {
public String contextPath ();
+ public String baseUrl ();
+
public String id ();
public Object get (String field);
@@ -67,4 +69,8 @@ public interface SiteProperties {
public default boolean spaEnabled () {
return false;
}
+
+ public UIProperties ui();
+
+ public TranslationProperties translation ();
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/TranslationProperties.java b/cms-api/src/main/java/com/condation/cms/api/TranslationProperties.java
new file mode 100644
index 000000000..e3d55bbbb
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/TranslationProperties.java
@@ -0,0 +1,39 @@
+package com.condation.cms.api;
+
+import java.util.List;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+/**
+ *
+ * @author thorstenmarx
+ */
+public interface TranslationProperties {
+ boolean isEnabled();
+
+ List getLanguages();
+
+ List getMapping ();
+
+ public static record Mapping (String site, String language){};
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/UIProperties.java b/cms-api/src/main/java/com/condation/cms/api/UIProperties.java
new file mode 100644
index 000000000..fc1de4287
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/UIProperties.java
@@ -0,0 +1,33 @@
+package com.condation.cms.api;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+/**
+ *
+ * @author thorstenmarx
+ */
+public interface UIProperties {
+ boolean force2fa();
+
+ boolean managerEnabled ();
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/annotations/FeatureScope.java b/cms-api/src/main/java/com/condation/cms/api/annotations/FeatureScope.java
index 650ab8f2f..798a703bb 100644
--- a/cms-api/src/main/java/com/condation/cms/api/annotations/FeatureScope.java
+++ b/cms-api/src/main/java/com/condation/cms/api/annotations/FeatureScope.java
@@ -43,7 +43,8 @@
public enum Scope {
REQUEST,
GLOBAL,
- MODULE
+ MODULE,
+ SERVER
}
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/annotations/ShortCode.java b/cms-api/src/main/java/com/condation/cms/api/annotations/Tag.java
similarity index 97%
rename from cms-api/src/main/java/com/condation/cms/api/annotations/ShortCode.java
rename to cms-api/src/main/java/com/condation/cms/api/annotations/Tag.java
index 9cef1bf04..7b39f8a24 100644
--- a/cms-api/src/main/java/com/condation/cms/api/annotations/ShortCode.java
+++ b/cms-api/src/main/java/com/condation/cms/api/annotations/Tag.java
@@ -33,6 +33,6 @@
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
-public @interface ShortCode {
+public @interface Tag {
String value ();
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/annotations/TemplateFunction.java b/cms-api/src/main/java/com/condation/cms/api/annotations/TemplateFunction.java
new file mode 100644
index 000000000..64180d58b
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/annotations/TemplateFunction.java
@@ -0,0 +1,38 @@
+package com.condation.cms.api.annotations;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface TemplateFunction {
+ String value ();
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/auth/Permissions.java b/cms-api/src/main/java/com/condation/cms/api/auth/Permissions.java
new file mode 100644
index 000000000..7a54fd011
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/auth/Permissions.java
@@ -0,0 +1,32 @@
+package com.condation.cms.api.auth;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+/**
+ *
+ * @author thmar
+ */
+public class Permissions {
+ public static final String CONTENT_EDIT = "content.edit";
+ public static final String CACHE_INVALIDATE = "cache.invalidate";
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/cache/CacheProvider.java b/cms-api/src/main/java/com/condation/cms/api/cache/CacheProvider.java
index c1de8888c..34afedd14 100644
--- a/cms-api/src/main/java/com/condation/cms/api/cache/CacheProvider.java
+++ b/cms-api/src/main/java/com/condation/cms/api/cache/CacheProvider.java
@@ -23,7 +23,6 @@
*/
-import java.io.Serializable;
import java.util.function.Function;
/**
diff --git a/cms-core/src/main/java/com/condation/cms/core/content/MapAccess.java b/cms-api/src/main/java/com/condation/cms/api/content/MapAccess.java
similarity index 95%
rename from cms-core/src/main/java/com/condation/cms/core/content/MapAccess.java
rename to cms-api/src/main/java/com/condation/cms/api/content/MapAccess.java
index dcd0423d8..04e2e28a5 100644
--- a/cms-core/src/main/java/com/condation/cms/core/content/MapAccess.java
+++ b/cms-api/src/main/java/com/condation/cms/api/content/MapAccess.java
@@ -1,4 +1,4 @@
-package com.condation.cms.core.content;
+package com.condation.cms.api.content;
/*-
* #%L
@@ -35,6 +35,10 @@ public class MapAccess implements Map {
private final Map wrapped;
+ public static MapAccess of (Map map) {
+ return new MapAccess(map);
+ }
+
@Override
public int size() {
return wrapped.size();
diff --git a/cms-api/src/main/java/com/condation/cms/api/db/ContentNode.java b/cms-api/src/main/java/com/condation/cms/api/db/ContentNode.java
index ad9f6b79a..6af063ef3 100644
--- a/cms-api/src/main/java/com/condation/cms/api/db/ContentNode.java
+++ b/cms-api/src/main/java/com/condation/cms/api/db/ContentNode.java
@@ -26,7 +26,8 @@
import com.condation.cms.api.feature.features.IsPreviewFeature;
import com.condation.cms.api.feature.features.SitePropertiesFeature;
import com.condation.cms.api.request.RequestContext;
-import com.condation.cms.api.request.ThreadLocalRequestContext;
+import com.condation.cms.api.request.RequestContextScope;
+import com.condation.cms.api.utils.DateRange;
import com.condation.cms.api.utils.MapUtil;
import com.condation.cms.api.utils.SectionUtil;
import java.io.Serializable;
@@ -69,8 +70,8 @@ public boolean isView () {
public String contentType() {
String defaultContentType = Constants.DEFAULT_CONTENT_TYPE;
- if (ThreadLocalRequestContext.REQUEST_CONTEXT.get() != null) {
- RequestContext requestContext = ThreadLocalRequestContext.REQUEST_CONTEXT.get();
+ if (RequestContextScope.REQUEST_CONTEXT.isBound()) {
+ RequestContext requestContext = RequestContextScope.REQUEST_CONTEXT.get();
defaultContentType = requestContext.get(SitePropertiesFeature.class).siteProperties().defaultContentType();
}
return (String) ((Map) data
@@ -100,29 +101,28 @@ public boolean isHidden() {
}
public boolean isDraft() {
- return !((boolean) data().getOrDefault(Constants.MetaFields.PUBLISHED, true));
+ return !((boolean) data().getOrDefault(Constants.MetaFields.PUBLISHED, false));
+ }
+
+ public boolean isParentPathHidden () {
+ return uri().startsWith(".") || uri().contains("/.");
}
- public boolean isPublished() {
- if (ThreadLocalRequestContext.REQUEST_CONTEXT.get() != null
- && ThreadLocalRequestContext.REQUEST_CONTEXT.get().has(IsPreviewFeature.class)) {
+ public boolean isVisible() {
+ if (RequestContextScope.REQUEST_CONTEXT.isBound()
+ && RequestContextScope.REQUEST_CONTEXT.get().has(IsPreviewFeature.class)) {
return true;
}
-
+
if (isDraft()) {
return false;
}
+
var publish_date = (Date) data.getOrDefault(Constants.MetaFields.PUBLISH_DATE, Date.from(Instant.now()));
- var now = Date.from(Instant.now());
- if (!(publish_date.before(now) || publish_date.equals(now))) {
- return false;
- }
+
var unpublish_date = (Date) data.getOrDefault(Constants.MetaFields.UNPUBLISH_DATE, null);
- if (unpublish_date != null
- && (unpublish_date.before(now) || unpublish_date.equals(now))) {
- return false;
- }
- return true;
+
+ return DateRange.isNowWithin(publish_date, unpublish_date);
}
public boolean isSection() {
diff --git a/cms-api/src/main/java/com/condation/cms/api/db/ContentQuery.java b/cms-api/src/main/java/com/condation/cms/api/db/ContentQuery.java
index b541a85cd..b57376f42 100644
--- a/cms-api/src/main/java/com/condation/cms/api/db/ContentQuery.java
+++ b/cms-api/src/main/java/com/condation/cms/api/db/ContentQuery.java
@@ -66,6 +66,8 @@ public interface ContentQuery {
ContentQuery whereNotIn(final String field, final List value);
ContentQuery whereExists(final String field);
+
+ ContentQuery expression(final String expressions);
public static interface Sort {
public ContentQuery asc();
diff --git a/cms-api/src/main/java/com/condation/cms/api/db/cms/NIOReadOnlyFile.java b/cms-api/src/main/java/com/condation/cms/api/db/cms/NIOReadOnlyFile.java
index 7ac2e682a..5c3042aaa 100644
--- a/cms-api/src/main/java/com/condation/cms/api/db/cms/NIOReadOnlyFile.java
+++ b/cms-api/src/main/java/com/condation/cms/api/db/cms/NIOReadOnlyFile.java
@@ -46,6 +46,16 @@ public class NIOReadOnlyFile implements ReadOnlyFile {
protected final Path file;
private final Path basePath;
+ @Override
+ public String uri () {
+ return PathUtil.toURL(file, basePath);
+ }
+
+ @Override
+ public String relativePath() {
+ return PathUtil.toRelativeFile(file, basePath);
+ }
+
@Override
public boolean exists() {
return Files.exists(file);
@@ -116,7 +126,9 @@ public ReadOnlyFile getParent() {
@Override
public List children() throws IOException {
- return Files.list(file).map(child -> new NIOReadOnlyFile(child, basePath)).map(ReadOnlyFile.class::cast).toList();
+ try (var childrenStream = Files.list(file)) {
+ return childrenStream.map(child -> new NIOReadOnlyFile(child, basePath)).map(ReadOnlyFile.class::cast).toList();
+ }
}
@Override
diff --git a/cms-api/src/main/java/com/condation/cms/api/db/cms/ReadOnlyFile.java b/cms-api/src/main/java/com/condation/cms/api/db/cms/ReadOnlyFile.java
index b5e17c4d8..371eb0366 100644
--- a/cms-api/src/main/java/com/condation/cms/api/db/cms/ReadOnlyFile.java
+++ b/cms-api/src/main/java/com/condation/cms/api/db/cms/ReadOnlyFile.java
@@ -34,6 +34,10 @@
public interface ReadOnlyFile {
boolean exists ();
+ String uri ();
+
+ String relativePath();
+
ReadOnlyFile resolve (String uri);
String getContent () throws IOException;
diff --git a/cms-api/src/main/java/com/condation/cms/api/eventbus/EventListener.java b/cms-api/src/main/java/com/condation/cms/api/eventbus/EventListener.java
index fe5ee4843..cf797d475 100644
--- a/cms-api/src/main/java/com/condation/cms/api/eventbus/EventListener.java
+++ b/cms-api/src/main/java/com/condation/cms/api/eventbus/EventListener.java
@@ -26,6 +26,7 @@
/**
*
* @author t.marx
+ * @param
*/
public interface EventListener {
diff --git a/cms-api/src/main/java/com/condation/cms/api/request/ThreadLocalRequestContext.java b/cms-api/src/main/java/com/condation/cms/api/eventbus/events/InvalidateMediaCache.java
similarity index 80%
rename from cms-api/src/main/java/com/condation/cms/api/request/ThreadLocalRequestContext.java
rename to cms-api/src/main/java/com/condation/cms/api/eventbus/events/InvalidateMediaCache.java
index cd75d22e4..7230ad0a9 100644
--- a/cms-api/src/main/java/com/condation/cms/api/request/ThreadLocalRequestContext.java
+++ b/cms-api/src/main/java/com/condation/cms/api/eventbus/events/InvalidateMediaCache.java
@@ -1,4 +1,4 @@
-package com.condation.cms.api.request;
+package com.condation.cms.api.eventbus.events;
/*-
* #%L
@@ -23,10 +23,11 @@
*/
+import com.condation.cms.api.eventbus.Event;
+import java.nio.file.Path;
+
/**
*
* @author t.marx
*/
-public class ThreadLocalRequestContext {
- public static ThreadLocal REQUEST_CONTEXT = new ThreadLocal<>();
-}
+public record InvalidateMediaCache (Path mediaPath) implements Event {}
diff --git a/cms-api/src/main/java/com/condation/cms/api/eventbus/events/ReIndexContentMetaDataEvent.java b/cms-api/src/main/java/com/condation/cms/api/eventbus/events/ReIndexContentMetaDataEvent.java
index 7520131ac..2ebea142e 100644
--- a/cms-api/src/main/java/com/condation/cms/api/eventbus/events/ReIndexContentMetaDataEvent.java
+++ b/cms-api/src/main/java/com/condation/cms/api/eventbus/events/ReIndexContentMetaDataEvent.java
@@ -29,4 +29,8 @@
*
* @author t.marx
*/
-public record ReIndexContentMetaDataEvent () implements Event {}
+public record ReIndexContentMetaDataEvent (String uri) implements Event {
+ public ReIndexContentMetaDataEvent () {
+ this(null);
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/exceptions/AnnotationExecutionException.java b/cms-api/src/main/java/com/condation/cms/api/exceptions/AnnotationExecutionException.java
new file mode 100644
index 000000000..9e3662eb5
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/exceptions/AnnotationExecutionException.java
@@ -0,0 +1,46 @@
+package com.condation.cms.api.exceptions;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2024 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+
+/**
+ *
+ * @author t.marx
+ */
+public class AnnotationExecutionException extends RuntimeException {
+
+ /**
+ * Creates a new instance of AccessNotAllowedException without detail message.
+ */
+ public AnnotationExecutionException() {
+ }
+
+ /**
+ * Constructs an instance of AccessNotAllowedException with the specified detail message.
+ *
+ * @param msg the detail message.
+ */
+ public AnnotationExecutionException(String msg) {
+ super(msg);
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/AbstractExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/AbstractExtensionPoint.java
index 3ef887553..1ae62c05c 100644
--- a/cms-api/src/main/java/com/condation/cms/api/extensions/AbstractExtensionPoint.java
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/AbstractExtensionPoint.java
@@ -23,8 +23,8 @@
*/
-import com.condation.cms.api.module.CMSModuleContext;
-import com.condation.cms.api.module.CMSRequestContext;
+import com.condation.cms.api.module.SiteModuleContext;
+import com.condation.cms.api.module.SiteRequestContext;
import com.condation.modules.api.ExtensionPoint;
import com.condation.modules.api.ModuleConfiguration;
import lombok.Getter;
@@ -33,13 +33,13 @@
*
* @author t.marx
*/
-public abstract class AbstractExtensionPoint implements ExtensionPoint {
+public abstract class AbstractExtensionPoint implements ExtensionPoint {
@Getter
protected ModuleConfiguration moduleConfiguration;
@Getter
- protected CMSModuleContext context;
+ protected SiteModuleContext context;
@Getter
- protected CMSRequestContext requestContext;
+ protected SiteRequestContext requestContext;
@Override
public void setConfiguration(ModuleConfiguration configuration) {
@@ -47,11 +47,11 @@ public void setConfiguration(ModuleConfiguration configuration) {
}
@Override
- public void setContext(CMSModuleContext context) {
+ public void setContext(SiteModuleContext context) {
this.context = context;
}
@Override
- public void setRequestContext(CMSRequestContext requestContext) {
+ public void setRequestContext(SiteRequestContext requestContext) {
this.requestContext = requestContext;
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/BackupFilePostProcessingExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/BackupFilePostProcessingExtensionPoint.java
new file mode 100644
index 000000000..0138f4cf0
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/BackupFilePostProcessingExtensionPoint.java
@@ -0,0 +1,38 @@
+package com.condation.cms.api.extensions;
+
+import java.nio.file.Path;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2024 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+
+
+
+/**
+ *
+ * @author t.marx
+ */
+public abstract class BackupFilePostProcessingExtensionPoint extends AbstractExtensionPoint {
+
+ public abstract void postProcess (Path backupFile);
+
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/HookSystemRegisterExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/HookSystemRegisterExtensionPoint.java
index 7d875ad52..be18db96e 100644
--- a/cms-api/src/main/java/com/condation/cms/api/extensions/HookSystemRegisterExtensionPoint.java
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/HookSystemRegisterExtensionPoint.java
@@ -30,6 +30,5 @@
*/
public abstract class HookSystemRegisterExtensionPoint extends AbstractExtensionPoint{
- public abstract void register (final HookSystem hookSystem);
-
+ public void register (final HookSystem hookSystem) {}
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/Mapping.java b/cms-api/src/main/java/com/condation/cms/api/extensions/Mapping.java
index 7363eb7cc..df4045d98 100644
--- a/cms-api/src/main/java/com/condation/cms/api/extensions/Mapping.java
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/Mapping.java
@@ -36,21 +36,23 @@
*/
public class Mapping {
- private Map handlerMapping;
+ private List handlers;
+
+ private static record PathHandler (PathSpec pathSpec, HttpHandler httpHandler){}
public Mapping () {
- handlerMapping = new HashMap<>();
+ handlers = new ArrayList<>();
}
public void add (PathSpec pathSpec, HttpHandler handler) {
- handlerMapping.put(pathSpec, handler);
+ handlers.add(new PathHandler(pathSpec, handler));
}
public Optional getMatchingHandler (String uri) {
- return handlerMapping.entrySet().stream().filter(entry -> entry.getKey().matches(uri)).map(entry -> entry.getValue()).findFirst();
+ return handlers.stream().filter(handler -> handler.pathSpec.matches(uri)).map(PathHandler::httpHandler).findFirst();
}
public List getHandlers () {
- return new ArrayList<>(handlerMapping.values());
+ return handlers.stream().map(PathHandler::httpHandler).toList();
}
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/RegisterShortCodesExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/RegisterTagsExtensionPoint.java
similarity index 84%
rename from cms-api/src/main/java/com/condation/cms/api/extensions/RegisterShortCodesExtensionPoint.java
rename to cms-api/src/main/java/com/condation/cms/api/extensions/RegisterTagsExtensionPoint.java
index a5adadf36..b719c51fa 100644
--- a/cms-api/src/main/java/com/condation/cms/api/extensions/RegisterShortCodesExtensionPoint.java
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/RegisterTagsExtensionPoint.java
@@ -33,13 +33,13 @@
*
* @author t.marx
*/
-public abstract class RegisterShortCodesExtensionPoint extends AbstractExtensionPoint {
+public abstract class RegisterTagsExtensionPoint extends AbstractExtensionPoint {
- public Map> shortCodes () {
+ public Map> tags () {
return Collections.emptyMap();
}
- public List shortCodeDefinitions () {
+ public List tagDefinitions () {
return Collections.emptyList();
}
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/HttpRouteExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/RegisterTemplateFunctionExtensionPoint.java
similarity index 65%
rename from cms-api/src/main/java/com/condation/cms/api/extensions/HttpRouteExtensionPoint.java
rename to cms-api/src/main/java/com/condation/cms/api/extensions/RegisterTemplateFunctionExtensionPoint.java
index 48c8095c2..93ae9d614 100644
--- a/cms-api/src/main/java/com/condation/cms/api/extensions/HttpRouteExtensionPoint.java
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/RegisterTemplateFunctionExtensionPoint.java
@@ -22,18 +22,24 @@
* #L%
*/
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Response;
-import org.eclipse.jetty.util.Callback;
+import com.condation.cms.api.model.Parameter;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
/**
*
* @author t.marx
*/
-@Deprecated(since = "7.3.0", forRemoval = true)
-public abstract class HttpRouteExtensionPoint extends AbstractExtensionPoint {
- abstract public String getRoute ();
+public abstract class RegisterTemplateFunctionExtensionPoint extends AbstractExtensionPoint {
- abstract public void handle (Request request, Response response, Callback callback);
+ public Map> functions () {
+ return Collections.emptyMap();
+ }
+
+ public List functionDefinitions () {
+ return Collections.emptyList();
+ }
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/TemplateModelExtendingExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/TemplateModelExtendingExtensionPoint.java
index 8ea1dd4fe..917e7f122 100644
--- a/cms-api/src/main/java/com/condation/cms/api/extensions/TemplateModelExtendingExtensionPoint.java
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/TemplateModelExtendingExtensionPoint.java
@@ -26,25 +26,13 @@
* #L%
*/
-import com.condation.cms.api.template.TemplateEngine;
public abstract class TemplateModelExtendingExtensionPoint extends AbstractExtensionPoint{
- /**
- * deprecated: use @TemplateModelExtendingExtensionPoint.getModel instead
- * @param model
- */
- @Deprecated(since = "7.3.0", forRemoval = true)
- public abstract void extendModel (TemplateEngine.Model model);
-
- public Map getModel () {
- TemplateEngine.Model model = new TemplateEngine.Model(null, null, null);
- extendModel(model);
- return model.values;
- }
+ public abstract Map getModel ();
public String getNamespace () {
- return Constants.DEFAULT_MODULE_NAMESPACE;
+ return Constants.TemplateNamespaces.DEFAULT_MODULE_NAMESPACE;
}
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/http/routes/RoutesExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/http/routes/RoutesExtensionPoint.java
index 496cd7da4..e28b393a8 100644
--- a/cms-api/src/main/java/com/condation/cms/api/extensions/http/routes/RoutesExtensionPoint.java
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/http/routes/RoutesExtensionPoint.java
@@ -23,12 +23,10 @@
*/
import com.condation.cms.api.extensions.AbstractExtensionPoint;
-import java.util.List;
/**
*
* @author thorstenmarx
*/
public abstract class RoutesExtensionPoint extends AbstractExtensionPoint {
- abstract public List getRouteDefinitions ();
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/http/routes/RoutesManager.java b/cms-api/src/main/java/com/condation/cms/api/extensions/http/routes/RoutesManager.java
index d3ef35d61..3429b9b43 100644
--- a/cms-api/src/main/java/com/condation/cms/api/extensions/http/routes/RoutesManager.java
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/http/routes/RoutesManager.java
@@ -21,14 +21,13 @@
* .
* #L%
*/
-
import com.condation.cms.api.annotations.Route;
import com.condation.cms.api.extensions.http.HttpHandler;
import com.condation.cms.api.extensions.http.PathMapping;
+import com.condation.cms.api.utils.AnnotationsUtil;
import org.eclipse.jetty.http.pathmap.PathSpec;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
+import java.util.List;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.server.Request;
@@ -38,45 +37,31 @@
@Slf4j
public class RoutesManager {
- private final PathMapping pathMapping = new PathMapping();
+ private final PathMapping pathMapping = new PathMapping();
- public Optional findFirst (String path, String method) {
+ public Optional findFirst(String path, String method) {
return pathMapping.getMatchingHandler(path, method);
}
-
- public void register(Object controller) {
- Class> clazz = controller.getClass();
-
- for (Method method : clazz.getDeclaredMethods()) {
- Route route = method.getAnnotation(Route.class);
- if (route != null && isValidHandlerMethod(method)) {
- method.setAccessible(true);
- PathSpec pathSpec = PathSpec.from(route.value());
+ public void register(Object controller) {
- HttpHandler handler = (request, response, callback) -> {
- try {
- return (Boolean) method.invoke(controller, request, response, callback);
- } catch (Exception e) {
- log.error("", e);
- response.setStatus(500);
- return true;
- }
- };
- pathMapping.add(pathSpec, route.method(), handler);
- }
- }
- }
+ AnnotationsUtil.process(
+ controller,
+ Route.class,
+ List.of(Request.class, Response.class, Callback.class), boolean.class)
+ .forEach(cmsAnnotation -> {
+ PathSpec pathSpec = PathSpec.from(cmsAnnotation.annotation().value());
- private boolean isValidHandlerMethod(Method method) {
- // Muss "boolean handle(Request, Response, Callback)" sein
- if (!Modifier.isPublic(method.getModifiers())) return false;
- if (!method.getReturnType().equals(boolean.class)) return false;
-
- Class>[] params = method.getParameterTypes();
- return params.length == 3 &&
- Request.class.isAssignableFrom(params[0]) &&
- Response.class.isAssignableFrom(params[1]) &&
- Callback.class.isAssignableFrom(params[2]);
- }
+ HttpHandler handler = (request, response, callback) -> {
+ try {
+ return cmsAnnotation.invoke(request, response, callback);
+ } catch (Exception e) {
+ log.error("", e);
+ response.setStatus(500);
+ return true;
+ }
+ };
+ pathMapping.add(pathSpec, cmsAnnotation.annotation().method(), handler);
+ });
+ }
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/server/ServerExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/server/ServerExtensionPoint.java
new file mode 100644
index 000000000..530c3505a
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/server/ServerExtensionPoint.java
@@ -0,0 +1,61 @@
+package com.condation.cms.api.extensions.server;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.api.module.ServerModuleContext;
+import com.condation.cms.api.module.ServerRequestContext;
+import com.condation.modules.api.ExtensionPoint;
+import com.condation.modules.api.ModuleConfiguration;
+import lombok.Getter;
+
+/**
+ *
+ * @author thmar
+ */
+public abstract class ServerExtensionPoint implements ExtensionPoint {
+ @Getter
+ protected ModuleConfiguration moduleConfiguration;
+ @Getter
+ protected ServerModuleContext context;
+ @Getter
+ protected ServerRequestContext requestContext;
+
+ @Override
+ public void setConfiguration(ModuleConfiguration configuration) {
+ this.moduleConfiguration = configuration;
+ }
+
+ @Override
+ public void setContext(ServerModuleContext context) {
+ this.context = context;
+ }
+
+ @Override
+ public void setRequestContext(ServerRequestContext requestContext) {
+ this.requestContext = requestContext;
+ }
+
+ @Override
+ public void init() {
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/server/ServerHookSystemRegisterExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/server/ServerHookSystemRegisterExtensionPoint.java
new file mode 100644
index 000000000..e27148fd5
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/server/ServerHookSystemRegisterExtensionPoint.java
@@ -0,0 +1,34 @@
+package com.condation.cms.api.extensions.server;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2024 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.api.hooks.HookSystem;
+
+/**
+ * ExtensionPoint for modules to register hooks.
+ *
+ */
+public abstract class ServerHookSystemRegisterExtensionPoint extends ServerExtensionPoint {
+
+ public void register (final HookSystem hookSystem) {}
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/extensions/server/ServerLifecycleExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/extensions/server/ServerLifecycleExtensionPoint.java
new file mode 100644
index 000000000..a5d3fb0f9
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/extensions/server/ServerLifecycleExtensionPoint.java
@@ -0,0 +1,35 @@
+package com.condation.cms.api.extensions.server;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+/**
+ *
+ * @author thmar
+ */
+public abstract class ServerLifecycleExtensionPoint extends ServerExtensionPoint {
+
+ public abstract void started ();
+
+ public abstract void stopped ();
+
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/feature/features/HookSystemFeature.java b/cms-api/src/main/java/com/condation/cms/api/feature/features/HookSystemFeature.java
index c15bfcc5c..f6d99fc80 100644
--- a/cms-api/src/main/java/com/condation/cms/api/feature/features/HookSystemFeature.java
+++ b/cms-api/src/main/java/com/condation/cms/api/feature/features/HookSystemFeature.java
@@ -33,5 +33,4 @@
*/
@FeatureScope({FeatureScope.Scope.REQUEST})
public record HookSystemFeature(HookSystem hookSystem) implements Feature {
-
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/feature/features/InjectorFeature.java b/cms-api/src/main/java/com/condation/cms/api/feature/features/InjectorFeature.java
index 346f4b225..4ea7a181b 100644
--- a/cms-api/src/main/java/com/condation/cms/api/feature/features/InjectorFeature.java
+++ b/cms-api/src/main/java/com/condation/cms/api/feature/features/InjectorFeature.java
@@ -31,7 +31,7 @@
*
* @author t.marx
*/
-@FeatureScope({FeatureScope.Scope.REQUEST})
+@FeatureScope({FeatureScope.Scope.REQUEST, FeatureScope.Scope.MODULE, FeatureScope.Scope.SERVER})
public record InjectorFeature(Injector injector) implements Feature {
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/feature/features/IsPreviewFeature.java b/cms-api/src/main/java/com/condation/cms/api/feature/features/IsPreviewFeature.java
index 87bce7454..8d457ae00 100644
--- a/cms-api/src/main/java/com/condation/cms/api/feature/features/IsPreviewFeature.java
+++ b/cms-api/src/main/java/com/condation/cms/api/feature/features/IsPreviewFeature.java
@@ -31,6 +31,33 @@
* @author t.marx
*/
@FeatureScope({FeatureScope.Scope.REQUEST})
-public record IsPreviewFeature() implements Feature {
+public record IsPreviewFeature(Mode mode) implements Feature {
+ public IsPreviewFeature() {
+ this(Mode.PREVIEW);
+ }
+
+ public static enum Mode {
+ MANAGER("manager"),
+ PREVIEW("preview");
+
+ private final String value;
+
+ private Mode (String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public static Mode forValue (String value) {
+ for (var mode : values()) {
+ if (mode.value.equals(value)) {
+ return mode;
+ }
+ }
+ return PREVIEW;
+ }
+ }
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/feature/features/ServerHookSystemFeature.java b/cms-api/src/main/java/com/condation/cms/api/feature/features/ServerHookSystemFeature.java
new file mode 100644
index 000000000..68428a459
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/feature/features/ServerHookSystemFeature.java
@@ -0,0 +1,37 @@
+package com.condation.cms.api.feature.features;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2024 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+
+import com.condation.cms.api.annotations.FeatureScope;
+import com.condation.cms.api.feature.Feature;
+import com.condation.cms.api.hooks.HookSystem;
+import java.util.function.Supplier;
+
+/**
+ *
+ * @author t.marx
+ */
+@FeatureScope({FeatureScope.Scope.SERVER})
+public record ServerHookSystemFeature(HookSystem hookSystem) implements Feature {
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/feature/features/TemplateEngineFeature.java b/cms-api/src/main/java/com/condation/cms/api/feature/features/TemplateEngineFeature.java
index 4c7da2693..10a2d90f4 100644
--- a/cms-api/src/main/java/com/condation/cms/api/feature/features/TemplateEngineFeature.java
+++ b/cms-api/src/main/java/com/condation/cms/api/feature/features/TemplateEngineFeature.java
@@ -37,7 +37,7 @@
@FeatureScope({FeatureScope.Scope.REQUEST})
public record TemplateEngineFeature(TemplateEngine templateEngine) implements Feature {
- public String render(String template, Map model, RequestContext requestContext) {
+ public String render(String template, Map model, RequestContext requestContext) {
try {
var templateModel = new TemplateEngine.Model(null, null, requestContext);
templateModel.values.putAll(model);
diff --git a/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java b/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java
index b173e4d6e..2106a7837 100644
--- a/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java
+++ b/cms-api/src/main/java/com/condation/cms/api/hooks/HookSystem.java
@@ -23,11 +23,13 @@
*/
import com.condation.cms.api.annotations.Filter;
import com.condation.cms.api.annotations.Action;
+import com.condation.cms.api.utils.AnnotationsUtil;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -39,70 +41,37 @@
* @author t.marx
*/
@Slf4j
-@RequiredArgsConstructor
public class HookSystem {
Multimap actions = ArrayListMultimap.create();
Multimap filters = ArrayListMultimap.create();
- private HookSystem(HookSystem source) {
+ public HookSystem () {
+
+ }
+ public HookSystem(HookSystem source) {
this.actions.putAll(source.actions);
this.filters.putAll(source.filters);
}
- public HookSystem clone() {
- return new HookSystem(this);
- }
-
public void register(Object sourceObject) {
- Class> objectClass = sourceObject.getClass();
- for (Method method : objectClass.getDeclaredMethods()) {
- // regsiter actions
- var actionAnnotation = method.getAnnotation(Action.class);
- if (actionAnnotation != null) {
-
- var parameters = method.getParameterTypes();
- if (parameters.length != 1 || !ActionContext.class.isAssignableFrom(parameters[0])) {
- log.warn("Method {}.{} ignored: must have exactly one parameter of type ActionContext",
- objectClass.getSimpleName(), method.getName());
- } else {
- if (!method.canAccess(sourceObject)) {
- method.setAccessible(true);
- }
+ // Action-Methoden registrieren
+ List> actionMethods
+ = AnnotationsUtil.process(sourceObject, Action.class, List.of(ActionContext.class), Void.class);
- registerAction(actionAnnotation.value(), context -> {
- try {
- return method.invoke(sourceObject, context);
- } catch (Exception e) {
- log.error("Error invoking action method {}.{}", objectClass.getSimpleName(), method.getName(), e);
- throw new RuntimeException(e);
- }
- }, actionAnnotation.priority());
- }
- }
-
- // register filters
- var filterAnnotation = method.getAnnotation(Filter.class);
- if (filterAnnotation != null) {
- var parameters = method.getParameterTypes();
- if (parameters.length != 1 || !FilterContext.class.isAssignableFrom(parameters[0])) {
- log.warn("Method {}.{} ignored for Filter: must have exactly one parameter of type FilterContext",
- objectClass.getSimpleName(), method.getName());
- } else {
- if (!method.canAccess(sourceObject)) {
- method.setAccessible(true);
- }
- registerFilter(filterAnnotation.value(), context -> {
- try {
- return method.invoke(sourceObject, context);
- } catch (Exception e) {
- log.error("Error invoking filter method {}.{}", objectClass.getSimpleName(), method.getName(), e);
- throw new RuntimeException(e);
- }
- }, filterAnnotation.priority());
- }
- }
+ for (AnnotationsUtil.CMSAnnotation ann : actionMethods) {
+ Action annotation = ann.annotation();
+ registerAction(annotation.value(), context -> ann.invoke(context), annotation.priority());
+ }
+
+ // Filter-Methoden registrieren
+ List> filterMethods
+ = AnnotationsUtil.process(sourceObject, Filter.class, List.of(FilterContext.class), Object.class);
+
+ for (AnnotationsUtil.CMSAnnotation ann : filterMethods) {
+ Filter annotation = ann.annotation();
+ registerFilter(annotation.value(), context -> ann.invoke(context), annotation.priority());
}
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/hooks/Hooks.java b/cms-api/src/main/java/com/condation/cms/api/hooks/Hooks.java
index 17781464a..6d79e5f20 100644
--- a/cms-api/src/main/java/com/condation/cms/api/hooks/Hooks.java
+++ b/cms-api/src/main/java/com/condation/cms/api/hooks/Hooks.java
@@ -31,7 +31,7 @@ public enum Hooks {
NAVIGATION_PATH("system/navigation/%s/path"),
NAVIGATION_LIST("system/navigation/%s/list"),
- CONTENT_SHORTCODE("system/content/shortcodes"),
+ CONTENT_TAGS("system/content/tags"),
CONTENT_FILTER("system/content/filter"),
TEMPLATE_COMPONENT("system/template/component"),
DB_QUERY_OPERATIONS("system/db/query/operations"),
diff --git a/cms-api/src/main/java/com/condation/cms/api/mapper/ContentNodeMapper.java b/cms-api/src/main/java/com/condation/cms/api/mapper/ContentNodeMapper.java
index e0eac51f0..a4d0d363a 100644
--- a/cms-api/src/main/java/com/condation/cms/api/mapper/ContentNodeMapper.java
+++ b/cms-api/src/main/java/com/condation/cms/api/mapper/ContentNodeMapper.java
@@ -23,6 +23,7 @@
*/
import com.condation.cms.api.Constants;
import com.condation.cms.api.content.ContentParser;
+import com.condation.cms.api.content.MapAccess;
import com.condation.cms.api.db.ContentNode;
import com.condation.cms.api.db.DB;
import com.condation.cms.api.db.cms.ReadOnlyFile;
@@ -32,7 +33,6 @@
import com.condation.cms.api.utils.HTTPUtil;
import com.condation.cms.api.utils.NodeUtil;
import com.condation.cms.api.utils.PathUtil;
-import com.google.inject.Inject;
import java.io.IOException;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
@@ -75,7 +75,7 @@ public ListNode toListNode(final ContentNode node, final RequestContext context,
var md = parse(temp_path);
var excerpt = NodeUtil.excerpt(node, md.get().content(), excerptLength, context.get(MarkdownRendererFeature.class).markdownRenderer());
- return new ListNode(name, url, excerpt, node.data());
+ return new ListNode(name, url, excerpt, MapAccess.of(node.data()));
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/media/Media.java b/cms-api/src/main/java/com/condation/cms/api/media/Media.java
index 70f6be19e..a07dbea29 100644
--- a/cms-api/src/main/java/com/condation/cms/api/media/Media.java
+++ b/cms-api/src/main/java/com/condation/cms/api/media/Media.java
@@ -29,4 +29,13 @@
*
* @author t.marx
*/
-public record Media (String uri, Meta meta, boolean exists) {}
+public record Media (String uri, Meta meta, boolean exists, Size size) {
+
+ public static Size NO_SIZE = new Size(-1, -1);
+
+ public Media (String uri, Meta meta, boolean exists) {
+ this(uri, meta, exists, NO_SIZE);
+ }
+
+ public static record Size(int width, int height) {}
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/media/meta/Meta.java b/cms-api/src/main/java/com/condation/cms/api/media/meta/Meta.java
index a0de890a8..bc686f104 100644
--- a/cms-api/src/main/java/com/condation/cms/api/media/meta/Meta.java
+++ b/cms-api/src/main/java/com/condation/cms/api/media/meta/Meta.java
@@ -21,14 +21,35 @@
* .
* #L%
*/
-
-
+import java.util.Collections;
import java.util.HashMap;
+import java.util.Map;
/**
*
* @author t.marx
*/
-public class Meta extends HashMap{
+public class Meta extends HashMap {
+
+ public double getFocalPoint_x() {
+ Object value = ((Map) getOrDefault("focal", Collections.emptyMap())).getOrDefault("x", 0.5);
+ return toDouble(value, 0.5);
+ }
+
+ public double getFocalPoint_y() {
+ Object value = ((Map) getOrDefault("focal", Collections.emptyMap())).getOrDefault("y", 0.5);
+ return toDouble(value, 0.5);
+ }
+
+ private double toDouble(Object value, double defaultValue) {
+ if (value instanceof Number) {
+ return ((Number) value).doubleValue();
+ }
+ try {
+ return Double.parseDouble(value.toString());
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
}
diff --git a/cms-git/src/main/java/com/condation/cms/git/Repo.java b/cms-api/src/main/java/com/condation/cms/api/module/ServerModuleContext.java
similarity index 69%
rename from cms-git/src/main/java/com/condation/cms/git/Repo.java
rename to cms-api/src/main/java/com/condation/cms/api/module/ServerModuleContext.java
index b8845f852..d7c73e816 100644
--- a/cms-git/src/main/java/com/condation/cms/git/Repo.java
+++ b/cms-api/src/main/java/com/condation/cms/api/module/ServerModuleContext.java
@@ -1,8 +1,8 @@
-package com.condation.cms.git;
+package com.condation.cms.api.module;
/*-
* #%L
- * cms-git
+ * cms-api
* %%
* Copyright (C) 2023 - 2024 CondationCMS
* %%
@@ -22,26 +22,16 @@
* #L%
*/
-import lombok.Data;
+import com.condation.cms.api.feature.FeatureContainer;
+import com.condation.modules.api.Context;
+import lombok.RequiredArgsConstructor;
/**
*
* @author t.marx
*/
-@Data
-public class Repo {
-
- private String name;
- private String uri;
- private String folder;
- private String branch;
- private String cron;
- private Credentials credentials;
+@RequiredArgsConstructor
+public class ServerModuleContext extends FeatureContainer implements Context {
- @Data
- public static class Credentials {
- private String username;
- private String password;
- }
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/module/ServerRequestContext.java b/cms-api/src/main/java/com/condation/cms/api/module/ServerRequestContext.java
new file mode 100644
index 000000000..e0371cfc3
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/module/ServerRequestContext.java
@@ -0,0 +1,35 @@
+package com.condation.cms.api.module;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2024 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+
+import com.condation.modules.api.ModuleRequestContext;
+import lombok.RequiredArgsConstructor;
+
+/**
+ *
+ * @author t.marx
+ */
+@RequiredArgsConstructor
+public class ServerRequestContext implements ModuleRequestContext {
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/module/CMSModuleContext.java b/cms-api/src/main/java/com/condation/cms/api/module/SiteModuleContext.java
similarity index 92%
rename from cms-api/src/main/java/com/condation/cms/api/module/CMSModuleContext.java
rename to cms-api/src/main/java/com/condation/cms/api/module/SiteModuleContext.java
index e347ac0fe..0fa6db58e 100644
--- a/cms-api/src/main/java/com/condation/cms/api/module/CMSModuleContext.java
+++ b/cms-api/src/main/java/com/condation/cms/api/module/SiteModuleContext.java
@@ -32,6 +32,6 @@
* @author t.marx
*/
@RequiredArgsConstructor
-public class CMSModuleContext extends FeatureContainer implements Context {
+public class SiteModuleContext extends FeatureContainer implements Context {
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/module/CMSRequestContext.java b/cms-api/src/main/java/com/condation/cms/api/module/SiteRequestContext.java
similarity index 95%
rename from cms-api/src/main/java/com/condation/cms/api/module/CMSRequestContext.java
rename to cms-api/src/main/java/com/condation/cms/api/module/SiteRequestContext.java
index 1b8167dd6..1af951201 100644
--- a/cms-api/src/main/java/com/condation/cms/api/module/CMSRequestContext.java
+++ b/cms-api/src/main/java/com/condation/cms/api/module/SiteRequestContext.java
@@ -34,7 +34,7 @@
* @author t.marx
*/
@RequiredArgsConstructor
-public class CMSRequestContext extends RequestContext implements ModuleRequestContext {
+public class SiteRequestContext extends RequestContext implements ModuleRequestContext {
private final RequestContext delegate;
diff --git a/cms-api/src/main/java/com/condation/cms/api/request/RequestContextScope.java b/cms-api/src/main/java/com/condation/cms/api/request/RequestContextScope.java
new file mode 100644
index 000000000..fa377e43c
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/request/RequestContextScope.java
@@ -0,0 +1,31 @@
+package com.condation.cms.api.request;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+/**
+ *
+ * @author thorstenmarx
+ */
+public class RequestContextScope {
+ public static final ScopedValue REQUEST_CONTEXT = ScopedValue.newInstance();
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/site/Site.java b/cms-api/src/main/java/com/condation/cms/api/site/Site.java
new file mode 100644
index 000000000..1cd0f2cad
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/site/Site.java
@@ -0,0 +1,68 @@
+package com.condation.cms.api.site;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+import com.condation.cms.api.SiteProperties;
+import com.google.inject.Injector;
+import java.util.List;
+
+/**
+ *
+ * @author thmar
+ */
+public record Site(Injector injector) {
+
+ public String id() {
+ return injector.getInstance(SiteProperties.class).id();
+ }
+
+ public List modules() {
+ return injector.getInstance(SiteProperties.class).activeModules();
+ }
+
+ public String baseurl() {
+ return (String) injector.getInstance(SiteProperties.class).get("baseurl");
+ }
+
+ public boolean manager() {
+ return injector.getInstance(SiteProperties.class).ui().managerEnabled();
+ }
+
+ public String realUrl() {
+ var baseUrl = baseurl();
+ var contextPath = injector.getInstance(SiteProperties.class).contextPath();
+
+ // Normalize baseUrl: remove trailing slashes
+ String normalizedBase = baseUrl.replaceAll("/+$", "");
+
+ // Normalize contextPath: ensure it starts with a slash (except if it's just "/")
+ String normalizedContext = contextPath.equals("/") ? "" : contextPath.replaceAll("^/+", "");
+
+ // Combine
+ if (normalizedContext.isEmpty()) {
+ return normalizedBase + "/";
+ } else {
+ return normalizedBase + "/" + normalizedContext + "/";
+ }
+ }
+
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/site/SiteService.java b/cms-api/src/main/java/com/condation/cms/api/site/SiteService.java
new file mode 100644
index 000000000..053e08126
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/site/SiteService.java
@@ -0,0 +1,35 @@
+package com.condation.cms.api.site;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import java.util.stream.Stream;
+
+/**
+ *
+ * @author thmar
+ */
+public interface SiteService {
+ void add (Site site);
+
+ Stream sites ();
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/action/UIAction.java b/cms-api/src/main/java/com/condation/cms/api/ui/action/UIAction.java
new file mode 100644
index 000000000..5c0207688
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/action/UIAction.java
@@ -0,0 +1,40 @@
+package com.condation.cms.api.ui.action;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2024 - 2025 Condation
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+/**
+ *
+ * @author thorstenmarx
+ */
+public abstract class UIAction {
+
+ private final String type;
+
+ public UIAction (String type) {
+ this.type = type;
+ }
+
+ public String getType () {
+ return type;
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/action/UIHookAction.java b/cms-api/src/main/java/com/condation/cms/api/ui/action/UIHookAction.java
new file mode 100644
index 000000000..fcfb3ac0b
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/action/UIHookAction.java
@@ -0,0 +1,52 @@
+package com.condation.cms.api.ui.action;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2024 - 2025 Condation
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+import java.util.Map;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class UIHookAction extends UIAction {
+
+ public static final String TYPE = "hook";
+
+ private String hook;
+
+ private Map parameters;
+
+ public UIHookAction () {
+ super(TYPE);
+ }
+
+ public UIHookAction(String hook, Map parameters) {
+ this();
+ this.hook = hook;
+ this.parameters = parameters;
+ }
+
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/action/UIScriptAction.java b/cms-api/src/main/java/com/condation/cms/api/ui/action/UIScriptAction.java
new file mode 100644
index 000000000..ad390c962
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/action/UIScriptAction.java
@@ -0,0 +1,58 @@
+package com.condation.cms.api.ui.action;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2024 - 2025 Condation
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+import java.util.Map;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class UIScriptAction extends UIAction {
+
+ public static final String TYPE = "script";
+
+ private String function;
+ private String module;
+
+ private Map parameters;
+
+ public UIScriptAction() {
+ super(TYPE);
+ }
+
+ public UIScriptAction (String module, String function, Map parameters) {
+ this();
+ this.function = function;
+ this.module = module;
+ this.parameters = parameters;
+ }
+
+ public UIScriptAction (String module, Map parameters) {
+ this(module, "runAction", parameters);
+ }
+
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/annotations/HookAction.java b/cms-api/src/main/java/com/condation/cms/api/ui/annotations/HookAction.java
new file mode 100644
index 000000000..af83e8ced
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/annotations/HookAction.java
@@ -0,0 +1,38 @@
+package com.condation.cms.api.ui.annotations;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface HookAction {
+ String value();
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/annotations/MenuEntry.java b/cms-api/src/main/java/com/condation/cms/api/ui/annotations/MenuEntry.java
new file mode 100644
index 000000000..783b3912b
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/annotations/MenuEntry.java
@@ -0,0 +1,46 @@
+package com.condation.cms.api.ui.annotations;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface MenuEntry {
+ String name();
+ String id();
+ String parent() default "";
+ boolean divider() default false;
+ int position() default 0;
+ String[] permissions();
+
+ ScriptAction scriptAction () default @ScriptAction(function = "", module = "");
+ HookAction hookAction () default @HookAction("");
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/annotations/RemoteMethod.java b/cms-api/src/main/java/com/condation/cms/api/ui/annotations/RemoteMethod.java
new file mode 100644
index 000000000..7f0f09f11
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/annotations/RemoteMethod.java
@@ -0,0 +1,39 @@
+package com.condation.cms.api.ui.annotations;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface RemoteMethod {
+ String name();
+ String[] permissions();
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/annotations/ScriptAction.java b/cms-api/src/main/java/com/condation/cms/api/ui/annotations/ScriptAction.java
new file mode 100644
index 000000000..378286c46
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/annotations/ScriptAction.java
@@ -0,0 +1,39 @@
+package com.condation.cms.api.ui.annotations;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface ScriptAction {
+ String module ();
+ String function () default "runAction";
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/annotations/ShortCut.java b/cms-api/src/main/java/com/condation/cms/api/ui/annotations/ShortCut.java
new file mode 100644
index 000000000..73d1d7975
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/annotations/ShortCut.java
@@ -0,0 +1,48 @@
+package com.condation.cms.api.ui.annotations;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface ShortCut {
+ String title();
+ String id();
+ String parent() default "";
+ String icon() default "";
+ String hotkey () default "";
+ String section () default "";
+
+ String[] permissions();
+
+ ScriptAction scriptAction () default @ScriptAction(function = "", module = "");
+ HookAction hookAction () default @HookAction("");
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/elements/ContentTypes.java b/cms-api/src/main/java/com/condation/cms/api/ui/elements/ContentTypes.java
new file mode 100644
index 000000000..c76afd142
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/elements/ContentTypes.java
@@ -0,0 +1,114 @@
+package com.condation.cms.api.ui.elements;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+/**
+ *
+ * @author thmar
+ */
+public class ContentTypes {
+
+ public Set pageTemplates = new HashSet();
+
+ public Set sectionTemplates = new HashSet<>();
+
+ public Set listItemTypes = new HashSet<>();
+
+ public void registerListItemType(Map listItemType) {
+ listItemTypes.add(new ListItemType(listItemType));
+ }
+
+ public Set getListItemTypes () {
+ return new HashSet<>(listItemTypes);
+ }
+
+ public Optional getPageTemplate (String name) {
+ return pageTemplates.stream().filter(pt -> pt.name.equals(name)).findFirst();
+ }
+
+ public void registerPageTemplate(Map pageTemplate) {
+ pageTemplates.add(new PageTemplate(pageTemplate));
+ }
+
+ public void registerSectionTemplate(Map sectionTempate) {
+ sectionTemplates.add(new SectionTemplate(sectionTempate));
+ }
+
+ public Set getPageTemplates () {
+ return new HashSet<>(pageTemplates);
+ }
+
+ public Set getSectionTemplates (String section) {
+ return sectionTemplates.stream()
+ .filter(template -> template.section().equals(section))
+ .collect(Collectors.toSet());
+ }
+
+ public static record PageTemplate(String name, String template, Map data) {
+
+ public PageTemplate (Map data) {
+ this(
+ (String) data.getOrDefault("name", ""),
+ (String) data.getOrDefault("template", ""),
+ data);
+ }
+
+ public Map getForm (String name) {
+ var forms = (Map)data.getOrDefault("forms", Collections.emptyMap());
+ return (Map)forms.getOrDefault(name, Collections.emptyMap());
+ }
+ }
+
+ public static record SectionTemplate(String name, String template, Map data) {
+
+ public SectionTemplate (Map data) {
+ this(
+ (String) data.getOrDefault("name", ""),
+ (String) data.getOrDefault("template", ""),
+ data);
+ }
+
+ public String section() {
+ return (String) data.getOrDefault("section", "");
+ }
+ }
+
+ public static record ListItemType(String name, Map data) {
+
+ public ListItemType (Map data) {
+ this(
+ (String) data.getOrDefault("name", ""),
+ data);
+ }
+
+ public Map getForm (String name) {
+ return (Map)data.getOrDefault("form", Collections.emptyMap());
+ }
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/elements/MediaForms.java b/cms-api/src/main/java/com/condation/cms/api/ui/elements/MediaForms.java
new file mode 100644
index 000000000..0c9ec6f4c
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/elements/MediaForms.java
@@ -0,0 +1,52 @@
+package com.condation.cms.api.ui.elements;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+/**
+ *
+ * @author thmar
+ */
+public class MediaForms {
+
+ public Map metaForms = new HashMap<>();
+
+
+ public void registerForm(String name, Map metaForm) {
+ metaForms.put(name, new MetaForm(name, metaForm));
+ }
+
+
+
+ public Map getMetaForms () {
+ return new HashMap<>(metaForms);
+ }
+
+ public static record MetaForm(String name, Map form) {
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/elements/Menu.java b/cms-api/src/main/java/com/condation/cms/api/ui/elements/Menu.java
new file mode 100644
index 000000000..7ebff0c96
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/elements/Menu.java
@@ -0,0 +1,65 @@
+package com.condation.cms.api.ui.elements;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+public class Menu {
+
+ private Map menu = new HashMap<>();
+
+ public Optional getMenuEntry(String id) {
+ return Optional.ofNullable(menu.get(id));
+ }
+
+ public void addMenuEntry(MenuEntry entry) {
+ menu.put(entry.getId(), entry);
+ }
+
+ public List entries() {
+ List entries = new ArrayList<>(menu.values());
+ sortMenuEntries(entries);
+ return entries;
+ }
+
+ private void sortMenuEntries(List entries) {
+ if (entries == null || entries.isEmpty()) {
+ return;
+ }
+ // Sortiere die aktuelle Ebene
+ entries.sort(Comparator.comparingInt(MenuEntry::getPosition));
+
+ // Sortiere rekursiv die Kinder
+ for (MenuEntry entry : entries) {
+ sortMenuEntries(entry.getChildren());
+ }
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/elements/MenuEntry.java b/cms-api/src/main/java/com/condation/cms/api/ui/elements/MenuEntry.java
new file mode 100644
index 000000000..ee4b64e21
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/elements/MenuEntry.java
@@ -0,0 +1,74 @@
+package com.condation.cms.api.ui.elements;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2024 - 2025 Condation
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.api.ui.action.UIAction;
+import com.condation.cms.api.utils.JSONUtil;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Builder;
+import lombok.Getter;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+@Builder
+@Getter
+public class MenuEntry {
+
+ private String name;
+
+ private String id;
+
+ @Builder.Default
+ private boolean divider = false;
+
+ @Builder.Default
+ private List permissions = new ArrayList<>();
+
+ @Builder.Default
+ private int position = 0;
+
+ @Builder.Default
+ private List children = new ArrayList<>();
+
+ private UIAction action;
+
+ public void addChildren (MenuEntry entry) {
+ if (children == null) {
+ children = new ArrayList<>();
+ }
+
+ children = new ArrayList<>(children);
+ children.add(entry);
+ }
+
+ public String getActionDefinition () {
+ return action != null ? JSONUtil.toJson(action) : "";
+ }
+
+ public boolean hasChildren () {
+ return children != null && ! children.isEmpty();
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/extensions/UIActionsExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/ui/extensions/UIActionsExtensionPoint.java
new file mode 100644
index 000000000..1b88e4e58
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/extensions/UIActionsExtensionPoint.java
@@ -0,0 +1,37 @@
+package com.condation.cms.api.ui.extensions;
+
+/*-
+ * #%L
+ * ui-api
+ * %%
+ * Copyright (C) 2024 Marx-Software
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.api.module.SiteModuleContext;
+import com.condation.cms.api.module.SiteRequestContext;
+import com.condation.cms.api.ui.elements.Menu;
+import com.condation.modules.api.ExtensionPoint;
+
+/**
+ *
+ * @author t.marx
+ */
+public interface UIActionsExtensionPoint extends ExtensionPoint {
+
+ public default void addMenuItems (Menu menu) {};
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/extensions/UILocalizationExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/ui/extensions/UILocalizationExtensionPoint.java
new file mode 100644
index 000000000..61873a3e0
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/extensions/UILocalizationExtensionPoint.java
@@ -0,0 +1,37 @@
+package com.condation.cms.api.ui.extensions;
+
+/*-
+ * #%L
+ * ui-api
+ * %%
+ * Copyright (C) 2024 Marx-Software
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.api.module.SiteModuleContext;
+import com.condation.cms.api.module.SiteRequestContext;
+import com.condation.modules.api.ExtensionPoint;
+import java.util.Map;
+
+/**
+ *
+ * @author t.marx
+ */
+public interface UILocalizationExtensionPoint extends ExtensionPoint {
+
+ public Map> getLocalizations ();
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/extensions/UIRemoteMethodExtensionPoint.java b/cms-api/src/main/java/com/condation/cms/api/ui/extensions/UIRemoteMethodExtensionPoint.java
new file mode 100644
index 000000000..896a8a8b2
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/extensions/UIRemoteMethodExtensionPoint.java
@@ -0,0 +1,33 @@
+package com.condation.cms.api.ui.extensions;
+
+/*-
+ * #%L
+ * ui-api
+ * %%
+ * Copyright (C) 2024 Marx-Software
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.api.extensions.AbstractExtensionPoint;
+
+/**
+ *
+ * @author t.marx
+ */
+public abstract class UIRemoteMethodExtensionPoint extends AbstractExtensionPoint {
+
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/rpc/RPCError.java b/cms-api/src/main/java/com/condation/cms/api/ui/rpc/RPCError.java
new file mode 100644
index 000000000..edf2e28bc
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/rpc/RPCError.java
@@ -0,0 +1,33 @@
+package com.condation.cms.api.ui.rpc;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+/**
+ *
+ * @author thorstenmarx
+ */
+public record RPCError (int code, String message) {
+ public RPCError (String message) {
+ this(-1, message);
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/rpc/RPCException.java b/cms-api/src/main/java/com/condation/cms/api/ui/rpc/RPCException.java
new file mode 100644
index 000000000..16fc84b9e
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/rpc/RPCException.java
@@ -0,0 +1,45 @@
+package com.condation.cms.api.ui.rpc;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+/**
+ *
+ * @author thmar
+ */
+public class RPCException extends Exception {
+
+ private int code = 0;
+
+ public RPCException (String message) {
+ this(0, message);
+ }
+
+ public RPCException(int code, String message) {
+ super(message);
+ this.code = code;
+ }
+
+ public int getCode () {
+ return code;
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/rpc/RPCResult.java b/cms-api/src/main/java/com/condation/cms/api/ui/rpc/RPCResult.java
new file mode 100644
index 000000000..3db295adb
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/ui/rpc/RPCResult.java
@@ -0,0 +1,41 @@
+package com.condation.cms.api.ui.rpc;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+/**
+ *
+ * @author thorstenmarx
+ */
+public record RPCResult (Object result, RPCError error) {
+ public RPCResult () {
+ this(null, null);
+ }
+
+ public RPCResult (Object result) {
+ this(result, null);
+ }
+
+ public RPCResult (RPCError error) {
+ this(null, error);
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/utils/AnnotationsUtil.java b/cms-api/src/main/java/com/condation/cms/api/utils/AnnotationsUtil.java
new file mode 100644
index 000000000..17eaa95fa
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/utils/AnnotationsUtil.java
@@ -0,0 +1,115 @@
+package com.condation.cms.api.utils;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+import com.condation.cms.api.exceptions.AnnotationExecutionException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ *
+ * @author thmar
+ */
+@Slf4j
+public class AnnotationsUtil {
+
+ public static List> process(Object target, Class annotation, Class returnType) {
+ return process(target, annotation, Collections.emptyList(), returnType);
+ }
+
+ public static List> process(Object target, Class annotationClass, List> parameters, Class returnType) {
+ Objects.requireNonNull(target);
+ Objects.requireNonNull(annotationClass);
+ Objects.requireNonNull(parameters);
+
+ List> result = new ArrayList();
+ Class> clazz = target.getClass();
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (!method.isAnnotationPresent(annotationClass)) {
+ continue;
+ }
+ if (!hasValidSignatur(method, parameters, returnType)) {
+ continue;
+ }
+
+ A annotation = method.getAnnotation(annotationClass);
+
+ result.add(new CMSAnnotation<>(annotation, (params) -> {
+ try {
+ return (R) method.invoke(target, params);
+ } catch (IllegalAccessException | InvocationTargetException ex) {
+ log.error("", ex);
+ throw new AnnotationExecutionException(ex.getMessage());
+ }
+ }));
+ }
+
+ return result;
+ }
+
+ private static boolean hasValidSignatur(Method method, List> parameters, Class> returnType) {
+
+ if (!Modifier.isPublic(method.getModifiers())) {
+ return false;
+ }
+
+ if (returnType != Void.class) {
+ Class> actualReturnType = method.getReturnType();
+ if (returnType == Object.class) {
+ if (actualReturnType == Void.TYPE) {
+ return false;
+ }
+ } else if (!actualReturnType.equals(returnType)) {
+ return false;
+ }
+ }
+
+ Class>[] params = method.getParameterTypes();
+
+ if (params.length != parameters.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < params.length; i++) {
+ if (!params[i].isAssignableFrom(parameters.get(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public record CMSAnnotation(A annotation, Function function) {
+
+ public R invoke(Object... parameters) {
+ return function.apply(parameters);
+ }
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/utils/DateRange.java b/cms-api/src/main/java/com/condation/cms/api/utils/DateRange.java
new file mode 100644
index 000000000..891b5a056
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/utils/DateRange.java
@@ -0,0 +1,53 @@
+package com.condation.cms.api.utils;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import java.time.Instant;
+import java.util.Date;
+
+public class DateRange {
+
+
+
+ /**
+ * Checks if the current UTC time is within the range.
+ * @param from
+ * @param to
+ * @return
+ */
+ public static boolean isNowWithin(Date from, Date to) {
+
+ var now = Date.from(Instant.now());
+
+ if ( from != null && !(from.before(now) || from.equals(now)) ) {
+ return false;
+ }
+ if (to != null
+ && (to.before(now) || to.equals(now))) {
+ return false;
+ }
+ return true;
+ }
+
+
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/utils/FileUtils.java b/cms-api/src/main/java/com/condation/cms/api/utils/FileUtils.java
index a16e62fb7..c0aeaee11 100644
--- a/cms-api/src/main/java/com/condation/cms/api/utils/FileUtils.java
+++ b/cms-api/src/main/java/com/condation/cms/api/utils/FileUtils.java
@@ -23,11 +23,12 @@
*/
import java.io.File;
import java.io.IOException;
+import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
import java.util.Comparator;
/**
@@ -40,16 +41,48 @@ private FileUtils() {
}
public static void deleteFolder(Path pathToBeDeleted) throws IOException {
- Files.walk(pathToBeDeleted)
- .sorted(Comparator.reverseOrder())
- .map(Path::toFile)
- .forEach(File::delete);
+ if (!Files.exists(pathToBeDeleted)) {
+ return;
+ }
+ try (var walkStream = Files.walk(pathToBeDeleted)) {
+ walkStream.sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ }
}
- public static void touch(Path path) throws IOException{
+ public static void deleteDirectoryContents(Path directory) throws IOException {
+ if (!Files.exists(directory) || !Files.isDirectory(directory)) {
+ throw new IllegalArgumentException("Pfad ist kein existierendes Verzeichnis: " + directory);
+ }
+
+ Files.walkFileTree(directory, new SimpleFileVisitor<>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ if (!dir.equals(directory)) {
+ Files.delete(dir);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ public static void touch(Path path) throws IOException {
long timeMillis = System.currentTimeMillis();
FileTime accessFileTime = FileTime.fromMillis(timeMillis);
Files.setAttribute(path, "lastAccessTime", accessFileTime);
Files.setLastModifiedTime(path, accessFileTime);
}
+
+ public static long countChildren(final Path path) throws IOException {
+ try (var children = Files.list(path)) {
+ return children.count();
+ }
+ }
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/utils/HTTPUtil.java b/cms-api/src/main/java/com/condation/cms/api/utils/HTTPUtil.java
index b726ec84c..46644f819 100644
--- a/cms-api/src/main/java/com/condation/cms/api/utils/HTTPUtil.java
+++ b/cms-api/src/main/java/com/condation/cms/api/utils/HTTPUtil.java
@@ -26,7 +26,6 @@
import com.condation.cms.api.feature.FeatureContainer;
import com.condation.cms.api.feature.features.IsPreviewFeature;
import com.condation.cms.api.feature.features.SitePropertiesFeature;
-import com.condation.cms.api.request.RequestContext;
import com.google.common.base.Strings;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
@@ -61,10 +60,11 @@ public static String modifyUrl(String url, final FeatureContainer featureContain
url = modifyUrl(url, featureContainer.get(SitePropertiesFeature.class).siteProperties());
if (featureContainer.has(IsPreviewFeature.class)) {
+ var feature = featureContainer.get(IsPreviewFeature.class);
if (url.contains("?")) {
- url += "&preview=true";
+ url += "&preview=" + feature.mode().getValue();
} else {
- url += "?preview=true";
+ url += "?preview=" + feature.mode().getValue();
}
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/utils/ImageUtil.java b/cms-api/src/main/java/com/condation/cms/api/utils/ImageUtil.java
new file mode 100644
index 000000000..77ea44ba7
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/utils/ImageUtil.java
@@ -0,0 +1,72 @@
+package com.condation.cms.api.utils;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+import com.condation.cms.api.feature.features.SitePropertiesFeature;
+import com.condation.cms.api.request.RequestContext;
+import java.nio.file.Paths;
+import java.util.List;
+
+/**
+ *
+ * @author thmar
+ */
+public class ImageUtil {
+
+ /**
+ * This method takes image pathes like
+ *
+ * /de/assets/test.jpg /de/media/test.jpg /assets/test.jpg /media/test.jpg
+ *
+ * and extract the real path of the image and returns: test.jpg
+ *
+ * @param image
+ * @param requestContext
+ * @return raw image path
+ */
+ public static String getRawPath(String image, RequestContext requestContext) {
+ if (image == null || image.isEmpty()) {
+ return "";
+ }
+
+ String normalized = image.trim();
+
+ if (normalized.startsWith("/")) {
+ normalized = normalized.substring(1);
+ }
+
+ String contextPath = requestContext.get(SitePropertiesFeature.class).siteProperties().contextPath();
+ if (contextPath != null && !contextPath.isEmpty()) {
+ contextPath = contextPath.replaceAll("^/+", "").replaceAll("/+$", "");
+ if (normalized.startsWith(contextPath + "/")) {
+ normalized = normalized.substring(contextPath.length() + 1);
+ }
+ }
+ for (var path : List.of("assets", "media")) {
+ if (normalized.startsWith(path + "/")) {
+ normalized = normalized.substring(path.length() + 1);
+ }
+ }
+
+ return normalized;
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/utils/JSONUtil.java b/cms-api/src/main/java/com/condation/cms/api/utils/JSONUtil.java
new file mode 100644
index 000000000..a7ba0c730
--- /dev/null
+++ b/cms-api/src/main/java/com/condation/cms/api/utils/JSONUtil.java
@@ -0,0 +1,37 @@
+package com.condation.cms.api.utils;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.google.gson.Gson;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+public class JSONUtil {
+ private static final Gson GSON = new Gson();
+
+ public static String toJson (Object value) {
+ return GSON.toJson(value);
+ }
+}
diff --git a/cms-api/src/main/java/com/condation/cms/api/utils/PathUtil.java b/cms-api/src/main/java/com/condation/cms/api/utils/PathUtil.java
index ee6a850a9..28bee1f49 100644
--- a/cms-api/src/main/java/com/condation/cms/api/utils/PathUtil.java
+++ b/cms-api/src/main/java/com/condation/cms/api/utils/PathUtil.java
@@ -90,29 +90,6 @@ public static String toRelativeFile(ReadOnlyFile contentFile, final ReadOnlyFile
return uri;
}
- /**
- *
- * @param contentFile
- * @param contentBase
- * @return
- * @deprecated use PathUtil.toURL instead
- */
- @Deprecated(since = "8.0.0")
- public static String toURI (final Path contentFile, final Path contentBase) {
- return toURL(contentFile, contentBase);
- }
- /**
- *
- * @param contentFile
- * @param contentBase
- * @return
- * @deprecated use PathUtil.toURL instead
- */
- @Deprecated(since = "8.0.0")
- public static String toURI(final ReadOnlyFile contentFile, final ReadOnlyFile contentBase) {
- return toURL(contentFile, contentBase);
- }
-
public static String toURL(final Path contentFile, final Path contentBase) {
var relFile = toRelativeFile(contentFile, contentBase);
return toURL(relFile);
@@ -123,7 +100,7 @@ public static String toURL(final ReadOnlyFile contentFile, final ReadOnlyFile co
return toURL(relFile);
}
- private static String toURL (String relFile) {
+ public static String toURL (String relFile) {
if (relFile.endsWith("index.md")) {
relFile = relFile.replace("index.md", "");
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/utils/RequestUtil.java b/cms-api/src/main/java/com/condation/cms/api/utils/RequestUtil.java
index 1fc1559ae..1ab9b80d8 100644
--- a/cms-api/src/main/java/com/condation/cms/api/utils/RequestUtil.java
+++ b/cms-api/src/main/java/com/condation/cms/api/utils/RequestUtil.java
@@ -23,6 +23,11 @@
*/
+import com.condation.cms.api.Constants;
+import com.condation.cms.api.feature.features.SitePropertiesFeature;
+import com.condation.cms.api.request.RequestContext;
+import java.net.InetSocketAddress;
+import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.Request;
/**
@@ -31,6 +36,11 @@
*/
public class RequestUtil {
+ public static String getContextPath(Request request) {
+ var requestContext = (RequestContext) request.getAttribute(Constants.REQUEST_CONTEXT_ATTRIBUTE_NAME);
+ return requestContext.get(SitePropertiesFeature.class).siteProperties().contextPath();
+ }
+
/**
* removes the context from the path
* @param request
@@ -38,7 +48,7 @@ public class RequestUtil {
*/
public static String getContentPath(Request request) {
var path = request.getHttpURI().getPath();
- var contextPath = request.getContext().getContextPath();
+ var contextPath = getContextPath(request);
if (!"/".equals(contextPath) && path.startsWith(contextPath)) {
path = path.replaceFirst(contextPath, "");
}
@@ -49,4 +59,13 @@ public static String getContentPath(Request request) {
return path;
}
+
+ public static String clientAddress(Request request) {
+ String forwarded = request.getHeaders().get(HttpHeader.X_FORWARDED_FOR);
+ if (forwarded != null && !forwarded.isEmpty()) {
+ return forwarded.split(",")[0].trim();
+ }
+ return ((InetSocketAddress) request.getConnectionMetaData().getRemoteSocketAddress())
+ .getAddress().getHostAddress();
+ }
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/utils/SectionUtil.java b/cms-api/src/main/java/com/condation/cms/api/utils/SectionUtil.java
index 6364d15c0..bee06b90a 100644
--- a/cms-api/src/main/java/com/condation/cms/api/utils/SectionUtil.java
+++ b/cms-api/src/main/java/com/condation/cms/api/utils/SectionUtil.java
@@ -30,13 +30,13 @@
*/
public class SectionUtil {
- public static boolean isOrderedSection(final String name) {
- return Constants.SECTION_ORDERED_PATTERN.matcher(name).matches();
+ public static boolean isNamedSection(final String name) {
+ return Constants.SECTION_NAMED_PATTERN.matcher(name).matches();
}
public static String getSectionName(final String name) {
- if (isOrderedSection(name)) {
- var matcher = Constants.SECTION_ORDERED_PATTERN.matcher(name);
+ if (isNamedSection(name)) {
+ var matcher = Constants.SECTION_NAMED_PATTERN.matcher(name);
matcher.matches();
return matcher.group("section");
} else {
@@ -46,18 +46,8 @@ public static String getSectionName(final String name) {
}
}
- public static int getSectionIndex(final String name) {
- if (isOrderedSection(name)) {
- var matcher = Constants.SECTION_ORDERED_PATTERN.matcher(name);
- matcher.matches();
- return Integer.parseInt(matcher.group("index"));
- } else {
- return Constants.DEFAULT_SECTION_ORDERED_INDEX;
- }
- }
-
public static boolean isSection(final String name) {
return Constants.SECTION_PATTERN.matcher(name).matches()
- || Constants.SECTION_ORDERED_PATTERN.matcher(name).matches();
+ || Constants.SECTION_NAMED_PATTERN.matcher(name).matches();
}
}
diff --git a/cms-api/src/main/java/com/condation/cms/api/utils/SiteUtil.java b/cms-api/src/main/java/com/condation/cms/api/utils/SiteUtil.java
index 715a08774..a0ede2241 100644
--- a/cms-api/src/main/java/com/condation/cms/api/utils/SiteUtil.java
+++ b/cms-api/src/main/java/com/condation/cms/api/utils/SiteUtil.java
@@ -21,6 +21,7 @@
* .
* #L%
*/
+import com.condation.cms.api.Constants;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -29,6 +30,8 @@
import com.condation.cms.api.SiteProperties;
import com.condation.cms.api.theme.Theme;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
*
@@ -44,20 +47,36 @@ public static boolean isSite(Path check) {
|| Files.exists(check.resolve("site.toml"));
}
+ public static Stream sitesStream() throws IOException {
+ List sites;
+ try (var siteStream = Files.list(ServerUtil.getPath(Constants.Folders.HOSTS))) {
+ sites = siteStream
+ .filter(SiteUtil::isSite)
+ .map(Site::new)
+ .collect(Collectors.toList());
+ }
+ return sites.stream();
+ }
+
public static List getActiveModules(SiteProperties siteProperties, Theme theme) {
List activeModules = new ArrayList<>();
activeModules.addAll(siteProperties.activeModules());
if (!theme.empty()) {
activeModules.addAll(theme.properties().activeModules());
-
+
if (theme.getParentTheme() != null) {
activeModules.addAll(theme.getParentTheme().properties().activeModules());
}
}
return activeModules;
}
-
+
public static String getRequiredTheme(SiteProperties siteProperties, Theme theme) {
return siteProperties.theme();
}
+
+ public record Site(Path basePath) {
+
+ }
+;
}
diff --git a/cms-api/src/test/java/com/condation/cms/api/ConstantsNGTest.java b/cms-api/src/test/java/com/condation/cms/api/ConstantsNGTest.java
index 45fd8a296..f5fa19c03 100644
--- a/cms-api/src/test/java/com/condation/cms/api/ConstantsNGTest.java
+++ b/cms-api/src/test/java/com/condation/cms/api/ConstantsNGTest.java
@@ -54,36 +54,36 @@ public void test_section_pattern() {
@Test
- public void test_ordered_sections_pattern() {
- Assertions.assertThat(Constants.SECTION_ORDERED_PATTERN.matcher("index.md").matches()).isFalse();
- Assertions.assertThat(Constants.SECTION_ORDERED_PATTERN.matcher(".section.md").matches()).isFalse();
+ public void test_named_sections_pattern() {
+ Assertions.assertThat(Constants.SECTION_NAMED_PATTERN.matcher("index.md").matches()).isFalse();
+ Assertions.assertThat(Constants.SECTION_NAMED_PATTERN.matcher(".section.md").matches()).isFalse();
- Matcher matcher = Constants.SECTION_ORDERED_PATTERN.matcher("page.section.md");
+ Matcher matcher = Constants.SECTION_NAMED_PATTERN.matcher("page.section.md");
Assertions.assertThat(matcher.matches()).isFalse();
- matcher = Constants.SECTION_ORDERED_PATTERN.matcher("page.section..md");
+ matcher = Constants.SECTION_NAMED_PATTERN.matcher("page.section..md");
Assertions.assertThat(matcher.matches()).isFalse();
- matcher = Constants.SECTION_ORDERED_PATTERN.matcher("index.card.1.md");
+ matcher = Constants.SECTION_NAMED_PATTERN.matcher("index.card.1.md");
Assertions.assertThat(matcher.matches()).isTrue();
Assertions.assertThat(matcher.group("section")).isEqualTo("card");
- Assertions.assertThat(matcher.group("index")).isEqualTo("1");
+ Assertions.assertThat(matcher.group("id")).isEqualTo("1");
- matcher = Constants.SECTION_ORDERED_PATTERN.matcher("index.card.10.md");
+ matcher = Constants.SECTION_NAMED_PATTERN.matcher("index.card.10.md");
Assertions.assertThat(matcher.matches()).isTrue();
Assertions.assertThat(matcher.group("section")).isEqualTo("card");
- Assertions.assertThat(matcher.group("index")).isEqualTo("10");
+ Assertions.assertThat(matcher.group("id")).isEqualTo("10");
}
@Test
- public void test_ordered_section_of() {
+ public void test_named_section_of() {
- var pattern = Constants.SECTION_ORDERED_OF_PATTERN.apply("page");
+ var pattern = Constants.SECTION_NAMED_OF_PATTERN.apply("page");
var matcher = pattern.matcher("page.left.10.md");
Assertions.assertThat(matcher.matches()).isTrue();
- pattern = Constants.SECTION_ORDERED_OF_PATTERN.apply("other");
+ pattern = Constants.SECTION_NAMED_OF_PATTERN.apply("other");
matcher = pattern.matcher("page.left.10.md");
Assertions.assertThat(matcher.matches()).isFalse();
diff --git a/cms-api/src/test/java/com/condation/cms/api/db/ContentNodeTest.java b/cms-api/src/test/java/com/condation/cms/api/db/ContentNodeTest.java
index 3c76ad9ae..e7f7c32d1 100644
--- a/cms-api/src/test/java/com/condation/cms/api/db/ContentNodeTest.java
+++ b/cms-api/src/test/java/com/condation/cms/api/db/ContentNodeTest.java
@@ -40,7 +40,7 @@ public class ContentNodeTest {
@Test
public void test_publish() {
var contentNode = new ContentNode("", "", Map.of());
- Assertions.assertThat(contentNode.isPublished()).isTrue();
+ Assertions.assertThat(contentNode.isVisible()).isFalse();
}
@Test
@@ -48,9 +48,10 @@ public void test_publish_date_1_11_2023() {
var cal = Calendar.getInstance();
cal.set(2023, 11, 1);
var contentNode = new ContentNode("", "", Map.of(
- Constants.MetaFields.PUBLISH_DATE, cal.getTime()
+ Constants.MetaFields.PUBLISH_DATE, cal.getTime(),
+ Constants.MetaFields.PUBLISHED, true
));
- Assertions.assertThat(contentNode.isPublished()).isTrue();
+ Assertions.assertThat(contentNode.isVisible()).isTrue();
}
@Test
@@ -58,9 +59,10 @@ public void test_publish_date_1_11_2123() {
var cal = Calendar.getInstance();
cal.set(2123, 11, 1);
var contentNode = new ContentNode("", "", Map.of(
- Constants.MetaFields.PUBLISH_DATE, cal.getTime()
+ Constants.MetaFields.PUBLISH_DATE, cal.getTime(),
+ Constants.MetaFields.PUBLISHED, true
));
- Assertions.assertThat(contentNode.isPublished()).isFalse();
+ Assertions.assertThat(contentNode.isVisible()).isFalse();
}
@Test
@@ -68,9 +70,10 @@ public void test_unpublish_date_1_11_2023() {
var cal = Calendar.getInstance();
cal.set(2023, 11, 1);
var contentNode = new ContentNode("", "", Map.of(
- Constants.MetaFields.UNPUBLISH_DATE, cal.getTime()
+ Constants.MetaFields.UNPUBLISH_DATE, cal.getTime(),
+ Constants.MetaFields.PUBLISHED, true
));
- Assertions.assertThat(contentNode.isPublished()).isFalse();
+ Assertions.assertThat(contentNode.isVisible()).isFalse();
}
@Test
@@ -78,8 +81,9 @@ public void test_unpublish_date_1_11_2123() {
var cal = Calendar.getInstance();
cal.set(2123, 11, 1);
var contentNode = new ContentNode("", "", Map.of(
- Constants.MetaFields.UNPUBLISH_DATE, cal.getTime()
+ Constants.MetaFields.UNPUBLISH_DATE, cal.getTime(),
+ Constants.MetaFields.PUBLISHED, true
));
- Assertions.assertThat(contentNode.isPublished()).isTrue();
+ Assertions.assertThat(contentNode.isVisible()).isTrue();
}
}
diff --git a/cms-api/src/test/java/com/condation/cms/api/extensions/http/routes/RoutesManagerTest.java b/cms-api/src/test/java/com/condation/cms/api/extensions/http/routes/RoutesManagerTest.java
index 9139f8b1f..08f53a9dc 100644
--- a/cms-api/src/test/java/com/condation/cms/api/extensions/http/routes/RoutesManagerTest.java
+++ b/cms-api/src/test/java/com/condation/cms/api/extensions/http/routes/RoutesManagerTest.java
@@ -1,7 +1,3 @@
-/*
- * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
- * Click nbfs://nbhost/SystemFileSystem/Templates/UnitTests/JUnit5TestClass.java to edit this template
- */
package com.condation.cms.api.extensions.http.routes;
/*-
diff --git a/cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java b/cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java
index 1f2155166..cbe974ca2 100644
--- a/cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java
+++ b/cms-api/src/test/java/com/condation/cms/api/hooks/HookSystemTest.java
@@ -25,7 +25,6 @@
import com.condation.cms.api.annotations.Filter;
import com.condation.cms.api.annotations.Action;
-import com.condation.cms.api.hooks.HookSystem;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
diff --git a/cms-api/src/test/java/com/condation/cms/api/utils/AnnotationsUtilTest.java b/cms-api/src/test/java/com/condation/cms/api/utils/AnnotationsUtilTest.java
new file mode 100644
index 000000000..e9741e649
--- /dev/null
+++ b/cms-api/src/test/java/com/condation/cms/api/utils/AnnotationsUtilTest.java
@@ -0,0 +1,122 @@
+package com.condation.cms.api.utils;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.api.exceptions.AnnotationExecutionException;
+import com.condation.cms.api.utils.AnnotationsUtil.CMSAnnotation;
+import org.junit.jupiter.api.Test;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.*;
+
+public class AnnotationsUtilTest {
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface TestMarker {}
+
+ static class TestTarget {
+
+ @TestMarker
+ public String greet(String name) {
+ return "Hello " + name;
+ }
+
+ @TestMarker
+ public String wrongParameterType(Integer i) {
+ return "Number " + i;
+ }
+
+ @TestMarker
+ public void wrongReturnType(String name) {}
+
+ @TestMarker
+ private String privateMethod(String name) {
+ return "Private " + name;
+ }
+
+ public String notAnnotated(String name) {
+ return "Ignored";
+ }
+ }
+
+ @Test
+ void shouldReturnOnlyPublicAnnotatedMethodsWithCorrectSignature() {
+ TestTarget target = new TestTarget();
+
+ List> results =
+ AnnotationsUtil.process(target, TestMarker.class, List.of(String.class), String.class);
+
+ assertThat(results).hasSize(1);
+ String result = results.get(0).invoke("World");
+ assertThat(result).isEqualTo("Hello World");
+ }
+
+ @Test
+ void shouldIgnorePrivateMethods() {
+ TestTarget target = new TestTarget();
+
+ List> results =
+ AnnotationsUtil.process(target, TestMarker.class, List.of(String.class), String.class);
+
+ boolean includesPrivate = results.stream()
+ .anyMatch(ann -> ann.annotation().annotationType().equals(TestMarker.class)
+ && ann.invoke("test").startsWith("Private"));
+
+ assertThat(includesPrivate).isFalse();
+ }
+
+ @Test
+ void shouldIgnoreWrongParameterTypes() {
+ TestTarget target = new TestTarget();
+
+ List> results =
+ AnnotationsUtil.process(target, TestMarker.class, List.of(Double.class), String.class);
+
+ assertThat(results).isEmpty();
+ }
+
+ @Test
+ void shouldIgnoreMethodsWithWrongReturnType() {
+ TestTarget target = new TestTarget();
+
+ List> results =
+ AnnotationsUtil.process(target, TestMarker.class, List.of(String.class), String.class);
+
+ assertThat(results).noneMatch(ann -> ann.annotation().annotationType().equals(TestMarker.class)
+ && ann.invoke("Test") == null);
+ }
+
+
+ @Test
+ void shouldIgnoreNonAnnotatedMethods() {
+ TestTarget target = new TestTarget();
+
+ List> results =
+ AnnotationsUtil.process(target, TestMarker.class, List.of(String.class), String.class);
+
+ assertThat(results).allSatisfy(ann -> assertThat(ann.annotation()).isInstanceOf(TestMarker.class));
+ }
+}
diff --git a/cms-api/src/test/java/com/condation/cms/api/utils/DateRangeTest.java b/cms-api/src/test/java/com/condation/cms/api/utils/DateRangeTest.java
new file mode 100644
index 000000000..a88f16c69
--- /dev/null
+++ b/cms-api/src/test/java/com/condation/cms/api/utils/DateRangeTest.java
@@ -0,0 +1,96 @@
+package com.condation.cms.api.utils;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import org.junit.jupiter.api.Test;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class DateRangeTest {
+
+ @Test
+ void should_return_true_when_both_dates_are_null() {
+ assertThat(DateRange.isNowWithin(null, null)).isTrue();
+ }
+
+ @Test
+ void should_return_true_when_now_is_after_from_and_to_is_null() {
+ Date from = Date.from(Instant.now().minus(1, ChronoUnit.HOURS));
+ assertThat(DateRange.isNowWithin(from, null)).isTrue();
+ }
+
+ @Test
+ void should_return_false_when_now_is_before_from_and_to_is_null() {
+ Date from = Date.from(Instant.now().plus(1, ChronoUnit.HOURS));
+ assertThat(DateRange.isNowWithin(from, null)).isFalse();
+ }
+
+ @Test
+ void should_return_true_when_now_is_before_to_and_from_is_null() {
+ Date to = Date.from(Instant.now().plus(1, ChronoUnit.HOURS));
+ assertThat(DateRange.isNowWithin(null, to)).isTrue();
+ }
+
+ @Test
+ void should_return_false_when_now_is_after_to_and_from_is_null() {
+ Date to = Date.from(Instant.now().minus(1, ChronoUnit.HOURS));
+ assertThat(DateRange.isNowWithin(null, to)).isFalse();
+ }
+
+ @Test
+ void should_return_true_when_now_is_between_from_and_to() {
+ Date from = Date.from(Instant.now().minus(1, ChronoUnit.HOURS));
+ Date to = Date.from(Instant.now().plus(1, ChronoUnit.HOURS));
+ assertThat(DateRange.isNowWithin(from, to)).isTrue();
+ }
+
+ @Test
+ void should_return_false_when_now_is_before_from_even_if_to_is_in_future() {
+ Date from = Date.from(Instant.now().plus(30, ChronoUnit.MINUTES));
+ Date to = Date.from(Instant.now().plus(2, ChronoUnit.HOURS));
+ assertThat(DateRange.isNowWithin(from, to)).isFalse();
+ }
+
+ @Test
+ void should_return_false_when_now_is_after_to_even_if_from_is_in_past() {
+ Date from = Date.from(Instant.now().minus(2, ChronoUnit.HOURS));
+ Date to = Date.from(Instant.now().minus(30, ChronoUnit.MINUTES));
+ assertThat(DateRange.isNowWithin(from, to)).isFalse();
+ }
+
+ @Test
+ void should_return_true_when_now_equals_from_and_to_is_null() {
+ Date now = Date.from(Instant.now());
+ assertThat(DateRange.isNowWithin(now, null)).isTrue();
+ }
+
+ @Test
+ void should_return_false_when_now_equals_to() {
+ Date now = Date.from(Instant.now());
+ assertThat(DateRange.isNowWithin(null, now)).isFalse();
+ }
+}
diff --git a/cms-api/src/test/java/com/condation/cms/api/utils/ImageUtilTest.java b/cms-api/src/test/java/com/condation/cms/api/utils/ImageUtilTest.java
new file mode 100644
index 000000000..2490821c0
--- /dev/null
+++ b/cms-api/src/test/java/com/condation/cms/api/utils/ImageUtilTest.java
@@ -0,0 +1,78 @@
+package com.condation.cms.api.utils;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.api.SiteProperties;
+import com.condation.cms.api.feature.features.SitePropertiesFeature;
+import com.condation.cms.api.request.RequestContext;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ *
+ * @author thmar
+ */
+@ExtendWith(MockitoExtension.class)
+public class ImageUtilTest {
+
+ @Mock
+ private RequestContext requestContext;
+
+ @Test
+ public void test_raw_path_default_context() {
+
+ var siteProperties = Mockito.mock(SiteProperties.class);
+ Mockito.when(siteProperties.contextPath()).thenReturn("/");
+ Mockito.when(requestContext.get(SitePropertiesFeature.class)).thenReturn(new SitePropertiesFeature(siteProperties));
+
+ Assertions.assertThat(ImageUtil.getRawPath("/test.jpg", requestContext)).isEqualTo("test.jpg");
+ Assertions.assertThat(ImageUtil.getRawPath("/images/test.jpg", requestContext)).isEqualTo("images/test.jpg");
+
+ Assertions.assertThat(ImageUtil.getRawPath("/assets/test.jpg", requestContext)).isEqualTo("test.jpg");
+ Assertions.assertThat(ImageUtil.getRawPath("/assets/images/test.jpg", requestContext)).isEqualTo("images/test.jpg");
+
+ Assertions.assertThat(ImageUtil.getRawPath("/media/test.jpg", requestContext)).isEqualTo("test.jpg");
+ Assertions.assertThat(ImageUtil.getRawPath("/media/images/test.jpg", requestContext)).isEqualTo("images/test.jpg");
+ }
+
+ @Test
+ public void test_raw_path_context() {
+
+ var siteProperties = Mockito.mock(SiteProperties.class);
+ Mockito.when(siteProperties.contextPath()).thenReturn("/de");
+ Mockito.when(requestContext.get(SitePropertiesFeature.class)).thenReturn(new SitePropertiesFeature(siteProperties));
+
+ Assertions.assertThat(ImageUtil.getRawPath("/de/test.jpg", requestContext)).isEqualTo("test.jpg");
+ Assertions.assertThat(ImageUtil.getRawPath("/de/images/test.jpg", requestContext)).isEqualTo("images/test.jpg");
+
+ Assertions.assertThat(ImageUtil.getRawPath("/de/assets/test.jpg", requestContext)).isEqualTo("test.jpg");
+ Assertions.assertThat(ImageUtil.getRawPath("/de/assets/images/test.jpg", requestContext)).isEqualTo("images/test.jpg");
+
+ Assertions.assertThat(ImageUtil.getRawPath("/de/media/test.jpg", requestContext)).isEqualTo("test.jpg");
+ Assertions.assertThat(ImageUtil.getRawPath("/de/media/images/test.jpg", requestContext)).isEqualTo("images/test.jpg");
+ }
+}
diff --git a/cms-git/src/test/java/com/condation/cms/git/ConfigTest.java b/cms-api/src/test/java/com/condation/cms/api/utils/JSONUtilTest.java
similarity index 63%
rename from cms-git/src/test/java/com/condation/cms/git/ConfigTest.java
rename to cms-api/src/test/java/com/condation/cms/api/utils/JSONUtilTest.java
index 62c776c95..88cce7135 100644
--- a/cms-git/src/test/java/com/condation/cms/git/ConfigTest.java
+++ b/cms-api/src/test/java/com/condation/cms/api/utils/JSONUtilTest.java
@@ -1,10 +1,10 @@
-package com.condation.cms.git;
+package com.condation.cms.api.utils;
/*-
* #%L
- * cms-git
+ * cms-api
* %%
- * Copyright (C) 2023 - 2024 CondationCMS
+ * Copyright (C) 2023 - 2025 CondationCMS
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
@@ -21,29 +21,20 @@
* .
* #L%
*/
-
-
-import com.condation.cms.git.Config;
-import java.io.IOException;
-import java.nio.file.Path;
+import java.util.List;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
-
/**
*
- * @author t.marx
+ * @author thorstenmarx
*/
-public class ConfigTest {
+public class JSONUtilTest {
-
@Test
- public void testSomeMethod() throws IOException {
- var config = Config.load(Path.of("git.yaml"));
-
- Assertions.assertThat(config).isNotNull();
-
- Assertions.assertThat(config.getRepos()).isNotNull().isNotEmpty().hasSize(1);
+ void testList() {
+ var listJson = JSONUtil.toJson(List.of("Condation", "CMS"));
+ Assertions.assertThat(listJson).isEqualToIgnoringWhitespace("[\"Condation\", \"CMS\"]");
}
}
diff --git a/cms-api/src/test/java/com/condation/cms/api/utils/PathUtilTest.java b/cms-api/src/test/java/com/condation/cms/api/utils/PathUtilTest.java
index c26c4b1e0..5e8d997bc 100644
--- a/cms-api/src/test/java/com/condation/cms/api/utils/PathUtilTest.java
+++ b/cms-api/src/test/java/com/condation/cms/api/utils/PathUtilTest.java
@@ -43,7 +43,7 @@ public void test_canonical () throws IOException {
}
@Test
- public void test_to_uri() {
+ public void test_to_url() {
Path contentBase = Path.of("src/");
diff --git a/cms-api/src/test/java/com/condation/cms/api/utils/SectionUtilTest.java b/cms-api/src/test/java/com/condation/cms/api/utils/SectionUtilTest.java
new file mode 100644
index 000000000..d555434c7
--- /dev/null
+++ b/cms-api/src/test/java/com/condation/cms/api/utils/SectionUtilTest.java
@@ -0,0 +1,44 @@
+package com.condation.cms.api.utils;
+
+/*-
+ * #%L
+ * cms-api
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+/**
+ *
+ * @author thorstenmarx
+ */
+public class SectionUtilTest {
+
+ @ParameterizedTest
+ @CsvSource({
+ "index.asection.md, asection",
+ "index.asection.1.md, asection",
+ "index.asection.blabla.md, asection"
+ })
+ public void test_getSectionName(String filename, String sectionname) {
+ Assertions.assertThat(SectionUtil.getSectionName(filename)).isEqualTo(sectionname);
+ }
+}
diff --git a/cms-auth/pom.xml b/cms-auth/pom.xml
index 79a5caf7f..b7468bab2 100644
--- a/cms-auth/pom.xml
+++ b/cms-auth/pom.xml
@@ -4,7 +4,7 @@
com.condation.cms
cms-parent
- 7.8.0
+ 8.0.0
cms-auth
jar
diff --git a/cms-auth/src/main/java/com/condation/cms/auth/permissions/Permission.java b/cms-auth/src/main/java/com/condation/cms/auth/permissions/Permission.java
new file mode 100644
index 000000000..a7a122dd2
--- /dev/null
+++ b/cms-auth/src/main/java/com/condation/cms/auth/permissions/Permission.java
@@ -0,0 +1,31 @@
+package com.condation.cms.auth.permissions;
+
+/*-
+ * #%L
+ * cms-auth
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+/**
+ *
+ * @author thmar
+ */
+public record Permission(String key, String description) {
+ public static final Permission CONTENT_EDIT = new Permission("content.edit", "Edit content");
+}
diff --git a/cms-auth/src/main/java/com/condation/cms/auth/permissions/PermissionRegistry.java b/cms-auth/src/main/java/com/condation/cms/auth/permissions/PermissionRegistry.java
new file mode 100644
index 000000000..378aebcb8
--- /dev/null
+++ b/cms-auth/src/main/java/com/condation/cms/auth/permissions/PermissionRegistry.java
@@ -0,0 +1,48 @@
+package com.condation.cms.auth.permissions;
+
+/*-
+ * #%L
+ * cms-auth
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author thmar
+ */
+public class PermissionRegistry {
+
+ private static final Map registry = new HashMap<>();
+
+ public static void register(Permission permission) {
+ registry.put(permission.key(), permission);
+ }
+
+ public static Permission get(String key) {
+ return registry.get(key);
+ }
+
+ public static Collection all() {
+ return registry.values();
+ }
+}
diff --git a/cms-auth/src/main/java/com/condation/cms/auth/services/AuthService.java b/cms-auth/src/main/java/com/condation/cms/auth/services/AuthService.java
index 86ffeb303..522ae7ab0 100644
--- a/cms-auth/src/main/java/com/condation/cms/auth/services/AuthService.java
+++ b/cms-auth/src/main/java/com/condation/cms/auth/services/AuthService.java
@@ -79,18 +79,18 @@ public Optional find (final String path) {
public static class AuthPath {
private String path;
private String realm;
- private List groups;
+ private List roles;
- public boolean allowed (UserService.User user) {
- if (user.groups() == null || user.groups().length == 0) {
+ public boolean allowed (User user) {
+ if (user.roles() == null || user.roles().length == 0) {
return false;
}
- if (groups == null || groups.isEmpty()) {
+ if (roles == null || roles.isEmpty()) {
return false;
}
- for (String group : user.groups()) {
- if (groups.contains(group)) {
+ for (String role : user.roles()) {
+ if (roles.contains(role)) {
return true;
}
}
diff --git a/cms-auth/src/main/java/com/condation/cms/auth/services/AuthorizationService.java b/cms-auth/src/main/java/com/condation/cms/auth/services/AuthorizationService.java
new file mode 100644
index 000000000..ce29e8fcf
--- /dev/null
+++ b/cms-auth/src/main/java/com/condation/cms/auth/services/AuthorizationService.java
@@ -0,0 +1,119 @@
+package com.condation.cms.auth.services;
+
+/*-
+ * #%L
+ * cms-auth
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.api.auth.Permissions;
+import com.condation.cms.auth.permissions.Permission;
+import com.condation.cms.auth.permissions.PermissionRegistry;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Authorization service for role and permission checks.
+ *
+ * Permissions are managed via a central registry.
+ * Roles only contain permission keys so that modules can add custom permissions.
+ */
+public class AuthorizationService {
+
+ /**
+ * Predefined roles with associated permission keys.
+ * These can later be extended via configuration or by modules.
+ */
+ public enum Role {
+ EDITOR(Set.of(Permissions.CONTENT_EDIT)),
+ MANAGER(Set.of(Permissions.CONTENT_EDIT, Permissions.CACHE_INVALIDATE)),
+ ADMIN(Set.of(Permissions.CONTENT_EDIT, Permissions.CACHE_INVALIDATE));
+
+ private final Set permissionKeys;
+
+ Role(Set permissionKeys) {
+ this.permissionKeys = permissionKeys;
+ }
+
+ public Set getPermissionKeys() {
+ return permissionKeys;
+ }
+
+ public static Role fromString(String role) {
+ try {
+ return Role.valueOf(role.toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException e) {
+ return null; // ignore unknown roles
+ }
+ }
+ }
+
+ /**
+ * Collects all permission keys of a given user (based on their roles).
+ */
+ public Set getPermissionKeys(User user) {
+ if (user == null || user.roles() == null) {
+ return Set.of();
+ }
+ return Arrays.stream(user.roles())
+ .map(Role::fromString)
+ .filter(Objects::nonNull)
+ .flatMap(r -> r.getPermissionKeys().stream())
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * Checks if a user has a specific permission.
+ * @param user
+ * @param permissionKey
+ * @return
+ */
+ public boolean hasPermission(User user, String permissionKey) {
+ return getPermissionKeys(user).contains(permissionKey);
+ }
+
+ /**
+ * Checks if a user has at least one of the given permissions.
+ * @param user
+ * @param required
+ * @return
+ */
+ public boolean hasAnyPermission(User user, String... required) {
+ Set userPerms = getPermissionKeys(user);
+ for (String key : required) {
+ if (userPerms.contains(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if a user has all of the given permissions.
+ */
+ public boolean hasAllPermissions(User user, String... required) {
+ Set userPerms = getPermissionKeys(user);
+ return userPerms.containsAll(Set.of(required));
+ }
+
+ // --- Register default/core permissions ---
+ static {
+ PermissionRegistry.register(Permission.CONTENT_EDIT);
+ }
+}
diff --git a/cms-auth/src/main/java/com/condation/cms/auth/services/Realm.java b/cms-auth/src/main/java/com/condation/cms/auth/services/Realm.java
new file mode 100644
index 000000000..57090960f
--- /dev/null
+++ b/cms-auth/src/main/java/com/condation/cms/auth/services/Realm.java
@@ -0,0 +1,36 @@
+package com.condation.cms.auth.services;
+
+/*-
+ * #%L
+ * cms-auth
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+
+
+/**
+ *
+ * @author thorstenmarx
+ */
+public record Realm (String name) {
+
+ public static Realm of(String name) {
+ return new Realm(name);
+ }
+}
diff --git a/cms-auth/src/main/java/com/condation/cms/auth/services/User.java b/cms-auth/src/main/java/com/condation/cms/auth/services/User.java
new file mode 100644
index 000000000..088616589
--- /dev/null
+++ b/cms-auth/src/main/java/com/condation/cms/auth/services/User.java
@@ -0,0 +1,76 @@
+package com.condation.cms.auth.services;
+
+/*-
+ * #%L
+ * cms-auth
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.core.configuration.GSONProvider;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+
+
+/**
+ *
+ * @author thorstenmarx
+ */
+public record User (String username, String passwordHash, String [] roles, Map data) {
+
+ public User(String username, String passwordHash, String[] roles) {
+ this(username, passwordHash, roles, Collections.emptyMap());
+ }
+
+ public String line() {
+ try {
+ String json = GSONProvider.GSON.toJson(data != null ? data : Map.of());
+ String encodedData = Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8));
+ return "%s:%s:%s:%s\r\n".formatted(username, passwordHash, roles != null ? String.join(",", roles) : "", encodedData);
+ } catch (Exception e) {
+ throw new RuntimeException("Error writing user data", e);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof User other)) {
+ return false;
+ }
+ return Objects.equals(username, other.username) && Objects.equals(passwordHash, other.passwordHash) && Arrays.equals(roles, other.roles) && Objects.equals(data, other.data);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(username, passwordHash, data);
+ result = 31 * result + Arrays.hashCode(roles);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "User{" + "username='" + username + '\'' + ", passwordHash='***'" + ", roles=" + Arrays.toString(roles) + ", data=" + data + '}';
+ }
+}
diff --git a/cms-auth/src/main/java/com/condation/cms/auth/services/UserService.java b/cms-auth/src/main/java/com/condation/cms/auth/services/UserService.java
index a1ddf92a5..799d82fba 100644
--- a/cms-auth/src/main/java/com/condation/cms/auth/services/UserService.java
+++ b/cms-auth/src/main/java/com/condation/cms/auth/services/UserService.java
@@ -32,12 +32,10 @@
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -55,9 +53,13 @@ public class UserService {
private final static Splitter userSplitter = Splitter.on(":").trimResults();
private final static Splitter groupSplitter = Splitter.on(",").trimResults();
- private final Path hostBase;
+ private final Path serverBase;
- public void addUser(Realm realm, String username, String password, String[] groups) throws IOException {
+ public void addUser (Realm realm, String username, String password, String[] roles) throws Exception {
+ addUser(realm, username, password, roles, Map.of());
+ }
+
+ public void addUser(Realm realm, String username, String password, String[] roles, Map data) throws IOException {
List users = loadUsers(realm);
users = new ArrayList<>(users.stream()
.filter(user -> !user.username().equals(username))
@@ -67,19 +69,28 @@ public void addUser(Realm realm, String username, String password, String[] grou
String saltBase64 = Base64.getEncoder().encodeToString(salt);
String passwordHash = SecurityUtil.hashPBKDF2(password, salt);
- Map data = new HashMap<>();
- data.put("salt", saltBase64);
+ Map userData = new HashMap<>(data);
+ userData.put("salt", saltBase64);
- users.add(new User(username, passwordHash, groups, data));
+ users.add(new User(username, passwordHash, roles, userData));
saveUsers(realm, users);
}
public void removeUser(Realm realm, String username) throws IOException {
- var users = loadUsers(realm);
- users = new ArrayList<>(users.stream().filter(user -> !user.username.equals(username)).toList());
+ java.util.List users = loadUsers(realm);
+ users = new ArrayList<>(users.stream().filter(user -> !user.username().equals(username)).toList());
saveUsers(realm, users);
}
+ public Optional byUsername(final Realm realm, final String username) {
+ try {
+ return loadUsers(realm).stream().filter(user -> user.username().equals(username)).findFirst();
+ } catch (Exception ex) {
+ log.error("", ex);
+ }
+ return Optional.empty();
+ }
+
private static User fromString(final String userString) {
List userParts = userSplitter.splitToList(userString);
@@ -102,7 +113,7 @@ private static User fromString(final String userString) {
}
private List loadUsers(final Realm realm) throws IOException {
- Path usersFile = hostBase.resolve("config/" + FILENAME_PATTERN.formatted(realm.name));
+ Path usersFile = serverBase.resolve("config/" + FILENAME_PATTERN.formatted(realm.name()));
List users = new ArrayList<>();
if (Files.exists(usersFile)) {
List lines = Files.readAllLines(usersFile, StandardCharsets.UTF_8);
@@ -123,7 +134,7 @@ private List loadUsers(final Realm realm) throws IOException {
public Optional login(final Realm realm, final String username, final String password) {
try {
- var userOpt = loadUsers(realm).stream()
+ java.util.Optional userOpt = loadUsers(realm).stream()
.filter(user -> user.username().equals(username))
.findFirst();
@@ -153,9 +164,11 @@ public Optional login(final Realm realm, final String username, final Stri
}
private void saveUsers(Realm realm, List users) throws IOException {
- Path usersFile = hostBase.resolve("config/" + FILENAME_PATTERN.formatted(realm.name));
+ Path usersFile = serverBase.resolve("config/" + FILENAME_PATTERN.formatted(realm.name()));
Files.deleteIfExists(usersFile);
+ Files.createDirectories(usersFile.getParent());
+
StringBuilder userContent = new StringBuilder();
users.forEach(user -> userContent.append(user.line()));
@@ -163,49 +176,5 @@ private void saveUsers(Realm realm, List users) throws IOException {
Files.writeString(usersFile, userContent, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
}
- public static record User(String username, String passwordHash, String[] groups, Map data) {
-
- public String line() {
- try {
- String json = GSONProvider.GSON.toJson(data != null ? data : Map.of());
- String encodedData = Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8));
- return "%s:%s:%s:%s\r\n".formatted(
- username,
- passwordHash,
- groups != null ? String.join(",", groups) : "",
- encodedData
- );
- } catch (Exception e) {
- throw new RuntimeException("Error writing user data", e);
- }
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof User other)) {
- return false;
- }
- return Objects.equals(username, other.username)
- && Objects.equals(passwordHash, other.passwordHash)
- && Arrays.equals(groups, other.groups)
- && Objects.equals(data, other.data);
- }
-
- @Override
- public int hashCode() {
- int result = Objects.hash(username, passwordHash, data);
- result = 31 * result + Arrays.hashCode(groups);
- return result;
- }
- }
-
- public static record Realm(String name) {
- public static Realm of(String name) {
- return new Realm(name);
- }
- }
}
diff --git a/cms-auth/src/test/java/com/condation/cms/auth/services/AuthorizationServiceTest.java b/cms-auth/src/test/java/com/condation/cms/auth/services/AuthorizationServiceTest.java
new file mode 100644
index 000000000..1f656820e
--- /dev/null
+++ b/cms-auth/src/test/java/com/condation/cms/auth/services/AuthorizationServiceTest.java
@@ -0,0 +1,96 @@
+package com.condation.cms.auth.services;
+
+/*-
+ * #%L
+ * cms-auth
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.api.auth.Permissions;
+import com.condation.cms.auth.permissions.Permission;
+import com.condation.cms.auth.permissions.PermissionRegistry;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class AuthorizationServiceTest {
+
+ private AuthorizationService authorizationService;
+
+ @BeforeEach
+ void setUp() {
+ authorizationService = new AuthorizationService();
+ }
+
+ @Test
+ void editorShouldHaveEditPermissionButNotPublishPermission() {
+ User editor = new User("editor", "hash", new String[]{"editor"});
+
+ assertThat(authorizationService.hasPermission(editor, Permissions.CONTENT_EDIT)).isTrue();
+ assertThat(authorizationService.hasPermission(editor, Permissions.CACHE_INVALIDATE)).isFalse();
+ }
+
+ @Test
+ void managerShouldHaveEditAndPublishPermissions() {
+ User manager = new User("manager", "hash", new String[]{"manager"});
+
+ assertThat(authorizationService.hasPermission(manager, Permissions.CONTENT_EDIT)).isTrue();
+ assertThat(authorizationService.hasPermission(manager, Permissions.CACHE_INVALIDATE)).isTrue();
+ }
+
+ @Test
+ void adminShouldHaveAllCorePermissions() {
+ User admin = new User("admin", "hash", new String[]{"admin"});
+
+ assertThat(authorizationService.hasPermission(admin, Permissions.CONTENT_EDIT)).isTrue();
+ assertThat(authorizationService.hasPermission(admin, Permissions.CACHE_INVALIDATE)).isTrue();
+ }
+
+ @Test
+ void hasAnyPermissionShouldWorkCorrectly() {
+ User manager = new User("manager", "hash", new String[]{"manager"});
+
+ assertThat(authorizationService.hasAnyPermission(manager, Permissions.CACHE_INVALIDATE, Permissions.CONTENT_EDIT)).isTrue();
+ assertThat(authorizationService.hasAllPermissions(manager, Permissions.CACHE_INVALIDATE, "user.manage")).isFalse();
+ }
+
+ @Test
+ void hasAllPermissionsShouldWorkCorrectly() {
+ User admin = new User("admin", "hash", new String[]{"admin"});
+
+ assertThat(authorizationService.hasAllPermissions(admin, Permissions.CACHE_INVALIDATE, Permissions.CONTENT_EDIT)).isTrue();
+ assertThat(authorizationService.hasAllPermissions(admin, Permissions.CONTENT_EDIT, "unknown.permission")).isFalse();
+ }
+
+ @Test
+ void customModulePermissionShouldBeRegisterable() {
+ // Register a custom permission from a module
+ PermissionRegistry.register(
+ new Permission("blog.write", "Write blog posts")
+ );
+
+ // Extend role manually (ADMIN should conceptually have all, but for test we check via key set)
+ User admin = new User("admin", "hash", new String[]{"admin"});
+
+ assertThat(PermissionRegistry.get("blog.write")).isNotNull();
+ // Admin does not automatically get blog.write unless Role.ADMIN is extended.
+ assertThat(authorizationService.hasPermission(admin, "blog.write")).isFalse();
+ }
+}
diff --git a/cms-auth/src/test/java/com/condation/cms/auth/services/UserServiceTest.java b/cms-auth/src/test/java/com/condation/cms/auth/services/UserServiceTest.java
index e4fc05fb3..c71591d6b 100644
--- a/cms-auth/src/test/java/com/condation/cms/auth/services/UserServiceTest.java
+++ b/cms-auth/src/test/java/com/condation/cms/auth/services/UserServiceTest.java
@@ -49,9 +49,9 @@ public static void setup () throws IOException {
@Test
- public void test_login_and_remove() throws IOException {
+ public void test_login_and_remove() throws Exception {
- var realm = UserService.Realm.of("users");
+ com.condation.cms.auth.services.Realm realm = Realm.of("users");
Assertions.assertThat(userService.login(realm, "test", "demo")).isEmpty();
@@ -65,9 +65,9 @@ public void test_login_and_remove() throws IOException {
}
@Test
- public void test_multiple_users() throws IOException {
+ public void test_multiple_users() throws Exception {
- var realm = UserService.Realm.of("musers");
+ com.condation.cms.auth.services.Realm realm = Realm.of("musers");
userService.addUser(realm, "test1", "demo", new String[]{"eins","zwei"});
userService.addUser(realm, "test2", "demo", new String[]{"eins","zwei"});
diff --git a/cms-auth/src/test/resources/hosts/demo/config/auth.yaml b/cms-auth/src/test/resources/hosts/demo/config/auth.yaml
index 9ca86f398..38080c7ae 100644
--- a/cms-auth/src/test/resources/hosts/demo/config/auth.yaml
+++ b/cms-auth/src/test/resources/hosts/demo/config/auth.yaml
@@ -1,4 +1,4 @@
paths:
- path: "/secured"
realm: "users"
- groups: ["eins"]
\ No newline at end of file
+ roles: ["eins"]
\ No newline at end of file
diff --git a/cms-content/pom.xml b/cms-content/pom.xml
index 132b80aa2..55db26983 100644
--- a/cms-content/pom.xml
+++ b/cms-content/pom.xml
@@ -6,16 +6,33 @@
com.condation.cms
cms-parent
- 7.8.0
+ 8.0.0
cms-content
jar
-
-
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+
+ --enable-preview
+
+
+
+
+
+
com.condation.cms
cms-api
+
+ com.condation.cms
+ cms-core
+
com.condation.cms
cms-filesystem
diff --git a/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java b/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java
index c4ac5eac4..42e9efdcc 100644
--- a/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java
+++ b/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java
@@ -34,6 +34,7 @@
import com.condation.cms.api.request.RequestContext;
import com.condation.cms.api.utils.HTTPUtil;
import com.condation.cms.api.utils.PathUtil;
+import com.condation.cms.core.content.ContentResolvingStrategy;
import com.google.common.base.Strings;
import java.io.IOException;
import java.util.List;
@@ -86,6 +87,7 @@ public Optional getErrorContent (final RequestContext context)
}
private Optional getContent(final RequestContext context, boolean checkVisibility) {
+ /*
String path;
if (Strings.isNullOrEmpty(context.get(RequestFeature.class).uri())) {
path = "";
@@ -111,7 +113,12 @@ private Optional getContent(final RequestContext context, boole
contentFile = temp;
}
}
+ */
+ var contentBase = db.getReadOnlyFileSystem().contentBase();
+ var path = ContentResolvingStrategy.uriToPath(context.get(RequestFeature.class).uri());
+ Optional contentFileOpt = ContentResolvingStrategy.resolve(context.get(RequestFeature.class).uri(), db);
+ ReadOnlyFile contentFile = contentFileOpt.orElse(null);
// handle alias
ContentNode contentNode = null;
boolean aliasRedirect = false;
@@ -125,7 +132,10 @@ private Optional getContent(final RequestContext context, boole
}
} else {
var uri = PathUtil.toRelativeFile(contentFile, contentBase);
- contentNode = db.getContent().byUri(uri).get();
+ final Optional nodeByUri = db.getContent().byUri(uri);
+ if (nodeByUri.isPresent()) {
+ contentNode = nodeByUri.get();
+ }
}
if (contentNode == null) {
diff --git a/cms-content/src/main/java/com/condation/cms/content/DefaultContentParser.java b/cms-content/src/main/java/com/condation/cms/content/DefaultContentParser.java
index 068146c63..b2d14486b 100644
--- a/cms-content/src/main/java/com/condation/cms/content/DefaultContentParser.java
+++ b/cms-content/src/main/java/com/condation/cms/content/DefaultContentParser.java
@@ -27,8 +27,6 @@
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
import lombok.extern.slf4j.Slf4j;
import org.yaml.snakeyaml.Yaml;
diff --git a/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java b/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java
index 0dcdbf242..95503f72b 100644
--- a/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java
+++ b/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java
@@ -36,6 +36,7 @@
import com.condation.cms.api.feature.features.InjectorFeature;
import com.condation.cms.api.feature.features.IsDevModeFeature;
import com.condation.cms.api.feature.features.IsPreviewFeature;
+import com.condation.cms.api.feature.features.MarkdownRendererFeature;
import com.condation.cms.api.feature.features.RequestFeature;
import com.condation.cms.api.feature.features.ServerPropertiesFeature;
import com.condation.cms.api.feature.features.SiteMediaServiceFeature;
@@ -47,15 +48,19 @@
import com.condation.cms.api.utils.SectionUtil;
import com.condation.cms.content.pipeline.ContentPipelineFactory;
import com.condation.cms.content.views.model.View;
-import com.condation.cms.core.content.MapAccess;
+import com.condation.cms.api.content.MapAccess;
+import com.condation.cms.api.utils.HTTPUtil;
import com.condation.cms.extensions.hooks.DBHooks;
import com.condation.cms.extensions.hooks.TemplateHooks;
import com.condation.cms.content.template.functions.LinkFunction;
+import com.condation.cms.content.template.functions.MarkdownFunction;
import com.condation.cms.content.template.functions.list.NodeListFunctionBuilder;
import com.condation.cms.content.template.functions.navigation.NavigationFunction;
import com.condation.cms.content.template.functions.query.QueryFunction;
-import com.condation.cms.content.template.functions.shortcode.ShortCodeTemplateFunction;
+import com.condation.cms.content.template.functions.tag.TagTemplateFunction;
import com.condation.cms.content.template.functions.taxonomy.TaxonomyFunction;
+import com.condation.cms.content.template.functions.translation.NodeTranslations;
+import com.condation.cms.content.template.functions.translation.SiteTranslations;
import com.condation.modules.api.ModuleManager;
import java.io.IOException;
import java.util.ArrayList;
@@ -137,44 +142,48 @@ public String render(final ReadOnlyFile contentFile, final RequestContext contex
TemplateEngine.Model model = new TemplateEngine.Model(
contentFile,
- contentNode.isPresent() ? contentNode.get() : null,
+ contentNode.orElse(null),
context);
modelExtending.accept(model);
Namespace namespace = new Namespace();
- model.values.put("meta", new MapAccess(meta));
- model.values.put("sections", sections);
-
- namespace.add("node", "meta", new MapAccess(meta));
- namespace.add("node", "sections", sections);
+ namespace.add(Constants.TemplateNamespaces.NODE, "meta", new MapAccess(meta));
+ namespace.add(Constants.TemplateNamespaces.NODE, "sections", sections);
+ namespace.add(Constants.TemplateNamespaces.NODE, "uri", uri);
+ namespace.add(Constants.TemplateNamespaces.NODE, "translation", new NodeTranslations(contentNode.orElse(null), siteProperties));
+
+ var canonicalUrl = "";
+ if (contentNode.isPresent()) {
+ canonicalUrl = PathUtil.toURL(contentNode.get().uri());
+ canonicalUrl = HTTPUtil.modifyUrl(canonicalUrl, siteProperties);
+ }
+ namespace.add(Constants.TemplateNamespaces.NODE, "canonicalUrl", canonicalUrl);
- ShortCodeTemplateFunction shortCodeFunction = createShortCodeFunction(context);
- model.values.put(ShortCodeTemplateFunction.KEY, shortCodeFunction);
- namespace.add("cms", ShortCodeTemplateFunction.KEY, shortCodeFunction);
+ TagTemplateFunction tagFunction = createTagFunction(context);
+ namespace.add(Constants.TemplateNamespaces.CMS, TagTemplateFunction.KEY, tagFunction);
NavigationFunction navigationFunction = createNavigationFunction(contentFile, context);
- model.values.put("navigation", navigationFunction);
- namespace.add("cms", "navigation", shortCodeFunction);
+ namespace.add(Constants.TemplateNamespaces.CMS, "navigation", navigationFunction);
NodeListFunctionBuilder nodeListFunction = createNodeListFunction(contentFile, context);
- model.values.put("nodeList", nodeListFunction);
- namespace.add("cms", "nodeList", nodeListFunction);
+ namespace.add(Constants.TemplateNamespaces.CMS, "nodeList", nodeListFunction);
QueryFunction queryFunction = createQueryFunction(contentFile, context);
- model.values.put("query", queryFunction);
- namespace.add("cms", "query", queryFunction);
+ namespace.add(Constants.TemplateNamespaces.CMS, "query", queryFunction);
+ MarkdownFunction markdownFunction = createMarkdownFunction(context);
+ namespace.add(Constants.TemplateNamespaces.CMS, "markdown", markdownFunction);
+
model.values.put("requestContext", context.get(RequestFeature.class));
model.values.put("theme", context.get(RenderContext.class).theme());
- model.values.put("site", siteProperties);
- model.values.put("mediaService", context.get(SiteMediaServiceFeature.class).mediaService());
- namespace.add("cms", "mediaService", context.get(SiteMediaServiceFeature.class).mediaService());
-
- model.values.put("taxonomies", context.get(InjectorFeature.class).injector().getInstance(TaxonomyFunction.class));
- namespace.add("cms", "taxonomies", context.get(InjectorFeature.class).injector().getInstance(TaxonomyFunction.class));
+ namespace.add(Constants.TemplateNamespaces.CMS, "mediaService", context.get(SiteMediaServiceFeature.class).mediaService());
+ namespace.add(Constants.TemplateNamespaces.CMS, "taxonomies", context.get(InjectorFeature.class).injector().getInstance(TaxonomyFunction.class));
+ namespace.add(Constants.TemplateNamespaces.SITE, "properties", siteProperties);
+ namespace.add(Constants.TemplateNamespaces.SITE, "translation", new SiteTranslations(siteProperties));
+
var theme = context.get(RenderContext.class).theme();
if (theme.empty()) {
model.values.put("messages", context.get(InjectorFeature.class).injector().getInstance(MessageSource.class));
@@ -182,13 +191,12 @@ public String render(final ReadOnlyFile contentFile, final RequestContext contex
model.values.put("messages", theme.getMessages());
}
- model.values.put("hooks", context.get(HookSystemFeature.class).hookSystem());
- namespace.add("cms", "hooks", context.get(HookSystemFeature.class).hookSystem());
+ namespace.add(Constants.TemplateNamespaces.CMS, "hooks", context.get(HookSystemFeature.class).hookSystem());
- model.values.put("links", new LinkFunction(context));
- namespace.add("cms", "links", new LinkFunction(context));
+ namespace.add(Constants.TemplateNamespaces.CMS, "links", new LinkFunction(context));
model.values.put("PREVIEW_MODE", isPreview(context));
+ model.values.put("MANAGER", isManager(context));
model.values.put("DEV_MODE", isDevMode(context));
model.values.put("ENV", context.get(ServerPropertiesFeature.class).serverProperties().env());
@@ -198,11 +206,7 @@ public String render(final ReadOnlyFile contentFile, final RequestContext contex
context.get(TemplateHooks.class).getTemplateSupplier().getRegisterTemplateSupplier().forEach(service -> {
model.values.put(service.name(), service.supplier());
- namespace.add(Constants.DEFAULT_MODULE_NAMESPACE, service.name(), service.supplier());
- });
- context.get(TemplateHooks.class).getTemplateFunctions().getRegisterTemplateFunctions().forEach(service -> {
- model.values.put(service.name(), service.function());
- namespace.add(Constants.DEFAULT_MODULE_NAMESPACE, service.name(), service.function());
+ namespace.add(Constants.TemplateNamespaces.DEFAULT_MODULE_NAMESPACE, service.name(), service.supplier());
});
extendModel(model, namespace);
@@ -212,13 +216,17 @@ public String render(final ReadOnlyFile contentFile, final RequestContext contex
String content = renderContent(rawContent, context, modelCopy);
model.values.put("content", content);
- namespace.add("node", "content", content);
+ namespace.add(Constants.TemplateNamespaces.NODE, "content", content);
model.values.putAll(namespace.getNamespaces());
return templates.get().render((String) meta.get("template"), model);
}
+ protected MarkdownFunction createMarkdownFunction(final RequestContext context) {
+ return new MarkdownFunction(context.get(MarkdownRendererFeature.class).markdownRenderer());
+ }
+
protected QueryFunction createQueryFunction(final ReadOnlyFile contentFile, final RequestContext context) {
Map> customOperators = new HashMap<>();
@@ -250,6 +258,13 @@ private boolean isPreview(final RequestContext context) {
return context.has(IsPreviewFeature.class);
}
+ private boolean isManager(final RequestContext context) {
+ if (context.has(IsPreviewFeature.class)) {
+ return context.get(IsPreviewFeature.class).mode().equals(IsPreviewFeature.Mode.MANAGER);
+ }
+ return false;
+ }
+
private boolean isDevMode(final RequestContext context) {
return context.has(IsDevModeFeature.class);
}
@@ -282,13 +297,13 @@ public Map> renderSections(final List section
var sectionPath = contentBase.resolve(node.uri());
var content = render(sectionPath, context);
var name = SectionUtil.getSectionName(node.name());
- var index = SectionUtil.getSectionIndex(node.name());
+ var index = node.getMetaValue(Constants.MetaFields.LAYOUT_ORDER, Constants.DEFAULT_SECTION_LAYOUT_ORDER);
if (!sections.containsKey(name)) {
sections.put(name, new ArrayList<>());
}
- sections.get(name).add(new Section(name, index, content));
+ sections.get(name).add(new Section(name, index, content, node.data()));
} catch (Exception ex) {
log.error("error render section", ex);
}
@@ -300,8 +315,8 @@ public Map> renderSections(final List section
return sections;
}
- private ShortCodeTemplateFunction createShortCodeFunction(RequestContext context) {
- return new ShortCodeTemplateFunction(context, context.get(RenderContext.class).shortCodes());
+ private TagTemplateFunction createTagFunction(RequestContext context) {
+ return new TagTemplateFunction(context, context.get(RenderContext.class).tags());
}
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/RenderContext.java b/cms-content/src/main/java/com/condation/cms/content/RenderContext.java
index 5931e72af..7a1e98d3a 100644
--- a/cms-content/src/main/java/com/condation/cms/content/RenderContext.java
+++ b/cms-content/src/main/java/com/condation/cms/content/RenderContext.java
@@ -27,7 +27,7 @@
import com.condation.cms.api.feature.Feature;
import com.condation.cms.api.markdown.MarkdownRenderer;
import com.condation.cms.api.theme.Theme;
-import com.condation.cms.content.shortcodes.ShortCodes;
+import com.condation.cms.content.tags.Tags;
import lombok.extern.slf4j.Slf4j;
/**
@@ -36,7 +36,7 @@
*/
@Slf4j
@FeatureScope(FeatureScope.Scope.REQUEST)
-public record RenderContext(MarkdownRenderer markdownRenderer, ShortCodes shortCodes, Theme theme)
+public record RenderContext(MarkdownRenderer markdownRenderer, Tags tags, Theme theme)
implements AutoCloseable, Feature {
@Override
diff --git a/cms-content/src/main/java/com/condation/cms/content/Section.java b/cms-content/src/main/java/com/condation/cms/content/Section.java
index d9a5e3f18..74f53d932 100644
--- a/cms-content/src/main/java/com/condation/cms/content/Section.java
+++ b/cms-content/src/main/java/com/condation/cms/content/Section.java
@@ -24,15 +24,20 @@
import com.condation.cms.api.Constants;
+import java.util.Map;
/**
*
* @author t.marx
*/
-public record Section(String name, int index, String content) {
+public record Section(String name, int index, String content, Map data, String uri) {
- public Section(String name, String content) {
- this(name, Constants.DEFAULT_SECTION_ORDERED_INDEX, content);
+ public Section(String name, int index, String content, Map data) {
+ this(name, index, content, data, null);
+ }
+
+ public Section(String name, String content, Map data) {
+ this(name, Constants.DEFAULT_SECTION_LAYOUT_ORDER, content, data, null);
}
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/markdown/InlineBlock.java b/cms-content/src/main/java/com/condation/cms/content/markdown/InlineBlock.java
index 5f9cd492b..b407968e4 100644
--- a/cms-content/src/main/java/com/condation/cms/content/markdown/InlineBlock.java
+++ b/cms-content/src/main/java/com/condation/cms/content/markdown/InlineBlock.java
@@ -1,5 +1,10 @@
package com.condation.cms.content.markdown;
+import com.condation.cms.api.feature.features.IsPreviewFeature;
+import com.condation.cms.api.request.RequestContext;
+import com.condation.cms.api.request.RequestContextScope;
+import java.util.Optional;
+
/*-
* #%L
* cms-content
@@ -21,15 +26,30 @@
* .
* #L%
*/
-
-
/**
*
* @author t.marx
*/
public interface InlineBlock {
+
int start();
+
int end();
+
+ String render();
+
+ default boolean isPreview() {
+ if (!RequestContextScope.REQUEST_CONTEXT.isBound()) {
+ return false;
+ }
+ var requestContext = RequestContextScope.REQUEST_CONTEXT.get();
+ return requestContext != null && requestContext.has(IsPreviewFeature.class);
+ }
- String render ();
+ default Optional getRequestContext () {
+ if (!RequestContextScope.REQUEST_CONTEXT.isBound()) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(RequestContextScope.REQUEST_CONTEXT.get());
+ }
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/markdown/Options.java b/cms-content/src/main/java/com/condation/cms/content/markdown/Options.java
index e670fa3eb..8d543bcd0 100644
--- a/cms-content/src/main/java/com/condation/cms/content/markdown/Options.java
+++ b/cms-content/src/main/java/com/condation/cms/content/markdown/Options.java
@@ -29,7 +29,7 @@
import com.condation.cms.content.markdown.rules.block.HeadingBlockRule;
import com.condation.cms.content.markdown.rules.block.HorizontalRuleBlockRule;
import com.condation.cms.content.markdown.rules.block.ListBlockRule;
-import com.condation.cms.content.markdown.rules.block.ShortCodeBlockRule;
+import com.condation.cms.content.markdown.rules.block.TagBlockRule;
import com.condation.cms.content.markdown.rules.block.TableBlockRule;
import com.condation.cms.content.markdown.rules.block.TaskListBlockRule;
import com.condation.cms.content.markdown.rules.inline.HighlightInlineRule;
@@ -38,7 +38,7 @@
import com.condation.cms.content.markdown.rules.inline.ItalicInlineRule;
import com.condation.cms.content.markdown.rules.inline.LinkInlineRule;
import com.condation.cms.content.markdown.rules.inline.NewlineInlineRule;
-import com.condation.cms.content.markdown.rules.inline.ShortCodeInlineBlockRule;
+import com.condation.cms.content.markdown.rules.inline.TagInlineBlockRule;
import com.condation.cms.content.markdown.rules.inline.StrikethroughInlineRule;
import com.condation.cms.content.markdown.rules.inline.StrongInlineRule;
import com.condation.cms.content.markdown.rules.inline.SubscriptInlineRule;
@@ -54,7 +54,7 @@ public class Options {
public static Options all () {
Options options = new Options();
- options.addInlineRule(new ShortCodeInlineBlockRule());
+ options.addInlineRule(new TagInlineBlockRule());
options.addInlineRule(new StrongInlineRule());
options.addInlineRule(new ItalicInlineRule());
options.addInlineRule(new NewlineInlineRule());
@@ -66,7 +66,7 @@ public static Options all () {
options.addInlineRule(new SubscriptInlineRule());
options.addInlineRule(new SuperscriptInlineRule());
- options.addBlockRule(new ShortCodeBlockRule());
+ options.addBlockRule(new TagBlockRule());
options.addBlockRule(new CodeBlockRule());
options.addBlockRule(new HeadingBlockRule());
options.addBlockRule(new TaskListBlockRule());
diff --git a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/block/ShortCodeBlockRule.java b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/block/TagBlockRule.java
similarity index 89%
rename from cms-content/src/main/java/com/condation/cms/content/markdown/rules/block/ShortCodeBlockRule.java
rename to cms-content/src/main/java/com/condation/cms/content/markdown/rules/block/TagBlockRule.java
index eaab38343..8db72fb5c 100644
--- a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/block/ShortCodeBlockRule.java
+++ b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/block/TagBlockRule.java
@@ -24,15 +24,15 @@
import com.condation.cms.content.markdown.Block;
import com.condation.cms.content.markdown.BlockElementRule;
import com.condation.cms.content.markdown.InlineRenderer;
-import com.condation.cms.content.shortcodes.TagMap;
-import com.condation.cms.content.shortcodes.TagParser;
+import com.condation.cms.content.tags.TagMap;
+import com.condation.cms.content.tags.TagParser;
import java.util.List;
/**
*
* @author t.marx
*/
-public class ShortCodeBlockRule implements BlockElementRule {
+public class TagBlockRule implements BlockElementRule {
private static final TagParser tagParser = new TagParser(null);
@@ -51,14 +51,14 @@ public boolean has(String codeName) {
return null;
}
var tag = tags.getFirst();
- return new ShortCodeBlock(
+ return new TagBlock(
tag.startIndex(),
tag.endIndex(),
tag);
}
- public static record ShortCodeBlock(int start, int end, TagParser.TagInfo tagInfo) implements Block {
+ public static record TagBlock(int start, int end, TagParser.TagInfo tagInfo) implements Block {
@Override
public String render(InlineRenderer inlineRenderer) {
@@ -73,7 +73,7 @@ public String render(InlineRenderer inlineRenderer) {
.formatted(
tagInfo.name(),
String.join(" ", params),
- tagInfo.rawAttributes().getOrDefault("_content", ""),
+ inlineRenderer.render((String)tagInfo.rawAttributes().getOrDefault("_content", "")),
tagInfo.name()
);
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ImageInlineRule.java b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ImageInlineRule.java
index 4aa4f7854..b0540506e 100644
--- a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ImageInlineRule.java
+++ b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ImageInlineRule.java
@@ -23,8 +23,11 @@
*/
+import com.condation.cms.api.feature.features.SiteMediaServiceFeature;
+import com.condation.cms.api.utils.ImageUtil;
import com.condation.cms.content.markdown.InlineBlock;
import com.condation.cms.content.markdown.InlineElementRule;
+import com.google.common.base.Strings;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -50,12 +53,26 @@ public static record ImageInlineBlock(int start, int end, String src, String alt
@Override
public String render() {
+ var altText = alt;
+ var requestContext = getRequestContext();
+ if (Strings.isNullOrEmpty(altText) && requestContext.isPresent()) {
+ var imageUrl = ImageUtil.getRawPath(src, requestContext.get());
+ var media = requestContext.get().get(SiteMediaServiceFeature.class).mediaService().get(imageUrl);
+
+ if (media != null && media.meta().containsKey("alt")) {
+ altText = (String) media.meta().get("alt");
+ }
+ }
+
+ var uiSelector = "";
+ if (isPreview()) {
+ uiSelector = " data-cms-ui-selector=\"content-image\" ";
+ }
+
if (title != null && !"".equals(title.trim())) {
- return " ".formatted(src, alt, title);
+ return " ".formatted(src, altText, title, uiSelector);
}
- return " ".formatted(src, alt);
+ return " ".formatted(src, altText, uiSelector);
}
-
}
-
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ImageLinkInlineRule.java b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ImageLinkInlineRule.java
index 24cb57489..cf164fcee 100644
--- a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ImageLinkInlineRule.java
+++ b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ImageLinkInlineRule.java
@@ -24,7 +24,7 @@
import com.github.slugify.Slugify;
import com.condation.cms.api.feature.features.IsPreviewFeature;
import com.condation.cms.api.feature.features.SitePropertiesFeature;
-import com.condation.cms.api.request.ThreadLocalRequestContext;
+import com.condation.cms.api.request.RequestContextScope;
import com.condation.cms.content.markdown.InlineBlock;
import com.condation.cms.content.markdown.InlineElementRule;
import java.util.regex.Pattern;
@@ -52,11 +52,13 @@ public InlineBlock next(String md) {
var id = SLUG.slugify(alt);
- var requestContext = ThreadLocalRequestContext.REQUEST_CONTEXT.get();
+
- if (requestContext != null
+ if (RequestContextScope.REQUEST_CONTEXT.isBound()
&& isInternalUrl(href)) {
-
+
+ var requestContext = RequestContextScope.REQUEST_CONTEXT.get();
+
if (requestContext.has(SitePropertiesFeature.class)) {
var contextPath = requestContext.get(SitePropertiesFeature.class).siteProperties().contextPath();
if (!"/".equals(contextPath) && !href.startsWith(contextPath) && href.startsWith("/")) {
@@ -64,10 +66,11 @@ && isInternalUrl(href)) {
}
}
if (requestContext.has(IsPreviewFeature.class)) {
+ var previewContext = requestContext.get(IsPreviewFeature.class);
if (href.contains("?")) {
- href += "&preview";
+ href += "&preview=" + previewContext.mode().getValue();
} else {
- href += "?preview";
+ href += "?preview=" + previewContext.mode().getValue();
}
}
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/LinkInlineRule.java b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/LinkInlineRule.java
index ca77987a1..ca8b1c9ec 100644
--- a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/LinkInlineRule.java
+++ b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/LinkInlineRule.java
@@ -25,7 +25,7 @@
import com.github.slugify.Slugify;
import com.condation.cms.api.feature.features.IsPreviewFeature;
import com.condation.cms.api.feature.features.SitePropertiesFeature;
-import com.condation.cms.api.request.ThreadLocalRequestContext;
+import com.condation.cms.api.request.RequestContextScope;
import com.condation.cms.content.markdown.InlineBlock;
import com.condation.cms.content.markdown.InlineElementRule;
import java.util.regex.Pattern;
@@ -51,11 +51,12 @@ public InlineBlock next(String md) {
var id = SLUG.slugify(text);
- var requestContext = ThreadLocalRequestContext.REQUEST_CONTEXT.get();
-
- if (requestContext != null
+
+ if (RequestContextScope.REQUEST_CONTEXT.isBound()
&& isInternalUrl(href)) {
+ var requestContext = RequestContextScope.REQUEST_CONTEXT.get();
+
if (requestContext.has(SitePropertiesFeature.class)) {
var contextPath = requestContext.get(SitePropertiesFeature.class).siteProperties().contextPath();
if (!"/".equals(contextPath) && !href.startsWith(contextPath) && href.startsWith("/")) {
@@ -63,10 +64,11 @@ && isInternalUrl(href)) {
}
}
if (requestContext.has(IsPreviewFeature.class)) {
+ var previewContext = requestContext.get(IsPreviewFeature.class);
if (href.contains("?")) {
- href += "&preview";
+ href += "&preview=" + previewContext.mode().getValue();
} else {
- href += "?preview";
+ href += "?preview=" + previewContext.mode().getValue();
}
}
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ShortCodeInlineBlockRule.java b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/TagInlineBlockRule.java
similarity index 87%
rename from cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ShortCodeInlineBlockRule.java
rename to cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/TagInlineBlockRule.java
index 62080ce1f..953fbb10c 100644
--- a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ShortCodeInlineBlockRule.java
+++ b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/TagInlineBlockRule.java
@@ -23,15 +23,15 @@
*/
import com.condation.cms.content.markdown.InlineBlock;
import com.condation.cms.content.markdown.InlineElementRule;
-import com.condation.cms.content.shortcodes.TagMap;
-import com.condation.cms.content.shortcodes.TagParser;
+import com.condation.cms.content.tags.TagMap;
+import com.condation.cms.content.tags.TagParser;
import java.util.List;
/**
*
* @author t.marx
*/
-public class ShortCodeInlineBlockRule implements InlineElementRule {
+public class TagInlineBlockRule implements InlineElementRule {
private static final TagParser tagParser = new TagParser(null);
@@ -48,13 +48,13 @@ public boolean has(String codeName) {
return null;
}
var tag = tags.getFirst();
- return new ShortCodeInlineBlock(
+ return new TagInlineBlock(
tag.startIndex(),
tag.endIndex(),
tag);
}
- public static record ShortCodeInlineBlock(int start, int end, TagParser.TagInfo tagInfo) implements InlineBlock {
+ public static record TagInlineBlock(int start, int end, TagParser.TagInfo tagInfo) implements InlineBlock {
@Override
public String render() {
diff --git a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java
index 508b125cb..d50d14720 100644
--- a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java
+++ b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java
@@ -58,7 +58,7 @@ protected void init() {
pipeline.forEach(processor -> {
switch (processor) {
case "markdown" -> hookSystem.registerFilter(Hooks.CONTENT_FILTER.hook(), this::processMarkdown, prio.getAndAdd(10));
- case "shortcode" -> hookSystem.registerFilter(Hooks.CONTENT_FILTER.hook(), this::processShortCodes, prio.getAndAdd(10));
+ case "tags" -> hookSystem.registerFilter(Hooks.CONTENT_FILTER.hook(), this::processTags, prio.getAndAdd(10));
case "template" -> hookSystem.registerFilter(Hooks.CONTENT_FILTER.hook(), this::processTemplate, prio.getAndAdd(10));
}
});
@@ -73,8 +73,8 @@ private String processMarkdown(FilterContext context) {
return requestContext.get(RenderContext.class).markdownRenderer().render(context.value());
}
- private String processShortCodes(FilterContext context) {
- return requestContext.get(RenderContext.class).shortCodes().replace(context.value(), model.values, requestContext);
+ private String processTags(FilterContext context) {
+ return requestContext.get(RenderContext.class).tags().replace(context.value(), model.values, requestContext);
}
private String processTemplate(FilterContext context) {
diff --git a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipelineFactory.java b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipelineFactory.java
index 9cd1a31dd..73c1c5a37 100644
--- a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipelineFactory.java
+++ b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipelineFactory.java
@@ -23,6 +23,7 @@
*/
import com.condation.cms.api.feature.features.HookSystemFeature;
+import com.condation.cms.api.hooks.HookSystem;
import com.condation.cms.api.request.RequestContext;
import com.condation.cms.api.template.TemplateEngine;
import lombok.AccessLevel;
@@ -38,7 +39,7 @@ public final class ContentPipelineFactory {
public static ContentPipeline create (final RequestContext requestContext, final TemplateEngine.Model model) {
var hookSystem = requestContext.get(HookSystemFeature.class).hookSystem();
- var pipeline = new ContentPipeline(hookSystem.clone(), requestContext, model);
+ var pipeline = new ContentPipeline(new HookSystem(hookSystem), requestContext, model);
pipeline.init();
return pipeline;
diff --git a/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodeParser.java b/cms-content/src/main/java/com/condation/cms/content/tags/ShortCodeParser.java
similarity index 98%
rename from cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodeParser.java
rename to cms-content/src/main/java/com/condation/cms/content/tags/ShortCodeParser.java
index be7c57566..c61edd180 100644
--- a/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodeParser.java
+++ b/cms-content/src/main/java/com/condation/cms/content/tags/ShortCodeParser.java
@@ -1,4 +1,4 @@
-package com.condation.cms.content.shortcodes;
+package com.condation.cms.content.tags;
/*-
* #%L
diff --git a/cms-content/src/main/java/com/condation/cms/content/shortcodes/TagMap.java b/cms-content/src/main/java/com/condation/cms/content/tags/TagMap.java
similarity index 97%
rename from cms-content/src/main/java/com/condation/cms/content/shortcodes/TagMap.java
rename to cms-content/src/main/java/com/condation/cms/content/tags/TagMap.java
index b8daff203..dedaee969 100644
--- a/cms-content/src/main/java/com/condation/cms/content/shortcodes/TagMap.java
+++ b/cms-content/src/main/java/com/condation/cms/content/tags/TagMap.java
@@ -1,4 +1,4 @@
-package com.condation.cms.content.shortcodes;
+package com.condation.cms.content.tags;
/*-
* #%L
diff --git a/cms-content/src/main/java/com/condation/cms/content/shortcodes/TagParser.java b/cms-content/src/main/java/com/condation/cms/content/tags/TagParser.java
similarity index 73%
rename from cms-content/src/main/java/com/condation/cms/content/shortcodes/TagParser.java
rename to cms-content/src/main/java/com/condation/cms/content/tags/TagParser.java
index e7f16aac3..3f91cd504 100644
--- a/cms-content/src/main/java/com/condation/cms/content/shortcodes/TagParser.java
+++ b/cms-content/src/main/java/com/condation/cms/content/tags/TagParser.java
@@ -1,4 +1,4 @@
-package com.condation.cms.content.shortcodes;
+package com.condation.cms.content.tags;
/*-
* #%L
@@ -59,11 +59,19 @@ public List findTags(String text, TagMap tagHandlers) {
tagContent = tagContent.substring(0, tagContent.length() - 1).trim();
}
- int spaceIndex = tagContent.indexOf(' ');
- String tagName = spaceIndex == -1 ? tagContent : tagContent.substring(0, spaceIndex);
- Parameter rawAttributes = spaceIndex == -1
+ // Suche erstes Whitespace-Zeichen (auch Zeilenumbrüche etc.)
+ int firstWhitespaceIndex = -1;
+ for (int j = 0; j < tagContent.length(); j++) {
+ if (Character.isWhitespace(tagContent.charAt(j))) {
+ firstWhitespaceIndex = j;
+ break;
+ }
+
+ }
+ String tagName = firstWhitespaceIndex == -1 ? tagContent : tagContent.substring(0, firstWhitespaceIndex);
+ Parameter rawAttributes = firstWhitespaceIndex == -1
? new Parameter()
- : parseRawAttributes(tagContent.substring(spaceIndex + 1));
+ : parseRawAttributes(tagContent.substring(firstWhitespaceIndex + 1));
int closingTagIndex = -1;
if (!isSelfClosing) {
@@ -124,9 +132,9 @@ public String parse(String text, TagMap tagHandlers, Map context
return result.toString();
}
- // Methode zum Finden des Endes eines Tags
+ // Methode zum Finden des Endes eines Tags, auch über mehrere Zeilen
private int findTagEnd(String text, int startIndex) {
- for (int i = startIndex; i < text.length() - 1; i++) {
+ for (int i = startIndex + 2; i < text.length() - 1; i++) {
if (text.charAt(i) == ']' && text.charAt(i + 1) == ']') {
return i;
}
@@ -134,39 +142,73 @@ private int findTagEnd(String text, int startIndex) {
return -1; // Kein schließendes ']]' gefunden
}
- // Methode zur Attribut-Analyse im ersten Schritt (Rohwerte als Strings speichern)
private Parameter parseRawAttributes(String attributesString) {
Parameter attributes = new Parameter();
- StringBuilder key = new StringBuilder();
+ String key = null;
StringBuilder value = new StringBuilder();
boolean inQuotes = false;
- boolean readingKey = true;
+ char quoteChar = 0;
+ boolean readingValue = false;
+ StringBuilder buffer = new StringBuilder();
for (int i = 0; i < attributesString.length(); i++) {
char c = attributesString.charAt(i);
- if (c == '"' || c == '\'') {
- inQuotes = !inQuotes;
- } else if (!inQuotes && (c == '=' || c == ' ')) {
- if (readingKey) {
- readingKey = false;
- } else {
- attributes.put(key.toString().trim(), value.toString().trim()); // Rohwert speichern
- key.setLength(0);
+
+ if (c == '\n' || c == '\r') {
+ // Zeilenumbrüche im Attributwert oder Key sind nicht erlaubt → aktuelles Attribut abbrechen
+ key = null;
+ value.setLength(0);
+ readingValue = false;
+ inQuotes = false;
+ buffer.setLength(0);
+ continue;
+ }
+
+ if (!inQuotes && (c == '"' || c == '\'')) {
+ inQuotes = true;
+ quoteChar = c;
+ continue;
+ }
+
+ if (inQuotes && c == quoteChar) {
+ inQuotes = false;
+ if (key != null) {
+ attributes.put(key.trim(), value.toString().trim());
+ key = null;
value.setLength(0);
- readingKey = true;
+ readingValue = false;
}
- } else {
- if (readingKey) {
- key.append(c);
- } else {
- value.append(c);
+ continue;
+ }
+
+ if (!inQuotes && c == '=') {
+ key = buffer.toString().trim();
+ buffer.setLength(0);
+ readingValue = true;
+ continue;
+ }
+
+ if (!inQuotes && Character.isWhitespace(c)) {
+ if (readingValue && key != null && value.length() > 0) {
+ // Nur dann speichern, wenn der Wert abgeschlossen wurde (z. B. name="abc")
+ attributes.put(key.trim(), value.toString().trim());
+ key = null;
+ value.setLength(0);
+ readingValue = false;
}
+ continue;
+ }
+
+ if (readingValue) {
+ value.append(c);
+ } else {
+ buffer.append(c);
}
}
- // Letztes Attribut verarbeiten
- if (key.length() > 0 && value.length() > 0) {
- attributes.put(key.toString().trim(), value.toString().trim()); // Rohwert speichern
+ // Falls etwas am Ende übrig bleibt (nur gültig, wenn kein Zeilenumbruch):
+ if (key != null && value.length() > 0 && !inQuotes) {
+ attributes.put(key.trim(), value.toString().trim());
}
return attributes;
@@ -195,9 +237,9 @@ private Object parseValue(String value, Map contextModel, Reques
var contextMap = new HashMap();
contextMap.putAll(contextModel);
if (requestContext != null) {
- contextMap.putAll(requestContext.getVariables());
+ contextMap.putAll(requestContext.getVariables());
}
-
+
var expression = engine.createExpression(expressionString);
return expression.evaluate(new MapContext(contextMap));
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodes.java b/cms-content/src/main/java/com/condation/cms/content/tags/Tags.java
similarity index 65%
rename from cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodes.java
rename to cms-content/src/main/java/com/condation/cms/content/tags/Tags.java
index ba0d1c6a4..c3b72cc1e 100644
--- a/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodes.java
+++ b/cms-content/src/main/java/com/condation/cms/content/tags/Tags.java
@@ -1,4 +1,4 @@
-package com.condation.cms.content.shortcodes;
+package com.condation.cms.content.tags;
/*-
* #%L
@@ -21,10 +21,9 @@
* .
* #L%
*/
-import com.condation.cms.api.annotations.ShortCode;
import com.condation.cms.api.model.Parameter;
import com.condation.cms.api.request.RequestContext;
-import java.lang.reflect.Method;
+import com.condation.cms.api.utils.AnnotationsUtil;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -33,6 +32,7 @@
import java.util.function.Function;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import com.condation.cms.api.annotations.Tag;
/**
*
@@ -40,18 +40,18 @@
*/
@Slf4j
@RequiredArgsConstructor
-public class ShortCodes {
+public class Tags {
private final TagMap tagMap;
private final TagParser parser;
- public ShortCodes(Map> codes, TagParser tagParser) {
+ public Tags(Map> codes, TagParser tagParser) {
this.parser = tagParser;
this.tagMap = new TagMap();
this.tagMap.putAll(codes);
}
- public Set getShortCodeNames() {
+ public Set getTagNames() {
return tagMap.names();
}
@@ -85,7 +85,7 @@ public String execute(String name, Map parameters, RequestContex
return "";
}
- public static ShortCodes.Builder builder(TagParser tagParser) {
+ public static Tags.Builder builder(TagParser tagParser) {
return new Builder(tagParser);
}
@@ -93,63 +93,56 @@ public static class Builder {
private final TagParser tagParser;
- private final Map> codes = new HashMap<>();
+ private final Map> tags = new HashMap<>();
private Builder(TagParser tagParser) {
this.tagParser = tagParser;
}
- public Builder register(String name, Function shortCodeFN) {
- codes.put(name, shortCodeFN);
+ public Builder register(String name, Function tagFN) {
+ tags.put(name, tagFN);
return this;
}
-
- public Builder register (Map> codes) {
- this.codes.putAll(codes);
+
+ public Builder register(Map> codes) {
+ this.tags.putAll(codes);
return this;
}
- public Builder register (List handlers) {
+ public Builder register(List handlers) {
if (handlers == null || handlers.isEmpty()) {
return this;
}
-
+
handlers.forEach(this::register);
-
+
return this;
}
-
+
public Builder register(Object handler) {
if (handler == null) {
return this;
}
- Class> clazz = handler.getClass();
- for (Method method : clazz.getDeclaredMethods()) {
- if (method.isAnnotationPresent(ShortCode.class)) {
- if (method.getParameterCount() == 1 && method.getParameterTypes()[0] == Parameter.class) {
- method.setAccessible(true); // falls private
- ShortCode annotation = method.getAnnotation(ShortCode.class);
- String key = annotation.value();
-
- codes.put(key, param -> {
- try {
- return (String) method.invoke(handler, param);
- } catch (Exception e) {
- throw new RuntimeException("Error calling shortcode: " + key, e);
- }
- });
- } else {
- log.error("ignore methode" + method.getName() + " – wrong signature.");
+ // Wir erwarten Methoden mit @Tag(Parameter) -> String
+ var annotations = AnnotationsUtil.process(handler, Tag.class, List.of(Parameter.class), String.class);
+
+ for (var entry : annotations) {
+ String key = entry.annotation().value();
+ tags.put(key, param -> {
+ try {
+ return entry.invoke(param);
+ } catch (Exception e) {
+ throw new RuntimeException("Error calling tag: " + key, e);
}
- }
+ });
}
return this;
}
- public ShortCodes build() {
- return new ShortCodes(codes, tagParser);
+ public Tags build() {
+ return new Tags(tags, tagParser);
}
}
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/template/functions/AbstractCurrentNodeFunction.java b/cms-content/src/main/java/com/condation/cms/content/template/functions/AbstractCurrentNodeFunction.java
index 6fd1d5899..bad4c26ec 100644
--- a/cms-content/src/main/java/com/condation/cms/content/template/functions/AbstractCurrentNodeFunction.java
+++ b/cms-content/src/main/java/com/condation/cms/content/template/functions/AbstractCurrentNodeFunction.java
@@ -28,7 +28,7 @@
import com.condation.cms.api.mapper.ContentNodeMapper;
import com.condation.cms.api.markdown.MarkdownRenderer;
import com.condation.cms.api.request.RequestContext;
-import com.condation.cms.api.request.ThreadLocalRequestContext;
+import com.condation.cms.api.request.RequestContextScope;
import com.condation.cms.api.utils.HTTPUtil;
import java.io.IOException;
import java.util.Optional;
@@ -78,13 +78,21 @@ protected String getUrl(ReadOnlyFile node) {
}
protected boolean isPreview() {
- if (ThreadLocalRequestContext.REQUEST_CONTEXT.get() != null
- && ThreadLocalRequestContext.REQUEST_CONTEXT.get().has(IsPreviewFeature.class)) {
+ if (RequestContextScope.REQUEST_CONTEXT.isBound()
+ && RequestContextScope.REQUEST_CONTEXT.get().has(IsPreviewFeature.class)) {
return true;
}
return false;
}
+ protected String getPreviewMode() {
+ if (RequestContextScope.REQUEST_CONTEXT.isBound()
+ && RequestContextScope.REQUEST_CONTEXT.get().has(IsPreviewFeature.class)) {
+ return RequestContextScope.REQUEST_CONTEXT.get().get(IsPreviewFeature.class).mode().getValue();
+ }
+
+ return IsPreviewFeature.Mode.PREVIEW.getValue();
+ }
protected Optional parse(ReadOnlyFile node) {
try {
diff --git a/cms-content/src/main/java/com/condation/cms/content/template/functions/MarkdownFunction.java b/cms-content/src/main/java/com/condation/cms/content/template/functions/MarkdownFunction.java
new file mode 100644
index 000000000..285cb8a6d
--- /dev/null
+++ b/cms-content/src/main/java/com/condation/cms/content/template/functions/MarkdownFunction.java
@@ -0,0 +1,38 @@
+package com.condation.cms.content.template.functions;
+
+/*-
+ * #%L
+ * cms-content
+ * %%
+ * Copyright (C) 2023 - 2025 CondationCMS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+
+import com.condation.cms.api.markdown.MarkdownRenderer;
+
+public class MarkdownFunction {
+
+ private final MarkdownRenderer renderer;
+
+ public MarkdownFunction(MarkdownRenderer renderer) {
+ this.renderer = renderer;
+ }
+
+ public String render (String markdown) {
+ return renderer.render(markdown);
+ }
+}
diff --git a/cms-content/src/main/java/com/condation/cms/content/template/functions/list/NodeListFunctionBuilder.java b/cms-content/src/main/java/com/condation/cms/content/template/functions/list/NodeListFunctionBuilder.java
index 4dc4cacc3..14d4c7eb8 100644
--- a/cms-content/src/main/java/com/condation/cms/content/template/functions/list/NodeListFunctionBuilder.java
+++ b/cms-content/src/main/java/com/condation/cms/content/template/functions/list/NodeListFunctionBuilder.java
@@ -21,7 +21,6 @@
* .
* #L%
*/
-
import com.condation.cms.api.Constants;
import com.condation.cms.api.db.ContentNode;
import com.condation.cms.api.db.DB;
@@ -56,7 +55,7 @@ public class NodeListFunctionBuilder extends AbstractCurrentNodeFunction {
String sort = "title";
boolean reverse = false;
-
+
String contentType = Constants.DEFAULT_CONTENT_TYPE;
final NodeListFunction nodeListFunction;
@@ -95,37 +94,37 @@ public NodeListFunctionBuilder contentType(String contentType) {
this.contentType = contentType;
return this;
}
-
+
public NodeListFunctionBuilder json() {
this.contentType = "application/json";
return this;
}
-
+
public NodeListFunctionBuilder excerpt(long length) {
- this.excerptLength = (int)length;
+ this.excerptLength = (int) length;
return this;
}
-
+
public NodeListFunctionBuilder page(long page) {
- this.page = (int)page;
+ this.page = (int) page;
return this;
}
-
+
public NodeListFunctionBuilder page(String page) {
this.page = Integer.parseInt(page.trim());
return this;
}
public NodeListFunctionBuilder size(long size) {
- this.size = (int)size;
+ this.size = (int) size;
return this;
}
-
+
public NodeListFunctionBuilder size(String size) {
this.size = Integer.parseInt(size.trim());
return this;
}
-
+
public NodeListFunctionBuilder index(boolean index) {
this.index = index;
return this;
@@ -156,26 +155,33 @@ public Page list() {
}
private Comparator