diff --git a/config/alfresco/client/config/repo-clients-apps.properties b/config/alfresco/client/config/repo-clients-apps.properties index fff40c330a..a4a043fdcb 100644 --- a/config/alfresco/client/config/repo-clients-apps.properties +++ b/config/alfresco/client/config/repo-clients-apps.properties @@ -1,4 +1,9 @@ # registry of clients that are able to email -repo.client-app.sfs.templateAssetsUrl=http://localhost:8082/sfs -repo.client-app.sfs.sharedLinkBaseUrl=http://localhost:8082/sfs/s \ No newline at end of file +repo.client-app.share.templateAssetsUrl=${shareUrl}/res/components/images/ +repo.client-app.share.sharedLinkBaseUrl=${shareUrl}/s +# the path could be: +# an XPATH (e.g. app:company_home/app:dictionary/app:email_templates/example-email.ftl) +# a NodeRef of the template +# a class path of the template +repo.client-app.share.sharedLinkTemplatePath= \ No newline at end of file diff --git a/config/alfresco/messages/quickshare-service.properties b/config/alfresco/messages/quickshare-service.properties index be2973e68c..9d1892ff0f 100644 --- a/config/alfresco/messages/quickshare-service.properties +++ b/config/alfresco/messages/quickshare-service.properties @@ -1,2 +1,6 @@ # QuickShare Service quickshare.notifier.email.subject={0} {1} shared {2} with you + +# email template +template.quickshare-email.ftl.title={0} shared with you +template.quickshare-email.ftl.detail={0} {1} has shared {3} with you. diff --git a/config/alfresco/messages/templates-messages.properties b/config/alfresco/messages/templates-messages.properties index 4e733711fc..03f8570ffd 100644 --- a/config/alfresco/messages/templates-messages.properties +++ b/config/alfresco/messages/templates-messages.properties @@ -91,3 +91,9 @@ templates.invite-email-moderated.html.info={0} requested to join the ''{1}'' sit templates.invite-email-moderated.html.see_pending=See Pending Requests templates.invite-email.html.salutation_generic=Hi, + +# common for most email templates +templates.generic-email.ftl.salutation=Hi {0}, +templates.generic-email.ftl.copy_right=All Rights Reserved. +templates.generic-email.ftl.contact_us=Contact Us + diff --git a/config/alfresco/quickshare-services-context.xml b/config/alfresco/quickshare-services-context.xml index ed5c1552e4..0c568b5ffb 100644 --- a/config/alfresco/quickshare-services-context.xml +++ b/config/alfresco/quickshare-services-context.xml @@ -65,12 +65,13 @@ - + + diff --git a/config/alfresco/repo-clients-apps-context.xml b/config/alfresco/repo-clients-apps-context.xml index b39b4c033f..79383c4e40 100644 --- a/config/alfresco/repo-clients-apps-context.xml +++ b/config/alfresco/repo-clients-apps-context.xml @@ -15,4 +15,11 @@ + + + + + + + \ No newline at end of file diff --git a/config/alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl b/config/alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl index 1171753db4..498f36bd20 100644 --- a/config/alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl +++ b/config/alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl @@ -317,10 +317,10 @@ display:block; border-collapse:collapse; outline:none; text-decoration:none;">

${shared_node_name} shared with you

+middle;">

${message("template.quickshare-email.ftl.title", shared_node_name)?html}

-

${sender_first_name} ${sender_last_name} has shared ${shared_node_name} with you.

+

${message("template.quickshare-email.ftl.detail", sender_first_name, sender_last_name, shared_node_url, shared_node_name)}

<#if sender_message??>

${sender_message?html}

@@ -411,15 +411,16 @@ middle;">

${shared_node_name} shared with you

