############################################################################## # # This software is released under the Zope Public License (ZPL) Version 1.0 # # Copyright (c) Digital Creations. All rights reserved. # Portions Copyright (c) 1999 by Butch Landingin. # Portions Copyright (c) 2000-2001 by Chris Withers. # ############################################################################## __version__='$Revision: 1.35 $'[11:-2] from Globals import Persistent from Globals import HTMLFile import Globals from BTrees.IIBTree import IISet from Squishfile import Squishfile from Acquisition import Implicit from time import time, localtime, strftime, gmtime from string import strip,split,join from string import lower,atoi from urllib import quote, unquote from Utility import CRLF, tagRegex, doAddPosting, getitem from SquishPermissions import ModeratePostings,AddPostings,View from stripogram import html2safehtml from DateTime import DateTime from OFS.Traversable import Traversable from AccessControl import ClassSecurityInfo class Posting(Persistent, Implicit, Traversable): """Squishdot Posting""" security = ClassSecurityInfo() security.setDefaultAccess("allow") meta_type='Posting' icon ='misc_/Squishdot/posting_img' root=0 # Default encoding for old postings encoding = 'HTML' # fields in this type of posting _fields=[] manage_editForm=HTMLFile('editPostingForm', globals()) security.declareProtected(ModeratePostings, 'manage_editForm') if hasattr(manage_editForm,'_setName'): manage_editForm._setName('manage_editForm') # Aliases for manage_editForm manage =manage_editForm manage_main =manage_editForm security.declarePrivate('__init__') def __init__(self, id, thread,level,reviewed): self.id =str(id) self.ids =IISet() self.thread =thread self.created =id self.modified=id self.level =level self.revsub =0 self.reply_cnt =0 self.reviewed=reviewed security.declarePrivate('index') def index(self): # index this posting is the Squishdot site that contains it. self.catalog_object(self,join(self.getPhysicalPath(),'/')) # recatalog all the postings in our thread path if self.thread: self.aq_parent.index() security.declareProtected(View, 'getFields') def getFields(self): """Return a list of fields that this posting has""" return self._fields security.declareProtected(View, 'getThread') def getThread(self, index): """A better abstaction rather than accessing the list directly""" return self.thread[index] security.declarePublic('__len__') def __len__(self): return 1 security.declareProtected(View, '__getitem__') __getitem__ = getitem security.declarePrivate('setItem') def setItem(self,id,obj,index=1): # Make sure the object we store is not wrapped with # an acquisition wrapper, since we will be wrapping # it again manually in __getitem__ when it is needed. bobj = getattr(obj,'aq_base',obj) self.ids.insert(id) self.data[id]=bobj #index the new posting if index: obj.index() security.declarePrivate('textToSearch') def textToSearch(self): # returns the text to search for a ZCatalog text='' for line in self.body: # strip out HTML and append a newline to each line. text = text+tagRegex.sub("",line)+'\n' return text security.declareProtected(View, 'date_posted') def date_posted(self,fmstr='%A %B %d, @%I:%M%p'): # """ date when article was posted """ ltime = localtime(self.created) return strftime(fmstr,ltime) # deprecated methods date_created = time_created = date_posted security.declareProtected(View, 'date') def date(self): """return the date of creation for indexing purposes""" return DateTime(self.created) security.declareProtected(View, 'body_len') def body_len(self,divisor=None): # """ total body length of text """ tlen = 0 if not self.body: tlen = 0 else: for line in self.body: tlen = tlen + len(line) if divisor is None: if tlen == 0: return '' if tlen > 51200: tlen = tlen / 1024 return str(tlen) + ' Kb' else: return str(tlen) + ' bytes' if divisor < 1: return tlen else: return tlen/divisor security.declareProtected(ModeratePostings, 'postingValues') def postingValues(self): # """ return all replies """ return self.data_map(self.ids) security.declareProtected(View, 'tpId') def tpId(self): return self.id security.declareProtected(View, 'tpURL') def tpURL(self): return self.id security.declareProtected(View, 'this') def this(self): return self security.declareProtected(View, 'has_items') def has_items(self): return len(self.ids) security.declarePrivate('sub_ids') def sub_ids(self,ids): map(ids.insert, self.ids) for item in self.data_map(self.ids): ids=item.sub_ids(ids) return ids security.declareProtected(View, 'desc_items') def desc_items(self): # """ return latest list of replies """ items = [] postings = map(self.__getitem__,self.ids) reviewed = filter(lambda p: p.reviewed, postings) for item in reviewed: items.append(item) items.extend(item.desc_items()) return items security.declareProtected(View, 'attachment') def attachment(self): # """ file attachment """ file=self.file return file and (file,) or None security.declareProtected(AddPostings, 'suggest_title') def suggest_title(self): # """ suggested title of reply """ t=self.title return (lower(t[:3])=='re:') and t or 'Re: %s' % t security.declareProtected(View, 'thread_path') def thread_path(self): return join(map(lambda x: '/%s' % x, self.thread), '') security.declareProtected(View, 'index_html') def index_html(self,REQUEST): """ squishdot article main page (the read more page) """ return self.posting_html(self,REQUEST) security.declarePrivate('doNotify') def doNotify(self, msg, REQUEST): # """ sends mail to notify person being replied to """ if self.notify and self.email: self.sendEmail(msg,self.email,REQUEST) security.declarePublic('cancelNotify') def cancelNotify(self, REQUEST): """ cancels email notification of replies """ self.notify='' return self.showMessage(self, REQUEST=REQUEST, title='Cancelled Notification', message='You will no longer be notified of replies to this message', action=self.absolute_url() ) security.declareProtected(AddPostings, 'dummyPosting') def dummyPosting(self): """ returns a dummy posting for the previewPosting method """ return Comment(0,[],0,1).__of__(self) security.declareProtected(AddPostings, 'addPosting') def addPosting(self, file='', REQUEST=None,RESPONSE=None): """ add a Comment """ return doAddPosting(self,file,REQUEST,RESPONSE, moderated='mod_comment', message ='Your reply has been posted', klass =Comment) def _processReviewed(self,reviewed): if self.mod_comment: self.set_reviewed(self,reviewed) security.declareProtected(ModeratePostings, 'edit') def edit(self,REQUEST=None,RESPONSE=None,delete_attachment=None,new_attachment='',reviewed=0,index=1): """ edit replies """ processed,message=self.validatePosting(raw=REQUEST) if message is not None: return self.showError(self, values=processed,title='Data Missing', message=message, action=self.REQUEST.HTTP_REFERER ) for field in self.getFields(): value = processed.get(field,'') if field in ['body','summary']: value=split(CRLF.sub('\n',value),'\n') setattr(self,field,value) self.notify = processed['notify'] self.encoding= processed['encoding'] have_new_file = (hasattr(new_attachment,'filename') and new_attachment.filename) if delete_attachment or have_new_file: # delete the old file try: delattr(self,self.aq_base.file.file_name()) except AttributeError: pass self.file='' if have_new_file: # store the new file file=Squishfile(new_attachment) setattr(self,file.file_name(),file) self.file=file self._processReviewed(reviewed) # change the created date date = REQUEST.get('date') if date is not None: self.created=int(date.timeTime()) # change the modified value self.modified = time() # re-catalog this posting if index: self.index() # should only get here if someone is editing a posting during moderation if RESPONSE: RESPONSE.redirect(self.REQUEST.HTTP_REFERER) # Used to display the body of the posting with the appropriate formatting security.declareProtected(View, 'showBody') def showBody(self): return self.render(self.body,self.encoding) # Return the plain text body of a posting suitable for mailing... security.declareProtected(View, 'plain_text') def plain_text(self): if self.encoding == 'HTML': return self.html2text(join(self.body,' ')) else: return join(self.body,'\n') security.declarePublic('getId') def getId(self): return self.id Globals.InitializeClass(Posting) # Comment has to be in this file to stop import infinite recursion class Comment(Posting): """ Kindof small, isn't it ;-)""" meta_type ='Comment' icon ='misc_/Squishdot/comment_img' _fields =['title','author','body','email'] Globals.InitializeClass(Comment)