コード例 #1
0
ファイル: __init__.py プロジェクト: tuchang/ZWiki
def manage_addWiki(self,
                   new_id,
                   new_title='',
                   wiki_type='basic',
                   REQUEST=None,
                   enter=0):
    """
    Create a new zwiki web of the specified type
    """
    # check for a configuration wizard
    if safe_hasattr(self, wiki_type + '_config'):
        if REQUEST:
            REQUEST.RESPONSE.redirect('%s/%s_config?new_id=%s&new_title=%s' % \
                                      (REQUEST['URL1'],
                                       wiki_type,
                                       urllib.quote(new_id),
                                       urllib.quote(new_title)))

    else:
        if wiki_type in self.listFsWikis():
            self.addWikiFromFs(new_id, new_title, wiki_type, REQUEST)
        elif wiki_type in self.listZodbWikis():
            self.addWikiFromZodb(new_id, new_title, wiki_type, REQUEST)
        else:
            if REQUEST:
                return MessageDialog(title=_('Unknown wiki type'),
                                     message='',
                                     action='')
            else:
                raise AttributeError, (
                    _('The requested wiki type does not exist.'))

        if REQUEST:
            folder = getattr(self, new_id)
            if safe_hasattr(folder, 'setup_%s' % (wiki_type)):
                REQUEST.RESPONSE.redirect(REQUEST['URL3'] + '/' + new_id +
                                          '/setup_%s' % (wiki_type))
            elif enter:
                # can't see why this doesn't work after addWikiFromFs
                #REQUEST.RESPONSE.redirect(getattr(self,new_id).absolute_url())
                REQUEST.RESPONSE.redirect(REQUEST['URL3'] + '/' + new_id + '/')
            else:
                try:
                    u = self.DestinationURL()
                except AttributeError:
                    u = REQUEST['URL1']
                REQUEST.RESPONSE.redirect(u + '/manage_main?update_menu=1')
        else:  # we're called programmatically through xx.manage_addProduct...
            return new_id
コード例 #2
0
ファイル: Mail.py プロジェクト: tuchang/ZWiki
    def mailhost(self):  # -> mailhost; depends on: folder context
        """
        Give the MailHost that should be used for sending mail, or None.

        This needs to just work, as follows: we want to find a real
        mailhost in a robust way, ie not relying only on a MailHost id,
        and acquiring it from a parent folder if necessary.  NB there are
        at least two kinds, a MaildropHost can be transaction-safe and
        prevents duplicates, a MailHost sends immediately and almost never
        sends duplicates in practice; we won't favour one or the other.
        So: look for the first object with Maildrop Host or Mail Host
        meta_type in this folder, then in the parent folder, and so on.
        When multiple mailhosts are found in one folder, choose the
        alphabetically first.
        """
        mhost = None
        folder = self.folder()
        # XXX folder might not have objectValues, don't know why (#938)
        while (not mhost) and folder and safe_hasattr(folder, 'objectValues'):
            mhostids = sorted(
                folder.objectIds(spec=[
                    'Mail Host', 'Secure Mail Host', 'Maildrop Host',
                    'Secure Maildrop Host'
                ]))
            if mhostids: mhost = folder[mhostids[0]]
            folder = getattr(folder, 'aq_parent', None)
        return mhost
コード例 #3
0
ファイル: Editing.py プロジェクト: tuchang/ZWiki
 def getSpamPatterns(self):
     """Fetch spam patterns from the global zwiki spam blacklist,
     or a local property. Returns a list of stripped non-empty
     regular expression strings.
     """
     if safe_hasattr(self.folder(), 'spampatterns'):
         return list(getattr(self.folder(),'spampatterns',[]))
     else:
         BLATHER('checking zwiki.org spam blacklist')
         req = urllib2.Request(
             ZWIKI_SPAMPATTERNS_URL, 
             None,
             {'User-Agent':'Zwiki %s' % self.zwiki_version()}
             )
         # have to set timeout this way for python 2.4. XXX safe ?
         saved = socket.getdefaulttimeout()
         socket.setdefaulttimeout(ZWIKI_SPAMPATTERNS_TIMEOUT)
         try:
             try:
                 response = urllib2.urlopen(req)
                 t = response.read()
             except urllib2.URLError, e:
                 BLATHER('failed to read blacklist, skipping (%s)' % e)
                 t = ''
         finally:
             socket.setdefaulttimeout(saved)
         return self.parseSpamPatterns(t)