href="http://go.alfresco.com/a0100K050L0000KZjI2d00U" target="_blank">alfresco.com
+ src="${template_assets_url}/AlfrescoGlobal_Logo_BW.png" border="0" alt="alfresco.com" width="92" height="29">
- Contact Us © 2015 Alfresco Software, Inc. All Rights Reserved.
+ style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #0c79bf;">${message("templates.generic-email.ftl.contact_us")} + © ${date?string["yyyy"]} Alfresco Software, Inc. + ${message("templates.generic-email.ftl.copy_right")}
Bridge Ave, The Place Maidenhead SL6 1AF United Kingdom
1825 S Grant St, Suite 900 San Mateo, CA 94402 USA
diff --git a/source/java/org/alfresco/repo/quickshare/QuickShareClientNotFoundException.java b/source/java/org/alfresco/repo/client/config/ClientAppNotFoundException.java similarity index 74% rename from source/java/org/alfresco/repo/quickshare/QuickShareClientNotFoundException.java rename to source/java/org/alfresco/repo/client/config/ClientAppNotFoundException.java index 622240b670..33e1280a8a 100644 --- a/source/java/org/alfresco/repo/quickshare/QuickShareClientNotFoundException.java +++ b/source/java/org/alfresco/repo/client/config/ClientAppNotFoundException.java @@ -1,8 +1,8 @@ -/* +/*- * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2017 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -24,23 +24,23 @@ * #L% */ -package org.alfresco.repo.quickshare; +package org.alfresco.repo.client.config; import org.alfresco.error.AlfrescoRuntimeException; /** * @author Jamal Kaabi-Mofrad */ -public class QuickShareClientNotFoundException extends AlfrescoRuntimeException +public class ClientAppNotFoundException extends AlfrescoRuntimeException { - private static final long serialVersionUID = -2567136374291827783L; + private static final long serialVersionUID = 1112329540603622387L; - public QuickShareClientNotFoundException(String msgId) + public ClientAppNotFoundException(String msgId) { super(msgId); } - public QuickShareClientNotFoundException(String msgId, Object[] msgParams) + public ClientAppNotFoundException(String msgId, Object[] msgParams) { super(msgId, msgParams); } diff --git a/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java b/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java index 1711076ece..c8ba3c3b17 100644 --- a/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java +++ b/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java @@ -44,7 +44,9 @@ import org.alfresco.model.QuickShareModel; import org.alfresco.repo.Client; import org.alfresco.repo.Client.ClientType; import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.client.config.ClientAppConfig; +import org.alfresco.repo.client.config.ClientAppNotFoundException; import org.alfresco.repo.copy.CopyBehaviourCallback; import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyServicePolicies; @@ -70,7 +72,6 @@ import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction; import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedActionService; import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.preference.PreferenceService; import org.alfresco.service.cmr.quickshare.InvalidSharedIdException; import org.alfresco.service.cmr.quickshare.QuickShareDTO; import org.alfresco.service.cmr.quickshare.QuickShareDisabledException; @@ -95,10 +96,12 @@ import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.EmailHelper; import org.alfresco.util.EqualsHelper; import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; import org.alfresco.util.PropertyCheck; +import org.alfresco.util.UrlUtil; import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -107,7 +110,6 @@ import org.joda.time.Interval; import org.joda.time.PeriodType; import org.safehaus.uuid.UUID; import org.safehaus.uuid.UUIDGenerator; -import org.springframework.extensions.surf.util.I18NUtil; /** * QuickShare Service implementation. @@ -126,13 +128,14 @@ public class QuickShareServiceImpl implements QuickShareService, static final String ATTR_KEY_SHAREDIDS_ROOT = ".sharedIds"; - private static final String CONFIG_SHARED_LINK_BASE_URL = "sharedLinkBaseUrl"; private static final String FTL_SHARED_NODE_URL = "shared_node_url"; private static final String FTL_SHARED_NODE_NAME = "shared_node_name"; private static final String FTL_SENDER_MESSAGE = "sender_message"; private static final String FTL_SENDER_FIRST_NAME = "sender_first_name"; private static final String FTL_SENDER_LAST_NAME = "sender_last_name"; private static final String FTL_TEMPLATE_ASSETS_URL = "template_assets_url"; + + private static final String CONFIG_SHARED_LINK_BASE_URL = "sharedLinkBaseUrl"; private static final String DEFAULT_EMAIL_SUBJECT = "quickshare.notifier.email.subject"; private static final String EMAIL_TEMPLATE_REF ="alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl"; @@ -146,12 +149,13 @@ public class QuickShareServiceImpl implements QuickShareService, private ThumbnailService thumbnailService; private EventPublisher eventPublisher; private ActionService actionService; - private PreferenceService preferenceService; /** Component to determine which behaviours are active and which not */ private BehaviourFilter behaviourFilter; private SearchService searchService; private SiteService siteService; private AuthorityService authorityService; + private SysAdminParams sysAdminParams; + private EmailHelper emailHelper; private boolean enabled; private String defaultEmailSender; @@ -240,14 +244,6 @@ public class QuickShareServiceImpl implements QuickShareService, this.actionService = actionService; } - /** - * Set the preferenceService - */ - public void setPreferenceService(PreferenceService preferenceService) - { - this.preferenceService = preferenceService; - } - /** * Spring configuration * @@ -288,6 +284,26 @@ public class QuickShareServiceImpl implements QuickShareService, this.authorityService = authorityService; } + /** + * Spring configuration + * + * @param sysAdminParams the sysAdminParams to set + */ + public void setSysAdminParams(SysAdminParams sysAdminParams) + { + this.sysAdminParams = sysAdminParams; + } + + /** + * Spring configuration + * + * @param emailHelper the emailHelper to set + */ + public void setEmailHelper(EmailHelper emailHelper) + { + this.emailHelper = emailHelper; + } + /** * Enable or disable this service. */ @@ -357,13 +373,14 @@ public class QuickShareServiceImpl implements QuickShareService, PropertyCheck.mandatory(this, "thumbnailService", thumbnailService); PropertyCheck.mandatory(this, "eventPublisher", eventPublisher); PropertyCheck.mandatory(this, "actionService", actionService); - PropertyCheck.mandatory(this, "preferenceService", preferenceService); PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter); PropertyCheck.mandatory(this, "defaultEmailSender", defaultEmailSender); PropertyCheck.mandatory(this, "clientAppConfig", clientAppConfig); PropertyCheck.mandatory(this, "searchService", searchService); PropertyCheck.mandatory(this, "siteService", siteService); PropertyCheck.mandatory(this, "authorityService", authorityService); + PropertyCheck.mandatory(this, "sysAdminParams", sysAdminParams); + PropertyCheck.mandatory(this, "emailHelper", emailHelper); PropertyCheck.mandatory(this, "scheduledPersistedActionService", scheduledPersistedActionService); PropertyCheck.mandatory(this, "quickShareLinkExpiryActionPersister", quickShareLinkExpiryActionPersister); } @@ -901,7 +918,7 @@ public class QuickShareServiceImpl implements QuickShareService, ClientApp clientApp = clientAppConfig.getClient(emailRequest.getClient()); if (clientApp == null) { - throw new QuickShareClientNotFoundException("Client was not found [" + emailRequest.getClient() + "]"); + throw new ClientAppNotFoundException("Client was not found [" + emailRequest.getClient() + "]"); } // Set the details of the person sending the email @@ -918,6 +935,7 @@ public class QuickShareServiceImpl implements QuickShareService, templateModel.put(FTL_SENDER_LAST_NAME, senderLastName); final String sharedNodeUrl = getUrl(clientApp.getProperty(CONFIG_SHARED_LINK_BASE_URL), CONFIG_SHARED_LINK_BASE_URL) + '/' + emailRequest.getSharedId(); + templateModel.put(FTL_SHARED_NODE_URL, sharedNodeUrl); templateModel.put(FTL_SHARED_NODE_NAME, emailRequest.getSharedNodeName()); templateModel.put(FTL_SENDER_MESSAGE, emailRequest.getSenderMessage()); @@ -934,9 +952,12 @@ public class QuickShareServiceImpl implements QuickShareService, actionParams.put(MailActionExecuter.PARAM_SUBJECT_PARAMS, new Object[] { senderFirstName, senderLastName, emailRequest.getSharedNodeName() }); actionParams.put(MailActionExecuter.PARAM_IGNORE_SEND_FAILURE, emailRequest.isIgnoreSendFailure()); // Pick the template - actionParams.put(MailActionExecuter.PARAM_TEMPLATE, EMAIL_TEMPLATE_REF); + final String templatePath = emailHelper.getEmailTemplate(clientApp.getName(), getSharedLinkEmailTemplatePath(clientApp), EMAIL_TEMPLATE_REF); + actionParams.put(MailActionExecuter.PARAM_TEMPLATE, templatePath); actionParams.put(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable) templateModel); - actionParams.put(MailActionExecuter.PARAM_LOCALE, getDefaultIfNull(getEmailCreatorLocale(authenticatedUser), emailRequest.getLocale())); + actionParams.put(MailActionExecuter.PARAM_LOCALE, getDefaultIfNull( + emailHelper.getUserLocaleOrDefault(authenticatedUser), + emailRequest.getLocale())); for (String to : emailRequest.getToEmails()) { @@ -947,6 +968,11 @@ public class QuickShareServiceImpl implements QuickShareService, } } + protected String getSharedLinkEmailTemplatePath(ClientApp clientApp) + { + return clientApp.getProperty("sharedLinkTemplatePath"); + } + @Override public boolean canDeleteSharedLink(NodeRef nodeRef, String sharedByUserId) { @@ -1011,9 +1037,10 @@ public class QuickShareServiceImpl implements QuickShareService, } if (url.endsWith("/")) { - return url.substring(0, url.length() - 1); + url = url.substring(0, url.length() - 1); } - return url; + // Replace '${shareUrl} placeholder if it does exist. + return UrlUtil.replaceShareUrlPlaceholder(url, sysAdminParams); } private T getDefaultIfNull(T defaultValue, T newValue) @@ -1021,12 +1048,6 @@ public class QuickShareServiceImpl implements QuickShareService, return (newValue == null) ? defaultValue : newValue; } - private Locale getEmailCreatorLocale(String userId) - { - String localeString = (String) preferenceService.getPreference(userId, "locale"); - return I18NUtil.parseLocale(localeString); - } - /** * Represents an email request to send a quick share link. */ diff --git a/source/java/org/alfresco/util/EmailHelper.java b/source/java/org/alfresco/util/EmailHelper.java new file mode 100644 index 0000000000..efb3a91a28 --- /dev/null +++ b/source/java/org/alfresco/util/EmailHelper.java @@ -0,0 +1,253 @@ +/*- + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2017 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ + +package org.alfresco.util; + +import freemarker.cache.TemplateLoader; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.template.ClassPathRepoTemplateLoader; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.preference.PreferenceService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +/** + * A helper class to provide email template related utility functions. + * + * @author Jamal Kaabi-Mofrad + * @since 5.2.1 + */ +public class EmailHelper +{ + private static final Log LOGGER = LogFactory.getLog(EmailHelper.class); + + private ServiceRegistry serviceRegistry; + private NodeService nodeService; + private SearchService searchService; + private NamespaceService namespaceService; + private FileFolderService fileFolderService; + private PersonService personService; + private PreferenceService preferenceService; + private Repository repositoryHelper; + private TemplateLoader templateLoader; + private String companyHomeChildName; + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + public void setPreferenceService(PreferenceService preferenceService) + { + this.preferenceService = preferenceService; + } + + public void setRepositoryHelper(Repository repositoryHelper) + { + this.repositoryHelper = repositoryHelper; + } + + public void setTemplateLoader(TemplateLoader templateLoader) + { + this.templateLoader = templateLoader; + } + + public void setCompanyHomeChildName(String companyHomeChildName) + { + this.companyHomeChildName = companyHomeChildName; + } + + public void init() + { + PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry); + PropertyCheck.mandatory(this, "preferenceService", preferenceService); + PropertyCheck.mandatory(this, "repositoryHelper", repositoryHelper); + PropertyCheck.mandatory(this, "companyHomeChildName", companyHomeChildName); + + this.nodeService = serviceRegistry.getNodeService(); + this.searchService = serviceRegistry.getSearchService(); + this.namespaceService = serviceRegistry.getNamespaceService(); + this.fileFolderService = serviceRegistry.getFileFolderService(); + this.personService = serviceRegistry.getPersonService(); + // set the template loader + setTemplateLoader(new ClassPathRepoTemplateLoader(nodeService, serviceRegistry.getContentService(), "utf-8")); + } + + /** + * Gets the email template path or the given fallback template path. + * + * @param clientName optional client app name (used only for logging) + * @param emailTemplatePath the email template xpath or class path + * @param fallbackTemplatePath the fallback template + * @return
    + *
  • If {@code emailTemplatePath} is empty the fallback template is returned.
  • + *
  • if the given {@code emailTemplatePath} is an xpath (i.e. starts with app:company_home), + * then an xpath search will be performed to find the {@code NodeRef}. If no nodeRef + * is found, the fallback template is returned.
  • + *
  • If {@code emailTemplatePath} is a nodeRef and the node does not exist, the fallback + * template is returned, otherwise a string representation of the NodeRef is returned.
  • + *
  • if {@code emailTemplatePath} is a class path which results in a template being found, + * then the {@code emailTemplatePath} is returned; otherwise, the fallback template is returned.
  • + *
