diff --git a/src/main/java/org/olat/admin/layout/LayoutAdminController.java b/src/main/java/org/olat/admin/layout/LayoutAdminController.java index fb92789ee4d2b308954bfedbc03eb3b5096b25e7..0f4d5b9259e4793941f4f2589e99e5ccaf842e15 100644 --- a/src/main/java/org/olat/admin/layout/LayoutAdminController.java +++ b/src/main/java/org/olat/admin/layout/LayoutAdminController.java @@ -21,7 +21,9 @@ package org.olat.admin.layout; import java.io.File; import java.io.FilenameFilter; +import java.util.Arrays; +import org.apache.commons.lang.ArrayUtils; import org.olat.admin.SystemAdminMainController; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItem; @@ -95,6 +97,20 @@ public class LayoutAdminController extends FormBasicController { File theme = themes[i]; themesStr[i] = theme.getName(); } + + // add custom themes from configuration if available + File customThemesDir = Settings.getGuiCustomThemePath(); + if (customThemesDir != null) { + File[] customThemes = customThemesDir.listFiles(new ThemesFileNameFilter()); + String[] customThemesStr = new String[customThemes.length]; + for (int i = 0; i < customThemes.length; i++) { + File theme = customThemes[i]; + customThemesStr[i] = theme.getName(); + } + themesStr = (String[]) ArrayUtils.addAll(themesStr, customThemesStr); + Arrays.sort(themesStr); + } + return themesStr; } diff --git a/src/main/java/org/olat/core/defaults/dispatcher/StaticMediaDispatcher.java b/src/main/java/org/olat/core/defaults/dispatcher/StaticMediaDispatcher.java index abb680df39760e210c14189b1bd1d402231091dc..c40106012ad08ba69acceac03e3a1de0c2e5468e 100644 --- a/src/main/java/org/olat/core/defaults/dispatcher/StaticMediaDispatcher.java +++ b/src/main/java/org/olat/core/defaults/dispatcher/StaticMediaDispatcher.java @@ -119,6 +119,15 @@ public class StaticMediaDispatcher extends LogDelegator implements Dispatcher { staticAbsPath = WebappHelper.getContextRoot() + STATIC_DIR_NAME; } File staticFile = new File(staticAbsPath, normalizedRelPath); + + // try loading themes from custom themes folder if configured + if (!staticFile.exists() && normalizedRelPath.contains("/themes/") && Settings.getGuiCustomThemePath() != null) { + File customThemesDir = Settings.getGuiCustomThemePath(); + String path = staticFile.getAbsolutePath(); + path = path.substring(path.indexOf("/static/themes/") + 15); + staticFile = new File(customThemesDir, path); + } + // only serve if file exists if (!staticFile.exists()) { if (isLogDebugEnabled()) { diff --git a/src/main/java/org/olat/core/helpers/Settings.java b/src/main/java/org/olat/core/helpers/Settings.java index 235257dd1feb67143db35febc7b89d125651f635..24abdf97c92c6d0d02a662d1ff154065decf700a 100644 --- a/src/main/java/org/olat/core/helpers/Settings.java +++ b/src/main/java/org/olat/core/helpers/Settings.java @@ -28,6 +28,7 @@ */ package org.olat.core.helpers; +import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -84,8 +85,8 @@ public class Settings implements Initializable, Destroyable, GenericEventListene private static String clusterMode; private static Date buildDate; private static String repoRevision; - private static String patchRepoRevision; private static String crossOriginFilter; + private static File guiCustomThemePath; /** * [used by spring] @@ -364,6 +365,31 @@ public class Settings implements Initializable, Destroyable, GenericEventListene Settings.guiThemeIdentifyer = guiThemeIdentifyer; } + /** + * @return the File object pointing to the custom themes folder or null if + * no custom themes folder configured + */ + public static File getGuiCustomThemePath() { + return guiCustomThemePath; + } + + /** + * Set the custom CSS themes folder (optional). Only used by spring. + * + * @param guiCustomThemePath + * Absolute path pointing to the custom themes directory + */ + public void setGuiCustomThemePath(String guiCustomThemePath) { + File newPath = new File(guiCustomThemePath); + if (newPath.exists()) { + Settings.guiCustomThemePath = newPath; + } else { + log.info("No custom theme directory configured, path::" + + guiCustomThemePath + + " invalid. Configure property layout.custom.themes.dir if you want to use a custom themes directory."); + } + } + /** * Set the CSS theme used for this webapp. The configuration is stored in * the olatdata/system/configuration properties file and overrides the diff --git a/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml b/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml index 02e7092cc9ea39e59fbdb4eab9bc4a36f345a53d..64fb55b9a2d8c9d244569307041623bcb4aa9462 100644 --- a/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml +++ b/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml @@ -97,6 +97,12 @@ Set a specific theme. --> <property name="guiThemeIdentifyer" value="${layout.theme}" /> + <!-- + Optional path to a directory which contains custom themes. This is an alternative to placing the theme + into the webapp, default is to have your custom themes in olatdata/customizing/themes + Note that this must be an absolute path. + --> + <property name="guiCustomThemePath" value="${layout.custom.themes.dir}" /> <!-- the versionid is prepended to various dynamically linked sources like css and js lib includes. the prepending guarantees that all browsers are forced to reload the new files, since e.g. css cache invalidation based on lastmodified http headers is broken on some browsers. this here is the only safe way. diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties index bd485c48e06318e799a602c4363cc45c66e1d93e..60676c79aeba75b26e39bffc41bb43e1838731eb 100644 --- a/src/main/resources/serviceconfig/olat.properties +++ b/src/main/resources/serviceconfig/olat.properties @@ -315,6 +315,8 @@ instance.id=myopenolat # you can also configure a theme via the admin GUI which takes precedence layout.theme=openolat layout.coursetemplates.blacklist= +# Absolute path to directory where custom themes are loaded from (optional) +layout.custom.themes.dir=${userdata.dir}/customizing/themes # test user generation user.generateTestUsers=false