コード例 #4
0
ファイル: OutlineSupport.py プロジェクト: tuchang/ZWiki
    def subtopicsEnabled(self, **kw):
        """
        Decide in a complicated way if this page should display it's subtopics.

        First, if the folder has a show_subtopics property (can acquire)
        and it's false, we will never show subtopics.  Otherwise look for
        a show_subtopics property
        - in REQUEST
        - on the current page (don't acquire)
        - on our primary ancestor pages, all the way to the top,
        and return the first one we find. Otherwise return true.
        """
        prop = 'show_subtopics'
        # disabled by a folder property ?
        if not getattr(self.folder(), prop, 1): return 0
        # specified by a request var ?
        if safe_hasattr(self, 'REQUEST') and self.REQUEST.has_key(prop):
            return self.REQUEST.get(prop) and 1
        # specified on this page ?
        if base_hasattr(self, prop): return getattr(self, prop) and 1
        # specified on a parent or elder ancestor ?
        for a in self.ancestorsAsList2():
            p = self.pageWithName(a)
            if base_hasattr(p, prop): return getattr(p, prop) and 1
        # not specified, use default
        return 1
コード例 #5
0
ファイル: Archive.py プロジェクト: tuchang/ZWiki
 def archiveFolder(self):
     """Get the archive subfolder, even called from within it."""
     if self.inArchiveFolder():
         return self.folder()
     elif safe_hasattr(self.folder().aq_base, ARCHIVE_FOLDER_ID):
         f = self.folder()[ARCHIVE_FOLDER_ID]
         if f.isPrincipiaFolderish:
             return f
     return None
コード例 #6
0
ファイル: Mail.py プロジェクト: tuchang/ZWiki
 def defaultMailinPageName(
         self):  # -> string | none; depends on self, folder
     """The name of the wiki's default destination page for mailins, or
     None.  This is specified by the default_mailin_page property, or
     is None if that property is blank, otherwise is the current page.
     """
     if safe_hasattr(self.folder(), 'default_mailin_page'):
         return self.folder().default_mailin_page or None
     else:
         return self.pageName()
コード例 #7
0
ファイル: Editing.py プロジェクト: tuchang/ZWiki
 def fileOrImageLink():
     folderurl = self.uploadFolder().absolute_url()
     shouldinline = (
         (content_type.startswith('image')
          and not (safe_hasattr(REQUEST,'dontinline') and REQUEST.dontinline)
          and size <= LARGE_FILE_SIZE))
     if shouldinline:
         return self.pageType().inlineImage(self,id,folderurl+'/'+id)
     else:
         return self.pageType().linkFile(self,id,folderurl+'/'+id)
コード例 #8
0
ファイル: OutlineSupport.py プロジェクト: tuchang/ZWiki
    def subtopicsPropertyStatus(self):
        """
        Get the status of the show_subtopics property on this page.

        no property:    -1 ("default")
        true property:   1 ("always")
        false property:  0 ("never")
        """
        if not safe_hasattr(self.aq_base, 'show_subtopics'): return -1
        else: return self.show_subtopics and 1
コード例 #9
0
 def revisionsFolder(self):
     """Get the revisions subfolder, even called from within it, or
     None if it does not exist yet."""
     if self.inRevisionsFolder():
         return self.folder()
     elif safe_hasattr(self.folder().aq_base, REVISIONS_FOLDER_ID):
         f = self.folder()[REVISIONS_FOLDER_ID]
         if f.isPrincipiaFolderish:
             return f
     return None
コード例 #10
0
ファイル: Editing.py プロジェクト: tuchang/ZWiki
    def checkEditConflict(self, timeStamp, REQUEST):
        """
        Warn if this edit would be in conflict with another.

        Edit conflict checking based on timestamps -
        
        things to consider: what if
        - we are behind a proxy so all ip's are the same ?
        - several people use the same cookie-based username ?
        - people use the same cookie-name as an existing member name ?
        - no-one is using usernames ?

        strategies:
        0. no conflict checking

        1. strict - require a matching timestamp. Safest but obstructs a
        user trying to backtrack & re-edit. This was the behaviour of
        early zwiki versions.

        2. semi-careful - record username & ip address with the timestamp,
        require a matching timestamp or matching non-anonymous username
        and ip.  There will be no conflict checking amongst users with the
        same username (authenticated or cookie) connecting via proxy.
        Anonymous users will experience strict checking until they
        configure a username.

        3. relaxed - require a matching timestamp or a matching, possibly
        anonymous, username and ip. There will be no conflict checking
        amongst anonymous users connecting via proxy. This is the current
        behaviour.
        """
        username = self.usernameFrom(REQUEST)
        if (timeStamp is not None and
            timeStamp != self.timeStamp() and
            (not safe_hasattr(self,'last_editor') or
             not safe_hasattr(self,'last_editor_ip') or
             username != self.lastEditor() or
             REQUEST.REMOTE_ADDR != self.lastEditorIp())):
            return 1
        else:
            return 0
