##############################################################################
#
# 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 <shibata@webcore.co.jp>'
__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)

