From dbc6fa1abf72b4aef4941ddf3470cb70a2b99302 Mon Sep 17 00:00:00 2001 From: Kevin Roast Date: Fri, 25 Aug 2006 13:30:35 +0000 Subject: [PATCH] . Email Space Users functionality - New action "Email Space Users" available on the details page for a space - Users/Groups explicity invited to the space are shown for emailing - Email Space Users UI with new JSF component for hiearchical selection of users/groups - Template based email to selected users/groups . Refactored beans using template based email into a helper bean reusable by other classes . Renamed the old template based Dashboard View (on doc/space details screens) to Custom View git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@3610 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/messages/webclient.properties | 13 +- config/alfresco/web-client-config-actions.xml | 12 + config/alfresco/web-client-config-dialogs.xml | 11 +- .../web/bean/TemplateMailHelperBean.java | 268 ++++++++++ .../web/bean/users/EmailSpaceUsersDialog.java | 497 ++++++++++++++++++ .../web/bean/users/UserMembersBean.java | 58 +- .../web/bean/wizard/InviteUsersWizard.java | 215 +------- .../ui/repo/component/UIUserGroupPicker.java | 240 +++++++++ .../web/ui/repo/tag/UserGroupPickerTag.java | 90 ++++ source/web/WEB-INF/faces-config-beans.xml | 35 ++ source/web/WEB-INF/faces-config-repo.xml | 5 + source/web/WEB-INF/repo.tld | 54 ++ source/web/css/main.css | 9 + source/web/images/icons/email_users.gif | Bin 0 -> 590 bytes source/web/images/icons/email_users_large.gif | Bin 0 -> 1457 bytes source/web/jsp/browse/browse.jsp | 2 +- source/web/jsp/browse/dashboard.jsp | 4 +- source/web/jsp/dialog/apply-doc-template.jsp | 2 +- .../web/jsp/dialog/apply-space-template.jsp | 2 +- source/web/jsp/dialog/document-details.jsp | 4 +- source/web/jsp/dialog/space-details.jsp | 4 +- source/web/jsp/users/email-space-users.jsp | 74 +++ .../wizard/invite-content-users/notify.jsp | 12 +- source/web/jsp/wizard/invite-users/notify.jsp | 12 +- 24 files changed, 1379 insertions(+), 244 deletions(-) create mode 100644 source/java/org/alfresco/web/bean/TemplateMailHelperBean.java create mode 100644 source/java/org/alfresco/web/bean/users/EmailSpaceUsersDialog.java create mode 100644 source/java/org/alfresco/web/ui/repo/component/UIUserGroupPicker.java create mode 100644 source/java/org/alfresco/web/ui/repo/tag/UserGroupPickerTag.java create mode 100644 source/web/images/icons/email_users.gif create mode 100644 source/web/images/icons/email_users_large.gif create mode 100644 source/web/jsp/users/email-space-users.jsp diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties index 4945dd8b9a..fa92b5fa81 100644 --- a/config/alfresco/messages/webclient.properties +++ b/config/alfresco/messages/webclient.properties @@ -514,8 +514,7 @@ modify_props_of=Modify Properties of modify_space_properties=Modify Space Properties modify_content_properties=Modify Content Properties preview=Preview in Template -dashboard_view=Dashboard View -dashboard=Dashboard +custom_view=Custom View view_links=Links not_inline_editable=This document is not inline editable. allow_inline_editing=Allow Inline Editing @@ -546,9 +545,9 @@ success_unlock=Successfully unlocked the document. inherit_permissions=Inherit Parent Space Permissions success_inherit_permissions=Successfully changed Inherit Parent Permissions to 'Yes' success_not_inherit_permissions=Successfully changed Inherit Parent Permissions to 'No' -apply_dashboard=Apply Dashboard -apply_dashboard_info=Select a template to be applied to the Space as a Dashboard view. -apply_dashboard_doc_info=Select a template to be applied to the Document as a Dashboard view. +apply_template=Apply Template +apply_dashboard_info=Select a template to be applied to the Space as a Custom view. +apply_dashboard_doc_info=Select a template to be applied to the Document as a Custom view. apply_rss_feed=Apply RSS Feed Template apply_rss_feed_info=Select a template to be applied to the Space as an RSS feed. apply_rss_feed_warning1=This Space must be visible to the Guest user for the RSS feed to be publically viewable, you can Invite the Guest user using the @@ -925,6 +924,10 @@ delete_op_files=Only the files within this space. delete_op_folders=Only the folders within this space. delete_op_contents=Files and folders within this space. +# Email users dialog +email_space_users=Email Space users +email_space_users_desc=Send an email to the users and groups assigned to this space. + # Workflow messages start_workflow=Start Workflow start_workflow_wizard=Start New Workflow Wizard diff --git a/config/alfresco/web-client-config-actions.xml b/config/alfresco/web-client-config-actions.xml index c6a6467ef7..1484cbaa8a 100644 --- a/config/alfresco/web-client-config-actions.xml +++ b/config/alfresco/web-client-config-actions.xml @@ -372,6 +372,17 @@ + + + email_space_users + /images/icons/email_users.gif + dialog:emailSpaceUsers + #{BrowseBean.setupSpaceAction} + + #{actionContext.id} + + + org.alfresco.web.action.evaluator.ShortcutNodeEvaluator @@ -601,6 +612,7 @@ + diff --git a/config/alfresco/web-client-config-dialogs.xml b/config/alfresco/web-client-config-dialogs.xml index 14e25ad8a8..2768dd5126 100644 --- a/config/alfresco/web-client-config-dialogs.xml +++ b/config/alfresco/web-client-config-dialogs.xml @@ -80,7 +80,16 @@ + description-id="create_topic_description" error-message-id="error_create_topic_dialog" /> + + + + + + + diff --git a/source/java/org/alfresco/web/bean/TemplateMailHelperBean.java b/source/java/org/alfresco/web/bean/TemplateMailHelperBean.java new file mode 100644 index 0000000000..6bc9834053 --- /dev/null +++ b/source/java/org/alfresco/web/bean/TemplateMailHelperBean.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.web.bean; + +import java.text.MessageFormat; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ContentReader; +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.TemplateNode; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.repo.component.template.DefaultModelHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.mail.javamail.MimeMessagePreparator; + +/** + * @author Kevin Roast + */ +public class TemplateMailHelperBean +{ + private static Log logger = LogFactory.getLog(TemplateMailHelperBean.class); + + /** JavaMailSender bean reference */ + protected JavaMailSender mailSender; + + /** NodeService bean reference */ + protected NodeService nodeService; + + /** dialog state */ + private String subject = null; + private String body = null; + private String automaticText = null; + private String template = null; + private String usingTemplate = null; + private String finalBody; + + /** + * @param mailSender The JavaMailSender to set. + */ + public void setMailSender(JavaMailSender mailSender) + { + this.mailSender = mailSender; + } + + /** + * @param nodeService The nodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Initialises the bean + */ + public TemplateMailHelperBean() + { + subject = ""; + body = ""; + automaticText = ""; + template = null; + usingTemplate = null; + } + + /** + * Send an email notification to the specified User authority + * + * @param person Person node representing the user + * @param node Node they are invited too + * @param from From text message + * @param roleText The role display label for the user invite notification + */ + public void notifyUser(NodeRef person, NodeRef node, final String from, String roleText) + { + final String to = (String)this.nodeService.getProperty(person, ContentModel.PROP_EMAIL); + + if (to != null && to.length() != 0) + { + String body = this.body; + if (this.usingTemplate != null) + { + FacesContext fc = FacesContext.getCurrentInstance(); + + // use template service to format the email + NodeRef templateRef = new NodeRef(Repository.getStoreRef(), this.usingTemplate); + ServiceRegistry services = Repository.getServiceRegistry(fc); + Map model = DefaultModelHelper.buildDefaultModel( + services, Application.getCurrentUser(fc), templateRef); + model.put("role", roleText); + model.put("space", new TemplateNode(node, Repository.getServiceRegistry(fc), null)); + + body = services.getTemplateService().processTemplate("freemarker", templateRef.toString(), model); + } + this.finalBody = body; + + MimeMessagePreparator mailPreparer = new MimeMessagePreparator() + { + public void prepare(MimeMessage mimeMessage) throws MessagingException + { + MimeMessageHelper message = new MimeMessageHelper(mimeMessage); + message.setTo(to); + message.setSubject(subject); + message.setText(finalBody); + message.setFrom(from); + } + }; + + if (logger.isDebugEnabled()) + logger.debug("Sending notification email to: " + to + "\n...with subject:\n" + subject + "\n...with body:\n" + body); + + try + { + // Send the message + this.mailSender.send(mailPreparer); + } + catch (Throwable e) + { + // don't stop the action but let admins know email is not getting sent + logger.error("Failed to send email to " + to, e); + } + } + } + + /** + * Action handler called to insert a template as the email body + */ + public void insertTemplate(ActionEvent event) + { + if (this.template != null && this.template.equals(TemplateSupportBean.NO_SELECTION) == false) + { + // get the content of the template so the user can get a basic preview of it + try + { + NodeRef templateRef = new NodeRef(Repository.getStoreRef(), this.template); + ContentService cs = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getContentService(); + ContentReader reader = cs.getReader(templateRef, ContentModel.PROP_CONTENT); + if (reader != null && reader.exists()) + { + this.body = reader.getContentString(); + + this.usingTemplate = this.template; + } + } + catch (Throwable err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + } + } + } + + /** + * Action handler called to discard the template from the email body + */ + public void discardTemplate(ActionEvent event) + { + this.body = this.automaticText; + usingTemplate = null; + } + + /** + * @return Returns the email body text. + */ + public String getBody() + { + return this.body; + } + + /** + * @param body The email body text to set. + */ + public void setBody(String body) + { + this.body = body; + } + + /** + * @return Returns the email subject text. + */ + public String getSubject() + { + return this.subject; + } + + /** + * @param subject The email subject text to set. + */ + public void setSubject(String subject) + { + this.subject = subject; + } + + /** + * @return Returns the automatic text. + */ + public String getAutomaticText() + { + return this.automaticText; + } + + /** + * @param automaticText The automatic text to set. + */ + public void setAutomaticText(String automaticText) + { + this.automaticText = automaticText; + } + + /** + * @return Returns the email template Id + */ + public String getTemplate() + { + return this.template; + } + + /** + * @param template The email template to set. + */ + public void setTemplate(String template) + { + this.template = template; + } + + /** + * @return Returns if a template has been inserted by a user for email body. + */ + public String getUsingTemplate() + { + return this.usingTemplate; + } + + /** + * @param usingTemplate Template that has been inserted by a user for the email body. + */ + public void setUsingTemplate(String usingTemplate) + { + this.usingTemplate = usingTemplate; + } +} diff --git a/source/java/org/alfresco/web/bean/users/EmailSpaceUsersDialog.java b/source/java/org/alfresco/web/bean/users/EmailSpaceUsersDialog.java new file mode 100644 index 0000000000..53eb16465f --- /dev/null +++ b/source/java/org/alfresco/web/bean/users/EmailSpaceUsersDialog.java @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.web.bean.users; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.IContextListener; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.TemplateMailHelperBean; +import org.alfresco.web.bean.dialog.BaseDialogBean; +import org.alfresco.web.bean.repository.MapNode; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.repo.WebResources; +import org.alfresco.web.ui.repo.component.UIUserGroupPicker; +import org.alfresco.web.ui.repo.component.UIUserGroupPicker.PickerEvent; +import org.springframework.mail.javamail.JavaMailSender; + +/** + * Dialog bean managing the state for the Email Space Users page. Calculates the user/groups + * that are invited to a space and builds the data structures needed to display and modify + * the list in the web-client UI. Notifies the selected user/groups with a templatable email. + * + * @author Kevin Roast + */ +public class EmailSpaceUsersDialog extends BaseDialogBean implements IContextListener +{ + private static final String PROP_DUPLICATE = "duplicate"; + private static final String PROP_PARENT = "parent"; + private static final String PROP_ID = "id"; + private static final String PROP_ISGROUP = "isGroup"; + private static final String PROP_ICON = "icon"; + private static final String PROP_FULLNAME = "fullName"; + private static final String PROP_ROLES = "roles"; + private static final String PROP_EXPANDED = "expanded"; + private static final String PROP_SELECTED = "selected"; + private static final String PROP_USERNAME = "userName"; + + /** Injected Bean references */ + protected PermissionService permissionService; + protected PersonService personService; + protected AuthorityService authorityService; + protected JavaMailSender mailSender; + + /** Helper providing template based mailing facilities */ + protected TemplateMailHelperBean mailHelper; + + /** List of user/group property map/node instances */ + private List usersGroups = null; + + /** Quick lookup table of authority to user/group instance */ + private Map userGroupLookup = new HashMap(); + + + /** + * Default constructor + */ + public EmailSpaceUsersDialog() + { + UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this); + } + + /** + * Setup the dialog + */ + public void init(Map parameters) + { + super.init(parameters); + + mailHelper = new TemplateMailHelperBean(); + mailHelper.setMailSender(mailSender); + mailHelper.setNodeService(nodeService); + } + + /** + * @see org.alfresco.web.bean.dialog.BaseDialogBean#finishImpl(javax.faces.context.FacesContext, java.lang.String) + */ + @Override + protected String finishImpl(FacesContext context, String outcome) throws Exception + { + // get the space ref this mail applies to + NodeRef spaceRef = getSpace().getNodeRef(); + + // calculate the 'from' email address + User user = Application.getCurrentUser(context); + String from = (String)this.nodeService.getProperty(user.getPerson(), ContentModel.PROP_EMAIL); + if (from == null || from.length() == 0) + { + // if the user does not have an email address get the default one from the config service + from = Application.getClientConfig(context).getFromEmailAddress(); + } + + Set mailedAuthorities = new HashSet(usersGroups.size()); + + // walk the list of users/groups to notify + for (Map node : usersGroups) + { + String authority = (String)node.get(PROP_USERNAME); + boolean selected = (Boolean)node.get(PROP_SELECTED); + + // if User, email then, else if Group get all members and email them + AuthorityType authType = AuthorityType.getAuthorityType(authority); + if (authType.equals(AuthorityType.USER)) + { + if (selected == true && this.personService.personExists(authority)) + { + if (mailedAuthorities.contains(authority) == false) + { + this.mailHelper.notifyUser( + this.personService.getPerson(authority), spaceRef, from, (String)node.get(PROP_ROLES)); + mailedAuthorities.add(authority); + } + } + } + else if (authType.equals(AuthorityType.GROUP)) + { + // is the group expanded? if so we'll deal with the child authorities instead + boolean expanded = (Boolean)node.get(PROP_EXPANDED); + if (expanded == false && selected == true) + { + // notify all members of the group + Set users = this.authorityService.getContainedAuthorities(AuthorityType.USER, authority, false); + for (String userAuth : users) + { + if (this.personService.personExists(userAuth) == true) + { + if (mailedAuthorities.contains(userAuth) == false) + { + this.mailHelper.notifyUser( + this.personService.getPerson(userAuth), spaceRef, from, (String)node.get(PROP_ROLES)); + mailedAuthorities.add(userAuth); + } + } + } + } + } + } + + return outcome; + } + + + // ------------------------------------------------------------------------------ + // IContextListener implementation + + /** + * @see org.alfresco.web.app.context.IContextListener#contextUpdated() + */ + public void contextUpdated() + { + this.usersGroups = null; + this.userGroupLookup = new HashMap(); + } + + + // ------------------------------------------------------------------------------ + // Bean Getters and Setters + + /** + * @param permissionService The PermissionService to set + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * @param permissionService The PersonService to set + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * @param mailSender The JavaMailSender to set. + */ + public void setMailSender(JavaMailSender mailSender) + { + this.mailSender = mailSender; + } + + /** + * @param authorityService The AuthorityService to set. + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + /** + * @return The space to email users for + */ + public Node getSpace() + { + return this.browseBean.getActionSpace(); + } + + /** + * Return the List of objects representing the Users and Groups invited to this space. + * The picker is then responsible for rendering a view to represent those users and groups + * which allows the users to select and deselect users and groups, also to expand groups + * to show sub-groups and users. + * + * @return List of Map objects representing the users/groups assigned to the current space + */ + public List getUsersGroups() + { + if (this.usersGroups == null) + { + FacesContext context = FacesContext.getCurrentInstance(); + + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(context, true); + tx.begin(); + + // Return all the permissions set against the current node + // for any authentication instance (user/group). + // Then combine them into a single list for each authentication found. + Map> permissionMap = new HashMap>(8, 1.0f); + Set permissions = permissionService.getAllSetPermissions(getSpace().getNodeRef()); + for (AccessPermission permission : permissions) + { + // we are only interested in Allow and not groups/owner etc. + if (permission.getAccessStatus() == AccessStatus.ALLOWED && + (permission.getAuthorityType() == AuthorityType.USER || + permission.getAuthorityType() == AuthorityType.GROUP || + permission.getAuthorityType() == AuthorityType.GUEST || + permission.getAuthorityType() == AuthorityType.EVERYONE)) + { + String authority = permission.getAuthority(); + + List userPermissions = permissionMap.get(authority); + if (userPermissions == null) + { + // create for first time + userPermissions = new ArrayList(4); + permissionMap.put(authority, userPermissions); + } + // add the permission name for this authority + userPermissions.add(permission.getPermission()); + } + } + + // create the structure as a linked list for fast insert/removal of items + this.usersGroups = new LinkedList(); + + // for each authentication (username/group key) found we get the Person + // node represented by it and use that for our list databinding object + for (String authority : permissionMap.keySet()) + { + Map node = buildAuthorityMap(authority, UserMembersBean.roleListToString(context, permissionMap.get(authority))); + if (node != null) + { + this.usersGroups.add(node); + } + } + + // commit the transaction + tx.commit(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + context, Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}) ); + this.usersGroups = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (Throwable err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + context, Repository.ERROR_GENERIC), err.getMessage()), err ); + this.usersGroups = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + } + return this.usersGroups; + } + + /** + * Build a Map representing a user/group with a set of useful property values required + * by the UIUserGroupPicker UI component. + * + * @param authority User/Group authority + * @param roles Role text for the authority + * + * @return Map + */ + private Map buildAuthorityMap(String authority, String roles) + { + Map node = null; + + if (AuthorityType.getAuthorityType(authority) == AuthorityType.GUEST || + this.personService.personExists(authority)) + { + NodeRef nodeRef = this.personService.getPerson(authority); + if (nodeRef != null) + { + // create our Node representation + node = new MapNode(nodeRef); + + // set data binding properties + // this will also force initialisation of the props now during the UserTransaction + // it is much better for performance to do this now rather than during page bind + Map props = ((MapNode)node).getProperties(); + props.put(PROP_FULLNAME, ((String)props.get("firstName")) + ' ' + ((String)props.get("lastName"))); + props.put(PROP_ICON, WebResources.IMAGE_PERSON); + props.put(PROP_ISGROUP, false); + } + } + else if (AuthorityType.getAuthorityType(authority) == AuthorityType.GROUP) + { + // need a map (dummy node) to represent props for this Group Authority + node = new HashMap(8, 1.0f); + if (authority.startsWith(PermissionService.GROUP_PREFIX) == true) + { + node.put(PROP_FULLNAME, authority.substring(PermissionService.GROUP_PREFIX.length())); + } + else + { + node.put(PROP_FULLNAME, authority); + } + node.put(PROP_USERNAME, authority); + node.put(PROP_ID, authority); + node.put(PROP_ICON, WebResources.IMAGE_GROUP); + node.put(PROP_ISGROUP, true); + node.put(PROP_EXPANDED, false); + } + if (node != null) + { + // add the common properties + node.put(PROP_ROLES, roles); + node.put(PROP_PARENT, null); + + if (this.userGroupLookup.get(authority) != null) + { + // this authority already exists in the list somewhere else - mark as duplicate + node.put(PROP_DUPLICATE, true); + node.put(PROP_SELECTED, false); + } + else + { + // add to table for the first time, not a duplicate + this.userGroupLookup.put(authority, node); + node.put(PROP_DUPLICATE, false); + node.put(PROP_SELECTED, true); + } + } + + return node; + } + + /** + * @return TemplateMailHelperBean instance for this wizard + */ + public TemplateMailHelperBean getMailHelper() + { + return this.mailHelper; + } + + + // ------------------------------------------------------------------------------ + // Action Event Listeners + + /** + * Action handler for a user/group selector event + */ + public void userGroupSelectorAction(ActionEvent event) + { + if (event instanceof PickerEvent) + { + PickerEvent pickerEvent = (PickerEvent)event; + + // find the user/group this event represents + Map userGroup = null; + int index = 0; + for (; index authorities = authorityService.getContainedAuthorities( + null, pickerEvent.Authority, true); + for (String authority : authorities) + { + Map node = buildAuthorityMap(authority, (String)userGroup.get(PROP_ROLES)); + if (node != null) + { + node.put(PROP_PARENT, userGroup); + node.put(PROP_SELECTED, selected); + this.usersGroups.add(++index, node); + } + } + } + else + { + // remove the children for the group + for (index++; index> permissionMap = new HashMap>(13, 1.0f); + Map> permissionMap = new HashMap>(8, 1.0f); Set permissions = permissionService.getAllSetPermissions(getNode().getNodeRef()); - if (permissions != null) + for (AccessPermission permission : permissions) { - for (AccessPermission permission : permissions) + // we are only interested in Allow and not groups/owner etc. + if (permission.getAccessStatus() == AccessStatus.ALLOWED && + (permission.getAuthorityType() == AuthorityType.USER || + permission.getAuthorityType() == AuthorityType.GROUP || + permission.getAuthorityType() == AuthorityType.GUEST || + permission.getAuthorityType() == AuthorityType.EVERYONE)) { - // we are only interested in Allow and not groups/owner etc. - if (permission.getAccessStatus() == AccessStatus.ALLOWED && - (permission.getAuthorityType() == AuthorityType.USER || - permission.getAuthorityType() == AuthorityType.GROUP || - permission.getAuthorityType() == AuthorityType.GUEST || - permission.getAuthorityType() == AuthorityType.EVERYONE)) + String authority = permission.getAuthority(); + + List userPermissions = permissionMap.get(authority); + if (userPermissions == null) { - String authority = permission.getAuthority(); - - List userPermissions = permissionMap.get(authority); - if (userPermissions == null) - { - // create for first time - userPermissions = new ArrayList(4); - permissionMap.put(authority, userPermissions); - } - // add the permission name for this authority - userPermissions.add(permission.getPermission()); + // create for first time + userPermissions = new ArrayList(4); + permissionMap.put(authority, userPermissions); } + // add the permission name for this authority + userPermissions.add(permission.getPermission()); } } - // for each authentication (username key) found we get the Person + // for each authentication (username/group key) found we get the Person // node represented by it and use that for our list databinding object personNodes = new ArrayList(permissionMap.size()); for (String authority : permissionMap.keySet()) @@ -337,7 +334,7 @@ public abstract class UserMembersBean implements IContextListener // it is much better for performance to do this now rather than during page bind Map props = node.getProperties(); props.put("fullName", ((String)props.get("firstName")) + ' ' + ((String)props.get("lastName"))); - props.put("roles", listToString(context, permissionMap.get(authority))); + props.put("roles", roleListToString(context, permissionMap.get(authority))); props.put("icon", WebResources.IMAGE_PERSON); personNodes.add(node); @@ -357,7 +354,7 @@ public abstract class UserMembersBean implements IContextListener } node.put("userName", authority); node.put("id", authority); - node.put("roles", listToString(context, permissionMap.get(authority))); + node.put("roles", roleListToString(context, permissionMap.get(authority))); node.put("icon", WebResources.IMAGE_GROUP); personNodes.add(node); } @@ -369,7 +366,7 @@ public abstract class UserMembersBean implements IContextListener catch (InvalidNodeRefException refErr) { Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - context, Repository.ERROR_NODEREF), new Object[] {"root"}) ); + context, Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}) ); personNodes = Collections.emptyList(); try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} } @@ -384,7 +381,16 @@ public abstract class UserMembersBean implements IContextListener return personNodes; } - private static String listToString(FacesContext context, List list) + /** + * Convert a list of user Roles to a comma separated string list. Each individual role + * will be looked up in message bundle to convert to a human readable string value. + * + * @param context FacesContext + * @param list List of Role names + * + * @return Comma separated string of human readable roles + */ + public static String roleListToString(FacesContext context, List list) { StringBuilder buf = new StringBuilder(); diff --git a/source/java/org/alfresco/web/bean/wizard/InviteUsersWizard.java b/source/java/org/alfresco/web/bean/wizard/InviteUsersWizard.java index df0afdfb48..2a150fc77a 100644 --- a/source/java/org/alfresco/web/bean/wizard/InviteUsersWizard.java +++ b/source/java/org/alfresco/web/bean/wizard/InviteUsersWizard.java @@ -20,7 +20,6 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.ResourceBundle; import java.util.Set; @@ -30,16 +29,10 @@ import javax.faces.event.ActionEvent; import javax.faces.model.DataModel; import javax.faces.model.ListDataModel; import javax.faces.model.SelectItem; -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; import javax.transaction.UserTransaction; import org.alfresco.model.ContentModel; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.TemplateNode; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PermissionService; @@ -47,20 +40,16 @@ import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.web.app.Application; import org.alfresco.web.app.context.UIContextService; -import org.alfresco.web.bean.TemplateSupportBean; +import org.alfresco.web.bean.TemplateMailHelperBean; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.Repository; import org.alfresco.web.bean.repository.User; import org.alfresco.web.ui.common.SortableSelectItem; import org.alfresco.web.ui.common.Utils; import org.alfresco.web.ui.common.component.UIGenericPicker; -import org.alfresco.web.ui.repo.component.template.DefaultModelHelper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; -import org.springframework.mail.javamail.MimeMessagePreparator; /** * @author Kevin Roast @@ -96,6 +85,9 @@ public abstract class InviteUsersWizard extends AbstractWizardBean /** personService bean reference */ protected PersonService personService; + /** Helper providing template based mailing facilities */ + protected TemplateMailHelperBean mailHelper; + /** datamodel for table of roles for users */ private DataModel userRolesDataModel = null; @@ -104,12 +96,6 @@ public abstract class InviteUsersWizard extends AbstractWizardBean /** dialog state */ private String notify = NOTIFY_YES; - private String subject = null; - private String body = null; - private String automaticText = null; - private String template = null; - private String usingTemplate = null; - private String finalBody; /** * @return a cached list of available permissions for the type being dealt with @@ -175,11 +161,9 @@ public abstract class InviteUsersWizard extends AbstractWizardBean notify = NOTIFY_YES; userGroupRoles = new ArrayList(8); - subject = ""; - body = ""; - automaticText = ""; - template = null; - usingTemplate = null; + mailHelper = new TemplateMailHelperBean(); + mailHelper.setMailSender(mailSender); + mailHelper.setNodeService(nodeService); } /** @@ -239,7 +223,8 @@ public abstract class InviteUsersWizard extends AbstractWizardBean { if (this.personService.personExists(authority) == true) { - notifyUser(this.personService.getPerson(authority), nodeRef, from, userGroupRole.getRole()); + this.mailHelper.notifyUser( + this.personService.getPerson(authority), nodeRef, from, userGroupRole.getRole()); } } else if (authType.equals(AuthorityType.GROUP)) @@ -250,7 +235,8 @@ public abstract class InviteUsersWizard extends AbstractWizardBean { if (this.personService.personExists(userAuth) == true) { - notifyUser(this.personService.getPerson(userAuth), nodeRef, from, userGroupRole.getRole()); + this.mailHelper.notifyUser( + this.personService.getPerson(userAuth), nodeRef, from, userGroupRole.getRole()); } } } @@ -274,65 +260,6 @@ public abstract class InviteUsersWizard extends AbstractWizardBean return outcome; } - /** - * Send an email notification to the specified User authority - * - * @param person Person node representing the user - * @param node Node they are invited too - * @param from From text message - * @param roleText The role display label for the user invite notification - */ - private void notifyUser(NodeRef person, NodeRef node, final String from, String roleText) - { - final String to = (String)this.nodeService.getProperty(person, ContentModel.PROP_EMAIL); - - if (to != null && to.length() != 0) - { - String body = this.body; - if (this.usingTemplate != null) - { - FacesContext fc = FacesContext.getCurrentInstance(); - - // use template service to format the email - NodeRef templateRef = new NodeRef(Repository.getStoreRef(), this.usingTemplate); - ServiceRegistry services = Repository.getServiceRegistry(fc); - Map model = DefaultModelHelper.buildDefaultModel( - services, Application.getCurrentUser(fc), templateRef); - model.put("role", roleText); - model.put("space", new TemplateNode(node, Repository.getServiceRegistry(fc), null)); - - body = services.getTemplateService().processTemplate("freemarker", templateRef.toString(), model); - } - this.finalBody = body; - - MimeMessagePreparator mailPreparer = new MimeMessagePreparator() - { - public void prepare(MimeMessage mimeMessage) throws MessagingException - { - MimeMessageHelper message = new MimeMessageHelper(mimeMessage); - message.setTo(to); - message.setSubject(subject); - message.setText(finalBody); - message.setFrom(from); - } - }; - - if (logger.isDebugEnabled()) - logger.debug("Sending notification email to: " + to + "\n...with subject:\n" + subject + "\n...with body:\n" + body); - - try - { - // Send the message - this.mailSender.send(mailPreparer); - } - catch (Throwable e) - { - // don't stop the action but let admins know email is not getting sent - logger.error("Failed to send email to " + to, e); - } - } - } - /** * Returns the properties for current user-roles JSF DataModel * @@ -552,43 +479,6 @@ public abstract class InviteUsersWizard extends AbstractWizardBean return roles; } - /** - * Action handler called to insert a template as the email body - */ - public void insertTemplate(ActionEvent event) - { - if (this.template != null && this.template.equals(TemplateSupportBean.NO_SELECTION) == false) - { - // get the content of the template so the user can get a basic preview of it - try - { - NodeRef templateRef = new NodeRef(Repository.getStoreRef(), this.template); - ContentService cs = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getContentService(); - ContentReader reader = cs.getReader(templateRef, ContentModel.PROP_CONTENT); - if (reader != null && reader.exists()) - { - this.body = reader.getContentString(); - - this.usingTemplate = this.template; - } - } - catch (Throwable err) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); - } - } - } - - /** - * Action handler called to discard the template from the email body - */ - public void discardTemplate(ActionEvent event) - { - this.body = this.automaticText; - usingTemplate = null; - } - /** * @return Returns the notify listbox selection. */ @@ -605,70 +495,6 @@ public abstract class InviteUsersWizard extends AbstractWizardBean this.notify = notify; } - /** - * @return Returns the email body text. - */ - public String getBody() - { - return this.body; - } - - /** - * @param body The email body text to set. - */ - public void setBody(String body) - { - this.body = body; - } - - /** - * @return Returns the email subject text. - */ - public String getSubject() - { - return this.subject; - } - - /** - * @param subject The email subject text to set. - */ - public void setSubject(String subject) - { - this.subject = subject; - } - - /** - * @return Returns the email template Id - */ - public String getTemplate() - { - return this.template; - } - - /** - * @param template The email template to set. - */ - public void setTemplate(String template) - { - this.template = template; - } - - /** - * @return Returns if a template has been inserted by a user for email body. - */ - public String getUsingTemplate() - { - return this.usingTemplate; - } - - /** - * @param usingTemplate Template that has been inserted by a user for the email body. - */ - public void setUsingTemplate(String usingTemplate) - { - this.usingTemplate = usingTemplate; - } - /** * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepDescription() */ @@ -771,7 +597,7 @@ public abstract class InviteUsersWizard extends AbstractWizardBean personName}) ); // default the subject line to an informative message - this.subject = buf.toString(); + this.mailHelper.setSubject(buf.toString()); // add the rest of the automatic body text buf.append("\r\n\r\n"); @@ -790,10 +616,9 @@ public abstract class InviteUsersWizard extends AbstractWizardBean buf.append(roleText); - this.automaticText = buf.toString(); - - // default the body content to this text - this.body = this.automaticText; + // set the body content and default text to this text + this.mailHelper.setAutomaticText(buf.toString()); + this.mailHelper.setBody(this.mailHelper.getAutomaticText()); } return outcome; @@ -825,7 +650,15 @@ public abstract class InviteUsersWizard extends AbstractWizardBean } return outcome; - } + } + + /** + * @return TemplateMailHelperBean instance for this wizard + */ + public TemplateMailHelperBean getMailHelper() + { + return this.mailHelper; + } /** * Simple wrapper class to represent a user/group and a role combination diff --git a/source/java/org/alfresco/web/ui/repo/component/UIUserGroupPicker.java b/source/java/org/alfresco/web/ui/repo/component/UIUserGroupPicker.java new file mode 100644 index 0000000000..fd99017deb --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/component/UIUserGroupPicker.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.web.ui.repo.component; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.faces.component.NamingContainer; +import javax.faces.component.UICommand; +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.event.ActionEvent; + +import org.alfresco.web.app.Application; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.WebResources; + +/** + * Seld rendering component tied to the EmailSpaceUsersDialog bean. Renders a hierarchy of + * user/group authorities. Each authority can be (de)selected and groups can be expanded/collapsed + * to display and select from the child authorities in the group. Nested groups are supported. + * + * @author Kevin Roast + */ +public class UIUserGroupPicker extends UICommand +{ + /** action ids */ + public final static int ACTION_NONE = -1; + public final static int ACTION_EXPANDCOLLAPSE = 0; + public final static int ACTION_SELECT = 1; + + private static String SELECTED_AUTHORITY = "_check"; + + + // ------------------------------------------------------------------------------ + // Component implementation + + /** + * Default constructor + */ + public UIUserGroupPicker() + { + setRendererType(null); + } + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.UserGroupPicker"; + } + + /** + * @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext) + */ + public void decode(FacesContext context) + { + Map requestMap = context.getExternalContext().getRequestParameterMap(); + Map valuesMap = context.getExternalContext().getRequestParameterValuesMap(); + String fieldId = getHiddenFieldName(context); + String value = (String)requestMap.get(fieldId); + + if (value != null && value.length() != 0) + { + // decode the values - we are expecting an action identifier and an authority name + int sepIndex = value.indexOf(NamingContainer.SEPARATOR_CHAR); + int action = Integer.parseInt(value.substring(0, sepIndex)); + String authority = value.substring(sepIndex + 1); + + // queue an event + PickerEvent event = new PickerEvent(this, action, authority); + queueEvent(event); + } + } + + /** + * @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeBegin(FacesContext context) throws IOException + { + if (isRendered() == false) + { + return; + } + + ResponseWriter out = context.getResponseWriter(); + + ResourceBundle bundle = Application.getBundle(context); + + String clientId = getClientId(context); + + // start outer table + out.write(""); + + // get the data that represents the users/groups to display + List userGroups = (List)getValue(); + if (userGroups != null) + { + for (Map authority : userGroups) + { + String authorityId = (String)authority.get("id"); + + out.write(""); + } + } + + out.write("
"); + + // walk parent hierarchy to calculate width of this cell + int width = 16; + Map parent = (Map)authority.get("parent"); + while (parent != null) + { + width += 16; + parent = (Map)parent.get("parent"); + } + out.write(""); + + out.write("
"); + + // output expanded/collapsed icon if authority is a group + boolean expanded = false; + boolean isGroup = (Boolean)authority.get("isGroup"); + if (isGroup) + { + // either output the expanded or collapsed selectable widget + expanded = (Boolean)authority.get("expanded"); + String image = expanded ? WebResources.IMAGE_EXPANDED : WebResources.IMAGE_COLLAPSED; + out.write(Utils.buildImageTag(context, image, 11, 11, "", + generateFormSubmit(context, ACTION_EXPANDCOLLAPSE, authorityId))); + } + out.write(""); + + // output selected checkbox if not expanded and not a duplicate + boolean duplicate = (Boolean)authority.get("duplicate"); + if (duplicate == false && (isGroup == false || expanded == false)) + { + boolean selected = (Boolean)authority.get("selected"); + out.write("'); + } + out.write(""); + + // output icon + out.write(Utils.buildImageTag(context, (String)authority.get("icon"), 16, 16, "")); + out.write(""); + + // output textual information + if (duplicate) + { + out.write(""); + } + out.write((String)authority.get("fullName")); + out.write(" ("); + out.write((String)authority.get("roles")); + out.write(")"); + if (duplicate) + { + out.write(""); + } + out.write("
"); + } + + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * We use a hidden field per picker instance on the page. + * + * @return hidden field name + */ + private String getHiddenFieldName(FacesContext context) + { + return getClientId(context); + } + + /** + * Generate FORM submit JavaScript for the specified action + * + * @param context FacesContext + * @param action Action index + * @param authority Authority Id of the action source + * + * @return FORM submit JavaScript + */ + private String generateFormSubmit(FacesContext context, int action, String authority) + { + return Utils.generateFormSubmit(context, this, getHiddenFieldName(context), + Integer.toString(action) + NamingContainer.SEPARATOR_CHAR + authority); + } + + + // ------------------------------------------------------------------------------ + // Inner classes + + /** + * Class representing the an action relevant to the User Group picker component. + */ + public static class PickerEvent extends ActionEvent + { + public PickerEvent(UIComponent component, int action, String authority) + { + super(component); + Action = action; + Authority = authority; + } + + public String Authority; + public int Action; + } +} diff --git a/source/java/org/alfresco/web/ui/repo/tag/UserGroupPickerTag.java b/source/java/org/alfresco/web/ui/repo/tag/UserGroupPickerTag.java new file mode 100644 index 0000000000..011ba48a96 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/tag/UserGroupPickerTag.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.web.ui.repo.tag; + +import javax.faces.component.UICommand; +import javax.faces.component.UIComponent; + +import org.alfresco.web.ui.common.tag.HtmlComponentTag; + +/** + * @author Kevin Roast + */ +public class UserGroupPickerTag extends HtmlComponentTag +{ + /** + * @see javax.faces.webapp.UIComponentTag#getComponentType() + */ + public String getComponentType() + { + return "org.alfresco.faces.UserGroupPicker"; + } + + /** + * @see javax.faces.webapp.UIComponentTag#getRendererType() + */ + public String getRendererType() + { + return null; + } + + /** + * @see javax.faces.webapp.UIComponentTag#setProperties(javax.faces.component.UIComponent) + */ + protected void setProperties(UIComponent component) + { + super.setProperties(component); + + setStringProperty(component, "value", this.value); + setActionListenerProperty((UICommand)component, this.actionListener); + } + + /** + * @see org.alfresco.web.ui.common.tag.HtmlComponentTag#release() + */ + public void release() + { + super.release(); + this.value = null; + } + + /** + * Set the value (binding to the list of user/group data) + * + * @param value the value + */ + public void setValue(String value) + { + this.value = value; + } + + /** + * Set the actionListener + * + * @param actionListener the actionListener + */ + public void setActionListener(String actionListener) + { + this.actionListener = actionListener; + } + + /** the value (binding to the list of user/group data) */ + private String value; + + /** the actionListener */ + private String actionListener; +} diff --git a/source/web/WEB-INF/faces-config-beans.xml b/source/web/WEB-INF/faces-config-beans.xml index 95e5400be4..cf5c8b273d 100644 --- a/source/web/WEB-INF/faces-config-beans.xml +++ b/source/web/WEB-INF/faces-config-beans.xml @@ -1881,6 +1881,40 @@ + + + The bean that backs up the Email Space Users Dialog + + EmailSpaceUsersDialog + org.alfresco.web.bean.users.EmailSpaceUsersDialog + session + + browseBean + #{BrowseBean} + + + nodeService + #{NodeService} + + + permissionService + #{PermissionService} + + + personService + #{PersonService} + + + authorityService + #{AuthorityService} + + + mailSender + #{mailService} + + + + @@ -2035,6 +2069,7 @@ request + diff --git a/source/web/WEB-INF/faces-config-repo.xml b/source/web/WEB-INF/faces-config-repo.xml index 2bd25e9e89..9b779c54f9 100644 --- a/source/web/WEB-INF/faces-config-repo.xml +++ b/source/web/WEB-INF/faces-config-repo.xml @@ -139,6 +139,11 @@ org.alfresco.web.ui.repo.component.UIDialogButtons + + org.alfresco.faces.UserGroupPicker + org.alfresco.web.ui.repo.component.UIUserGroupPicker + + diff --git a/source/web/WEB-INF/repo.tld b/source/web/WEB-INF/repo.tld index b3406812ba..55b9930f2e 100644 --- a/source/web/WEB-INF/repo.tld +++ b/source/web/WEB-INF/repo.tld @@ -1560,4 +1560,58 @@ + + userGroupPicker + org.alfresco.web.ui.repo.tag.UserGroupPickerTag + JSP + + + The userGroupPicker component renders a multi-select hierarchical list of groups + and users. The groups and be expanded to show the child users and groups for individual + selection and deselection. + + + + id + false + true + + + + value + true + true + + + + binding + false + true + + + + rendered + false + true + + + + actionListener + false + true + + + + style + false + true + + + + styleClass + false + true + + + diff --git a/source/web/css/main.css b/source/web/css/main.css index cf3b3368db..dc8bd137bd 100644 --- a/source/web/css/main.css +++ b/source/web/css/main.css @@ -519,3 +519,12 @@ a.topToolbarLinkHighlight, a.topToolbarLinkHighlight:link, a.topToolbarLinkHighl padding: 4px; -moz-border-radius: 4px; } + +.userGroupPickerList +{ + padding: 2px; + background-color: #EEEEEE; + border-width: 1px; + border-style: solid; + border-color: #AAAAAA; +} diff --git a/source/web/images/icons/email_users.gif b/source/web/images/icons/email_users.gif new file mode 100644 index 0000000000000000000000000000000000000000..31ddcd03a0b148dcc693cd031755b97f1d279b2c GIT binary patch literal 590 zcmb7>`%6=C0L5=*xUB5Xw>h)9${MRyYB{Hq#K*K|&FS2HBG?3{A%$PCLgxMD2X~z6LEVSd71>Pg}VR^lP1A|aw$T(x~$sebceDb18e$A`w8Jrfm{r<7>}nXRHS`SH^y4Z$i|fiQ;& zD?SyuUtJialI9CH`ZKx^gVAs2`eaG^mVnGOW|QVblA>teo}+Qe8EL6ZreNK{W5V1G v*^TYiv3XBx%v!x)q^V3z*Qe;;$XjEx2HGO(dknMIc|j0E-xe8n)0FZr z7CD@yZx|J=UDVoLyR2_%DQ&4Oyr<7x&3tBFr~51J`NMObKR)L%&*y=#(7>PsXM!`~ zAHsJ5fV|d7US}e&GgUX+YJ_Ce?|}xqb&q|}Gv9B+{z$YFMgw$Dc4CqMkOhHkHzopdraVTt-I{s%~H(jD>}aTm?-Q%;(^9*)V?% zO;5noN0H#X=CQh#laAB+Yf(Jqh$e;e6@OZTF|-MXhi+TK8r?*lm%AKkC( z55|Vq0v!|~NJ7*0{fNs#V2pu{gy8ZpG!&|ROb68=P(>_D*4Q4wl}BOX7)-EmRWzt$ zaMf{qJPsy*!N=o~^f*kiVJZoY#xIFuFhv4PpMvSrNR|XMsW6j<wg=cE%VDz>qiCfCAVJ4a{cn-3< zk|X&Z^eCx`W!!_bZc3x)U_SAv=5#NI$*gEM-~{I+N?E>%B984UIXqt#zJ(&~L~bfK zQ`Vtr%DIoys`#JsHze^FD}9^-*_|n47xlMDJp7sK@|d^Ej(2C>4x$fMcEt6jua+CO z@>;B1cL#SPE-5|iiZH7yC%54DEhnW?)5H?vjrM+5a%tW<-OX0FY3G5>!Sc-PcAZ}t zj;!xDv-gkhIXJ;5Z3`>x%b-M#h>Q$brTxWT9SU}?cPE=Hp|lGS&0{E`Uc9MJH>oko zbLLnRn*_>YFTZ|T?s^typ~%#8<(-}jiC>0O&a(AxUJ7inFyzHr zCc7U=i4Z#qi1p%i(L{D3Aud>{mw&pc$l7TO!*ZR;S);>Thhx?CjRdFIbHg?G|oJQp3 z?I<$Nj^ea9Mzbu2+ODOHhw7Vu@eQ?3HA|$r*q`{b(6I94U!GmhmBmc^>dTRacGUqw zV*`u*!h%QAKT-{QN292J_k5AGc0Bp2ZZ+Dp@YyT9@9Vjg5bIdQ(NKG{UAh&~52qrz aJ$4BvR>ihd!K%a|L$^BQOtLa3?D-cXRm&v+ literal 0 HcmV?d00001 diff --git a/source/web/jsp/browse/browse.jsp b/source/web/jsp/browse/browse.jsp index ff5e427bcf..4314fc5948 100644 --- a/source/web/jsp/browse/browse.jsp +++ b/source/web/jsp/browse/browse.jsp @@ -185,7 +185,7 @@ - + diff --git a/source/web/jsp/browse/dashboard.jsp b/source/web/jsp/browse/dashboard.jsp index d6d8327d1d..e19d044612 100644 --- a/source/web/jsp/browse/dashboard.jsp +++ b/source/web/jsp/browse/dashboard.jsp @@ -87,7 +87,7 @@ - + @@ -110,7 +110,7 @@
- + diff --git a/source/web/jsp/dialog/apply-doc-template.jsp b/source/web/jsp/dialog/apply-doc-template.jsp index bf5596d8fa..f328f07a00 100644 --- a/source/web/jsp/dialog/apply-doc-template.jsp +++ b/source/web/jsp/dialog/apply-doc-template.jsp @@ -69,7 +69,7 @@ -
''
+
''
diff --git a/source/web/jsp/dialog/apply-space-template.jsp b/source/web/jsp/dialog/apply-space-template.jsp index 9b3eaeef89..0f195400c8 100644 --- a/source/web/jsp/dialog/apply-space-template.jsp +++ b/source/web/jsp/dialog/apply-space-template.jsp @@ -69,7 +69,7 @@ -
''
+
''
diff --git a/source/web/jsp/dialog/document-details.jsp b/source/web/jsp/dialog/document-details.jsp index 6dae40f147..4e575061ac 100644 --- a/source/web/jsp/dialog/document-details.jsp +++ b/source/web/jsp/dialog/document-details.jsp @@ -125,14 +125,14 @@ - @@ -160,8 +160,8 @@ diff --git a/source/web/jsp/wizard/invite-users/notify.jsp b/source/web/jsp/wizard/invite-users/notify.jsp index 191a78e7ea..740aa959cd 100644 --- a/source/web/jsp/wizard/invite-users/notify.jsp +++ b/source/web/jsp/wizard/invite-users/notify.jsp @@ -134,7 +134,7 @@ @@ -146,12 +146,12 @@ - - + +
- +
diff --git a/source/web/jsp/dialog/space-details.jsp b/source/web/jsp/dialog/space-details.jsp index f1d2f8670e..32dc64a698 100644 --- a/source/web/jsp/dialog/space-details.jsp +++ b/source/web/jsp/dialog/space-details.jsp @@ -113,14 +113,14 @@ - @@ -146,12 +146,12 @@ - - + +
- +
diff --git a/source/web/jsp/users/email-space-users.jsp b/source/web/jsp/users/email-space-users.jsp new file mode 100644 index 0000000000..75994aa0b6 --- /dev/null +++ b/source/web/jsp/users/email-space-users.jsp @@ -0,0 +1,74 @@ +<%-- + Copyright (C) 2005 Alfresco, Inc. + + Licensed under the Mozilla Public License version 1.1 + with a permitted attribution clause. You may obtain a + copy of the License at + + http://www.alfresco.org/legal/license.txt + + 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. +--%> +<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> +<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %> +<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %> + + + + + + +
+ + +
+ + + + + + +  * + + + + + + + + + + + + + + + diff --git a/source/web/jsp/wizard/invite-content-users/notify.jsp b/source/web/jsp/wizard/invite-content-users/notify.jsp index daf88c4a7f..dca4970d27 100644 --- a/source/web/jsp/wizard/invite-content-users/notify.jsp +++ b/source/web/jsp/wizard/invite-content-users/notify.jsp @@ -134,7 +134,7 @@
: -  * +  *
: <%-- Templates drop-down selector --%> - +
: - +
: -  * +  *
: <%-- Templates drop-down selector --%> - +
@@ -160,8 +160,8 @@ : - +