Skip to content

mxm, IT's mad science

Sections
Personal tools
You are here: Home » Products » Open Source » mxm Workgroups for Plone » BCC for Plone 2.5 and Workgroup 3.0
Downloads
You can download mxm products here.

Due to it's technical and international nature, this section is in english.

Max M Has a blog too.

og er glad for mad

 

Comment

Above in this comment thread: mxm Workgroups for Plone

BCC for Plone 2.5 and Workgroup 3.0

Posted by Anonymous User at 2009-01-16 03:08 AM
When sending out messages from a workgroup using the mail tool, the mail header contains all the names of the recipients, which is many times undesirable. I was hacking around and found a solution that works on Windows and with Plone 2.55, Zope 2.9.8-final and Python 2.4.4. SecureMailhost 1.05 is also a requirement, that provides the Bcc header capability.
The solution involves the modification of 2 files: the mail template and the email mixin script. Here is a listing for the 2 files for your kind reference. Please note that this is a hack, works for me, but probably lacks error checking, test cases and optimization. Use if at your own risk, or ask Max real nicely to implement is as pros do :-)
--- listing of Workgroup_mailForm.pt ---
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US"
lang="en-US"
metal:use-macro="here/main_template/macros/master"
i18n:domain="ATWorkgroup">


<div metal:fill-slot="main"
tal:define="memberMails here/getAccesibleMemberMails">


<form class="group" name="mail_form" action="." method="post" enctype="multipart/form-data"
tal:attributes="action here/absolute_url">

<h1 i18n:translate="send_an_email">Send an email to the group</h1>
<br/>

<!-- Mail Subject -->
<div class="row">
<div class="label" i18n:translate="subject">Subject</div>
<div class="field">
<input type="text" name="subject" size="40"/>
</div>
</div>


<!-- Mail Body -->
<div class="row">
<div class="label" i18n:translate="body">Body</div>
<div class="field">
<textarea cols="80"
rows="15"
name="body"
></textarea>
</div>
</div>


<!-- Mail From -->
<div class="row">
<div class="label" i18n:translate="from">From</div>
<div class="field">
<input type="text" name="mfrom" size="32" tal:attributes="value python:member.getProperty('email', '')"/>
</div>
</div>

<!-- Recepients -->
<!-- create seperate To: and BCC: sections -->
<div class="label" i18n:translate="To">To</div>
<div class="field">
<input type="text"
name="to"
size="32"
tal:attributes="value python:member.getProperty('email', '')"/>
</div>
<div class="row">
<div class="label" i18n:translate="Bcc">Bcc:</div>
<div class="field">
<tal:block repeat="email memberMails">
<input class="noborder"
type="checkbox"
name="bcc:list" checked
tal:attributes="value email">
<a tal:attributes="href string:MAILTO:$email" tal:content="email" />
</tal:block>
</div>
</div>


<div class="row">
<div class="label" i18n:translate="Attachments">Attachment</div>
<div class="field">
<input name="attachments:list" type="file">
</div>
<div class="field">
<input name="attachments:list" type="file">
</div>
</div>


<!-- Submitting machinery -->
<div class="row">
<div class="label"></div>
<div class="field">
<input class="context" type="submit" name="Workgroup_mailFormAction:method" value=" Send "
i18n:attributes="value"/>
</div>
</div>


</form>

</div>

</html>
--- end listing of Workgroup_mailForm.pt ---
--- listing of EmailMixin.py ---
# python

import email
from email import Encoders
from email.Message import Message
from email.Header import Header
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
try:
set
except NameError:
from sets import Set as set

# zope
from BTrees.OOBTree import OOBTree
from ZODB.PersistentList import PersistentList
from AccessControl import ClassSecurityInfo
from DocumentTemplate.DT_Util import html_quote

# cmf
from Products.CMFCore.utils import getToolByName

# product
from Products.ATWorkgroup.config import *

View = 'View'

class OOBTreeWrapper:

"""
Makes a OOBTree readable
"""

__allow_access_to_unprotected_subobjects__ = 1

def __init__(self, obj):
self._obj = obj

def __getitem__(self, key):
if key == 'attachments':
attachments = self._obj[key]
return [OOBTreeWrapper(a) for a in attachments]
else:
return self._obj[key]

def __getattr__(self, attr):
return getattr(self._obj, attr)



class EmailMixin:

