diff --git a/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java b/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java index 6b434b8585e07a01cf491f4d7c59d3584ef5e58d..d302b09cf4e2eeb305f1d4995549a7554479982e 100644 --- a/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java +++ b/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java @@ -354,7 +354,7 @@ public class ICalFileCalendarManager implements CalendarManager, InitializingBea @Override public boolean persistCalendar(Kalendar kalendar) { Calendar calendar = buildCalendar(kalendar); - boolean success = writeCalendarFile(calendar,kalendar.getType(), kalendar.getCalendarID()); + boolean success = writeCalendarFile(calendar, kalendar.getType(), kalendar.getCalendarID()); calendarCache.update(getKeyFor(kalendar.getType(), kalendar.getCalendarID()), kalendar); return success; } @@ -1204,7 +1204,9 @@ public class ICalFileCalendarManager implements CalendarManager, InitializingBea } reloadedCal.removeEvent(kalendarEvent); // remove old event - reloadedCal.addEvent(kalendarEvent); // add changed event + // clone the event to initialize the immutable date used to control moving events + KalendarEvent clonedEvent = getKalendarEvent(getVEvent(kalendarEvent)); + reloadedCal.addEvent(clonedEvent); // add changed event boolean successfullyPersist = persistCalendar(reloadedCal); // inform all controller about calendar change for reload diff --git a/src/main/java/org/olat/core/util/StringHelper.java b/src/main/java/org/olat/core/util/StringHelper.java index 7be94fd659c807a33f444186cbeebc83a70fc644..2cc3cedf3227d7e6f817f2f6aaa250cc5dd9d5a4 100644 --- a/src/main/java/org/olat/core/util/StringHelper.java +++ b/src/main/java/org/olat/core/util/StringHelper.java @@ -35,6 +35,7 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -42,7 +43,6 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -542,67 +542,44 @@ public class StringHelper { } /** - * set of strings to one string comma separated.<br> + * Collection of strings to one string comma separated. This method doesn't do any escaping + * and will never do escaping to be backwards compatible.<br> * e.g. ["a","b","c","s"] -> "a,b,c,s" * @param selection * @return */ - public static String formatAsCSVString(Set<String> entries) { - boolean isFirst = true; - String csvStr = null; - for (Iterator<String> iter = entries.iterator(); iter.hasNext();) { - String group = iter.next(); - if (isFirst) { - csvStr = group; - isFirst = false; - } else { - csvStr += ", " + group; + public static String formatAsCSVString(Collection<String> entries) { + StringBuilder csv = new StringBuilder(256); + for (String group:entries) { + if (csv.length() > 0) { + csv.append(","); } + csv.append(group); } - return csvStr; + return csv.toString(); } /** - * list of strings to one string comma separated.<br> - * e.g. ["a","b","c","s"] -> "a,b,c,s" + * Collection of strings to one string comma separated. The method escaped + * " and ,<br> + * e.g. ["a","b,1","c","s"] -> "a,"b,1",c,s" * @param selection * @return */ - public static String formatAsCSVString(List<String> entries) { - boolean isFirst = true; - String csvStr = null; - for (Iterator<String> iter = entries.iterator(); iter.hasNext();) { - String group = iter.next(); - if (isFirst) { - csvStr = group; - isFirst = false; - } else { - csvStr += ", " + group; + public static String formatAsEscapedCSVString(Collection<String> entries) { + StringBuilder csv = new StringBuilder(256); + for (String entry:entries) { + entry = entry.replace("\"", "\"\""); + if (entry.contains(",")) { + entry = "\"" + entry + "\""; } + + if (csv.length() > 0) { + csv.append(","); + } + csv.append(entry); } - return csvStr; - } - - /** - * list of strings to one string comma separated.<br> - * e.g. ["z","a","b","c","s","a"] -> "a, b, c, s, z" - * No duplicates, alphabetically sorted - * @param selection - * @return - */ - public static String formatAsSortUniqCSVString(List<String> s) { - - Map <String,String>u = new HashMap<String,String>(); - for (Iterator <String> si = s.iterator(); si.hasNext();) { - u.put(si.next().trim(), null); - } - - List <String>rv = new ArrayList<String>(); - rv.addAll(u.keySet()); - rv.remove(""); - Collections.sort(rv); - - return formatAsCSVString (rv); + return csv.toString(); } /** @@ -612,18 +589,17 @@ public class StringHelper { * @param selection * @return */ - public static String formatAsSortUniqCSVString(Set<String> s) { - - Map <String,String>u = new HashMap<String,String>(); - for (Iterator <String> si = s.iterator(); si.hasNext();) { + public static String formatAsSortUniqCSVString(Collection<String> s) { + Map<String,String> u = new HashMap<>(); + for (Iterator<String> si = s.iterator(); si.hasNext();) { u.put(si.next().trim(), null); } - List <String>rv = new ArrayList<String>(); + List <String>rv = new ArrayList<>(u.size()); rv.addAll(u.keySet()); rv.remove(""); Collections.sort(rv); - return formatAsCSVString (rv); + return formatAsCSVString(rv); } } \ No newline at end of file diff --git a/src/main/java/org/olat/course/statistic/export/LogLineConverter.java b/src/main/java/org/olat/course/statistic/export/LogLineConverter.java index 9a5ca06e7d6393084438fdadce4997bcad723dc3..bba51ebe2608baecbf9394934e8d52ec106f5603 100644 --- a/src/main/java/org/olat/course/statistic/export/LogLineConverter.java +++ b/src/main/java/org/olat/course/statistic/export/LogLineConverter.java @@ -125,7 +125,7 @@ public class LogLineConverter { PropertyDescriptor pd = it.next(); propertyNames.add(pd.getName()); } - return StringHelper.formatAsCSVString(propertyNames); + return StringHelper.formatAsEscapedCSVString(propertyNames); } /** @@ -160,7 +160,7 @@ public class LogLineConverter { loggingObjectList.add(strValue); } - return StringHelper.formatAsCSVString(loggingObjectList); + return StringHelper.formatAsEscapedCSVString(loggingObjectList); } /** diff --git a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java index dbd5d5219fe71b5917ac9e4909fd5f025950e8bf..c778a89db8b41385740b21e43b322a92b411adbd 100644 --- a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java +++ b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java @@ -1623,7 +1623,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD List<Identity> ownerList = getMembers(groups, GroupRoles.participant.name()); repoTutorList.retainAll(ownerList); if(!dryRun) { - repositoryManager.removeTutors(ureqIdentity, repoTutorList, entry); + repositoryManager.removeTutors(ureqIdentity, repoTutorList, entry, new MailPackage(false)); } count += repoTutorList.size(); } diff --git a/src/main/java/org/olat/repository/RepositoryMailing.java b/src/main/java/org/olat/repository/RepositoryMailing.java index 85ef024fb528192c3dced73a70b90812b6877195..c8d9edfa14745703501f989238de84e4ce9584d0 100644 --- a/src/main/java/org/olat/repository/RepositoryMailing.java +++ b/src/main/java/org/olat/repository/RepositoryMailing.java @@ -106,7 +106,7 @@ public class RepositoryMailing { * @param actor * @return the generated MailTemplate */ - private static MailTemplate createRemoveParticipantMailTemplate(RepositoryEntry re, Identity actor) { + private static MailTemplate createRemoveMailTemplate(RepositoryEntry re, Identity actor) { String subjectKey = "notification.mail.removed.subject"; String bodyKey = "notification.mail.removed.body"; return createMailTemplate(re, actor, subjectKey, bodyKey); @@ -118,12 +118,14 @@ public class RepositoryMailing { switch(type) { case addParticipant: return createAddParticipantMailTemplate(re, ureqIdentity); - case removeParticipant: - return createRemoveParticipantMailTemplate(re, ureqIdentity); case addTutor: return createAddTutorMailTemplate(re, ureqIdentity); case addOwner: return createAddOwnerMailTemplate(re, ureqIdentity); + case removeParticipant: + case removeTutor: + case removeOwner: + return createRemoveMailTemplate(re, ureqIdentity); } return null; } @@ -176,7 +178,9 @@ public class RepositoryMailing { addParticipant, removeParticipant, addTutor, - addOwner + removeTutor, + addOwner, + removeOwner } private static MailTemplate createMailTemplate(RepositoryEntry re, Identity actor, String subjectKey, String bodyKey) { diff --git a/src/main/java/org/olat/repository/RepositoryManager.java b/src/main/java/org/olat/repository/RepositoryManager.java index 2e1f16c9d85b4a6608bbe80ab71d9f79433f23be..d66e275bc245e03999e66a32a13e6125204417fc 100644 --- a/src/main/java/org/olat/repository/RepositoryManager.java +++ b/src/main/java/org/olat/repository/RepositoryManager.java @@ -1561,11 +1561,11 @@ public class RepositoryManager { * @param re * @param logger */ - public void removeOwners(Identity ureqIdentity, List<Identity> removeIdentities, RepositoryEntry re){ + public void removeOwners(Identity ureqIdentity, List<Identity> removeIdentities, RepositoryEntry re, MailPackage mailing) { List<RepositoryEntryMembershipModifiedEvent> deferredEvents = new ArrayList<>(); for (Identity identity : removeIdentities) { - removeOwner(ureqIdentity, identity, re); + removeOwner(ureqIdentity, identity, re, mailing); deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(identity, re)); } @@ -1581,9 +1581,10 @@ public class RepositoryManager { } } - private void removeOwner(Identity ureqIdentity, Identity identity, RepositoryEntry re) { + private void removeOwner(Identity ureqIdentity, Identity identity, RepositoryEntry re, MailPackage mailing) { repositoryEntryRelationDao.removeRole(identity, re, GroupRoles.owner.name()); + RepositoryMailing.sendEmail(ureqIdentity, identity, re, RepositoryMailing.Type.removeTutor, mailing); ActionType actionType = ThreadLocalUserActivityLogger.getStickyActionType(); ThreadLocalUserActivityLogger.setStickyActionType(ActionType.admin); @@ -1687,19 +1688,21 @@ public class RepositoryManager { * @param re * @param logger */ - public void removeTutors(Identity ureqIdentity, List<Identity> removeIdentities, RepositoryEntry re) { + public void removeTutors(Identity ureqIdentity, List<Identity> removeIdentities, RepositoryEntry re, MailPackage mailing) { List<RepositoryEntryMembershipModifiedEvent> deferredEvents = new ArrayList<>(); for (Identity identity : removeIdentities) { - removeTutor(ureqIdentity, identity, re); + removeTutor(ureqIdentity, identity, re, mailing); deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(identity, re)); } dbInstance.commit(); sendDeferredEvents(deferredEvents, re); } - private void removeTutor(Identity ureqIdentity, Identity identity, RepositoryEntry re) { + private void removeTutor(Identity ureqIdentity, Identity identity, RepositoryEntry re, MailPackage mailing) { repositoryEntryRelationDao.removeRole(identity, re, GroupRoles.coach.name()); + RepositoryMailing.sendEmail(ureqIdentity, identity, re, RepositoryMailing.Type.removeTutor, mailing); + ActionType actionType = ThreadLocalUserActivityLogger.getStickyActionType(); ThreadLocalUserActivityLogger.setStickyActionType(ActionType.admin); try{ @@ -2325,7 +2328,7 @@ public class RepositoryManager { if(changes.getRepoOwner().booleanValue()) { addOwners(ureqIdentity, new IdentitiesAddEvent(changes.getMember()), re, mailing); } else { - removeOwner(ureqIdentity, changes.getMember(), re); + removeOwner(ureqIdentity, changes.getMember(), re, mailing); deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(changes.getMember(), re)); } } @@ -2334,7 +2337,7 @@ public class RepositoryManager { if(changes.getRepoTutor().booleanValue()) { addTutors(ureqIdentity, ureqRoles, new IdentitiesAddEvent(changes.getMember()), re, mailing); } else { - removeTutor(ureqIdentity, changes.getMember(), re); + removeTutor(ureqIdentity, changes.getMember(), re, mailing); deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(changes.getMember(), re)); } } diff --git a/src/main/java/org/olat/restapi/repository/RepositoryEntryResource.java b/src/main/java/org/olat/restapi/repository/RepositoryEntryResource.java index 83ecb074a4446e061a7ad1a6c4e600dfbc725aac..c63f92bda5bcd6b90062ffe1fc6d2c9436c88d84 100644 --- a/src/main/java/org/olat/restapi/repository/RepositoryEntryResource.java +++ b/src/main/java/org/olat/restapi/repository/RepositoryEntryResource.java @@ -286,7 +286,7 @@ public class RepositoryEntryResource { } final UserRequest ureq = RestSecurityHelper.getUserRequest(request); - repositoryManager.removeOwners(ureq.getIdentity(), Collections.singletonList(identityToRemove), repoEntry); + repositoryManager.removeOwners(ureq.getIdentity(), Collections.singletonList(identityToRemove), repoEntry, new MailPackage(false)); return Response.ok().build(); } catch (Exception e) { log.error("Trying to remove an owner to a repository entry", e); @@ -406,7 +406,7 @@ public class RepositoryEntryResource { } final UserRequest ureq = RestSecurityHelper.getUserRequest(request); - repositoryManager.removeTutors(ureq.getIdentity(), Collections.singletonList(identityToRemove), repoEntry); + repositoryManager.removeTutors(ureq.getIdentity(), Collections.singletonList(identityToRemove), repoEntry, new MailPackage(false)); return Response.ok().build(); } catch (Exception e) { log.error("Trying to remove a coach from a repository entry", e); diff --git a/src/main/java/org/olat/restapi/repository/course/CourseWebService.java b/src/main/java/org/olat/restapi/repository/course/CourseWebService.java index 92e9519186260c115805c6d0dc34448ad212ce06..ba0c26fdf5c04a88eaa159afb45a8b502ee57c39 100644 --- a/src/main/java/org/olat/restapi/repository/course/CourseWebService.java +++ b/src/main/java/org/olat/restapi/repository/course/CourseWebService.java @@ -788,7 +788,7 @@ public class CourseWebService { RepositoryManager rm = RepositoryManager.getInstance(); RepositoryEntry repositoryEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); List<Identity> authors = Collections.singletonList(author); - rm.removeOwners(identity, authors, repositoryEntry); + rm.removeOwners(identity, authors, repositoryEntry, new MailPackage(false)); return Response.ok().build(); } diff --git a/src/test/java/org/olat/core/util/StringHelperTest.java b/src/test/java/org/olat/core/util/StringHelperTest.java index f554c6b3cabbfe8e28d434faf315fec9044afe9b..5f71eddda9668371c82f452eec410ec122be2f03 100644 --- a/src/test/java/org/olat/core/util/StringHelperTest.java +++ b/src/test/java/org/olat/core/util/StringHelperTest.java @@ -22,6 +22,9 @@ package org.olat.core.util; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; + import org.junit.Assert; import org.junit.Test; @@ -176,4 +179,25 @@ public class StringHelperTest { Assert.assertTrue(StringHelper.isHtml("<ul><li>Hello<li>world</ul>")); Assert.assertTrue(StringHelper.isHtml("Hello<br>world")); } + + + @Test + public void formatAsCSVString() { + List<String> entries = new ArrayList<>(); + entries.add("Hell\"o\""); + entries.add("Test,dru,"); + entries.add("Final"); + String csv = StringHelper.formatAsCSVString(entries); + Assert.assertEquals("Hell\"o\",Test,dru,,Final", csv); + } + + @Test + public void formatAsEscapedCSVString() { + List<String> entries = new ArrayList<>(); + entries.add("Hell\"o\""); + entries.add("Test,dru,"); + entries.add("Final"); + String csv = StringHelper.formatAsEscapedCSVString(entries); + Assert.assertEquals("Hell\"\"o\"\",\"Test,dru,\",Final", csv); + } } diff --git a/src/test/java/org/olat/repository/manager/RepositoryEntryMembershipProcessorTest.java b/src/test/java/org/olat/repository/manager/RepositoryEntryMembershipProcessorTest.java index 642567fc9912283ea20c2e62be68067ec50ab98b..d868776ab0abbb6a878653a66cdb514d68976b7c 100644 --- a/src/test/java/org/olat/repository/manager/RepositoryEntryMembershipProcessorTest.java +++ b/src/test/java/org/olat/repository/manager/RepositoryEntryMembershipProcessorTest.java @@ -141,7 +141,7 @@ public class RepositoryEntryMembershipProcessorTest extends OlatTestCase { List<Identity> removeIdentities = new ArrayList<>(2); removeIdentities.add(member); removeIdentities.add(coach); - repositoryManager.removeTutors(owner, removeIdentities, re); + repositoryManager.removeTutors(owner, removeIdentities, re, new MailPackage(false)); //wait for the remove of subscription waitForCondition(new CheckUnsubscription(member, context, dbInstance, notificationManager), 5000);