############################################################################## # # coreblog2.py # Class for COREBlog2 Folder # # 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: coreblog2.py 152 2005-12-31 11:30:31Z ats $ #$URL$ #$Rev: 152 $ #$Date: 2005-12-31 20:30:31 +0900 (土, 31 12 2005) $ # ############################################################################## #Base classes from Products.ATContentTypes.content.base import ATCTBTreeFolder,\ ATCTMixin,updateActions,updateAliases from Products.ATContentTypes.content.folder import ATBTreeFolder,\ ATBTreeFolderSchema,\ finalizeATCTSchema from Products.ATContentTypes.interfaces import IATBTreeFolder from Products.Archetypes.public import Schema,registerType #Fields from Products.Archetypes.public import StringField,IntegerField,\ LinesField,ReferenceField,BooleanField #Widgets from Products.Archetypes.public import StringWidget,TextAreaWidget,\ IntegerWidget,SelectionWidget,BooleanWidget from Products.Archetypes.utils import DisplayList from Products.Archetypes.Marshall import PrimaryFieldMarshaller from Products.CMFPlone.migrations.migration_util import safeEditProperty from Products.COREBlog2.config import PROJECTNAME,\ comment_folder_id,catetory_folder_id,\ stuff_folder_id,images_folder_id,default_category_id,\ coreblog2_meta_type,coreblogentry_meta_type,\ coreblogcomment_meta_type,coreblogtrackback_meta_type,\ coreblogcategory_meta_type,far_far_past,far_far_future from Products.COREBlog2.content.coreblogentry\ import COREBlogEntry,comment_status,trackback_status from Products.COREBlog2.configuration import zconf from DateTime import DateTime from AccessControl import ClassSecurityInfo from Products.CMFCore.CMFCorePermissions import View,ListFolderContents,\ ModifyPortalContent from Products.CMFCore.utils import getToolByName import calendar import os.path import sys __doc__="""Blog Product for Plone 'COREBlog:COREBlog' $Id: coreblog2.py 152 2005-12-31 11:30:31Z ats $""" COREBLOG2_DIR = os.path.abspath(os.path.dirname(__file__)) COREBLOG2_DIR = os.path.join(COREBLOG2_DIR,os.path.pardir) __version__ = open(os.path.join(COREBLOG2_DIR,'version.txt')).read().strip() __product_version__ = "COREBlog2 " + __version__ __author__ = 'Atsushi Shibata ' __docformat__ = 'plaintext' COREBlog2Schema = ATBTreeFolder.schema.copy() + Schema(( #Basic settings StringField('long_description', searchable=1, widget=TextAreaWidget(label='Long description', description='', label_msgid='label_long_description', description_msgid='help_long_description', i18n_domain='plone', cols=60,rows=5), ), #Entry listing IntegerField('top_entry_count_setting', searchable=0, default = 1, widget=SelectionWidget(label='Entries per page', description='', label_msgid='label_top_entry_count_setting', description_msgid='help_top_entry_count_setting', i18n_domain='plone', ), vocabulary=DisplayList(( (1, 'In days','label_show_entry_in_days'), (2, 'In entry count','label_show_entry_in_count'), )), schemata='cbsettings_display', ), IntegerField('top_entry_count', searchable=0, default = zconf.coreblog2.top_entry_count_default, widget=IntegerWidget(label='Entries count', description='', label_msgid='label_top_entry_count', description_msgid='help_top_entry_count', i18n_domain='plone', ), schemata='cbsettings_display', ), IntegerField('portlet_item_count', searchable=0, default = zconf.coreblog2.portlet_item_count_default, widget=IntegerWidget(label='Items per portlet', description='', label_msgid='label_portlet_item_count', description_msgid='help_portlet_item_count', i18n_domain='plone', ), schemata='cbsettings_display', ), IntegerField('batch_size', searchable=0, default = zconf.coreblog2.batch_size_default, widget=IntegerWidget(label='Batch size', description='', label_msgid='label_batch_size', description_msgid='help_batch_size', i18n_domain='plone', ), schemata='cbsettings_display', ), #PING/Trackback StringField('trackback_base', searchable=0, widget=StringWidget(label='Trackback base URL', description='', label_msgid='label_trackback_base', description_msgid='help_trackback_base', i18n_domain='plone', size=60, ), schemata='cbsettings_entry', ), StringField('ping_servers', searchable=1, widget=TextAreaWidget(label='Ping servers', description='', label_msgid='label_ping_servers', description_msgid='help_ping_servers', i18n_domain='plone', cols=60,rows=5 ), schemata='cbsettings_entry', ), #Entry StringField('entrydate_format', searchable=0, default = '%Y/%m/%d', widget=StringWidget(label='Entrydate format', description='', label_msgid='label_entrydate_format', description_msgid='help_entrydate_format', i18n_domain='plone', size=60, ), schemata='cbsettings_display', ), StringField('body_default_format', searchable=0, default = 'text/html', widget=SelectionWidget(label='Default format for entry body', description='', label_msgid='label_body_default_format', description_msgid='help_body_default_format', i18n_domain='plone', ), vocabulary=zconf.coreblog2.allowed_content_types , schemata='cbsettings_entry', ), IntegerField('allow_comment_default', searchable=0, default = COREBlogEntry.comment_open, widget=SelectionWidget(label='Default comment status', label_msgid='label_allow_comment_default', i18n_domain='plone',), vocabulary=DisplayList(comment_status), schemata='cbsettings_entry', ), IntegerField('receive_trackback_default', searchable=0, default = COREBlogEntry.trackback_open, widget=SelectionWidget(label='Default trackback status', label_msgid='label_receive_trackback_default', i18n_domain='plone',), vocabulary=DisplayList(trackback_status), schemata='cbsettings_entry', ), BooleanField('dont_send_ping', searchable=1, default=zconf.coreblog2.dont_send_ping_default, widget=BooleanWidget(\ label='Don\'t send PING/Trackback when entry added.', description='', label_msgid='label_dont_send_ping', description_msgid='help_dont_send_ping', i18n_domain='plone', ), schemata='cbsettings_entry', ), #Comment validation BooleanField('comment_require_author', searchable=1, default=True, widget=BooleanWidget(label='Require author', description='', label_msgid='label_comment_require_author', description_msgid='help_comment_require_author', i18n_domain='plone', ), schemata='cbsettings_comment_trackback', ), BooleanField('comment_require_email', searchable=1, default=True, widget=BooleanWidget(label='Require email', description='', label_msgid='label_comment_require_email', description_msgid='help_comment_require_email', i18n_domain='plone', ), schemata='cbsettings_comment_trackback', ), BooleanField('comment_require_url', searchable=1, default=True, widget=BooleanWidget(label='Require URL', description='', label_msgid='label_comment_require_url', description_msgid='help_comment_require_url', i18n_domain='plone', ), schemata='cbsettings_comment_trackback', ), #Nortifications BooleanField('send_comment_notification', searchable=1, default=True, widget=BooleanWidget(label='Send a notification mail on new comment', description='', label_msgid='label_send_comment_notification', description_msgid='help_send_comment_notification', i18n_domain='plone', ), schemata='cbsettings_comment_trackback', ), BooleanField('send_trackback_notification', searchable=1, default=True, widget=BooleanWidget(label='Send a notification mail on new trackback', description='', label_msgid='label_send_trackback_notification', description_msgid='help_send_trackback_notification', i18n_domain='plone', ), schemata='cbsettings_comment_trackback', ), StringField('notify_from', searchable=1, widget=StringWidget(label='Notify From', description='', label_msgid='label_notify_from', description_msgid='help_notify_from', i18n_domain='plone', size=60), schemata='cbsettings_comment_trackback', ), StringField('notify_to', searchable=1, widget=TextAreaWidget(label='Notify To', description='', label_msgid='label_notify_to', description_msgid='help_notify_to', i18n_domain='plone', cols=60,rows=2), schemata='cbsettings_comment_trackback', ), ), ) #Finalize schema definition finalizeATCTSchema(COREBlog2Schema) class COREBlog2(ATBTreeFolder): """ This is a COREBlog2 class, base folder for COREBlog2 """ archetype_name = "COREBlog2" meta_type = coreblog2_meta_type content_icon = 'coreblog2folder_icon.gif' typeDescription= 'A root folder for COREBlog2, contains blog content, such as entry,comment,category etc.' typeDescMsgId = 'coreblog2folder_description' global_allow = True suppl_views = () schema = COREBlog2Schema allowed_content_types = [ 'COREBlogEntry', ] _at_rename_after_creation = True __implements__ = (ATCTBTreeFolder.__implements__, IATBTreeFolder) security = ClassSecurityInfo() # Set up views default_view = 'coreblog_view' immediate_view = 'coreblog_view' actions = updateActions(ATCTMixin, (\ { 'id': 'edit', 'name': 'Blog settings', 'action': 'string:${object_url}/edit', 'permissions': (View,) }, { 'id': 'entrylisting', 'name': 'Entries', 'action': 'string:${object_url}/entry_listing', 'permissions': (ListFolderContents,), }, { 'id': 'category', 'name': 'Categories', 'action': 'string:${object_url}/categories/', 'permissions': (ListFolderContents,) }, )) aliases = updateAliases(ATBTreeFolder, { 'folder_contents' : 'cbfolder_contents', 'view' : 'coreblog_view', 'edit' : 'blogsettings_edit', }) def initializeArchetype(self, **kwargs): ATBTreeFolder.initializeArchetype(self, **kwargs) # Set slot properties left_slots = zconf.coreblog2.left_slots_default safeEditProperty(self, 'left_slots', left_slots, 'lines') right_slots = zconf.coreblog2.right_slots_default safeEditProperty(self, 'right_slots', right_slots, 'lines') # create subfolders if not hasattr(self, comment_folder_id): # Folder for comments/trackbacks self.portal_types.constructContent('COREBlogCommentFolder', self, comment_folder_id, title='Comments') if not hasattr(self, catetory_folder_id): # Folder for categories self.portal_types.constructContent('COREBlogCategoryFolder', self, catetory_folder_id, title='Categories') ## Make default category #cat_folder = self[catetory_folder_id] #cat_folder.constructContent('COREBlogCategory', # self, # default_category_id, # title='Default') if not hasattr(self, stuff_folder_id): # Folder for some stuff self.portal_types.constructContent('Folder', self, stuff_folder_id, title='Stuff') if not hasattr(self, images_folder_id): # Folder for images self.portal_types.constructContent('Folder', self, images_folder_id, title='Images') self.indexObject() def canSetDefaultPage(self): return False security.declarePrivate('setLong_description') def setLong_description(self, value, **kwargs): # # The setter for long_description, also making copy to 'description', # self.getField('long_description').set(self, value, **kwargs) transformer = getToolByName(self,'portal_transforms') flat_desc = transformer.convertToData('text/plain',\ self.getLong_description()) orig_len = len(flat_desc) flat_desc = flat_desc.replace('\n','') flat_desc = flat_desc.replace('\r','') self.setDescription(flat_desc) security.declareProtected(View,'blog_url') def blog_url(self): return self.absolute_url() security.declareProtected(View,'blog_object') def blog_object(self): return self # # Catalog queries for object listing # security.declareProtected(View,'getRecentEntry') def getRecentEntry(self,type=0,limit=0,\ sort_on='Date',sort_order='reverse',\ full_objects=True): # # Returns recent entries # for entry list on blog's top page # # Make dictionary for query arguments path = {} path['query'] = '/'.join(self.getPhysicalPath()) path['depth'] = 1 args = {} args['path'] = path if not type: type = self.getTop_entry_count_setting() if not limit: limit = self.getTop_entry_count() args['meta_type'] = [coreblogentry_meta_type] args['sort_on'] = sort_on args['sort_order'] = sort_order if type == 1: # If type is 1,show entries for dates. # So we need to find date of most recent entry. args['sort_limit'] = 1 args['Date'] = (DateTime(),DateTime(far_far_past)) args['Date_usage'] = "range:max:min" recent_entries = self.portal_catalog(show_inactive=False,**args) if recent_entries: recent_date = recent_entries[0].Date else: recent_date = DateTime() args['Date'] = (DateTime(recent_date),DateTime(recent_date)-limit-1) args['Date_usage'] = "range:max:min" del args['sort_limit'] else: args['Date'] = (DateTime(),DateTime(far_far_past)) args['Date_usage'] = "range:max:min" args['sort_limit'] = limit # Initialize list to return objs = self.portal_catalog(show_inactive=False,**args) if full_objects: objs = [b.getObject() for b in objs] return objs security.declareProtected(View,'getRecentEntry') def synContentValues(self): # # play nice with portal_syndication_tool # return self.getRecentEntry() security.declareProtected(View,'getEntryInDate') def getEntryInDate(self,year,month,day=0,\ batch=False,b_size=0,b_start=0,\ sort_on='Date',sort_order='',\ full_objects=True): # # Return entryes in specific month,or day # Used for calender portlet and # # Make dictionary for query arguments path = {} path['query'] = '/'.join(self.getPhysicalPath()) path['depth'] = 1 args = {} args['meta_type'] = [coreblogentry_meta_type] args['path'] = path if day: startday = day endday = day else: #Find month daycount startday = 1 endday = calendar.monthrange(year,month)[1] start_date = DateTime("%d-%d-%d 00:00:00" % (year,month,startday)) end_date = DateTime("%d-%d-%d 23:59:59" % (year,month,endday)) if DateTime() < end_date: # Hide future entries end_date = DateTime() args['Date'] = (start_date,end_date) args['Date_usage'] = "range:min:max" args['sort_on'] = sort_on args['sort_order'] = sort_order # Initialize list to return objs = self.portal_catalog(show_inactive=False,**args) if full_objects: objs = [b.getObject() for b in objs] if batch: from Products.CMFPlone import Batch batch = Batch(objs, b_size, int(b_start), orphan=0) return batch return objs security.declareProtected(View,'getEntryForCalendar') def getEntryForCalendar(self, month, year): # # Returns entries on month, # For every days in calendar,if entry exists, # store data for mapping following... # {'day': #, 'entry': None} # year=int(year) month=int(month) daysByWeek=calendar.monthcalendar(year, month) weeks=[] enteirs_in_month=self.getEntryInDate(year,month,full_objects=False) entries = {} for entry in enteirs_in_month: day = DateTime(entry.Date).day() entries[day] = 1 for week in daysByWeek: days=[] for day in week: if entries.has_key(day): days.append({'day':day,'entry':1}) else: days.append({'day':day,'entry':None}) weeks.append(days) return weeks security.declareProtected(View,'getNearestEntry') def getNearestEntry(self,base_date,sort_order='reverse',\ sort_on='Date',full_objects=True): # # Returns nearest entries # # Make dictionary for query arguments path = {} path['query'] = '/'.join(self.getPhysicalPath()) path['depth'] = 1 args = {} args['path'] = path args['meta_type'] = [coreblogentry_meta_type] args['sort_on'] = sort_on args['sort_order'] = sort_order args['sort_limit'] = 2 if sort_order == 'reverse': args['Date'] = (DateTime(base_date),DateTime(far_far_past)) args['Date_usage'] = "range:max:min" else: args['Date'] = (DateTime(far_far_future),DateTime(base_date)) args['Date_usage'] = "range:max:min" # Initialize list to return objs = self.portal_catalog(show_inactive=False,**args) if full_objects: objs = [b.getObject() for b in objs] return objs security.declarePrivate('getRecentItem') def getRecentItem(self,limit=0,meta_types=[],\ sort_on='Date',sort_order='',full_objects=True): # Make dictionary for query arguments path = {} path['query'] = '/'.join(self.getCommentFolder().getPhysicalPath()) path['depth'] = 1 args = {} args['path'] = path args['Date'] = (DateTime(),DateTime(far_far_past)) args['Date_usage'] = "range:max:min" if not limit: limit = self.getPortlet_item_count() args['meta_type'] = meta_types args['sort_on'] = sort_on args['sort_order'] = sort_order args['sort_limit'] = limit # Initialize list to return objs = self.portal_catalog(show_inactive=False,**args) if full_objects: objs = [b.getObject() for b in objs] return objs security.declareProtected(View,'getRecentComment') def getRecentComment(self,limit=0,\ sort_on='Date',sort_order='reverse',\ full_objects=True): # # Returns recent comments # return self.getRecentItem(meta_types=[coreblogcomment_meta_type],\ sort_on=sort_on,sort_order=sort_order,\ full_objects=full_objects) security.declareProtected(View,'getRecentTrackback') def getRecentTrackback(self,limit=0,\ sort_on='Date',sort_order='reverse',\ full_objects=True): # # Returns recent trackback # return self.getRecentItem(meta_types=[coreblogtrackback_meta_type],\ sort_on=sort_on,sort_order=sort_order,\ full_objects=full_objects) # # Accessor for subfolders # security.declareProtected(View,'getCategoryFolder') def getCategoryFolder(self): """ Return Category Folder """ return self[catetory_folder_id] security.declareProtected(View,'getCommentFolder') def getCommentFolder(self): """ Return Comment Folder """ return self[comment_folder_id] # # Category management # security.declareProtected(View,'getEntryInCategory') def getEntryInCategory(self,category_ids,\ sort_on='Date',sort_order='',\ batch=False,b_size=0,b_start=0,\ full_objects=True): # # Return entryes in specific category(s) # # Make dictionary for query arguments path = {} path['query'] = '/'.join(self.getPhysicalPath()) path['depth'] = 1 args = {} args['path'] = path args['meta_type'] = [coreblogentry_meta_type] category_ids = [str(b) for b in category_ids] args['getEntry_categories'] = category_ids args['meta_type'] = [coreblogentry_meta_type] args['sort_on'] = sort_on args['sort_order'] = sort_order # Initialize list to return objs = self.portal_catalog(show_inactive=False,**args) if full_objects: objs = [b.getObject() for b in objs] if batch: from Products.CMFPlone import Batch batch = Batch(objs,b_size, int(b_start), orphan=0) return batch return objs security.declareProtected(View, 'getCategoryMap') def getCategoryMap(self,full_objects=True): #Returns category mapping cat_d = {} catfolder = self.getCategoryFolder() if full_objects: # Infrate real object for cat_obj in catfolder.objectValues(coreblogcategory_meta_type): cat_d[str(cat_obj.getInternal_id())] = \ {'title':cat_obj.title,'id':cat_obj.id, 'url':cat_obj.absolute_url(), 'obj':cat_obj } else: path = {} path['query'] = '/'.join(catfolder.getPhysicalPath()) path['depth'] = 1 contentFilter = {'path':path,\ 'portal_type':coreblogcategory_meta_type} for cat_obj in \ self.portal_catalog.queryCatalog(contentFilter,show_all=1): cat_d[str(cat_obj.getInternal_id)] = \ {'title':cat_obj.title,'id':cat_obj.id, 'url':catfolder.absolute_url() + '/' + cat_obj.id} return cat_d security.declareProtected(View, 'getCategoryById') def getCategoryById(self,id,full_objects=True): #Returns category by internal ID cat_folder = self[catetory_folder_id] path = {} path['query'] = '/'.join(cat_folder.getPhysicalPath()) path['depth'] = 1 contentFilter = {'path':path,'portal_type':coreblogcategory_meta_type} for cat_obj in \ self.portal_catalog.queryCatalog(contentFilter,show_all=1): if str(cat_obj.getInternal_id) == str(id): if full_objects: return cat_obj.getObject() else: return cat_obj return None # # Sending PING # security.declareProtected(ModifyPortalContent, 'sendPing') def sendPing(self,idx = -1): """ Send ping to servers on setting. If any idx served, only one ping will be sent. If idx == -1, try to send all ping servers. """ cbtool = getToolByName(self,'coreblog2_tool') blogurl = self.absolute_url() if self.getTrackback_base(): blogurl = self.getTrackback_base() svs = self.getPing_servers().split('\n') indexlist = range(0,len(svs)) if idx != -1: indexlist = [index] rl = [] for i in indexlist: if svs[i].find('http:') == 0: r = cbtool.sendPing(svs[i],self.title,blogurl, __product_version__) rl.append(r) return rl # # Misc methods # security.declareProtected(View, 'getEntryFieldOrder') def getEntryFieldOrder(self): # #Returns edit field order for entry # return ['id','title','subtitle','entry_categories','body','extend', 'allow_comment','receive_trackback','relatedItems', 'media_position','media_size'] registerType(COREBlog2,PROJECTNAME)