"""
This class is used to seperate oout all the email handling functions.
"""

security = ClassSecurityInfo()

def _props(self):
return getToolByName(self, 'portal_properties')

def _members_emails(self, members):
"Returns list of emails from list of members"
emails = []
for member in members:
stripped_mail = member.getProperty('email', '').strip()
if stripped_mail:
emails.append(stripped_mail)
return emails


security.declareProtected(Workgroup_mailPermission, 'getMemberMails')
def getMemberMails(self):
"Returns a list of all members email adress"
return self._members_emails(self.getGroupMembers())


security.declareProtected(Workgroup_mailPermission, 'getAccesibleMemberMails')
def getAccesibleMemberMails(self):
"Returns a list of all visible members email adress"
return self._members_emails(self.getAccesibleGroupMembers())


def _site_encoding(self):
"Returns the site encoding"
site_props = self._props().site_properties
return site_props.default_charset or 'utf-8'


def getSmtpHost(self):
mh = self.MailHost
return (mh.smtp_host, mh.smtp_port)


def _site_encoding(self):
"Returns the site encoding"
site_props = self._props().site_properties
return site_props.default_charset or 'utf-8'

def _mailhost(self):
# tries to fetch a mailhost
if hasattr(self, 'MailHost'):
return self.MailHost

def _getAttachments(self, attachments):
"""
Returns [{'filename':fn, 'contentType':ct, 'data':data},] for attachements
"""
if attachments is None:
attachments = []
attachments = [a for a in attachments if a]
result = []
if attachments:
for attachment in attachments:
headers = email.message_from_string(str(attachment.headers))
contentType = headers['Content-Type']
filename = headers.get_param('filename', 'Attachment', 'Content-Disposition')
# clean up IE paths
filename = filename[max(filename.rfind('/'),
filename.rfind('\\'),
filename.rfind(':')
)+1:]
data = attachment.read()
result.append({'filename':filename, 'contentType':contentType, 'data':data})
return result


security.declareProtected(Workgroup_mailPermission, 'Workgroup_mailFormAction')
def Workgroup_mailFormAction(self, to=None, cc=None, bcc=None, inReplyTo=None,
subject=None, body='', attachments=None, mfrom='',
REQUEST=None):
"""
Sends a message. Many of the input parameters are not currently used,
but can be used for skinning the functionlity.
"""
site_encoding = self._site_encoding()
##################
# Create the message
msg = Message()
msg.set_payload(body, site_encoding)
#####################################
# if attachment, convert to multipart
# file fields are posted even if empty, so we need to remove those :-s

attachments = self._getAttachments(attachments)
if attachments:
mimeMsg = MIMEMultipart()
mimeMsg.attach(msg)
for attachment in attachments:
# Add the attachment
contentType = attachment['contentType']
filename = attachment['filename']
attach_part = Message()
attach_part.add_header('Content-Type', contentType, name=filename)
attach_part.add_header('Content-Disposition', 'attachment', filename=filename)
attach_part.set_payload(attachment['data'])
Encoders.encode_base64(attach_part)
mimeMsg.attach(attach_part)
msg = mimeMsg
########################
# set headers on message
####
# if to is None:
# to = []
# else:
# msg['To'] = ','.join(to)
if cc is None:
cc = []
else:
msg['Cc'] = ','.join(cc)
if bcc is None:
bcc = []
else:
msg['Bcc'] = ','.join(bcc)
# set recepients
if mfrom:
# to.append(mfrom) # send copy to sender
msg['From'] = mfrom
msg['Reply-To'] = mfrom
####
now = self.ZopeTime()
msg['Date'] = now.rfc822() # needed by some servers
if inReplyTo:
msg['In-Reply-To'] = inReplyTo
msg['Subject'] = Header(subject, site_encoding)
##################
# Send the message
SMTPserver = self._mailhost()
success = 0
all_receivers = list(set(bcc))
if all_receivers: # only send if any recipients
for a in all_receivers:
# try:
self._mailhost().send(msg.as_string(), mto=a, mfrom=mfrom)
success = 1
# returns a portal status message
if REQUEST:
if success:
message = 'Succes! The message was sent '
else:
message = 'Error! The message could not be sent'
REQUEST.RESPONSE.redirect(self.absolute_url() + '/Workgroup_mailForm?portal_status_message=%s' % message)
self.saveMessage(to=to, cc=cc, bcc=bcc, inReplyTo=inReplyTo, subject=subject, body=body, attachments=attachments, mfrom=mfrom, date=now,)


