/* * ConcourseConnect * Copyright 2009 Concursive Corporation * http://www.concursive.com * * This file is part of ConcourseConnect, an open source social business * software and community platform. * * Concursive ConcourseConnect is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, version 3 of the License. * * Under the terms of the GNU Affero General Public License you must release the * complete source code for any application that uses any part of ConcourseConnect * (system header files and libraries used by the operating system are excluded). * These terms must be included in any work that has ConcourseConnect components. * If you are developing and distributing open source applications under the * GNU Affero General Public License, then you are free to use ConcourseConnect * under the GNU Affero General Public License. * * If you are deploying a web site in which users interact with any portion of * ConcourseConnect over a network, the complete source code changes must be made * available. For example, include a link to the source archive directly from * your web site. * * For OEMs, ISVs, SIs and VARs who distribute ConcourseConnect with their * products, and do not license and distribute their source code under the GNU * Affero General Public License, Concursive provides a flexible commercial * license. * * To anyone in doubt, we recommend the commercial license. Our commercial license * is competitively priced and will eliminate any confusion about how * ConcourseConnect can be used and distributed. * * ConcourseConnect is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with ConcourseConnect. If not, see <http://www.gnu.org/licenses/>. * * Attribution Notice: ConcourseConnect is an Original Work of software created * by Concursive Corporation */ package com.concursive.connect.web.modules.userprofile.workflow; import com.concursive.commons.email.SMTPMessage; import com.concursive.commons.email.SMTPMessageFactory; import com.concursive.commons.text.StringUtils; import com.concursive.commons.workflow.ComponentContext; import com.concursive.commons.workflow.ComponentInterface; import com.concursive.commons.workflow.ObjectHookComponent; import com.concursive.connect.Constants; import com.concursive.connect.web.modules.issues.dao.TicketContact; import com.concursive.connect.web.modules.issues.dao.TicketContactList; import com.concursive.connect.web.modules.issues.workflow.LoadTicketDetails; import com.concursive.connect.web.modules.login.dao.User; import com.concursive.connect.web.modules.login.dao.UserList; import com.concursive.connect.web.modules.login.utils.UserUtils; import com.concursive.connect.web.modules.members.dao.TeamMember; import com.concursive.connect.web.modules.members.dao.TeamMemberList; import com.concursive.connect.web.modules.profile.dao.Project; import com.concursive.connect.web.modules.profile.utils.ProjectUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.sql.Connection; import java.util.ArrayList; import java.util.Iterator; import java.util.StringTokenizer; /** * A component to send emails * * @author matt rajkowski * @created January 14, 2003 */ public class SendUserNotification extends ObjectHookComponent implements ComponentInterface { private static Log LOG = LogFactory.getLog(SendUserNotification.class); public final static String HOST = "notification.host"; public final static String USER_TO_NOTIFY = "notification.userToNotify"; public final static String NOTIFY_PROJECT_LEADS = "notification.addProjectLeads"; public final static String NOTIFY_TICKET_CONTACT_LIST = "notification.addTicketContactList"; public final static String NOTIFY_FORUM_CONTACTS = "notification.addForumContacts"; public final static String NOTIFY_ADMINS = "notification.addAdmins"; public final static String SUBJECT = "notification.subject"; public final static String FROM = "notification.from"; public final static String USERS_FROM = "notification.users.from"; public final static String REPLY_TO = "notification.replyTo"; public final static String BODY = "notification.body"; // Comma-separated email addresses public final static String EMAIL_TO = "notification.to"; // Comma-separated user ids public final static String NOTIFICATION_USERS_TO = "notification.users.to"; // Comma-separated user ids public final static String EXCLUDE_LIST = "notification.excludeUsers"; // Comma-separated user ids public final static String USERS_TO_IDS = "userIds"; // Message attributes public final static String ALERT_FONT_COLOR = "notification.alert.font.color"; /** * Gets the description attribute of the SendUserNotification object * * @return The description value */ public String getDescription() { return "Send an email notification to a user"; } /** * This component sends email based on various notification parameters * * @param context Description of the Parameter * @return Description of the Return Value */ public boolean execute(ComponentContext context) { Connection db = null; // The recipients to send email to ArrayList<Integer> users = new ArrayList<Integer>(); ArrayList<String> emails = new ArrayList<String>(); try { // Use this database connection sparingly db = getConnection(context); // Add admins boolean addAdmins = "true".equals(context.getParameter(NOTIFY_ADMINS)); if (addAdmins) { UserList userList = new UserList(); userList.setAdmin(Constants.TRUE); userList.setValidUser(Constants.TRUE); userList.buildList(db); for (User user : userList) { if (StringUtils.hasText(user.getEmail())) { if (user.getAccessAdmin()) { users.add(user.getId()); } else { LOG.error("A non-admin user was almost added to SendUserNotification"); } } } if (LOG.isDebugEnabled() && userList.size() == 0) { LOG.warn("No admins found"); } } // Add project leads Project thisProject = (Project) context.getAttribute("project"); boolean addProjectLeads = "true".equals(context.getParameter(NOTIFY_PROJECT_LEADS)); if (addProjectLeads && thisProject != null) { TeamMemberList members = new TeamMemberList(); members.setProjectId(thisProject.getId()); members.setMinimumRoleLevel(TeamMember.MANAGER); members.buildList(db); for (TeamMember member : members) { if (!users.contains(member.getUserId())) { users.add(member.getUserId()); } } } // Add forum contacts boolean addForumContacts = "true".equals(context.getParameter(NOTIFY_FORUM_CONTACTS)); if (addForumContacts && thisProject != null) { // Go through the users of a project and see if they have access to discussions TeamMemberList teamMembers = new TeamMemberList(); teamMembers.setProjectId(thisProject.getId()); teamMembers.buildList(db); for (TeamMember member : teamMembers) { User thisUser = UserUtils.loadUser(member.getUserId()); if (thisUser.getWatchForums() && ProjectUtils.hasAccess(thisProject.getId(), thisUser, "project-discussion-topics-view")) { users.add(thisUser.getId()); } } } // Add ticket contact list TicketContactList ticketContactList = (TicketContactList) context.getAttribute(LoadTicketDetails.CONTACT_LIST); boolean addTicketContactList = "true".equals(context.getParameter(NOTIFY_TICKET_CONTACT_LIST)); if (addTicketContactList && ticketContactList != null) { Iterator i = ticketContactList.iterator(); while (i.hasNext()) { TicketContact contact = (TicketContact) i.next(); contact.buildEmailAddress(db); if (contact.getUserId() > -1) { if (!users.contains(contact.getUserId())) { users.add(contact.getUserId()); } } else if (StringUtils.hasText(contact.getContactEmail())) { emails.add(contact.getContactEmail()); } } } // Add user to notify int userToNotify = context.getParameterAsInt(USER_TO_NOTIFY); if (userToNotify > -1) { if (!users.contains(userToNotify)) { users.add(userToNotify); } } // Go through users to String includeList = context.getParameter(NOTIFICATION_USERS_TO); if (includeList != null) { StringTokenizer st = new StringTokenizer(includeList, ","); while (st.hasMoreTokens()) { Integer id = Integer.parseInt(st.nextToken().trim()); if (!users.contains(id)) { users.add(id); } } } // Go through userIds set as attributes includeList = (String) context.getAttribute(USERS_TO_IDS); if (includeList != null) { StringTokenizer st = new StringTokenizer(includeList, ","); while (st.hasMoreTokens()) { Integer id = Integer.parseInt(st.nextToken().trim()); if (!users.contains(id)) { users.add(id); } } } // Go through email to String emailValues = context.getParameter(EMAIL_TO); if (emailValues != null) { StringTokenizer st = new StringTokenizer(emailValues, ","); while (st.hasMoreTokens()) { String emailAddress = st.nextToken().trim(); if (!emails.contains(emailAddress)) { emails.add(emailAddress); } } } // Go through exclude list String excludeList = context.getParameter(EXCLUDE_LIST); if (excludeList != null) { StringTokenizer st = new StringTokenizer(excludeList, ","); while (st.hasMoreTokens()) { String id = st.nextToken().trim(); users.remove(new Integer(Integer.parseInt(id))); } } } catch (Exception e) { LOG.error("build email list", e); } finally { freeConnection(context, db); } // NOTE: Now that the database connections are done, send the emails try { sendEmails(context, users, emails); return true; } catch (Exception e) { LOG.error("send emails", e); } return false; } /** * Send the emails * * @param context ComponentContext * @param users List of users * @param emails List of email addresses * @throws Exception mail error */ private void sendEmails(ComponentContext context, ArrayList<Integer> users, ArrayList<String> emails) throws Exception { if (users.size() > 0 || emails.size() > 0) { LOG.debug("Constructing mail object"); SMTPMessage mail = SMTPMessageFactory.createSMTPMessageInstance(context.getApplicationPrefs()); String from = StringUtils.toHtmlValue(context.getParameter(FROM)); String fromId = StringUtils.toHtmlValue(context.getParameter(USERS_FROM)); if (from != null && !"".equals(from)) { mail.setFrom(from); } else if (fromId != null && !"".equals(fromId)) { User thisUser = UserUtils.loadUser(Integer.parseInt(fromId)); mail.setFrom(thisUser.getEmail()); } else { mail.setFrom(context.getParameter(ComponentContext.APPLICATION_EMAIL_ADDRESS)); } mail.setType("text/html"); mail.setSubject(context.getParameter(SUBJECT)); /* //TODO: Populate the message using a freemarker template Configuration configuration = (Configuration) context.getAttribute(ComponentContext.FREEMARKER_CONFIGURATION); Template template = configuration.getTemplate("send_user_notification_email-html.ftl"); Map bodyMappings = new HashMap(); bodyMappings.put("body", context.getParameter(BODY)); // Parse and send StringWriter inviteBodyTextWriter = new StringWriter(); template.process(bodyMappings, inviteBodyTextWriter); mail.setBody(inviteBodyTextWriter.toString()); */ mail.setBody(context.getParameter(BODY)); // Send to each user for (Integer id : users) { User thisUser = UserUtils.loadUser(id); String email = thisUser.getEmail(); if (email != null) { LOG.debug("Sending to user: " + email); mail.setTo(email); int status = mail.send(); LOG.debug("Send status: " + status); } } // Send to each contact for (String email : emails) { if (email != null) { LOG.debug("Sending to contact: " + email); mail.setTo(email); int status = mail.send(); LOG.debug("Send status: " + status); } } } else { LOG.warn("No users or emails to send notification to"); } } }