コード例 #11
0
ファイル: Catalog.py プロジェクト: tuchang/ZWiki
    def catalog(self):
        """
        Return the catalog object used by this page, if any.

        By default, Zwiki looks for a catalog named 'Catalog' in this
        folder (will not acquire) or a 'portal_catalog' in this folder
        or above (will acquire). Revision objects never have a catalog.
        """
        if self.inRevisionsFolder(): return None
        folder = self.folder()
        folderaqbase = getattr(folder,'aq_base',
                               folder) # make tests work
        if safe_hasattr(folderaqbase,'Catalog') and safe_hasattr(folderaqbase.Catalog,'indexes'):
            return folder.Catalog
        else:
            try:
                # acquisition wrapper is explicit in plone 2.1/ATCT or
                # zope 2.8.1 (#1137)
                return folder.aq_acquire('portal_catalog')
            except AttributeError:
                return getattr(folder,'portal_catalog',None)
コード例 #12
0
ファイル: OutlineSupport.py プロジェクト: tuchang/ZWiki
 def ensureWikiOutline(self):
     """
     Ensure this wiki has an outline cache, for fast page
     hierarchy.  This gets called before any use of the outline
     cache, so that it is always present and current.  If it's
     missing or if it's one of the early types (which get lost
     during a folder rename or when moving pages to a new folder)
     or it it's a pre-0.61 non-unicode outline, rebuild it.
     """
     o = safe_hasattr(self.folder().aq_base,
                      'outline') and self.folder().outline or None
     if not o:
         self.rebuildWikiOutline()
コード例 #13
0
ファイル: Views.py プロジェクト: tuchang/ZWiki
    def editform(self, REQUEST=None, page=None, text=None):
        """
        Render the edit form (template-customizable).

        This is usually called by createform also, and can handle both
        editing and creating. The form's textarea contents may be specified.
        """
        if not self.checkSufficientId(REQUEST):
            return self.denied(
                _("Sorry, this wiki doesn't allow anonymous edits. Please configure a username in options first."
                  ))

        page = self.tounicode(self.urlunquote(page or ''))
        text = self.tounicode(text or '')
        if ((not page or page == self.pageName())
                and safe_hasattr(self, 'wl_isLocked') and self.wl_isLocked()):
            return self.davLockDialog()

        # what are we going to do ? set up page, text & action accordingly
        if not page:
            # no page specified - editing the current page
            action = 'Edit'
            page = self.pageName()
            if not self.checkPermission(Permissions.Edit, self):
                raise Unauthorized, (
                    _('You are not authorized to edit pages in this wiki.'))
            text = self.read()
        elif self.pageWithFuzzyName(page):
            # editing a different page
            return self.pageWithFuzzyName(page).editform(REQUEST)
        else:
            # editing a brand-new page
            action = 'Create'
            if not self.checkPermission(Permissions.Add, self.folder()):
                raise Unauthorized, (
                    _('You are not authorized to add pages in this wiki.'))

        # display the edit form - a dtml method or the builtin default
        # NB we redefine id as a convenience, so that one header can work
        # for pages and editforms
        # XXX can we simplify this/make dtml more version independent ?
        # NB 'id' and 'oldid' are no longer used, but provide them for
        # backwards compatibility with old templates

        return self.getSkinTemplate('editform')(self,
                                                REQUEST,
                                                page=page,
                                                text=text,
                                                action=action,
                                                id=page,
                                                oldid=self.id())