+ */ + public String getEmailTemplate(String clientName, String emailTemplatePath, String fallbackTemplatePath) + { + if (StringUtils.isEmpty(emailTemplatePath)) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("No email template path is set for client [" + clientName + "]. The fallback template will be used: " + + fallbackTemplatePath); + } + return fallbackTemplatePath; + } + + // Make sure that the path doesn't start with '/' + if (emailTemplatePath.startsWith("/")) + { + emailTemplatePath = emailTemplatePath.substring(1); + } + + if (emailTemplatePath.startsWith(companyHomeChildName)) + { + NodeRef nodeRef = getLocalizedEmailTemplateNodeRef(emailTemplatePath); + if (nodeRef == null) + { + LOGGER.warn("Couldn't find email template with the XPath [" + emailTemplatePath + "] for client [" + clientName + + "]. The fallback template will be used: " + fallbackTemplatePath); + + return fallbackTemplatePath; + } + return nodeRef.toString(); + } + else if (NodeRef.isNodeRef(emailTemplatePath)) + { + // Just check whether the nodeRef exists or not + NodeRef ref = new NodeRef(emailTemplatePath); + if (!nodeService.exists(ref)) + { + LOGGER.warn("Couldn't find email template with the NodeRef [" + ref + "] for client [" + clientName + + "]. The fallback template will be used: " + fallbackTemplatePath); + + return fallbackTemplatePath; + } + + // No need to return the nodeRef, as this will be handled later by the 'ClassPathRepoTemplateLoader' class + return emailTemplatePath; + } + else + { + try + { + // We just use the template loader to check whether the given + // template path is valid and the file is found + Object template = templateLoader.findTemplateSource(emailTemplatePath); + if (template != null) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Using email template with class path [" + emailTemplatePath + "] for client: " + clientName); + } + return emailTemplatePath; + } + else + { + LOGGER.warn("Couldn't find email template with class path [" + emailTemplatePath + "] for client [" + clientName + + "]. The fallback template will be used: " + fallbackTemplatePath); + } + } + catch (IOException ex) + { + LOGGER.error("Error occurred while finding the email template with the class path [" + emailTemplatePath + "] for client [" + + clientName + "]. The fallback template will be used: " + fallbackTemplatePath, ex); + + } + return fallbackTemplatePath; + } + } + + /** + * Gets the localized email template nodeRef. + * + * @param emailTemplateXPath the xpath of the template + * @return {@code NodeRef} of the localized template or null if no node is found + */ + public NodeRef getLocalizedEmailTemplateNodeRef(String emailTemplateXPath) + { + if (StringUtils.isEmpty(emailTemplateXPath)) + { + return null; + } + + List nodeRefs = searchService.selectNodes(repositoryHelper.getRootHome(), emailTemplateXPath, null, this.namespaceService, false); + if (nodeRefs.isEmpty()) + { + return null; + } + + NodeRef nodeRef = nodeRefs.get(0); + if (nodeRefs.size() > 1) + { + LOGGER.error("Found too many email templates using XPath [" + emailTemplateXPath + "]. The first element will be used: " + nodeRef); + } + + // Now localise this + return fileFolderService.getLocalizedSibling(nodeRef); + } + + /** + * Gets the user's locale. + * + * @param userId the user id + * @return the default locale or the user's preferred locale, if available + */ + public Locale getUserLocaleOrDefault(String userId) + { + if (userId != null && personService.personExists(userId)) + { + String localeString = AuthenticationUtil.runAsSystem(() -> (String) preferenceService.getPreference(userId, "locale")); + if (localeString != null) + { + return I18NUtil.parseLocale(localeString); + } + } + + return I18NUtil.getLocale(); + } +} diff --git a/source/java/org/alfresco/util/UrlUtil.java b/source/java/org/alfresco/util/UrlUtil.java index 86e4b335f4..8c5f247803 100644 --- a/source/java/org/alfresco/util/UrlUtil.java +++ b/source/java/org/alfresco/util/UrlUtil.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2017 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -28,6 +28,7 @@ package org.alfresco.util; import org.alfresco.repo.admin.SysAdminParams; +import java.util.regex.Pattern; /** * Alfresco URL related utility functions. @@ -36,6 +37,9 @@ import org.alfresco.repo.admin.SysAdminParams; */ public class UrlUtil { + // ${shareUrl} placeholder + public static final Pattern PATTERN = Pattern.compile("\\$\\{shareUrl\\}"); + /** * Builds up the Url to Alfresco based on the settings in the * {@link SysAdminParams}. @@ -65,7 +69,25 @@ public class UrlUtil sysAdminParams.getSharePort(), sysAdminParams.getShareContext()); } - + + /** + * Replaces the share url placeholder, namely {@literal ${shareUrl}}, with share url. + * + * @param value the string value which contains the share url placeholder + * @param sysAdminParams the {@code SysAdminParams} object + * @return if the given {@code value} contains share url placeholder, + * the placeholder is replaced with share url; otherwise, the given {@code value} is simply returned + */ + public static String replaceShareUrlPlaceholder(String value, SysAdminParams sysAdminParams) + { + if (value != null) + { + return PATTERN.matcher(value).replaceAll( + getShareUrl(sysAdminParams)); + } + return value; + } + protected static String buildUrl(String protocol, String host, int port, String context) { StringBuilder url = new StringBuilder(); diff --git a/source/test-java/org/alfresco/AllUnitTestsSuite.java b/source/test-java/org/alfresco/AllUnitTestsSuite.java index ae02238ba2..a42a814ced 100644 --- a/source/test-java/org/alfresco/AllUnitTestsSuite.java +++ b/source/test-java/org/alfresco/AllUnitTestsSuite.java @@ -133,5 +133,6 @@ public class AllUnitTestsSuite extends TestSuite suite.addTest(org.alfresco.traitextender.TraitExtenderUnitTestSuite.suite()); suite.addTest(org.alfresco.repo.virtual.VirtualizationUnitTestSuite.suite()); suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.security.authentication.AuthenticationServiceImplTest.class)); + suite.addTest(new JUnit4TestAdapter(org.alfresco.util.EmailHelperTest.class)); } } diff --git a/source/test-java/org/alfresco/util/EmailHelperTest.java b/source/test-java/org/alfresco/util/EmailHelperTest.java new file mode 100644 index 0000000000..10d7ee909a --- /dev/null +++ b/source/test-java/org/alfresco/util/EmailHelperTest.java @@ -0,0 +1,228 @@ +/*- + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2017 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ + +package org.alfresco.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import freemarker.cache.TemplateLoader; +import org.alfresco.repo.model.Repository; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.preference.PreferenceService; +import org.alfresco.service.cmr.repository.ContentService; +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.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.springframework.extensions.surf.util.I18NUtil; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; + +/** + * Unit tests for {@link EmailHelper} class. + * + * @author Jamal Kaabi-Mofrad + */ +public class EmailHelperTest +{ + + // this is just a path, the template does not exist! + private static final String FALLBACK_TEMPLATE_PATH = "alfresco/templates/email-templates/test-email-template.ftl"; + private static final String CLIENT_NAME = "test-client"; + + @Mock + private ServiceRegistry serviceRegistryMock; + @Mock + private NodeService nodeServiceMock; + @Mock + private SearchService searchServiceMock; + @Mock + private NamespaceService namespaceServiceMock; + @Mock + private FileFolderService fileFolderServiceMock; + @Mock + private PersonService personServiceMock; + @Mock + private PreferenceService preferenceServiceMock; + @Mock + private Repository repositoryHelperMock; + @Mock + private TemplateLoader templateLoaderMock; + + private EmailHelper emailHelper; + + private NodeRef dummyTemplateNodeRef; + + @Before + public void setup() throws Exception + { + this.dummyTemplateNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate()); + // Mock the required services + this.serviceRegistryMock = mock(ServiceRegistry.class); + this.nodeServiceMock = mock(NodeService.class); + this.searchServiceMock = mock(SearchService.class); + this.namespaceServiceMock = mock(NamespaceService.class); + this.fileFolderServiceMock = mock(FileFolderService.class); + this.personServiceMock = mock(PersonService.class); + this.preferenceServiceMock = mock(PreferenceService.class); + this.repositoryHelperMock = mock(Repository.class); + this.templateLoaderMock = mock(TemplateLoader.class); + + when(serviceRegistryMock.getNodeService()).thenReturn(nodeServiceMock); + when(serviceRegistryMock.getSearchService()).thenReturn(searchServiceMock); + when(serviceRegistryMock.getNamespaceService()).thenReturn(namespaceServiceMock); + when(serviceRegistryMock.getFileFolderService()).thenReturn(fileFolderServiceMock); + when(serviceRegistryMock.getPersonService()).thenReturn(personServiceMock); + when(serviceRegistryMock.getContentService()).thenReturn(mock(ContentService.class)); + when(repositoryHelperMock.getRootHome()).thenReturn(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate())); + when(fileFolderServiceMock.getLocalizedSibling(dummyTemplateNodeRef)).thenReturn(dummyTemplateNodeRef); + + this.emailHelper = new EmailHelper(); + emailHelper.setServiceRegistry(serviceRegistryMock); + emailHelper.setPreferenceService(preferenceServiceMock); + emailHelper.setRepositoryHelper(repositoryHelperMock); + emailHelper.setCompanyHomeChildName("app:company_home"); + //test init + emailHelper.init(); + // Note: set the template loader after the init method + emailHelper.setTemplateLoader(templateLoaderMock); + } + + @After + public void tearDown() throws Exception + { + + } + + @Test + public void testGetEmailTemplate() throws Exception + { + String result = emailHelper.getEmailTemplate(CLIENT_NAME, null, FALLBACK_TEMPLATE_PATH); + assertEquals("The given template path is null, so the fallback template should be returned.", result, FALLBACK_TEMPLATE_PATH); + + // XPath + { + final String emailTemplateXPath = "app:company_home/app:dictionary/app:email_templates/example-email.ftl"; + + when(searchServiceMock.selectNodes(repositoryHelperMock.getRootHome(), emailTemplateXPath, null, namespaceServiceMock, false)) + .thenReturn(Collections.emptyList()); + result = emailHelper.getEmailTemplate(CLIENT_NAME, emailTemplateXPath, FALLBACK_TEMPLATE_PATH); + assertEquals("Couldn't find email template with the given XPath, so the fallback template should be returned.", result, + FALLBACK_TEMPLATE_PATH); + + when(searchServiceMock.selectNodes(repositoryHelperMock.getRootHome(), emailTemplateXPath, null, namespaceServiceMock, false)) + .thenReturn(Collections.singletonList(dummyTemplateNodeRef)); + result = emailHelper.getEmailTemplate(CLIENT_NAME, emailTemplateXPath, FALLBACK_TEMPLATE_PATH); + // XPath search returned a valid result + assertEquals(result, dummyTemplateNodeRef.toString()); + + // Simulate a path that starts with '/' + String xpath = "/" + emailTemplateXPath; + result = emailHelper.getEmailTemplate(CLIENT_NAME, xpath, FALLBACK_TEMPLATE_PATH); + // XPath search returned a valid result + assertEquals(result, dummyTemplateNodeRef.toString()); + + when(searchServiceMock.selectNodes(repositoryHelperMock.getRootHome(), emailTemplateXPath, null, namespaceServiceMock, false)) + .thenReturn(Arrays.asList(dummyTemplateNodeRef, new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate()), + new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate()))); + result = emailHelper.getEmailTemplate(CLIENT_NAME, emailTemplateXPath, FALLBACK_TEMPLATE_PATH); + // XPath search returned 3 results. But after logging an error, we just return the first element + assertEquals(result, dummyTemplateNodeRef.toString()); + } + + //NodeRed + { + when(nodeServiceMock.exists(dummyTemplateNodeRef)).thenReturn(false); + result = emailHelper.getEmailTemplate(CLIENT_NAME, dummyTemplateNodeRef.toString(), FALLBACK_TEMPLATE_PATH); + assertEquals("Couldn't find email template with the given NodeRef, so the fallback template should be returned.", result, + FALLBACK_TEMPLATE_PATH); + + when(nodeServiceMock.exists(dummyTemplateNodeRef)).thenReturn(true); + result = emailHelper.getEmailTemplate(CLIENT_NAME, dummyTemplateNodeRef.toString(), FALLBACK_TEMPLATE_PATH); + // The NodeRef exists + assertEquals(result, dummyTemplateNodeRef.toString()); + } + + //class path + { + String classPathTemplate = "alfresco/templates/email-templates/new-template.html"; + when(templateLoaderMock.findTemplateSource(classPathTemplate)).thenReturn(null); + result = emailHelper.getEmailTemplate(CLIENT_NAME, classPathTemplate, FALLBACK_TEMPLATE_PATH); + assertEquals("Couldn't find email template with the given class path, so the fallback template should be returned.", result, + FALLBACK_TEMPLATE_PATH); + + when(templateLoaderMock.findTemplateSource(classPathTemplate)).thenReturn(new Object()); + result = emailHelper.getEmailTemplate(CLIENT_NAME, classPathTemplate, FALLBACK_TEMPLATE_PATH); + // The class path is valid and a template is found + assertEquals(result, classPathTemplate); + + when(templateLoaderMock.findTemplateSource(classPathTemplate)).thenThrow(new IOException()); + result = emailHelper.getEmailTemplate(CLIENT_NAME, classPathTemplate, FALLBACK_TEMPLATE_PATH); + assertEquals("Error occurred while finding the email template with the class path, so the fallback template should be returned.", result, + FALLBACK_TEMPLATE_PATH); + } + + // test null/empty value + NodeRef nodeRef = emailHelper.getLocalizedEmailTemplateNodeRef(null); + assertNull(nodeRef); + nodeRef = emailHelper.getLocalizedEmailTemplateNodeRef(""); + assertNull(nodeRef); + } + + @Test + public void testGetUserLocaleOrDefault() throws Exception + { + String userId = "testUser"; + + Locale locale = emailHelper.getUserLocaleOrDefault(null); + assertEquals(I18NUtil.getLocale(), locale); + + when(personServiceMock.personExists(userId)).thenReturn(false); + locale = emailHelper.getUserLocaleOrDefault(userId); + assertEquals(I18NUtil.getLocale(), locale); + + when(personServiceMock.personExists(userId)).thenReturn(true); + when(preferenceServiceMock.getPreference(userId, "locale")).thenReturn(null); + locale = emailHelper.getUserLocaleOrDefault(userId); + assertEquals(I18NUtil.getLocale(), locale); + + when(preferenceServiceMock.getPreference(userId, "locale")).thenReturn("fr-FR"); + locale = emailHelper.getUserLocaleOrDefault(userId); + assertEquals(Locale.FRANCE, locale); + } +}