######################################
# Email backup

def msgCounterCount(self):
"Returns increments current value and increses it by one"
if not hasattr(self, '_msgCounter'):
# persistentlist to write as little as possible to Data.fs
self._msgCounter = OOBTree({'value':0})
self._msgCounter['value'] += 1
return self._msgCounter['value']

security.declareProtected(View, 'getMessages')
def getMessages(self):
"Returns the message storage"
if not hasattr(self, '_messageStorage'):
self._messageStorage = OOBTree()
return self._messageStorage


security.declareProtected(View, 'getVisibleMessages')
def getVisibleMessages(self, sortkey=None, reverse=0):
"Returns the message storage"
messages = self.getMessages()
wrapped = [OOBTreeWrapper(m) for m in messages.values()]
if sortkey:
decorated = [(o.get(sortkey, None), o) for o in wrapped]
decorated.sort()
sorted = [d[-1] for d in decorated]
if reverse:
sorted.reverse()
return sorted
else:
return wrapped

security.declareProtected(View, 'getMessage')
def getMessage(self, msgId):
"Returns the message storage"
return self.getMessages()[msgId]

security.declareProtected(View, 'getVisibleMessage')
def getVisibleMessage(self, msgId):
"Returns the message storage"
return OOBTreeWrapper(self.getMessage(msgId))

security.declarePublic('html_quote')
def html_quote(self, text):
"Quotes text"
return html_quote(text).replace('\n', '<br />')


security.declareProtected(View, 'saveMessage')
def saveMessage(self, to=None, cc=None, bcc=None, inReplyTo=None,
subject=None, body='', attachments=None, mfrom='', date=None):
"The message is saved in an OOBTree (dict)"
msg = OOBTree()
if to:
msg['to'] = ', '.join(to)
if cc:
msg['cc'] = ', '.join(cc)
if bcc:
msg['bcc'] = ', '.join(bcc)
if inReplyTo:
msg['inReplyTo'] = inReplyTo
if subject:
msg['subject'] = subject
if body:
msg['body'] = body
if attachments:
msg['attachments'] = PersistentList(attachments)
if mfrom:
msg['from'] = mfrom
if date:
msg['date'] = date
id = self.msgCounterCount()
msg['id'] = id
self.getMessages()[id] = msg


security.declarePublic('canDeleteMessages')
def canDeleteMessages(self):
"returns true if current user can delete messages."
mtool = self.portal_membership
member = mtool.getAuthenticatedMember()
return member.has_permission(Workgroup_editPermission, self)


security.declareProtected(Workgroup_editPermission, 'deleteMessages')
def deleteMessages(self, messageIds=None, REQUEST=None):
"Deletes messages"
if messageIds is None:
messageIds = []
messages = self.getMessages()
for id in messageIds:
del messages[id]
if not REQUEST is None:
self_url = self.absolute_url()
psm = 'portal_status_message=%s messages deleted.' % len(messageIds)
REQUEST.RESPONSE.redirect(self_url + '/Workgroup_mailArchive')


security.declarePublic('getAttachmentIconUrl')
def getAttachmentIconUrl(self, attach):
"Returns icon for mimetype"
portal = self.portal_url.getPortalObject()
portal_url = portal.absolute_url()
if hasattr(portal, 'mimetypes_registry'):
mimetypes = portal.mimetypes_registry
matching_types = mimetypes.lookup(attach['contentType'])
if matching_types:
mimetype = matching_types[0]
return '%s/%s' % (portal_url, mimetype.icon_path)
return portal_url + '/file_icon.gif'


security.declareProtected(View, 'downloadAttachment')
def downloadAttachment(self, msgId, attachIdx, REQUEST):
"Returns the attachment"
RESPONSE = REQUEST.RESPONSE
attach = self.getMessage(msgId)['attachments'][attachIdx]
RESPONSE.setHeader('Content-Type', attach['contentType'])
RESPONSE.setHeader('Content-Disposition', 'filename=%s' % attach['filename'])
return attach['data']
--- end listing of EmailMixin.py ---

Cheers,
Zoltan

Ah, all the indentation is stripped. Bad for Python...

Posted by Anonymous User at 2009-01-16 03:13 AM
Sorry. zoltan dot soos at the google email server.