From 4ad93727b7037618b8f40ca7e37d657d800cf7e2 Mon Sep 17 00:00:00 2001 From: Juan David Massanet Puentes <94039846+JuanDavid102@users.noreply.github.com> Date: Thu, 14 Dec 2023 18:14:23 +0100 Subject: [PATCH] S2U-29 5.1.3.3 Private Messages: Request a read receipt feature - master (#12193) --- msgcntr/messageforums-api/pom.xml | 4 + .../messagecenter/bundle/Messages.properties | 8 ++ .../bundle/Messages_ca.properties | 7 ++ .../bundle/Messages_es.properties | 7 ++ .../bundle/Messages_eu.properties | 7 ++ .../messageforums/DiscussionForumService.java | 2 + .../PrivateMessageRecipient.java | 4 + ...PrivateMessageUserNotificationHandler.java | 107 ++++++++++++++++++ .../ui/PrivateMessageManager.java | 7 +- .../messageforums/PrivateMessagesTool.java | 19 ++-- .../src/webapp/jsp/compose.jsp | 17 +++ .../src/webapp/jsp/pvtMsgForward.jsp | 17 +++ .../src/webapp/jsp/pvtMsgReply.jsp | 17 +++ .../src/webapp/jsp/pvtMsgReplyAll.jsp | 17 +++ .../PrivateMessageSchedulerServiceImpl.java | 2 +- .../ui/PrivateMessageManagerImpl.java | 81 +++++++++++-- .../dao/hibernate/MessageImpl.hbm.xml | 3 + .../PrivateMessageRecipientImpl.java | 12 ++ .../src/sql/mysql/mfr_conversion_28x-30x.sql | 2 + .../main/bundle/sui-notifications.properties | 2 + .../js/sui-notifications/sui-notifications.js | 9 ++ 21 files changed, 329 insertions(+), 22 deletions(-) create mode 100644 msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java diff --git a/msgcntr/messageforums-api/pom.xml b/msgcntr/messageforums-api/pom.xml index 6bcb4e26475c..deff02be1701 100644 --- a/msgcntr/messageforums-api/pom.xml +++ b/msgcntr/messageforums-api/pom.xml @@ -38,6 +38,10 @@ org.hibernate hibernate-core + + org.springframework + spring-context + diff --git a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties index bf2288b61985..ffc0fd8e33ec 100644 --- a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties +++ b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties @@ -987,3 +987,11 @@ cdfm_faq_forum_title=FAQ Forum cdfm_faq_forum_description=Forum for frequently asked questions cdfm_faq_topic_title=FAQ Topic cdfm_faq_topic_description=Topic for frequently asked questions + +#S2U-29 +pvt_read_receipt_label=Read Receipt +pvt_read_receipt_text=Receive a notification when the message is opened by the recipient + +pvt_read_receipt_email={0}({1}) has read the message with the subject: `{2}` on {3}. \n\nTo see this message, click in this url and see the message of the site: {4} +pvt_email_href={1}<\a> +pvt_read_receipt_email_subject={0} has been read diff --git a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_ca.properties b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_ca.properties index 1ff500e1f6ba..fa266fb22c25 100644 --- a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_ca.properties +++ b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_ca.properties @@ -981,3 +981,10 @@ cdfm_faq_forum_title=F\u00f2rum Preguntes Freq\u00fcents cdfm_faq_forum_description=F\u00f2rum per Preguntes Freq\u00fcents cdfm_faq_topic_title=Tema Preguntes Freq\u00fcents cdfm_faq_topic_description=Tema per a Preguntes Freq\u00fcents + +#S2U-29 +pvt_read_receipt_label=Acusament de rebut +pvt_read_receipt_text=Rebre una notificaci\u00f3 quan el missatge sigui obert pel destinatari + +pvt_read_receipt_email={0}({1}) ha llegit el missatge amb l`assumpte `{2}` el {3}. \n\nPer veure aquest missatge, feu clic en aquest URL i vegeu el missatge del lloc: {4} +pvt_read_receipt_email_subject={0} ha estat llegit diff --git a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_es.properties b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_es.properties index bddbd60c8c53..66153fb56f22 100644 --- a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_es.properties +++ b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_es.properties @@ -966,3 +966,10 @@ cdfm_faq_forum_title=Foro preguntas frecuentes cdfm_faq_forum_description=Foro para preguntas frecuentes cdfm_faq_topic_title=Preguntas frecuentes cdfm_faq_topic_description=Tema para las preguntas m\u00e1s frecuentes + +#S2U-29 +pvt_read_receipt_label=Acuse de recibo +pvt_read_receipt_text=Recibir una notificaci\u00f3n cuando el mensaje sea abierto por el destinatario + +pvt_read_receipt_email={0}({1}) ha le\u00eddo el mensaje con asunto `{2}` el {3}. \n\nPara ver el mensaje que envi\u00f3, haga clic en este enlace y acceda al mensaje del sitio: {4} +pvt_read_receipt_email_subject={0} ha sido le\u00eddo diff --git a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_eu.properties b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_eu.properties index fcd8c622268a..b0fcbcd54f05 100644 --- a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_eu.properties +++ b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_eu.properties @@ -983,3 +983,10 @@ cdfm_faq_forum_title=FAQ foroa cdfm_faq_forum_description=Galdera ohikoentzako foroa cdfm_faq_topic_title=FAQ gaia cdfm_faq_topic_description=Galdera ohikoentzako gaia + +#S2U-29 +pvt_read_receipt_label=Jaso izanaren akta +pvt_read_receipt_text=Jaso jakinarazpen bat hartzaileak mezua irekitzen duenean + +pvt_read_receipt_email={0}({1})-ek `{2}` mezua irakurri du {3}an. \n\nMezu hau ikusteko, egin klik url honetan eta ikusi gunearen mezua: {4} +pvt_read_receipt_email_subject={0} irakurri da diff --git a/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/DiscussionForumService.java b/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/DiscussionForumService.java index 02c3432c7ef5..ed914cee6e8d 100644 --- a/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/DiscussionForumService.java +++ b/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/DiscussionForumService.java @@ -49,6 +49,8 @@ public interface DiscussionForumService extends EntityProducer public static final String EVENT_MESSAGES_RESPONSE = "messages.reply"; public static final String EVENT_MESSAGES_FORWARD = "messages.forward"; + + public static final String EVENT_MESSAGES_READ_RECEIPT = "message.read.receipt"; // Events for the (Discussion) Forums tool public static final String EVENT_FORUMS_ADD = "forums.new"; diff --git a/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageRecipient.java b/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageRecipient.java index 319486ce856a..e3c63c8dc8fb 100644 --- a/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageRecipient.java +++ b/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageRecipient.java @@ -47,5 +47,9 @@ public interface PrivateMessageRecipient public void setReplied(Boolean replied); + public Boolean getReadReceipt(); + + public void setReadReceipt(Boolean readReceipt); + } diff --git a/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java b/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java new file mode 100644 index 000000000000..991c51ca620b --- /dev/null +++ b/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2003-2023 The Apereo Foundation + * + * Licensed under the Educational Community License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://opensource.org/licenses/ecl2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sakaiproject.api.app.messageforums; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +import javax.annotation.Resource; + +import org.sakaiproject.api.app.messageforums.PrivateMessage; +import org.sakaiproject.api.app.messageforums.ui.PrivateMessageManager; +import org.sakaiproject.component.api.ServerConfigurationService; +import org.sakaiproject.event.api.Event; +import org.sakaiproject.exception.IdUnusedException; +import org.sakaiproject.messaging.api.AbstractUserNotificationHandler; +import org.sakaiproject.messaging.api.UserNotificationData; +import org.sakaiproject.site.api.Site; +import org.sakaiproject.site.api.SiteService; + +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class PrivateMessageUserNotificationHandler extends AbstractUserNotificationHandler{ + + @Resource + private PrivateMessageManager privateMessageManager; + + @Resource + private SiteService siteService; + + @Resource + private ServerConfigurationService serverConfigurationService; + + @Override + public List getHandledEvents() { + return Arrays.asList(DiscussionForumService.EVENT_MESSAGES_READ_RECEIPT); + } + + @Override + public Optional> handleEvent(Event e) { + + String from = e.getUserId(); + + String ref = e.getResource(); + String[] pathParts = ref.split("/"); + + String siteId = pathParts[3]; + String pvtMessageId = pathParts[pathParts.length - 2]; + + try { + PrivateMessage pvtMessage = privateMessageManager.getPrivateMessageByDecryptedId(pvtMessageId); + switch (e.getEvent()) { + case DiscussionForumService.EVENT_MESSAGES_READ_RECEIPT: + return Optional.of(handleAdd(from, siteId, pvtMessage.getCreatedBy(), pvtMessage)); + default: + return Optional.empty(); + } + } catch (Exception ex) { + log.error("Failed to find the privateMessage: " + pvtMessageId, ex); + } + + return Optional.empty(); + } + + private List handleAdd(String from, String siteId, String userId, PrivateMessage pvtMessage) { + + List notificationEvents = new ArrayList<>(); + + Date openTime = pvtMessage.getCreated(); + if (openTime == null || openTime.before(new Date()) && !pvtMessage.getDraft()) { + try { + Site site = siteService.getSite(siteId); + String title = pvtMessage.getTitle(); + if (!from.equals(userId)) { + String toolId = site.getToolForCommonId(DiscussionForumService.MESSAGES_TOOL_ID).getId(); + String url = serverConfigurationService.getPortalUrl() + "/site/" + siteId + + "/tool/" + toolId + "/privateMsg/pvtMsgDirectAccess?current_msg_detail=" + pvtMessage.getId(); + notificationEvents.add(new UserNotificationData(from, userId, siteId, title, url)); + } + } catch (IdUnusedException idEx) { + log.error("Failed to find the site: " + siteId, idEx); + } + + } + + return notificationEvents; + } +} diff --git a/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/ui/PrivateMessageManager.java b/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/ui/PrivateMessageManager.java index 1ece63815b31..f5929222f14c 100644 --- a/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/ui/PrivateMessageManager.java +++ b/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/ui/PrivateMessageManager.java @@ -123,9 +123,10 @@ public List getMessagesByTypeByContext(final String typeUuid, final String conte * @param message * @param recipients * @param asEmail + * @param readReceipt */ - public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail); - public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, List draftRecipients, List draftBccRecipients); + public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, boolean readReceipt); + public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, List draftRecipients, List draftBccRecipients, boolean readReceipt); /** @@ -263,5 +264,7 @@ public List getMessagesByTypeByContext(final String typeUuid, final String conte boolean isAllowToFieldMyGroups(User user, String contextId); boolean isAllowToFieldMyGroupMembers(User user, String contextId); + + public PrivateMessage getPrivateMessageByDecryptedId(String id) throws MessagingException; } diff --git a/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/PrivateMessagesTool.java b/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/PrivateMessagesTool.java index 7c123502cfe2..c98d6f863ed5 100644 --- a/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/PrivateMessagesTool.java +++ b/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/PrivateMessagesTool.java @@ -335,6 +335,8 @@ public class PrivateMessagesTool { private String composeSendAsPvtMsg=SET_AS_YES; // currently set as Default as change by user is allowed @Setter private boolean booleanEmailOut = ServerConfigurationService.getBoolean("mc.messages.ccEmailDefault", false); + @Getter @Setter + private boolean booleanReadReceipt; @Getter private String composeSubject; @Getter @@ -1719,6 +1721,7 @@ public void resetComposeContents() this.setBooleanSchedulerSend(false); this.setOpenDate(""); this.setSchedulerSendDateString(""); + this.setBooleanReadReceipt(false); } public String processPvtMsgPreview(){ @@ -1814,7 +1817,7 @@ public String processPvtMsgSend() { return processPvtMsgComposeCancel(); } else { PrivateMessageSchedulerService.removeScheduledReminder(pMsg.getId()); - prtMsgManager.sendPrivateMessage(pMsg, recipients, isSendEmail); + prtMsgManager.sendPrivateMessage(pMsg, recipients, isSendEmail, booleanReadReceipt); } // if you are sending a reply Message replying = pMsg.getInReplyTo(); @@ -1948,7 +1951,7 @@ public String processPvtMsgSaveDraft() { List draftRecipients = drDelegate.getDraftRecipients(getSelectedComposeToList(), courseMemberMap); List draftBccRecipients = drDelegate.getDraftRecipients(getSelectedComposeBccList(), courseMemberMap); - prtMsgManager.sendPrivateMessage(dMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients); + prtMsgManager.sendPrivateMessage(dMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); //reset contents resetComposeContents(); @@ -2441,9 +2444,9 @@ private String processPvtMsgReplySentAction(PrivateMessage rrepMsg){ List draftRecipients = drDelegate.getDraftRecipients(getSelectedComposeToList(), courseMemberMap); List draftBccRecipients = drDelegate.getDraftRecipients(getSelectedComposeBccList(), courseMemberMap); - prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients); + prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); } else { - prtMsgManager.sendPrivateMessage(rrepMsg, recipients, isSendEmail()); + prtMsgManager.sendPrivateMessage(rrepMsg, recipients, isSendEmail(), booleanReadReceipt); } if(!rrepMsg.getDraft()){ @@ -2829,9 +2832,9 @@ private void processPvtMsgForwardSendHelper(PrivateMessage rrepMsg){ List draftRecipients = drDelegate.getDraftRecipients(getSelectedComposeToList(), courseMemberMap); List draftBccRecipients = drDelegate.getDraftRecipients(getSelectedComposeBccList(), courseMemberMap); - prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients); + prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); } else { - prtMsgManager.sendPrivateMessage(rrepMsg, recipients, isSendEmail()); + prtMsgManager.sendPrivateMessage(rrepMsg, recipients, isSendEmail(), booleanReadReceipt); } if(!rrepMsg.getDraft()){ @@ -3121,9 +3124,9 @@ private PrivateMessage processPvtMsgReplyAllSendHelper(boolean preview, Boolean List draftRecipients = drDelegate.getDraftRecipients(getSelectedComposeToList(), courseMemberMap); List draftBccRecipients = drDelegate.getDraftRecipients(getSelectedComposeBccList(), courseMemberMap); - prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients); + prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); } else { - prtMsgManager.sendPrivateMessage(rrepMsg, returnSet, isSendEmail()); + prtMsgManager.sendPrivateMessage(rrepMsg, returnSet, isSendEmail(), booleanReadReceipt); } if(!rrepMsg.getDraft()){ diff --git a/msgcntr/messageforums-app/src/webapp/jsp/compose.jsp b/msgcntr/messageforums-app/src/webapp/jsp/compose.jsp index c5c3b1b4c6d6..4ce90d92ac57 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/compose.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/compose.jsp @@ -209,6 +209,23 @@
+ + + + + +
+
+ + + + + + +
+
+
+
diff --git a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgForward.jsp b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgForward.jsp index 9fee405be936..542fea65e39c 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgForward.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgForward.jsp @@ -210,6 +210,23 @@
+
+ + + + + +
+
+ + + + + + +
+
+
diff --git a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReply.jsp b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReply.jsp index ba0858d0797b..1b5942f5d943 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReply.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReply.jsp @@ -226,6 +226,23 @@
+
+ + + + + +
+
+ + + + + + +
+
+
diff --git a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReplyAll.jsp b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReplyAll.jsp index 1a11cacff455..6351c7a8bca7 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReplyAll.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReplyAll.jsp @@ -241,6 +241,23 @@
+
+ + + + + +
+
+ + + + + + +
+
+
diff --git a/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/scheduler/PrivateMessageSchedulerServiceImpl.java b/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/scheduler/PrivateMessageSchedulerServiceImpl.java index e99710e9ec5b..84f9a7323734 100644 --- a/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/scheduler/PrivateMessageSchedulerServiceImpl.java +++ b/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/scheduler/PrivateMessageSchedulerServiceImpl.java @@ -131,7 +131,7 @@ public void execute(String opaqueContext) { pvtMsg.setScheduler(false); pvtMsg.setDraft(false); - prtMsgManager.sendPrivateMessage(pvtMsg, recipients, false); + prtMsgManager.sendPrivateMessage(pvtMsg, recipients, false, false); // if you are sending a reply Message replying = pvtMsg.getInReplyTo(); diff --git a/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/ui/PrivateMessageManagerImpl.java b/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/ui/PrivateMessageManagerImpl.java index 7a7d0a810145..3eddbd05cfc4 100644 --- a/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/ui/PrivateMessageManagerImpl.java +++ b/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/ui/PrivateMessageManagerImpl.java @@ -21,6 +21,7 @@ package org.sakaiproject.component.app.messageforums.ui; import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -111,6 +112,9 @@ import org.sakaiproject.util.api.FormattedText; import org.sakaiproject.util.ResourceLoader; +import java.text.DateFormat; +import java.text.SimpleDateFormat; + @Slf4j public class PrivateMessageManagerImpl extends HibernateDaoSupport implements PrivateMessageManager { @@ -1145,7 +1149,7 @@ private Message saveMessage(Message message, boolean isMailArchive, String conte return pmessage; } - private boolean getForwardingEnabled(Map recipients, Map pfMap, String currentUserAsString, String contextId, List recipientList, List fAddresses, boolean asEmail) throws MessagingException{ + private boolean getForwardingEnabled(Map recipients, Map pfMap, String currentUserAsString, String contextId, List recipientList, List fAddresses, boolean asEmail, boolean readReceipt) throws MessagingException{ boolean forwardingEnabled = false; //this only needs to be done if the message is not being sent int submitterEmailReceiptPref; @@ -1199,6 +1203,7 @@ else if (pf == null && oldPf != null && (oldPf.getAutoForward()==PrivateForumImp PrivateMessageRecipientImpl receiver = new PrivateMessageRecipientImpl( userId, typeManager.getReceivedPrivateMessageType(), contextId, isRecipientCurrentUser, bcc); + receiver.setReadReceipt(readReceipt); recipientList.add(receiver); } return forwardingEnabled; @@ -1224,14 +1229,15 @@ private String getSystemAndReplyEmail(String defaultEmail, User currentUser, Mes } /** - * @see org.sakaiproject.api.app.messageforums.ui.PrivateMessageManager#sendPrivateMessage(org.sakaiproject.api.app.messageforums.PrivateMessage, java.util.Set, boolean) + * @see org.sakaiproject.api.app.messageforums.ui.PrivateMessageManager#sendPrivateMessage(org.sakaiproject.api.app.messageforums.PrivateMessage, java.util.Set, boolean, boolean) */ - public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail) { - sendPrivateMessage(message, recipients, asEmail, Collections.emptyList(), Collections.emptyList()); + public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, boolean readReceipt) { + sendPrivateMessage(message, recipients, asEmail, Collections.emptyList(), Collections.emptyList(), readReceipt); } @Override - public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, List draftRecipients, List draftBccRecipients) { + public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, + List draftRecipients, List draftBccRecipients, boolean readReceipt) { try { @@ -1267,7 +1273,7 @@ public void sendPrivateMessage(PrivateMessage message, Map recipi if (message.getDraft()) { PrivateMessageRecipient receiver = new PrivateMessageRecipientImpl(currentUserAsString, typeManager.getDraftPrivateMessageType(), contextId, Boolean.TRUE, false); - + receiver.setReadReceipt(readReceipt); recipientList.add(receiver); message.setRecipients(recipientList); Message savedMessage = saveMessage(message, isMailArchive, contextId, currentUserAsString); @@ -1312,9 +1318,9 @@ public void sendPrivateMessage(PrivateMessage message, Map recipi pfMap.put(pf1.getOwner(), pf1); } - List fAddresses = new ArrayList(); - boolean forwardingEnabled = getForwardingEnabled(recipients, pfMap, currentUserAsString, contextId, recipientList, fAddresses, asEmail); - //this only needs to be done if the message is not being sent + List fAddresses = new ArrayList(); + boolean forwardingEnabled = getForwardingEnabled(recipients, pfMap, currentUserAsString, contextId, recipientList, fAddresses, asEmail, readReceipt); + //this only needs to be done if the message is not being sent /** add sender as a saved recipient */ PrivateMessageRecipientImpl sender = new PrivateMessageRecipientImpl( @@ -1588,7 +1594,13 @@ public void markMessageAsReadForUser(final PrivateMessage message, final String if (((PrivateMessageRecipientImpl) pvtMessage.getRecipients().get(i)).getUserId().equals(searchRecipient.getUserId())){ recordIndex = i; if (! ((PrivateMessageRecipientImpl) recipientList.get(recordIndex)).getRead()) { - ((PrivateMessageRecipientImpl) recipientList.get(recordIndex)).setRead(Boolean.TRUE); + if (((PrivateMessageRecipientImpl) recipientList.get(recordIndex)).getReadReceipt() != null && ((PrivateMessageRecipientImpl) recipientList.get(recordIndex)).getReadReceipt()) { + + this.sendConfirmationEmail(pvtMessage, (PrivateMessageRecipientImpl) recipientList.get(recordIndex), contextId); + ((PrivateMessageRecipientImpl) recipientList.get(recordIndex)).setReadReceipt(false); + + } + ((PrivateMessageRecipientImpl) recipientList.get(recordIndex)).setRead(Boolean.TRUE); } } } @@ -1599,6 +1611,47 @@ public void markMessageAsReadForUser(final PrivateMessage message, final String } } + public void sendConfirmationEmail(PrivateMessage pvtMessage, PrivateMessageRecipientImpl pvtRecipient, String contextId){ + String defaultEmail = serverConfigurationService.getString("setup.request","postmaster@" + serverConfigurationService.getServerName()); + String currentUserAsString = currentUserAsString(pvtMessage, false); + + String systemEmail = ""; + try { + User currentUser = currentUser(pvtMessage, false); + List replyEmail = new ArrayList<>(); + systemEmail = getSystemAndReplyEmail(defaultEmail, currentUser, pvtMessage, replyEmail, contextId); + } catch (MessagingException e) { + log.warn("PrivateMessageManagerImpl.sendConfirmationEmail: exception: " + e.getMessage(), e); + } + + List additionalHeaders = new ArrayList(1); + additionalHeaders.add("Content-Type: text/html; charset=utf-8"); + additionalHeaders.add("From: " + systemEmail); + additionalHeaders.add("Subject: " + rb.getFormattedMessage("pvt_read_receipt_email_subject", pvtMessage.getTitle())); + + User user = null; + User messageCreator = null; + try { + user = userDirectoryService.getUser(pvtRecipient.getUserId()); + messageCreator = userDirectoryService.getUser(pvtMessage.getCreatedBy()); + } catch (UserNotDefinedException e) { } + + SimpleDateFormat formatter_date = new SimpleDateFormat(rb.getString("date_format"), new ResourceLoader().getLocale()); + Site site = null; + try { + site = siteService.getSite(contextId); + } catch (IdUnusedException e) { + log.error(e.getMessage(), e); + } + String messageUrl = serverConfigurationService.getPortalUrl() + "/site/" + contextId + "/tool/" + + site.getToolForCommonId(DiscussionForumService.MESSAGES_TOOL_ID).getId() + + "/privateMsg/pvtMsgDirectAccess?current_msg_detail=" + pvtMessage.getId(); + + String bodyString = rb.getFormattedMessage("pvt_read_receipt_email", user.getDisplayName(), user.getEid(), pvtMessage.getTitle(), formatter_date.format(new Date()), rb.getFormattedMessage("pvt_email_href", messageUrl, site.getTitle())); + + emailService.sendToUser(messageCreator, additionalHeaders, bodyString); + eventTrackingService.post(eventTrackingService.newEvent(DiscussionForumService.EVENT_MESSAGES_READ_RECEIPT, getEventMessage(pvtMessage, DiscussionForumService.MESSAGES_TOOL_ID, pvtMessage.getCreatedBy(), contextId), false)); + } /** * @see org.sakaiproject.api.app.messageforums.ui.PrivateMessageManager#markMessageAsReadForUser(org.sakaiproject.api.app.messageforums.PrivateMessage) @@ -2248,6 +2301,12 @@ public PrivateMessage getPrivateMessage(final String id) throws MessagingExcepti return currentMessage; } + public PrivateMessage getPrivateMessageByDecryptedId(String id) throws MessagingException { + PrivateMessage currentMessage = (PrivateMessage) messageManager.getMessageByIdWithAttachments(Long.parseLong(id)); + getHibernateTemplate().initialize(currentMessage.getRecipients()); + return currentMessage; + } + private PrivateMessage createResponseMessage(PrivateMessage currentMessage, MimeMessage msg, String from) throws MessagingException { PrivateMessage rrepMsg = messageManager.createPrivateMessage() ; @@ -2406,7 +2465,7 @@ public void processPvtMsgReplySentAction(PrivateMessage currentMessage, PrivateM if (rrepMsg != null) { Map recipients = getRecipients(rrepMsg.getRecipients()); - sendPrivateMessage(rrepMsg, recipients, false); + sendPrivateMessage(rrepMsg, recipients, false, false); if (!rrepMsg.getDraft()) { markMessageAsRepliedForUser(currentMessage, rrepMsg.getAuthorId()); diff --git a/msgcntr/messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageImpl.hbm.xml b/msgcntr/messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageImpl.hbm.xml index 2f9e9f46fca5..fb332c33563f 100644 --- a/msgcntr/messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageImpl.hbm.xml +++ b/msgcntr/messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageImpl.hbm.xml @@ -122,6 +122,9 @@ + + + diff --git a/msgcntr/messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/PrivateMessageRecipientImpl.java b/msgcntr/messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/PrivateMessageRecipientImpl.java index 45f2fa9a3a14..da85a644e081 100644 --- a/msgcntr/messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/PrivateMessageRecipientImpl.java +++ b/msgcntr/messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/PrivateMessageRecipientImpl.java @@ -31,6 +31,7 @@ public class PrivateMessageRecipientImpl implements PrivateMessageRecipient{ private Boolean read; private Boolean bcc; private Boolean replied; + private Boolean readReceipt; /** * default constructor @@ -51,6 +52,7 @@ public PrivateMessageRecipientImpl(String userId, String typeUuid, String contex this.read = read; this.bcc = bcc; this.replied = false; + this.readReceipt = true; } /** @@ -120,6 +122,16 @@ public void setReplied(Boolean replied) this.replied = replied; } + public Boolean getReadReceipt() + { + return readReceipt; + } + + public void setReadReceipt(Boolean readReceipt) + { + this.readReceipt = readReceipt; + } + /** * @see java.lang.Object#equals(java.lang.Object) */ diff --git a/msgcntr/messageforums-hbm/src/sql/mysql/mfr_conversion_28x-30x.sql b/msgcntr/messageforums-hbm/src/sql/mysql/mfr_conversion_28x-30x.sql index d9d9dc3b0519..72bfb8dcbf73 100644 --- a/msgcntr/messageforums-hbm/src/sql/mysql/mfr_conversion_28x-30x.sql +++ b/msgcntr/messageforums-hbm/src/sql/mysql/mfr_conversion_28x-30x.sql @@ -136,3 +136,5 @@ alter table MFR_TOPIC_T modify CONTEXT_ID varchar(255); -- SAK-48085 - We don't need to backfill roles in this patcher script since all the roles have been in sakai_realm.sql since 2010 +--S2U-29 +alter table MFR_PVT_MSG_USR_T add READ_RECEIPT bit(1) DEFAULT null; diff --git a/webcomponents/bundle/src/main/bundle/sui-notifications.properties b/webcomponents/bundle/src/main/bundle/sui-notifications.properties index 9aa815e90f9a..ccd556d4d400 100644 --- a/webcomponents/bundle/src/main/bundle/sui-notifications.properties +++ b/webcomponents/bundle/src/main/bundle/sui-notifications.properties @@ -10,6 +10,8 @@ connection_request_received=You received a connection request from {0} connection_request_accepted={0} accepted your connection request hide=Hide mark_all_viewed=Mark all as viewed +message=Private Messages +message_read=Has read the message "{0}" from the site "{1}" message_received={0} sent you a message motd=Message of the Day no_notifications=No notifications diff --git a/webcomponents/tool/src/main/frontend/js/sui-notifications/sui-notifications.js b/webcomponents/tool/src/main/frontend/js/sui-notifications/sui-notifications.js index 6f8f7382db2e..11f1b0288c4b 100644 --- a/webcomponents/tool/src/main/frontend/js/sui-notifications/sui-notifications.js +++ b/webcomponents/tool/src/main/frontend/js/sui-notifications/sui-notifications.js @@ -87,6 +87,8 @@ class SuiNotifications extends SakaiElement { this._decorateCommonsNotification(noti); } else if (toolEventPrefix === "sam") { this._decorateSamigoNotification(noti); + } else if (toolEventPrefix === "message") { + this._decorateMessageNotification(noti); } } @@ -148,6 +150,13 @@ class SuiNotifications extends SakaiElement { } } + _decorateMessageNotification(noti) { + + if (noti.event === "message.read.receipt") { + noti.title = this.i18n.message_read.replace('{0}', noti.title).replace('{1}', noti.siteTitle); + } + } + fireLoadedEvent() { this.dispatchEvent(new CustomEvent("notifications-loaded", { detail: { count: this.notifications.filter(n => !n.viewed).length }, bubbles: true })); }