コード例 #14
0
ファイル: OutlineSupport.py プロジェクト: tuchang/ZWiki
    def updateWikiOutline(self):
        """
        Regenerate the wiki folder's cached outline object.

        The wiki's outline object (a PersistentOutline) is a
        representation of the page hierarchy, containing the same
        information as in the pages' parents properties but in a form
        easier to query. This method either generates a new one from the
        parents properties, or updates an old one trying to preserve the
        order of subtopics. Complications.

        This checks and corrects any invalid parents information.  It also
        loads all page objects from the ZODB, which is probably ok as this
        is not done frequently.
        """
        BLATHER('updating outline data for wiki', self.folder().getId())
        oldchildmap = {}
        # backwards compatibility
        # there have been three kinds of outline cache: folder attribute,
        # non-SimpleItem-based PersistentOutline object, and
        # SimpleItem-based PersistentOutline object
        # a pre-0.39 outline is just an attribute, delete (but save childmap)
        if (safe_hasattr(self.folder().aq_base, 'outline')
                and not 'outline' in self.folder().objectIds()):
            oldchildmap = self.folder().outline.childmap()
            del self.folder().outline
        # if there's no outline object, make one
        if not safe_hasattr(self.folder().aq_base, 'outline'):
            self.folder()._setObject('outline', PersistentOutline())
            self.folder().outline.setChildmap(oldchildmap)
        # regenerate the parentmap
        parentmap = {}
        for p in self.pageObjects():
            p.ensureValidParents()  # poor caching
            parentmap[p.pageName()] = p.getParents()
        self.folder().outline.setParentmap(parentmap)
        # update the childmap (without losing subtopics order) and nesting
        self.folder().outline.update()
コード例 #15
0
ファイル: Views.py プロジェクト: tuchang/ZWiki
def loadDtmlMethod(name, dir='skins/zwiki'):
    """
    Load the named dtml method from the filesystem and return it, or None.
    """
    f = os.path.join(abszwikipath(dir), name)
    if os.path.exists('%s.dtml' % f):
        # need this one for i18n gettext patch to work ?
        #dm = DTMLFile(os.path.join(dir,name), globals())
        dm = HTMLFile(f, globals())
        # work around some (2.7 ?) glitch
        if not safe_hasattr(dm, 'meta_type'):
            dm.meta_type = 'DTML Method (File)'
        return dm
    else:
        return None
コード例 #16
0
ファイル: Mail.py プロジェクト: tuchang/ZWiki
 def _getSubscribers(
     self,
     parent=0
 ):  # -> [string]; depends on self, folder; modifies self, folder
     """
     Return a copy of this page's subscriber list, as a list.
     
     With parent flag, manage the parent folder's subscriber list instead.
     """
     if AUTO_UPGRADE: self._upgradeSubscribers()
     if parent:
         if safe_hasattr(self.folder(), 'subscriber_list'):
             return stripList(self.folder().subscriber_list)
         else:
             return []
     else:
         return list(self.subscriber_list)
コード例 #17
0
ファイル: Views.py プロジェクト: tuchang/ZWiki
    def addSkinTo(self, body, **kw):
        """
        Add the main wiki page skin to some body text, unless 'bare' is set.

        XXX used only for the main page view. Perhaps a wikipage view
        method should replace it ? Well for now this is called by the page
        type render methods, which lets them say whether the skin is
        applied or not.
        """
        REQUEST = getattr(self, 'REQUEST', None)
        if (safe_hasattr(REQUEST, 'bare') or kw.has_key('bare')):
            return body
        else:
            return self.getSkinTemplate('wikipage')(self,
                                                    REQUEST,
                                                    body=body,
                                                    **kw)
コード例 #18
0
ファイル: Mail.py プロジェクト: tuchang/ZWiki
 def emailAddressFrom(self,
                      subscriber):  # -> string; depends on cmf/plone site
     """
     Convert a zwiki subscriber list entry to an email address.
     
     A zwiki subscriber list entry can be: an email address, or a CMF
     member id (if we are in a CMF/Plone site), or either of those with
     ':edits' appended.  We figure out the bare email address and
     return it (lower-cased), or if we can't, return None.
     """
     if not subscriber or type(subscriber) != StringType:
         return None
     subscriber = re.sub(r':edits$', '', subscriber)
     if isEmailAddress(subscriber):
         email = subscriber
     elif self.inCMF():
         #and not self.portal_membership.isAnonymousUser()
         # don't look up member email addresses if user is anonymous ?
         # No I think it's better to minimise confusion due to
         # authenticated vs. unauthenticated (un)subscriptions, even if
         # it allows an anonymous visitor to unsubscribe a member whose
         # address they know
         from Products.CMFCore.utils import getToolByName
         membership = getToolByName(self, 'portal_membership')
         memberdata = getToolByName(self, 'portal_memberdata')
         member = membership.getMemberById(subscriber)
         if not member:
             # also check for a pseudo-member (a user acquired from above)
             # NB doesn't work with CMFMember
             if safe_hasattr(memberdata, '_members'):
                 member = memberdata._members.get(subscriber, None)
         # dumb robust fix for http://zwiki.org/1400
         try:
             email = member.getProperty('email',
                                        getattr(member, 'email', ''))
         except AttributeError:
             email = getattr(member, 'email', '')
     else:
         email = ''
     return email.lower() or None
