Added "following email"

Added activities email templates patch

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28883 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Florian Mü
2011-07-08 11:09:02 +00:00
parent 664c2bd4c8
commit 5060681197
12 changed files with 451 additions and 2 deletions

View File

@@ -472,6 +472,7 @@
<value>alfresco.messages.jbpm-engine-messages</value>
<value>alfresco.messages.activiti-engine-messages</value>
<value>alfresco.messages.wdr-messages</value>
<value>alfresco.messages.subscription-service</value>
</list>
</property>
</bean>

View File

@@ -525,6 +525,11 @@
<prop key="location">alfresco/templates/activities-email-templates.acp</prop>
<prop key="messages">alfresco/messages/bootstrap-spaces</prop>
</props>
<props>
<prop key="path">/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}</prop>
<prop key="location">alfresco/templates/following-email-templates.acp</prop>
<prop key="messages">alfresco/messages/bootstrap-spaces</prop>
</props>
<props>
<prop key="path">/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.rss.childname}</prop>
<prop key="location">alfresco/templates/rss_templates.acp</prop>

View File

@@ -97,6 +97,11 @@ email.template.email_template_for_notifying_users_of_an_Invite=Email template fo
email.templates.email_template_for_notifying_new_users=Email template used to inform new users of their accounts
spaces.templates.email.following.name=Following Email Templates
spaces.templates.email.following.description=Following Email Templates
email.templates.email_template_for_following_notifications=Email template used to generate following notification emails
version.default=Default version
version.french=French version
version.german=German version

View File

@@ -409,5 +409,13 @@ patch.exampleJavaScript.description=Loads sample Javascript file into datadictio
patch.fixAclInheritance.description=Fixes any ACL inheritance issues.
patch.fixAclInheritance.result=Fixed {0} ACLs.
patch.followingMailTemplates.description=Adds email templates for following notifications
patch.activitiesTemplatesUpdate.description=Updates activities email templates.
patch.activitiesTemplatesUpdate.err.template_folder_not_found=Activities email template folder could not be found.
patch.activitiesTemplatesUpdate.err.source_not_found=New activities email template ACP could not be found.
patch.activitiesTemplatesUpdate.err.update_failed=Could not update activities email template: {0}
patch.activitiesTemplatesUpdate.result=Updated {0} activities email templates.
patch.avmToAdmRemoteStore.description=Migrates Share Surf config from AVM sitestore to DM Sites folder.
patch.avmToAdmRemoteStore.complete=Completed Share Surf config migration.

View File

@@ -0,0 +1,7 @@
# Subscription service messages
subscription.notification.email.subject={0} is now following you
subscription_service.err.disabled=The subscription is disabled
subscription_service.err.write-denied=No permissions to update
subscription_service.err.private-list=This list is marked as private

View File

@@ -2601,6 +2601,50 @@
</property>
</bean>
<bean id="patch.activitiesEmailTemplateUpdate" class="org.alfresco.repo.admin.patch.impl.ActivitiesTemplatesUpdatePatch" parent="basePatch">
<property name="id">
<value>patch.activitiesTemplatesUpdate</value>
</property>
<property name="description">
<value>patch.activitiesTemplatesUpdate.description</value>
</property>
<property name="fixesFromSchema" value="0" />
<property name="fixesToSchema" value="5010" />
<property name="targetSchema" value="5011" />
<property name="dependsOn">
<list>
<ref bean="patch.activitiesEmailTemplate" />
</list>
</property>
<property name="fileFolderService">
<ref bean="fileFolderService" />
</property>
<property name="versionService">
<ref bean="versionService" />
</property>
<property name="newTemplatesFile">
<value>alfresco/templates/activities-email-templates.acp</value>
</property>
</bean>
<bean id="patch.followingMailTemplates" class="org.alfresco.repo.admin.patch.impl.GenericBootstrapPatch" parent="basePatch" >
<property name="id"><value>patch.followingMailTemplates</value></property>
<property name="description"><value>patch.followingMailTemplates.description</value></property>
<property name="fixesFromSchema"><value>0</value></property>
<property name="fixesToSchema"><value>5010</value></property>
<property name="targetSchema"><value>5011</value></property>
<property name="importerBootstrap">
<ref bean="spacesBootstrap" />
</property>
<property name="bootstrapView">
<props>
<prop key="path">/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}</prop>
<prop key="location">alfresco/templates/following-email-templates.acp</prop>
<prop key="messages">alfresco/messages/bootstrap-spaces</prop>
</props>
</property>
</bean>
<bean id="patch.newUserEmailTemplates" class="org.alfresco.repo.admin.patch.impl.GenericBootstrapPatch" parent="basePatch" >
<property name="id"><value>patch.newUserEmailTemplates</value></property>
<property name="description"><value>patch.newUserEmailTemplates.description</value></property>

