diff --git a/pom.xml b/pom.xml
index 64f4642997efa7ae3f6c9273a9d7f2079ce7858a..01add9d00225563fc6b79e1f74ca38d7131bcb74 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1817,7 +1817,7 @@
 		<dependency>
 			<groupId>org.openolat.jamwiki</groupId>
 			<artifactId>jamwiki-core</artifactId>
-			<version>1.0.0</version>
+			<version>1.0.1</version>
 		</dependency>
 		<dependency>
 			<groupId>opensaml</groupId>
diff --git a/src/main/java/org/olat/core/commons/services/text/impl/nutch/LanguageIdentifier.java b/src/main/java/org/olat/core/commons/services/text/impl/nutch/LanguageIdentifier.java
index 7cd48d66dacd3a7eebcf1e729dcae1fdbe0f9281..318baeb002463dea12834686d1a49cf02f6b9742 100644
--- a/src/main/java/org/olat/core/commons/services/text/impl/nutch/LanguageIdentifier.java
+++ b/src/main/java/org/olat/core/commons/services/text/impl/nutch/LanguageIdentifier.java
@@ -66,8 +66,7 @@ public class LanguageIdentifier {
   /** A global index of ngrams of all supported languages */
   private Map<CharSequence, NGramEntry[]> ngramsIdx = new HashMap<CharSequence, NGramEntry[]>();
 
-  /** The NGramProfile used for identification */
-  private NGramProfile suspect = null;
+
 
 
   /**
@@ -136,7 +135,6 @@ public class LanguageIdentifier {
       }
       log.info(list.toString());
       // Create the suspect profile
-      suspect = new NGramProfile("suspect", minLength, maxLength);
     } catch (Exception e) {
       log.error("", e);
     }
@@ -172,6 +170,7 @@ public class LanguageIdentifier {
         text.setLength(analyzeLength);
     }
 
+    NGramProfile suspect = new NGramProfile("suspect", minLength, maxLength);
     suspect.analyze(text);
     Iterator<NGramEntry> iter = suspect.getSorted().iterator();
     float topscore = Float.MIN_VALUE;
diff --git a/src/main/java/org/olat/core/commons/services/text/impl/nutch/NGramProfile.java b/src/main/java/org/olat/core/commons/services/text/impl/nutch/NGramProfile.java
index 0dd725e558fecf2bde09c50858d51776800371da..b7bb2ce8b5d6599d5074f34eeaf8cf60e746b26c 100644
--- a/src/main/java/org/olat/core/commons/services/text/impl/nutch/NGramProfile.java
+++ b/src/main/java/org/olat/core/commons/services/text/impl/nutch/NGramProfile.java
@@ -127,7 +127,7 @@ public class NGramProfile {
    * 
    * @param w is the word to add
    */
-  public void add(StringBuffer w) {
+  public void add(StringBuilder w) {
     for (int i=minLength; (i <= maxLength) && (i < w.length()); i++) {
       add(w, i);
     }
@@ -203,7 +203,7 @@ public class NGramProfile {
    * @param w
    * @param n sequence length
    */
-  private void add(StringBuffer w, int n) {
+  private void add(StringBuilder w, int n) {
     for (int i=0; i <= w.length()-n; i++) {
       add(w.subSequence(i, i + n));
     }
@@ -254,9 +254,10 @@ public class NGramProfile {
   }
   
   // Inherited JavaDoc
+  @Override
   public String toString() {
 
-    StringBuffer s = new StringBuffer().append("NGramProfile: ")
+    StringBuilder s = new StringBuilder().append("NGramProfile: ")
                                        .append(name).append("\n");
 
     Iterator<NGramEntry> i = getSorted().iterator();
@@ -543,7 +544,7 @@ public class NGramProfile {
      * @param count is the number of occurences of this ngram
      */
     public NGramEntry(String seq, int count) {
-      this.seq = new StringBuffer(seq).subSequence(0, seq.length());
+      this.seq = new StringBuilder(seq).subSequence(0, seq.length());
       this.count = count;
     }
 
diff --git a/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java b/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java
index 32a612f91776795a3c89f6b81335ce484f3006d5..16dfa3f314cfdcf72b8a860767254c7484989347 100644
--- a/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java
+++ b/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java
@@ -274,18 +274,19 @@ public class WebDAVAuthManager implements AuthenticationSPI {
 			try {
 				dbInstance.commit();
 				Identity reloadedIdentity = securityManager.loadIdentityByKey(identity.getKey());
-				securityManager.createAndPersistAuthentication(reloadedIdentity, provider, authUsername, digestToken, Encoder.Algorithm.md5_noSalt);
+				securityManager.createAndPersistAuthentication(reloadedIdentity, provider, authUsername, digestToken, Encoder.Algorithm.md5_iso_8859_1);
 				log.audit(doer.getKey() + " created new WebDAV (HA1) authentication for identity: " + identity.getKey() + " (" + authUsername + ")");
 			} catch(DBRuntimeException e) {
 				log.error("Cannot create digest password with provider " + provider + " for identity:" + identity, e);
 				dbInstance.commit();
 			}
 		} else {
-			String md5DigestToken = Encoder.encrypt(digestToken, null, Encoder.Algorithm.md5_noSalt);
+			String md5DigestToken = Encoder.encrypt(digestToken, null, Encoder.Algorithm.md5_iso_8859_1);
 			if (!md5DigestToken.equals(authHa1.getCredential()) || !authHa1.getAuthusername().equals(authUsername)) {
 				try {
 					authHa1.setCredential(md5DigestToken);
 					authHa1.setAuthusername(authUsername);
+					authHa1.setAlgorithm(Encoder.Algorithm.md5_iso_8859_1.name());
 					securityManager.updateAuthentication(authHa1);
 					log.audit(doer.getKey() + " set new WebDAV (HA1) password for identity: " + identity.getKey() + " (" + authUsername + ")");
 				} catch (DBRuntimeException e) {
diff --git a/src/main/java/org/olat/core/gui/render/StringOutput.java b/src/main/java/org/olat/core/gui/render/StringOutput.java
index 412d7433c221ca65b2fb9712038445fd086ff987..0118b2ea155809d4f10fa95160b36813248b0592 100644
--- a/src/main/java/org/olat/core/gui/render/StringOutput.java
+++ b/src/main/java/org/olat/core/gui/render/StringOutput.java
@@ -207,6 +207,10 @@ public class StringOutput extends Writer {
 		return sb.length();
 	}
 	
+	public boolean contains(String str) {
+		return sb.indexOf(str) >= 0;
+	}
+	
 	public Reader getReader() {
 		return new StringOutputReader();
 	}
diff --git a/src/main/java/org/olat/core/util/Encoder.java b/src/main/java/org/olat/core/util/Encoder.java
index 59e88a10a7c8cea7552f3025c6e2096db9157412..1856b19dd172b80edf310898005c1435101535c0 100644
--- a/src/main/java/org/olat/core/util/Encoder.java
+++ b/src/main/java/org/olat/core/util/Encoder.java
@@ -27,6 +27,7 @@
 package org.olat.core.util;
 
 import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
@@ -52,22 +53,49 @@ public class Encoder {
 	private static final OLog log = Tracing.createLoggerFor(Encoder.class);
 	
 	public enum Algorithm {
-		md5("MD5", 1, true),
-		md5_noSalt("MD5", 1, false),
-		sha1("SHA-1", 100, true),
-		sha256("SHA-256", 100, true),
-		sha512("SHA-512", 100, true),
-		pbkdf2("PBKDF2WithHmacSHA1", 20000, true),
-		sha256Exam("SHA-256", 1, false);
+		/**
+		 * md5 with one iteration and salted (conversion string to bytes made with UTF-8)
+		 */
+		md5("MD5", 1, true, null),
+		/**
+		 * md5 with one iteration without any salt (conversion string to bytes made with UTF-8)
+		 */
+		md5_noSalt("MD5", 1, false, null),
+		/**
+		 * md5 with one iteration and salted (conversion string to bytes made with ISO-8859-1)
+		 */
+		md5_iso_8859_1("MD5", 1, false, "ISO-8859-1"),
+		/**
+		 * SHA-1 with 100 iterations and salted
+		 */
+		sha1("SHA-1", 100, true, null),
+		/**
+		 * SHA-256 with 100 iterations and salted
+		 */
+		sha256("SHA-256", 100, true, null),
+		/**
+		 * SHA-512 with 100 iterations and salted
+		 */
+		sha512("SHA-512", 100, true, null),
+		/**
+		 * PBKDF2 with 20'000 iterations and salted, made to be slow to prevent brute force attack
+		 */
+		pbkdf2("PBKDF2WithHmacSHA1", 20000, true, null),
+		/**
+		 * SHA-256 with one iteration no salted
+		 */
+		sha256Exam("SHA-256", 1, false, null);
 
 		private final boolean salted;
 		private final int iterations;
 		private final String algorithm;
+		private final Charset charset;
 		
-		private Algorithm(String algorithm, int iterations, boolean salted) {
+		private Algorithm(String algorithm, int iterations, boolean salted, String charsetName) {
 			this.algorithm = algorithm;
 			this.iterations = iterations;
 			this.salted = salted;
+			charset = charsetName == null ? null : Charset.forName(charsetName);
 		}
 		
 		public boolean isSalted() {
@@ -82,6 +110,10 @@ public class Encoder {
 			return iterations;
 		}
 		
+		public Charset getCharset() {
+			return charset;
+		}
+		
 		public static final Algorithm find(String str) {
 			if(StringHelper.containsNonWhitespace(str)) {
 				for(Algorithm value:values()) {
@@ -106,7 +138,7 @@ public class Encoder {
 	 * @return MD5 encrypted string
 	 */
 	public static String md5hash(String s) {
-		return md5(s, null);
+		return md5(s, null, null);
 	}
 	
 	public static String sha256Exam(String s) {
@@ -128,24 +160,30 @@ public class Encoder {
 	
 	public static String encrypt(String s, String salt, Algorithm algorithm) {
 		switch(algorithm) {
-			case md5: return md5(s, salt);
+			case md5:
+				return md5(s, salt, algorithm.getCharset());
+			case md5_noSalt:
+				return md5(s, null, algorithm.getCharset());
+			case md5_iso_8859_1:
+				return md5(s, salt, algorithm.getCharset());
 			case sha1:
 			case sha256:
 			case sha512:
 				return digest(s, salt, algorithm);
 			case pbkdf2:
 				return secretKey(s, salt, algorithm);
-			default: return md5(s, salt);
+			default: return md5(s, salt, algorithm.getCharset());
 		}
 	}
 
-	protected static String md5(String s, String salt) {
+	protected static String md5(String s, String salt, Charset charset) {
 		try {
-			byte[] inbytes = s.getBytes();
+			byte[] inbytes = charset == null ? s.getBytes() : s.getBytes(charset);
 			MessageDigest digest = MessageDigest.getInstance(Algorithm.md5.algorithm);
 			digest.reset();
 			if(salt != null) {
-				digest.update(salt.getBytes());
+				byte[] saltbytes = charset == null ? salt.getBytes() : salt.getBytes(charset);
+				digest.update(saltbytes);
 			}
 			byte[] outbytes = digest.digest(inbytes);
 			return md5Encoder.encode(outbytes);
diff --git a/src/main/java/org/olat/core/util/Formatter.java b/src/main/java/org/olat/core/util/Formatter.java
index a4356f856ae82b665312ec876973eaab32a45142..28b0ad2caaa5fb74cc8df3be14ed5afbbce4f748 100644
--- a/src/main/java/org/olat/core/util/Formatter.java
+++ b/src/main/java/org/olat/core/util/Formatter.java
@@ -68,9 +68,6 @@ public class 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;
 	private final DateFormat longDateFormat;
diff --git a/src/main/java/org/olat/core/util/WebappHelper.java b/src/main/java/org/olat/core/util/WebappHelper.java
index 9b9431688e968da772d580a15e8ac3b1ab8ff70a..0aa920f952ac8ac8497ac97dbcce21185660c0c6 100644
--- a/src/main/java/org/olat/core/util/WebappHelper.java
+++ b/src/main/java/org/olat/core/util/WebappHelper.java
@@ -77,6 +77,8 @@ public class WebappHelper implements Initializable, Destroyable, ServletContextA
 	
 	private static String mathJaxCdn;
 	private static String mathJaxConfig;
+	private static boolean mathJaxMarkers;
+	
 	private static String mobileContext;
 	
 	/** need to set this at least once before the actual request, since we cannot extract it from the servletContext, 
@@ -312,6 +314,14 @@ public class WebappHelper implements Initializable, Destroyable, ServletContextA
 	public void setMathJaxConfig(String mathJaxConfig) {
 		WebappHelper.mathJaxConfig = mathJaxConfig;
 	}
+	
+	public static boolean isMathJaxMarkers() {
+		return mathJaxMarkers;
+	}
+	
+	public void setMathJaxMarkers(boolean mathJaxMarkers) {
+		WebappHelper.mathJaxMarkers = mathJaxMarkers;
+	}
 
 	public void setFullPathToSrc(String fullPathToSrc) {
 		File path = new File(fullPathToSrc);
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 3ba94af78d718724cd494ba568c72c1832380527..71919276e949e9e9f71aa88dcaeac36ddfd6dbfa 100644
--- a/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml
+++ b/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml
@@ -49,6 +49,7 @@
 		<property name="mobileContext" value="${mobile.context}" />
 		<property name="mathJaxCdn" value="${mathjax.cdn}"/>
 		<property name="mathJaxConfig" value="${mathjax.config}"/>
+		<property name="mathJaxMarkers" value="${mathjax.markers}"/>
 	</bean>
 
 	<bean id="org.olat.core.helpers.Settings" class="org.olat.core.helpers.Settings" depends-on="org.olat.core.util.WebappHelper">
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
index 68c23ea336c9868a747ec535a1a51de559566c6f..f2a0ef1aa36f330d9d47813e23cc494697fc6619 100644
--- a/src/main/java/org/olat/core/util/filter/impl/NekoHTMLMathScanner.java
+++ b/src/main/java/org/olat/core/util/filter/impl/NekoHTMLMathScanner.java
@@ -24,8 +24,10 @@ import java.io.StringReader;
 import org.cyberneko.html.parsers.SAXParser;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
+import org.olat.core.util.WebappHelper;
 import org.xml.sax.Attributes;
 import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
 /**
@@ -80,5 +82,15 @@ public class NekoHTMLMathScanner {
 				}
 			}
 		}
+
+		@Override
+		public void characters(char[] ch, int start, int length) throws SAXException {
+			if(!mathFound && WebappHelper.isMathJaxMarkers()) {
+				String content = new String(ch, start, length);
+				if(content.contains("\\(") || content.contains("\\[") || content.contains("$$")) {
+					mathFound = true;
+				}
+			}
+		}
 	}
 }
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java
index 2cfa1a044ce1700da89288305387c951170f3309..cf61e54aaa4daab522cd8f9a710d9cb146d2bb37 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java
@@ -30,7 +30,9 @@ import org.olat.core.gui.translator.Translator;
 import org.olat.core.logging.OLATRuntimeException;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
+import org.olat.core.util.Formatter;
 import org.olat.core.util.StringHelper;
+import org.olat.core.util.WebappHelper;
 import org.olat.ims.qti21.AssessmentTestSession;
 import org.olat.ims.qti21.model.audit.CandidateEvent;
 import org.olat.ims.qti21.model.audit.CandidateItemEventType;
@@ -90,6 +92,11 @@ public class AssessmentItemComponentRenderer extends AssessmentObjectComponentRe
             /* Render event */
             AssessmentRenderer renderHints = new AssessmentRenderer(renderer);
             renderItemEvent(renderHints, sb, cmp, latestEvent, itemSessionState, ubu, translator);
+            
+            if(renderHints.isMathJax()
+            		|| (WebappHelper.isMathJaxMarkers() && ((sb.contains("\\(") || sb.contains("\\[") || sb.contains("&&"))))) {
+				sb.append(Formatter.elementLatexFormattingScript("itemBody"));
+			}
         }
 		
 		sb.append("</div>");
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 ec1193e6a76dbd39faae0200360c1a0db76037fa..39f161764afe31e3842661284d6d1917f9dc4176 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
@@ -83,7 +83,6 @@ import org.olat.core.helpers.Settings;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.CodeHelper;
-import org.olat.core.util.Formatter;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
 import org.olat.ims.qti21.QTI21Constants;
@@ -472,27 +471,20 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent
 			}
 			case RubricBlock.QTI_CLASS_NAME: break; //never rendered automatically
 			case Math.QTI_CLASS_NAME: {
-				String domid = "mw_" + CodeHelper.getRAMUniqueID();
-				sb.append("<div id=\"").append(domid).append("\">");
+				sb.append("<div>");
 				renderMath(renderer, sb, component, resolvedAssessmentItem, itemSessionState, (Math)block);
-				sb.append("</div>")
-				  .append(Formatter.elementLatexFormattingScript(domid));
+				sb.append("</div>");
+				renderer.setMathJax(true);
 				break;
 			}
 			case Div.QTI_CLASS_NAME: {
-				String domid = null;
 				if (containsClass(block, "math")) {
-					domid = "mw_" + CodeHelper.getRAMUniqueID();
-					sb.append("<div id=\"").append(domid).append("\">");
+					renderer.setMathJax(true);
 				}
 				renderStartHtmlTag(sb, component, resolvedAssessmentItem, block, null);
 				((Div)block).getFlows().forEach((flow)
 						-> renderFlow(renderer, sb, component, resolvedAssessmentItem, itemSessionState, flow, ubu, translator));
 				renderEndTag(sb, block);
-				if (domid != null) {
-					sb.append("</div>")
-					  .append(Formatter.elementLatexFormattingScript(domid));
-				}
 				break;
 			}
 			case Ul.QTI_CLASS_NAME:
@@ -688,11 +680,10 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent
 				break;
 			}
 			case Math.QTI_CLASS_NAME: {
-				String domid = "mw_" + CodeHelper.getRAMUniqueID();
-				sb.append("<span id=\"").append(domid).append("\">");
+				sb.append("<span>");
 				renderMath(renderer, sb, component, resolvedAssessmentItem, itemSessionState, (Math)inline);
-				sb.append("</span>")
-				  .append(Formatter.elementLatexFormattingScript(domid));
+				sb.append("</span>");
+				renderer.setMathJax(true);
 				break;
 			}
 			case Img.QTI_CLASS_NAME: {
@@ -730,22 +721,12 @@ 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) {
 		if (containsClass(span,"math")) {
-			String domid = "mw_" + CodeHelper.getRAMUniqueID();
-			sb.append("<span id=\"").append(domid).append("\">");
-			
-			renderStartHtmlTag(sb, component, resolvedAssessmentItem, span, null);
-			span.getInlines().forEach((child)
-					-> renderInline(renderer, sb, component, resolvedAssessmentItem, itemSessionState, child, ubu, translator));
-			renderEndTag(sb, span);
-			
-			sb.append("</span>")
-			  .append(Formatter.elementLatexFormattingScript(domid));
-		} else {
-			renderStartHtmlTag(sb, component, resolvedAssessmentItem, span, null);
-			span.getInlines().forEach((child)
-					-> renderInline(renderer, sb, component, resolvedAssessmentItem, itemSessionState, child, ubu, translator));
-			renderEndTag(sb, span);
+			renderer.setMathJax(true);
 		}
+		renderStartHtmlTag(sb, component, resolvedAssessmentItem, span, null);
+		span.getInlines().forEach((child)
+			-> renderInline(renderer, sb, component, resolvedAssessmentItem, itemSessionState, child, ubu, translator));
+		renderEndTag(sb, span);
 	}
 	
 	protected final void renderA(AssessmentRenderer renderer, StringOutput sb, A a, AssessmentObjectComponent component,
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderer.java
index 5e773414c4cda504eb35662abf276596f60427fc..c02ac5c605e0171dd6923b787970f537eff160f4 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderer.java
@@ -32,6 +32,7 @@ import org.olat.core.gui.render.URLBuilder;
 public class AssessmentRenderer {
 
 	private Renderer renderer;
+	private boolean mathJax;
 	private boolean mathXsltDisabled;
 	private boolean solutionMode;
 	private boolean reviewMode;
@@ -145,6 +146,14 @@ public class AssessmentRenderer {
 		this.showTitles = showTitles;
 	}
 
+	public boolean isMathJax() {
+		return mathJax;
+	}
+
+	public void setMathJax(boolean mathJax) {
+		this.mathJax = mathJax;
+	}
+
 	public void setRenderer(Renderer renderer) {
 		this.renderer = renderer;
 	}
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
index df888483943013e99fd4004ca158bb35e216fb23..3c48cf5ca2b6ae1ce11c3e26183d5601fdacdc04 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
@@ -51,7 +51,9 @@ import org.olat.core.gui.translator.Translator;
 import org.olat.core.logging.OLATRuntimeException;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
+import org.olat.core.util.Formatter;
 import org.olat.core.util.StringHelper;
+import org.olat.core.util.WebappHelper;
 import org.olat.course.assessment.AssessmentHelper;
 import org.olat.ims.qti21.AssessmentTestSession;
 import org.olat.ims.qti21.QTI21Constants;
@@ -127,6 +129,11 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe
 				/* Render event */
 				AssessmentRenderer renderHints = new AssessmentRenderer(renderer);
 				renderTestEvent(testSessionController, renderHints, sb, cmp, ubu, translator);
+				
+				if(renderHints.isMathJax()
+	            		|| (WebappHelper.isMathJaxMarkers() && ((sb.contains("\\(") || sb.contains("\\[") || sb.contains("&&"))))) {
+					sb.append(Formatter.elementLatexFormattingScript("itemBody"));
+				}
 			}
 		}
 	}
diff --git a/src/main/java/org/olat/modules/wiki/gui/components/wikiToHtml/OlatWikiDataHandler.java b/src/main/java/org/olat/modules/wiki/gui/components/wikiToHtml/OlatWikiDataHandler.java
index 0e17435af6486558da56883f5f1a4c99cf4414a7..cef8bf64a7525f304cff4aefbf9c69fe570ea5e7 100644
--- a/src/main/java/org/olat/modules/wiki/gui/components/wikiToHtml/OlatWikiDataHandler.java
+++ b/src/main/java/org/olat/modules/wiki/gui/components/wikiToHtml/OlatWikiDataHandler.java
@@ -146,7 +146,7 @@ public class OlatWikiDataHandler implements DataHandler {
 			if (InterWikiHandler.isInterWiki(topic)) {
 				return true;
 			}
-		} catch (Exception e) {
+		} catch (Exception | Error e) {
 			log.warn("Cannot initialize InterWikiHandler", e);
 		}
 
diff --git a/src/main/java/org/olat/modules/wiki/gui/components/wikiToHtml/WikiMarkupRenderer.java b/src/main/java/org/olat/modules/wiki/gui/components/wikiToHtml/WikiMarkupRenderer.java
index 2b78d36222d7562c4e9e74cf5ee1874d302dfaa8..cdf4682d6d141ec2844eb8cf8d463912aa11b61a 100644
--- a/src/main/java/org/olat/modules/wiki/gui/components/wikiToHtml/WikiMarkupRenderer.java
+++ b/src/main/java/org/olat/modules/wiki/gui/components/wikiToHtml/WikiMarkupRenderer.java
@@ -55,14 +55,6 @@ import org.olat.core.util.Formatter;
  */
 public class WikiMarkupRenderer extends DefaultComponentRenderer {
 
-	/**
-	 * @see org.olat.core.gui.components.ComponentRenderer#render(org.olat.core.gui.render.Renderer,
-	 *      org.olat.core.gui.render.StringOutput,
-	 *      org.olat.core.gui.components.Component,
-	 *      org.olat.core.gui.render.URLBuilder,
-	 *      org.olat.core.gui.translator.Translator,
-	 *      org.olat.core.gui.render.RenderResult, java.lang.String[])
-	 */
 	@Override
 	public void render(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, Translator translator,
 			RenderResult renderResult, String[] args) {
@@ -97,9 +89,9 @@ public class WikiMarkupRenderer extends DefaultComponentRenderer {
 				String targetUrl = " onclick=\"o_XHREvent(jQuery(this).attr('href'),false,true); return false;\"";
 				input.setURLTarget(targetUrl);
 			}
-			sb.append("<div style=\"min-height:"+ wikiComp.getMinHeight() +"px\" id=\"");
-			sb.append(uniqueId);
-			sb.append("\">");
+			sb.append("<div style=\"min-height:").append(wikiComp.getMinHeight()).append("px\" id=\"")
+			  .append(uniqueId)
+			  .append("\">");
 		
 			JFlexParser parser = new JFlexParser(input);
 			parsedDoc = parser.parseHTML(wikiComp.getWikiContent());
@@ -109,12 +101,11 @@ public class WikiMarkupRenderer extends DefaultComponentRenderer {
 			throw new OLATRuntimeException(this.getClass(), "error while rendering wiki page with content:"+ wikiComp.getWikiContent(), e);
 		}
 		// Use global js math formatter for latex formulas
-		sb.append(Formatter.formatLatexFormulas(parsedDoc.getContent()));
-		sb.append("</div>");
+		sb.append(Formatter.formatLatexFormulas(parsedDoc.getContent()))
+		  .append("</div>");
 		//set targets of media, image and external links to target "_blank" 
-		sb.append("<script type=\"text/javascript\">/* <![CDATA[ */ ");
-		String instanceUrl = Settings.getServerContextPathURI();
-		sb.append("changeAnchorTargets('").append(uniqueId).append("','").append(instanceUrl).append("');");
-		sb.append("/* ]]> */</script>");
+		sb.append("<script type=\"text/javascript\">/* <![CDATA[ */ ")
+		  .append("changeAnchorTargets('").append(uniqueId).append("','").append(Settings.getServerContextPathURI()).append("');")
+		  .append("/* ]]> */</script>");
 	}
 }
diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties
index 2d6d9e0ad9b6e079370faddf52706d7968425145..62e2daa99201caa91f67da558c455e41dc674800 100644
--- a/src/main/resources/serviceconfig/olat.properties
+++ b/src/main/resources/serviceconfig/olat.properties
@@ -518,6 +518,8 @@ jmx.rmi.port=3000
 
 mathjax.cdn=//mathjax.openolat.org/mathjax/2.7-latest/
 mathjax.config=TeX-AMS-MML_HTMLorMML
+# find \( \), \[ \], $$ to trigger MathJax rendering
+mathjax.markers=true
 
 ########################################################################
 # Database settings
diff --git a/src/test/java/org/olat/core/commons/services/text/TextServiceTest.java b/src/test/java/org/olat/core/commons/services/text/TextServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5817480152c415d5af0e4a71aafba8f3ab154f59
--- /dev/null
+++ b/src/test/java/org/olat/core/commons/services/text/TextServiceTest.java
@@ -0,0 +1,97 @@
+package org.olat.core.commons.services.text;
+
+import java.util.Locale;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 29 juin 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class TextServiceTest extends OlatTestCase {
+
+	private static final OLog log = Tracing.createLoggerFor(TextServiceTest.class);
+	
+	@Autowired
+	private TextService textService;
+	
+	@Test
+	public void detectLocale_french() {
+		Locale locale = textService.detectLocale("Bonjour, je parle français.");
+		Assert.assertNotNull(locale);
+		Assert.assertEquals("fr", locale.getLanguage());
+	}
+	
+	@Test
+	public void detectLocale_english() {
+		Locale locale = textService.detectLocale("Hi, I speak British english.");
+		Assert.assertNotNull(locale);
+		Assert.assertEquals("en", locale.getLanguage());
+	}
+	
+	@Test
+	public void concurrentDetection() throws InterruptedException {
+		int numOfThreads = 10;
+		CountDownLatch latch = new CountDownLatch(numOfThreads);
+		NGramThread[] threads = new NGramThread[numOfThreads];
+		for(int i=numOfThreads; i-->0; ) {
+			threads[i] = new NGramThread(textService, latch);
+		}
+		
+		for(int i=numOfThreads; i-->0; ) {
+			threads[i].start();
+		}
+
+		latch.await(120, TimeUnit.SECONDS);
+		
+		for(int i=numOfThreads; i-->0; ) {
+			if(threads[i].getException() != null) {
+				log.error("", threads[i].getException());
+			}
+			Assert.assertNull(threads[i].getException());
+		}
+	}
+
+	private static class NGramThread extends Thread {
+
+		private final TextService service;
+		private final CountDownLatch latch;
+		
+		private Exception exception;
+		
+		public NGramThread(TextService service, CountDownLatch latch) {
+			this.service = service;
+			this.latch = latch;
+		}
+		
+		public Exception getException() {
+			return exception;
+		}
+
+		@Override
+		public void run() {
+			try {
+				Thread.sleep(100);
+				for(int i=0; i<2500; i++) {
+					Locale locale = service.detectLocale("Bonjour, je parle français.");
+					Assert.assertNotNull(locale);
+					
+				}
+			} catch (Exception e) {
+				exception = e;
+			} finally {
+				latch.countDown();
+			}
+		}
+	}
+
+}
diff --git a/src/test/java/org/olat/core/util/EncoderTest.java b/src/test/java/org/olat/core/util/EncoderTest.java
index de7bb013083623cce7800e11154533b5798cb43d..e0180c0ad6790bc1bb146fe91c9cfebb6ebac649 100644
--- a/src/test/java/org/olat/core/util/EncoderTest.java
+++ b/src/test/java/org/olat/core/util/EncoderTest.java
@@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.junit.Assert;
 import org.junit.Test;
+import org.olat.core.commons.services.webdav.manager.WebDAVManagerImpl;
 import org.olat.core.util.Encoder.Algorithm;
 
 /**
@@ -41,7 +42,7 @@ public class EncoderTest {
 		//the openolat password as saved on our database
 		String openolat = "c14c4d01c090a065eaa619ca92f8cbc0";
 
-		String hashedOpenolat_1 = Encoder.md5("openolat", null);
+		String hashedOpenolat_1 = Encoder.md5("openolat", null, null);
 		Assert.assertEquals(openolat, hashedOpenolat_1);
 		
 		String encryptedOpenolat_1 = Encoder.md5hash("openolat");
@@ -51,12 +52,35 @@ public class EncoderTest {
 		Assert.assertEquals(openolat, encryptedOpenolat_2);
 	}
 	
+	/**
+	 * The test check the use of the UTF-8 or the ISO-8859-1
+	 * on the hashing algorithm used by the digest authentication.
+	 */
+	@Test
+	public void testDigestLikeCompatibility() {
+		// UTF-8 encoded of standard username
+		String rawDigest = digest("myUsername@openolat.org", "de#34KL");
+		String ha1_utf8 = Encoder.encrypt(rawDigest, null, Encoder.Algorithm.md5_noSalt);
+		String ha1_iso = Encoder.encrypt(rawDigest, null, Encoder.Algorithm.md5_iso_8859_1);
+		Assert.assertEquals(ha1_utf8, ha1_iso);
+		
+		// ISO-8859-1 difference with Umlaut
+		String rawUmlautDigest = digest("myUsern\u00E4me@openolat.org", "de#34KL");
+		String ha1_umlaut_utf8 = Encoder.encrypt(rawUmlautDigest, null, Encoder.Algorithm.md5_noSalt);
+		String ha1_umlaut_iso = Encoder.encrypt(rawUmlautDigest, null, Encoder.Algorithm.md5_iso_8859_1);
+		Assert.assertNotEquals(ha1_umlaut_utf8, ha1_umlaut_iso);		
+	}
+	
+	private String digest(String authUsername, String password) {
+		return authUsername + ":" + WebDAVManagerImpl.BASIC_AUTH_REALM + ":" + password;
+	}
+	
 	/**
 	 * Dummy test which check that the salts are not always equals.
 	 */
 	@Test
 	public void testSalt() {
-		Set<String> history = new HashSet<String>();
+		Set<String> history = new HashSet<>();
 		for(int i=0; i<100; i++) {
 			String salt = Encoder.getSalt();
 			Assert.assertFalse(history.contains(salt));
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index c522d8d91989d71a3a35df8171501d4d2af2fa9d..7b3ff4d4151bd5227b0d9044a1b89076c2bc59c2 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -109,6 +109,7 @@ import org.junit.runners.Suite;
 	org.olat.core.commons.services.sms.manager.MessageLogDAOTest.class,
 	org.olat.core.commons.services.taskexecutor.PersistentTaskDAOTest.class,
 	org.olat.core.commons.services.taskexecutor.TaskExecutorManagerTest.class,
+	org.olat.core.commons.services.text.TextServiceTest.class,
 	org.olat.admin.user.delete.service.UserDeletionManagerTest.class,
 	org.olat.group.BusinessGroupManagedFlagsTest.class,
 	org.olat.group.test.BGRightManagerTest.class,
diff --git a/src/test/java/org/olat/user/UserNameAndPasswordSyntaxCheckerWithRegexpTest.java b/src/test/java/org/olat/user/UserNameAndPasswordSyntaxCheckerWithRegexpTest.java
index eb6ba5527b085090702c8f11fcdf04431a7477da..8ac186f96499df318c9419becad751b067b9b749 100644
--- a/src/test/java/org/olat/user/UserNameAndPasswordSyntaxCheckerWithRegexpTest.java
+++ b/src/test/java/org/olat/user/UserNameAndPasswordSyntaxCheckerWithRegexpTest.java
@@ -30,25 +30,34 @@ import org.junit.Test;
  */
 public class UserNameAndPasswordSyntaxCheckerWithRegexpTest {
 	
+	@Test
+	public void defaultPasswordCheck() {
+		UserNameAndPasswordSyntaxCheckerWithRegexp checker = new UserNameAndPasswordSyntaxCheckerWithRegexp();
+		Assert.assertFalse(checker.syntaxCheckOlatPassword("Kan"));
+		Assert.assertTrue(checker.syntaxCheckOlatPassword("Kanu#01"));
+		Assert.assertFalse(checker.syntaxCheckOlatPassword("Kan\u00FC#01"));
+	}
+	
 	/**
 	 * Min. 7 characters, one uppercase, one lowercase, one number
 	 */
 	@Test
-	public void testCustomPasswordCheck_upperLowerCase_number() {
+	public void customPasswordCheck_upperLowerCase_number() {
 		UserNameAndPasswordSyntaxCheckerWithRegexp checker = new UserNameAndPasswordSyntaxCheckerWithRegexp();
 		checker.setPasswordRegExp("(?=^.{7,}$)((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$");
 		
 		Assert.assertTrue(checker.syntaxCheckOlatPassword("Kanu#01"));
 		Assert.assertTrue(checker.syntaxCheckOlatPassword("Kanuunc1"));
 		Assert.assertFalse(checker.syntaxCheckOlatPassword("Kanu#1"));//less than 7 characters
-		Assert.assertFalse(checker.syntaxCheckOlatPassword("Kanuunch"));//no number
+		Assert.assertFalse(checker.syntaxCheckOlatPassword("Kanuunch"));//no number	Kan\u00FC
+		Assert.assertTrue(checker.syntaxCheckOlatPassword("Kan\u00FCunc1"));// Umlaut allowed
 	}
 	
 	/**
 	 * Min. 8 characters, one uppercase, one lowercase, one number, one special character
 	 */
 	@Test
-	public void testCustomPasswordCheck_upperLowerCase_number_special() {
+	public void customPasswordCheck_upperLowerCase_number_special() {
 		UserNameAndPasswordSyntaxCheckerWithRegexp checker = new UserNameAndPasswordSyntaxCheckerWithRegexp();
 		checker.setPasswordRegExp("(?=^.{8,}$)((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z])(?=.*[$@$!%*#?&]).*$");