コード例 #19
0
ファイル: Views.py プロジェクト: tuchang/ZWiki
    def setCMFSkin(self, REQUEST, skin):
        """
        Change the user's CMF/Plone skin preference, if possible.
        """
        # are we in a CMF site ?
        if not self.inCMF(): return
        portal_skins = self.portal_url.getPortalObject().portal_skins
        portal_membership = self.portal_url.getPortalObject().portal_membership

        # does the named skin exist ?
        def hasSkin(s):
            return portal_skins.getSkinPath(s) != s

        if not hasSkin(skin): return
        # is the user logged in ? if not, return harmlessly
        member = portal_membership.getAuthenticatedMember()
        if not safe_hasattr(member, 'setProperties'): return
        # change their skin preference and reload page
        REQUEST.form['portal_skin'] = skin
        member.setProperties(REQUEST)
        portal_skins.updateSkinCookie()
        REQUEST.RESPONSE.redirect(REQUEST.get('URL1'))
コード例 #20
0
ファイル: Editing.py プロジェクト: tuchang/ZWiki
 def folderContains(self,folder,id):
     """check folder contents safely, without acquiring"""
     return safe_hasattr(folder.aq_base,id)
コード例 #21
0
ファイル: Editing.py プロジェクト: tuchang/ZWiki
 def requestHasFile(self,r):
     return (r and safe_hasattr(r,'file') and safe_hasattr(r.file,'filename') and r.file.filename)
コード例 #22
0
ファイル: Editing.py プロジェクト: tuchang/ZWiki
 def hasCreatorInfo(self):
     """True if this page already has creator attributes."""
     return (safe_hasattr(self,'creator') and
             safe_hasattr(self,'creation_time') and
             safe_hasattr(self,'creator_ip'))
コード例 #23
0
ファイル: Editing.py プロジェクト: tuchang/ZWiki
 def isDavLocked(self):
     return safe_hasattr(self,'wl_isLocked') and self.wl_isLocked()
コード例 #24
0
ファイル: Editing.py プロジェクト: tuchang/ZWiki
 def isExternalEditEnabled(self):
     return (safe_hasattr(self.getPhysicalRoot().misc_,'ExternalEditor') and
             self.checkPermission(Permissions.Edit, self) and
             self.checkPermission(Permissions.ExternalEdit, self))
コード例 #25
0
 def inCMF(self):
     """return true if this page is in a CMF site"""
     return safe_hasattr(self.aq_inner.aq_parent, 'portal_membership')