View File

@@ -376,6 +376,7 @@ spaces.templates.content.childname=app:content_templates
spaces.templates.email.childname=app:email_templates
spaces.templates.email.invite1.childname=app:invite_email_templates
spaces.templates.email.notify.childname=app:notify_email_templates
spaces.templates.email.following.childname=app:following
spaces.templates.rss.childname=app:rss_templates
spaces.savedsearches.childname=app:saved_searches
spaces.scripts.childname=app:scripts

View File

@@ -25,6 +25,10 @@
<property name="personService" ref="PersonService" />
<property name="activityService" ref="activityService" />
<property name="authorityService" ref="AuthorityService" />
<property name="actionService" ref="ActionService" />
<property name="searchService" ref="SearchService" />
<property name="namespaceService" ref="NamespaceService" />
<property name="fileFolderService" ref="FileFolderService" />
</bean>
</beans>

View File

@@ -0,0 +1,210 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.admin.patch.impl;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.admin.patch.AbstractPatch;
import org.alfresco.repo.version.VersionModel;
import org.alfresco.service.cmr.admin.PatchException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.cmr.version.VersionType;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.TempFileProvider;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.springframework.extensions.surf.util.I18NUtil;
import org.springframework.util.FileCopyUtils;
/**
* Patch to update the activities email templates. Current templates become
* versions of the new templates.
*
* @author Florian Mueller
*/
public class ActivitiesTemplatesUpdatePatch extends AbstractPatch
{
private static final String MSG_SUCCESS = "patch.activitiesTemplatesUpdate.result";
protected FileFolderService fileFolderService;
protected VersionService versionService;
protected String newTemplatesFile;
protected String newTemplatesName;
protected ZipFile zipFile;
public void setFileFolderService(FileFolderService fileFolderService)
{
this.fileFolderService = fileFolderService;
}
public void setVersionService(VersionService versionService)
{
this.versionService = versionService;
}
public void setNewTemplatesFile(String newTemplatesFile)
{
this.newTemplatesFile = newTemplatesFile;
int x = newTemplatesFile.lastIndexOf("/");
if (x < 0)
{
newTemplatesName = newTemplatesFile;
} else
{
newTemplatesName = newTemplatesFile.substring(x + 1);
}
x = newTemplatesName.lastIndexOf(".");
if (x > 0)
{
newTemplatesName = newTemplatesName.substring(0, x);
}
}
@Override
protected String applyInternal() throws Exception
{
// get templates folder
NodeRef templateFolder = getTemplateFolder();
// get ACP file
ZipFile zipFile = getZipFile();
// iterate over all templates in the ACP file and apply version
@SuppressWarnings("unchecked")
Enumeration<ZipArchiveEntry> zae = (Enumeration<ZipArchiveEntry>) zipFile.getEntries();
int count = 0;
while (zae.hasMoreElements())
{
ZipArchiveEntry entry = zae.nextElement();
if (!(entry.getName().startsWith(newTemplatesName + "/") && entry.getName().endsWith(".ftl")))
{
// ignore non-template files
continue;
}
// find matching node and add version
NodeRef nodeRef = findMatchingNode(templateFolder, entry);
if (nodeRef != null)
{
addNewVersion(nodeRef, zipFile, entry);
count++;
}
}
return I18NUtil.getMessage(MSG_SUCCESS, count);
}
protected NodeRef getTemplateFolder()
{
String xpath = "app:company_home/app:dictionary/app:email_templates/cm:activities";
try
{
NodeRef rootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
List<NodeRef> nodeRefs = searchService.selectNodes(rootNodeRef, xpath, null, namespaceService, false);
if (nodeRefs.size() < 1)
{
throw new PatchException("patch.activitiesTemplatesUpdate.err.template_folder_not_found");
}
return nodeRefs.get(0);
} catch (Exception e)
{
throw new PatchException("patch.activitiesTemplatesUpdate.err.template_folder_not_found", e);
}
}
protected ZipFile getZipFile()
{
InputStream templateFileStream = ActivitiesTemplatesUpdatePatch.class.getClassLoader().getResourceAsStream(
newTemplatesFile);
if (templateFileStream == null)
{
throw new PatchException("patch.activitiesTemplatesUpdate.err.source_not_found");
}
try
{
File tempFile = TempFileProvider.createTempFile("templateFile", ".tmp");
FileOutputStream os = new FileOutputStream(tempFile);
FileCopyUtils.copy(templateFileStream, os);
return new ZipFile(tempFile, "Cp437");
} catch (IOException e)
{
throw new PatchException("patch.activitiesTemplatesUpdate.err.source_not_found", e);
}
}
protected NodeRef findMatchingNode(NodeRef parentNodeRef, ZipArchiveEntry entry)
{
String name = entry.getName();
int x = name.lastIndexOf("/");
if (x > 0)
{
name = name.substring(x + 1);
}
return nodeService.getChildByName(parentNodeRef, ContentModel.ASSOC_CONTAINS, name);
}
protected void addNewVersion(NodeRef nodeRef, ZipFile zipFile, ZipArchiveEntry entry)
{
try
{
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false)
{
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
props.put(ContentModel.PROP_INITIAL_VERSION, true);
props.put(ContentModel.PROP_AUTO_VERSION, false);
nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props);
}
Map<String, Serializable> versionProperties = new HashMap<String, Serializable>();
versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR);
versionService.createVersion(nodeRef, versionProperties);
ContentWriter writer = fileFolderService.getWriter(nodeRef);
writer.setMimetype("text/plain");
writer.setEncoding("UTF-8");
writer.putContent(zipFile.getInputStream(entry));
} catch (Exception e)
{
throw new PatchException("patch.activitiesTemplatesUpdate.err.update_failed", e);
}
}
}

