From: Nikolay <faf...@us...> - 2004-01-02 19:02:05
|
Update of /cvsroot/collective/PortalTransport In directory sc8-pr-cvs1:/tmp/cvs-serv31589 Modified Files: MailLoader.py MailTemplate.py README.txt __init__.py config.py templates.py utils.py Added Files: Subscribeable.py SubscriptionEngine.py Removed Files: BaseSubscribeable.py SubscriptionInfo.py Log Message: * added SubscriptionEngine, general way to subscribe to any content * added anonymous subscription machinery Index: __init__.py =================================================================== RCS file: /cvsroot/collective/PortalTransport/__init__.py,v retrieving revision 1.2 retrieving revision 1.3 diff -u -d -r1.2 -r1.3 --- __init__.py 24 Dec 2003 12:09:38 -0000 1.2 +++ __init__.py 2 Jan 2004 19:01:59 -0000 1.3 @@ -12,10 +12,11 @@ from config import SKINS_DIR, GLOBALS, PROJECTNAME from config import ADD_CONTENT_PERMISSION -import MailLoader, MailSpool, MailTemplatesTool +import MailLoader, MailSpool, MailTemplatesTool, SubscriptionEngine tools = (MailSpool.MailSpool, MailLoader.MailLoader, MailTemplatesTool.MailTemplatesTool, + SubscriptionEngine.SubscriptionEngine, ) registerDirectory(SKINS_DIR, GLOBALS) --- SubscriptionInfo.py DELETED --- --- BaseSubscribeable.py DELETED --- Index: MailLoader.py =================================================================== RCS file: /cvsroot/collective/PortalTransport/MailLoader.py,v retrieving revision 1.8 retrieving revision 1.9 diff -u -d -r1.8 -r1.9 --- MailLoader.py 26 Dec 2003 08:58:02 -0000 1.8 +++ MailLoader.py 2 Jan 2004 19:01:59 -0000 1.9 @@ -345,5 +345,18 @@ else: return st, 'text/plain' + security.declarePrivate('genMessageId') + def genMessageId(self, content): + """ """ + s = '<%s...@cm...>' % content.UID() + return s + + security.declarePrivate('getObjectsFromId') + def getObjectsFromId(self, reply_to): + """ """ + tool = getToolByName(self, TOOL_NAME) + uid = reply_to[1:-1].split('@')[0] + return tool.lookupObject(uid) + InitializeClass(MailLoader) Index: utils.py =================================================================== RCS file: /cvsroot/collective/PortalTransport/utils.py,v retrieving revision 1.1 retrieving revision 1.2 diff -u -d -r1.1 -r1.2 --- utils.py 22 Dec 2003 09:26:12 -0000 1.1 +++ utils.py 2 Jan 2004 19:01:59 -0000 1.2 @@ -3,6 +3,7 @@ # Author: Nik Kim <fa...@le...> __version__ = '$Revision$'[11:-2] +import time, random, md5, socket from email.Header import decode_header # word-wrap function that preserves line breaks and spaces @@ -27,3 +28,16 @@ if not hasattr(c_ob, template): c_ob.invokeFactory('MailTemplate', template, **attrs) + +# code from Archetypes-1.3 +try: + _v_network = socket.gethostbyname(socket.gethostbyname()) +except: + _v_network = random.random() * 100000000000000000L + +def make_uuid(*args): + t = long(time.time() * 1000) + r = long(random.random()*100000000000000000L) + data = str(t)+' '+str(r)+' '+str(_v_network)+' '+str(args) + data = md5.md5(data).hexdigest() + return data Index: templates.py =================================================================== RCS file: /cvsroot/collective/PortalTransport/templates.py,v retrieving revision 1.3 retrieving revision 1.4 diff -u -d -r1.3 -r1.4 --- templates.py 24 Dec 2003 12:09:38 -0000 1.3 +++ templates.py 2 Jan 2004 19:01:59 -0000 1.4 @@ -56,6 +56,34 @@ %(portal_title)s %(portal_url)s """} +subscription_activation = {'title': 'Subscription activation message', + 'mail_subject': 'You need to activate subscription.', + 'mail_body': +""" + +You need to activate subscription to +"%(content_title)s" %(content_url)s + +to activate %(portal_url)s/%(activate_action)s + +_____________________________ +%(portal_title)s %(portal_url)s +"""} + +subscription_deactivation = {'title': 'Subscription deactivation message', + 'mail_subject': 'To unsubscribe you need confirmation.', + 'mail_body': +""" + +To unsubscribe, you need confirmation +"%(content_title)s" %(content_url)s + +to confirm %(portal_url)s/%(activate_action)s + +_____________________________ +%(portal_title)s %(portal_url)s +"""} + unsubscribed_message = {'title': 'Unsubscribed message', 'mail_subject': 'You have been unsubscribed.', @@ -85,6 +113,8 @@ 'error_3': error_3, 'subscribed_message': subscribed_message, 'unsubscribed_message': unsubscribed_message, + 'subscription_activation': subscription_activation, + 'subscription_deactivation': subscription_deactivation, } } } --- NEW FILE: Subscribeable.py --- # -*- coding: UTF-8 -*- # -*- Mode: Python; py-indent-offset: 4 -*- # Authors: Nik Kim <fa...@le...> import re, traceback from Acquisition import aq_parent, aq_inner from ExtensionClass import Base from Globals import InitializeClass from AccessControl import ClassSecurityInfo from BTrees.OIBTree import OIBTree from Products.CMFCore.utils import getToolByName from Products.CMFCore.CMFCorePermissions import View, ModifyPortalContent from Products.Archetypes.config import TOOL_NAME from email.Utils import formataddr from config import PROJECTNAME from interfaces.subscribeable import ISubscribeable class Subscribeable(Base): """ """ __implements__ = (ISubscribeable,) security = ClassSecurityInfo() security.declareProtected(View, 'getEmailAddress') def getEmailAddress(): """ """ raise NotImplemented, 'getEmailAddress' security.declareProtected(ModifyPortalContent, 'getSubscribers') def getSubscribers(self): """ """ subs = getToolByName(self, 'portal_subscriptions') return subs.getSubscribers((self,)) security.declareProtected(ModifyPortalContent, 'addSubscribers') def addSubscribers(self, members): """ """ subs = getToolByName(self, 'portal_subscriptions') subs.addSubscriptions(members, self) security.declareProtected(ModifyPortalContent, 'removeSubscribers') def removeSubscribers(self, members): """ """ subs = getToolByName(self, 'portal_subscriptions') subs.removeSubscriptions(members, self) security.declareProtected(View, 'isAllowAnonymousSubscribers') def isAllowAnonymousSubscribers(self): """ """ return 0 security.declareProtected(View, 'isAutoActivateSubscription') def isAutoActivateSubscription(): """ auto activate or start activate process """ return 1 security.declareProtected(View, 'isAutoActivateAnonymousSubscription') def isAutoActivateAnonymousSubscription(): """ auto activate or start activate process """ return 0 security.declareProtected(View, 'isSendSubscriptionNotifications') def isSendSubscriptionNotifications(self): """ """ return 1 security.declareProtected(View, 'isSubscribed') def isSubscribed(self, user_id=None): """ """ subs = getToolByName(self, 'portal_subscriptions') return subs.isSubscribed(self, user_id) security.declareProtected(View, 'Subscribe') def Subscribe(self): """ """ mtool = self.portal_membership if mtool.isAnonymousUser(): return 0 subs = self.portal_subscriptions member = mtool.getAuthenticatedMember() subs.addSubscriptions((member.getMemberId(),), self) security.declareProtected(View, 'Unsubscribe') def Unsubscribe(self): """ """ mtool = self.portal_membership if mtool.isAnonymousUser(): return 0 subs = self.portal_subscriptions member = mtool.getAuthenticatedMember() subs.removeSubscriptions((member.getMemberId(),), self) security.declarePrivate('createHeaders') def createHeaders(self, content, parent, reference, source, sourceEmail, mtool): """ """ headers = {} email_from_address=self.email_from_address.strip() # create from header user = mtool.getMemberById(content.Creator()) if user: fullname = user.getProperty('fullname', str(user)) if not fullname: fullname = str(user) email = user.getProperty('email').strip() if not email: email = email_from_address headers['From'] = formataddr((fullname, email)) else: headers['From'] = formataddr(('Anonymous', email_from_address)) # message-id and in-reply-to headers['Message-ID'] = '<%s...@cm...>'%content.UID() if (parent is not None) and (content is not parent): headers['In-Reply-To'] = '<%s...@cm...>'%parent.UID() if reference is not None: # refernce to topic headers['References'] = '<%s...@cm...>'%reference.UID() if sourceEmail: headers['To'] = sourceEmail headers['Reply-To'] = sourceEmail headers['List-Id'] = '%s, %s'%(source.Title(), sourceEmail.replace('@', '.')) headers['List-Post'] = '<mailto:%s>'%sourceEmail return headers security.declarePrivate('sendMessage') def sendMessage(self, message, parent): """ send message to all subsribed users """ raise NotImplemented, 'sendMessage' InitializeClass(Subscribeable) --- NEW FILE: SubscriptionEngine.py --- # -*- coding: UTF-8 -*- # -*- Mode: Python; py-indent-offset: 4 -*- # Authors: Nik Kim <fa...@le...> __version__ = '$Revision: 1.1 $'[11:-2] import re, traceback from Globals import InitializeClass from Acquisition import aq_base, aq_inner, aq_parent from AccessControl import ClassSecurityInfo from OFS.SimpleItem import SimpleItem from Products.ZCatalog.ZCatalog import ZCatalog from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2 from Products.CMFCore.utils import getToolByName, UniqueObject from Products.CMFCore.CMFCorePermissions import \ View, ModifyPortalContent, ManagePortal, ReviewPortalContent from Products.Archetypes.config import TOOL_NAME from Products.Archetypes.interfaces.referenceable import IReferenceable from config import * from utils import make_uuid from interfaces.subscribeable import ISubscribeable from email.Utils import formataddr isEmail = re.compile('^[0-9a-zA-Z_&.%+-]+@([0-9a-zA-Z]([0-9a-zA-Z-]*[0-9a-zA-Z])?\.)+[a-zA-Z]{2,6}$') indexes = (('uid', 'FieldIndex'), ('active', 'FieldIndex'), ('member_id', 'FieldIndex'), ('email', 'FieldIndex'), ('content_uid', 'FieldIndex'),) class SubscriptionEngine(UniqueObject, BTreeFolder2, ZCatalog): """ class for manage single subscription """ id = 'portal_subscriptions' meta_type = PROJECTNAME + ' Subscriptions Tool' security = ClassSecurityInfo() manage_options = ZCatalog.manage_options def __init__(self): BTreeFolder2.__init__(self, self.id) ZCatalog.__init__(self, self.id) for idx_name, idx_type in indexes: self.addIndex(idx_name, idx_type) security.declareProtected(ReviewPortalContent, 'getSubscribers') def getSubscribers(self, contents): """ """ uids = [] for content in contents: if IReferenceable.isImplementedBy(content): uids.append(content.UID()) else: uids.append('/'.join(content.getPhysicalPath())) mtool = aq_parent(aq_inner(self)).portal_membership # search subscribers infos = {} for brain in self.searchResults(content_uid=uids, active=1): try: o = self._getOb('ref_%s'%brain.getPath()) infos[o.id] = o except: pass # generate results results = [] for id, info in infos.items(): if info.is_anonymous: results.append((None, formataddr((info.fullname, info.email)), id)) else: member = mtool.getMemberById(info.member_id) email = member.getProperty('email','') if isEmail.match(email): results.append(( member, formataddr((member.getProperty('fullname',''), email)), id)) return results def addAnonymousSubscription(self, member, content, email, fullname): """ subscribe anonymous """ if (ISubscribeable.isImplementedBy(content) and conent.isAllowAnonymousSubscribers()): if content.isAutoActivateAnonymousSubscribers(): info = SubscriptionInfo(member, content, email, fullname) self._addSubscription(info, content) else: info = SubscriptionInfo(member, content, email, fullname) self._addSubscription(info, content, 0) else: raise Exception, "Can't subscribe anonymous user." security.declareProtected(View, 'activateSubscription') def activateSubscription(self, uid): """ """ info = self._getOb('ref_%s'%uid, None) if info is not None: info.activateSubscription() self.catalog_object(info, uid) # fixme: move this code to python script if REQUEST is not None: REQUEST.RESPONSE.redirect( '%s?portal_status_message=%s'% (content.absolute_url(), 'You have been subscribed.')) security.declareProtected(View, 'deactivateSubscription') def deactivateSubscription(self, uid, REQUEST): """ """ id = 'ref_%s'%uid info = self._getOb(id, None) if info is not None: content = info.getSubscribedContent() info.removeSubscription() self.uncatalog_object(uid) self._delObject(id) else: content = aq_parent(aq_inner(self)) # fixme: move this code to python script if REQUEST is not None: REQUEST.RESPONSE.redirect( '%s?portal_status_message=%s'% (content.absolute_url(), 'You have been unsubscribed.')) security.declareProtected(ManagePortal, 'addSubscriptions') def addSubscriptions(self, members, content): """ """ mtool = aq_parent(aq_inner(self)).portal_membership if type(members) == type(''): members = [members,] for m in members: try: member = mtool.getMemberById(m) info = SubscriptionInfo(member, content) self._addSubscription(info, content) except: pass security.declareProtected(ModifyPortalContent, 'removeSubscriptions') def removeSubscriptions(self, members, content): """ """ mtool = aq_parent(aq_inner(self)).portal_membership if type(members) == type(''): members = [members,] # create content uid if IReferenceable.isImplementedBy(content): content_uid = content.UID() else: content_uid = '/'.join(content.getPhysicalPath()) infos = {} for brain in self.searchResults(content_uid=content_uid, member_id=members): try: o = self._getOb('ref_%s'%brain.getPath()) infos[o.id] = o except: pass # remove subscription for id, info in infos.items(): if info.is_anonymous and info.active: info.startDeactivationProcess(content) else: info.removeSubscription(content) self.uncatalog_object(info.uid) self._delObject(id) security.declareProtected(View, 'isSubscribed') def isSubscribed(self, content, user_id=None): """ """ if user_id is None: mtool = getattr(aq_parent(aq_inner(self)), 'portal_membership') member = mtool.getAuthenticatedMember() user_id = member.getMemberId() if IReferenceable.isImplementedBy(content): content_uid = content.UID() else: content_uid = '/'.join(content.getPhysicalPath()) if self.searchResults(member_id=user_id, content_uid=content_uid): return 1 else: return 0 def _addSubscription(self, info, content, activate=1): id = info.id uid = info.uid self._setObject(id, info) info = getattr(self, id) if not activate: info.startActivationProcess(content) else: info.activateSubscription(content) self.catalog_object(info, uid) InitializeClass(SubscriptionEngine) class SubscriptionInfo(SimpleItem): """ class for manage single subscription """ security = ClassSecurityInfo() def __init__(self, member, content, email='', fullname=''): uid = make_uuid() self.id = 'ref_%s'%uid self.uid = uid self.active = 0 member_id = member.getMemberId() self.member_id = member_id if member_id == 'Anonymous User': if not isEmail.match(email): raise ValueError, "Wrong Email address '%s'"%email self.is_anonymous = 1 self.email = email self.fullname = fullname else: self.is_anonymous = 0 self.email = '' self.fulname = '' if not isEmail.match(member.getProperty('email', '')): raise ValueError, "Wrong Email address '%s'"%email if IReferenceable.isImplementedBy(content): self.content_uid = content.UID() self.referenceable = 1 else: self.content_uid = '/'.join(content.getPhysicalPath()) self.referenceable = 0 security.declarePrivate('activateSubscription') def activateSubscription(self, content=None): """ """ if not isEmail.match(self.getRawEmail()): raise ValueError, "Wrong Email address '%s'"%email self.active = 1 self.sendSubscribedNotification(content) security.declarePrivate('deactivateSubscription') def deactivateSubscription(self, content=None): """ """ if not isEmail.match(self.getRawEmail()): raise ValueError, "Wrong Email address '%s'"%email self.active = 0 self.sendUnsubscribedNotification(content) def startActivationProcess(self, content): # activate # send mail with link, etc spool = getToolByName(self, 'portal_mailspool') templ = getToolByName(self, 'portal_mailtemplates') msg = templ.createMessage(PROJECTNAME, subscription_activation, {'content_title': content.Title(), 'content_url': content.absolute_url(), 'activate_action': activate_action%self.uid}, {'To': self.getEmail()} ) spool.addMessage(msg) def startDeactivationProcess(self, content): # send mail with link, etc spool = getToolByName(self, 'portal_mailspool') templ = getToolByName(self, 'portal_mailtemplates') msg = templ.createMessage(PROJECTNAME, subscription_deactivation, {'content_title': content.Title(), 'content_url': content.absolute_url(), 'deactivate_action': deactivate_action%self.uid}, {'To': self.getEmail()} ) spool.addMessage(msg) def getEmail(self): """ """ if self.is_anonymous: formataddr((self.fullname, self.email)) else: mtool = getToolByName(self, 'portal_membership') member = mtool.getMemberById(self.member_id) return formataddr((member.getProperty('fullname'), member.getProperty('email'))) def getRawEmail(self): """ """ if self.is_anonymous: return self.email else: mtool = getToolByName(self, 'portal_membership') member = mtool.getMemberById(self.member_id) return member.getProperty('email') def getSubscribedContent(self): """ """ if self.referenceable: tool = getToolByName(self, TOOL_NAME) return tool.lookupObject(self.content_uid) else: return self.unrestrictedTraverse(self.content_uid) security.declarePrivate('sendSubscribedNotification') def sendSubscribedNotification(self, content=None): """ send notification to subscriber """ if content is None: content = self.getSubscribedContent() try: if not content.isSendSubscriptionNotifications(): return except: pass portal = getToolByName(self, 'portal_url').getPortalObject() pmt = portal.portal_mailtemplates mspool = portal.portal_mailspool email = self.getEmail() title = content.Title() try: from_email = content.getEmailAddress() or self.email_from_address except: from_email = self.email_from_address info = {'content': content, 'content_url': content.absolute_url(), 'content_title': title, 'content_email': email, } headers = {'To': email, 'From': formataddr((title, from_email))} msg = pmt.createMessage(PROJECTNAME, 'subscribed_message', info, headers) mspool.addMessage(msg) security.declarePrivate('sendUnsubscribedNotification') def sendUnsubscribedNotification(self, content=None): """ send unsubscriotion notification to author """ if content is None: content = self.getSubscribedContent() try: if not content.isSendSubscriptionNotifications(): return except: pass portal = aq_parent(aq_parent(aq_inner(self))) pmt = portal.portal_mailtemplates mspool = portal.portal_mailspool title = content.Title() email = self.getEmail() try: from_email = content.getEmailAddress() or self.email_from_address except: from_email = self.email_from_address info = {'content': content, 'content_url': content.absolute_url(), 'content_title': title, 'content_email': from_email, } headers = {'To': email, 'From': formataddr((title, from_email))} msg = pmt.createMessage(PROJECTNAME, 'unsubscribed_message', info, headers) mspool.addMessage(msg) InitializeClass(SubscriptionInfo) Index: MailTemplate.py =================================================================== RCS file: /cvsroot/collective/PortalTransport/MailTemplate.py,v retrieving revision 1.5 retrieving revision 1.6 diff -u -d -r1.5 -r1.6 --- MailTemplate.py 26 Dec 2003 08:58:02 -0000 1.5 +++ MailTemplate.py 2 Jan 2004 19:01:59 -0000 1.6 @@ -126,16 +126,16 @@ return message - def getMessages(self, info, headers={}, charset=None, text_format='plain'): + def getMessage(self, info, headers={}, charset=None, text_format='plain'): """ """ script = self.getBeforeScript() - if iscallable(script): + if callable(script): # fixme: is needed move to try except block? script.__of__(self)( info=info, headers=headers, charset=charset, text_format=text_format) - return self._getMessages(info, headers, charset, text_format) + return self._getMessage(info, headers, charset, text_format) def getMultipartMessage(self, info, headers={}, charset=None, text_format='plain', messages=(), files=()): @@ -144,7 +144,7 @@ """ script = self.getBeforeScript() - if iscallable(script): + if callable(script): # fixme: is needed move to try except block? script.__of__(self)( info=info, headers=headers, charset=charset, text_format=text_format) Index: README.txt =================================================================== RCS file: /cvsroot/collective/PortalTransport/README.txt,v retrieving revision 1.1 retrieving revision 1.2 diff -u -d -r1.1 -r1.2 --- README.txt 22 Dec 2003 09:26:12 -0000 1.1 +++ README.txt 2 Jan 2004 19:01:59 -0000 1.2 @@ -19,4 +19,4 @@ run 'newaliases'. Your new alias with 'foo' as your mailinglist-email-address-name should look like: - foo: "|mailloader.py http://ZopeServer:Port/PloneSite/portal_mailloader/load [maxBytes]" + foo: "|mailloader.py http://ZopeServer:Port/PloneSite/portal_mailloader/deliver [maxBytes]" Index: config.py =================================================================== RCS file: /cvsroot/collective/PortalTransport/config.py,v retrieving revision 1.1 retrieving revision 1.2 diff -u -d -r1.1 -r1.2 --- config.py 22 Dec 2003 09:26:12 -0000 1.1 +++ config.py 2 Jan 2004 19:01:59 -0000 1.2 @@ -64,3 +64,9 @@ 'permission': ('Manage portal',), 'imageUrl': 'mailloader_icon.gif', } + +# portal subscriptions +subscription_activation = 'subscription_activation' +subscription_deactivation = 'subscription_deactivation' +activate_action = 'portal_subscriptions/activateSubscription?uid=%s' +deactivate_action = 'portal_subscriptions/deactivateSubscription?uid=%s' |