diff --git a/.hgtags b/.hgtags
index 6fd26d55df3680cf35cee3831ffb45539bd668ca..ffea6a03ad7c21ae4e7fada7bcc96baa046d76f6 100644
--- a/.hgtags
+++ b/.hgtags
@@ -216,3 +216,4 @@ b600dc769f2f7b69b57bdea60591488d8e27ee7d OpenOLAT 12.2.8
 de92931b87a20ec26edb2c65e3496c471bce6481 OpenOLAT 12.3.2
 a83d9c176d6c269b4ec124d7700f961e15b2a508 OpenOLAT 12.3.3
 e4c7003b290148810092848bc73bcec4e61da7be OpenOLAT 12.4.0
+f55676080d2436df8078a8a0f1f6fb1afbdbe58c OpenOLAT 12.4.1
diff --git a/src/main/java/org/olat/admin/layout/LayoutAdminController.java b/src/main/java/org/olat/admin/layout/LayoutAdminController.java
index f146990b5fb0c1b89c7dd9df7de407ec95ab5a84..bdc9e37f34a94c3f4965f4ed66a17cdbe3189b95 100644
--- a/src/main/java/org/olat/admin/layout/LayoutAdminController.java
+++ b/src/main/java/org/olat/admin/layout/LayoutAdminController.java
@@ -63,7 +63,7 @@ import org.springframework.beans.factory.annotation.Autowired;
  */
 public class LayoutAdminController extends FormBasicController {
 	
-	private static final Set<String> imageMimeTypes = new HashSet<String>();
+	private static final Set<String> imageMimeTypes = new HashSet<>();
 	static {
 		imageMimeTypes.add("image/gif");
 		imageMimeTypes.add("image/jpg");
@@ -198,10 +198,10 @@ public class LayoutAdminController extends FormBasicController {
 	
 	@Override
 	protected boolean validateFormLogic(UserRequest ureq) {
-		boolean allOk = true;
+		boolean allOk = super.validateFormLogic(ureq);
 		allOk &= validateUrl(logoUrlEl);
 		allOk &= validateUrl(footerUrl);
-		return allOk & super.validateFormLogic(ureq);
+		return allOk;
 	}
 	
 	private boolean validateUrl(TextElement el) {
@@ -248,7 +248,6 @@ public class LayoutAdminController extends FormBasicController {
 			String newThemeIdentifyer = themeSelection.getSelectedKey();
 			guiSettings.setGuiThemeIdentifyer(newThemeIdentifyer);
 			// use new theme in current window
-			getWindowControl().getWindowBackOffice().getWindow().getGuiTheme().init(newThemeIdentifyer);
 			getWindowControl().getWindowBackOffice().getWindow().setDirty(true);
 			logAudit("GUI theme changed", newThemeIdentifyer);
 			fireEvent(ureq, Event.CHANGED_EVENT);
diff --git a/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java b/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java
index d2b0aba76dd23d62b4beb46bb619aa8d7f703e6d..4eeee56b92bd9b240433df45c3eb1738eda8c910 100644
--- a/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java
+++ b/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java
@@ -57,7 +57,6 @@ import org.olat.core.gui.components.ComponentCollection;
 import org.olat.core.gui.components.Window;
 import org.olat.core.gui.components.countdown.CountDownComponent;
 import org.olat.core.gui.components.htmlheader.jscss.CustomCSS;
-import org.olat.core.gui.components.htmlheader.jscss.CustomJSComponent;
 import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.components.link.LinkFactory;
 import org.olat.core.gui.components.panel.OncePanel;
@@ -84,7 +83,6 @@ import org.olat.core.gui.control.navigation.NavElement;
 import org.olat.core.gui.control.navigation.SiteInstance;
 import org.olat.core.gui.control.util.ZIndexWrapper;
 import org.olat.core.gui.control.winmgr.JSCommand;
-import org.olat.core.gui.themes.Theme;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.helpers.Settings;
 import org.olat.core.id.Identity;
@@ -362,8 +360,6 @@ public class BaseFullWebappController extends BasicController implements DTabs,
 		// out the correct value
 		mainVc.contextPut("theme", w.getGuiTheme());
 		mainVc.contextPut("globalSettings", winman.getGlobalSettings());
-		// also add the optional theme javascript 
-		addThemeJS();
 		
 		// Add JS analytics code, e.g. for google analytics
 		if (analyticsModule.isAnalyticsEnabled()) {
@@ -604,22 +600,6 @@ public class BaseFullWebappController extends BasicController implements DTabs,
 			mainVc.setDirty(true);
 		}
 	}
-
-	/**
-	 * adds the theme custom js-code to the mainVc
-	 */
-	private void addThemeJS() {
-		Theme currentTheme = getWindowControl().getWindowBackOffice().getWindow().getGuiTheme();
-		if (currentTheme.hasCustomJS()) {
-			String relPath = currentTheme.getRelPathToCustomJS();
-			CustomJSComponent customJS = new CustomJSComponent("customThemejs", new String[] { relPath });
-			if (isLogDebugEnabled()) {
-				logDebug("injecting custom javascript from current OLAT-Theme", relPath);
-			}
-			// no need to wrap in panel, will never change
-			mainVc.put(customJS.getComponentName(), customJS);
-		}
-	}
 	
 	@Override
 	protected void event(UserRequest ureq, Component source, Event event) {
diff --git a/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html b/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html
index c1c7a7deb2cf98b53df6ccf3c46c1a0c91bc9675..09649bf3c0e78c0e8027c1ee4812680dc27ade06 100644
--- a/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html
+++ b/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html
@@ -162,14 +162,9 @@ jQuery(function() {
 ## LAYOUT
 ## 1) Dynamic component CSS included by components
 $r.render("jsCssRawHtmlHeader", "pre-theme")
-## 2) CSS theme inclusion and favicons
-<link rel="icon" href="${theme.getBaseURI()}favicon.ico" type="image/x-icon" /> 
-<link rel="shortcut icon" href="${theme.getBaseURI()}favicon.ico" type="image/x-icon" /> 
-<link id="o_theme_css" href="${theme.getBaseURI()}theme.css" rel="stylesheet" type="text/css" />
-<!--[if IE 9]>
-<link id="o_theme_css_ie" href="${theme.getBaseURI()}theme_ie_completions.css" rel="stylesheet" type="text/css" />
-<![endif]-->
-## 3) Insert IE specific class names
+## 2) CSS theme inclusion, theme JavaScript, favicons and appicons
+$theme.renderHTMLHeaderElements()
+ ## 3) Insert IE specific class names
 <!--[if IE 8]>
 <script type="text/javascript">
 	jQuery(document).ready(function() { jQuery('body').addClass('o_browser_ie8'); });
diff --git a/src/main/java/org/olat/core/commons/services/csp/ui/CSPLogDataSource.java b/src/main/java/org/olat/core/commons/services/csp/ui/CSPLogDataSource.java
index b66a56010bf8f17ab429d90350cff5915d9455d8..db69137ca7535e7e8b04a79348e0352db805b055 100644
--- a/src/main/java/org/olat/core/commons/services/csp/ui/CSPLogDataSource.java
+++ b/src/main/java/org/olat/core/commons/services/csp/ui/CSPLogDataSource.java
@@ -1,3 +1,22 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
 package org.olat.core.commons.services.csp.ui;
 
 import java.util.Collections;
diff --git a/src/main/java/org/olat/core/commons/services/csp/ui/CSPLogEntryController.java b/src/main/java/org/olat/core/commons/services/csp/ui/CSPLogEntryController.java
index 499a1338f79d38159a970a26a5bff9a4f4ac1534..30023cca549875ec10d31f92f69973ada8d9bcf2 100644
--- a/src/main/java/org/olat/core/commons/services/csp/ui/CSPLogEntryController.java
+++ b/src/main/java/org/olat/core/commons/services/csp/ui/CSPLogEntryController.java
@@ -1,3 +1,22 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
 package org.olat.core.commons.services.csp.ui;
 
 import org.olat.basesecurity.BaseSecurity;
diff --git a/src/main/java/org/olat/core/gui/components/Window.java b/src/main/java/org/olat/core/gui/components/Window.java
index af876691854d6c1706abfeb60856084d69a29f9a..dd234346c19be4f09aca3ee4cb938f85063f267b 100644
--- a/src/main/java/org/olat/core/gui/components/Window.java
+++ b/src/main/java/org/olat/core/gui/components/Window.java
@@ -193,7 +193,7 @@ public class Window extends AbstractComponent implements CustomCSSDelegate {
 		this.wbackofficeImpl = wbackoffice;
 		jsAndCssAdder = wbackoffice.createJSAndCSSAdder();
 		// set default theme
-		Theme myTheme = new Theme(CoreSpringFactory.getImpl(GUISettings.class).getGuiThemeIdentifyer());
+		Theme myTheme = CoreSpringFactory.getImpl(GUISettings.class).getGuiTheme();
 		setGuiTheme(myTheme);
 		// add analytics service provider if configured
 		AnalyticsModule analyticsModule = CoreSpringFactory.getImpl(AnalyticsModule.class);
diff --git a/src/main/java/org/olat/core/gui/themes/Theme.java b/src/main/java/org/olat/core/gui/themes/Theme.java
index a3dd93329932a1c7a4cf4b5001960adbc6965217..027b6d3011f27b8f3df6abbcb821d64df1b18371 100644
--- a/src/main/java/org/olat/core/gui/themes/Theme.java
+++ b/src/main/java/org/olat/core/gui/themes/Theme.java
@@ -37,15 +37,27 @@ import org.olat.core.util.WebappHelper;
  */
 public class Theme {
 	public static final String DEFAULTTHEME = "light";
+	private static final String IE_COMPLETION_FILENAME = "theme_ie_completions.css";	
+	private static final String CUSTOM_JS_FILENAME = "theme.js";
+	private static final String CUSTOM_FAVICON_ICO_FILENAME = "favicon.ico"; // legacy
+	private static final String CUSTOM_FAVICON_PNG16_FILENAME = "meta/favicon16.png";
+	private static final String CUSTOM_FAVICON_PNG32_FILENAME = "meta/favicon32.png";
+	private static final String CUSTOM_FAVICON_PNG64_FILENAME = "meta/favicon64.png";
+	private static final String CUSTOM_APPICON_PNG180_FILENAME = "meta/appicon180.png";
+	private static final String CUSTOM_TILEICON_PNG70_FILENAME = "meta/tileicon70.png";
+	private static final String CUSTOM_TILEICON_PNG150_FILENAME = "meta/tileicon150.png";
+	private static final String CUSTOM_TILEICON_PNG310_FILENAME = "meta/tileicon310.png";
+	private static final String CUSTOM_MANIFEST_FILENAME = "meta/manifest.json";
+	private static final String CUSTOM_MS_APPLICATION_CONFIG_FILENAM = "meta/msapplication-config.xml";
 	
 	private String identifyer;
 	private String baseURI;
 	private String relPathToThemesDir;
-	private boolean hasCustomJSFile = false;
-
-	private static String CUSTOMFILENAME = "theme.js";
+	private String htmlHeaderElements;
 
 	/**
+	 * Theme is cached and shared with all sessions.
+	 * 
 	 * @param name
 	 *            The unique theme identifyer
 	 */
@@ -60,37 +72,6 @@ public class Theme {
 		return identifyer;
 	}
 
-	/**
-	 * checks whether the OLAT-Theme-Folder of this theme contains a file
-	 * "theme.js"
-	 * 
-	 * @return returns if the OLAT-Theme-Folder contains a file "theme.js"
-	 */
-	public boolean hasCustomJS() {
-		return hasCustomJSFile;
-	}
-
-	/**
-	 * returns a new File-instance that points to the "theme.js" file in the
-	 * current OLAT-Theme folder
-	 * 
-	 * @return
-	 */
-	private File getCustomJSFile() {
-		String staticThemesPath = WebappHelper.getContextRealPath("/static/themes/");
-		if(staticThemesPath == null) {
-			staticThemesPath = WebappHelper.getContextRoot() + "/static/themes/";
-		}
-		
-		String guiThemIdentifyer = CoreSpringFactory.getImpl(GUISettings.class).getGuiThemeIdentifyer();
-		File themeFolder = new File(staticThemesPath, guiThemIdentifyer);
-		if (!themeFolder.exists() && Settings.getGuiCustomThemePath() != null) {
-			// fallback to custom themes folder
-			themeFolder = new File(Settings.getGuiCustomThemePath(), guiThemIdentifyer);
-		}
-		return new File(themeFolder, CUSTOMFILENAME);
-	}
-
 	/**
 	 * @return The base URI for this theme, e.g.
 	 *         'http://www.myserver.com/olat/raw/61x/themes/default/'
@@ -99,6 +80,13 @@ public class Theme {
 		return baseURI;
 	}
 
+	/**
+	 * @return the http header elements used to implement this theme
+	 */
+	public String renderHTMLHeaderElements() {
+		return htmlHeaderElements;
+	}
+	
 	/**
 	 * returns the relative path to the custom js <br />
 	 * ( check first with <code>hasCustomJS()</code> )<br />
@@ -114,7 +102,7 @@ public class Theme {
 	 *         themes/frentix/theme.js
 	 */
 	public String getRelPathToCustomJS() {
-		return relPathToThemesDir + CUSTOMFILENAME;
+		return relPathToThemesDir + CUSTOM_JS_FILENAME;
 	}
 
 	/**
@@ -129,8 +117,78 @@ public class Theme {
 		StringOutput themePath = new StringOutput();
 		StaticMediaDispatcher.renderStaticURI(themePath, relPathToThemesDir);
 		this.baseURI = themePath.toString();
-		// Check if theme has a custom JS file to tweak UI on JS level
-		hasCustomJSFile = getCustomJSFile().exists();
+		
+		// Build theme header include string with resources available in the theme
+		this.htmlHeaderElements = buildHTMLHeaderElements();
 	}
 
+	
+	/**
+	 * Helper to generate the theme relevant html header elements
+	 * @return
+	 */
+	private String buildHTMLHeaderElements() {
+		StringBuilder sb = new StringBuilder();
+		// 1) lookup theme in release files
+		String staticThemesPath = WebappHelper.getContextRealPath("/static/themes/");
+		if(staticThemesPath == null) {
+			staticThemesPath = WebappHelper.getContextRoot() + "/static/themes/";
+		}
+		String guiThemIdentifyer = CoreSpringFactory.getImpl(GUISettings.class).getGuiThemeIdentifyer();
+		File themeFolder = new File(staticThemesPath, guiThemIdentifyer);
+		if (!themeFolder.exists() && Settings.getGuiCustomThemePath() != null) {
+			// 2) fallback to custom themes folder
+			themeFolder = new File(Settings.getGuiCustomThemePath(), guiThemIdentifyer);
+		}
+		// Include the theme css file
+		sb.append("<link id='o_theme_css' href='").append(baseURI).append("theme.css' rel='stylesheet' type='text/css' />\n");
+		// additionally add the blessed IE css if available
+		if (new File(themeFolder,IE_COMPLETION_FILENAME).exists()) {
+			sb.append("<!--[if IE 9]>\n");
+			sb.append("<link id='o_theme_css' href='").append(baseURI).append(IE_COMPLETION_FILENAME).append("' rel='stylesheet' type='text/css' />\n");
+			sb.append("<![endif]-->\n");
+		}		
+		// Include custom theme javascript file, for login caroussel, js-based layout patches etc
+		if (new File(themeFolder,CUSTOM_JS_FILENAME).exists()) {
+			sb.append("<script type='text/javascript' src='").append(baseURI).append(CUSTOM_JS_FILENAME).append("'></script>\n");
+		}
+		// Include the favicons in legacy .ico format and others in png format and different resolutions
+		if (new File(themeFolder,CUSTOM_FAVICON_ICO_FILENAME).exists()) {
+			sb.append("<link rel='icon' href='").append(baseURI).append(CUSTOM_FAVICON_ICO_FILENAME).append("' type='image/x-icon' />\n");
+		}
+		if (new File(themeFolder,CUSTOM_FAVICON_PNG16_FILENAME).exists()) {
+			sb.append("<link rel='icon' href='").append(baseURI).append(CUSTOM_FAVICON_PNG16_FILENAME).append("' type='image/png' sizes='16x16' />\n");
+		}
+		if (new File(themeFolder,CUSTOM_FAVICON_PNG32_FILENAME).exists()) {
+			sb.append("<link rel='icon' href='").append(baseURI).append(CUSTOM_FAVICON_PNG32_FILENAME).append("' type='image/png' sizes='32x32' />\n");
+		}
+		if (new File(themeFolder,CUSTOM_FAVICON_PNG64_FILENAME).exists()) {
+			sb.append("<link rel='icon' href='").append(baseURI).append(CUSTOM_FAVICON_PNG64_FILENAME).append("' type='image/png' sizes='64x64' />\n");
+		}
+		// Include high-res apple app/touch icon
+		if (new File(themeFolder,CUSTOM_APPICON_PNG180_FILENAME).exists()) {
+			sb.append("<link rel='apple-touch-icon' href='").append(baseURI).append(CUSTOM_APPICON_PNG180_FILENAME).append("' type='image/png' sizes='180x180' />\n");
+		}
+		// Include Google manifest file
+		if (new File(themeFolder,CUSTOM_MANIFEST_FILENAME).exists()) {
+			sb.append("<link rel='manifest' href='").append(baseURI).append(CUSTOM_MANIFEST_FILENAME).append("' />\n");
+		}
+		// Include Microsoft application config file (make sure any referenced image in the file has absolute path configuration
+		if (new File(themeFolder,CUSTOM_MS_APPLICATION_CONFIG_FILENAM).exists()) {
+			sb.append("<meta name='msapplication-config' content='").append(baseURI).append(CUSTOM_MS_APPLICATION_CONFIG_FILENAM).append("' />\n");
+		} else {
+			sb.append("<meta name='msapplication-TileColor' content='").append("#ffffff").append("' />\n");
+			if (new File(themeFolder,CUSTOM_TILEICON_PNG70_FILENAME).exists()) {
+				sb.append("<meta name='msapplication-square70x70logo' content='").append(baseURI).append(CUSTOM_TILEICON_PNG70_FILENAME).append("' />\n");
+			}
+			if (new File(themeFolder,CUSTOM_TILEICON_PNG150_FILENAME).exists()) {
+				sb.append("<meta name='msapplication-square150x150logo' content='").append(baseURI).append(CUSTOM_TILEICON_PNG150_FILENAME).append("' />\n");
+			}
+			if (new File(themeFolder,CUSTOM_TILEICON_PNG310_FILENAME).exists()) {
+				sb.append("<meta name='msapplication-square310x310logo' content='").append(baseURI).append(CUSTOM_TILEICON_PNG310_FILENAME).append("' />\n");
+			}
+		}
+		
+		return sb.toString();
+	}
 }
diff --git a/src/main/java/org/olat/core/helpers/GUISettings.java b/src/main/java/org/olat/core/helpers/GUISettings.java
index 2c4fa1adf818ee10ede014ad7934f09b62593510..32ce17e196664214a5633648e96717f995b5169a 100644
--- a/src/main/java/org/olat/core/helpers/GUISettings.java
+++ b/src/main/java/org/olat/core/helpers/GUISettings.java
@@ -20,6 +20,7 @@
 package org.olat.core.helpers;
 
 import org.olat.core.configuration.AbstractSpringModule;
+import org.olat.core.gui.themes.Theme;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -37,6 +38,8 @@ public class GUISettings extends AbstractSpringModule {
 
 	private static final String KEY_GUI_THEME_IDENTIFYER = "layout.theme";
 	
+	private Theme guiTheme;
+	
 	/**
 	 * Set the system theme here. Make sure the directory webapp/WEB-INF/static/themes/YOURTHEME exists. 
 	 * This is only the default value in case no user configuration is found. Use the administration GUI to
@@ -70,6 +73,13 @@ public class GUISettings extends AbstractSpringModule {
 	public String getGuiThemeIdentifyer() {
 		return guiThemeIdentifyer;			
 	}
+	
+	public synchronized Theme getGuiTheme() {
+		if(guiTheme == null) {
+			guiTheme = new Theme(getGuiThemeIdentifyer());
+		}
+		return guiTheme;			
+	}
 
 	/**
 	 * Set the CSS theme used for this webapp. Only used by spring. Use static
@@ -80,5 +90,6 @@ public class GUISettings extends AbstractSpringModule {
 	public void setGuiThemeIdentifyer(String guiThemeIdentifyer) {
 		this.guiThemeIdentifyer = guiThemeIdentifyer;
 		setStringProperty(KEY_GUI_THEME_IDENTIFYER, guiThemeIdentifyer, true);
+		getGuiTheme().init(guiThemeIdentifyer);
 	}
 }
diff --git a/src/main/java/org/olat/core/util/Formatter.java b/src/main/java/org/olat/core/util/Formatter.java
index ab3e7b97f2877a30d651d983062d0ae9be80e350..3b172294d014173a5c2ecb472901c304b73a41ef 100644
--- a/src/main/java/org/olat/core/util/Formatter.java
+++ b/src/main/java/org/olat/core/util/Formatter.java
@@ -50,6 +50,7 @@ import org.apache.commons.lang.time.DurationFormatUtils;
 import org.olat.core.dispatcher.impl.StaticMediaDispatcher;
 import org.olat.core.gui.render.StringOutput;
 import org.olat.core.helpers.Settings;
+import org.olat.core.util.filter.impl.NekoHTMLMathScanner;
 
 /**
  * enclosing_type Description: <br>
@@ -65,7 +66,10 @@ public class Formatter {
 	private static final DateFormat formatDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
 	private static final DateFormat shortFormatDateFileSystem = new SimpleDateFormat("yyyyMMdd");
 
-	private static final Map<Locale,Formatter> localToFormatterMap = new HashMap<Locale,Formatter>();
+	private static final Map<Locale,Formatter> localToFormatterMap = new HashMap<>();
+
+	// Pattern to find math classes
+	private static final Pattern classMathPattern = Pattern.compile(".*class[ ]*=[ ]*(math|(['\"])([a-zA-Z0-9_\\- ]* )*math( [a-zA-Z0-9_\\- ]*)*\\2).*");
 	
 	private final Locale locale;
 	private final DateFormat shortDateFormat;
@@ -651,7 +655,7 @@ public class Formatter {
 	public static String formatLatexFormulas(String htmlFragment) {
 		if (htmlFragment == null) return "";
 		// optimize, reduce jsmath calls on client
-		if (htmlFragment.contains("<math") || htmlFragment.contains("class='math'") || htmlFragment.contains("class=\"math\"")) {
+		if (new NekoHTMLMathScanner().scan(htmlFragment)) {
 			// add math wrapper
 			String domid = "mw_" + CodeHelper.getRAMUniqueID();
 			String elem = htmlFragment.contains("<div") || htmlFragment.contains("<p") ? "div" : "span";
@@ -661,7 +665,7 @@ public class Formatter {
 			sb.append("</").append(elem).append(">");
 			sb.append("\n<script type='text/javascript'>\n/* <![CDATA[ */\n setTimeout(function() { BFormatter.formatLatexFormulas('").append(domid).append("');}, 100);\n/* ]]> */\n</script>");
 			return sb.toString();
-		}			
+		}
 		return htmlFragment;
 	}
 	
diff --git a/src/main/java/org/olat/core/util/filter/impl/NekoHTMLMathScanner.java b/src/main/java/org/olat/core/util/filter/impl/NekoHTMLMathScanner.java
new file mode 100644
index 0000000000000000000000000000000000000000..68c23ea336c9868a747ec535a1a51de559566c6f
--- /dev/null
+++ b/src/main/java/org/olat/core/util/filter/impl/NekoHTMLMathScanner.java
@@ -0,0 +1,84 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.util.filter.impl;
+
+import java.io.StringReader;
+
+import org.cyberneko.html.parsers.SAXParser;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Detect the tag math and a CSS clas named math
+ * 
+ * 
+ * Initial date: 23 avr. 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class NekoHTMLMathScanner {
+	
+	private static final OLog log = Tracing.createLoggerFor(NekoHTMLMathScanner.class);
+
+	public boolean scan(String original) {
+		if (original == null) return false;
+		
+		try {
+			SAXParser parser = new SAXParser();
+			parser.setFeature("http://cyberneko.org/html/features/balance-tags/document-fragment", true);
+			parser.setFeature("http://cyberneko.org/html/features/balance-tags", false);
+			HTMLHandler contentHandler = new HTMLHandler();
+			parser.setContentHandler(contentHandler);
+			parser.parse(new InputSource(new StringReader(original)));
+			return contentHandler.mathFound();
+		} catch (Exception e) {
+			log.error("", e);
+			return false;
+		}
+	}
+
+	private static class HTMLHandler extends DefaultHandler {
+		private boolean mathFound = false;
+		
+		public boolean mathFound() {
+			return mathFound;
+		}
+
+		@Override
+		public void startElement(String uri, String localName, String qName, Attributes attributes) {
+			if("MATH".equals(localName)) {
+				mathFound = true;
+			} else if(attributes != null) {
+				String css = attributes.getValue("class");
+				if(css != null) {
+					String[] splited = css.split("\\s+");
+					for(String split:splited) {
+						if(split.equals("math")) {
+							mathFound = true;
+						}
+					}
+				}
+			}
+		}
+	}
+}
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java
index 8c1ab501f700500d440d81c0423a3e2e6bf50655..b4a38444a428ea6756e1279e762f0827b812e34a 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java
@@ -709,9 +709,9 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent
 	
 	protected final void renderSpan(AssessmentRenderer renderer, StringOutput sb, Span span, AssessmentObjectComponent component,
 			ResolvedAssessmentItem resolvedAssessmentItem, ItemSessionState itemSessionState, URLBuilder ubu, Translator translator) {
-		Attribute<?> attrClass = span.getAttributes().get("class");
+		StringMultipleAttribute attrClass = span.getAttributes().getStringMultipleAttribute("class");
 
-		if(attrClass != null && attrClass.getValue() != null && attrClass.getValue().toString().equals("[math]")) {
+		if (attrClass != null && attrClass.getValue() != null && attrClass.getValue().contains("math")) {
 			String domid = "mw_" + CodeHelper.getRAMUniqueID();
 			sb.append("<span id=\"").append(domid).append("\">");
 			
diff --git a/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_de.properties
index 208b4a381e48cea1d6a9689c5a9f3f8294caaea4..8fda3810de258c7e331c59ddcb48a35c50b33fa4 100644
--- a/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_de.properties
@@ -17,7 +17,7 @@ create.type=Fragetyp erstellen
 delete.level=L\u00F6schen
 delete.level.confirm=Wollen Sie wirklich diese Stufe l\u00F6schen?
 delete.qustions.without.author=Fragen l\u00F6schen, wenn Autor gel\u00F6scht
-delete.qustions.without.author.info=Beim L\u00F6schen eines Benutzers wird er als Autoren von allen Fragen ausgetragen. Wenn diese Option eingeschaltet ist und der Benutzer der einzige Autor einer Frage ist, wird die Frage ebenfalls gel\u00F6scht.
+delete.qustions.without.author.info=Beim L\u00F6schen eines Benutzers wird er als Autor von allen Fragen ausgetragen. Wenn diese Option eingeschaltet ist und der Benutzer der einzige Autor einer Frage ist, wird die Frage ebenfalls gel\u00F6scht.
 delete.taxonomyLevel=L\u00F6schen
 delete.taxonomyLevel.confirm=Wollen Sie wirklich diese Fachbereich "{0}" l\u00F6schen?
 delete.type=L\u00F6schen
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/free/ui/FreeAccessConfigurationController.java b/src/main/java/org/olat/resource/accesscontrol/provider/free/ui/FreeAccessConfigurationController.java
index ad3238f3167fe5dbdacb207d48884d9340a07e9b..2d6737c5c04c30459c54b42665b94b9d9a37f359 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/free/ui/FreeAccessConfigurationController.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/free/ui/FreeAccessConfigurationController.java
@@ -61,14 +61,18 @@ public class FreeAccessConfigurationController extends AbstractConfigurationMeth
 
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		formLayout.setElementCssClass("o_sel_accesscontrol_free_form");
+		
 		String desc = null;
 		if(link.getOffer() != null) {
 			desc = link.getOffer().getDescription();
 		}
 		descEl = uifactory.addTextAreaElement("offer-desc", "offer.description", 2000, 6, 80, false, desc, formLayout);
+		descEl.setElementCssClass("o_sel_accesscontrol_description");
 		
 		String[] autoValues = new String[]{ translate("auto.booking.value") };
 		autoEl = uifactory.addCheckboxesHorizontal("auto.booking", "auto.booking", formLayout, autoKeys, autoValues);
+		autoEl.setElementCssClass("o_sel_accesscontrol_auto_booking");
 		if(link.getOffer() != null && link.getOffer().getKey() != null) {
 			autoEl.select(autoKeys[0], link.getOffer().isAutoBooking());
 		} else {
diff --git a/src/main/webapp/static/themes/light/favicon.ico b/src/main/webapp/static/themes/light/favicon.ico
index 92b3c8891901712cdd96b8f309b0414cb1adc327..33e065593e00dec87af45063fb9b891c9047cf7e 100644
Binary files a/src/main/webapp/static/themes/light/favicon.ico and b/src/main/webapp/static/themes/light/favicon.ico differ
diff --git a/src/main/webapp/static/themes/light/meta/appicon180.png b/src/main/webapp/static/themes/light/meta/appicon180.png
new file mode 100644
index 0000000000000000000000000000000000000000..198e34500c45bb54faaee5763e1c7b94c7f5d326
Binary files /dev/null and b/src/main/webapp/static/themes/light/meta/appicon180.png differ
diff --git a/src/main/webapp/static/themes/light/meta/appicon192.png b/src/main/webapp/static/themes/light/meta/appicon192.png
new file mode 100644
index 0000000000000000000000000000000000000000..ec53d0efa46f903cd388481bb4509b22334c2097
Binary files /dev/null and b/src/main/webapp/static/themes/light/meta/appicon192.png differ
diff --git a/src/main/webapp/static/themes/light/meta/favicon16.png b/src/main/webapp/static/themes/light/meta/favicon16.png
new file mode 100644
index 0000000000000000000000000000000000000000..5a6dbecf1324283d6ccf0cea6d39627643b69044
Binary files /dev/null and b/src/main/webapp/static/themes/light/meta/favicon16.png differ
diff --git a/src/main/webapp/static/themes/light/meta/favicon32.png b/src/main/webapp/static/themes/light/meta/favicon32.png
new file mode 100644
index 0000000000000000000000000000000000000000..9725a2aeceddccbb4d3d3b68197d22be5e91d92c
Binary files /dev/null and b/src/main/webapp/static/themes/light/meta/favicon32.png differ
diff --git a/src/main/webapp/static/themes/light/meta/favicon64.png b/src/main/webapp/static/themes/light/meta/favicon64.png
new file mode 100644
index 0000000000000000000000000000000000000000..dfa7c180906fc435fb02934d35e7125b8ae67eca
Binary files /dev/null and b/src/main/webapp/static/themes/light/meta/favicon64.png differ
diff --git a/src/main/webapp/static/themes/light/meta/manifest.json b/src/main/webapp/static/themes/light/meta/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..9a6aafebe06ce56ca795b908c35b0f73f917c540
--- /dev/null
+++ b/src/main/webapp/static/themes/light/meta/manifest.json
@@ -0,0 +1,10 @@
+{
+	"name":"OpenOLAT eLearning",
+	"icons": [
+		{
+			"src":"appicon192.png", "sizes":"192x192", "type":"image/png"
+		}
+	],
+	"theme_color":"#ffffff",
+	"display":"minimal-ui"
+}
\ No newline at end of file
diff --git a/src/main/webapp/static/themes/light/meta/src/appicon.png b/src/main/webapp/static/themes/light/meta/src/appicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..350e590e9ea5a5b200167a747148590a8c583a13
Binary files /dev/null and b/src/main/webapp/static/themes/light/meta/src/appicon.png differ
diff --git a/src/main/webapp/static/themes/light/meta/src/favicon.png b/src/main/webapp/static/themes/light/meta/src/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..b83c9a419e00197825ada39d9ee974af7099fd29
Binary files /dev/null and b/src/main/webapp/static/themes/light/meta/src/favicon.png differ
diff --git a/src/main/webapp/static/themes/light/meta/src/tileicon.png b/src/main/webapp/static/themes/light/meta/src/tileicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..4b9fe8935b0bb5759e70b4f4df6d368694c7789a
Binary files /dev/null and b/src/main/webapp/static/themes/light/meta/src/tileicon.png differ
diff --git a/src/main/webapp/static/themes/light/meta/tileicon150.png b/src/main/webapp/static/themes/light/meta/tileicon150.png
new file mode 100644
index 0000000000000000000000000000000000000000..29bafe6cdc5059bd8b98d9821076a9e6586733e1
Binary files /dev/null and b/src/main/webapp/static/themes/light/meta/tileicon150.png differ
diff --git a/src/main/webapp/static/themes/light/meta/tileicon310.png b/src/main/webapp/static/themes/light/meta/tileicon310.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f832765e7dd664b7531bb84c53b21028d9f3bf3
Binary files /dev/null and b/src/main/webapp/static/themes/light/meta/tileicon310.png differ
diff --git a/src/main/webapp/static/themes/light/meta/tileicon70.png b/src/main/webapp/static/themes/light/meta/tileicon70.png
new file mode 100644
index 0000000000000000000000000000000000000000..0e49469e7f4521e1c11fc88fe092080e7e9e60c4
Binary files /dev/null and b/src/main/webapp/static/themes/light/meta/tileicon70.png differ
diff --git a/src/main/webapp/static/themes/openolat/favicon.ico b/src/main/webapp/static/themes/openolat/favicon.ico
deleted file mode 100644
index 92b3c8891901712cdd96b8f309b0414cb1adc327..0000000000000000000000000000000000000000
Binary files a/src/main/webapp/static/themes/openolat/favicon.ico and /dev/null differ
diff --git a/src/main/webapp/static/themes/openolat/meta/appicon180.png b/src/main/webapp/static/themes/openolat/meta/appicon180.png
new file mode 100644
index 0000000000000000000000000000000000000000..198e34500c45bb54faaee5763e1c7b94c7f5d326
Binary files /dev/null and b/src/main/webapp/static/themes/openolat/meta/appicon180.png differ
diff --git a/src/main/webapp/static/themes/openolat/meta/appicon192.png b/src/main/webapp/static/themes/openolat/meta/appicon192.png
new file mode 100644
index 0000000000000000000000000000000000000000..ec53d0efa46f903cd388481bb4509b22334c2097
Binary files /dev/null and b/src/main/webapp/static/themes/openolat/meta/appicon192.png differ
diff --git a/src/main/webapp/static/themes/openolat/meta/favicon16.png b/src/main/webapp/static/themes/openolat/meta/favicon16.png
new file mode 100644
index 0000000000000000000000000000000000000000..5a6dbecf1324283d6ccf0cea6d39627643b69044
Binary files /dev/null and b/src/main/webapp/static/themes/openolat/meta/favicon16.png differ
diff --git a/src/main/webapp/static/themes/openolat/meta/favicon32.png b/src/main/webapp/static/themes/openolat/meta/favicon32.png
new file mode 100644
index 0000000000000000000000000000000000000000..9725a2aeceddccbb4d3d3b68197d22be5e91d92c
Binary files /dev/null and b/src/main/webapp/static/themes/openolat/meta/favicon32.png differ
diff --git a/src/main/webapp/static/themes/openolat/meta/favicon64.png b/src/main/webapp/static/themes/openolat/meta/favicon64.png
new file mode 100644
index 0000000000000000000000000000000000000000..dfa7c180906fc435fb02934d35e7125b8ae67eca
Binary files /dev/null and b/src/main/webapp/static/themes/openolat/meta/favicon64.png differ
diff --git a/src/main/webapp/static/themes/openolat/meta/manifest.json b/src/main/webapp/static/themes/openolat/meta/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..9a6aafebe06ce56ca795b908c35b0f73f917c540
--- /dev/null
+++ b/src/main/webapp/static/themes/openolat/meta/manifest.json
@@ -0,0 +1,10 @@
+{
+	"name":"OpenOLAT eLearning",
+	"icons": [
+		{
+			"src":"appicon192.png", "sizes":"192x192", "type":"image/png"
+		}
+	],
+	"theme_color":"#ffffff",
+	"display":"minimal-ui"
+}
\ No newline at end of file
diff --git a/src/main/webapp/static/themes/openolat/meta/tileicon150.png b/src/main/webapp/static/themes/openolat/meta/tileicon150.png
new file mode 100644
index 0000000000000000000000000000000000000000..29bafe6cdc5059bd8b98d9821076a9e6586733e1
Binary files /dev/null and b/src/main/webapp/static/themes/openolat/meta/tileicon150.png differ
diff --git a/src/main/webapp/static/themes/openolat/meta/tileicon310.png b/src/main/webapp/static/themes/openolat/meta/tileicon310.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f832765e7dd664b7531bb84c53b21028d9f3bf3
Binary files /dev/null and b/src/main/webapp/static/themes/openolat/meta/tileicon310.png differ
diff --git a/src/main/webapp/static/themes/openolat/meta/tileicon70.png b/src/main/webapp/static/themes/openolat/meta/tileicon70.png
new file mode 100644
index 0000000000000000000000000000000000000000..0e49469e7f4521e1c11fc88fe092080e7e9e60c4
Binary files /dev/null and b/src/main/webapp/static/themes/openolat/meta/tileicon70.png differ
diff --git a/src/main/webapp/static/themes/themes.README b/src/main/webapp/static/themes/themes.README
index f43d460ebe58e431c91ac41c648a7beb4ba30056..296f11aea62ca8817382d7461065f4329b2817a3 100644
--- a/src/main/webapp/static/themes/themes.README
+++ b/src/main/webapp/static/themes/themes.README
@@ -78,7 +78,24 @@ A good way is to start with a copy of "openolat" (i.e. copy openolat to mytheme)
 - Open mytheme/_mytheme_content.scss and override or extend styles from the light theme. 
 
 Optional:
-- Place your own favicon.ico file to mytheme/favicon.ico
+- Place your own favicon.ico file to mytheme/favicon.ico (legacy support)
+- Place your own favicon (new style) and app icons to 
+  mytheme/meta/favicon.16.png  				-> optional, to add a 16x16 bookmark icon
+  mytheme/meta/favicon.32.png  				-> optional, to add a 32x32 bookmark icon
+  mytheme/meta/favicon.64.png  				-> optional, to add a 64x64 bookmark icon
+- Place your own application icons for older iOS to
+  mytheme/meta/appicon180.png  				-> optional, to add iOS homescreen icons
+- Place your own manifest and application icons for modern Safari and Android to
+  mytheme/meta/manifest.json  				-> optional, to add Android homescreen icons (and add the appicons linked from within manifest.json)
+- Place your own Windows Tile icons to
+  mytheme/meta/tileicon70png
+  mytheme/meta/tileicon150png
+  mytheme/meta/tileicon310png
+- OR alternatively add a browser config file to the location below. 
+  mytheme/meta/msapplication-config.xml		-> optional, to add Windows tiles icons 
+  Note, when the browser config is there, the tile icons above will not be included. In the browser config the 
+  links to the tile icons must be absolute path to work despite the MS doku tells otherwise
+
 - Create a file mytheme/theme.js and add your theme specific JavaScript. Best is to create a JQuery module 
   for it. Make sure you check for necessary changes after each DOM replacement cycle. See the theme.js file
   in the openolat theme for an example.
diff --git a/src/test/java/org/olat/core/commons/services/csp/manager/CSPManagerTest.java b/src/test/java/org/olat/core/commons/services/csp/manager/CSPManagerTest.java
index bc8c42eaadb837704c9fbd11905919e38e438aae..4808b26acb12b32ef809befb9e01a15343e2a05d 100644
--- a/src/test/java/org/olat/core/commons/services/csp/manager/CSPManagerTest.java
+++ b/src/test/java/org/olat/core/commons/services/csp/manager/CSPManagerTest.java
@@ -1,3 +1,22 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
 package org.olat.core.commons.services.csp.manager;
 
 import org.junit.Test;
diff --git a/src/test/java/org/olat/core/util/FormatLatexFormulasTest.java b/src/test/java/org/olat/core/util/FormatLatexFormulasTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2dbc4a3076fc77278611b6822a1fd04cdd3e4577
--- /dev/null
+++ b/src/test/java/org/olat/core/util/FormatLatexFormulasTest.java
@@ -0,0 +1,80 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * 
+ * Initial date: 23 avr. 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@RunWith(Parameterized.class)
+public class FormatLatexFormulasTest {
+	
+	
+	@Parameters
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] {
+                { "<span class='math'></span>", Boolean.TRUE },
+                { "<span class='math inline'></span>", Boolean.TRUE },
+                { "<div class = \"inline math special\"></div>", Boolean.TRUE },
+                { "<span class = math></span>", Boolean.TRUE },
+                { "<math></math>", Boolean.TRUE },
+                { "<MATH></MATH>", Boolean.TRUE },
+        		
+                { "<span class = \"nomath\"></span>", Boolean.FALSE },
+                { "<span class='test' id='math'></span>", Boolean.FALSE },
+                { "<span class='math\"></span>", Boolean.FALSE },
+        			// real example of a wiki
+                { "&lt;h2&gt;Hier den unformatierten Text eingeben&lt;/h2&gt;<p>Ceci est une page avec LaTeX\n</p><div class=\"math\">x^2</div>", Boolean.TRUE },
+                { "<span class=\"math\" title=\"A%20%3D%20%5Cpmatrix%7Ba_%7B11%7D%20%26%20a_%7B12%7D%20%26%20%5Cldots%20%26%20a_%7B1n%7D%5Ccr%0A%20%20%20%20%20%20%20%20%20%20%20%20%20a_%7B21%7D%20%26%20a_%7B22%7D%20%26%20%5Cldots%20%26%20a_%7B2n%7D%5Ccr%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cvdots%20%26%20%5Cvdots%20%26%20%5Cddots%20%26%20%5Cvdots%5Ccr%0A%20%20%20%20%20%20%20%20%20%20%20%20%20a_%7Bm1%7D%20%26%20a_%7Bm2%7D%20%26%20%5Cldots%20%26%20a_%7Bmn%7D%7D%0A%5Cqquad%5Cqquad%0A%5Cpmatrix%7By_1%5Ccr%5Cvdots%5Ccr%20y_k%7D\">A = \\pmatrix{a_{11} &amp; a_{12} &amp; \\ldots &amp; a_{1n}\\cr a_{21} &amp; a_{22} &amp; \\ldots &amp; a_{2n}\\cr \\vdots &amp; \\vdots &amp; \\ddots &amp; \\vdots\\cr a_{m1} &amp; a_{m2} &amp; \\ldots &amp; a_{mn}} \\qquad\\qquad \\pmatrix{y_1\\cr\\vdots\\cr y_k}</span></p>\n\"", Boolean.TRUE },
+                { "<DIV CLASS='math'></DIV>", Boolean.TRUE },
+                
+                { "<span class='math\"''></span>", Boolean.FALSE },
+                { "<span class='math\"'\"></span>", Boolean.FALSE },
+                { "<span class='math></\"span>", Boolean.FALSE },
+                { "<span class='math></'span>", Boolean.FALSE },
+                { "<span class='math", Boolean.TRUE },
+                { "<span class=math\"", Boolean.FALSE }
+        });
+    }
+    
+    private String text;
+    private Boolean result;
+    
+    public FormatLatexFormulasTest(String text, Boolean result) {
+    		this.text = text;
+    		this.result = result;
+    }
+    
+	@Test
+	public void formatLatexFormulas() {
+		Assert.assertEquals(text, result.booleanValue(), Formatter.formatLatexFormulas(text).contains("<script"));
+	}
+}
diff --git a/src/test/java/org/olat/core/util/FormatterTest.java b/src/test/java/org/olat/core/util/FormatterTest.java
index 682e38935e981dfa509494b31bd6683f62279a49..60073f1557c7ec2b6a156e688fbd7a0d6b82ac7a 100644
--- a/src/test/java/org/olat/core/util/FormatterTest.java
+++ b/src/test/java/org/olat/core/util/FormatterTest.java
@@ -132,6 +132,4 @@ public class FormatterTest {
 		Assert.assertEquals("32:23:45", Formatter.formatTimecode(116625000l));
 		Assert.assertEquals("532:23:45", Formatter.formatTimecode(1916625000l));
 	}
-	
-	
 }
diff --git a/src/test/java/org/olat/selenium/CourseTest.java b/src/test/java/org/olat/selenium/CourseTest.java
index e71458f1ccad566a121107c9b9abc965190328af..ae9b91914b03718c23d3338288389eda0e365611 100644
--- a/src/test/java/org/olat/selenium/CourseTest.java
+++ b/src/test/java/org/olat/selenium/CourseTest.java
@@ -871,6 +871,91 @@ public class CourseTest extends Deployments {
 			.assertFirstNameInList(ryomou);
 	}
 	
+
+	/**
+	 * An author creates a course, make it visible for
+	 * members and add an access control by free booking
+	 * with the auto booking enabled.<br/>
+	 * The user search for the course and enters it.<br/>
+	 * The author checks in the list of orders if the booking
+	 * of the user is there and after it checks if the user is
+	 * in the member list too.
+	 * 
+	 * @param loginPage
+	 * @param userBrowser
+	 * @throws IOException
+	 * @throws URISyntaxException
+	 */
+	@Test
+	@RunAsClient
+	public void courseFreeBooking(@InitialPage LoginPage loginPage,
+			@Drone @User WebDriver userBrowser)
+	throws IOException, URISyntaxException {
+		
+		UserVO author = new UserRestClient(deploymentUrl).createAuthor();
+		loginPage.loginAs(author.getLogin(), author.getPassword());
+		UserVO user = new UserRestClient(deploymentUrl).createRandomUser("Kanu");
+		
+		//go to authoring
+		AuthoringEnvPage authoringEnv = navBar
+			.assertOnNavigationPage()
+			.openAuthoringEnvironment();
+		
+		String title = "AutoBooking-" + UUID.randomUUID();
+		//create course
+		authoringEnv
+			.openCreateDropDown()
+			.clickCreate(ResourceType.course)
+			.fillCreateForm(title)
+			.assertOnGeneralTab();
+
+		//open course editor
+		CoursePageFragment course = new CoursePageFragment(browser);
+		RepositoryAccessPage courseAccess = course
+			.openToolsMenu()
+			.edit()
+			.createNode("info")
+			.autoPublish()
+			.accessConfiguration()
+			.setUserAccess(UserAccess.registred);
+		//add booking by secret token
+		courseAccess
+			.boooking()
+			.openAddDropMenu()
+			.addFreeBooking()
+			.configureFreeBooking("It's free");
+		courseAccess
+			.clickToolbarBack();
+		
+		//a user search the course
+		LoginPage userLoginPage = LoginPage.getLoginPage(userBrowser, deploymentUrl);
+		userLoginPage
+			.loginAs(user.getLogin(), user.getPassword())
+			.resume();
+		NavigationPage userNavBar = new NavigationPage(userBrowser);
+		userNavBar
+			.openMyCourses()
+			.openSearch()
+			.extendedSearch(title)
+			.book(title);//book the course
+		//check the course
+		CoursePageFragment bookedCourse = CoursePageFragment.getCourse(userBrowser);
+		bookedCourse
+			.assertOnTitle(title);
+		
+		//Author go in the list of bookings of the course
+		BookingPage bookingList = course
+			.openToolsMenu()
+			.bookingTool();
+		bookingList
+			.assertFirstNameInListIsOk(user);
+		
+		//Author go to members list
+		course
+			.members()
+			.assertFirstNameInList(user);
+	}
+	
 	/**
 	 * An author create a course, set a start and end date for life-cycle.
 	 * It add a participant to the course. It creates a reminder
diff --git a/src/test/java/org/olat/selenium/page/core/BookingPage.java b/src/test/java/org/olat/selenium/page/core/BookingPage.java
index 3f95b8be2440b7d5c1cf99e7716784e6844bce3d..fce48e8a4c746c64ae55efd534ba7a1861668a63 100644
--- a/src/test/java/org/olat/selenium/page/core/BookingPage.java
+++ b/src/test/java/org/olat/selenium/page/core/BookingPage.java
@@ -71,10 +71,9 @@ public class BookingPage {
 	private BookingPage addMethod(String iconClassname) {
 		//wait menu
 		By addMenuBy = By.cssSelector("fieldset.o_ac_configuration ul.dropdown-menu");
-		OOGraphene.waitElement(addMenuBy, 5, browser);
-		By addMethodBy = By.xpath("//fieldset[contains(@class,'o_ac_configuration')]//ul[contains(@class,'dropdown-menu')]//a[//i[contains(@class,'" + iconClassname + "')]]");
-		WebElement methodLink = browser.findElement(addMethodBy);
-		methodLink.click();
+		OOGraphene.waitElement(addMenuBy, browser);
+		By addMethodBy = By.xpath("//fieldset[contains(@class,'o_ac_configuration')]//ul[contains(@class,'dropdown-menu')]//a[i[contains(@class,'" + iconClassname + "')]]");
+		browser.findElement(addMethodBy).click();
 		OOGraphene.waitBusy(browser);
 		return this;
 	}
@@ -113,6 +112,33 @@ public class BookingPage {
 		OOGraphene.waitBusy(browser);
 	}
 	
+	/**
+	 * Select the free booking option
+	 * 
+	 * @return Itself
+	 */
+	public BookingPage addFreeBooking() {
+		addMethod("o_ac_free_icon");
+		OOGraphene.waitModalDialog(browser);
+		return this;
+	}
+	
+	/**
+	 * Save the free booking.
+	 * 
+	 * @param description The description of the booking.
+	 * @return Itself
+	 */
+	public BookingPage configureFreeBooking(String description) {
+		By descriptionBy = By.cssSelector(".o_sel_accesscontrol_free_form .o_sel_accesscontrol_description textarea");
+		browser.findElement(descriptionBy).sendKeys(description);
+		
+		By submitBy = By.cssSelector(".o_sel_accesscontrol_free_form button.btn-primary");
+		browser.findElement(submitBy).click();
+		OOGraphene.waitBusy(browser);
+		return this;
+	}
+	
 	public void save() {
 		By saveButtonBy = By.cssSelector("form button.btn-primary");
 		browser.findElement(saveButtonBy).click();
diff --git a/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java b/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java
index 4a058b65bcb9166c404610564733528737b139e0..5a016a79268b878f0281374531bd8f3369191038 100644
--- a/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java
+++ b/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java
@@ -435,6 +435,15 @@ public class OOGraphene {
 		waitModalDialogDisappears(browser);
 	}
 	
+	public static final void closeWarningBox(WebDriver browser) {
+		By errorBoxBy = By.cssSelector(".modal-body.alert.alert-warning");
+		waitElement(errorBoxBy, 5, browser);
+		By closeButtonBy = By.xpath("//div[not(@id='o_form_dirty_message')]/div[contains(@class,'modal-dialog')]//button[@class='close']");
+		waitElement(closeButtonBy, 5, browser);
+		browser.findElement(closeButtonBy).click();
+		waitModalDialogDisappears(browser);
+	}
+	
 	public static final void waitAndCloseBlueMessageWindow(WebDriver browser) {
 		try {
 			Graphene.waitModel(browser).withTimeout(5, TimeUnit.SECONDS)
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index ca838854f26d8e6694700dc0d0c0a7b004d45f6f..de53ec73f788d8eeb0341c014ab420e61e25ac26 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -65,6 +65,7 @@ import org.junit.runners.Suite;
 	org.olat.core.util.FileUtilsTest.class,
 	org.olat.core.util.FileNameSuffixFilterTest.class,
 	org.olat.core.util.FormatterTest.class,
+	org.olat.core.util.FormatLatexFormulasTest.class,
 	org.olat.core.util.FormatterHourAndSecondsTest.class,
 	org.olat.core.util.EncoderTest.class,
 	org.olat.core.util.SimpleHtmlParserTest.class,