View File

@@ -19,16 +19,27 @@
package org.alfresco.repo.subscriptions;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.query.PagingRequest;
import org.alfresco.repo.action.executer.MailActionExecuter;
import org.alfresco.repo.activities.ActivityType;
import org.alfresco.repo.domain.subscriptions.SubscriptionsDAO;
import org.alfresco.repo.search.SearcherException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.activities.ActivityService;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.subscriptions.PagingFollowingResults;
@@ -37,10 +48,12 @@ import org.alfresco.service.cmr.subscriptions.PrivateSubscriptionListException;
import org.alfresco.service.cmr.subscriptions.SubscriptionItemTypeEnum;
import org.alfresco.service.cmr.subscriptions.SubscriptionService;
import org.alfresco.service.cmr.subscriptions.SubscriptionsDisabledException;
import org.alfresco.service.namespace.NamespaceService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.extensions.surf.util.I18NUtil;
public class SubscriptionServiceImpl implements SubscriptionService
{
@@ -54,9 +67,12 @@ public class SubscriptionServiceImpl implements SubscriptionService
private static final String FOLLOWER_FIRSTNAME = "followerFirstName";
private static final String FOLLOWER_LASTNAME = "followerLastName";
private static final String FOLLOWER_USERNAME = "followerUserName";
private static final String FOLLOWER_JOBTITLE = "followerJobTitle";
private static final String USER_FIRSTNAME = "userFirstName";
private static final String USER_LASTNAME = "userLastName";
private static final String USER_USERNAME = "userUserName";
private static final String FOLLOWING_COUNT = "followingCount";
private static final String FOLLOWER_COUNT = "followerCount";
private static final String SUBSCRIBER_FIRSTNAME = "subscriberFirstName";
private static final String SUBSCRIBER_LASTNAME = "subscriberLastName";
@@ -68,6 +84,10 @@ public class SubscriptionServiceImpl implements SubscriptionService
protected PersonService personService;
protected ActivityService activityService;
protected AuthorityService authorityService;
protected ActionService actionService;
protected SearchService searchService;
protected NamespaceService namespaceService;
protected FileFolderService fileFolderService;
/**
* Sets the subscriptions DAO.
@@ -109,6 +129,38 @@ public class SubscriptionServiceImpl implements SubscriptionService
this.authorityService = authorityService;
}
/**
* Sets the action service.
*/
public final void setActionService(ActionService actionService)
{
this.actionService = actionService;
}
/**
* Set the search service.
*/
public final void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
/**
* Set the namespace service.
*/
public final void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
/**
* Set the fileFolder service.
*/
public final void setFileFolderService(FileFolderService fileFolderService)
{
this.fileFolderService = fileFolderService;
}
@Override
public PagingSubscriptionResults getSubscriptions(String userId, SubscriptionItemTypeEnum type,
PagingRequest pagingRequest)
@@ -215,9 +267,10 @@ public class SubscriptionServiceImpl implements SubscriptionService
if (userId.equalsIgnoreCase(AuthenticationUtil.getRunAsUser()))
{
String activityDataJSON = null;
try
{
String activityDataJSON = null;
NodeRef followerNode = personService.getPerson(userId, false);
NodeRef userNode = personService.getPerson(userToFollow, false);
JSONObject activityData = new JSONObject();
@@ -229,13 +282,23 @@ public class SubscriptionServiceImpl implements SubscriptionService
activityData.put(USER_FIRSTNAME, nodeService.getProperty(userNode, ContentModel.PROP_FIRSTNAME));
activityData.put(USER_LASTNAME, nodeService.getProperty(userNode, ContentModel.PROP_LASTNAME));
activityDataJSON = activityData.toString();
activityService.postActivity(ActivityType.SUBSCRIPTIONS_FOLLOW, null, ACTIVITY_TOOL, activityDataJSON);
} catch (JSONException je)
{
// log error, subsume exception
logger.error("Failed to get activity data: " + je);
}
activityService.postActivity(ActivityType.SUBSCRIPTIONS_FOLLOW, null, ACTIVITY_TOOL, activityDataJSON);
try
{
sendFollowingMail(userId, userToFollow);
} catch (Exception e)
{
// log error, subsume exception
logger.error("Failed to send following email: " + e);
}
}
}
@@ -379,4 +442,105 @@ public class SubscriptionServiceImpl implements SubscriptionService
throw new IllegalArgumentException("Only user nodes supported!");
}
}
/**
* Sends an email to the person that is followed.
*/
protected void sendFollowingMail(String userId, String userToFollow)
{
NodeRef followerNode = personService.getPerson(userId, false);
NodeRef userNode = personService.getPerson(userToFollow, false);
Serializable emailFeedDisabled = nodeService.getProperty(userNode, ContentModel.PROP_EMAIL_FEED_DISABLED);
if (emailFeedDisabled instanceof Boolean && ((Boolean) emailFeedDisabled).booleanValue())
{
// this user doesn't want to be notified
return;
}
Serializable emailAddress = nodeService.getProperty(userNode, ContentModel.PROP_EMAIL);
if (emailAddress == null)
{
// we can't send an email without email address
return;
}
NodeRef templateNodeRef = getEmailTemplateRef();
if (templateNodeRef == null)
{
// we can't send an email without template
return;
}
// compile the mail subject
String followerFullName = (nodeService.getProperty(followerNode, ContentModel.PROP_FIRSTNAME) + " " + nodeService
.getProperty(followerNode, ContentModel.PROP_LASTNAME)).trim();
String subjectText = I18NUtil.getMessage("subscription.notification.email.subject", followerFullName);
Map<String, Object> model = new HashMap<String, Object>();
model.put(FOLLOWER_USERNAME, userId);
model.put(FOLLOWER_FIRSTNAME, nodeService.getProperty(followerNode, ContentModel.PROP_FIRSTNAME));
model.put(FOLLOWER_LASTNAME, nodeService.getProperty(followerNode, ContentModel.PROP_LASTNAME));
model.put(FOLLOWER_JOBTITLE, nodeService.getProperty(followerNode, ContentModel.PROP_JOBTITLE));
model.put(USER_USERNAME, userToFollow);
model.put(USER_FIRSTNAME, nodeService.getProperty(userNode, ContentModel.PROP_FIRSTNAME));
model.put(USER_LASTNAME, nodeService.getProperty(userNode, ContentModel.PROP_LASTNAME));
model.put(FOLLOWING_COUNT, -1);
try
{
model.put(FOLLOWING_COUNT, getFollowingCount(userId));
} catch (Exception e)
{
}
model.put(FOLLOWER_COUNT, -1);
try
{
model.put(FOLLOWER_COUNT, getFollowersCount(userId));
} catch (Exception e)
{
}
Action mail = actionService.createAction(MailActionExecuter.NAME);
mail.setParameterValue(MailActionExecuter.PARAM_TO, emailAddress);
mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT, subjectText);
mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, templateNodeRef);
mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable) model);
actionService.executeAction(mail, null);
}
/**
* Returns the NodeRef of the email template or <code>null</code> if the
* template coudln't be found.
*/
protected NodeRef getEmailTemplateRef()
{
// Find the following email template
String xpath = "app:company_home/app:dictionary/app:email_templates/app:following/cm:following-email.html.ftl";
try
{
NodeRef rootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
List<NodeRef> nodeRefs = searchService.selectNodes(rootNodeRef, xpath, null, namespaceService, false);
if (nodeRefs.size() > 1)
{
logger.error("Found too many email templates using: " + xpath);
nodeRefs = Collections.singletonList(nodeRefs.get(0));
} else if (nodeRefs.size() == 0)
{
logger.error("Cannot find the email template using " + xpath);
return null;
}
// Now localise this
NodeRef base = nodeRefs.get(0);
NodeRef local = fileFolderService.getLocalizedSibling(base);
return local;
} catch (SearcherException e)
{
logger.error("Cannot find the email template!", e);
}
return null;
}
}