############################################################################## # # coreblogentry.py # Class for COREBlog2 Entry # # Copyright (c) 2005 Atsushi Shibata(shibata@webcore.co.jp). # All Rights Reserved. # # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose and without fee is hereby granted, provided that # the above copyright notice appear in all copies and that both that copyright # notice and this permission notice appear in supporting documentation, and that # the name of Atsushi Shibata not be used in advertising or publicity pertaining # to distribution of the software without specific, written prior permission. # # ATSUSHI SHIBAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO # EVENT SHALL SHIBAT ATSUSHI BE LIABLE FOR ANY SPECIAL, INDIRECT OR # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF # USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. # #$Id: coreblogentry.py 164 2006-01-25 06:54:56Z ats $ # ############################################################################## #Base classes from Products.ATContentTypes.content.base import ATCTContent,\ ATCTMixin,updateActions,updateAliases,translateMimetypeAlias from Products.ATContentTypes.content.schemata import ATContentTypeSchema,\ finalizeATCTSchema from Products.ATContentTypes.interfaces import IATBTreeFolder from Products.Archetypes.public import Schema,registerType from Products.ATContentTypes.lib.historyaware import HistoryAwareMixin from Products.ATContentTypes.interfaces import IATDocument #Fields from Products.Archetypes.public import StringField,TextField,IntegerField,\ ReferenceField,LinesField #Widgets from Products.Archetypes.public import StringWidget,TextAreaWidget,\ IntegerWidget,RichWidget,\ ReferenceWidget,SelectionWidget,\ IdWidget from Products.Archetypes.utils import DisplayList from Products.Archetypes.public import AnnotationStorage #Marshaller from Products.Archetypes.public import RFC822Marshaller #Custom widget from Products.COREBlog2.COREBlogWidgets import CategoryWidget from Products.Archetypes.public import BaseSchema, Schema from Products.Archetypes.public import StringField,IntegerField from Products.Archetypes.public import StringWidget from Products.Archetypes.public import BaseContent, registerType from Products.Archetypes.Marshall import PrimaryFieldMarshaller from Products.CMFCore import CMFCorePermissions from Products.CMFCore.utils import getToolByName from Products.COREBlog2.config import PROJECTNAME,\ comemnt_rel_id,trackback_rel_id,\ coreblogentry_meta_type,coreblogcomment_meta_type,\ coreblogtrackback_meta_type from Products.COREBlog2.configuration import zconf from AccessControl import ClassSecurityInfo from Products.CMFCore.CMFCorePermissions import View,ModifyPortalContent from sets import Set description_length = zconf.coreblog2.description_length comment_status = ( (1, "None(cannot add,hidden)", "label_entrycommentstatus_none"), (2, "Open(can add,shown)", "label_entrycommentstatus_open"), (3, "Closed(cannot add,shown)", "label_entrycommentstatus_closed"), ) trackback_status = ((1, "None(cannot add,hidden)", "label_entrycommentstatus_none"), (2, "Open(can add,shown)", "label_entrycommentstatus_open"), (3, "Closed(cannot add,shown)", "label_entrycommentstatus_closed"), ) COREBlogEntrySchema = ATContentTypeSchema.copy() + Schema(( StringField('id', required=True, widget=IdWidget(label='ID', description='', label_msgid='label_entry_id', description_msgid='help_entry_id', i18n_domain='plone', size=60), ), #Entry contents StringField('subtitle', searchable=True, index='TextIndex', widget=StringWidget(label='Subtitle', description='', label_msgid='label_subtitle', description_msgid='help_subtitle', i18n_domain='plone', size=60), ), TextField('body', required=True, primary=True, searchable=True, index='TextIndex', default_output_type='text/html', default_content_type='text/html', allowable_content_types = zconf.coreblog2.allowed_content_types , widget=RichWidget(label='Body', description='', label_msgid='label_body', description_msgid='help_body', i18n_domain='plone', allow_file_upload=False, cols=40,rows=30), ), TextField('extend', searchable=True, index='TextIndex', default_output_type='text/html', default_content_type='text/html', allowable_content_types = zconf.coreblog2.allowed_content_types , widget=RichWidget(label='Extend', description='', label_msgid='label_extend', description_msgid='help_extend', i18n_domain='plone', allow_file_upload=False, cols=40,rows=5), schemata='cbentry_extented_fields', ), #Category LinesField('entry_categories', required=True, widget=CategoryWidget(label='Category(s)', description='', label_msgid='label_entry_categories', description_msgid='help_entry_categories', i18n_domain='plone',), index='KeywordIndex', ), #Comments ReferenceField('entry_comments', multivalued=True, relationship=comemnt_rel_id, allowed_types=(coreblogcomment_meta_type,), widget=ReferenceWidget(label='Comment references', label_msgid='label_entry_comments', description_msgid='help_entry_comments', visible={'view':'invisible','edit':'hidden'}, i18n_domain='plone',), ), #Trackbacks ReferenceField('entry_trackbacks', multivalued=True, relationship=trackback_rel_id, allowed_types=(coreblogtrackback_meta_type,), ), TextField('trackback_url', searchable=0, default_output_type='text/plain', widget=TextAreaWidget(label='Trackback', description='', label_msgid='label_trackback', description_msgid='help_trackback', i18n_domain='plone', allow_file_upload=False, #visible={'view':'invisible','edit':'hidden'}, cols=40,rows=3), ), TextField('sent_trackback_url', searchable=False, default_output_type='text/plain', ), IntegerField('allow_comment', searchable=0, default = 2, widget=SelectionWidget(label='Comment status', label_msgid='label_allow_comment', description_msgid='help_allow_comment', i18n_domain='plone',), vocabulary=DisplayList(comment_status), schemata='cbentry_extented_fields', ), IntegerField('receive_trackback', searchable=0, default = 2, widget=SelectionWidget(label='Trackback status', label_msgid='label_receive_trackback', description_msgid='help_receive_trackback', i18n_domain='plone',), vocabulary=DisplayList(trackback_status), schemata='cbentry_extented_fields', ), IntegerField('media_position', searchable=0, default = 1, widget=SelectionWidget(label='Media position', description='', label_msgid='label_media_position', description_msgid='help_media_position', i18n_domain='plone',), vocabulary=DisplayList(( (1, "Top,horizontal", "label_media_position_top"), (2, "Bottom,horizontal", "label_media_position_bottom"), (3, "Left,vertical", "label_media_position_left"), (4, "Right,vertical", "label_media_position_right"), )) ), StringField('media_size', searchable=0, default = 'thumb', widget=SelectionWidget(label='Media size', description='', label_msgid='label_media_size', description_msgid='help_media_size', i18n_domain='plone',), vocabulary=DisplayList(( ('large', "Large (768,768)", "label_media_size_large"), ('preview', "Prenew (400,400)", "label_media_size_preview"), ('mini', "Mini (200,200)", "label_media_size_mini"), ('thumb', "Thumbnail (128,128)", "label_media_size_thumbnail"), ('no_resize', "No resize", "label_media_size_noresize"), )) ), ), marshall=RFC822Marshaller() ) finalizeATCTSchema(COREBlogEntrySchema) class COREBlogEntry(ATCTContent): """ This is an Entry class for coreblog2 """ schema = COREBlogEntrySchema archetype_name = "COREBlog Entry" meta_type = coreblogentry_meta_type typeDescription= 'A Entry for COREBlog2.' typeDescMsgId = 'coreblog2entry_description' content_icon = 'coreblogentry_icon.gif' # Not to be shown at add item menu global_allow = False allow_discussion = False __implements__ = (ATCTContent.__implements__, IATDocument, HistoryAwareMixin.__implements__, ) security = ClassSecurityInfo() security.declareObjectProtected(View) # Set up views default_view = 'cbentry_view' immediate_view = 'cbentry_view' _at_rename_after_creation = True actions = updateActions(ATCTMixin,({ 'id': 'comments', 'name': 'Comments', 'action': 'string:${object_url}/cbentry_comments', 'permissions': (CMFCorePermissions.ModifyPortalContent,), }, { 'id': 'trackbacks', 'name': 'Trackbacks', 'action': 'string:${object_url}/cbentry_trackbacks', 'permissions': (CMFCorePermissions.ModifyPortalContent,), }, )) aliases = updateAliases(ATCTContent, { 'edit' : 'cbentry_edit', 'view' : 'cbentry_view' }) # Not to be shown at navigation exclude_from_nav = True # Constants comment_none = 1 comment_open = 2 comment_closed = 3 trackback_none = 1 trackback_open = 2 trackback_closed = 3 def initializeArchetype(self, **kwargs): ATCTContent.initializeArchetype(self, **kwargs) self._ping_sent = False security.declarePrivate('manage_beforeDelete') def manage_beforeDelete(self, item, container): # # Hook method, called before object deletion # # Get list for ids of referenced comments,trackbacks ids = [obj.id for obj in self.getComment()] ids += [obj.id for obj in self.getTrackback()] # Now delete them all! self.aq_parent.getCommentFolder().manage_delObjects(ids) # Call superclass method ATCTContent.manage_beforeDelete(self,item, container) security.declareProtected(ModifyPortalContent, 'setBody') def setBody(self, value, **kwargs): # # The setter for body, also making copy to 'description', # and store 'flat_descriptin' for trackback auto discoverty # self.getField('body').set(self, value, **kwargs) transformer = getToolByName(self,'portal_transforms') flat_desc = transformer.convertToData('text/plain',self.getBody()) orig_len = len(flat_desc) flat_desc = unicode(flat_desc,'utf-8','ignore') flat_desc = flat_desc[:description_length].encode('utf-8') flat_desc = flat_desc.replace('\n','') flat_desc = flat_desc.replace('\r','') if orig_len > description_length: flat_desc += '...' self.setDescription(flat_desc) # # Codes for CMF compatibility # security.declareProtected(View, 'CookedBody') def CookedBody(self, stx_level='ignored'): """ Method for CMF compatibility, returns rendered body """ return self.getBody() security.declareProtected(ModifyPortalContent, 'EditableBody') def EditableBody(self): """ Method for CMF compatibility, returns raw body """ return self.getRawBody() security.declareProtected(ModifyPortalContent, 'setFormat') def setFormat(self, value): """ Method for CMF compatibility, set for body format """ if not value: value = 'text/html' else: value = translateMimetypeAlias(value) ATCTContent.setFormat(self, value) security.declareProtected(View, 'getObjSize') def getObjSize(self): return len(self.getBody() + self.getExtend()) security.declarePrivate('_renameAfterCreation') def _renameAfterCreation(self, check_auto_id=False): """ Renames an object like its normalized title. Overridden method """ if check_auto_id and not self._isIDAutoGenerated(self.getId()): # No auto generated id return False # Check if hook method is there if hasattr(self,'getEntryId'): new_id = self.getEntryId() if new_id: invalid_id = False check_id = getattr(self, 'check_id', None) if check_id is not None: invalid_id = check_id(new_id, required=1) else: # If check_id is not available just look for conflicting ids parent = aq_parent(aq_inner(self)) invalid_id = new_id in parent.objectIds() if not invalid_id: # Can't rename without a subtransaction commit when using # portal_factory! get_transaction().commit(1) self.setId(new_id) return new_id return BaseContent._renameAfterCreation(self, check_auto_id) # # Accessor to prev. next entry # security.declareProtected(View, 'getNextEntry') def getNextEntry(self,full_objects=True): """ """ # # Returns next entry, according to date # parent = self.aq_parent.getCommentFolder() ent_list = parent.getNearestEntry(sort_order='normal',\ base_date=self.Date(),full_objects=full_objects) if len(ent_list) > 1: return ent_list[1] return None security.declareProtected(View, 'getPreviousEntry') def getPreviousEntry(self,full_objects=True): """ """ # # Returns previous entry, according to date # parent = self.aq_parent.getCommentFolder() ent_list = parent.getNearestEntry(sort_order='reverse',\ base_date=self.Date(),full_objects=full_objects) if len(ent_list) > 1: return ent_list[1] return None # # Comment Management # security.declareProtected(View, 'getComment') def getComment(self,full_objects=True): #Returns entry comments #Get refecences comment coms = self.getRefs(comemnt_rel_id) obj_list = [] #Set content filter path = {} path['depth'] = 0 contentFilter = {'path':path,'portal_type':coreblogcomment_meta_type} for com in coms: path['query'] = '/'.join(com.getPhysicalPath()) #Tell catalog if comment will be shown in this context for cat_obj in \ self.queryCatalog(contentFilter,show_all=1): if full_objects: #Append comment object itself obj_list.append(cat_obj.getObject()) else: #Append catalog data obj_list.append(cat_obj) if full_objects: obj_list.sort(lambda x,y: cmp(x.Date(),y.Date())) else: obj_list.sort(lambda x,y: cmp(x.Date,y.Date)) return obj_list security.declareProtected(View, 'countComment') def countComment(self): #Returns entry comment count #Get refecences comment coms = self.getRefs(comemnt_rel_id) cnt = 0 #Set content filter path = {} path['depth'] = 0 contentFilter = {'path':path,'portal_type':coreblogcomment_meta_type} for com in coms: #Tell catalog if comment will be shown in this context path['query'] = \ '/'.join(com.getPhysicalPath()) if self.queryCatalog(contentFilter,show_all=False): cnt += 1 return cnt security.declareProtected(View, 'addComment2Entry') def addComment2Entry(self,author='',email='',url='',title='', body='',post_ip='',id='',REQUEST=None): #Add comment and reference to entry #Further feature is gurded by Zope Security, #so some settings in security tab should be done #for anonymous comment or something. # Check for comment status if self.getAllow_comment() != self.comment_open: return None type_name=coreblogcomment_meta_type parent = self.aq_parent.getCommentFolder() # Call factory method to create comment object if not id: id = parent.generateUniqueId() new_id = parent.invokeFactory(id=id, type_name=type_name) if new_id is None or new_id == '': new_id = id # Get new comment object com = parent[id] if not post_ip and REQUEST and REQUEST.has_key('REMOTE_ADDR'): post_ip = REQUEST['REMOTE_ADDR'] # Set attributes com.setAuthor(author) com.setEmail(email) com.setUrl(url) com.setTitle(title) com.setBody(body) com.setPost_ip(post_ip) com.setEffectiveDate('') # Make sure to be catalloged com.indexObject() # Add reference self.addReference(com,comemnt_rel_id) return new_id # # PING Management # security.declareProtected(ModifyPortalContent, 'setBody') def setPingStatus(self,state=True): self._ping_sent = state security.declareProtected(View, 'hasPingSent') def hasPingSent(self): try: return self._ping_sent except: self._ping_sent = True return True # # Trackback Management(Receiving) # security.declareProtected(View, 'getTrackback') def getTrackback(self,full_objects=True): #Get refecences trackback tbs = self.getRefs(trackback_rel_id) obj_list = [] #Set content filter path = {} path['depth'] = 0 contentFilter = {'path':path,'portal_type':coreblogtrackback_meta_type} for tb in tbs: path['query'] = \ '/'.join(tb.getPhysicalPath()) #Tell catalog if trackback will be shown in this context for tb_obj in \ self.queryCatalog(contentFilter,show_all=1): if full_objects: #Append trackback object itself obj_list.append(tb_obj.getObject()) else: #Append catalog data obj_list.append(tb_obj) if full_objects: obj_list.sort(lambda x,y: cmp(x.Date(),y.Date())) else: obj_list.sort(lambda x,y: cmp(x.Date,y.Date)) return obj_list security.declareProtected(View, 'countTrackback') def countTrackback(self): #Returns entry trackback count tbs = self.getRefs(trackback_rel_id) cnt = 0 path = {} path['depth'] = 0 contentFilter = {'path':path} for tb in tbs: path['query'] = \ '/'.join(tb.getPhysicalPath()) if self.queryCatalog(contentFilter,show_all=1): cnt += 1 return cnt security.declareProtected(View, 'addTrackback2Entry') def addTrackback2Entry(self,title='',url='',blog_name='', excerpt='',post_ip='',id='',REQUEST=None): #Add trackback and reference to entry #Further feature is gurded by Zope Security, #so some settings in security tab should be done #for anonymous trackback or something. # Check for trackback status if self.getReceive_trackback() != self.trackback_open: return None type_name=coreblogtrackback_meta_type parent = self.aq_parent.getCommentFolder() # Call factory method to create trackback object if not id: id = parent.generateUniqueId() new_id = parent.invokeFactory(id=id, type_name=type_name) if new_id is None or new_id == '': new_id = id # Get new trackback object tb = parent[id] if not post_ip and REQUEST and REQUEST.has_key('REMOTE_ADDR'): post_ip = REQUEST['REMOTE_ADDR'] # Set attributes tb.setTitle(title) tb.setUrl(url) tb.setBlog_name(blog_name) tb.setExcerpt(excerpt) tb.setPost_ip(post_ip) # Make sure to be catalloged tb.indexObject() # Add reference self.addReference(tb,trackback_rel_id) return new_id # # Trackback Management(Sending) # security.declareProtected(ModifyPortalContent, 'getUnsentTrackbacks') def getUnsentTrackbacks(self): """ Find if there is unsent trackback, if there, return true. """ tobesent = Set(self.getTrackback_url().split('\n')) alreadysent = Set(self.getSent_trackback_url().split('\n')) # Get unique unsent trackback url(s) unsent = tobesent - alreadysent return list(unsent) security.declareProtected(ModifyPortalContent, 'sendTrackbacks') def sendTrackbacks(self,idx = -1): """ Send trackback. If any idx served, only one trackback will be sent. If idx == -1, try to send all trackback. """ cbtool = getToolByName(self,'coreblog2_tool') tburls = self.getUnsentTrackbacks() idxlist = range(0,len(tburls)) if idx != -1: idxlist = [idx] senttbs = [] entry_url = self.absolute_url() if self.aq_parent.getTrackback_base(): repbase = self.aq_parent.absolute_url() entry_url = entry_url.replace(repbase,\ self.aq_parent.getTrackback_base()) blog_title = self.aq_parent.title rls = [] successfully_sent = [] for i in idxlist: tb = tburls[i] if tb: try: r = cbtool.sendTrackback(tb,self.title,entry_url, blog_title,self.Description()) rls.append(r) if len(r) >= 2 and r[0] == 0: # Trackback has been added successfully successfully_sent.append(tb) except: pass [ tburls.remove(x) for x in successfully_sent ] self.setTrackback_url('\n'.join(tburls)) self.setSent_trackback_url('\n'.join(successfully_sent)) return rls registerType(COREBlogEntry, PROJECTNAME)