diff --git a/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumController.java b/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumController.java index e206c36eefdf52cecbea57664c8c80c87e44d91c..f13424a54cfebdb22c9ba26ae743f28175079538 100644 --- a/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumController.java +++ b/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumController.java @@ -559,7 +559,8 @@ public class ForumController extends BasicController implements GenericEventList currentMsg = msgEditCtr.getMessageBackAfterEdit(); currentMsg.setModifier(ureq.getIdentity()); - fm.updateMessage(currentMsg, null); + final boolean changeLastModifiedDate = true; // OLAT-6295 + fm.updateMessage(currentMsg, changeLastModifiedDate, null); // if notification is enabled -> notify the publisher about news if (subsContext != null) { NotificationsManager.getInstance().markPublisherNews(subsContext, ureq.getIdentity()); @@ -1484,9 +1485,10 @@ public class ForumController extends BasicController implements GenericEventList currentMsg = fm.loadMessage(msg.getKey()); Status status = Status.getStatus(currentMsg.getStatusCode()); status.setClosed(closed); - if(currentMsg.getParent()==null) { - currentMsg.setStatusCode(Status.getStatusCode(status)); - fm.updateMessage(currentMsg, new ForumChangedEvent("close")); + if (currentMsg.getParent() == null) { + currentMsg.setStatusCode(Status.getStatusCode(status)); + final boolean changeLastModifiedDate = !closed; //OLAT-6295 + fm.updateMessage(currentMsg, changeLastModifiedDate, new ForumChangedEvent("close")); } // do logging ILoggingAction loggingAction; @@ -1518,7 +1520,8 @@ public class ForumController extends BasicController implements GenericEventList status.setHidden(hidden); if(currentMsg.getParent()==null) { currentMsg.setStatusCode(Status.getStatusCode(status)); - fm.updateMessage(currentMsg, new ForumChangedEvent("hide")); + final boolean changeLastModifiedDate = !hidden; //OLAT-6295 + fm.updateMessage(currentMsg, changeLastModifiedDate, new ForumChangedEvent("hide")); } // do logging ILoggingAction loggingAction; diff --git a/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumManager.java b/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumManager.java index 5e2746d4aa9fcff1ee2352720c608d9e95d91c0c..271cf1034b954897a24cbfb5976d9e0af2634a80 100644 --- a/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumManager.java +++ b/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumManager.java @@ -320,14 +320,23 @@ public class ForumManager extends BasicManager { } /** - * Update message and fire MultiUserEvent, if any provided. - * If a not null ForumChangedEvent object is provided, then fire event to listeners. + * Update message and fire MultiUserEvent, if any provided. If a not null + * ForumChangedEvent object is provided, then fire event to listeners. + * * @param m + * @param updateLastModifiedDate + * true: the last modified date is updated to trigger a + * notification; false: last modified date is not modified and no + * notification is sent * @param event */ - public void updateMessage(Message m, ForumChangedEvent event) { + public void updateMessage(final Message m, final boolean updateLastModifiedDate, final ForumChangedEvent event) { updateCounters(m); - m.setLastModified(new Date()); + // OLAT-6295 Only update last modified for the operations edit(update), show, and open. + // Don't update the last modified date for the operations close, hide, move and split. + if (updateLastModifiedDate) { + m.setLastModified(new Date()); + } DBFactory.getInstance().updateObject(m); if (event!=null) { CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new ForumChangedEvent("hide"), m.getForum()); @@ -488,20 +497,21 @@ public class ForumManager extends BasicManager { Iterator<Message> messageIterator = subthreadList.iterator(); Message firstMessage = null; - DB db = DBFactory.getInstance(); + final DB db = DBFactory.getInstance(); + final boolean changeLastModifiedDate = false; // OLAT-6295 if (messageIterator.hasNext()) { firstMessage = messageIterator.next(); firstMessage = (Message) db.loadObject(firstMessage); firstMessage.setParent(null); firstMessage.setThreadtop(null); - this.updateMessage(firstMessage, new ForumChangedEvent("split")); + this.updateMessage(firstMessage, changeLastModifiedDate, new ForumChangedEvent("split")); newTopMessage = firstMessage; } while (firstMessage != null && messageIterator.hasNext()) { Message message = messageIterator.next(); message = (Message) db.loadObject(message); message.setThreadtop(firstMessage); - this.updateMessage(message, null); + this.updateMessage(message, changeLastModifiedDate, null); } } return newTopMessage; @@ -522,17 +532,19 @@ public class ForumManager extends BasicManager { this.getSubthread(msg, oldThreadList, subThreadList); // one has to set a new parent for all childs of the moved message // first message of sublist has to get the parent from the moved message - for(Message childMessage : subThreadList) { - childMessage = (Message)db.loadObject(childMessage); + final boolean changeLastModifiedDate = false; // OLAT-6295 + for (Message childMessage : subThreadList) { + childMessage = (Message) db.loadObject(childMessage); childMessage.setParent(msg.getParent()); - updateMessage(childMessage, null); + updateMessage(childMessage, changeLastModifiedDate, null); } // now move the message to the choosen thread - Message oldMessage = (Message)db.loadObject(msg); + final Message oldMessage = (Message) db.loadObject(msg); Message message = createMessage(); message.setCreator(oldMessage.getCreator()); message.setForum(oldMessage.getForum()); message.setModifier(oldMessage.getModifier()); + message.setLastModified(oldMessage.getLastModified()); // OLAT-6295 message.setTitle(oldMessage.getTitle()); message.setBody(oldMessage.getBody()); message.setThreadtop(topMsg); diff --git a/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumNotificationsHandler.java b/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumNotificationsHandler.java index ecf943c8710e01475c2ecf931349c48d9b21da0c..867067a3981e08fc187dac8c0f384facb19143f4 100644 --- a/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumNotificationsHandler.java +++ b/olat3/webapp/WEB-INF/src/org/olat/modules/fo/ForumNotificationsHandler.java @@ -95,7 +95,8 @@ public class ForumNotificationsHandler extends LogDelegator implements Notificat } else { name = NotificationHelper.getFormatedName(creator); } - String desc = translator.translate("notifications.entry", new String[] { title, name }); + final String descKey = "notifications.entry" + (mInfo.getCreationDate().equals(mInfo.getLastModified()) ? "" : ".modified"); + final String desc = translator.translate(descKey, new String[] { title, name }); String urlToSend = null; if(p.getBusinessPath() != null) { urlToSend = NotificationHelper.getURLFromBusinessPathString(p, businessControlString + mInfo.getKey().toString() + "]"); diff --git a/olat3/webapp/WEB-INF/src/org/olat/modules/fo/_i18n/LocalStrings_de.properties b/olat3/webapp/WEB-INF/src/org/olat/modules/fo/_i18n/LocalStrings_de.properties index cf92bf480cd16a2b1a11ebf1a57218f985da78e2..93e51812b5ecd5bc0ced2fdfed4dd9de38427c3a 100644 --- a/olat3/webapp/WEB-INF/src/org/olat/modules/fo/_i18n/LocalStrings_de.properties +++ b/olat3/webapp/WEB-INF/src/org/olat/modules/fo/_i18n/LocalStrings_de.properties @@ -65,6 +65,7 @@ msg.update=Editieren msg.upload=Datei anh\u00E4ngen no=Nein notifications.entry=Nachricht "{0}" von {1} erstellt +notifications.entry.modified=Nachricht "{0}" von {1} ver\u00E4ndert notifications.header=In einem von Ihnen abonnierten Forum befinden sich neue Nachrichten\: notifications.header.course=Forum in Kurs "{0}" notifications.header.group=Forum in Gruppe "{0}" diff --git a/olat3/webapp/WEB-INF/src/org/olat/modules/fo/_i18n/LocalStrings_en.properties b/olat3/webapp/WEB-INF/src/org/olat/modules/fo/_i18n/LocalStrings_en.properties index 894091f5a7656ab1278bdcf8aef4f39182bb7001..8b23ad1873a14cabd58388d25e1faed13435c10c 100644 --- a/olat3/webapp/WEB-INF/src/org/olat/modules/fo/_i18n/LocalStrings_en.properties +++ b/olat3/webapp/WEB-INF/src/org/olat/modules/fo/_i18n/LocalStrings_en.properties @@ -68,6 +68,7 @@ msg.update=Edit msg.upload=Attach file no=No notifications.entry=Message "{0}" created by {1} +notifications.entry.modified=Message "{0}" modified by {1} notifications.header=There are new messages in a forum subscribed by you\: notifications.header.course=Forum in course "{0}" notifications.header.group=Forum in group "{0}" diff --git a/olat3/webapp/WEB-INF/src/org/olat/portfolio/model/structel/EPStructureElement.java b/olat3/webapp/WEB-INF/src/org/olat/portfolio/model/structel/EPStructureElement.java index f0aa61199b895220cec1976e02f17a55ef8dfed4..47110da1326b54a2ecc5e3980652cffdbec74080 100755 --- a/olat3/webapp/WEB-INF/src/org/olat/portfolio/model/structel/EPStructureElement.java +++ b/olat3/webapp/WEB-INF/src/org/olat/portfolio/model/structel/EPStructureElement.java @@ -23,8 +23,10 @@ package org.olat.portfolio.model.structel; import java.util.ArrayList; import java.util.List; +import org.olat.core.commons.persistence.PersistenceHelper; import org.olat.core.commons.persistence.PersistentObject; import org.olat.core.id.OLATResourceable; +import org.olat.core.util.Formatter; import org.olat.core.util.filter.FilterFactory; import org.olat.portfolio.model.restriction.CollectRestriction; import org.olat.resource.OLATResource; @@ -84,7 +86,8 @@ public class EPStructureElement extends PersistentObject implements PortfolioStr * @uml.property name="title" */ public void setTitle(String title) { - this.title = title; + // OLAT-6439 truncate to allowed database limit + this.title = PersistenceHelper.truncateStringDbSave(title, 512, true); } /** @@ -107,7 +110,8 @@ public class EPStructureElement extends PersistentObject implements PortfolioStr * @uml.property name="description" */ public void setDescription(String description) { - this.description = description; + // OLAT-6439 truncate to allowed database limit + this.description = PersistenceHelper.truncateStringDbSave(description, 2024, true); } @@ -120,9 +124,7 @@ public class EPStructureElement extends PersistentObject implements PortfolioStr } else if(desc.length() > 50) { //to remain valid html: remove html tags desc = FilterFactory.getHtmlTagAndDescapingFilter().filter(desc); - if(desc.length() > 50) { - desc = desc.substring(0, 50) + "..."; - } + desc = Formatter.truncate(desc, 50); } return desc; } diff --git a/olatcore/src/main/java/org/olat/core/commons/persistence/PersistenceHelper.java b/olatcore/src/main/java/org/olat/core/commons/persistence/PersistenceHelper.java index 1614ef45cb8f607345f0102f2b8a56caad871241..631e3e13cfa3286be3d0ad37d0bcaeb70a52189d 100644 --- a/olatcore/src/main/java/org/olat/core/commons/persistence/PersistenceHelper.java +++ b/olatcore/src/main/java/org/olat/core/commons/persistence/PersistenceHelper.java @@ -23,8 +23,11 @@ package org.olat.core.commons.persistence; import java.util.Iterator; import java.util.List; +import java.util.Properties; import org.olat.core.id.Persistable; +import org.olat.core.util.Formatter; +import org.olat.core.util.filter.FilterFactory; /** * Description:<BR> @@ -35,6 +38,67 @@ import org.olat.core.id.Persistable; * @author gnaegi */ public class PersistenceHelper { + private static boolean charCountNativeUTF8 = true; + + /** + * Spring only constructor. Use static methods to access the helper + * @param firstKeyValues init properties from spring + */ + public PersistenceHelper(Properties firstKeyValues) { + // OLAT-6439 Init flag to know if the database understands native UTF-8 + // Only necessary for old MySQL database of UZH + if (firstKeyValues != null) { + String dbvendor = firstKeyValues.getProperty("dbvendor"); + String dboptions = firstKeyValues.getProperty("mysqloptions"); + if ("mysql".equals(dbvendor) && dboptions != null && dboptions.contains("characterEncoding=UTF-8") && dboptions.contains("useOldUTF8Behavior=true")) { + charCountNativeUTF8 = false; + } + } + } + + /** + * Truncate the given original string to the defined max length. The method + * does also check if some legacy UTF-8 conversion is done by the database + * driver and shortens the allowed length to two third to be on the save + * side + * + * @param original + * The original String + * @param maxLength + * The max length allowed by the database schema + * @param showThreeDots + * Replace the last three characters with ... to indicate that + * the string has been truncated + * @return The truncated string + */ + public static String truncateStringDbSave(final String original, int maxLength, final boolean showThreeDots) { + if (original == null) { + return null; + } + if (!charCountNativeUTF8) { + // When using legacy UTF-8 conversion we have actually no idea how + // long the string can be since this is a hidden information to + // MySQL. In that case we subtract 1/3 and cross our fingers + maxLength = maxLength - (maxLength/3); + } + // Check if too long + int length = original.length(); + if (length <= maxLength) { + return original; + } + // 1) Remove all HTML markup first as truncating could lead to invalid HTML code. + String result = FilterFactory.getHtmlTagAndDescapingFilter().filter(original); + if (length <= maxLength) { + return original; + } + // 2) Truncate to maxLength + if (showThreeDots) { + result = Formatter.truncate(result, maxLength); + } else { + result = Formatter.truncateOnly(result, maxLength); + } + return result; + } /** * diff --git a/olatcore/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml b/olatcore/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml index 05bf9a33a8bb1f4813f8d946f66b5869a8ae9dc5..25de2aa6992fe19f42118830c2c22fe33ac34945 100644 --- a/olatcore/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml +++ b/olatcore/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml @@ -95,7 +95,14 @@ <property name="sessionFactory" ref="sessionFactory"/> </bean> - +<bean id="persistenceHelper" class="org.olat.core.commons.persistence.PersistenceHelper"> + <constructor-arg> + <props> + <prop key="dbvendor">${db.vendor}</prop> + <prop key="mysqloptions">${db.url.options.mysql}</prop> + </props> + </constructor-arg> +</bean> <bean id="mysqlHibernateProperties" class="org.olat.core.commons.persistence.DBVendorHibernatePropertiesSimplification"> <constructor-arg> diff --git a/olatcore/src/main/java/org/olat/core/servlets/DefaultServlet.java b/olatcore/src/main/java/org/olat/core/servlets/DefaultServlet.java index 1d9e7af0c79654dd95abea03e1a755066337a2d8..6dd7b185a68ffb562dddd36e4aea67fb8652de7c 100644 --- a/olatcore/src/main/java/org/olat/core/servlets/DefaultServlet.java +++ b/olatcore/src/main/java/org/olat/core/servlets/DefaultServlet.java @@ -91,10 +91,11 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.RandomAccessFile; import java.io.Reader; -import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.Timestamp; +import java.text.Normalizer; +import java.text.Normalizer.Form; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -119,6 +120,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.naming.resources.Resource; import org.apache.naming.resources.ResourceAttributes; import org.olat.core.commons.servlets.util.URLEncoder; +import org.olat.core.helpers.Settings; import org.olat.core.util.servlets.FastHttpDateFormat; import org.olat.core.util.servlets.Globals; import org.olat.core.util.servlets.MD5Encoder; @@ -814,14 +816,21 @@ public class DefaultServlet if (normalized == null) return (null); - if (decode) { - try { // we need to decode potential UTF-8 characters in the URL - normalized = new String(normalized.getBytes(), "UTF-8"); - } catch (UnsupportedEncodingException e) {} - } - - if (normalized.equals("/.")) - return "/"; + ///// + // OLAT-6294 Commented decoding code block + // Never decode URL as URL encoding must be set to UTF-8 on + // the connector level which leads to a double decoding which breaks + // umlaute in WebDAV +// if (decode) { +// try { // we need to decode potential UTF-8 characters in the URL +// normalized = new String(normalized.getBytes(), "UTF-8"); +// } catch (UnsupportedEncodingException e) {} +// } + ///// + // Normalize the unicode characters for comparison with file system + normalized = Normalizer.normalize(normalized, Form.NFC); + + if (normalized.equals("/.")) return "/"; // Normalize the slashes and add leading slash if necessary if (normalized.indexOf('\\') >= 0) @@ -1057,9 +1066,8 @@ public class DefaultServlet if (resourceInfo.collection) { if (content) { - // Serve the directory browser - resourceInfo.setStream - (render(request.getContextPath(), resourceInfo)); + // Serve the directory browser + resourceInfo.setStream(render(request.getContextPath() + "/webdav", resourceInfo)); } } @@ -1401,20 +1409,21 @@ public class DefaultServlet sb.append("<head>\r\n"); sb.append("<title>"); sb.append(name); - sb.append("</title>\r\n"); - sb.append("<STYLE><!--"); - sb.append("H1{font-family : sans-serif,Arial,Tahoma;color : white;background-color : #0086b2;} "); - sb.append("H3{font-family : sans-serif,Arial,Tahoma;color : white;background-color : #0086b2;} "); - sb.append("BODY{font-family : sans-serif,Arial,Tahoma;color : black;background-color : white;} "); - sb.append("B{color : white;background-color : #0086b2;} "); - sb.append("A{color : black;} "); - sb.append("HR{color : #0086b2;} "); - sb.append("--></STYLE> "); - sb.append("</head>\r\n"); - sb.append("<body>"); - sb.append("<h1>"); - sb.append(name); - + sb.append("</title>\r\n"); + sb.append("<STYLE><!--"); + sb.append("H1{font-family : Verdana, Tahoma, Arial, Geneva, Helvetica, sans-serif; color : white;background-color : #96A4BA;} "); + sb.append("H3{font-family : Verdana, Tahoma, Arial, Geneva, Helvetica, sans-serif; color : white;background-color : #96A4BA;} "); + sb.append("BODY{font-family : Verdana, Tahoma, Arial, Geneva, Helvetica, sans-serif; color : black;background-color : white;} "); + sb.append("A{color : #2A518D;} "); + sb.append("HR{color : #0086b2;} "); + sb.append("--></STYLE> "); + sb.append("</head>\r\n"); + sb.append("<body>"); + sb.append("<h1>"); + sb.append(name); + sb.append(" "); + sb.append("</h1>"); + // Render the link to our parent (if required) String parentDirectory = name; if (parentDirectory.endsWith("/")) { @@ -1422,23 +1431,19 @@ public class DefaultServlet parentDirectory.substring(0, parentDirectory.length() - 1); } int slash = parentDirectory.lastIndexOf('/'); - if (slash >= 0) { - String parent = name.substring(0, slash); - sb.append(" - <a href=\""); - sb.append(rewriteUrl(contextPath)); - if (parent.equals("")) - parent = "/"; - sb.append(rewriteUrl(parent)); - if (!parent.endsWith("/")) - sb.append("/"); - sb.append("\">"); - sb.append("<b>"); - sb.append(parent); - sb.append("</b>"); - sb.append("</a>"); - } - - sb.append("</h1>"); + if (slash >= 0) { + String parent = name.substring(0, slash); + sb.append("<p><a href=\""); + sb.append(rewriteUrl(contextPath)); + if (parent.equals("")) parent = "/"; + sb.append(rewriteUrl(parent)); + if (!parent.endsWith("/")) sb.append("/"); + sb.append("\">"); + sb.append("Back to "); + sb.append(parent); + sb.append("</a></p>"); + } + sb.append("<HR size=\"1\" noshade>"); sb.append("<table width=\"100%\" cellspacing=\"0\"" + @@ -1482,13 +1487,12 @@ public class DefaultServlet sb.append(">\r\n"); shade = !shade; - sb.append("<td align=\"left\"> \r\n"); - sb.append("<a href=\""); - sb.append(rewriteUrl(contextPath)); - resourceName = rewriteUrl(name + resourceName); - sb.append(resourceName); - if (childResourceInfo.collection) - sb.append("/"); + sb.append("<td align=\"left\"> \r\n"); + sb.append("<a href=\""); + sb.append(rewriteUrl(contextPath)); + resourceName = rewriteUrl(name + "/" + resourceName); + sb.append(resourceName); + if (childResourceInfo.collection) sb.append("/"); sb.append("\"><tt>"); sb.append(trimmed); if (childResourceInfo.collection) @@ -1516,8 +1520,8 @@ public class DefaultServlet // Render the page footer sb.append("</table>\r\n"); - sb.append("<HR size=\"1\" noshade>"); - sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>"); + sb.append("<HR size=\"1\" noshade>"); + sb.append("<h3>").append(Settings.getFullVersionInfo()).append("</h3>"); sb.append("</body>\r\n"); sb.append("</html>\r\n"); diff --git a/olatcore/src/main/java/org/olat/core/servlets/SecureWebdavServlet.java b/olatcore/src/main/java/org/olat/core/servlets/SecureWebdavServlet.java index b59acbd42d4903d6c86bc59c287c25e1330247a8..bb5225b61f44c78aa8ffe6a640ade87cc3f1af50 100644 --- a/olatcore/src/main/java/org/olat/core/servlets/SecureWebdavServlet.java +++ b/olatcore/src/main/java/org/olat/core/servlets/SecureWebdavServlet.java @@ -269,12 +269,17 @@ public class SecureWebdavServlet throws ServletException, IOException { boolean success = false; try { - Tracing.setUreq(req); - I18nManager.attachI18nInfoToThread(req); + Tracing.setUreq(req); + I18nManager.attachI18nInfoToThread(req); - String method = req.getMethod(); - String path = getRelativePath(req); + String method = req.getMethod(); + String path = getRelativePath(req); + + System.out.println(method + " " + path); + // OLAT-6294 alsways set encoding to UTF-8, overwritten later when a resource is different + resp.setCharacterEncoding("UTF-8"); + if (debug > 0) { Tracing.logDebug("[" + method + "] " + path, SecureWebdavServlet.class); } @@ -941,6 +946,10 @@ public class SecureWebdavServlet DirContext resources = getResources(req); VFSDirContext vfsContext = (VFSDirContext) resources; String destinationPath = req.getHeader("Destination"); + // First decode URL + destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8"); + // Then normalize to NFC form for comparison + destinationPath = normalize(destinationPath); if (!vfsContext.canWrite(destinationPath)) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return; @@ -1731,8 +1740,10 @@ public class SecureWebdavServlet } } - destinationPath = - RequestUtil.URLDecode(normalize(destinationPath), "UTF8"); + // First decode URL + destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8"); + // Then normalize to NFC form for comparison + destinationPath = normalize(destinationPath); if (debug > 0) Tracing.logDebug("Dest path :" + destinationPath, SecureWebdavServlet.class);