diff --git a/cms-core/pom.xml b/cms-core/pom.xml
index c12c589e9e..112a8ad3be 100644
--- a/cms-core/pom.xml
+++ b/cms-core/pom.xml
@@ -436,6 +436,16 @@
handlebars
+
+
+ org.apache.groovy
+ groovy
+
+
+ org.apache.groovy
+ groovy-json
+
+
org.glassfish.jersey.containers
diff --git a/cms-core/src/main/java/com/gentics/api/ChannelScope.java b/cms-core/src/main/java/com/gentics/api/ChannelScope.java
new file mode 100644
index 0000000000..8b7c65a9a9
--- /dev/null
+++ b/cms-core/src/main/java/com/gentics/api/ChannelScope.java
@@ -0,0 +1,40 @@
+package com.gentics.api;
+
+import com.gentics.api.lib.exception.NodeException;
+import com.gentics.contentnode.factory.ChannelTrx;
+import com.gentics.contentnode.factory.NoMcTrx;
+import com.gentics.contentnode.object.Node;
+import com.gentics.contentnode.object.parttype.NodePartType;
+import com.gentics.contentnode.render.RenderUtils;
+
+import groovy.lang.Closure;
+
+/**
+ * Channel Scope helper which should be used in Groovy Scripts to load objects in the scope of channels
+ */
+public class ChannelScope {
+ /**
+ * Private constructor
+ */
+ private ChannelScope() {
+ }
+
+ /**
+ * Run the closure body in a {@link ChannelTrx}
+ * @param type of the return value
+ * @param scope scope (channel)
+ * @param body closure body
+ * @return return value
+ * @throws NodeException
+ */
+ public static T withChannel(Object scope, Closure body) throws NodeException {
+ Node node = null;
+ try (final NoMcTrx nMcTrx = new NoMcTrx()) {
+ node = RenderUtils.getObject(scope, Node.class, NodePartType.class, NodePartType::getNode);
+ }
+
+ try (ChannelTrx cTrx = new ChannelTrx(node)) {
+ return body.call(cTrx);
+ }
+ }
+}
diff --git a/cms-core/src/main/java/com/gentics/api/Loader.java b/cms-core/src/main/java/com/gentics/api/Loader.java
new file mode 100644
index 0000000000..44529d12a9
--- /dev/null
+++ b/cms-core/src/main/java/com/gentics/api/Loader.java
@@ -0,0 +1,220 @@
+package com.gentics.api;
+
+import static com.gentics.contentnode.render.RenderUtils.wrap;
+
+import java.util.Map;
+
+import com.gentics.api.lib.exception.NodeException;
+import com.gentics.api.lib.resolving.Resolvable;
+import com.gentics.contentnode.factory.Transaction;
+import com.gentics.contentnode.factory.TransactionManager;
+import com.gentics.contentnode.object.File;
+import com.gentics.contentnode.object.Folder;
+import com.gentics.contentnode.object.ImageFile;
+import com.gentics.contentnode.object.Node;
+import com.gentics.contentnode.object.NodeObject;
+import com.gentics.contentnode.object.Page;
+import com.gentics.lib.log.NodeLogger;
+
+/**
+ * Loader helper, which should be used in Groovy Scripts to load objects
+ */
+public class Loader {
+ private static NodeLogger logger = NodeLogger.getNodeLogger(Loader.class);
+
+ /**
+ * Map of types to type ids
+ */
+ private final static Map TYPES = Map.of(
+ "page", Page.TYPE_PAGE,
+ "folder", Folder.TYPE_FOLDER,
+ "file", File.TYPE_FILE,
+ "image", ImageFile.TYPE_IMAGE,
+ "node", Node.TYPE_NODE);
+
+ /**
+ * Private constructor
+ */
+ private Loader() {
+ }
+
+ /**
+ * Load object with given type and id
+ * @param type object type
+ * @param id object id (as integer)
+ * @return loaded object or null, if not found
+ * @throws NodeException
+ */
+ public static Resolvable object(int type, int id) throws NodeException {
+ Transaction t = TransactionManager.getCurrentTransaction();
+
+ Class extends NodeObject> clazz = t.getClass(type);
+ if (clazz == null) {
+ logger.warn("Unable to load object of unknown type ID %d".formatted(type));
+ return null;
+ }
+
+ NodeObject object = t.getObject(clazz, id);
+ if (object == null && type == File.TYPE_FILE) {
+ return object(ImageFile.TYPE_IMAGE, id);
+ }
+
+ return wrap(object);
+ }
+
+ /**
+ * Load object with given type and id
+ * @param type object type
+ * @param id object id (as string)
+ * @return loaded object or null, if not found
+ * @throws NodeException
+ */
+ public static Resolvable object(int type, String id) throws NodeException {
+ Transaction t = TransactionManager.getCurrentTransaction();
+
+ Class extends NodeObject> clazz = t.getClass(type);
+ if (clazz == null) {
+ logger.warn("Unable to load object of unknown type ID %d".formatted(type));
+ return null;
+ }
+
+ NodeObject object = t.getObject(clazz, id);
+ if (object == null && type == File.TYPE_FILE) {
+ return object(ImageFile.TYPE_IMAGE, id);
+ }
+
+ return wrap(object);
+ }
+
+ /**
+ * Load object with given type and id
+ * @param type type
+ * @param id id (as integer)
+ * @return loaded object or null, if not found
+ * @throws NodeException
+ */
+ public static Resolvable object(String type, int id) throws NodeException {
+ if (TYPES.containsKey(type)) {
+ return object(TYPES.get(type), id);
+ } else {
+ logger.warn("Unable to load object of unknown type %s".formatted(type));
+ return null;
+ }
+ }
+
+ /**
+ * Load object with given type and id
+ * @param type type
+ * @param id id (as string)
+ * @return loaded object or null, if not found
+ * @throws NodeException
+ */
+ public static Resolvable object(String type, String id) throws NodeException {
+ if (TYPES.containsKey(type)) {
+ return object(TYPES.get(type), id);
+ } else {
+ logger.warn("Unable to load object of unknown type %s".formatted(type));
+ return null;
+ }
+ }
+
+ /**
+ * Load page
+ * @param id page id
+ * @return wrapped page
+ * @throws NodeException
+ */
+ public static Resolvable page(int id) throws NodeException {
+ return object(Page.TYPE_PAGE, id);
+ }
+
+ /**
+ * Load page
+ * @param id page id
+ * @return wrapped page
+ * @throws NodeException
+ */
+ public static Resolvable page(String id) throws NodeException {
+ return object(Page.TYPE_PAGE, id);
+ }
+
+ /**
+ * Load folder
+ * @param id folder id
+ * @return wrapped folder
+ * @throws NodeException
+ */
+ public static Resolvable folder(int id) throws NodeException {
+ return object(Folder.TYPE_FOLDER, id);
+ }
+
+ /**
+ * Load folder
+ * @param id folder id
+ * @return wrapped folder
+ * @throws NodeException
+ */
+ public static Resolvable folder(String id) throws NodeException {
+ return object(Folder.TYPE_FOLDER, id);
+ }
+
+ /**
+ * Load file
+ * @param id file id
+ * @return wrapped file
+ * @throws NodeException
+ */
+ public static Resolvable file(int id) throws NodeException {
+ return object(File.TYPE_FILE, id);
+ }
+
+ /**
+ * Load file
+ * @param id file id
+ * @return wrapped file
+ * @throws NodeException
+ */
+ public static Resolvable file(String id) throws NodeException {
+ return object(File.TYPE_FILE, id);
+ }
+
+ /**
+ * Load image
+ * @param id image id
+ * @return wrapped image
+ * @throws NodeException
+ */
+ public static Resolvable image(int id) throws NodeException {
+ return object(ImageFile.TYPE_IMAGE, id);
+ }
+
+ /**
+ * Load image
+ * @param id image id
+ * @return wrapped image
+ * @throws NodeException
+ */
+ public static Resolvable image(String id) throws NodeException {
+ return object(ImageFile.TYPE_IMAGE, id);
+ }
+
+ /**
+ * Load node
+ * @param id node id
+ * @return wrapped node
+ * @throws NodeException
+ */
+ public static Resolvable node(int id) throws NodeException {
+ return object(Node.TYPE_NODE, id);
+ }
+
+ /**
+ * Load node
+ * @param id node id
+ * @return wrapped node
+ * @throws NodeException
+ */
+ public static Resolvable node(String id) throws NodeException {
+ return object(Node.TYPE_NODE, id);
+ }
+}
diff --git a/cms-core/src/main/java/com/gentics/contentnode/devtools/AbstractSynchronizer.java b/cms-core/src/main/java/com/gentics/contentnode/devtools/AbstractSynchronizer.java
index bfde4df53f..8eeac9f107 100644
--- a/cms-core/src/main/java/com/gentics/contentnode/devtools/AbstractSynchronizer.java
+++ b/cms-core/src/main/java/com/gentics/contentnode/devtools/AbstractSynchronizer.java
@@ -28,6 +28,7 @@
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Strings;
import com.gentics.api.lib.etc.ObjectTransformer;
import com.gentics.api.lib.exception.NodeException;
@@ -559,8 +560,10 @@ protected void jsonToFile(Object object, File file) throws NodeException {
*/
protected String getProposedFilename(Part part) {
// special case for HandlebarsPartType
- if (part.getPartTypeId() == 43) {
+ if (part.getPartTypeId() == Part.HANDLEBARS) {
return "part." + part.getKeyname() + ".hbs";
+ } else if (part.getPartTypeId() == Part.GROOVY) {
+ return "part." + part.getKeyname() + ".groovy";
}
switch (Property.Type.get(part.getPartTypeId())) {
case STRING:
@@ -593,7 +596,7 @@ protected boolean isJsonFile(Part part) {
* @return true iff the filename belongs to a part value
*/
protected boolean isPartFilename(String filename) {
- return filename.startsWith("part.") && (filename.endsWith(".txt") || filename.endsWith(".html") || filename.endsWith(".json") || filename.endsWith(".hbs"));
+ return Strings.CI.startsWith(filename, "part.") && Strings.CI.endsWithAny(filename, ".txt", ".html", ".json", ".hbs", ".groovy");
}
/**
diff --git a/cms-core/src/main/java/com/gentics/contentnode/devtools/MainPackageSynchronizer.java b/cms-core/src/main/java/com/gentics/contentnode/devtools/MainPackageSynchronizer.java
index da16c49a5d..d1b6c3cbc6 100644
--- a/cms-core/src/main/java/com/gentics/contentnode/devtools/MainPackageSynchronizer.java
+++ b/cms-core/src/main/java/com/gentics/contentnode/devtools/MainPackageSynchronizer.java
@@ -38,7 +38,7 @@ public class MainPackageSynchronizer extends PackageSynchronizer {
* List containing invalid subpackage directory names
*/
public final static List INVALID_SUBPACKAGE_NAMES = Arrays.asList(CONSTRUCTS_DIR, DATASOURCES_DIR, OBJECTPROPERTIES_DIR, TEMPLATES_DIR, FILES_DIR,
- CR_FRAGMENTS_DIR, CONTENTREPOSITORIES_DIR, HANDLEBARS_DIR);
+ CR_FRAGMENTS_DIR, CONTENTREPOSITORIES_DIR, HANDLEBARS_DIR, SCRIPTS_DIR);
/**
* Lambda that generates the rest model for a package
diff --git a/cms-core/src/main/java/com/gentics/contentnode/devtools/PackageSynchronizer.java b/cms-core/src/main/java/com/gentics/contentnode/devtools/PackageSynchronizer.java
index 2b459f92af..f19f5ee15f 100644
--- a/cms-core/src/main/java/com/gentics/contentnode/devtools/PackageSynchronizer.java
+++ b/cms-core/src/main/java/com/gentics/contentnode/devtools/PackageSynchronizer.java
@@ -7,7 +7,6 @@
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
-import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
@@ -27,7 +26,7 @@
import java.util.function.Consumer;
import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Strings;
import org.tautua.markdownpapers.Markdown;
import com.gentics.api.lib.exception.NodeException;
@@ -97,6 +96,11 @@ public abstract class PackageSynchronizer {
*/
public final static String HANDLEBARS_DIR = "handlebars";
+ /**
+ * Name of the subdirectory containing (groovy) scripts
+ */
+ public final static String SCRIPTS_DIR = "scripts";
+
/**
* Directory map for object class
*/
@@ -186,6 +190,16 @@ public PackageSynchronizer(Path packagePath, boolean registerWatchers) throws No
// clean the cached handlebars helpers
handlebarsHelpers = null;
});
+ // add a handler for changes in the scripts directory
+ pathHandlers.put(new File(packagePath.toFile(), SCRIPTS_DIR).toPath(), changedPath -> {
+ try {
+ for (Node node : getNodes()) {
+ Synchronizer.invalidateGroovyClassLoader(node);
+ }
+ } catch (NodeException e) {
+ Synchronizer.invalidateGroovyClassLoader();
+ }
+ });
if (registerWatchers) {
try {
Synchronizer.registerAll(packagePath);
@@ -516,11 +530,11 @@ public String getHandlebarsHelpers() throws IOException {
if (helpersDirectory.isDirectory()) {
StringBuilder registerHelpers = new StringBuilder();
- File[] files = helpersDirectory.listFiles((dir, name) -> StringUtils.endsWith(name, ".js"));
+ File[] files = helpersDirectory.listFiles((dir, name) -> Strings.CI.endsWith(name, ".js"));
if (files != null) {
for (File helperFile : files) {
- String helperNameShort = StringUtils.removeEnd(helperFile.getName(), ".js");
+ String helperNameShort = Strings.CI.removeEnd(helperFile.getName(), ".js");
String helperName = String.format("%s.%s", packageName, helperNameShort);
String helperFileContents = FileUtils.readFileToString(helperFile, StandardCharsets.UTF_8);
String register = String.format("Handlebars.registerHelper('%s', %s)", helperName, helperFileContents);
@@ -547,6 +561,20 @@ public File getHandlebarsPartialsDirectory() {
return new File(handlebarsDirectory, "partials");
}
+ /**
+ * Get the script files of the package
+ * @return script files
+ */
+ public File[] getScriptFiles() {
+ File scriptsDirectory = new File(this.packagePath.toFile(), SCRIPTS_DIR);
+
+ if (scriptsDirectory.isDirectory()) {
+ return scriptsDirectory.listFiles((dir, name) -> Strings.CI.endsWith(name, ".groovy"));
+ }
+
+ return new File[0];
+ }
+
/**
* Add a synchronizer implementation
* @param path base path
diff --git a/cms-core/src/main/java/com/gentics/contentnode/devtools/Synchronizer.java b/cms-core/src/main/java/com/gentics/contentnode/devtools/Synchronizer.java
index d400a59dd9..bafd545639 100644
--- a/cms-core/src/main/java/com/gentics/contentnode/devtools/Synchronizer.java
+++ b/cms-core/src/main/java/com/gentics/contentnode/devtools/Synchronizer.java
@@ -20,7 +20,12 @@
import java.util.function.Function;
import java.util.stream.Collectors;
+import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.FileUtils;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.CompilerConfiguration;
+import org.codehaus.groovy.control.Phases;
+import org.codehaus.groovy.tools.GroovyClass;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -36,7 +41,9 @@
import com.gentics.contentnode.etc.NodePreferences;
import com.gentics.contentnode.etc.PrefixedThreadFactory;
import com.gentics.contentnode.etc.QueueWithDelay;
+import com.gentics.contentnode.etc.Timing;
import com.gentics.contentnode.factory.Transaction;
+import com.gentics.contentnode.i18n.I18NHelper;
import com.gentics.contentnode.object.Construct;
import com.gentics.contentnode.object.ContentRepository;
import com.gentics.contentnode.object.Datasource;
@@ -52,6 +59,8 @@
import com.gentics.lib.etc.StringUtils;
import com.gentics.lib.log.NodeLogger;
+import groovy.lang.GroovyClassLoader;
+
/**
* Synchronization implementation for the {@link Feature#DEVTOOLS}
*/
@@ -142,6 +151,11 @@ public class Synchronizer {
*/
private static ObjectMapper alternateMapper;
+ /**
+ * Map of {@link GroovyClassLoader} instances per Node
+ */
+ private static Map groovyClassLoaderPerNode = MapUtils.synchronizedMap(new HashMap<>());
+
static {
AtomicInteger priority = new AtomicInteger();
for (Class extends SynchronizableNodeObject> clazz : CLASSES) {
@@ -470,6 +484,9 @@ public static void removePackage(Node node, String packageName) throws NodeExcep
st.setInt(1, node.getId());
st.setString(2, packageName);
});
+
+ // invalidate the groovy classloader for the node
+ invalidateGroovyClassLoader(node);
}
/**
@@ -494,9 +511,62 @@ public static void addPackage(Node node, String packageName) throws NodeExceptio
AbstractSynchronizer extends SynchronizableNodeObject, ? extends AbstractModel> synchronizer = packageSynchronizer.synchronizersPerClass
.get(clazz);
synchronizer.assignAll(node);
- }
}
+ // invalidate the groovy classloader for the node
+ invalidateGroovyClassLoader(node);
+ }
+
+ /**
+ * Get the {@link GroovyClassLoader} instance for the given Node. The class loader will contain all
+ * classes compiled from scripts found in packages, which are assigned to the node
+ * @param node
+ * @return
+ * @throws NodeException
+ */
+ public static GroovyClassLoader getGroovyClassLoader(Node node) throws NodeException {
+ Set nodePackages = Synchronizer.getPackages(node);
+
+ return groovyClassLoaderPerNode.computeIfAbsent(node, n -> {
+ CompilerConfiguration config = new CompilerConfiguration();
+
+ GroovyClassLoader gcl = new GroovyClassLoader(Synchronizer.class.getClassLoader(), config);
+
+ try (Timing timing = Timing.get(-1, duration -> {
+ logger.info("Compiled scripts for node %s in %d ms".formatted(I18NHelper.getName(node), duration));
+ })) {
+ CompilationUnit unit = new CompilationUnit(config, null, gcl);
+ for (String packageName : nodePackages) {
+ MainPackageSynchronizer mainPack = Synchronizer.getPackage(packageName);
+ unit.addSources(mainPack.getScriptFiles());
+ }
+ unit.compile(Phases.CLASS_GENERATION);
+
+ for (GroovyClass groovyClass : unit.getClasses()) {
+ gcl.defineClass(groovyClass.getName(), groovyClass.getBytes());
+ }
+ } catch (NodeException ignored) {
+ }
+
+ return gcl;
+ });
+ }
+
+ /**
+ * Invalidate the {@link GroovyClassLoader} for the give node
+ * @param node node
+ */
+ public static void invalidateGroovyClassLoader(Node node) {
+ groovyClassLoaderPerNode.remove(node);
+ }
+
+ /**
+ * Invalidate the {@link GroovyClassLoader} for all nodes
+ */
+ public static void invalidateGroovyClassLoader() {
+ groovyClassLoaderPerNode.clear();
+ }
+
/**
* Get the package synchronizer for the given path or null
* @param path path
@@ -530,6 +600,7 @@ public static void removePackageSynchronizer(PackageContainer> container, Path
*/
public static void clearCache() {
container.clearCache();
+ invalidateGroovyClassLoader();
}
/**
diff --git a/cms-core/src/main/java/com/gentics/contentnode/object/Part.java b/cms-core/src/main/java/com/gentics/contentnode/object/Part.java
index ef03139901..a1d8f38a58 100644
--- a/cms-core/src/main/java/com/gentics/contentnode/object/Part.java
+++ b/cms-core/src/main/java/com/gentics/contentnode/object/Part.java
@@ -70,7 +70,9 @@ public abstract class Part extends AbstractContentObject implements I18nNamedNod
public static final int FILEUPLOAD = 38;
public static final int FOLDERUPLOAD = 39;
public static final int NODE = 40;
-
+ public static final int HANDLEBARS = 43;
+ public static final int GROOVY = 45;
+
/**
* The ttype of the part object.
*/
diff --git a/cms-core/src/main/java/com/gentics/contentnode/object/parttype/groovy/GroovyPartType.java b/cms-core/src/main/java/com/gentics/contentnode/object/parttype/groovy/GroovyPartType.java
new file mode 100644
index 0000000000..e60b0bcc6e
--- /dev/null
+++ b/cms-core/src/main/java/com/gentics/contentnode/object/parttype/groovy/GroovyPartType.java
@@ -0,0 +1,120 @@
+package com.gentics.contentnode.object.parttype.groovy;
+
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.Phases;
+import org.codehaus.groovy.tools.GroovyClass;
+
+import com.gentics.api.lib.exception.NodeException;
+import com.gentics.contentnode.factory.Transaction;
+import com.gentics.contentnode.factory.TransactionManager;
+import com.gentics.contentnode.object.Construct;
+import com.gentics.contentnode.object.Part;
+import com.gentics.contentnode.object.Tag;
+import com.gentics.contentnode.object.Value;
+import com.gentics.contentnode.object.ValueContainer;
+import com.gentics.contentnode.object.parttype.TextPartType;
+import com.gentics.contentnode.render.RenderResult;
+import com.gentics.contentnode.render.RenderType;
+import com.gentics.contentnode.resolving.NodeObjectResolverContext;
+import com.gentics.contentnode.resolving.ResolvableGetter;
+import com.gentics.contentnode.rest.model.Property;
+import com.gentics.contentnode.rest.model.Property.Type;
+import com.gentics.contentnode.rest.util.MiscUtils;
+import com.gentics.contentnode.utils.GroovyUtils;
+
+/**
+ * Implementation of the Groovy PartType
+ */
+public class GroovyPartType extends TextPartType {
+
+ private static final long serialVersionUID = 416330914123548280L;
+
+ /**
+ * Create an instance
+ * @param value value
+ * @throws NodeException
+ */
+ public GroovyPartType(Value value) throws NodeException {
+ super(value);
+ }
+
+ /**
+ * Execute the groovy script
+ * @return
+ * @throws NodeException
+ */
+ @ResolvableGetter
+ public Object getExecute() throws NodeException {
+ String code = getText();
+
+ if (StringUtils.isBlank(code)) {
+ return null;
+ }
+
+ Transaction t = TransactionManager.getCurrentTransaction();
+ RenderType renderType = t.getRenderType();
+
+ Tag tag = NodeObjectResolverContext.getNodeObject(Tag.class);
+
+ if (null != tag) {
+ renderType.push(tag);
+ }
+ try {
+ renderType.createCMSResolver();
+ try {
+ CompilationUnit unit = GroovyUtils.getCurrentCompilationUnit();
+
+ Value value = getValueObject();
+ String constructKeyword = Optional.ofNullable(value).map(v -> MiscUtils.execOrNull(Value::getContainer, v))
+ .map(cont -> MiscUtils.execOrNull(ValueContainer::getConstruct, cont)).map(Construct::getKeyword)
+ .orElse("");
+ String partKeyword = Optional.ofNullable(value).map(v -> MiscUtils.execOrNull(Value::getPart, v))
+ .map(Part::getKeyname).orElse("");
+ int valueId = Optional.ofNullable(value).map(Value::getId).orElse(0);
+ String scriptClassName = "%s_%s_%d".formatted(constructKeyword, partKeyword, valueId);
+ String scriptName = "%s.groovy".formatted(scriptClassName);
+
+ // check whether the unit already contains the class
+ GroovyClass groovyClass = GroovyUtils.findGroovyClass(unit, scriptClassName);
+
+ // class does not exist, so add it to the compilation unit and compile
+ if (groovyClass == null) {
+ unit.addSource(scriptName, code);
+ unit.compile(Phases.CLASS_GENERATION);
+
+ // when compiled, add it to the class path
+ groovyClass = GroovyUtils.findGroovyClass(unit, scriptClassName);
+ if (groovyClass != null) {
+ unit.getClassLoader().defineClass(groovyClass.getName(), groovyClass.getBytes());
+ }
+ }
+
+ return GroovyUtils.call(unit.getClassLoader(), scriptClassName, script -> {
+ GroovyUtils.injectCmsResolver(script);
+ });
+ } catch (CompilationFailedException e) {
+ throw new NodeException(e);
+ } finally {
+ renderType.popCMSResolver();
+ }
+ } finally {
+ if (tag != null) {
+ renderType.pop(tag);
+ }
+ }
+ }
+
+ @Override
+ public Type getPropertyType() {
+ return Property.Type.RICHTEXT;
+ }
+
+ @Override
+ public String render(RenderResult result, String template) throws NodeException {
+ return "";
+ }
+}
diff --git a/cms-core/src/main/java/com/gentics/contentnode/object/parttype/handlebars/HelperSource.java b/cms-core/src/main/java/com/gentics/contentnode/object/parttype/handlebars/HelperSource.java
index 665bf029eb..9fb7a8c016 100644
--- a/cms-core/src/main/java/com/gentics/contentnode/object/parttype/handlebars/HelperSource.java
+++ b/cms-core/src/main/java/com/gentics/contentnode/object/parttype/handlebars/HelperSource.java
@@ -12,6 +12,8 @@
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Strings;
+import org.codehaus.groovy.control.CompilationUnit;
import com.gentics.api.lib.datasource.Datasource;
import com.gentics.api.lib.etc.ObjectTransformer;
@@ -40,8 +42,10 @@
import com.gentics.contentnode.resolving.ResolvableMapWrappable;
import com.gentics.contentnode.resolving.ResolvableMapWrapper;
import com.gentics.contentnode.resolving.ResolvableMapWrapper.RenderContext;
+import com.gentics.contentnode.utils.GroovyUtils;
import com.gentics.lib.render.Renderable;
import com.github.jknack.handlebars.Options;
+import com.github.jknack.handlebars.helper.HelperFunction;
/**
* Source for helpers used when rendering a {@link HandlebarsPartType}
@@ -153,7 +157,7 @@ public static Collection