コード例 #26
0
ファイル: Mail.py プロジェクト: tuchang/ZWiki
    def _upgradeSubscribers(
            self):  # -> none; depends on self, folder; modifies self, folder
        """
        Upgrade old subscriber lists, both this page's and the folder's.

        Called as needed, ie on each access and also from ZWikiPage.upgrade()
        (set AUTO_UPGRADE=0 in Default.py to disable).
        
        XXX Lord have mercy! couldn't this be simpler
        """
        # upgrade the folder first; we'll check attributes then properties
        changed = 0
        f = self.folder().aq_base

        # migrate an old zwiki subscribers or wikifornow _subscribers attribute
        oldsubs = None
        if (safe_hasattr(f, 'subscribers')
                and type(f.subscribers) is StringType):
            if f.subscribers:
                oldsubs = re.sub(r'[ \t]+', r'', f.subscribers).split(',')
            try:
                del f.subscribers
            except KeyError:
                BLATHER('failed to delete self.folder().subscribers')
            changed = 1
        elif safe_hasattr(f, '_subscribers'):
            oldsubs = f._subscribers.keys()
            try:
                del f._subscribers
            except KeyError:
                BLATHER('failed to delete self.folder()._subscribers')
            changed = 1
        # ensure a subscriber_list attribute
        if not safe_hasattr(f, 'subscriber_list'): f.subscriber_list = []
        # transfer old subscribers to subscriber_list, unless it's already
        # populated in which case discard them
        if oldsubs and not f.subscriber_list: f.subscriber_list = oldsubs

        # update _properties
        props = map(lambda x: x['id'], f._properties)
        if 'subscribers' in props:
            f._properties = filter(lambda x: x['id'] != 'subscribers',
                                   f._properties)
            changed = 1
        if not 'subscriber_list' in props:
            f._properties = f._properties + \
                ({'id':'subscriber_list','type':'lines','mode':'w'},)

        if changed:
            BLATHER('upgraded %s folder subscriber list' % (f.id))

        # now do the page..
        changed = 0
        self = self.aq_base

        # migrate an old zwiki subscribers attribute
        oldsubs = None
        if (safe_hasattr(self, 'subscribers')
                and type(self.subscribers) is StringType):
            if self.subscribers:
                oldsubs = re.sub(r'[ \t]+', r'', self.subscribers).split(',')
            try:
                del self.subscribers
            except KeyError:
                BLATHER('failed to delete %s.subscribers' % (self.id()))
            changed = 1
        # copy old subscribers to subscriber_list, unless it's already
        # got some
        # XXX merge instead
        if oldsubs and not self.subscriber_list:
            self.subscriber_list = oldsubs

        # migrate a wikifornow _subscribers attribute
        oldsubs = None
        if safe_hasattr(self, '_subscribers'):
            oldsubs = self._subscribers.keys()
            try:
                del self._subscribers
            except KeyError:
                BLATHER('failed to delete %s._subscribers' % (self.id()))
            changed = 1
        if oldsubs and not self.subscriber_list:
            self.subscriber_list = oldsubs

        # update _properties
        props = map(lambda x: x['id'], self._properties)
        if 'subscribers' in props:
            self._properties = filter(lambda x: x['id'] != 'subscribers',
                                      self._properties)
            changed = 1
        if not 'subscriber_list' in props:
            self._properties = self._properties + \
                ({'id':'subscriber_list','type':'lines','mode':'w'},)

        if changed:
            BLATHER('upgraded %s subscriber list' % (self.id()))
コード例 #27
0
 def inPlone(self):
     """return true if this page is in a Plone site"""
     return self.inCMF() and safe_hasattr(self.aq_inner.aq_parent,
                                          'plone_utils')
