/* * File : $Source: /alkacon/cvs/alkacon/com.alkacon.opencms.newsletter/src/com/alkacon/opencms/newsletter/CmsNewsletterManager.java,v $ * Date : $Date: 2010/10/14 13:17:49 $ * Version: $Revision: 1.20 $ * * This file is part of the Alkacon OpenCms Add-On Module Package * * Copyright (c) 2007 Alkacon Software GmbH (http://www.alkacon.com) * * The Alkacon OpenCms Add-On Module Package is free software: * you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Alkacon OpenCms Add-On Module Package 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with the Alkacon OpenCms Add-On Module Package. * If not, see http://www.gnu.org/licenses/. * * For further information about Alkacon Software GmbH, please see the * company website: http://www.alkacon.com. * * For further information about OpenCms, please see the * project website: http://www.opencms.org. */ package com.alkacon.opencms.newsletter; import org.opencms.configuration.CmsConfigurationManager; import org.opencms.file.CmsFile; import org.opencms.file.CmsGroup; import org.opencms.file.CmsObject; import org.opencms.file.CmsProject; import org.opencms.file.CmsUser; import org.opencms.file.types.CmsResourceTypeFolder; import org.opencms.file.types.CmsResourceTypePlain; import org.opencms.jsp.CmsJspActionElement; import org.opencms.lock.CmsLock; import org.opencms.main.CmsException; import org.opencms.main.CmsLog; import org.opencms.main.OpenCms; import org.opencms.module.A_CmsModuleAction; import org.opencms.module.CmsModule; import org.opencms.security.CmsOrganizationalUnit; import org.opencms.security.CmsRole; import org.opencms.util.CmsUUID; import org.opencms.workplace.CmsWorkplace; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import javax.mail.internet.InternetAddress; import org.apache.commons.logging.Log; /** * Provides methods to manage the subscribers (users) for the newsletter, to get all newsletter units and to get the * initialized mail data class to use for sending the newsletter emails.<p> * * @author Andreas Zahner * * @version $Revision: 1.20 $ * * @since 7.0.3 */ public class CmsNewsletterManager extends A_CmsModuleAction { /** The name of the newsletter module. */ public static final String MODULE_NAME = CmsNewsletterManager.class.getPackage().getName(); /** Module parameter name for the class name to use for generating the newsletter mail data. */ public static final String MODULE_PARAM_CLASS_MAILDATA = "class_maildata"; /** Module parameter name for the user password of the newsletter users. */ public static final String MODULE_PARAM_PASSWORD_USER = "user_password"; /** Module parameter name for the project name to use for user deletion operations. */ public static final String MODULE_PARAM_PROJECT_NAME = "project_name"; /** Name of the prefix for newsletter sub-organizational units containing mailing lists and subscribers. */ public static final String NEWSLETTER_OU_NAMEPREFIX = "nl_"; /** Pattern to validate email addresses. */ public static final Pattern PATTERN_VALIDATION_EMAIL = Pattern.compile("(\\w[-._\\w]*\\w@\\w[-._\\w]*\\w\\.\\w{2,4})"); /** The name of the property where the send data (date and mailing list) is written to. */ public static final String PROPERTY_NEWSLETTER_DATA = "newsletter"; /** Name of the additional user info: flag to determine if the newsletter user is active. */ public static final String USER_ADDITIONALINFO_ACTIVE = "AlkNewsletter_ActiveUser:"; /** Name of the additional user info: flag to determine if the newsletter user is marked for deletion. */ public static final String USER_ADDITIONALINFO_TODELETE = "AlkNewsletter_UserToDelete:"; /** The VFS folder path where the information about last sent newsletters is saved. */ public static String VFS_PATH_NEWSLETTER_INFO = CmsWorkplace.VFS_PATH_SYSTEM + "shared/alkacon_newsletter/"; /** The default password for all newsletter users, can/should be overwritten in the module parameter. */ private static final String PASSWORD_USER = "Uw82-Qn!"; /** The log object for this class. */ private static final Log LOG = CmsLog.getLog(CmsNewsletterManager.class); /** The admin CmsObject that is used for user/group operations. */ private CmsObject m_adminCms; /** * Returns the mail data class generating the newsletter mail and recipients.<p> * * The instance must be correctly initialized afterwards using {@link I_CmsNewsletterMailData#initialize(CmsJspActionElement, CmsGroup, String)}.<p> * * @return the mail data class generating the newsletter mail and recipients * @throws Exception if instanciating the mail data class fails */ public static I_CmsNewsletterMailData getMailData() throws Exception { String className = OpenCms.getModuleManager().getModule(MODULE_NAME).getParameter( MODULE_PARAM_CLASS_MAILDATA, CmsNewsletterMailData.class.getName()); return (I_CmsNewsletterMailData)Class.forName(className).newInstance(); } /** * Returns the initialized mail data class generating the newsletter mail and recipients.<p> * * @param jsp the current action element * @param group the group to send the newsletter to * @param fileName the fileName of the newsletter * @return the initialized mail data class generating the newsletter mail and recipients * @throws Exception if instantiating the mail data class fails */ public static I_CmsNewsletterMailData getMailData(CmsJspActionElement jsp, CmsGroup group, String fileName) throws Exception { I_CmsNewsletterMailData result = getMailData(); result.initialize(jsp, group, fileName); return result; } /** * Returns the initialized mail data class generating the newsletter mail and recipients.<p> * * @param jsp the current action element * @param ou the organizational unit to send the newsletter to * @param fileName the fileName of the newsletter * @return the initialized mail data class generating the newsletter mail and recipients * @throws Exception if instantiating the mail data class fails */ public static I_CmsNewsletterMailData getMailData(CmsJspActionElement jsp, CmsOrganizationalUnit ou, String fileName) throws Exception { I_CmsNewsletterMailData result = getMailData(); result.initialize(jsp, ou, fileName); return result; } /** * Returns the initialized mail data class generating the newsletter mail and recipients.<p> * * @param jsp the current action element * @param recipients the recipients of the newsletter mail, items have to be of type {@link javax.mail.internet.InternetAddress} * @param fileName the fileName of the newsletter * @return the initialized mail data class generating the newsletter mail and recipients * @throws Exception if instantiating the mail data class fails */ public static I_CmsNewsletterMailData getMailData( CmsJspActionElement jsp, List<InternetAddress> recipients, String fileName) throws Exception { I_CmsNewsletterMailData result = getMailData(); result.initialize(jsp, recipients, fileName); return result; } /** * Returns the resource type name of the mail data XML content.<p> * * @return the resource type name of the mail data XML content * @throws Exception if instantiating the mail data class fails */ public static String getMailDataResourceTypeName() throws Exception { return getMailData().getResourceTypeName(); } /** * Returns the organizational units that can contain mailing lists to display.<p> * * @param cms the current cms context * * @return the organizational units * * @throws CmsException if something goes wrong */ public static List<CmsOrganizationalUnit> getOrgUnits(CmsObject cms) throws CmsException { List<CmsOrganizationalUnit> ous = OpenCms.getRoleManager().getOrgUnitsForRole( cms, CmsRole.ACCOUNT_MANAGER.forOrgUnit(""), true); Iterator<CmsOrganizationalUnit> it = ous.iterator(); while (it.hasNext()) { CmsOrganizationalUnit ou = it.next(); if (!ou.getSimpleName().startsWith(NEWSLETTER_OU_NAMEPREFIX)) { it.remove(); } } return ous; } /** * Returns the password to use for all newsletter users.<p> * * @return the password to use for all newsletter users */ public static String getPassword() { return OpenCms.getModuleManager().getModule(MODULE_NAME).getParameter(MODULE_PARAM_PASSWORD_USER, PASSWORD_USER); } /** * Returns if the given user is active to receive newsletter emails for the given group.<p> * * @param user the user to check * @param groupName the name of the group the user is a member of * @return true if the given user is active to receive newsletter emails, otherwise false */ public static boolean isActiveUser(CmsUser user, String groupName) { Boolean active = (Boolean)user.getAdditionalInfo(USER_ADDITIONALINFO_ACTIVE + groupName); return (((active != null) && active.booleanValue()) || (active == null)) && user.isEnabled(); } /** * Returns if the email is a valid one using a regular expression pattern.<p> * * @param email the email to check * @return true if the email is valid, otherwise false */ public static boolean isValidEmail(String email) { return PATTERN_VALIDATION_EMAIL.matcher(email).matches(); } /** * @see org.opencms.module.A_CmsModuleAction#initialize(org.opencms.file.CmsObject, org.opencms.configuration.CmsConfigurationManager, org.opencms.module.CmsModule) */ @Override public void initialize(CmsObject adminCms, CmsConfigurationManager configurationManager, CmsModule module) { // store the admin CmsObject as member m_adminCms = adminCms; // check if the folder exists where the last sent newsletter information is saved if (!getAdminCms().existsResource(VFS_PATH_NEWSLETTER_INFO)) { try { // in order to create the folder, we have to switch to an offline project CmsObject cms = OpenCms.initCmsObject(getAdminCms()); String projectName = OpenCms.getModuleManager().getModule(MODULE_NAME).getParameter( MODULE_PARAM_PROJECT_NAME, "Offline"); CmsProject project = cms.readProject(projectName); cms.getRequestContext().setCurrentProject(project); // create folder where the newsletter info files should be saved to cms.createResource(VFS_PATH_NEWSLETTER_INFO, CmsResourceTypeFolder.getStaticTypeId()); OpenCms.getPublishManager().publishResource(cms, VFS_PATH_NEWSLETTER_INFO); } catch (Exception e) { // TODO: error creating folder, log info } } } /** * Activates the newsletter user with the given email address in the given group.<p> * * @param email the email address of the user * @param groupName the name of the group to activate the newsletter user for * @return true if the user was activated, otherwise false */ protected boolean activateNewsletterUser(String email, String groupName) { try { CmsUser user = getAdminCms().readUser(getAdminCms().readGroup(groupName).getOuFqn() + email); if (user.getAdditionalInfo().get(USER_ADDITIONALINFO_ACTIVE) != null) { // remove flag that this user is not active at all user.deleteAdditionalInfo(USER_ADDITIONALINFO_ACTIVE); } user.setAdditionalInfo(USER_ADDITIONALINFO_ACTIVE + groupName, Boolean.valueOf(true)); getAdminCms().writeUser(user); return true; } catch (CmsException e) { // error reading or writing user return false; } } /** * Creates a new newsletter user with the specified email address.<p> * * Returns the created user or null if the user could not be created.<p> * * The user will be activated or not depending on the activate parameter value.<p> * * @param email the email address of the new user * @param groupName the name of the group to add the user to * @param activate if true, the user will be activated directly to receive newsletters, otherwise not * @return the new created user or null if the creation failed */ protected CmsUser createNewsletterUser(String email, String groupName, boolean activate) { CmsUser user = null; // create additional infos containing the active flag set to passed parameter try { String ouFqn = getAdminCms().readGroup(groupName).getOuFqn(); try { // first try to read the user user = getAdminCms().readUser(ouFqn + email); } catch (CmsException e) { // user does not exist } if (user == null) { // create the user with additional infos user = getAdminCms().createUser( ouFqn + email, getPassword(), "Alkacon OpenCms Newsletter", Collections.EMPTY_MAP); // set the users email address user.setEmail(email); if (!activate) { // set the additional info as marker that the new user is currently not active at all user.setAdditionalInfo(USER_ADDITIONALINFO_ACTIVE, Boolean.FALSE); } } else { Object o = user.getAdditionalInfo(USER_ADDITIONALINFO_ACTIVE + groupName); if (o != null) { // user tried to subscribe to this mailing list group before, return null to show error message return null; } } user.setAdditionalInfo(USER_ADDITIONALINFO_ACTIVE + groupName, Boolean.valueOf(activate)); if (activate && (user.getAdditionalInfo().get(USER_ADDITIONALINFO_ACTIVE) != null)) { // remove flag that this user is not active at all user.deleteAdditionalInfo(USER_ADDITIONALINFO_ACTIVE); } // write the user getAdminCms().writeUser(user); // add the user to the given mailing list group getAdminCms().addUserToGroup(user.getName(), groupName); } catch (CmsException e) { // error creating or modifying the user LOG.error("Error while creating user or modifying user: " + email, e); } return user; } /** * Deletes a newsletter user with given email address from the specified group.<p> * * If the delete flag should be checked, the user has to be marked for deletion for a successful delete operation.<p> * * @param email the email address of the user to delete * @param groupName the name of the group the user should be deleted from * @param checkDeleteFlag determines if the delete flag chould be checked before deleting the user * @return true if deletion was successful, otherwise false */ protected boolean deleteNewsletterUser(String email, String groupName, boolean checkDeleteFlag) { try { String ouFqn = getAdminCms().readGroup(groupName).getOuFqn(); CmsUser user = getAdminCms().readUser(ouFqn + email); boolean isToDelete = !checkDeleteFlag || ((Boolean)user.getAdditionalInfo(USER_ADDITIONALINFO_TODELETE + groupName)).booleanValue(); if (isToDelete) { // in order to delete a user, we have to switch to an offline project CmsObject cms = OpenCms.initCmsObject(getAdminCms()); String projectName = OpenCms.getModuleManager().getModule(MODULE_NAME).getParameter( MODULE_PARAM_PROJECT_NAME, "Offline"); CmsProject project = cms.readProject(projectName); cms.getRequestContext().setCurrentProject(project); // remove the user from the specified group cms.removeUserFromGroup(user.getName(), groupName); if (cms.getGroupsOfUser(user.getName(), true).size() < 1) { // delete the user if this was the last group the user belonged to cms.deleteUser(user.getName()); } else { // remove the additional info attributes for the mailing list group user.getAdditionalInfo().remove(USER_ADDITIONALINFO_TODELETE + groupName); user.getAdditionalInfo().remove(USER_ADDITIONALINFO_ACTIVE + groupName); cms.writeUser(user); } return true; } } catch (CmsException e) { // error reading or deleting user LOG.error("Error while deleting the webuser: " + email, e); } return false; } /** * Returns if a newsletter user with the given email address exists in the given group.<p> * * @param email the email address of the user * @param groupName the name of the group the user could be a member of * @return true if a newsletter user with the given email address exists, otherwise false */ protected boolean existsNewsletterUser(String email, String groupName) { try { String ouFqn = getAdminCms().readGroup(groupName).getOuFqn(); CmsUser user = getAdminCms().readUser(ouFqn + email); CmsGroup group = getAdminCms().readGroup(groupName); return getAdminCms().getGroupsOfUser(user.getName(), true).contains(group); } catch (CmsException e) { // error reading user, does not exist return false; } } /** * Returns if information about the last sent newsletter exists for the given group ID.<p> * * @param groupId the group ID to check the presence of an information file * * @return <code>true</code> if information about the last sent newsletter exists, otherwise <code>false</code> */ protected boolean existsSentNewsletterInfo(CmsUUID groupId) { // file path where the newsletter info file is saved String infoFilePath = VFS_PATH_NEWSLETTER_INFO + NEWSLETTER_OU_NAMEPREFIX + groupId.hashCode(); return getAdminCms().existsResource(infoFilePath); } /** * Returns if information about the last sent newsletter exists for the given group name.<p> * * @param groupName the group name to check the presence of an information file * * @return <code>true</code> if information about the last sent newsletter exists, otherwise <code>false</code> */ protected boolean existsSentNewsletterInfo(String groupName) { try { CmsUUID groupId = getAdminCms().readGroup(groupName).getId(); return existsSentNewsletterInfo(groupId); } catch (CmsException e) { // error reading group return false; } } /** * Returns the UUID of the last sent newsletter resource or <code>null</code>, if the information is not found.<p> * * @param groupName the group name to get the last sent newsletter resource UUID * * @return the UUID of the last sent newsletter resource */ protected CmsUUID getSentNewsletterInfo(String groupName) { try { CmsUUID groupId = getAdminCms().readGroup(groupName).getId(); String infoFilePath = VFS_PATH_NEWSLETTER_INFO + NEWSLETTER_OU_NAMEPREFIX + groupId.hashCode(); if (getAdminCms().existsResource(infoFilePath)) { // we have an info file, read the content and return it as UUID String idStr = new String(getAdminCms().readFile(infoFilePath).getContents()); return new CmsUUID(idStr); } } catch (CmsException e) { // error reading info file } return null; } /** * Marks a newsletter user to be deleted, this is necessary for the deletion confirmation.<p> * * @param email the email address of the user * @param groupName the name of the group the user should be marked for deletion * @return true if the user was successfully marked to be deleted, otherwise false */ protected boolean markToDeleteNewsletterUser(String email, String groupName) { try { String ouFqn = getAdminCms().readGroup(groupName).getOuFqn(); CmsUser user = getAdminCms().readUser(ouFqn + email); user.setAdditionalInfo(USER_ADDITIONALINFO_TODELETE + groupName, Boolean.valueOf(true)); getAdminCms().writeUser(user); return true; } catch (CmsException e) { // error reading or writing user return false; } } /** * Saves the information about the last sent newsletter resource to a file in <code>/system/shared/alkacon_newsletter/</code>.<p> * * This can be used to send the last newsletter to newly registered subscribers.<p> * * @param groupId the ID of the mailing list group to which the newsletter has been sent * @param id the structure ID of the newsletter resource */ protected void saveSentNewsletterInfo(CmsUUID groupId, CmsUUID id) { try { // in order to write the info, we have to switch to an offline project CmsObject cms = OpenCms.initCmsObject(getAdminCms()); String projectName = OpenCms.getModuleManager().getModule(MODULE_NAME).getParameter( MODULE_PARAM_PROJECT_NAME, "Offline"); CmsProject project = cms.readProject(projectName); cms.getRequestContext().setCurrentProject(project); // file name of the info file containing the path of the sent newsletter String infoFileName = NEWSLETTER_OU_NAMEPREFIX + groupId.hashCode(); // folder where the newsletter info file should be saved String infoFilePath = VFS_PATH_NEWSLETTER_INFO + infoFileName; if (!cms.existsResource(infoFilePath)) { cms.createResource(infoFilePath, CmsResourceTypePlain.getStaticTypeId(), id.toString().getBytes(), null); } else { CmsLock lock = cms.getLock(infoFilePath); if (lock.isNullLock()) { cms.lockResource(infoFilePath); lock = cms.getLock(infoFilePath); } if (lock.isOwnedBy(cms.getRequestContext().currentUser())) { CmsFile file = cms.readFile(infoFilePath); file.setContents(id.toString().getBytes()); cms.writeFile(file); } } cms.unlockResource(infoFilePath); OpenCms.getPublishManager().publishResource(cms, infoFilePath); } catch (Exception e) { // TODO: failed to save the information return; } } /** * Returns the admin CmsObject that is used for user/group operations.<p> * * @return the admin CmsObject that is used for user/group operations */ private CmsObject getAdminCms() { return m_adminCms; } }