############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import Globals, string from ForumPermissions import AddForumPosting from DateTime import DateTime from OFS.Image import File, Image from OFS.content_types import guess_content_type from AccessControl import ClassSecurityInfo from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl from Products.CMFCore.utils import getToolByName from Products.CMFDefault.Document import Document from Products.CMFDefault.SkinnedFolder import SkinnedFolder from Products.CMFCore.CMFCorePermissions import View, ManageProperties, \ ListFolderContents, ModifyPortalContent, AddPortalContent from Products.CMFForum.ForumPermissions import AddForumPosting, ViewForum from interfaces.Post import IPost factory_type_information = { 'id' : 'Post' , 'portal_type' : 'Post' , 'meta_type' : 'Post' , 'description' : """\ Posts are like Discussion items but they just can be added into Forums.""" , 'icon' : 'icon_post.gif' , 'product' : 'CMFForum' , 'factory' : 'addPost' , 'filter_content_types' : 1 , 'allowed_content_types' : ('Post', 'File', 'Image' ) , 'immediate_view' : 'metadata_edit_form' , 'global_allow' : 0 , 'actions' : ( { 'id' : 'view' , 'name' : 'View' , 'action' : 'post_view' , 'permissions' : ( View, ) } , { 'id' : 'post_reply' , 'name' : 'Reply' , 'action' : 'post_post_message' , 'permissions' : ('Add Forum posting', ) } , { 'id' : 'post_post' , 'name' : 'Post' , 'action' : 'forum_post_message' , 'permissions' : ('Add Forum posting', ) } , { 'id' : 'forum_home' , 'name' : 'Forum Home' , 'action' : 'forum_post_toForum' , 'permissions' : (View, ) } , { 'id' : 'forum_search' , 'name' : 'Search Forum' , 'action' : 'forum_post_toSearch' , 'permissions' : (View, ) } ) } def generate_id(self): """Find an available ID""" id = DateTime().millis() str_id = 'msg_%s' while hasattr(self, str_id % id): id = DateTime().millis() return str_id % id class Post(SkinnedFolder, Document): meta_type = 'Post' __implements__= (IPost, ) manage_options = SkinnedFolder.manage_options security = ClassSecurityInfo() def __init__(self, id, title='', description='', text='', author='', email='', in_reply_to='', text_format='plain'): DefaultDublinCoreImpl.__init__(self) self.id = id self.title = title self.description = description self.author = author self.email = email self._edit( text=text, text_format=text_format ) self.setFormat( text_format ) #self.created = self.modified = DateTime() # DefaultDublinCoreImpl.Date() uses now expects modified to be used, # differently self.created = DateTime() security.declareProtected(ViewForum, 'thread_top') def thread_top(self): """ returns the top ancestor """ if len(self.ancestors()) > 0: return self.ancestors()[-1] else: return self security.declareProtected(ViewForum, 'ancestors') def ancestors(self): """Return a list of ancestral posts""" ancestors = [] parent = self.aq_inner.aq_parent while parent.meta_type=='Post': ancestors.append(parent) parent = parent.aq_inner.aq_parent return ancestors security.declareProtected(ViewForum, 'children') def children(self, sort_key='id', REQUEST=None): """Return a list of replies to this item""" if REQUEST is None: REQUEST = self.REQUEST children = [] for child in self.contentValues(['Post']): children.append(child) children.sort(lambda x, y, sort_key=sort_key: cmp(getattr(x, sort_key, None) , getattr(y, sort_key, None))) return children security.declareProtected(ViewForum, 'inReplyTo') def inReplyTo(self): """ Return the object for which this object is a reply. """ parent = self.aq_inner.aq_parent if parent.meta_type=='Post': return parent else: return None # XXX remove this, use nextReplies security.declareProtected(ViewForum, 'nextReply') def nextReply(self): """ Return the next object that is reply. """ ids = self.contentIds(['Post']) ids.sort() if ids: return getattr(self, ids[0], None) return None security.declareProtected(ViewForum, 'nextReplies') def nextReplies(self): """ Return a sequence of objects that are replies. """ ids = self.contentIds(['Post']) ids.sort() if ids: return map(lambda x,self=self:getattr(self, x), ids) return None # XXX remove this, use nextMessages security.declareProtected(ViewForum, 'nextMessage') def nextMessage(self): """ Return the next object on the same level. """ ids = self.aq_inner.aq_parent.contentIds(['Post']) ids.sort() index = ids.index(self.getId()) next = index + 1 if next < len(ids): return getattr(self, ids[next], None) return None security.declareProtected(ViewForum, 'nextMessages') def nextMessages(self): """ Return a sequence of next objects on the same level. """ ids = self.aq_inner.aq_parent.contentIds(['Post']) ids.sort() index = ids.index(self.getId()) next = index + 1 if next < len(ids): return map(lambda x,self=self:getattr(self, x), ids[next:]) return None # XXX remove this, use previousMessages security.declareProtected(ViewForum, 'previousMessage') def previousMessage(self): """ Return the previous object on the same level. """ ids = self.aq_inner.aq_parent.contentIds(['Post']) ids.sort() index = ids.index(self.getId()) previous = index - 1 if previous >= 0: return getattr(self, ids[previous], None) return None security.declareProtected(ViewForum, 'previousMessages') def previousMessages(self): """ Return a sequence of previous objects on the same level. """ ids = self.aq_inner.aq_parent.contentIds(['Post']) ids.sort() ids.reverse() index = ids.index(self.getId()) previous = index + 1 if previous >= 0: return map(lambda x,self=self:getattr(self, x), ids[previous:]) return None security.declareProtected(ViewForum, 'in_reply_to') def in_reply_to(self): """ Return the id of the object for which this object is a reply. """ parent = self.aq_inner.aq_parent if parent.meta_type=='Post': return parent.getId() else: return '' security.declareProtected(ViewForum, 'childMessageCount') def childMessageCount(self): """ Counts the number of chilren of this message, including this message""" count=1 # this message for message in self.children(): count = count + message.childMessageCount() return count security.declareProtected(ViewForum, 'threadMessageCount') def threadMessageCount(self): """ Looks at the topmost ancestor, and then counts children""" message_count = self.thread_top().childMessageCount() return message_count security.declareProtected(ViewForum, 'childNewestMessageDate') def childNewestMessageDate(self): """ Looks through children and returns the newest message date""" newest=self.created for message in self.children(): test_date = message.childNewestMessageDate() if test_date > newest: newest = test_date return newest security.declareProtected(ViewForum, 'threadNewestMessageDate') def threadNewestMessageDate(self): """ Looks at the topmost ancestor, and then recurses through the messages finding the last created message""" newest=self.thread_top().childNewestMessageDate() return newest security.declareProtected(ViewForum, 'hasAttachments') def hasAttachments(self): """ Boolean value to test if the message has any attachment. """ return self.contentIds(['Portal File', 'Portal Image']) security.declareProtected(ViewForum, 'getAttachments') def getAttachments(self): """ Return a list of attachment objects. """ return self.contentValues(['Portal File', 'Portal Image']) security.declareProtected(AddForumPosting, 'addPost') def addPost(self, id='', title='', text='', author='', email='', file=None, REQUEST=None, text_format='structured-text'): """Add a Post to its container""" if not id: id = generate_id(self) item = Post(id, title=title, text=text, author=author, email=email, text_format=text_format) self._setObject(id, item) post = getattr(self, id) post._setPortalTypeName('Post') if file and file.filename: id = string.split(file.filename, "\\")[-1] body = file.read() content_type = file.headers['Content-Type'] if guess_content_type(id, body)[0][0:5] == 'image': post.invokeFactory( 'Image', id=id, file=file, content_type=content_type) else: post.invokeFactory( 'File', id=id, file=file, content_type=content_type) if REQUEST is not None: return self.manage_main(self, REQUEST) addPost = Post.addPost Globals.InitializeClass(Post)