コード例 #28
0
ファイル: Admin.py プロジェクト: tuchang/ZWiki
    def upgrade(self, REQUEST=None):
        """
        Upgrade an old page instance (and possibly the folder as well).

        Called on every page view (set AUTO_UPGRADE=0 in Default.py to
        prevent this).  You could also call this on every page in your
        wiki to do a batch upgrade. Affects bobobase_modification_time. If
        you later downgrade zwiki, the upgraded pages may not work so
        well.
        """
        # Note that the objects don't get very far unpickling, some
        # by-hand adjustment via command-line interaction is necessary
        # to get them over the transition, sigh. --ken
        # not sure what this means --SM

        # What happens in the zodb when class definitions change ? I think
        # all instances in the zodb conform to the new class shape
        # immediately on refresh/restart, but what happens to
        # (a) old _properties lists ? not sure, assume they remain in
        # unaffected and we need to add the new properties
        # and (b) old properties & attributes no longer in the class
        # definition ?  I think these lurk around, and we need to delete
        # them.

        changed = 0

        # As of 0.17, page ids are always canonicalIdFrom(title); we'll
        # rename to conform with this where necessary
        # too slow!
        # changed = self.upgradeId()

        # fix up attributes first, then properties
        # NB be a bit careful about  acquisition while doing this

        # migrate a WikiForNow _st_data attribute
        if safe_hasattr(self.aq_base, '_st_data'):
            self.raw = self._st_data
            del self._st_data
            changed = 1

        # upgrade old page types
        pagetype = self.pageTypeId()
        if pagetype in PAGE_TYPE_UPGRADES.keys():
            self.setPageType(self.modernPageTypeFor(pagetype))
            # clear render cache; don't bother prerendering just now
            self.clearCache()
            changed = 1

        # Pre-0.9.10, creation_time has been a string in custom format and
        # last_edit_time has been a DateTime. Now both are kept as
        # ISO 8601-format strings. Might not be strictly necessary to upgrade
        # in all cases.. will cause a lot of bobobase_mod_time
        # updates.. do it anyway.
        if not self.last_edit_time:
            self.last_edit_time = self.bobobase_modification_time().ISO8601()
            changed = 1
        elif type(self.last_edit_time) is not StringType:
            self.last_edit_time = self.last_edit_time.ISO8601()
            changed = 1
        elif len(self.last_edit_time) != 25:
            try:
                if len(self.last_edit_time) == 19:  # older "ISO()" format
                    # we're using the behaviour that was standard in
                    # Zope <= 2.9, where a timestamp without timezone
                    # information was assumed to be in UTC (aka GMT)
                    self.last_edit_time = \
                    DateTime(self.last_edit_time+' GMT+0').ISO8601()
                else:
                    # some other timestamp format, leave tz information
                    # untouched, or let DateTime guess at it
                    self.last_edit_time = \
                    DateTime(self.last_edit_time).ISO8601()
                changed = 1
            except DateTimeSyntaxError:
                # can't convert to ISO 8601, just leave it be
                pass

        # If no creation_time, just leave it blank for now.
        # we shouldn't find DateTimes here, but check anyway
        if not self.creation_time:
            pass
        elif type(self.creation_time) is not StringType:
            self.creation_time = self.creation_time.ISO8601()
            changed = 1
        elif len(self.creation_time) != 25:
            try:
                if len(self.creation_time) == 19:  # older "ISO()" format
                    self.creation_time = \
                    DateTime(self.creation_time+' GMT+0').ISO8601()
                else:
                    self.creation_time = \
                    DateTime(self.creation_time).ISO8601()
                changed = 1
            except DateTimeSyntaxError:
                # can't convert to ISO 8601, just leave it be
                pass

        # _wikilinks, _links and _prelinked are no longer used
        for a in (
                '_wikilinks',
                '_links',
                '_prelinked',
        ):
            if safe_hasattr(self.aq_base, a):
                delattr(self, a)
                self.clearCache()
                changed = 1

        # update _properties
        # keep in sync with _properties above. Better if we used that as
        # the template (efficiently)
        oldprops = { # not implemented
            'page_type'     :{'id':'page_type','type':'string'},
            }
        newprops = {
            #'page_type'     :{'id':'page_type','type':'selection','mode': 'w',
            #                  'select_variable': 'ZWIKI_PAGE_TYPES'},
            'creator': {
                'id': 'creator',
                'type': 'string',
                'mode': 'r'
            },
            'creator_ip': {
                'id': 'creator_ip',
                'type': 'string',
                'mode': 'r'
            },
            'creation_time': {
                'id': 'creation_time',
                'type': 'string',
                'mode': 'r'
            },
            'last_editor': {
                'id': 'last_editor',
                'type': 'string',
                'mode': 'r'
            },
            'last_editor_ip': {
                'id': 'last_editor_ip',
                'type': 'string',
                'mode': 'r'
            },
            'last_edit_time': {
                'id': 'creation_time',
                'type': 'string',
                'mode': 'r'
            },
            'last_log': {
                'id': 'last_log',
                'type': 'string',
                'mode': 'r'
            },
            'NOT_CATALOGED': {
                'id': 'NOT_CATALOGED',
                'type': 'boolean',
                'mode': 'w'
            },
        }
        props = map(lambda x: x['id'], self._properties)
        for p in oldprops.keys():
            if p in props:  # and oldprops[p]['type'] != blah blah blah :
                pass
                #ack!
                #self._properties = filter(lambda x:x['id'] != p,
                #                          self._properties)
                #changed = 1
                # XXX this does work in python 1.5 surely.. what's the
                # problem ?
        for p in newprops.keys():
            if not p in props:
                self._properties = self._properties + (newprops[p], )
                changed = 1

        # ensure parents property is a list
        if self.ensureParentsPropertyIsList(): changed = 1

        # call any extra upgrade actions eg from plugins
        if callHooks(upgrade_hooks, self): changed = 1

        if changed:
            # do a commit now so the current render will have the correct
            # bobobase_modification_time for display (many headers/footers
            # still show it)
            # XXX I don't think we need to dick around with commits any more
            #get_transaction().commit()
            BLATHER('upgraded ' + self.id())

        self.upgradeComments(REQUEST)

        # PageMailSupport does a bit more (merge here ?)
        self._upgradeSubscribers()

        # make sure there is a catalog for this wiki
        self.ensureCatalog()

        # make sure there is an up-to-date outline cache
        self.ensureWikiOutline()