ZWiki/0042755000175000017500000000000007654241746011174 5ustar simonsimonZWiki/Extensions/0042755000175000017500000000000007654241733013327 5ustar simonsimonZWiki/Extensions/Install.py0100644000175000017500000001013507642670501015276 0ustar simonsimon############################################################################## # # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ CMF ZWiki Installation script This file is a CMF installation script for ZWiki. It's meant to be used as an External Method. Compatible with the CMF quick installer, or to use manually, add an external method to the root of the CMF or Plone Site that you want ZWiki registered in with the configuration: id: cmf_install_zwiki title: module name: ZWiki.Install function name: install Then call this method in the context of the CMF/Plone site, by visiting http://SITEURL/cmf_install_zwiki . The install function will execute and give information about the steps it took. """ import string from cStringIO import StringIO from ZODB.PersistentMapping import PersistentMapping from Products.CMFCore.TypesTool import ContentFactoryMetadata from Products.CMFCore.DirectoryView import addDirectoryViews from Products.CMFCore.utils import getToolByName from Products.ZWiki.CMFInit import WikiFolder, wiki_globals, \ factory_type_information from Products.ZWiki.ZWikiPage import ZWikiPage from Products.ZWiki.Defaults import PAGE_PORTALTYPE, WIKI_PORTALTYPE def install(self): """ Register ZWikiPage and WikiFolder with portal_types and friends. """ out = StringIO() typestool = getToolByName(self, 'portal_types') skinstool = getToolByName(self, 'portal_skins') workflowtool = getToolByName(self, 'portal_workflow') # Borrowed from CMFDefault.Portal.PortalGenerator.setupTypes() # We loop through anything defined in the factory type information # and configure it in the types tool if it doesn't already exist for t in factory_type_information: if t['id'] not in typestool.objectIds(): cfm = apply(ContentFactoryMetadata, (), t) typestool._setObject(t['id'], cfm) out.write('Registered %s with the types tool\n' % t['id']) else: out.write('Object "%s" already existed in the types tool\n' % ( t['id'])) # Setup the skins # This is borrowed from CMFDefault/scripts/addImagesToSkinPaths.pys if 'zwiki_plone' not in skinstool.objectIds(): # We need to add Filesystem Directory Views for any directories # in our skins/ directory. These directories should already be # configured. addDirectoryViews(skinstool, 'skins', wiki_globals) out.write("Added zwiki skin directories to portal_skins\n") # Now we need to go through the skin configurations and insert # 'zwiki_plone'. Preferably, this should be right before where # 'content' is placed. Otherwise, we append it to the end. skins = skinstool.getSkinSelections() for skin in skins: path = skinstool.getSkinPath(skin) path = map(string.strip, string.split(path,',')) for dir in ('zwiki_plone',): if not dir in path: try: idx = path.index('custom') except ValueError: idx = 999 path.insert(idx+1, dir) path = string.join(path, ', ') # addSkinSelection will replace existing skins as well. skinstool.addSkinSelection(skin, path) out.write("Added 'zwiki_plone' to %s skin\n" % skin) # remove workflow for Wiki pages cbt = workflowtool._chains_by_type if cbt is None: cbt = PersistentMapping() cbt[PAGE_PORTALTYPE] = [] cbt[WIKI_PORTALTYPE] = [] workflowtool._chains_by_type = cbt out.write("Removed all workflow from Wiki Folders and Wiki Pages") return out.getvalue() ZWiki/Extensions/README0100644000175000017500000000076507543413137014206 0ustar simonsimonFiles in this directory are for use as zope external methods. To install one of these, in the ZMI add an External Method with:: id: anything, eg the filename without suffix title: module: ZWiki.modulename (modulename is filename without suffix) function: the name of the file's main function It may be necessary, with certain older zope versions, to copy the file to the Extensions subdirectory in your INSTANCE_HOME (or your main zope directory). Then it's just module: modulename. ZWiki/Extensions/__init__.py0100644000175000017500000000006307642354541015431 0ustar simonsimon# This file is there to make the module importable ZWiki/Extensions/fixprops.py0100644000175000017500000000176607636661460015563 0ustar simonsimonimport string def fixprops(self,properties,old,new): """ Do a text replace in certain properties of zwiki pages in this folder. For all zwiki pages in this folder, replace all occurrences of old with new in the specified property or properties (must be string properties). You could call this from your browser, eg like so: http://.../wikifolder/fixprops?properties:list=creation_time&properties:list=last_edit_time&old=Central Standard Time&new=CST Don't leave this method accessible to untrusted users. """ if not old: return if type(properties) == type(''): properties = (properties,) for page in self.pages(): for prop in properties: s = getattr(page,prop,'') if string.find(s,old) != -1: setattr(page,prop,string.replace(s,old,new)) self.REQUEST.RESPONSE.write( "changed %s.%s from %s to %s\n" % \ (page.id(),prop,s,getattr(page,prop))) ZWiki/Extensions/mailin.py0100644000175000017500000004007207636661757015164 0ustar simonsimon""" zwiki mailin - post an incoming email message to a wiki This is an external method for receiving mail from a mailer alias, procmail recipe or script via something like: | curl -n -F 'msg=<-' http://mysite/mywikifolder/mailin and posting it to a suitable zwiki page. It expects at least one argument, an RFC822 message. Here are the delivery rules: XXX see the latest CHANGES.txt for some updates to these. - if the message appears to be a zwiki mailout or from an auto-responder or junk, silently discard it - if called in a page context (http://site/wikipage/mailin), always use that page. Otherwise, - (DISABLED: if a recipient of the form ".*(wiki|mailin|tracker|bugs|issues)@virtualhost" (MAILINADDREXP) is found, where virtualhost matches an existing VirtualHostMonster entry, then in the corresponding folder, otherwise,) - in the current folder (which must contain at least one zwiki page), - unless called with subscribersonly=0 or the folder's mailin_policy property is 'open' (old posting_policy also supported), check that the sender is subscribed somewhere in the wiki; if not, bounce the message - decide which of the recipients is us, as follows: 1. if there's only one, use that one 2. or the first one whose address matches the folder's mail_from property 3. or the first one whose address matches MAILINADDREXP 4. or the first one NB cases 3 and 4 may sometimes lead it to guess the wrong recipient and potentially deliver to the wrong page. Perhaps we can get this from the mail servers and pass it as an argument. - if called with trackerissue=1 or the recipient matches ".*(tracker|bugs|issues)@" (TRACKERADDREXP), create a tracker issue page. - Otherwise, look for 1. a non-empty page name in the recipient real name (PAGEINREALNAMEEXP) 2. or the first WikiName or [bracketed name] in the subject 3. or the folder's default_page property (possibly acquired) 4. or the defaultpage argument we were called with (XXX remove ?) 5. or the DEFAULTPAGE defined below 6. or the first zwiki page in the folder (as returned by objectValues) - and add a comment to that page, creating it if necessary. Note page creation and comments will trigger subscriber mail-outs as usual. Tests for this code are in ZWiki/tests/testMailin.py. todo: size limits friendly bounce messages integrate pb's http posting, attachment handling ? use python 2.2 email package ? """ from types import * import re, string import rfc822, mimetools, multifile, StringIO from Products.ZWiki.Regexps import wikiname1,wikiname2,bracketedexpr from Products.ZWiki.Utils import DLOG DEFAULTPAGE = 'FrontPage' #PAGEINSUBJECTEXP = r'(%s|%s)' % (wikiname1,wikiname2) #PAGEINSUBJECTEXP = r'(%s|%s|%s)' % (wikiname1,wikiname2,bracketedexpr) PAGEINSUBJECTEXP = bracketedexpr MAILINADDREXP = r'(wiki|mailin|tracker|bugs|issues)@' TRACKERADDREXP = r'(tracker|bugs|issues)@' # extract a page name from the recipient real name # because different things show up here - 'name', mail address, etc. - # we recognize only page names beginning and ending with a word character # and not containing @ PAGEINREALNAMEEXP = r'(?=^[^@]*$).*?(?P\w.*\w)' def isJunk(msgtext): """ Return true if this message should be silently ignored. Ideally, this should block mail loops, auto-responders and spam, but allow mailing list messages and mailouts from other zwikis. Currently it flags everything that looks like it came from a bot. Actually I don't think works as a way to filter autoresponders. We should make sure to include mailing headers in our mailouts and then most autoresponders should ignore us. qmail-autoresponder's bot-filtering procedure is reportedly good - see http://untroubled.org/qmail-autoresponder/procedure.txt . TMDA and spamassassin are two good spam filters - see http://software.libertine.org/tmda , http://spamassassin.taint.org . """ # a zwiki mailout if re.search(r'(?mi)^X-Zwiki-Version:',msgtext): return 1 # the most common auto-response subject if re.search(r'(?mi)^Subject:.*out of office',msgtext): return 1 # XXX don't need this ? # mailing list or low precedence mail # should allow these, but need to pass through the list loop # headers to avoid mail loop between mutually-subscribed zwiki and # mail list #if re.search(r'(?mi)^(List-ID|(X-)?Mailing-List|X-ML-Name):',msgtext): # return 1 #if re.search( # r'(?mi)^List-(Help|Unsubscribe|Subscribe|Post|Owner|Archive):',msgtext): # return 1 #if re.search(r'(?mi)^Precedence:\s*(junk|bulk|list)\s*$',msgtext): # return 1 return 0 class MailIn: """ An incoming wikimail message. Responsibilities: - parse the message and expose the parts of interest. - figure out how to deliver itself as per the rules above. Uses the rfc822 package so as to work with python 1.5. """ folder = None recipient = None destpage = None destpagename = None workingpage = None trackerissue = 0 creating = 0 error = None def __init__(self, context, message, defaultpage=DEFAULTPAGE, subscribersonly=1, trackerissue=0, checkrecipient=1, checksubject=1, ): #DLOG('mailin.py: processing incoming message:\n%s' % message) # parse the message self.context = context self.raw = message self.stream = StringIO.StringIO(self.raw) self.msg = mimetools.Message(self.stream) self.date = self.msg['Date'] self.subject = (self.msg.has_key('Subject') and self.msg['Subject']) or '' self.realSubject = re.sub(r'.*?\[.*?\](.*)',r'\1',self.subject) self.sender = self.msg['From'] self.senderRealName = rfc822.parseaddr(self.sender)[0] self.senderEmail = rfc822.parseaddr(self.sender)[1] self.senderUserName = (self.senderRealName or re.sub(r'@.*$',r'',self.senderEmail)) self.recipients = [] for hdr in ['To','Cc','Bcc']: if self.msg.has_key(hdr): self.recipients.extend(self.msg.getaddrlist(hdr)) if self.isMime(): self.body = self.firstMimePartMatching('text/plain') else: self.body = self.msg.fp.read() # strip trailing newlines that seem to get added in transit self.body = re.sub(r'(?s)\n+$',r'\n',self.body) # strip TBC (typical bloody citations) #self.body = re.sub( # r'(?si)----- ?message d\'origine.*',r'',self.body) #self.body = re.sub( # r'(?si)----- ?original message.*',r'',self.body) # figure out where it should go if self.context.meta_type == 'ZWiki Page': self.folder = self.context.aq_parent self.destpage = self.workingpage = self.context self.destpagename = self.destpage.title_or_id() self.creating = 0 # or at least the folder and a working page else: self.folder = self.context # find default and working pages.. # NB the default page may have a freeform name defaultpagename = getattr(self.folder,'default_page',defaultpage) self.workingpage = getattr(self.folder,defaultpagename,None) if not self.workingpage: allpages = self.folder.objectValues(spec='ZWiki Page') if len(allpages): firstpage = allpages[0] self.workingpage = \ firstpage.pageWithName(defaultpagename) or firstpage defaultpagename = self.workingpage.title_or_id() else: DLOG('mailin.py: could not find a working page') self.error = '\nSorry, I could not find an existing wiki page to work with.\n\n\n' return # check if the sender is allowed to post there if (subscribersonly and not getattr(self.folder,'mailin_policy', getattr(self.folder,'posting_policy',None)) == 'open'): if len(self.workingpage.allSubscriptionsFor(self.senderEmail))==0: DLOG('mailin.py: bounced mail from non-subscriber', self.senderEmail) self.error = '\nSorry, you must be a subscriber to send mail to this wiki.\n\n\n' return # decide which of the recipients is us if len(self.recipients) == 1: self.recipient = self.recipients[0] elif getattr(self.folder,'mail_from',None): folder_mail_from = getattr(self.folder,'mail_from') for r in self.recipients: if r[1] == folder_mail_from: self.recipient = r break if not self.recipient: for r in self.recipients: if re.search(MAILINADDREXP,r[1]): self.recipient = r break if not self.recipient: self.recipient = self.recipients[0] # decide if this is a tracker issue if (trackerissue or re.search(TRACKERADDREXP,self.recipient[1])): self.trackerissue = 1 return # finalize our choice of destination page if not self.destpage: # look for a page name in recipient real name if checkrecipient: m = re.search(PAGEINREALNAMEEXP,self.recipient[0]) if m: self.destpagename = m.group('page') # or in the subject if (not self.destpagename) and checksubject: matches = re.findall(PAGEINSUBJECTEXP,self.subject) if matches: self.destpagename = matches[-1] # use rightmost # (and strip enclosing []'s if needed) if self.destpagename: self.destpagename = re.sub(bracketedexpr, r'\1', self.destpagename) # or use the default page name if not self.destpagename: self.destpagename = defaultpagename # destination page identified! # use it if it exists, otherwise we'll create if self.destpagename and \ self.workingpage.pageWithFuzzyName(self.destpagename, ignore_case=1): self.workingpage = self.destpage = \ self.workingpage.pageWithFuzzyName(self.destpagename, ignore_case=1) self.creating = 0 else: self.creating = 1 def isMime(self): return re.search(r'Content-Type:.*multipart',self.raw) # based on python manual example def firstMimePartMatching(self,mimetype): msgtype = self.msg.gettype() params = self.msg.getplist() data = StringIO.StringIO() if msgtype[:10] == "multipart/": file = multifile.MultiFile(self.stream) file.push(self.msg.getparam("boundary")) while file.next(): submsg = mimetools.Message(file) try: data = StringIO.StringIO() mimetools.decode(file, data, submsg.getencoding()) except ValueError: continue if submsg.gettype() == mimetype: break file.pop() return data.getvalue() # def findDestinationVirtualHost(self, msg): # """ # If we have one of the special recipients listed above along with a # matching virtual host, return the virtual host's folder; otherwise None. # Also save the username part of the matched recipient for later. # """ # folder = None # vurt = getattr(self,'vurt',None) # if vurt and msg.wikiRecipients: # for address, realname in msg.wikiRecipients: # alias, host = address # # based on SiteAccess # # extract this --> # dict = getattr(vurt, 'domain_path', None) # if dict: # real_host=None # l_host = string.lower(host) # if vurt.domain_path.has_key(l_host): # real_host=l_host # else: # for hostname in vurt.domain_path.keys(): # if hostname[:2] == '*.': # if string.find(l_host, hostname[2:]) != -1: # real_host = hostname # # <-- # if real_host: # path = vurt.domain_path[real_host] # folder = self.getPhysicalRoot().unrestrictedTraverse(path) # msg.aliasUsed = alias # break # return folder def mailin(self, msg, pagenameexp=None, #XXX temporary backwards compatibility defaultpage=DEFAULTPAGE, separator=None, #XXX temporary backwards compatibility checkrecipient=0, checksubject=1, trackerissue=0, subscribersonly=1, ): context = self # these come over the web as strings checkrecipient = int(checkrecipient or 0) checksubject = int(checksubject or 0) trackerissue = int(trackerissue or 0) subscribersonly = int(subscribersonly or 0) # silently discard any junk if isJunk(msg): return # parse and figure out how to deliver the message m = MailIn(context, msg, defaultpage=defaultpage, subscribersonly=subscribersonly, trackerissue=trackerissue, checkrecipient=checkrecipient, checksubject=checksubject, ) if m.error: return m.error # stash the sender's username in REQUEST so zwiki can use it to set # last_editor self.REQUEST.set('MAILIN_USERNAME', m.senderUserName) # a new tracker issue ? if m.trackerissue: subject = m.subject or 'no subject' self.REQUEST.set('newtitle', subject) # XXX italicize quoted text in replies (as in comment().. but DRY) body = re.sub(r'(?m)^>(.*)',r'
>\1',m.body) self.REQUEST.set('newtext', body) self.REQUEST.set('submitted', 1) m.folder.IssueTracker(REQUEST=self.REQUEST) #DLOG('mailin.py: created issue '+subject) return # a new page ? subject = m.realSubject if m.creating: subject = '(new) '+subject # XXX may need to pass REQUEST for authentication ? # but "REQUEST has no URL2" and create fails. Leave out for now. m.workingpage.create(m.destpagename,text='') m.destpage = m.workingpage.pageWithName(m.destpagename) #DLOG('mailin.py: created '+m.destpagename) # add comment # use the time of sending, or the time of posting to the wiki - see # how the latter works out # mailing list support: when a list and wiki are mutually subscribed, # we don't want to send mailouts back to the list. # how to implement this ? # perhaps: look at any X-BeenThere headers and if one (or more) is a # subscriber to destpage, remove it from the mailout recipients # simpler: hard-code your lists below # simplest: configure your list to discard duplicates domailout = 1 #if (re.search(r'(?mi)^X-BeenThere:\s*list1@dom\.ain\s*$',msg) or # re.search(r'(?mi)^X-BeenThere:\s*list2@dom\.ain\s*$',msg)): # domailout = 0 # # & maybe strip a list footer # m.body = re.sub(r'(?s)\n___.*?$',r'',m.body) m.destpage.comment(text=m.body, username=m.senderUserName, note=' (via mail)', use_heading=1, REQUEST=self.REQUEST, subject_heading=subject, do_mailout=domailout, ) #DLOG('mailin.py: commented on '+m.destpagename) ZWiki/CHANGES.txt0100644000175000017500000014762707654242135013012 0ustar simonsimonZwiki 0.18.0 2025-05-01 Summary Full Plone and CMF skin, miscellaneous fixes. Upgrade notes If you have a cmf_install_zwiki external method, refresh it after installing this release (by saving it, or restarting zope). The new zwiki_plone skin replaces the zwiki_cmf skin; although the latter is still shipped it will hopefully go away soon. An existing CMF zwiki should keep working as before. To change it over to the zwiki_plone skin, do this: 1. in your CMF site's portal_skins Contents tab, delete the default and zwiki_cmf folders (and zwiki_orig, if you don't use that) 2. in the properties tab, remove zwiki_cmf (and zwiki_orig) from all skins 3. run the install method again (CMFSITEURL/cmf_install_zwiki) Structured text headings will get bigger next time you save the page (or visit SOMEPAGEURL/upgradeAll to re-render all pages). Changes General * structured text headings now start at H2 not H3 * when using regulations, don't require change regulations permission just to comment (IssueNo0485) * be more robust displaying pages with old-style page ids that have not yet been upgraded (IssueNo0495) * use precise links in contents, rather than depending on standard_error_message (IssueNo0454, Leslie Barnes) * when saving a page via external editor/ftp/webdav, don't require a space after the : in the safety belt/type/log headers. Was causing a name error with external editor + emacs whitespace mode (IssueNo0438, Andrew Burrow) * if there is an id renaming collision during upgradeAll, just log the error and continue (IssueNo0483) * edit conflict & lock dialogs were giving NameErrors * new method: ancestorsAsList Skins and content * a fully functional Plone (and CMF) skin, at last! The zwiki_plone skin provides most current features of Zwiki's default skin, within the standard plone UI. It should also be functional for CMF sites, allowing zwiki_cmf to be retired. (Alexander Limi funded by Walt Ludwick, with Simon Michael, Sidnei da Silva, Alan Runyan) * skin code fixes; Zwiki accepts Page Templates, Filesystem Page Templates, and DTML Methods as skin objects, and will warn if any other type is found. * UserOptions was not honouring the redirectURL argument if just clearing cookies * tweaked blank subject comment heading format.. not happy with it yet Mail * mailouts were failing when creating a page with no initial text * make comment mailout work when there is no subject_heading field (IssueNo0481, az` on irc) Tracker * when changing issue properties, add a user-supplied or default comment to the page recording what was done (DeanGoodmanson) Zwiki 0.17.0 2025-04-01 Summary Simpler page ids, faster performance and better memory efficiency, new general-purpose page type including tracker and fit support, more robust parenting, skin improvements, preliminary stylesheet support, code cleanups, doctest no longer used. Upgrade notes If you have pages with punctuation in the names, or zwiki tracker issues, you'll need to change their id/name. See below. If you have a tracker in your wiki, you'll also need to install the latest IssueTracker/FilterIssues pages and add a new 'isIssue' FieldIndex to your catalog. Also, in a large wiki you may find changing issue descriptions is much slower than before. See below. issuedtml and stxprelinkdtmlhtml pages will be auto-upgraded to the new stxprelinkdtmlfitissuehtml type. The CMF install method in Extensions has been renamed to Install.py. Changes General * Simpler page ids Page ids are now always derived from the name (title). This improves performance, removes a source of confusion, but means pages can no longer have a totally different title and id (like tracker issues used to). Also, page ids no longer include punctuation, and fuzzy links now ignore punctuation. Pages with old-style ids should be renamed so that wiki links to them will work. You can upgrade these pages and update links throughout the wiki by visiting SOMEPAGEURL/upgradeAll. This may take a long time; you can watch progress in the debug log. It's safe to run this more than once. Alternately, you can visit SOMEPAGE/upgradeId to rename and relink a single page. Some incoming links may break due to url changes; having the zwiki standard_error_message installed will help. * Improved performance and memory efficiency Link rendering is faster due to the new ids and code refactoring. (Unscientific ab test: roughly a 4x speedup rendering a normal page and 20x for a long one.) Also, zwiki is no longer so eager to load all pages into the zodb cache. Certain operations will still trigger this (search, rename) but in general use, a zope serving large wikis should now use less memory. This may also help your performance and reduce "first page render" delay. * new/improved page lookup methods: pages, pageIds, pageNames, pageIdsStartingWith, pageNamesStartingWith, firstPageIdStartingWith, firstPageNameStartingWith, pageWithId, pageWithName, pageWithNameOrId, pageWithFuzzyName. Use these in your DTML code when possible as they will be optimized. pageWith* now return a correct acquisition wrapper (fixes IssueNo0472). pageWithFuzzyName's ignore_case has no extra cost now and is always on. The argument is left in place for backwards compatibility for the moment. Also this may have been acquiring pages, fixed. * new general-purpose page type incorporating issue and fit support: stxprelinkdtmlfitissuehtml. stxprelinkdtmlhtml and issuedtml pages are auto-upgraded to this. This page type displays an issue properties form if the page name begins with IssueNo. Also, any tables whose first cell begins with "fit." or "fittests." are run as fit tests at page view time. * rename improvements: support for upgrading to new-style ids; allow renames without sending mail; reduce unnecessary indexing; preserve creation info when renaming (IssueNo0398); be more tolerant of confused parentage; * Don't leave orphans when deleting a page * cleaner catalog logging * all doctests have been converted to pyunit tests * Details of WWML changes in last month's release (PeterMerel): - C2-compatible apostrophe embedding using minimal-munch - C2-compatible generation of indents from leading spaces - Intuitive fixed-width tables using | as a column delimiter. I purely hate the STX tables, which do everything except the most obvious formatting everybody wants. - Ability to use images as page names with [image-url] - Ability to generate blockquotes by using = instead of * - Ability to force a BR in definitions using \\ - Probably some other things I've forgotten. Skins and content * the latest zwiki.org RecentChanges: fix a mispaced try which allowed errors, be less chatty about brute force/catalog, move note field next to page name, drop the "use table" option and the T issue indicator. * edit form layout improvements * lose the gray title background and use site logo on all "form" pages * Made the diff form skinnable: support a 'diffform' page template or dtml method accepting two arguments: 'revA' and 'difftext'. * Made the contents view skinnable: support a 'contentspage' page template or dtml method accepting two arguments: 'hierarchy' (a html string) and 'singletons' (a list of html strings). * allow multiple space-separated wiki names in backlinks' other parents field and the page management form (PeterMerel) * contents improvements: make "you are here" bold; list singletons at the bottom, if any. (PeterMerel) * "contents" is used more consistently and is the preferred new name for the map method. * page creation links now have class "new" (IssueNo0456) * preliminary CSS support: the default skin embeds a "stylesheet" object if present. Mail * longish headings were not being stripped from mailouts * mail out comments with their original formatting (not the page diff) * don't try to find a subject in comment body any more * don't send mailouts from a page named TestPage, whether or not it has [test] subject; log discarded [test] mailouts CMF * cmf_install_zwiki.py has been renamed to Install.py (IssueNo0460) Tracker * Issue pages have a new naming scheme to conform with the new page ids: "IssueNoNNNN issue description". upgradeAll will take care of this for you. Old IssueNoXXXX urls will still work if you have standard_error_message installed. You need updated IssueTracker/FilterIssues pages which can be found in ZWiki/content/tracker. A new 'isIssue' FieldIndex is required in the catalog. * changeProperties has been replaced by changeIssueProperties. This does a full page rename, with link updating, when changing an issue description (slow). * The 'issuedtml' page type is deprecated; the new combined 'stxprelinkdtmlfitissuehtml' type is used instead. Old issue pages will be auto-upgraded. See also HowToInstallAZwikiTracker. * If you use the new page type throughout, you can change an ordinary page into an issue by renaming it "IssueNo..." and vice versa. Zwiki 0.16.0 2025-03-02 Summary CMF skin updates, various mail tweaks to support mailing list integration, enhancements to comment behaviour, WWML, misc. bugfixes. Changes Skins and content * enhancements and fixes to the CMF support and zwiki_cmf skin from the nzo project - title fix, wiki folder edit view, don't create standard_error_message in CMF, CMF-style edit form, other skin updates. (Sidnei da Silva, LaloMartins) * dropped zwiki_orig skin * default skin: subject field, always post with heading, tweaked page management form * more compact comment headings * add discussion icon to comment headings in a CMF site Mail * mailout all comments, regardless of use_heading flag * with multiple bracketed strings in a subject, use the right-most * mailin.py: support for preventing duplicates when using a mailing list, commented out; * generate list id header from mail_from, not mail_replyto * new mail_signature property to override mailout footer * new mail_page_name boolean property to disable page name in subject * mailin.py: parse boolean options correctly * don't add extra spaces in mailout subjects * log reason for mailout failures General * WWML: fixed a couple of little bugs and added a couple of little extra markups. (PeterMerel) * comment(): a non-blank comment subject now enables the heading if use_heading is not already true * preserve parentage when pages are renamed (IssueNo0168) * fixed a strange problem with catalog() * fix Localizer ustr NameError caused by non-unicode zope or unit testing * testing support - don't append or mail out comments with [test] subject * test modules re-organized Zwiki 0.15.0 2025-02-01 Summary Minor bugfixes and some changes to wikimail behaviour. Upgrade notes If you're doing mailin, don't forget to re-save your external method after installing this release. Changes Mail * don't store page names in recipient real name. This seems to create more confusion than it solves. mailin.py's checkrecipient option is now off by default, and the line in Mail.py:sendMailTo() which puts the page name in mailouts' reply-to is commented out. * don't recognize bare wiki names in mailin subjects - only page names enclosed in brackets. Explicit is better than implicit. More details on http://zwiki.org/WikiMailDiscussion, please comment there if you don't agree with these changes. * non-headed comments were sending error reports to mail_admin if configured Skins and content * default: some fixes for valid HTML (Pieter Biemond, IssueNo0399) * default, zwiki_orig: increase max quote size in header to 300 * zwiki_orig: get rid of the javascript double-click General * leave unknown link titles blank instead of showing "xxx zwiki bug" * a comment's subject (explicit from a field or inlined) is now used for the transaction log regardless of the use_heading checkbox. Zwiki 0.14.0 2025-01-09 Summary A mailin.py rewrite. Upgrade notes If you don't use mailin, no need to upgrade. After installing this you'll want to click your external method's save button to update it. Note this version of mailin.py drops the special virtual host support. Changes Mail * a rewritten mailin.py with tweaked delivery rules and unit tests. Fixes IssueNo0376. * be more fussy about recognizing a recipient real name as a page name * bracketed free form names in the subject are now also recognized (it will use the first bracketed thing found) Zwiki 0.13.1 2025-12-07 Summary A "bugfix-plus" release, for IssueNo0385 (missing permissions). Changes * finish the switch to modern security declarations, fixing IssueNo0385 and hopefully not breaking much else. * also send mailouts on page deletion and renaming Zwiki 0.13.0 2025-12-01 Summary Bugfixes, more solid CMF & Plone support, wikimail enhancements, skins re-organization. Upgrade notes If you have mailin set up and find replies going to the default page instead of the originating page, see IssueNo0376. Changes CMF * rename CMFInstall.py to cmf_install_zwiki.py * CMF skin support. zwiki_cmf is a lightweight CMF skin for zwiki; zwiki_orig is a CMF skin that looks like zwiki's standard UI * fix edit permission in CMF (IssueNo0366) * fix a timezones error when in CMF * make wiki folder & page management work in cmf/plone * catalog lookup changed: look for a CMF portal_catalog; look for SITE_CATALOG on page and containing folder only * when creating a Wiki Page in CMF, allow the standard_page_type folder property to control it's page_type. * hide user options when in CMF Mail * look up destination page in the recipient's real name (first); mailouts will encode the source page in the reply-to's real name. This should make addressing and replying more natural. * allow subject headings in mail and web comments. (To set the subject from a web comment, use an initial one-line paragraph that is bold (using '** or < b>') and no longer than 100 characters.) Subjects are saved as edit log note and vice versa. * generate a mailout when pages are created * mailin page creation fixes * mailin: handle freeform page names * mailin: work even when default page is not found, as long as there's at least one wiki page in the folder. * mailin: add some failure logging * look for 'mailin_policy' property instead of 'posting_policy'; the latter is still supported but deprecated * don't use < hr> in comment headings Skins and wiki content * clean out & reorganize templates and default content under skins, content * new unified RecentChanges implementation; use SITE_CATALOG, remove hard-coded zwiki.org General * wiki links to pages with accented names (and freeform names in general if standard_error_message is not present ?) were not working * parent wiki links were generating an error * workaround for zope 2.6 stx+dtml breakage (IssueNo0270) * fix for "some permissions had errors" (IssueNo0358) * fix title changing in ZMI Edit tab (IssueNo0280) * increase the added and removed lines truncation limits for diffs and mailouts (to 200 and 20 lines respectively) * ZWikiPage class initialization fix; fixes an event log file warning (and.. ?) * auto-upgrade WikiForNow pages * log messages at BLATHER priority instead of DEBUG, and don't require the Z_DEBUG_MODE or ZWIKI_DEBUG variable any more Zwiki 0.12.0 2025-11-01 Changes * subscription now accepts CMF member usernames as well as email addresses * add zwiki web, add zwiki page forms internationalized; .po files & spanish translation updated (JuanDavidIbanez) * preliminary fit (framework for interactive testing) support ('dtmlfithtml' & 'stxdtmlfithtml' page types) * allow a 'mail_subject_prefix' folder property to override the folder title for mailout subjects. Useful when mailout subject and cmf's "you are here" must differ. * self.ZopeTime didn't work on a page in a CMF site, now does * apply the stx initial-word-becomes-bullet workaround for all zope versions * support for auto-upgrading wikifornow pages * minor default ui & content tweaks Zwiki 0.11.0 2025-10-01 Summary Bugfixes, international page names, edit log notes, WikiForNow assimilation completed, CMFWiki integration (alpha). Upgrading notes See Regexps.py for notes on configuring international page names. When running large & extensively cataloged wikis, you might notice this version being more memory intensive than 0.10 (which itself may be more memory hungry than 0.9.9). This release should coexist with CMFWiki without problems. Otherwise the usual, see http://zwiki.org/TenMinuteZwikiUpgradeGuide & http://zwiki.org/HowToUpgradeZwiki for more. Changes (by category) Misc * better support for (single-byte) international characters in page names & ids; enable out of the box, with or without a locale set up (IssueNo0257) * merge CMF support into standard ZWiki pages. See Extensions/CMFInstall.py. (alpha) (CMFWiki) (ChrisMcDonough) * locking fixes for external editor support (CaseyDuncan) * save an optional log note with edit (or type change, file upload, ftp PUT, page creation, deletion (via DeleteMe)) (WikiForNow) (KenManheimer) * allow page type to be set via FTP (WikiForNow) * Don't allow completely anonymous invocations of rename/delete (IssueNo0235) * reindex after renaming * set "FTP access" permission, send manage_edit through wiki editing code the way WikiForNow does (IssueNo0243) * a couple of compatibility tweaks for running inside a CMF/plone instance - provide view & SearchableText methods, use portal_catalog if present, don't use rule in comment headings. Default UI & wiki content * custom wikipage template was being called with the wrong context (IssueNo0225) * custom standard_wiki_header/footer methods were being ignored (IssueNo0228) * provide a traceback in html source when header or footer rendering fails * standard_error_message: fix missing "; make it work when not in site's root folder (IssueNo0250) * editform: move options down a row to handle long page names better; fix tab ordering * display last log note in history link title in header and at top left when diff browsing; use a form button to return from diff browsing * use ZMI widgets for page_type and NOT_CATALOGED (WikiForNow) Rendering * new "text + links" page type * wwml: some rendering fixes and convert spaces to tabs, described on ConvertSpacesToTabs (PeterMerel) * use new rendering code for issue pages (IssueNo0252,IssueNo0253) * enable stx bullet workaround for all zope versions (IssueNo0273) Editing Mail * filter blank items from mailout recipients (IssueNo0221) Tracker Compatibility * be more robust when upgrading timestamps (IssueNo0222) * be more robust with older zopes which may not have page templates (IssueNo0224) * don't use prefix tag in add zwiki web form for compatiblity with old zopes (IssueNo0229) * be more robust reading version.txt.. zope-cvs doesn't have one (IssueNo0254) * fixprops.py utility method for fixing up zwiki page properties API changes (summary) Nothing major. Zwiki 0.10.0 2025-09-01 Changes * minor text cleanups in default editform & zwikidotorg content Zwiki 0.10.0rc1 2025-08-25 Summary Pre-rendering for better performance, new freeform page names and fuzzy linking, WikiForNow regulations support (beta), page renaming & deleting, UI enhancement & simplification, better upgradability, page templates support, i18n started, many bugfixes & minor enhancements. Upgrading notes This release has renamed page types and a new render-caching mechanism, to which pages are upgraded automatically; full-featured UI defaults which will be used if you delete your custom DTML methods; and one new and one renamed permission. See http://zwiki.org/TenMinuteZwikiUpgradeGuide & http://zwiki.org/HowToUpgradeZwiki for more. Changes (by category) Default UI * made built-in defaults full-featured (equivalent to current zwiki.org UI). * when adding a zwiki web, don't instantiate dtml methods, rely on the built-in defaults instead * support page templates as well as dtml methods for editform, subscribeform, backlinks and for main page layout (a page template named wikipage will take precedence over standard_wiki_header & standard_wiki_footer dtml methods). The page body is passed to in wikipage as options/body. The following additional options are passed to editform: page, text, action, id, oldid. * built-in defaults are now read from the filesystem. Defaults for standard_wiki_header/standard_wiki_footer are still provided but no longer quite as up-to-date and probably will be deprecated. * consistent api for accessing UI methods/templates/defaults * many UI updates and simplifications in default page layout, editform, backlinks etc.; made UI options more flexible & robust * full/simple/minimal display modes; simple (no page hierarchy) is displayed by default * retired jumpsearch (make the search box always do a simple search) * added tooltips & access keys to most links & form elements * display a convenient page rename/reparent/delete form in the footer in full mode if the user has a username (and permissions) * external editor support * make backlinks dtml more robust (IssueNo0210); fixed a case where the parent checkbox didn't show up; now uses a catalog if available to provide accurate fuzzy backlinks * upload permission was incorrect in editform (IssueNo0178) * removed "Show advanced edit form" option * ensure comment form depends solely on Add comments permission * fix white space in center of footer in NS 4.7 * don't show "sp" for spacer image in text-mode browsers * a missing slash caused edits to fail in netscape 4.7 * added secret AnnoyingQuote edit link * better support for web robots - removed robot-excluding meta tag from standard_wiki_header/wikipage, added robot exclusion tag to editform/subscribeform/backlinks; use form buttons instead of links to keep robots out of page history etc. Default zwikidotorg content * fewer pages * new FrontPage * new HelpPage * updated RecentChanges, uses catalog if available * updated SearchPage with selected extra search tools * cleaned up UserOptions * provide a list of timezones, MoinMoin style (IssueNo00146) * a standard_error_message method handles nonexistent urls, with fuzzy and partial matching on page names Rendering * Pre-rendering support: new page types (used by default) process text formatting rules and wiki linking rules once at edit time. This makes rendering of large pages much faster (possibly at the expense of quicker zodb growth). Also allows us to accurately catalog links. * Better freeform page names - square brackets now allow almost any page name, not just those with url- and zope-id-safe characters. Freeform names are converted to wikiname-like page ids and either may be used for linking, supporting co-existence of freeform and wikiname pages. * Fuzzy linking - square brackets also do fuzzy linking, ignoring whitespace and capitalization. * square brackets now link only to zwiki pages, not other zope objects or url paths * Wiki links now have title attributes/tooltips displaying the target's age and last editor when you mouse over (except for freeform page names in contents hierarchy and parent context) * support for simple sub wikis/WikiAcquisition - wikilink targets can be acquired from the parent folder (or above) and are displayed with ../ prepended * don't link WikiNames inside structured text links (IssueNo0190) or the content of html anchor tags (IssueNo0194) * allow stx tables to have + at the corners * the RemoteWikiURL tag is now case-insensitive * make accidental structured text footnote processing less likely; use more standards-compliant "ref" prefix for footnotes, like newer stx * don't treat a bare url followed by : as a remote wiki link * make stx links work in non-html stx modes (IssueNo0193) * fix for IssueNo0186 (page rendering fails if it contains a freeform link containing unbalanced parentheses) * added (?L) to the regexps which use \w or \b to make them locale-sensitive. (EdwardKreis, AlexyKhrabrov, InternationalCharsInRegexps) * display "you are here" in page hierarchy Editing * strip HTML header & footer found in edit text * make post-stx HTML & BODY tag stripping more robust (EdwardKreis) * check for a webdav lock before saving an edit or entering editform * don't highlight the last edit access key if it's not hyperlinked * allow existing files to be re-uploaded (IssueNo0006) * don't add a link for uploaded files if the page already has one * set last_editor properly during mailin (IssueNo0207) * recatalog pages after ftp/http/webdav put * abandon the old antidecap kludge Mail * fixed the multiple subscription bug (IssueNo0161) * fixed page creation * make mailin tracker issue creation more robust * mailin in the context of a zwiki page was not working as advertised * include standard mailing list headers in mailouts * discard all mail that appears to come from a bot, and the common "out of office" auto responder replies * post comments/create pages using the sender's real name or just the username component from their email address, not the full address (IssueNo0066, PieterB/SM) * fixed a bug in upgradeSubscribers which caused folder properties to proliferate Tracker * added a createIssue method which can replace the AddIssue & IssuePrototype pages * increased title's max length from 100 to 200 when editing issues * when rendering the issue form, generate option menus dynamically from the issue_* properties (MikeFair) Compatibility * better auto-upgrading & backwards compatibility; allow auto-upgrading to be disabled via flag in Defaults.py; new upgradeAll method for batch upgrading & re-rendering * removed STletters dependency for older zopes * don't pass stx header argument with zope 2.4.0 (IssueNo0152, TrevorToenjes) * added zope 2.5.1 to list of zope versions for unit testing * improved python1.5 support * improved zope version checking Misc * support WikiForNow-style regulations if use_regulations boolean folder property is true * add support for page renaming/deleting * tweak allowed suffixes for fs-based wiki templates * add more informative transaction notes * ZWikiPage.manage_changeProperties was non-functional * fix ZMI add zwiki page (IssueNo0187), make it equivalent to wiki page creation * spanish translation of initial i18n strings (J. David Ibáñez) * canadian french translation of initial i18n strings (JoannePlouffe) * the beginnings of i18n support, using Localizer if installed. A few strings (edit conflict, authentication errors) internationalized. * use last_edit_time everywhere in preference to bobobase_modification_time * debug logging of zwiki catalog operations (if Z_DEBUG_MODE or ZWIKI_DEBUG variables are true and STUPID_LOG_SEVERITY is <= -200) API changes (summary) * wikimail/editform/subscribeform/backlinks page templates will be used in preference to standard_wiki_header/standard_wiki_footer/ editform/subscribeform/backlinks DTML methods. The page body is passed to wikipage as options/body. These options are passed to editform: page, text, action, id, oldid. * page types renamed. Here is the current list of page types (see AllAboutPageTypes for details):: stxprelinkdtmlhtml stxdtmllinkhtml dtmlstxlinkhtml stxprelinkhtml stxlinkhtml stxprelink stxlink wwmlprelink wwmllink prelinkdtmlhtml dtmllinkhtml prelinkhtml linkhtml dtmlhtml html plaintext * the RemoteWikiURL tag is case-insensitive * the OFS.ObjectManager.bad_id definition is used as the basis for generating page ids * some properties are now looked up by containment rather than acquisition context, eg subscriber lists ADDED:: 'Zwiki: Rename pages' permission use_regulations folder property zwiki_displaymode cookie ZWIKI_DEBUG environment variable addStandardLayoutTo age applyLineEscapesIn asAgeString backlinks canonicalId canonicalLinks canonicalLinks changeIssueProperties createIssue creationTime editform folder htmlunquote isMailoutEnabled isZwikiPage lastEditInterval lastEditTime linkTitle linkTitleFrom offspringAsList offspringIdsAsList pageWithFuzzyName pageWithName pageWithNameOrId relative_urls renderLinksIn standard_wiki_footer standard_wiki_header stxToHtml subscribeform upgradeAll urlunquote wikipage CHANGED:: 'Zwiki: Recycle pages' permission -> 'Zwiki: Delete pages' creation_time and last_edit_time are now ISO-format strings doLegacyFixups -> upgrade REMOVED:: zwiki_advancededit cookie Zwiki 0.9.9 2025-04-16 Focus: unit tests, wikimail enhancements, general fixes and a permissions rename. See main release notes below (rc1). Bug fixes - zwikidotorg template: remove references to old permissions Zwiki 0.9.9rc6 2025-04-15 Bug fixes - recognize , in urls (IssueNo0130) - zwikidotorg template: remove comment form border Zwiki 0.9.9rc5 2025-04-14 Bug fixes - fix bracketed path linking (IssueNo0139) - missed some old-style imports in the subscription code Zwiki 0.9.9rc4 2025-04-13 Bug fixes - zwikidotorg template: UserOptions tweaks - make default editform dimensions match built-in default (60x15); don't use PST as default timezone - zwikidotorg template: make timezone conversion in the header more robust - remove a local PYTHONPATH dependency from all imports, and make unit tests more robust across zope versions & invocation methods - wikimail: multipart message handling got broken in the cleanup, fixed - wikimail: make inclusion of poster's username in mailout From header more robust Zwiki 0.9.9rc3 2025-04-10 Bug fixes - zwikidotorg template: removed SiteLogo capability & obsolete zwiki_homepage reference, added secret feature for minimalists. Zwiki 0.9.9rc2 2025-04-09 Bug fixes - render page header *after* body, so that body can turn it off with dtml - make create more robust for dtml use in a top-level folder - built-in ui tweaks - use colours like zwikidotorg's, make editform textarea smaller, remove non-useful help links - zwikidotorg template: include user option presets in the footer, make help link depend on a HelpPage, update default bookmarks API changes (summary) - new page method zwiki_version() Zwiki 0.9.9rc1 2025-04-08 Features - zwiki permissions renamed. Details:: 'Add ZWiki Pages' (no change) 'Add ZWiki Webs' (no change) 'Zwiki: Add comments to pages' (was Append to ZWiki Pages) 'Zwiki: Change page types' (was Change ZWiki Page Types) 'Zwiki: Edit pages' (was Change ZWiki Pages) 'Zwiki: Reparent pages' (was Reparent ZWiki Pages) 'Zwiki: Recycle pages' (was Send ZWiki Pages to Recycle Bin) Zwiki also uses zope's 'Add Documents, Images, and Files' (no change) Upgrade notes: you'll need to make a note of your zwiki permissions settings before upgrading and recreate them after this upgrade. You'll find all the new permissions at the bottom of the security page, aside from the Add permissions which are unchanged. Also you may have dtml which checks for the old permissions and needs to be updated. For example, the wiki templates refer to the permissions by name in standard_wiki_header, standard_wiki_footer and editform. - zwiki page 'subscribers' property replaced by 'subscriber_list' property; the folder also now exposes this in the ZMI. Upgrade notes: old pages will be upgraded as needed, this will affect the last-modified times. - new zwiki page properties: 'creator', 'creator_ip', and 'creation_time' - a 'redirectURL' REQUEST attribute can be used to control the destination after edit, append or comment. - the enclosing [] are displayed prior to page creation - relative paths within [] now display in their entirety (instead of just the last component) - UI and usability tweaks for the default subscription form; other page subscriptions are listed - the folder title is now used in html page titles, in mailout subjects and before the word "contents" in the page header. The old titleprefix method is no longer used. - wikimail: only comments (made via the "comment" method) are now mailed out by default; to mail out all edits as before, set a 'mailout_policy' folder property to "edits". - wikimail: hide mailout recipients (if Lennart Regebro's MailHostFix product is installed) - wikimail: mailout now requires either a 'mail_from' or 'mail_replyto' folder property. If 'mail_from' is present, always use that for the From: field. Otherwise, show the poster's email address or user name. (closes IssueNo0122) - wikimail: increase wrap margin from 70 to 78 in mailouts - wikimail: add X-Zwiki-Version, X-BeenThere & Precedence headers - wikimail: basic loop protection - silently discard any incoming messages containing X-Zwiki-Version - wikimail: mailin can now create pages (JosYule) - wikimail: mailin can create tracker issues (see mailin.py) - wikimail: mailin.py now accepts mail only from subscribers (somewhere in the wiki) by default. Call with 'subscribersonly=0' or set folder property 'posting_policy' to "open" to disable. - wikimail: the default destination page for mailin can be configured with a 'default_page' folder property - wikimail: use only the first plaintext part from multipart MIME messages - wikimail: use virtual host monster to help direct messages if present (may have some imeme-isms ?) - wikimail: some other tweaks to mailin delivery rules intended to simplify mailin alias setup (see mailin.py) - zwikidotorg template: header/footer/editform UI updates; display subscriber count in the header; set comment headings on by default for pages named "IssueNo*" - zwikidotorg template: for site logo, use the folder's 'site_logo' property/object or the default zwiki icon. - add zwiki web form: made this a little more robust; added support for template configuration wizards (if a form or script named TEMPLATE_config is found, redirect there to create the wiki) Bug fixes - DeleteMe should redirect to the first existing parent afterwards, now working again (IssueNo0008) - zwiki now coexists with structured text footnote references (yay!). [] will link to a matching footnote if there is one, otherwise it is treated as a wiki link (IssueNo0110) - don't treat [] as wiki links if they contain characters which zope does not allow in object ids (IssueNo0090) - don't html-quote international characters any more. If losing international characters due to edits by dumb browsers is now a problem for you, please follow up on IssueNo0004. (Taewook Kang & others) - make wikiname regular expressions a bit more international by using string.upper/lowercase. You may need to modify bad_id in zope's OFS/ObjectManager.py also. (LaloMartins, Alexy Khrabrov) - fix for IssueNo0112, structured text pages have extra html & body tags (natesain) - parenting tweaked to work better with acquisition/subfolders (IssueNo0108) (robert@redcor.ch) - wikimail: stray html tags were being left in mailouts containing long quoted lines (IssueNo0087) - wikimail: don't add extra blank lines in mailouts - wikimail: don't send duplicates when subscribed to both page and wiki (IssueNo0055) - wikimail: calling mailin in the context of a page should always use that page for posting, now it does - wikimail: format quoted replies in mailed-in tracker issues, as is done with mailed-in comments (IssueNo0070) - zwikidotorg template & general: edit access control ui improvements. Help/subscribe links now always visible; edit/append links conditionally visible; viewing editform requires edit permission; misc. color & layout tweaks. - zwikidotorg template: fix scrolling to bottom of page after comment - zwikidotorg template: don't include "set" links on backlinks, because they are vulnerable to robots - zwikidotorg template: show search and quote in header by default again - zwikidotorg template: removed the "preferred front page" option (IssueNo0120) - zwikidotorg template: adding an empty comment was giving an error (IssueNo0123) - tracker support: issuedtml page type: layout & colour scheme tweaks - tracker support: allow sorting by category/severity/status to give the expected order (see IssueNo0115) - tracker support: don't list other page types when editing an issue page - tracker support: don't inherit issuedtml page type when creating a new page from an issue page - Possibly fixed a bug with Add Zwiki Page permission. - unit tests overhauled, cleaned out and updated. Zwiki now follows the latest zope testing practices. API changes (summary) - permissions renamed (see above) - viewing editform() now requires edit permission - 'subscriber_list' page property replaces 'subscribers' - new page properties: 'creator', 'creator_ip', 'creation_time' - new optional folder (or page) property: 'mailout_policy' - new optional folder properties: 'mail_replyto', 'posting_policy', 'default_page' - 'titleprefix' property/method no longer used, folder title used instead - new optional REQUEST attribute: 'redirectURL' - sendMailTo() now takes a list of recipients, not a string - subscriberList(), wikiSubscriberList(), allSubscribers() now return a list - new page methods: allSubscriptionsFor(email), otherPageSubscriptionsFor(email) - zwiki_mailin.py external method renamed to mailin.py - new mailin() arguments: 'subscribersonly', 'trackerissue' - wikis subdirectory renamed to templates, and filesystem templates now use suffix to specify zope meta_type/zwiki page_type - zwikidotorg template: new optional folder property 'site_logo' - tracker support: new page methods: category_index(), severity_index(), status_index() Zwiki 0.9.8 2025-11-29 - auto-cataloging no longer requires DTMLDocumentExt and adding a zwiki page no longer gives "AttributeError: index_object" if it's not present. (IssueNo0054). Zwiki uses the catalog specified by the SITE_CATALOG property, or "Catalog", or none. - the add zwiki web form complained "list.remove(x): x not in list" due to missing ZWiki/wikis/DEFAULTS, fixed (IssueNo0043) - includes the experimental 'issuedtml' render method used by ZwikiTracker - zwikidotorg template: UserOptions was not displaying the username field properly - a somewhat important change that also went in last release: responsibility for generating the page header and footer has been moved into the render_* methods Zwiki 0.9.7.2 2025-11-26 - blank extra revision on each edit fixed; browsing diffs now works like it's supposed to (credits-to: Geoff Gardiner) - included TextFormatter.py in tarball - updated README, removed mention of zwikiwebs.zexp - added download link to announcements (credits-to: Ray Lance, Dietmar Gaffling) Zwiki 0.9.7.1 2025-11-23 - zwikidotorg template: remove "BookMarks" page reference from footer - ack it's going to be one of *those* releases :) zwikidotorg template: UserOptions page_type should be structuredtextdtml Zwiki 0.9.7 2025-11-23 - another zwikiwebs change. The 'Add ZWiki Web' form now looks for wiki templates in both the filesystem (.../ZWiki/wikis/) and the ZODB (/Control_Panel/Products/ZWiki/), with filesystem taking precedence. The zwikidotorg template is shipped (via filesystem) as the default; additional templates can be installed in either place, or not, at user discretion. Auto-import of zexp's has been disabled. (credits-to: CMFWiki) - new comment method added, just like append but convenient for adding standard comment headings - misc wikimail & comment heading tweaks; mail-outs now discard the comment heading if present; don't display fractional seconds in comment headings (credits-to: 1Jerry) - stopped antidecapitationkludge appearing in html source; no longer used in 2.4 - misc 2.4 compatibility updates; added workaround to disable stxNG's footnote links on 2.4 - zwikidotorg template: UserOptions cleanup; added workaround for zope 2.4.x-2.4.2's stx underline problem; added site logo option & presets - automatic cataloging is supported again. Currently, the DTMLDocumentExt product is assumed to be present. It's SITE_CATALOG property is required to activate this. (credits-to: DTMLDocumentExt) - textDiff now reports more localised changes, instead of spraying out the whole page; misc diff tweaks & fixes (credits-to: ndiff/difflib) - long lines are wrapped before diffing, and long diff chunks are abbreviated, for better readability in email. Currently it displays up to 10 deleted lines and up to 50 added lines. (credits-to: Hamish Lawson's TextFormatter) - lasttext and the diff methods accept one or more revision arguments (counted backwards from the latest revision). - the old html-format diff has been demoted to oldDiff. The new one colourizes textDiff's output and adds whizzy navigation links for stepping through the edits. (Click on the page timestamp). - zwikidotorg template: automatic comment headings now available on all pages and enabled by default on *Discussion pages; horizontal rules now used - zwikidotorg template: header & footer tweaks; don't display ip address for last editor - zwikidotorg template: subscribe form email address integrated with UserOptions - improved default subscribe form's layout; remember email address via cookie if possible Zwiki 0.9.6 2025-10-22 - zwikidotorg template updated to latest zwiki.org layout, example append_with_heading method added - mail subscribers now receive edits as well as appends - simple create method added to api - new page- & wiki-wide mail subscription mechanism (.../subscribeform) - wiki_page_url/wiki_base_url renamed to page_url/wiki_url; checkEditTimeStamp/editTimestamp renamed to checkEditConflict/timeStamp; old api kept for backwards compatibility - code cleanups, refactoring Zwiki 0.9.5 2025-10-13 - the link to an uploaded file or image used to be !-escaped; this is no longer necessary - file upload now requires "Add Documents, Images and Files" permission (was "Add Documents, Files and Images") - fixed zwiki_username_or_ip() so last editor username is saved again - moved zwiki web creation into core python product, so the manual ZWikiWebs import is no longer needed. The sample wikis are now shipped as individual zexp's in ZWiki/import, and are automatically imported to /Control_Panel/Products/ZWiki at product startup. (Install your own sample wikis there as well). /Control_Panel/Products/ZWikiWebs can be deleted. - "add zwiki web" form updates Zwiki 0.9.4 2025-08-04 - escaping remote wiki links with ! should now work - tests have been completely reorganized and updated - added custom __repr__ from CMFWiki - refactored code into multiple modules, following CMFWiki. Encapsulated some functionality in mix-in classes. - another stx fixup: a single letter followed by a period is no longer mistaken for a numeric bullet - fix for one of 0.9.3's stx fixups: spurious html comments no longer appear in stx examples - exposed the "zwiki_username_or_ip" utility method, which given REQUEST returns a best guess for username (authenticated user, zwiki_username cookie, or ip address) - experimental "lasttext" and "diff" methods show the text of a page's last revision and a concise diff with the latest - bare/noheader/nofooter flags can now also be passed as keyword args - from WikiForNow: wiki-linking is now inhibited within -

	- 
	- structured text :: examples
	- structured text '' quoted code
	- html tags.
	- append permission now works
	- tweaked the anti-javascript hack
	- tweaked the anti-decapitation kludge
	- added plainhtmldtml mode (DTML + HTML, nothing else)
Zwiki 0.9.3 2025-05-02
	- creating/editing/deleting pages with eg spaces in the name has
	been broken for a while, it seems - made some fixes in this area
	- made the edit conflict message more helpful
	- relaxed edit conflict checking: if your username & ip address
	match the last editor's, the timestamp will be ignored.  In other
	words, you can no longer have an edit conflict with yourself.
	This means eg you can backtrack in your browser, edit and click
	Change again. This change may disable conflict checking amongst
	anonymous users coming through a proxy.
	- renamed the "username" property to "last_editor", added
	"last_editor_ip", made these read-only in the mgmt. interface.
	Existing zwiki pages are upgraded when viewed. &dtml-username; is
	still supported for backwards compatibility, but deprecated;
	use &dtml-last_editor_or_ip; by preference.
	- stx workaround: trailing blank lines no longer cause unwanted
	headings
	- stx workaround: initial word plus period no longer becomes a
	numeric bullet
	- stx workaround: whitespace after :: no longer prevents example
	formatting
	- stx headings on first lines were broken - fixed
	- fix for a 0.9.1 bug: with hierarchy display enabled, creating a
	new page from a top-level page gave "typeerror"
	- gopher: urls are now recognized
	- renamed {wiki,page}_path to {wiki,page}_url in 0.9.2
Zwiki 0.9.2 2025-04-26
	- added a bunch of wiki_{page,base}_url variants for testing purposes
	- about: urls are now recognized
	- fixed a potential DeleteMe error message caused by incorrect parents
	- allow standard_wiki_page to be defined as a folder property
	- added plainhtml render mode - HTML only, no wiki-linking
	- test suite updates, documentation
Zwiki 0.9.1 2025-04-24
	-  allow non-wiki paths in []
	- image file names in [] are auto-inlined
	- use uploaded image size to help "add file/image" decide whether to inline
	- reset parents when they have become outdated/confused
	- folder attribute "standard_page_type" overrides type of all new pages
	- display new page name when creating a page
	- record username when creating a page
	- renamed wiki_page_url(), wiki_base_url() to page_path(),
	wiki_path(). The old names, used throughout existing dtml code,
	are supported but deprecated
	- made remotewikilinks more careful to avoid trailing punctuation
	- allow []-named pages in remote wiki links
	- disable structured text's [] footnote linking to avoid conflicts
Zwiki 0.9.0 2025-04-23
	- added append method
	- simple email notification (PageSubscribers)
	- made wikilinks absolute for greater robustness
	- header/footer layout tweaks
	- made last editor's authenticated username override username cookie
	- proxy role tweak
	- added file/image upload
	- added more detailed permissions
	- refactored edit()
	- changed/reverted wiki_{page,base}_url as per Christian Scholz
	for virtual hosting
	- allowed https: urls
	- allowed + and $ in remote wiki links
Zwiki 0.8.1 2025-01-04
	- added (experimental) page deletion
	- record IP address when username cookie is blank
	- make disabled javascript tags visible
	- added more unit tests
Zwiki 0.8.0rc1 2025-12-14
	- log last editor's IP address if there is no username cookie
	- wrapped a bunch of long lines
	- unit tests - added support for ZUnit and DocTest, and a few
	initial tests. http://zwiki.org/zwikidir/Makefile.sample contains
	some useful recipes for automated testing.
	ZWikiWebs.zexp: incorporated latest zwiki.org tweaks, namely:
	- removed a spurious menu from the add zwiki web form	
	- added UserOptions to the default BookMarks and removed it from
	the page footer
	- bookmarks, quote, search box and hierarchy may all be turned on
	or off in UserOptions
	- the default home page is now a user option
	- user options & search box tab ordering fixed
	- always show the editform even for write-protected pages, with
	appropriate header/footer color
	- page history is now accessible
	- simplified RecentChanges 
Zwiki 0.7.1 2025-11-03
	- fixed broken line-ending handling and non-rendering of initial
	  lines containing ":" in dtml modes
	- return to the wiki page after clicking the reparent button
	- added warning of incompatibility with old dtml methods to readme
Zwiki 0.7.0 2025-10-31 "Halloween!"
	- cookie-based user options, including edit form size, timezone,
	  bookmarks and wikiwikiweb-style username (help from Phil Armstrong)
	- ZWiki is now zope 2.2-compatible (Garth Kidd) and -requiring,
	  and benefits from the 2.2 security model. Executable dtml pages
	  now run with those permissions that are common to both the
	  page-viewing user and the wiki web's owner. Set the folder's
	  owner to limit the permissions of executable pages.
	- incorporated & updated Chris Withers' product for creating wiki webs
	- added streamlined "hierarchal2" wiki style & other layout tweaks
	- wikiwikiweb-style late page creation
	- added simple javascript-disabling code
	- made paths work with virtual hosting again (Evan Simpson)
	- fixed unreliable ! line protection in structuredtext modes
	- fixed unreliable remote wiki links in classicwiki mode
	- more permissive remote wiki link regexps (Geoff Gardiner)
	- "with this" dtml kludge no longer needed
	- added built-in defaults for all dtml methods
	- simpler, more consistent urls & api
	- code refactoring/cleanups, other misc. bugfixes
Zwiki 0.6.1 2025-05-03
	- documented permission configuration in zwiki_examples/index_html
Zwiki 0.6 2025-05-02 "skins-structure-permissions"
	- wikinames must now start on a word boundary
	- added # and = to url regexp
	- try allowing numbers in wikinames
	- added utility methods wiki_base_url & wiki_page_url
	- added KenManheimer's hierarchy & navigation code
	- added JimFulton's edit conflict safety belts for http & ftp
	- added jim's permission & validation patch
	- add & change zwiki page permissions are now functional
	- reorganized & expanded example content
	- deemphasised DTML-enabled content where not needed - 
	  changed pages to structuredtext where possible, 
	  restricted permissions on the rest, changed the default
	  page type to structuredtext
Zwiki 0.5 2025-03-27 "simple"
	- simplified the default wiki content & page layout
	- disabled catalog support for the moment
Zwiki 0.4 2025-02-14
	- new sample wiki, defaults to structuredtextdtml mode only
	- bare urls are automatically hyperlinked, others should be left alone
	- extensible markup modes - you can add your own render methods
	- code cleanups
	- tweaked markup modes for usability (see new TextFormattingRules)
	- made validation of newly-edited DTML more accurate
	- ZWikiPages are catalog-aware (mostly.. still some issues ?)
	- added RemoteWikiLinks
	- bracketed numbers are no longer wikilinks, so StructuredText's 
	  footnotes can work
Zwiki 0.3 2025-11-14
	- sample wiki: included latest DTML features from ZWikiWeb - 
	  SearchPage, JumpTo, AnnoyingQuote, separate edit page, etc etc.
	- multiple markup formats - 
		structured text
		classic wiki (TresSeaver)
	  	HTML/DTML
		plain text
	- better international character handling (AlexandreRatti)
	- LFCR line-terminations are converted to LF
	- tweaked editform layout
	- source cleanup
	- renamed default_wiki_page to standard_wiki_page for consistency
	- ! at the beginning of a line protects it from wiki translation
Zwiki 0.2 2025-11-08
	- now checks for valid DTML & reports errors
	- no longer requires "view management screens" permission
	- tweaked wikilink regexp
	- new icon
	- misc fixes
	- standard_wiki_header, standard_wiki_footer & default_wiki_page
	  have built-in defaults; define as dtml methods to override
	- sample wiki: AllPages is a zwikipage; pagenames with spaces 
	  are listed properly
	- sample wiki: added a RecentChanges page
	- sample wiki: simplified
Zwiki 0.1 2025-11-05 
	- initial development release
ZWiki/CMF.py0100644000175000017500000001056107631641311012134 0ustar  simonsimon# CMF compatibility
# this is split between CMF.py, CMFInit.py and Extensions/cmf_install_zwiki.py
# (unfortunately) and it's still not right (need to import makeWikiPage
# for PUT_factory ?). 
# move CMFInstall into the skin ?
__version__="$Revision: 1.14 $"[11:-2]
# fails when running unit tests
# keep the try out of ZWikiPage.py to help imenu
try:
    from types import *
    import string, re, os
    import Globals
    from AccessControl import ClassSecurityInfo
    from Acquisition import aq_base, aq_inner, aq_parent
    from OFS.DTMLDocument import DTMLDocument
    from Products.CMFCore.PortalContent import PortalContent
    from Products.CMFCore.utils import _getViewFor
    from Products.CMFDefault.SkinnedFolder import SkinnedFolder
    from Products import CMFDefault
    from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl
    import Permissions
    from Defaults import PAGE_PORTALTYPE, WIKI_PORTALTYPE, WIKI_METATYPE
    class CMFAwareness(PortalContent, DefaultDublinCoreImpl):
        """
        Mix-in class for CMF support
        """
        portal_type = PAGE_PORTALTYPE
        # provide this so DublinCore.Format works with old instances
        format = 'text/html'
        # permission defaults
        security = ClassSecurityInfo()
        set = security.setPermissionDefault
        set(Permissions.Edit, ('Owner', 'Manager', 'Authenticated'))
        set(Permissions.FTPRead, ('Owner', 'Manager'))
        set(Permissions.Regulate, ('Owner', 'Manager'))
        set(Permissions.Create, ('Owner', 'Manager', 'Authenticated'))
        #set(Permissions.Move, ('Owner', 'Manager'))
        set(Permissions.Comment, ('Owner', 'Manager', 'Authenticated'))
        set = None
        security.declarePublic('supportsCMF')
        def supportsCMF(self):
            return 1
        security.declarePublic('inCMF')
        def inCMF(self):
            """return true if this page is in a CMF portal"""
            return hasattr(self.aq_inner.aq_parent,'portal_membership')
        def __init__(self, source_string='', mapping=None, __name__=''):
            DTMLDocument.__init__(self,
                                  source_string=source_string,
                                  mapping=mapping,
                                  __name__=__name__,
                                  )
            DefaultDublinCoreImpl.__init__(self)
        security.declarePublic('getId')
        def getId(self):
            try: return self.id()
            except TypeError: return self.id
        security.declareProtected(Permissions.View, 'SearchableText')
        def SearchableText(self):
            return self.text()
        security.declareProtected(Permissions.View, '__call__')
        def __call__(self, client=None, REQUEST={}, RESPONSE=None, **kw):
            '''
            Invokes the default view.
            '''
            view = _getViewFor( self )
            if getattr(aq_base(view), 'isDocTemp', 0):
                return apply(view, (self, REQUEST))
            else:
                if REQUEST:
                    kw[ 'REQUEST' ] = REQUEST
                if RESPONSE:
                    kw[ 'RESPONSE' ] = RESPONSE
                return apply( view, (self,), kw )
        # needed ?
        #index_html = None  # This special value informs ZPublisher to use __call__
        security.declareProtected(Permissions.View, 'render')
        def render( self, client=None, REQUEST={}, RESPONSE=None, **kw ):
            """Render the page body."""
            return apply(self._renderMethod(),(self, REQUEST, RESPONSE), kw)
        def wiki_context(self, REQUEST=None, with_siblings=0):
            return self.context(REQUEST, with_siblings)
        security.declarePublic('Title')
        def Title(self): # for CMFCatalog
            return self.title_or_id()
    Globals.InitializeClass(CMFAwareness) # install permissions
    class WikiFolder(SkinnedFolder):
        meta_type=WIKI_METATYPE
        def PUT_factory(self, name, typ, body):
            if find(typ, 'text') != -1:
                return makeWikiPage(name, '', body) #XXX bug! need to import
        def Title(self): # for CMFCatalog
            fp = getattr(self, 'FrontPage', None)
            return getattr(self, 'title', None) or \
                   fp and fp.Title() or self.title_or_id()
except ImportError:
    class CMFAwareness:
        def supportsCMF(self): return 0
        def inCMF(self): return 0
ZWiki/CMFInit.py0100644000175000017500000001424507643556576013010 0ustar  simonsimon# stuff that didn't fit in CMF.py or __init__.py
from types import *
import string, re, os
from AccessControl import getSecurityManager
from DateTime import DateTime
from Globals import package_home
from ZWikiPage import ZWikiPage
from CMF import WikiFolder
import Permissions
from Defaults import PAGE_METATYPE, PAGE_PORTALTYPE, \
     WIKI_METATYPE, WIKI_PORTALTYPE, DEFAULT_PAGE_TYPE
from ZWikiWeb import _addDTMLMethod, _addZWikiPage
wiki_globals=globals()
default_perms = {
    'create': 'nonanon',
    'edit': 'nonanon',
    'comment': 'nonanon',
    'move': 'owners', # rename/delete/reparent
    'regulate': 'owners'
    }
def initPageMetadata(page):
    page.creation_date = DateTime()
    page._editMetadata(title='',
                       subject=(),
                       description='',
                       contributors=(),
                       effective_date=None,
                       expiration_date=None,
                       format='text_html',
                       language='',
                       rights = '')
def makeWikiPage(id, title, file):
    ob = ZWikiPage(source_string=file, __name__=id)
    ob.title = title
    ob.parents = []
    username = getSecurityManager().getUser().getUserName()
    ob.manage_addLocalRoles(username, ['Owner'])
    ob._getRegs().setSubOwner('both')
    initPageMetadata(ob)
    #XXX sets up default permissions/regulations
    #for name, perm in ob._perms.items():
    #    pseudoperm = default_perms[name]
    #    local_roles_map = ob._local_roles_map
    #    roles_map = ob._roles_map
    #    roles = (local_roles_map[name],) + roles_map[pseudoperm]
    #    ob.manage_permission(perm, roles=roles)
    return ob
def addWikiPage(self, id, title='', page_type=None, file=''):
    id=str(id)
    title=str(title)
    ob = makeWikiPage(id, title, file)
    ob.page_type = page_type or \
                   getattr(self,'standard_page_type',DEFAULT_PAGE_TYPE)
    self._setObject(id, ob)
def addWikiFolder(self, id, title=''):
    id = str(id)
    title = str(title)
    ob = WikiFolder(id, title)
    id = self._setObject(id, ob)
    ob = getattr(self, id)
    # duplicated from ZWikiWeb.py for now
    dir = package_home(globals()) + os.sep + 'content/basic'
    filenames = os.listdir(dir)
    for filename in filenames:
        if (filename[-5:] == '.dtml'
            and not filename == 'standard_error_message.dtml'):
            text = open(dir + os.sep + filename, 'r').read()
            _addDTMLMethod(ob, filename[:-5], title='', file=text)
        else:
            m = re.search(r'(.+)\.(.+)',filename)
            if m:
                text = open(dir + os.sep + filename, 'r').read()
                name, type = m.group(1), m.group(2)
                p = _addZWikiPage(ob,name,title='',page_type=type,file=text)
                initPageMetadata(p)
factory_type_information = (
    {'id': PAGE_PORTALTYPE,
     'content_icon': 'wikipage_icon.gif',
     'meta_type': PAGE_METATYPE,
     'product': 'ZWiki',
     'factory': 'addWikiPage',
     'immediate_view': 'wikipage_view',
     'actions': ({'id': 'view',
                  'name': 'View',
                  'action': 'wikipage_view',
                  'permissions': (Permissions.View,)},
                 {'id': 'edit',
                  'name': 'Edit',
                  'action': 'editform',
                  'permissions': (Permissions.Edit,)},
                 {'id': 'history',
                  'name': 'History',
                  'action': 'diff',
                  'permissions': (Permissions.View,)},
                 {'id': 'backlinks',
                  'name': 'Backlinks',
                  'action': 'backlinks',
                  'permissions': (Permissions.View,)},
                 {'id': 'subscribe',
                  'name': 'Subscribe',
                  'action': 'subscribeform',
                  'permissions': (Permissions.View,)},
#                 {'id': 'advanced',
#                  'name': 'Advanced',
#                  'action': 'wikipage_advanced_form',
#                  'permissions': (Permissions.View,)},
                 ),
     },
    {'id': WIKI_PORTALTYPE,
     'content_icon': 'folder_icon.gif',
     'meta_type': WIKI_METATYPE,
     'description': ('A wiki. A set of editable pages with flexible organization.'),
     'product': 'ZWiki',
     'factory': 'addWikiFolder',
     'immediate_view': 'wikifolder_edit_form',
     'actions': (#{'id': 'toc',
                 # 'name': 'Wiki Contents',
                 # 'category': 'folder',
                 # 'action':'wiki_toc',
                 # 'permissions': (Permissions.View,)},
                 {'id': 'view',
                  'name': 'View',
                  'category': 'object',
                  'action':'index_html',
                  'permissions': (Permissions.View,)},
                 {'id': 'edit',
                  'name': 'Edit',
                  'category': 'object',
                  'action':'wikifolder_edit_form',
                  'permissions': (Permissions.Edit,)},
                 #{'id': 'all',
                 # 'name': 'All Pages',
                 # 'category': 'folder',
                 # 'action':'wiki_allpages',
                 # 'permissions': (Permissions.View,)},
                 #{'id': 'recent_changes',
                 # 'name': 'Recent Changes',
                 # 'category': 'folder',
                 # 'action':'wiki_recentchanges',
                 # 'permissions': (Permissions.View,)},
                 #{'id': 'wikihelp',
                 # 'name': 'WikiHelp',
                 # 'category': 'folder',
                 # 'action': 'WikiHelp',
                 # 'permissions': (Permissions.View,)}
                 ),
     },
    )
from Products.CMFCore import utils
from Products.CMFCore.DirectoryView import registerDirectory
from Products.CMFCore.PortalFolder import PortalFolder
from Permissions import Create
def initialize(context): 
    """Initialize the ZWiki product for use in CMF.
    """
    registerDirectory('skins', globals())
    utils.ContentInit(
        'Wiki Content',
        content_types = (ZWikiPage, PortalFolder),
        permission = Create,
        extra_constructors = (addWikiPage, addWikiFolder),
        fti = factory_type_information,
        ).initialize(context)
ZWiki/CatalogAwareness.py0100644000175000017500000000512607633427767014776 0ustar  simonsimon# catalog awareness for zwiki pages
# based on Casey Duncan's DTMLDocumentExt 0.1
#
# todo: may want to make these all safe against Catalog errors
import string
from Utils import DLOG
###########################################################################
# CLASS CatalogAwareness
###########################################################################
class CatalogAwareness:
    """
    Holds most of ZWikiPage's catalog awareness code.
    Zope's CatalogAwareness didn't work for me and this one
    is a little more flexible or at least familiar.
    """
    NOT_CATALOGED=0
    _properties=(
        {'id':'NOT_CATALOGED', 'type': 'boolean', 'mode': 'w'},
        )
    # cataloging methods
    def getCatalogId(self):
        """Return the id of the appropriate catalog to use."""
        # make this work in unit test context too
        folder_noaq = getattr(self.folder(),'aq_base',self.folder())
        if hasattr(self.aq_base, 'SITE_CATALOG'):
            return self.aq_base.SITE_CATALOG
        elif hasattr(folder_noaq, 'SITE_CATALOG'):
            return folder_noaq.SITE_CATALOG
        # I want portal_catalog to be acquired
        elif hasattr(self.folder(),'portal_catalog'):
            return 'portal_catalog'
        else:
            return 'Catalog'
    def catalog(self):
        return getattr(self.folder(),self.getCatalogId(),None)
    def hasCatalog(self):
        return self.catalog() != None
    def isCatalogable(self):
        return not getattr(self, 'NOT_CATALOGED', 0)
    def url(self):
        """Return the absolute object path"""
        return string.join(self.getPhysicalPath(),'/')
    def index_object(self):
        """A common method to allow Findables to index themselves."""
        if (self.hasCatalog() and self.isCatalogable()):
            DLOG('indexing',self.url(),'in',self.getCatalogId())
            #DLOG(self.id(),"'s last_edit_time is",self.last_edit_time)
            self.catalog().catalog_object(self, self.url())
            self.is_indexed_ = 1
            #DLOG('indexing '+self.id()+'done')
    def unindex_object(self):
        """A common method to allow Findables to unindex themselves."""
        #DLOG('unindexing '+self.id())
        if self.hasCatalog():
            self.catalog().uncatalog_object(self.url())
            self.is_indexed_ = 0
    def reindex_object(self):
        """Reindex the object in the Catalog"""
        if getattr(self, 'is_indexed_', 0):
            self.unindex_object()
        self.index_object()
# enable catalog awareness for common ZMI operations
# have to do this in __init__ because of an import loop ?
ZWiki/ChangeLog0100644000175000017500000036136307654242135012746 0ustar  simonsimon2003-05-01  Simon Michael  
	* CHANGES.txt, ChangeLog: latest
	* Makefile, __init__.py, version.txt: version
	* UI.py: fix NameErrors due to missing imports
	* skins/zwiki_plone/wikipage_footer.pt:
	remove the bookmarks & help links for now
2025-04-26  Simon Michael  
	* CHANGES.txt, Makefile: tweaks from HEAD
	* __init__.py, content/basic/HelpPage.stx, version.txt: version
	* Makefile: updates for working with branches
        * skins/default/wikipage.zpt, skins/zwiki_plone/wikipage_footer.pt: add
	the subject field
        * skins/default/wikipage.zpt, skins/zwiki_plone/wikipage_footer.pt: use
	external editor borrow_lock option
	* content/basic/HelpPage.stx: bump version no.
	* tests/testZWikiPage.py: make tests pass
2025-04-25  Simon Michael  
	* Makefile, __init__.py, version.txt: version
	* ZWikiPage.py: start stx at H2 not H3
	* ZWikiPage.py: try ellipsis for blank subject
2025-04-22  Simon Michael  
	* CHANGES.txt: wording
2025-04-21  Simon Michael  
	* Parents.py:
	remove present_nesting's broken and now unnecessary realid argument
	* Tracker.py: log "property change" more discreetly
	* CHANGES.txt: corrections
	* CHANGES.txt: wording
	* CHANGES.txt: current notes
	* CHANGES.txt: tweaks
	* CHANGES.txt: wording tweaks
	* CHANGES.txt: start release notes
	* ChangeLog: latest
	* ZWikiPage.py:
	 - don't require change regulations permission just to comment (IssueNo0485, from irc)
	 - playing with the blank-subject comment heading format.. not happy with
	  it yet
	* Parents.py: Two fixes:
	 - be more robust with pre-0.17 page ids that have not yet been upgraded (IssueNo0495)
	 - use precise links in contents, standard_error_message no longer required (IssueNo0454, Leslie Barnes)
2025-04-14  Simon Michael  
	* Parents.py:
	quick ancestorsAsList to keep limi happy.. works for my 0, 1, 2-parent
	cases, tests later.
2025-04-08  Simon Michael  
	* Utils.py:
	when saving a page with the optional safety belt/type/log headings, don't
	require a space after the :. Was causing a name error with external editor
	+ emacs whitespace mode (IssueNo0438, AndrewBurrow)
	* Mail.py:
	mailouts were failing when creating a page with no initial text
2025-04-06  limi  
	* skins/zwiki_plone/diffform.pt:
	Updated form to use the Plone History page, some visual tweaks.
	* skins/zwiki_plone/contentspage.pt: Initial skinning of Contents Page
2025-04-05  limi  
	* skins/zwiki_plone/subscribeform.pt:
	Did some layout changes and added a condition so subscriber list isn't shown unless they exist
	* skins/zwiki_plone/diffform.pt:
	added revision variables, now it's functional too ;)
2025-04-05  Simon Michael  
	* UI.py: that should be 'Filesystem Page Template'
2025-04-05  limi  
	* skins/zwiki_plone/backlinks.pt: initial check-in
2025-04-05  Simon Michael  
	* UI.py: fixed dc's fixes, cleaned up
	backlinks/contentpage/diffform/editform/subscribeform and made them
	recognize CMF's FS Page Templates and give a warning if some unrecognized
	template object is found.
2025-04-05  limi  
	* skins/zwiki_plone/diffform.pt: initial non-functional cleanup
	* skins/zwiki_plone/subscribeform.pt: initial subscribeform
2025-04-05  dreamcatcher  
	* skins/zwiki_plone/editform.pt, UI.py:
	Wrap with nice context + some editform fixes
	* UI.py: diffform had the same problem: wrong context. fixed.
	* UI.py, CMFInit.py:
	subscribeform was getting a wrong context. *here* was the wikifolder, which has no wiki_url.
2025-04-05  Simon Michael  
	* Parents.py: docstring
	* content/basic/UserOptions.stxdtml:
	redirectURL was not being honoured if just clearing cookies
2025-04-04  Simon Michael  
	* ChangeLog: latest
	* Tracker.py:
	when changing issue properties, add a comment to record what was done
	(DeanGoodmanson)
	* ZWikiPage.py:
	log error and continue if there is an id renaming clash during upgradeAll
	(IssueNo0483)
2025-04-03  Simon Michael  
	* ZWikiPage.py, tests/testMailout.py:
	make comment mailout work when there is no subject_heading field
	(as with the zwiki_cmf skin); and a non-working unit test (IssueNo0481)
2025-04-02  Simon Michael  
	* skins/default/wikipage.zpt:
	enter in page management form does a reparent (IssueNo0170);
	preliminary CSS class attributes to control colours
	* Extensions/Install.py: fix docstring (IssueNo0406)
	* Makefile, __init__.py, version.txt: 0.17.0 (confusing isn't it ?)
2025-04-01  Simon Michael  
	* __init__.py, version.txt, Makefile: bump trunk version to 0.18.0cvs
	* version.txt, Makefile, __init__.py, ChangeLog:
	merge changes from this morning's release-0-17-branch
	* Makefile:
	simplify tarball-building; put it in the main ~/zwiki/releases when
	working in a branch copy
	* diffform.dtml, skins/default/diffform.dtml:
	move this to the right place
	* ChangeLog, Makefile, __init__.py, version.txt:
	note latest changes, bump version to rc3
	* diffform.dtml: New file.
	* diffform.dtml:
	this didn't get tagged for 0.17rc1 or 2 somehow. Add it now in
	release-0-17-branch.
2025-04-01  dreamcatcher  
	* Extensions/__init__.py: Adding __init__.py to make module importable
2025-03-29  Simon Michael  
	* skins/default/backlinks.pt: cleanup
	* skins/default/editform.pt:
	and lastly, a page template version of editform. I have not bothered with
	regulations until I hear from users.
	* Fit.py, ZWikiPage.py, skins/default/editform.dtml:
	remove the superseded dtmlfithtml and stxdtmlfithtml page types
	* skins/default/backlinks.pt: omit that div
	* skins/default/backlinks.pt:
	a new and improved page template-based backlinks form (calls backlinksFor).
	* ZWikiPage.py:
	backlinksFor optimisation: use catalog if present. This can now replace
	the dtml code that has been used in the backlinks form.
	* CHANGES.txt: cleanups
	* tests/testZWikiPage.py: backlinksFor test
	* ZWikiPage.py:
	backlinksFor cleanup, accept and search for either page name or id
	* UI.py: update backlinks & editform page template calls
2025-03-28  Simon Michael  
	* skins/default/subscribeform.pt: typo
	* UI.py: fix subscribeform page template context
	* skins/default/contentspage.pt, skins/default/contentspage.zpt:
	rename to use .pt
	* skins/default/diffform.pt, skins/default/subscribeform.pt:
	working current page template versions of diffform & subscribeform
	* skins/default/contentspage.zpt: title fix
	* skins/default/subscribeform.dtml: another typo
	* skins/default/subscribeform.dtml: typo
	* Mail.py: docstrings
2025-03-27  Simon Michael  
	* skins/default/diffform.pt: don't need the folder title here
	* skins/default/diffform.pt: *** empty log message ***
	* skins/default/diffform.dtml: typo
2025-03-27  limi  
	* Extensions/Install.py:
	Changed the install script to install Plone skin. Note that we are not removing the zwiki_cmf skin until we have a good Plone skin that works in both, but from here on it will install the Plone skin as default.
2025-03-26  Simon Michael  
	* CHANGES.txt, Makefile, __init__.py, version.txt: rc2
	* Fit.py: another fit dependency (IssueNo0478)
	* CHANGES.txt: cleanups
	* CHANGES.txt: add WWML notes
	* CHANGES.txt: finish release notes
	* CHANGES.txt, ChangeLog: latest
	* Makefile, __init__.py, version.txt: version
	* skins/default/wikipage.zpt:
	look for "stylesheet" not "wiki_stylesheet". This will be changing more.
2025-03-25  Simon Michael  
	* Fit.py:
	ZWikiPage.py assumes hasFitTests is defined, but fit may not be present
2025-03-23  Simon Michael  
	* Defaults.py, Diff.py, UI.py:
	Make diff skinnable: support a diffform page template or dtml method
	accepting two arguments: 'revA' and 'difftext'.
	* skins/default/editform.dtml: wording
	* skins/default/editform.dtml: more tooltips
	* skins/default/editform.dtml: more compact layout, better tab ordering
	* skins/default/editform.dtml: tooltip for the create/save button
	* skins/default/editform.dtml:
	Simpler Save/Create button. Beginnings of a cancel button commented out.
	* skins/default/editform.dtml:
	show a link to TextFormattingRules if it exists
	* skins/default/backlinks.dtml, skins/default/contentspage.dtml, skins/default/contentspage.zpt, skins/default/editform.dtml, skins/default/subscribeform.dtml:
	lose the gray title background and use site logo on all "form" pages
	* skins/default/contentspage.zpt: don't html-quote singletons
	* ChangeLog: latest
	* skins/default/contentspage.dtml, skins/default/contentspage.zpt, skins/default/standard_wiki_header.dtml, skins/default/wikipage.zpt, Parents.py, UI.py, ZWikiPage.py, Defaults.py:
	- Made the contents view skinnable, with a contentspage template
	(page template or dtml method) accepting two arguments: 'hierarchy'
	(a html string) and 'singletons' (a list of strings).
	- "contents" is used more consistently and is the preferred new name for
	the map method.
	- Some tweaks from PeterMerel: backlinks accepts multiple wiki names in
	"other parents"; make "you are here" bold; singletons are now listed at
	the bottom, if any.
2025-03-22  Simon Michael  
	* content/tracker/FilterIssues.stxdtml, content/tracker/IssueTracker.stxdtml:
	latest IssueTracker & FilterIssues from zwiki.org
	* content/tracker/IssueTracker.stxdtml:
	latest layout tweaks from zwiki.org
2025-03-21  Simon Michael  
	* ZWikiPage.py: auto-upgrade issuedtml page type
	* Tracker.py: remove changeProperties, cleanup
	* ZWikiPage.py, tests/testZWikiPage.py:
	pageWithFuzzyName case bugfixes & tests. ignore case has no extra cost now
	and is always used, the argument is ignored (but left in place for
	backwards compatibility).
	* Tracker.py:
	use changeIssueProperties now, and do a full page rename with link
	updating when changing issue description (slow).
	* ZWikiPage.py: don't let rename send mail unless the page name changes
	* Parents.py: dang, can't use pages() there either, yet
	* Extensions/mailin.py: oops, can't use pages() there
	* Parents.py, ZWikiPage.py, Extensions/fixprops.py, Extensions/mailin.py:
	new pages() method returns a list of pages in the wiki, consolidates
	objectValues calls.
	* ZWikiPage.py, tests/testZWikiPage.py: pageWithId ignore_case bugfix
	* ZWikiPage.py, tests/testCataloging.py, tests/testZWikiPage.py:
	Page lookup bugfixes, clean up, speed up. pageWith* now return a correct
	acquisition wrapper (fixes IssueNo0472), and look only in the current
	folder (pageWithFuzzyName may have been acquiring). Typical page rendering
	is roughly "twice as fast" as before.
2025-03-20  Simon Michael  
	* ChangeLog: latest
	* Mail.py, tests/testMailout.py, tests/testZWikiPage.py: tests, cleanup
	* ZWikiPage.py: mail out original comment text, not the page diff;
	don't look for a subject in comment body any more;
	also italicize citations when there is no subject;
	cleanups.
	* Mail.py, tests/testMailout.py, tests/testZWikiPage.py:
	long headings were not being stripped from mailouts due to line wrapping
	* Mail.py, ZWikiPage.py:
	don't send mailouts from a page named TestPage, whether or not it has
	[test] subject; log discarded test mailouts; cleanups
	* Tracker.py:
	use new stxprelinkdtmlfitissuehtml page type for new issues; cleanups
2025-03-19  Simon Michael  
	* ZWikiPage.py:
	make upgradeId (and upgradeAll) update all backlinks when renaming page
	ids, before I forget. This was really slow last time I tried, but better
	that than complicating wiki upgrade procedures.
2025-03-18  Simon Michael  
	* ChangeLog: latest
	* ZWikiPage.py, tests/testZWikiPage.py:
	 - faster wiki link lookup based on invariant ids
	 - a bugfix for pageWithName() & pageWithId() (don't acquire during
	   lookup, do return with acquisition wrapper)
2025-03-18  Simon Michael  
	* ZWikiPage.py, tests/testZWikiPage.py:
	 - faster wiki link lookup based on invariant ids
	 - a bugfix for pageWithName() & pageWithId() (don't acquire during
	   lookup, do return with acquisition wrapper)
2025-03-16  Simon Michael  
	* ZWikiPage.py, ZWikiWeb.py, skins/default/editform.dtml, skins/zwiki_cmf/editform.pt, tests/testRendering.py:
	stxprelinkdtmlfitissuehtml supersedes stxprelinkdtmlhtml
	* tests/testZWikiPage.py: cleanup
	* Fit.py:
	latest fit code tests tables whose first cell begins with "fit." or
	"fittests.". FitTest tag no longer needed.
	* Fit.py, Tracker.py, ZWikiPage.py, skins/default/editform.dtml, skins/zwiki_cmf/editform.pt, tests/support.py:
	new general-purpose page type incorporating issue and fit support:
	stxprelinkdtmlfitissuehtml. fit tests are run if the page contains a table
	marked with FitTest tag (requires a python fit modification).  An issue
	properties form is displayed if the page name begins with IssueNo.
2025-03-15  Simon Michael  
	* tests/support.py, tests/testZWikiPage.py:
	cleanup, fit experimentation
2025-03-13  Simon Michael  
	* content/basic/RecentChanges.stxdtml:
	a try got moved to the wrong place during debugging
	* tests/support.py: workaround for emacs imenu
	* Fit.py, ZWikiPage.py:
	preliminary fit support in stxprelinkdtmlhtml pages; fit now only runs
	tables which have FitTest as the last word in the first cell.
	* ZWikiPage.py: preserve creation info when renaming (IssueNo0398)
2025-03-12  Simon Michael  
	* ZWikiPage.py: make _replaceLink private
2025-03-11  Simon Michael  
	* ZWikiPage.py:
	utility methods for looking up pages and replacing links
	* Extensions/Install.py, Extensions/cmf_install_zwiki.py:
	rename CMF install method to suit CMFQuickInstaller (issue 0460)
	* content/basic/RecentChanges.stxdtml: no longer need T flag
	* Tracker.py:
	changeProperties typo was messing up page names (issue 0464)
	* ZWikiPage.py: fix delete typo
	* ChangeLog: latest
	* ZWikiPage.py, tests/testZWikiPage.py:
	Don't leave orphans when deleting a page, and a broken test for this; test
	cleanups
	* Tracker.py, content/tracker/IssueTracker.stxdtml:
	Tracker modifications to comply with new invariant page id policy. For now
	it's assumed that issue pages are named "IssueNoNNNN issue description".
	* ZWikiPage.py:
	- rename() changes: support for upgrading to new-style ids;
	  allow renames without sending mail; avoid unnecessary indexing
	- upgradeId() brings pages into compliance with new invariant ids
	  policy. May be called directly or via upgradeAll but not by
	  auto upgrade at present (too slow ?)
	* tests/testRendering.py: fix tests after adding link class attribute
	* CatalogAwareness.py: cleaner catalog logging
	* tests/support.py: cleanup
2025-03-07  Simon Michael  
	* ZWikiPage.py: page creation links now have class "new" (IssueNo0456)
2025-03-06  Simon Michael  
	* ZWikiPage.py: be more tolerant of confused parentage when renaming
	* content/basic/RecentChanges.stxdtml:
	the latest zwiki.org version. Dropped the 'usetable' option, be less
	chatty about brute force/catalog, move note field next to page name.
	* ZWikiPage.py, tests/testZWikiPage.py:
	Punctuation characters are now omitted from canonical ids.
	They are treated as word separators (affecting capitalization),
	except for '.
	* CMF.py, ZWikiPage.py:
	get conditional import out of ZWikiPage.py so imenu can list methods
	* ChangeLog: latest
	* ZWikiPage.py, tests/testRendering.py: cleanup
	* tests/doctest_zope.py, tests/testDoctests.py: remove doctest support
	* ZWikiPage.py:
	more cleanup. Moving the rendering & linking or editing categories to
	mixins seems hard.
	* Makefile: useless tweak
	* CatalogAwareness.py, Mail.py, ZWikiPage.py, tests/testCataloging.py, tests/testDoctests.py, tests/testMailout.py, tests/testRendering.py, tests/testZWikiPage.py, tests/testZWikiWeb.py:
	converted all the doctests to pyunit tests. Cleaner, faster and better
	overall.
	* ZWikiPage.py: more cleanup
	* UI.py, ZWikiPage.py: move ui methods into their own mixin/module
	* Makefile: functest target runs site tests
	* ZWikiPage.py:
	cleanup begins. removed _oldRenderLinksIn and _replaceWikilink
	* Makefile, __init__.py, version.txt, content/basic/HelpPage.stx:
	version
2025-03-02  Simon Michael  
	* CHANGES.txt, Makefile, __init__.py, version.txt: release
2025-02-26  Simon Michael  
	* WWML.py:
	Fixed a couple of little bugs and added a couple of little extra markups.
	(PeterMerel)
	* tests/testZWikiPage.py:
	non-functional unit test for renaming & parentage
	* ZWikiPage.py: preserve parentage when pages are renamed (IssueNo0168)
2025-02-24  Simon Michael  
	* CMF.py, CMFInit.py, skins/zwiki_cmf/editform.pt, skins/zwiki_cmf/wikifolder_edit.py, skins/zwiki_cmf/wikifolder_edit_form.pt, skins/zwiki_cmf/wikipage_footer.pt, skins/zwiki_cmf/wikipage_header.pt:
	enhancements and fixes to the CMF support and zwiki_cmf skin from
	the nzo project - title fix, wiki folder edit view, don't create
	standard_error_message in CMF, CMF-style edit form, other skin
	updates. (Sidnei da Silva, LaloMartins)
	* CatalogAwareness.py: fix a strange catalog bug
	* ZWikiPage.py: turn on heading if there is a subject
2025-02-20  Simon Michael  
	* ZWikiPage.py: fix long lines
	* Makefile: update test rules
	* Mail.py: don't mail out [test] comments by default
	* Extensions/mailin.py: this is no laughing matter.
	* Mail.py: mail_page_name bug
	* Mail.py: mail_signature bugfix
	* Mail.py: tweaks for mailing list and testing support:
	 - generate list id from mail_from
	 - allow mail_signature property to override mailout footer;
	 - allow mail_page_name boolean property to disable page name
	   in subject
	 - add x-no-archive header to test messages
	* Extensions/mailin.py: parse boolean arguments correctly
	* Extensions/mailin.py:
	support for not reflecting to mailing lists, commented out
	* ZWikiPage.py: mailin.py and comment() updates for
	mailing list support (do_mailout argument),
	testing support (skip comments with [test] subject),
	and add discussion icon to comment headings in CMF
	* Mail.py: do add one space, after the bracketed page name
	* Mail.py: don't add spaces in mailout subjects
	* tests/support.py, Extensions/mailin.py, Mail.py: cleanup
2025-02-07  Simon Michael  
	* Mail.py: debug-log reason for mailout failures
2025-02-06  Simon Michael  
	* LocalizerSupport.py:
	catch ustr NameError caused by non-unicode zope or unit testing
2025-02-05  Simon Michael  
	* tests/testRendering.py, tests/testZWikiPage.py, tests/testMailin.py, tests/testMailout.py, tests/testDoctests.py, tests/testCataloging.py:
	test modules re-org
	* ChangeLog: fix formatting for web page
	* ChangeLog: latest
	* Extensions/mailin.py:
	with multiple bracketed strings in a subject, use the right-most
	* ZWikiPage.py, tests/testZWikiPage.py: make tests pass
	* Mail.py: fix web comment heading stripping in mailouts
	* Makefile, __init__.py, version.txt: bump version
	* ZWikiPage.py: send mailouts regardless of the use_heading flag;
	use a more compact web comment heading format
2025-02-01  Simon Michael  
	* Makefile, __init__.py, version.txt, content/basic/HelpPage.stx:
	version number
2025-01-31  Simon Michael  
	* ChangeLog: latest
2025-01-30  Simon Michael  
	* tests/testMailin.py: update mailin tests
	* CHANGES.txt, Mail.py, Extensions/mailin.py:
	- don't store page names in recipient real name. This seems to create more
	confusion than it solves. mailin.py's checkrecipient option is now off by
	default, and the line in Mail.py:sendMailTo() which puts the page name in
	mailouts' reply-to is commented out.
	- don't recognize bare wiki names in mailin subjects - only page names
	enclosed in brackets. Explicit is better than implicit.
	* ChangeLog: latest
	* Extensions/mailin.py: disable checkrecipient by default
	* CHANGES.txt, ChangeLog: latest
	* Makefile, __init__.py, version.txt, content/basic/HelpPage.stx:
	version number
	* skins/zwiki_orig/wikipage_view.pt:
	mirror the default skin's maxquotesize increase
	* ZWikiPage.py:
	slight refactoring of comment and subjectAndTextFrom. A subject (explicit
	or inlined) should now be used for the transaction log regardless of the
	use_heading flag.
2025-01-30  Simon Michael  
	* Extensions/mailin.py: disable checkrecipient by default
	* CHANGES.txt, ChangeLog: latest
	* Makefile, __init__.py, version.txt, content/basic/HelpPage.stx:
	version number
	* skins/zwiki_orig/wikipage_view.pt:
	mirror the default skin's maxquotesize increase
	* ZWikiPage.py:
	slight refactoring of comment and subjectAndTextFrom. A subject (explicit
	or inlined) should now be used for the transaction log regardless of the
	use_heading flag.
	* Tracker.py: change capitalization for valid HTML
2025-01-21  Simon Michael  
	* ZWikiPage.py:
	leave unknown link titles blank instead of showing the "xxx zwiki bug"
	message
	* skins/default/wikipage.zpt:
	- fixes for valid HTML (Pieter Biemond, IssueNo0399)
	- increase maxquotesize to 300
	- handy (un)subscribe display (shows un when user is subscribed),
	  commented out for now because it's slow
	- handy HTML validation link, commented out
	* skins/zwiki_orig/wikipage_view.pt:
	oops, get rid of the javascript double-click from here too
2025-01-20  Simon Michael  
	* ZWikiPage.py, tests/testZWikiPage.py:
	non-headed comments were sending error reports to the mail_admin
2025-01-13  Simon Michael  
	* Makefile:
	note the version number hard-coded in standard_error_message too
2025-01-10  Simon Michael  
	* Makefile, __init__.py, version.txt: version number
2025-01-06  Simon Michael  
	* CHANGES.txt, ChangeLog: latest
	* Makefile, __init__.py, version.txt, content/basic/HelpPage.stx:
	version number
2025-12-19  Simon Michael  
	* ChangeLog: latest
	* tests/testMailin.py: 7 new tests
	* Extensions/mailin.py:
	fix the new page in real name regexp; really use the first existing page
	if default page doesn't exist
	* Extensions/mailin.py: the other part of the page in real name change
	* Extensions/mailin.py:
	be more fussy about extracting page from real name;
	allow mailing list & bulk mailins
2025-12-14  Simon Michael  
	* Extensions/mailin.py:
	looks like renaming mailin's "self" argument to "context" was a bad idea
2025-12-10  Simon Michael  
	* Extensions/mailin.py:
	cleanups, fix a typo that prevented tracker-type addresses from being
	recognized.
2025-12-08  Simon Michael  
	* mailin.py: a rewritten mailin.py and unit tests. Fixes IssueNo0376.
2025-12-07  Simon Michael  
	* mailin.py: doc updates
	* mailin.py: strip leading and trailing spaces from a real name
	* mailin.py:
	recognize bracketed free form names in subject (will use the first
	bracketed thing found); ignore real names which contain no word
	characters; cleanup
	* Makefile, __init__.py, version.txt, content/basic/HelpPage.stx:
	bump version number, update makefile docs
2025-12-06  Simon Michael  
	* Permissions.py:
	View depended on CMF being installed.. just use standard View permission
	in all cases. If CMFCorePermissions.View is ever something different, we'd
	need to adapt.
	* Parents.py, ZWikiPage.py:
	finish the switch to modern security declarations,
	cleanups
	* CMF.py, Permissions.py:
	"gosh, Permissions.Move doesn't seem to be used anywhere.."
	* Permissions.py: sort permissions by visible name
	* ChangeLog: latest
2025-12-05  Simon Michael  
	* content/tracker/IssueTracker.stxdtml:
	a non-empty page title was appearing as new issue title
	* Extensions/mailin.py: use the generic IssueTracker name
	* ZWikiPage.py: typo in rename
2025-12-04  Simon Michael  
	* ChangeLog: latest
	* ZWikiPage.py: send mailouts for page deletion and renaming
	* Makefile, __init__.py, version.txt, content/basic/HelpPage.stx:
	bump version number
2025-12-01  Simon Michael  
	* ChangeLog: latest changes
	* CHANGES.txt, Makefile, __init__.py, version.txt: bump version number
2025-11-30  Simon Michael  
	* Regulations.py: another missing import
	* Regulations.py: missing import
	* Regulations.py, ZWikiPage.py:
	switch RegulationsSupport to new-style security declarations
	to avoid a zope 2.5.0 class initialization bug
	* Mail.py:
	make isMailoutEnabled better, and use full page name in reply-to
2025-11-27  Simon Michael  
	* CHANGES.txt, ChangeLog: latest changes
	* ZWikiPage.py: fix title changing in ZMI Edit tab (IssueNo0280)
	* ChangeLog: latest changes
	* CatalogAwareness.py, ZWikiPage.py:
	make tests pass, at least with zope 2.6
	* skins/default/wikipage.zpt, skins/zwiki_orig/wikipage_view.pt:
	truncate quote when necessary, and fail silently if AnnoyingQuote is not
	present
	* ChangeLog, CHANGES.txt, ZWikiPage.py: latest changes
	* CMF.py: add missing security declarations
	* CMF.py, ZWikiPage.py, __init__.py:
	- begin the switch to declarative security
	- fix: initialize the ZWikiPage class, not the module!
	- make editing possible in CMF (IssueNo0366)
	- cleanup
	* Tracker.py: comment
	* Makefile: update for zope 2.6.0
2025-11-26  Simon Michael  
	* skins/default/wikipage.zpt: remove the double-click-to-edit here too
	* ZWikiPage.py: remove debug statement
	* ZWikiPage.py:
	handle _st_data properly when upgrading a WikiForNow page (untested)
	* ZWikiPage.py: this should be a list, fixes IssueNo0358
	* Utils.py:
	log messages at BLATHER priority instead of DEBUG, and don't
	require the extra Z_DEBUG_MODE/ZWIKI_DEBUG variable any more
	* ZWikiPage.py:
	use created page's name in mailout subject, not parent's (IssueNo0372)
	* skins/default/standard_wiki_header.dtml, skins/zwiki_orig/wikipage_view.pt:
	remove the double-click-to-edit javascript, maybe bring back later as an
	option
	* Utils.py: interim workaround for IssueNo0270, cleanup
2025-11-21  Simon Michael  
	* tests/testZWikiPage.py, ZWikiPage.py:
	don't use < hr> in comment headings
	* CMFInit.py: give wiki_folder a view action, update description
	* skins/default/standard_wiki_header.dtml, skins/default/wikipage.zpt:
	javascript double-click to edit, courtesy of Wikipedia
2025-11-20  Simon Michael  
	* content/basic/UserOptions.stxdtml: change "Set" to "Save"
	* Extensions/mailin.py:
	look for 'mailin_policy' property instead of 'posting_policy';
	the latter is still supported but deprecated
	* content/basic/RecentChanges.stxdtml:
	latest RecentChanges from zwiki.org
	* ChangeLog: latest changes, minus the usual change log mode lossage
	* skins/zwiki_orig/wikipage_view.pt:
	CMF skin that looks just like zwiki's standard non-CMF ui
	* CMF.py, CMFInit.py, ZWikiWeb.py, Extensions/cmf_install_zwiki.py:
	remove more CMFWiki-isms, make add wiki folder work in CMF, cleanups
	* Makefile: bump version number
	* ZWikiPage.py, tests/testDoctests.py: adapt to renamed WWML rename
	* Defaults.py, ZWikiWeb.py, Extensions/cmf_install_zwiki.py:
	adapt to rearranged skins & content
	* WWML.py, wwml.py: rename wwml.py to WWML.py
	* content/tracker/IssueTracker.stxdtml: a tracker content directory
	* content/basic/AnnoyingQuote.stx, content/basic/FrontPage.stx, content/basic/HelpPage.stx, content/basic/RecentChanges.stxdtml, content/basic/SearchPage.stxdtml, content/basic/UserOptions.stxdtml, content/basic/WikiWikiWeb.stx, content/basic/ZWiki.stx, content/basic/index_html.dtml, content/basic/standard_error_message.dtml:
	move templates/zwikidotorg to content/basic
	* ZWikiPage.py:
	make web comment subject recognition more careful. To set the subject
	from a web comment, use an initial one-line paragraph that is bold
	(using '** or < b>') and no longer than 100 characters.
2025-11-19  Simon Michael  
	* Extensions/mailin.py: bugfix ? and log some failures
	* Mail.py: strip web comment headings properly even if they contain
	the CMF discussion icon
	* ZWikiPage.py: generate a mailout when pages are created
	* Extensions/mailin.py: fix page creation a bit
	* ZWikiPage.py:  - update docs for _renderWithLinks, _setText
	 - wiki links to pages with accented names (and freeform names in
	   general if standard_error_message is not present ?) were not working
	 - parent wiki links were generating an error
	 - allow mail and web comments to post a subject heading.
	   The latter assumes an initial one-line paragraph is the subject.
	   Save comment subject as edit log note and vice versa
	* Mail.py: cleanup
	* Mail.py:
	encode source page in reply-to real name, allow arbitrary subject
	* Extensions/mailin.py:
	 - look for destination page in a recipient real name first
	 - post with original subject heading (cleaned up)
	* skins/wiki/wikipage_footer.pt:
	remove an empty table cell that showed up in plone
	* Defaults.py, Diff.py:
	increase the added and removed lines truncation limit and move
	it to Defaults
	* CatalogAwareness.py:
	 - update getCatalogId() so it returns 1 a SITE_CATALOG page
	   property, 2 a SITE_CATALOG containing folder property,
	   3 "portal_catalog" if it can be acquired, 4 "Catalog".
	 - make catalog() work
	* CMF.py: include commented out index_html in case it's needed
2025-11-16  Simon Michael  
	* templates/zwikidotorg/RecentChanges.stxdtml:
	limit list to 50 pages by default
	* templates/zwikidotorg/SearchPage.stxdtml: remove JumpSearch reference
	* templates/zwikidotorg/UserOptions.stxdtml:
	removed commented-out advanced edit option
2025-11-15  Simon Michael  
	* skins/wiki/wikipage_view.pt:
	pass client and REQUEST so more complex pages will render & authenticate right
2025-11-13  Simon Michael  
	* skins/wiki/wikipage_icon.gif: replace the icon
2025-11-12  Simon Michael  
	* templates/zwikidotorg/UserOptions.stxdtml:
	fix a timezones error when in CMF
	* CatalogAwareness.py:
	look for SITE_CATALOG on page and containing folder only
	* templates/zwikidotorg/UserOptions.stxdtml:
	disable user options when in CMF
	* templates/zwikidotorg/RecentChanges.stxdtml:
	use SITE_CATALOG, remove hard-coded zwiki.org
	* CMFInit.py, Extensions/cmf_install_zwiki.py, Permissions.py, skins/wiki/wikipage_footer.pt, skins/wiki/wikipage_header.pt, skins/wiki/wikipage_view.pt:
	a lightweight cmf skin for zwiki
	* skins/wiki/BackLinks.stx, skins/wiki/HowDoIEdit.stx, skins/wiki/HowDoINavigate.stx, skins/wiki/JumpTo.stx, skins/wiki/RegulatingYourPages.stx, skins/wiki/RemoteWikiLinks.stx, skins/wiki/RemoteWikiURL.stx, skins/wiki/StructuredText.stx, skins/wiki/StructuredTextExample.stx, skins/wiki/StructuredTextRules.stx, skins/wiki/TextFormattingRules.stx, skins/wiki/WikiHelp.stx, skins/wiki/WikiName.stx, skins/wiki/WikiWikiWeb.stx, skins/wiki/ZWiki.stx, skins/wiki/ZWikiLinks.stx, skins/wiki/ZWikiWeb.stx, skins/wiki/wiki_allpages.dtml, skins/wiki/wiki_recentchanges.dtml, skins/wiki/wiki_toc.dtml, skins/wiki/wiki_view.dtml, skins/wiki/wikipage_advanced_form.dtml, skins/wiki/wikipage_advanced_handler.py, skins/wiki/wikipage_alternatecreate_form.dtml, skins/wiki/wikipage_backlinks.dtml, skins/wiki/wikipage_comment_form.dtml, skins/wiki/wikipage_compare_versions.dtml, skins/wiki/wikipage_copyhistory_handler.py, skins/wiki/wikipage_create_form.dtml, skins/wiki/wikipage_create_handler.py, skins/wiki/wikipage_deleterename_handler.py, skins/wiki/wikipage_edit_form.dtml, skins/wiki/wikipage_editcomment_handler.py, skins/wiki/wikipage_header.dtml, skins/wiki/wikipage_history.dtml, skins/wiki/wikipage_icon.gif, skins/wiki/wikipage_recentchanges.py, skins/wiki/wikipage_reparent_handler.py, skins/wiki/wikipage_toc.dtml, skins/wiki/wikipage_view.dtml, skins/zpt_wiki/wiki_allpages.pt, skins/zpt_wiki/wiki_listpages.py, skins/zpt_wiki/wiki_recentchanges.pt, skins/zpt_wiki/wiki_skeleton.pt, skins/zpt_wiki/wiki_toc.pt, skins/zpt_wiki/wikipage_advanced_form.pt, skins/zpt_wiki/wikipage_backlinks.pt, skins/zpt_wiki/wikipage_comment_form.pt, skins/zpt_wiki/wikipage_create_form.pt, skins/zpt_wiki/wikipage_edit_form.pt, skins/zpt_wiki/wikipage_header.pt, skins/zpt_wiki/wikipage_history.pt, skins/zpt_wiki/wikipage_owners_description.py, skins/zpt_wiki/wikipage_regop_description.py, skins/zpt_wiki/wikipage_toc.pt, skins/zpt_wiki/wikipage_topic_report.pt, skins/zpt_wiki/wikipage_view.pt:
	remove old cmfwiki skin files
2025-11-11  Simon Michael  
	* Extensions/cmf_install_zwiki.py: cleanups
	* Extensions/cmf_install_zwiki.py: update docstring
	* Extensions/CMFInstall.py, Extensions/cmf_install_zwiki.py:
	rename CMFInstall.py to cmf_install_zwiki.py
	* CMF.py, ZWikiPage.py: use CMF skin when appropriate
2025-11-10  Simon Michael  
	* Makefile: less verbose test output
2025-11-06  Simon Michael  
	* CMFInit.py:
	when creating a Wiki Page in CMF, allow the standard_page_type folder
	property to control it's page_type.
2025-11-02  Simon Michael  
	* CHANGES.txt, Makefile, __init__.py, templates/zwikidotorg/HelpPage.stx:
	bump version number
2025-10-26  Simon Michael  
	* Makefile: remove workqueue stuff
	* templates/defaults/editform.dtml, Fit.py, Utils.py, ZWikiPage.py:
	preliminary fit support
2025-10-24  Simon Michael  
	* ChangeLog: this month's changes
2025-10-22  jdavid  
	* dtml/zwikiPageAdd.dtml, dtml/zwikiWebAdd.dtml, locale/es.po, locale/fr.po, locale/ga.po, locale/locale.pot, ZWikiWeb.py, LocalizerSupport.py, ZWikiPage.py:
	Added a dummy dtml-gettext tag to LocalizerSupport.
	Internationalized zwikiPageAdd. Minor fixes in zwikiWebAdd for consistency.
	Updated PO files, spanish translation updated.
2025-10-20  Simon Michael  
	* Mail.py: docs
2025-10-12  jdavid  
	* locale/es.po, locale/fr.po, locale/ga.po, locale/locale.pot, Makefile:
	Updated PO files using the new version of zgettext, which solves a
	number of problems.
2025-10-10  Simon Michael  
	* Mail.py: revert argument name changes from "email" to "subscriber",
	to avoid breaking legacy dtml.
	* ZWikiPage.py:
	that was a weird one - self.ZopeTime didn't work on a page in CMF,
	perhaps due to skin acquisition magic. Watch for the same thing
	elsewhere (comment, subscribeform, tracker).
	* Mail.py: make subscription tests aware of usernames; refactoring
2025-10-09  Simon Michael  
	* Mail.py:
	allow a 'mail_subject_prefix' folder property to override the folder title
	for mailout subjects. Useful when mailout subject and cmf's "you are here"
	must differ.
2025-10-08  Simon Michael  
	* __init__.py, version.txt, templates/defaults/standard_wiki_footer.dtml, templates/defaults/wikipage.zpt, templates/zwikidotorg/HelpPage.stx:
	bump version number, minor default ui & content tweaks
	* Mail.py, ZWikiPage.py:
	 - refactored stxtohtml; apply the initial-word-becomes-bullet workaround
	   for all zope versions
	 - mailsupport refactoring; upgrade wikifornow subscribers; allow
	   usernames in subscriber lists
	* Makefile: minor stuff
2025-10-07  jdavid  
	* ZWikiPage.py, ZWikiWeb.py, dtml/zwikiWebAdd.dtml, locale/en.po, locale/es.po, locale/fr.po, locale/locale.pot, LocalizerSupport.py, Makefile, Utils.py:
	Add ZWikiWeb internationalized and localized to spanish.
	Added LocalizerSupport module so ZWiki works with and without Localizer.
2025-10-01  Simon Michael  
	* Makefile: fixed zope.org news item creation
	* ChangeLog: latest changes
	* CHANGES.txt, Makefile, __init__.py, version.txt, templates/zwikidotorg/HelpPage.stx:
	bump version number to 0.11.0
2025-09-27  Simon Michael  
	* ChangeLog: latest changes
	* CHANGES.txt, Makefile, __init__.py, templates/zwikidotorg/HelpPage.stx, version.txt:
	bump version number to rc2
	* ZWikiPage.py:
	enable stx bullet workaround for all zope versions (IssueNo0273)
2025-09-26  Simon Michael  
	* CMF.py, ZWikiPage.py:
	fix an argument ordering issue.. this may have caused breakage when CMF
	was not present
	* tests/testZWikiPage.py: adapt to 2.6's Image tag format
2025-09-25  Simon Michael  
	* templates/zwikidotorg/standard_error_message.dtml:
	work when we are not in site's root folder (IssueNo0250)
	* templates/zwikidotorg/HelpPage.stx: clean up wikiname wording
	* CMF.py: comments
	* Regexps.py:
	try to enable international page names out of the box, with or
	without a locale setup (IssueNo0257)
	* Utils.py:
	be more robust reading version.txt.. zope-cvs doesn't have one
	(IssueNo0254)
	* Parents.py, ZWikiPage.py:
	Don't allow completely anonymous invocations of rename/delete
	(cookieless reparenting is still allowed, for now) (IssueNo0235)
	* Makefile: add a rule for updating the zodb analysis
	* Permissions.py, ZWikiPage.py, __init__.py:
	specify FTP access permission; send manage_edit through wiki editing code
	the way WikiForNow does (IssueNo0243)
	* ZWikiPage.py: more datetime robustness fixes for IssueNo0222
	* ZWikiPage.py: be more robust when upgrading timestamps (IssueNo0222)
	* ZWikiPage.py:
	remove unnecessary PageTemplates import, improve robustness with
	older zopes (IssueNo0224)
	* Mail.py: filter blank items from recipients (IssueNo0221);
	include some in-progress username & migration support,
	commented out for 0.11.
2025-09-24  Simon Michael  
	* dtml/zwikiWebAdd.dtml:
	don't use prefix for compatiblity with old zopes (IssueNo0229)
	* Tracker.py:
	use new rendering code for issue pages (IssueNo0252, IssueNo0253)
	* ZWikiPage.py: pageWithNameOrId was acquiring when it shouldn't
2025-09-23  Simon Michael  
	* Regexps.py:
	when setting wikiname regexps explicitly, make word boundary matching
	more robust
	* ZWikiPage.py:  - custom standard_wiki_footer was broken
	 - provide tracebacks in html source when failing to render
	   standard_wiki_header or footer
2025-09-22  Simon Michael  
	* Extensions/README: README for external methods directory
	* CMF.py, ZWikiPage.py:
	 - webdav lock/edit conflict checking fixes to support external editor
	   (CaseyDuncan)
	 - fix DublinCore initialisation and clarify inheritance ordering
	   issues with CMFAwareness
2025-09-20  Simon Michael  
	* ZWikiPage.py: add tres to short credits
	* CMF.py, CMFInit.py, Defaults.py:
	factor out wiki folder meta type properly
	* ChangeLog: latest changes
	* default_wiki_content/FrontPage.wiki: get rid of !
	* Makefile: functest rules, work around ridiculous find screwups
	* CMF.py, CMFInit.py, Defaults.py, ZWikiPage.py, __init__.py, Extensions/CMFInstall.py:
	move CMF support into standard ZWikiPage
2025-09-16  Simon Michael  
	* ChangeLog: latest changes
	* CMF.py, Extensions/CMFInstall.py, Permissions.py, __init__.py, default_wiki_content/FrontPage.wiki, default_wiki_content/SandBox.wiki, skins/wiki/BackLinks.stx, skins/wiki/HowDoIEdit.stx, skins/wiki/HowDoINavigate.stx, skins/wiki/JumpTo.stx, skins/wiki/RegulatingYourPages.stx, skins/wiki/RemoteWikiLinks.stx, skins/wiki/RemoteWikiURL.stx, skins/wiki/StructuredText.stx, skins/wiki/StructuredTextExample.stx, skins/wiki/StructuredTextRules.stx, skins/wiki/TextFormattingRules.stx, skins/wiki/WikiHelp.stx, skins/wiki/WikiName.stx, skins/wiki/WikiWikiWeb.stx, skins/wiki/ZWiki.stx, skins/wiki/ZWikiLinks.stx, skins/wiki/ZWikiWeb.stx, skins/wiki/wiki_allpages.dtml, skins/wiki/wiki_recentchanges.dtml, skins/wiki/wiki_toc.dtml, skins/wiki/wiki_view.dtml, skins/wiki/wikipage_advanced_form.dtml, skins/wiki/wikipage_advanced_handler.py, skins/wiki/wikipage_alternatecreate_form.dtml, skins/wiki/wikipage_backlinks.dtml, skins/wiki/wikipage_comment_form.dtml, skins/wiki/wikipage_compare_versions.dtml, skins/wiki/wikipage_copyhistory_handler.py, skins/wiki/wikipage_create_form.dtml, skins/wiki/wikipage_create_handler.py, skins/wiki/wikipage_deleterename_handler.py, skins/wiki/wikipage_edit_form.dtml, skins/wiki/wikipage_editcomment_handler.py, skins/wiki/wikipage_header.dtml, skins/wiki/wikipage_history.dtml, skins/wiki/wikipage_icon.gif, skins/wiki/wikipage_recentchanges.py, skins/wiki/wikipage_reparent_handler.py, skins/wiki/wikipage_toc.dtml, skins/wiki/wikipage_view.dtml, skins/zpt_wiki/wiki_allpages.pt, skins/zpt_wiki/wiki_listpages.py, skins/zpt_wiki/wiki_recentchanges.pt, skins/zpt_wiki/wiki_skeleton.pt, skins/zpt_wiki/wiki_toc.pt, skins/zpt_wiki/wikipage_advanced_form.pt, skins/zpt_wiki/wikipage_backlinks.pt, skins/zpt_wiki/wikipage_comment_form.pt, skins/zpt_wiki/wikipage_create_form.pt, skins/zpt_wiki/wikipage_edit_form.pt, skins/zpt_wiki/wikipage_header.pt, skins/zpt_wiki/wikipage_history.pt, skins/zpt_wiki/wikipage_owners_description.py, skins/zpt_wiki/wikipage_regop_description.py, skins/zpt_wiki/wikipage_toc.pt, skins/zpt_wiki/wikipage_topic_report.pt, skins/zpt_wiki/wikipage_view.pt:
	merge CMFWiki
	* ZWikiPage.py, Regulations.py: cleanup
2025-09-15  Simon Michael  
	* Mail.py, SubscriberList.py, Tracker.py, TrackerSupport.py:
	renamed SubscriberList.py to Mail.py, TrackerSupport.py to Tracker.py
	* CatalogAwareness.py, Diff.py, Parents.py, SubscriberList.py, ZWikiPage.py, tests/support.py, tests/testDoctests.py, tests/testSubscriberList.py, tests/testZWikiPage.py:
	refactoring
2025-09-14  Simon Michael  
	* CatalogAwareness.py: refactoring
	* templates/defaults/editform.dtml:
	move options down a row to handle long page names better
	* Regexps.py, ZWikiPage.py:
	better support for international characters in page names.. more work
	needed.
2025-09-13  Simon Michael  
	* Regexps.py: cleanups
2025-09-12  Simon Michael  
	* ZWikiPage.py, templates/defaults/editform.dtml:
	add textlink (text + links) page type
	* ZWikiPage.py:
	call custom wiki page templates with the right acquisition context
	(IssueNo0225)
	* templates/zwikidotorg/standard_error_message.dtml:
	missing " ? when the hell did that happen ?
	* ZWikiPage.py:
	also set log message for type change, file upload, creation and
	deletion (via DeleteMe)
	* Diff.py: italicise log message
	* ZWikiPage.py:
	allow page type and log message to be changed via ftp/webdav (WikiForNow)
	* Diff.py: cleanup
	* Diff.py, Utils.py, ZWikiPage.py: merge some code from wikifornow
	* Makefile, __init__.py, version.txt: bump version number
	* ZWikiPage.py:
	a couple of compatibility tweaks for running inside a CMF/plone instance -
	provide view & SearchableText methods, don't use rule in comment headings.
	* ZWikiPage.py: reindex after renaming
	* templates/defaults/editform.dtml: added tabindex attribute quotes
	* templates/defaults/editform.dtml: fix tab ordering
	* ZWikiPage.py: don't add quotes in last_log property
	* templates/defaults/editform.dtml:
	change wording to "optional note" to avoid confusion
2025-09-11  Simon Michael  
	* Diff.py, ZWikiPage.py, templates/defaults/editform.dtml:
	support edit log comments like wfn & other wikis; enable in zwikidotorg;
	display in history link title in header, and at top left when diff
	browsing; use a form button to return from diff browsing
	* CatalogAwareness.py, ZWikiPage.py:
	use ZMI widgets for page_type and NOT_CATALOGED like wikifornow
	* wwml.py:
	some rendering fixes and convert spaces to tabs, described on
	ConvertSpacesToTabs (PeterMerel)
2025-09-06  Simon Michael  
	* Extensions/fixprops.py:
	utility method for fixing up zwiki page properties
	* ZWikiPage.py:
	dtml header/footer methods were being ignored (IssueNo0228)
2025-09-02  Simon Michael  
	* CHANGES.txt: cleanup
	* CHANGES.txt: latest changes
	* Makefile, __init__.py, version.txt: bump version number
	* templates/zwikidotorg/HelpPage.stx: wording cleanups
	* templates/zwikidotorg/FrontPage.stx:
	escape the MailHost wikiname since a MailHost page is problematic
	* templates/defaults/editform.dtml:
	remove TextFormatting link, jump to HelpPage's editing section
	* Makefile: tags updates
2025-08-26  Simon Michael  
	* Makefile, __init__.py, templates/zwikidotorg/HelpPage.stx, version.txt:
	bump version number
	* README.txt: cleanups
	* CHANGES.txt: cleanup
	* ChangeLog: latest
	* tests/doctest_zope.py:
	work in progress to support zope 2.3 unit testing
	* Makefile: updates
	* CHANGES.txt: updated release notes
	* templates/zwikidotorg/HelpPage.stx: help page updates
	* dtml/zwikiWebAdd.dtml, templates/zwikidotorg/FrontPage.stx:
	collected admin info on FrontPage
2025-08-25  Simon Michael  
	* templates/zwikidotorg/HelpPage.stx: cleanup
2025-08-24  Simon Michael  
	* templates/defaults/wikipage.zpt: fix the external editor check
	* Makefile: misc updates
	* templates/zwikidotorg/HelpPage.stx: new help page
	* ZWikiPage.py: cleanup
2025-08-22  Simon Michael  
	* Defaults.py:
	set here and container so that wikipage.zpt's context appears to be that
	of the current wiki page
	* ZWikiPage.py, tests/support.py, tests/testZWikiPage.py:
	various contortions to get tests passing
2025-08-20  Simon Michael  
	* ZWikiPage.py: make [] fuzzy links more forgiving about case.
	* templates/defaults/wikipage.zpt:
	fix the page mgmt. panel's text field name
	* Makefile: add zope 2.5.1 to test running rules
	* Defaults.py, ZWikiPage.py, templates/defaults/wikipage.zpt:
	the default page layout now comes from templates/defaults/wikipage.zpt;
	standard_wiki_header & standard_wiki_footer defaults are still provided
	but no longer quite as up-to-date and probably will be deprecated.  The
	wikipage template now refers to the current page as "here" instead of
	options/this. Made addStandardLayoutTo more robust.
2025-08-19  Simon Michael  
	* templates/zwikidotorg/standard_error_message.dtml:
	add noindex,nofollow meta tag and use form buttons instead of links for
	robot safety
	* Parents.py, ZWikiPage.py:
	make rename/reparent use "pagename" option instead of newid
	* TrackerSupport.py:
	extend the recent zpt & pre-rendering tweaks to tracker issue pages
	* Permissions.py, ZWikiPage.py:
	add 'Zwiki: Rename pages' permission and use it for rename
	* Permissions.py: change to the more recognizable 'Zwiki: Delete pages'
	* ZWikiPage.py: make rename aware of freeform page names
2025-08-18  Simon Michael  
	* ZWikiPage.py: be more careful when updating links after renaming
	* Diff.py: cleanup
	* Parents.py: reparent transaction note
	* ZWikiPage.py:
	made the supports* methods return a proper boolean value;
	added supportsPreLinking; added transaction note in _renderWithLinks;
	call this after changing text to avoid an extra transaction
	* ZWikiPage.py: add transaction notes for prerendering & prelinking;
	don't prerender every time when page is empty
	* ZWikiPage.py: don't add a file link if one already exists
	* ZWikiPage.py:
	don't highlight the last edit access key if it's not hyperlinked;
	allow upload to existing files; note a fuzzy-matching bug
2025-08-17  Simon Michael  
	* Diff.py: also, add a noindex meta tag to the diff browser
	* Diff.py: anti-robot hack: use form buttons in diff browser
	* templates/defaults/backlinks.dtml, templates/defaults/editform.dtml, templates/defaults/subscribeform.dtml:
	make form icons depend on show logo option
	* Regulations.py: quick fix
	* ZWikiPage.py:
	support page templates as well as dtml methods for editform,
	subscribeform, backlinks and for main page layout (a page template
	named wikipage will take precedence over standard_wiki_header
	& standard_wiki_footer dtml methods). PT namespace issues: for now, the
	current page must be referenced as options/this. The page body is passed
	to in wikipage as options/body.  The following additional options are
	passed to editform: page, text, action, id, oldid.
	* Utils.py: list flattening utility for the offspringAsList methods
	* Regexps.py: strip doctype tag after edit as well
	* Parents.py: don't include self in the offspringAsList methods
2025-08-15  Simon Michael  
	* Parents.py:
	add offspringAsList and offspringIdsAsList utility methods
	* ZWikiPage.py:
	refactor final page layout; new addStandardLayoutTo method
	* Regexps.py, ZWikiPage.py:
	strip an HTML header & footer found in edit text
	* ZWikiPage.py:
	check for a webdav lock before saving an edit or entering editform
2025-08-11  Simon Michael  
	* ChangeLog: latest changes
	* templates/zwikidotorg/AnnoyingQuote.stx, templates/zwikidotorg/FrontPage.stx, templates/zwikidotorg/JumpSearch.stx, templates/zwikidotorg/RecentChanges.stxdtml, templates/zwikidotorg/RemoteWikiLinks.stx, templates/zwikidotorg/RemoteWikiURL.stx, templates/zwikidotorg/StructuredTextRules.stx, templates/zwikidotorg/TextFormattingRules.stx, templates/zwikidotorg/WikiName.stx, templates/zwikidotorg/ZWiki.stx, templates/zwikidotorg/standard_error_message.dtml:
	reduce default content
	* templates/defaults/standard_wiki_header.dtml:
	add secret AnnoyingQuote link
	* Makefile, version.txt: bump version number
	* TrackerSupport.py:
	security notes; new methods: createIssue, used by new ZwikiTracker page to
	simplify DTML, and changeIssueProperties, more secure replacement for
	changeProperties, not yet used
	* __init__.py: ZWikiPage.manage_changeProperties was non-functional
	* ZWikiPage.py, Extensions/mailin.py:
	set last_editor properly during mailin (IssueNo0207);
	cleanups
2025-08-10  Simon Michael  
	* templates/defaults/backlinks.dtml:
	make catalog and brute-force backlinks dtml more robust (IssueNo0210);
	fix a case where the parent checkbox didn't show up
2025-08-09  Simon Michael  
	* templates/defaults/standard_wiki_footer.dtml:
	try making with heading checkbox visible in simple mode;
	don't display external edit link unless visitor has 'View management
	screens' permission
2025-08-06  Simon Michael  
	* templates/zwikidotorg/SearchPage.stxdtml: sort by title or id
	* templates/zwikidotorg/UserOptions.stxdtml: cleanup
2025-08-05  Simon Michael  
	* ZWikiPage.py: don't hide errors when rendering backlinks default
	* templates/zwikidotorg/standard_error_message.dtml: update wording
	* Regexps.py: let the RemoteWikiURL tag be case-insensitive
	* templates/defaults/editform.dtml:
	switch upload file & change page type options;
	always show change page type if permissions allow it
	* templates/defaults/editform.dtml: reduce list of available page types
	* templates/zwikidotorg/UserOptions.stxdtml: move height & width down
	* templates/defaults/standard_wiki_footer.dtml:
	highlight the default display mode (simple) when no cookie is present
	* templates/defaults/standard_wiki_footer.dtml:
	make current display mode bold
	* templates/defaults/backlinks.dtml, templates/defaults/editform.dtml, templates/defaults/standard_wiki_footer.dtml, templates/defaults/standard_wiki_header.dtml, templates/defaults/subscribeform.dtml, templates/zwikidotorg/UserOptions.stxdtml:
	a load of UI updates
	 - use simple display mode by default
	 - get rid of advanced edit option, always show file upload,
	   show page type in full display mode
	 - don't highlight parent in backlinks if hierarchy is hidden (IssueNo0204)
	 - change default textarea height back to 25
	 - make UI options more flexible & robust
	 - misc bugfixes, tweaks & cleanups
	* ZWikiWeb.py: don't show defaults in template list
2025-08-04  Simon Michael  
	* templates/defaults/backlinks.dtml, templates/defaults/editform.dtml, templates/defaults/subscribeform.dtml:
	tone down title background box
	* templates/defaults/standard_wiki_footer.dtml, templates/defaults/standard_wiki_header.dtml, templates/zwikidotorg/UserOptions.stxdtml:
	added a 'zwiki_displaymode' useroptions cookie;
	show search box and last editor in place of quote
	in simple display mode; small useroptions bugfix & cleanups.
	* templates/zwikidotorg/UserOptions.stxdtml: cleanup
	* templates/defaults/backlinks.dtml, templates/defaults/editform.dtml, templates/defaults/standard_wiki_header.dtml, templates/defaults/subscribeform.dtml:
	remove partial robot exclusion meta tag from standard_wiki_header,
	add full robot exclusion meta tag to editform/subscribeform/backlinks
	* templates/defaults/standard_wiki_header.dtml:
	disable jumpsearch (make the search box always do a simple search)
	* templates/zwikidotorg/RecentChanges.stxdtml:
	latest implementation from zwiki.org
	* templates/zwikidotorg/SearchPage.stxdtml:
	latest SearchPage from zwiki.org plus selected extra search tools
	* templates/zwikidotorg/UserOptions.stxdtml:
	provide a list of timezones to choose from, MoinMoin style (IssueNo00146)
	* templates/defaults/standard_wiki_footer.dtml, templates/defaults/standard_wiki_header.dtml:
	fix some tab ordering issues
2025-08-03  Simon Michael  
	* SubscriberList.py: cleanup docstrings
	* SubscriberList.py, templates/defaults/standard_wiki_footer.dtml:
	new isMailoutEnabled method, display subscribe link only if true
	* Defaults.py, ZWikiPage.py: [] now links only to zwiki pages;
	misc bugfixes, cleanups, new isZwikiPage method
	* templates/zwikidotorg/backlinks.dtml, templates/zwikidotorg/editform.dtml, templates/zwikidotorg/standard_wiki_footer.dtml, templates/zwikidotorg/standard_wiki_header.dtml:
	don't instantiate dtml methods for zwikidotorg by default,
	rely on the built-in defaults instead
	* templates/defaults/backlinks.dtml, templates/defaults/editform.dtml, templates/defaults/standard_wiki_footer.dtml, templates/defaults/standard_wiki_header.dtml, templates/defaults/standard_wiki_page.dtml, templates/defaults/subscribeform.dtml:
	use the latest zwiki.org content for the defaults
	* Defaults.py, ZWikiPage.py:
	read built-in dtml methods from filesystem (templates/defaults/*)
	* Defaults.py, ZWikiPage.py:
	consistent api for accessing the standard dtml methods;
	_renderHeaderOrFooter cleanups
2025-08-02  Simon Michael  
	* tests/support.py, tests/testZWikiPage.py:
	hack to make MockZWikiPage support testCreateWithFileUpload is no longer
	effective - disable the test for now
2025-07-03  Simon Michael  
	* ChangeLog: latest
	* ZWikiPage.py: fix linkTitle's prettyprint argument
	* Extensions/mailin.py:
	bugfix for mailin in the context of a zwiki page
2025-07-02  Simon Michael  
	* ChangeLog: latest changes
	* ZWikiPage.py: fix asAgeString test
	* ZWikiPage.py: oops, arguments in wrong order
	* ZWikiPage.py:
	linkTitleFrom method to generate link titles from an absolute time
	* ZWikiPage.py: make applyLineEscapes in public
	* ChangeLog: latest changes
	* SubscriberList.py:
	include mailing list headers in mailouts and use freeform title in subject
	* ZWikiPage.py:
	new upgradeAll method for batch upgrading & re-rendering
	* Extensions/mailin.py:
	update comments, check for common "out of office"
2025-07-01  Simon Michael  
	* ZWikiPage.py:
	Don't save link renderings as a page attribute. _wikilinks (dictionary) is
	replaced by _links (list). Make link titles more robust for old-style page
	ids.
2025-06-29  Simon Michael  
	* ChangeLog: latest changes
	* Utils.py, ZWikiPage.py:
	within_literal was returning true for wikinames after an unclosed
	''. I think those are legal, so tightened it up to match only
	'
	* ZWikiPage.py: renamed _renderLinks back to _renderWithLinks
	* Regexps.py, Utils.py, ZWikiPage.py:
	refactored link rendering code, use new linking code everywhere; new
	methods age, lastEditInterval, linkTitle, renderLinksIn; cleanups
2025-06-27  Simon Michael  
	* Parents.py: change wording
	* ChangeLog: latest changes
	* ZWikiPage.py:
	new asAgeString & linkTitle methods; show age & last editor in title
	attribute of wikilinks
	* Defaults.py, Parents.py:
	add meaningful link titles & accesskey attributes, clean up the logo's alt
	tag
	* Regexps.py, Utils.py, ZWikiPage.py:
	don't link WikiNames within structured text links (IssueNo0190) or the
	contents of html anchor tags (IssueNo0194)
	* Utils.py, ZWikiPage.py:
	make our html_quote ignore ", make stx links work in non-html stx modes
	(IssueNo0193)
	* Regexps.py:
	make prelinking dtml modes recognize '&dtml-ZopeTime;' (IssueNo0189)
	* ChangeLog: latest changes
	* ZWikiPage.py:
	fix ZMI add zwiki page (IssueNo0187), make manage_addZWikiPage
	more similar to create, add doctest
	* TrackerSupport.py:
	in issue form, change title's max length from 100 to 200
	* Parents.py, Regulations.py, SubscriberList.py, ZWikiPage.py, tests/support.py, tests/testZWikiPage.py:
	use new folder method for all references to the containing folder;
	make tests pass
2025-06-26  Simon Michael  
	* SubscriberList.py: And by IssueNo0166, I mean IssueNo0161
	* SubscriberList.py:
	test and fix for the multiple subscription bug (IssueNo0166)
2025-06-25  Simon Michael  
	* ChangeLog: latest changes
	* Extensions/mailin.py:
	post comments/create pages using the sender's real name or just the
	username component from their email address, not the full address
	(IssueNo0066, PieterB/SM)
	* ZWikiPage.py: preRenderAll convenience method in progress
	* ZWikiPage.py:
	add doctests and fix for wikilink line-escaping in all render modes
	(IssueNo0188)
2025-06-18  Simon Michael  
	* ChangeLog: latest
	* locale/fr-CA.po:
	canadian french translation of initial i18n strings (JoannePlouffe)
	* locale/en.po: set project name
	* ZWikiPage.py: unit test and fix for IssueNo0186
2025-06-14  jdavid  
	* locale/es.po: Spanish translation, untested.
2025-06-11  Simon Michael  
	* templates/zwikidotorg/backlinks.dtml:
	use the new canonicalLinks method to provide accurate fuzzy backlinks
	if a suitable catalog is present; otherwise fall back to (exact) brute-force
	backlinks search.
	* templates/zwikidotorg/RecentChanges.stxdtml:
	show catalog or brute-force use as a html comment, not on the page
	* ZWikiPage.py:
	new methods: canonicalLinks, canonicalId; removed linkids
2025-06-08  Simon Michael  
	* ZWikiPage.py:
	make _renderLink work with things that aren't zwiki pages
	* ChangeLog: latest changes
	* ZWikiPage.py: linkids tweaks
	* ZWikiPage.py, templates/zwikidotorg/editform.dtml:
	show free-form titles in editform
	* Regexps.py, ZWikiPage.py:
	renamed pageIdFromName to canonicalIdFrom; added linkids method to support
	fuzzy backlinks; performance optimizations; compiled some regexps;
	cleanups
	* ZWikiPage.py: new htmlunquote & urlunquote methods;
	make accidental structured text footnote processing less likely;
	use more standards-compliant "ref" prefix for footnotes, like newer stx
2025-06-07  Simon Michael  
	* ChangeLog: latest changes
	* ZWikiPage.py:
	allow edit's page argument to be a page name (DylanJay);
	new pageWithNameOrId method
	* Parents.py, ZWikiPage.py: renamed pageWith*Title to pageWith*Name
	* Utils.py, ZWikiPage.py:
	fix some python2-isms; move zope version & localizer setup to Utils
	* Regexps.py:
	make bad_id syncing more accurate and make it work with zope 2.3
	* ZWikiPage.py:
	make a test pass; generate page ids using all available characters
	according to the OFS.ObjectManager.bad_id definition
	* Regexps.py:
	cleanup; allow anything in stx footnotes; provide zwikiidchars that is
	synced with zope's bad_id regexp
	* ZWikiPage.py:
	make post-stx html & body stripping more robust (EdwardKreis)
	* ZWikiPage.py: pageIdFromTitle bugfix, cleanups
	* ZWikiPage.py:
	add some pageWithFuzzyTitle argument to allow extra fuzziness;
	beginnings of making wikilink use the new linking code;
	change pageIdFromTitle so as to preserve international characters &
	punctuation
2025-06-04  Simon Michael  
	* ChangeLog:
	emacs's C-x-v-a missed a changelog entry between those last two -
	it was about fuzzy matching support for bracketed links.
	* Parents.py, ZWikiPage.py:
	Use the title by preference in the parents list.  This means title will
	appear in the parent context display, but urls will be wrong. For now, get
	around this with a custom standard_error_message which makes them right.
	Other changes: made pageWithTitle & pageWithFuzzyTitle public & added a
	helper flag for url-quoted titles; tweaked the pageIdFromTitle algorithm
	(separate words at punctuation).
	* Parents.py, Regexps.py, ZWikiPage.py, tests/testZWikiPage.py:
	experimental - support arbitrary page titles in addition to wikinames
2025-06-02  Simon Michael  
	* templates/zwikidotorg/UserOptions.stxdtml, templates/zwikidotorg/editform.dtml:
	sync with latest zwiki.org versions; list all available page types for now
	* templates/zwikidotorg/JumpSearch.htmldtml, templates/zwikidotorg/JumpSearch.stx:
	renamed so as to use stx
	* templates/zwikidotorg/SearchPage.htmldtml, templates/zwikidotorg/SearchPage.stxdtml:
	renamed & tweaked so as to use stx+dtml
	* templates/zwikidotorg/RecentChanges.htmldtml, templates/zwikidotorg/RecentChanges.stxdtml:
	renamed so as to use stx+dtml; use catalog if available
	* TrackerSupport.py, ZWikiPage.py:
	fix a bug with render_issuedtml & make it do pre-formatting;
	for all modes, do the _protectLine replace *after* stx;
	note a subtle bug with !-protected-lines & dtml
	* Regexps.py:
	interwikilink should not match a bare url on the left hand side
2025-06-01  Simon Michael  
	* templates/zwikidotorg/editform.dtml: update list of page types.
	* ZWikiPage.py: clear the render cache when we upgrade page types
	* Defaults.py, SubscriberList.py, ZWikiPage.py, ZWikiWeb.py:
	grand page types rename; auto-upgrading & backwards compatibility; allow
	auto-upgrading to be disabled via flag in Defaults.py; tweak allowed
	suffixes for fs-based wiki templates
	* SubscriberList.py, ZWikiPage.py:
	cleanups and fixed a bug in upgradeSubscribers which caused folder
	_properties to proliferate
2025-05-31  Simon Michael  
	* tests/support.py: make self.aq_base work in non-acquired mock pages
	* ZWikiPage.py:
	render methods cleanup, fixed a dtml caching bug, fixed a typo in upgrade
	* ChangeLog: latest
	* ZWikiPage.py:
	update doLegacyFixups & rename to upgrade; convert creation_time and
	last_edit_time to ISO-format strings; make lastEditTime & creationTime
	robust
	* SubscriberList.py: temporary separate fixup method for IssueNo0161
	* SubscriberList.py: never mind that last attempt.. time to stop
	* SubscriberList.py:
	this hack seems to fix pages involved in IssueNo0161
	* ZWikiPage.py:
	add creationTime & lastEditTime methods for generating DateTimes.
	Hopefully this is the approach CMF etc. follows.
	* templates/zwikidotorg/standard_wiki_header.dtml:
	last_edit_time is now a string
	* ZWikiPage.py:
	use ISO strings for both creation_time and last_edit_time.  Don't bother
	with updating old instances, they should be ok as-is.
	* Makefile: add rule for making .po files
	* ZWikiPage.py: i18n corrections - include %s in strings
	* locale/en.po, locale/es.po, locale/fr.po, locale/ga.po, locale/locale.pot:
	initial .po files, generated by localizer's zgettext.py.
2025-05-30  Simon Michael  
	* ZWikiPage.py:
	make links() always valid, small _preLink bugfix, cleanups
	* ZWikiPage.py: do remote wiki links when pre-linking
	* ZWikiPage.py:
	structuredtextdtml was calling _preLink(), no need for that yet
	* templates/zwikidotorg/standard_wiki_footer.dtml:
	quote bookmarks properly in full/simple/minimal links (IssueNo0180)
	* ZWikiPage.py: make pre-linking hide ! properly
	* Regexps.py:
	added (?L) to the regexps which use \w or \b to make them locale-sensitive.
	(EdwardKreis, AlexyKhrabrov, InternationalCharsInRegexps)
	* editform.dtml: upload permission was incorrect
	* ZWikiPage.py: fixed some typos
2025-05-29  Simon Michael  
	* ZWikiPage.py:
	the beginnings of i18n. Requires Localizer, has no effect if it's not
	available.
	* SubscriberList.py: missing import
2025-05-28  Simon Michael  
	* Makefile: cleanups
	* Defaults.py, Permissions.py, Regulations.py, Utils.py, ZWikiPage.py, templates/zwikidotorg/editform.dtml:
	support WikiForNow-style regulations if use_regulations folder property is
	true.
	* tests/support.py, tests/testZWikiPage.py:
	improve python1.5 support, make test pass with zope2.3 again
2002-05-25  Simon Michael  
	* ZWikiPage.py:  - fix some python2-isms
	 - make wikiLinks() generate the link cache if needed
	* Permissions.py: comment cleanups
2025-05-24  Simon Michael  
	* ChangeLog: latest changes
	* ZWikiPage.py:
	pre-linking support. Allows us to pre-process links at edit time for
	faster rendering. Also will allow us to accurately catalog the page's
	links.
	* Regexps.py, Utils.py:  - regexp tweaks for preserving dtml from stx
	 - allow stx tables to have + at the corners
2025-05-23  Simon Michael  
	* templates/zwikidotorg/UserOptions.stxdtml:
	rename "Show advanced edit form" to "Show advanced edit options"
	to help with endless confusion..
2025-05-22  Simon Michael  
	* templates/zwikidotorg/standard_wiki_footer.dtml:
	fix white space in center of footer in NS 4.7
	* ZWikiPage.py: the real fix
	* Extensions/mailin.py: docstring
	* SubscriberList.py: comments
	* ZWikiPage.py:  - fix a recent breakage of page creation
	 - work in progress to support wiki link pre-calculation
	* ChangeLog: latest
	* templates/zwikidotorg/standard_wiki_footer.dtml:
	fix a regression to ensure comment form textarea depends solely on Add
	comments permission
	* templates/zwikidotorg/editform.dtml: update page type descriptions
	* templates/zwikidotorg/editform.dtml, templates/zwikidotorg/standard_wiki_footer.dtml:
	I inadvertently reverted to the old permissions on 2025/04/30 - fix
2025-05-21  Simon Michael  
	* templates/zwikidotorg/standard_wiki_header.dtml:
	remove my debugging tags
	* templates/zwikidotorg/standard_wiki_footer.dtml, templates/zwikidotorg/standard_wiki_header.dtml:
	latest from zwiki.org - use last_edit_time in preference to
	bobobase_modification_time, and make minimal mode more minimal
	* templates/zwikidotorg/standard_wiki_header.dtml: no, like this
	* templates/zwikidotorg/standard_wiki_header.dtml:
	don't show "sp" in text-mode browsers
	* Extensions/mailin.py:
	we can use ZwikiTracker to add issues now rather than AddIssue
	* tests/testZWikiPage.py, SubscriberList.py, Utils.py, ZWikiPage.py:
	look up wiki subscribers only by containment, not context, & make tests pass
	* tests/testZWikiPage.py: started tests for the page rendering modes
	* Utils.py: disable stx literal experiments for now
	* ZWikiPage.py, tests/support.py: cleanups, make tests pass
	* SubscriberList.py, ZWikiPage.py, tests/support.py, tests/testMailin.py, tests/testSubscriberList.py, tests/testZWikiPage.py, tests/testZWikiWeb.py:
	renamed MockZWikiPageInFolder
2025-05-20  Simon Michael  
	* ZWikiPage.py: comment, make last_edit_time's default None
	* Regexps.py: cleanup/comment
2025-05-14  Simon Michael  
	* ZWikiPage.py:
	always give last_edit_time a value, either at creation or during legacy
	fixup
	* ZWikiPage.py:
	render caching bugfixes; index object after ftp/http/webdav put
	* CatalogAwareness.py: more verbose catalog logging
	* TrackerSupport.py: update render_issuedtml
	* ZWikiPage.py:
	support prerendering + dtml without modifying the object on each view;
	and make sure to do the prerendering at write time
	* Utils.py, ZWikiPage.py:
	make ftp/http/webdav PUT use _setText & update render cache etc; cleanups
	* Regexps.py: remove STletters dependency for older zopes
	* ZWikiPage.py, tests/support.py:
	more cleanups, simplify sub-wiki parent checking, make page creation links
	obey the relative_url property
	* Regexps.py: syntax error
	* Parents.py, Regexps.py, TrackerSupport.py, ZWikiPage.py, __init__.py, wwml.py:
	 - a bunch of cleanups & private method renames
	 - enabled experimental ``...`` stx escaping
2025-05-13  Simon Michael  
	* ZWikiPage.py: another sgml regexp tweak
	* ZWikiPage.py: more regexp/comment tweaks
	* ZWikiPage.py: cleanup
	* ZWikiPage.py: make my stx sgml matching regexp more precise
	* ZWikiPage.py:
	lightly tested cleanup of structured text handling. Includes an apparent
	fix for footnotes in stx classic. _structured_text() is now the public
	method stxToHtml().
	* ZWikiPage.py: forgot the *
	* ChangeLog: updates
	* ZWikiPage.py:  - make my stx document class do normal-size headings
	 - tune my sgml regexp to avoid exponential time
	 - cleanups
2025-05-12  Simon Michael  
	* CatalogAwareness.py, TrackerSupport.py, Utils.py, ZWikiPage.py:
	- cleanups
	- set up debug logging for catalog operations
	- support pre-rendering
2025-05-11  Simon Michael  
	* ZWikiPage.py: cleanups, abandon the old antidecap kludge
	* TrackerSupport.py:
	when rendering the issue form, generate option menus dynamically from the
	issue_* properties (MikeFair)
	* ZWikiPage.py:
	don't pass stx header argument with zope 2.4.0 (IssueNo0152, TrevorToenjes)
	* templates/zwikidotorg/standard_wiki_footer.dtml:
	in page rename form, leave "update backlinks" unchecked by default
	change the from-day-1 "edit this page" link to "edit"
	* ChangeLog:
	I would like to have this in the release and nightly tarballs.. hopefully
	there won't be problems
	* templates/zwikidotorg/Makefile, templates/zwikidotorg/TAGS:
	these shouldn't be in cvs
	* ZWikiPage.py:
	never mind, we already have document_src from DTML Document
	* ZWikiPage.py:
	support for wiki areas/nested wikis/simpler wiki acquisition -
	wikiname targets which have been acquired from the parent folder (or
	above) are linked with ../
	addendum to last comment: re-enabled relative urls if relative_urls
	property set, for testing
	* SubscriberList.py:
	to avoid confusion, don't acquire subscribers from parent folders
	* Parents.py: quick "you are here" hack
	* LICENSE.txt: clearer wording
2025-05-08  Simon Michael  
	* templates/zwikidotorg/editform.dtml:
	this missing slash caused edits to fail in netscape 4.7
2025-05-05  Simon Michael  
	* ZWikiPage.py: improve zope version checking
	implement document_src accessor
	add creationTimeAsDateTime hack
2025-05-04  Simon Michael  
	* Extensions/mailin.py:
	..also, post the correct page creation comment..
	* Extensions/mailin.py:
	also post page creation comments to the right place
	* Extensions/mailin.py: fix page creation
2025-05-01  Simon Michael  
	* LICENSE.txt, README.txt, ZWikiPage.py: license text cleanups
2025-04-30  Simon Michael  
	* templates/zwikidotorg/backlinks.dtml:
	include the page name in the heading
	* templates/zwikidotorg/backlinks.dtml, templates/zwikidotorg/editform.dtml:
	clean up & streamline the backlinks and editform. Use a simple dialog-ish
	header like the subscribeform, to differentiate from real page content.
	* ZWikiPage.py: fix a typo
	* templates/zwikidotorg/standard_wiki_footer.dtml:
	- display a convenient page rename/reparent/delete form in the footer for
	  managers who have advanced edit options turned on
	- always post comments with headings if advanced edit options are not turned on
	* Parents.py, ZWikiPage.py: add support for page renaming/deleting
	* Extensions/mailin.py:
	make issue creation more robust, note use of SiteAccess code
	* Permissions.py: note possible next permissions rename
	* Makefile: cleanup
2025-04-28  Simon Michael  
	* Defaults.py, templates/zwikidotorg/editform.dtml:
	make textarea width more robust when zwiki_width='' for whatever reason
	* templates/zwikidotorg/standard_wiki_footer.dtml:
	use same default textarea width as editform (60)
	* templates/zwikidotorg/standard_wiki_footer.dtml:
	make textarea width more robust when zwiki_width='' for whatever reason
2025-04-16  Simon Michael  
	* CHANGES.txt:
	update CHANGES to reflect important last minute fix that I am
	slipstreaming in and re-tagging.  Announcement hasn't gone out yet.
	* Makefile: CHECK THE TRACKER
	* Defaults.py, templates/zwikidotorg/editform.dtml, templates/zwikidotorg/standard_wiki_footer.dtml:
	how oh how did I miss these
	* CHANGES.txt, Makefile, __init__.py: 0.9.9 final
	* CHANGES.txt, Makefile, __init__.py: rc6
	* TrackerSupport.py, ZWikiPage.py: cleanup
	* Regexps.py: recognize , in urls (IssueNo0130)
	* templates/zwikidotorg/standard_wiki_footer.dtml:
	remove the comment form border
2025-04-14  Simon Michael  
	* Makefile: cleanup
	* Makefile, __init__.py: bump version no
	* CHANGES.txt: latest for rc5
	* ZWikiPage.py: add a test for bracketed path linking,
	add an import for more robust importing while debugging
	* Regexps.py: add / to bracketedexpr again so paths will be linked
	* SubscriberList.py, tests/testSubscriberList.py, tests/testZWikiWeb.py:
	missed some old imports
2025-04-13  Simon Michael  
	* CHANGES.txt: latest
	* templates/zwikidotorg/UserOptions.stxdtml:
	don't use PST as default timezone
	* templates/zwikidotorg/UserOptions.stxdtml:
	make default editform dimensions match built-in default
	* CHANGES.txt: include latest
	* templates/zwikidotorg/standard_wiki_header.dtml:
	make the timezone conversion more robust
	* Extensions/mailin.py, tests/__init__.py, tests/support.py, tests/testDoctests.py, tests/testMailin.py, tests/testSubscriberList.py, tests/testZWikiPage.py, tests/testZWikiWeb.py, CHANGES.txt, Makefile, ZWikiPage.py:
	remove a local PYTHONPATH dependency from all imports, and make unit tests
	more robust across zope versions & invocation methods
2025-04-12  Simon Michael  
	* CHANGES.txt: fix date
	* CHANGES.txt: latest for rc4
	* ZWikiPage.py:
	go back to using To: replytoaddress, overridable with mail_to property,
	for the moment.
	* Extensions/mailin.py: multipart message handling got broken, fixed
2025-04-11  Simon Michael  
	* Makefile, __init__.py: rc4
	* ZWikiPage.py: fix import path for __version__
	make inclusion of poster's username in mailout From header more robust
	use ; for To instead of sending a copy to the mailin address
2025-04-10  Simon Michael  
	* Makefile: thought I checked this in already
	* CHANGES.txt, __init__.py: rc3
	* templates/zwikidotorg/standard_wiki_header.dtml:
	made it easier to hit
	* templates/zwikidotorg/standard_wiki_header.dtml:
	removed obsolete zwiki_homepage reference and SiteLogo ability,
	added secret feature for minimalists.
2025-04-09  Simon Michael  
	* CHANGES.txt: more rc2 changes
	* templates/zwikidotorg/standard_wiki_footer.dtml:
	latest changes from zwiki.org - make help link depend on a HelpPage,
	update default bookmarks, include the new user option preset links
	* Makefile: rc2
	* CHANGES.txt: rc2 changes
	* __init__.py: bump version.. should be ok to use version suffixes here
	* ZWikiPage.py: make sendMailTo use zwiki_version
	* TrackerSupport.py: render header after body like other page_types;
	leave issue table at it's natural width so it stands out from page header
	* Defaults.py:
	built-in ui tweaks - use colours like zwikidotorg's, make editform
	textarea smaller, remove non-useful help links
	* ZWikiPage.py:
	 - render page header *after* body so that body can turn it off with dtml
	 - add zwiki_version method
	 - make create more robust for dtml use in a top-level folder
2025-04-08  Simon Michael  
	* Makefile: final release-building recipe
	* Makefile: *** empty log message ***
	* Makefile: new release-building procedure
	* tests/testMailin.py: cleanup
	* Extensions/mailin.py: missing bracket
	* templates/zwikidotorg/editform.dtml:
	don't offer to change the type of tracker issue pages
	* CHANGES.txt: note latest changes
	* templates/zwikidotorg/editform.dtml, templates/zwikidotorg/standard_wiki_footer.dtml, templates/zwikidotorg/standard_wiki_header.dtml:
	copy zwiki.org's latest gray look (aka "mr. efficiency")
	for site logo, use either the folder's 'site_logo' property
	or member object, the contents of the SiteLogo page, or
	the default zwiki icon.
	* tests/testDoctests.py, tests/testMailin.py, tests/testSubscriberList.py, tests/testZWikiPage.py:
	misc updates to test framework
	* Makefile: misc updates
	* SubscriberList.py: the real wiki -> page subscribers bugfix
	* Extensions/mailin.py: An overhaul of the mailin script.
	- cleanup, more oo, clarify rules
	- use only the first plaintext part from multipart MIME messages
	- support folder property 'posting_policy' (may be 'open')
	- support folder property 'default_page'
	- use virtual host monster to help direct messages if present
	  (may have some imeme-isms ?)
	- some other tweaks to delivery rules intended to simplify mailin alias
	  setup.
	* ZWikiPage.py: don't inherit page type from tracker issue pages
	* SubscriberList.py:
	well that's not super-elegant but should resolve subscriber_list upgrading problems
	* CHANGES.txt, Permissions.py: final permissions rename, hopefully
	* templates/zwikidotorg/standard_wiki_header.dtml:
	tweak html page titles
	* dtml/zwikiWebAdd.dtml:
	don't list *_config configuration forms, if present
	* __init__.py: make the manage hooks a little more robust
	* ZWikiWeb.py:
	make this a little more robust and support TEMPLATE_config configuration forms
2025-04-02  Simon Michael  
	* SubscriberList.py: cleanups
2025-03-19  Simon Michael  
	* CHANGES.txt: note mailout_replyto
	* CHANGES.txt, Regexps.py, ZWikiPage.py:
	- zwiki now coexists with structured text footnote references (yay!). []
	will link to a matching footnote if there is one, otherwise it is treated
	as a wiki link (IssueNo0110)
	* Parents.py:
	tweak parenting to work better with acquisition/subfolders
	(IssueNo0108) (robert@redcor)
	* CHANGES.txt, ZWikiPage.py:
	mailout now requires either a 'mail_from' or 'mail_replyto'
	folder property. If mail_from is present, always put that in the
	From: field. Otherwise, show the poster's email address or user
	name. (closes IssueNo0122)
	* CHANGES.txt, ZWikiPage.py:
	display the enclosing [] prior to page creation, to delineate the phrase
	* CHANGES.txt, Regexps.py, ZWikiPage.py:
	 - don't wiki-link [] links containing characters which zope does
	   not allow in object ids (IssueNo0090)
	 - don't wiki-link [] links containing all digits, but do link
	   those which only begin with a digit
	* CHANGES.txt: note latest changes
	* ZWikiPage.py, tests/testZWikiPage.py:
	fix the broken redirect & cleanup in _handleDeleteMe, & renamed _delete to
	_recycle
	* tests/support.py: fix our mock page's url
	* templates/zwikidotorg/editform.dtml:
	list "tracker issue" among the page types when editing,
	if a tracker's IssuePrototype is detected
	* templates/zwikidotorg/UserOptions.stxdtml:
	remove the "preferred front page" option (IssueNo0120)
	* ZWikiPage.py:
	adding an empty comment was giving an error (IssueNo0123)
	* templates/zwikidotorg/standard_wiki_header.dtml:
	show quote and search field by default
2025-03-18  Simon Michael  
	* SubscriberList.py:
	make subscriberlist more robust, make tests pass, docstring updates
	* templates/zwikidotorg/backlinks.dtml:
	removed the "set" links for now. They are handy, but removing them is
	a simpler way to protect against robots than requiring authentication.
	* CHANGES.txt: note new tracker methods
	* CHANGES.txt: missing newline
	* CHANGES.txt: updated, added api changes section
	* SubscriberList.py:
	allSubscribers now culls duplicates due to both page and wiki subscription
	(fixes IssueNo0055)
	* ZWikiPage.py: sendMailTo now takes a list of recipients, not a string
	* Permissions.py: cleanup
	* Defaults.py, Regexps.py, SubscriberList.py, TrackerSupport.py, ZWikiPage.py:
	use a 'subscribers_list' list property instead of the 'subscribers'
	property, and convert old instances on the fly. This needs more testing.
	Also update the folder's _properties so folder subscribers appear in the
	ZMI.
2025-03-17  Simon Michael  
	* CHANGES.txt, templates/zwikidotorg/editform.dtml, templates/zwikidotorg/standard_wiki_footer.dtml, templates/zwikidotorg/standard_wiki_header.dtml:
	editing permissions ui tweaks. Help/subscribe links now always visible,
	edit/append links conditionally visible, colored border around append &
	edit text areas, header & footer separators always gray.
	* ZWikiPage.py: require edit permission to view a page's editform
	* TrackerSupport.py: make *_index methods robust during issue creation
	* Regexps.py: comment update
2025-03-16  Simon Michael  
	* CHANGES.txt, TrackerSupport.py, Utils.py, ZWikiPage.py:
	 - factor tracker support into a mixin & add support methods
	   to help catalog sorting
	 - factor out some utility functions
	* ZWikiWeb.py, templates/DEFAULTS/standard_wiki_page, templates/zwikidotorg/AnnoyingQuote, templates/zwikidotorg/AnnoyingQuote.stx, templates/zwikidotorg/FrontPage, templates/zwikidotorg/FrontPage.stx, templates/zwikidotorg/HelpPage, templates/zwikidotorg/HelpPage.stx, templates/zwikidotorg/JumpSearch, templates/zwikidotorg/JumpSearch.htmldtml, templates/zwikidotorg/RecentChanges, templates/zwikidotorg/RecentChanges.htmldtml, templates/zwikidotorg/RemoteWikiLinks, templates/zwikidotorg/RemoteWikiLinks.stx, templates/zwikidotorg/RemoteWikiURL, templates/zwikidotorg/RemoteWikiURL.stx, templates/zwikidotorg/SearchPage, templates/zwikidotorg/SearchPage.htmldtml, templates/zwikidotorg/StructuredTextRules, templates/zwikidotorg/StructuredTextRules.stx, templates/zwikidotorg/TextFormattingRules, templates/zwikidotorg/TextFormattingRules.stx, templates/zwikidotorg/UserOptions, templates/zwikidotorg/UserOptions.stxdtml, templates/zwikidotorg/WikiName, templates/zwikidotorg/WikiName.stx, templates/zwikidotorg/ZWiki, templates/zwikidotorg/ZWiki.stx, templates/zwikidotorg/backlinks, templates/zwikidotorg/backlinks.dtml, templates/zwikidotorg/editform, templates/zwikidotorg/editform.dtml, templates/zwikidotorg/index_html, templates/zwikidotorg/index_html.dtml, templates/zwikidotorg/standard_wiki_footer, templates/zwikidotorg/standard_wiki_footer.dtml, templates/zwikidotorg/standard_wiki_header, templates/zwikidotorg/standard_wiki_header.dtml, templates/zwikidotorg/standard_wiki_page:
	 - use file suffixes to determine type of filesystem wiki content
	 - remove unused DEFAULTS directory
	* Makefile: miscellaneous tweaks
	* CHANGES.txt: add latest user-visible changes
	* Diff.py, ZWikiPage.py, tests/testZWikiPage.py:
	 - more diff & mailout formatting fixes
	 - test updates
	 - use latest colour scheme in render_issuedtml
	* tests/testZWikiWeb.py: updates, cleanups
	* tests/support.py:
	add SubscriberList to the mock zwiki page to support tests
	* dtml/zwikiWebAdd.dtml: update permission names
	* ZWikiWeb.py: import fix
	* SubscriberList.py:
	add a support method for listing other page subscriptions
	* README.txt: fix discussion url
	* Defaults.py:  - use folder title instead of "titleprefix"
	 - more subscription form updates
2025-03-12  Simon Michael  
	* Defaults.py: more usability tweaks
	* Defaults.py: aesthetic updates
	* Defaults.py: tweak subscription wording
	* CatalogAwareness.py, ZWikiPage.py, ZWikiWeb.py, tests/support.py, tests/testDoctests.py, tests/testZWikiPage.py:
	unit test updates
	* tests/support.py, tests/testMailin.py, tests/testZWikiPage.py, tests/testZWikiWeb.py:
	added some tests for the mailin method, added ZopeTime to mock zwikipage,
	cleanups
	* Extensions/mailin.py: cleanups
	* ZWikiPage.py:
	fiddled with the zope version checking, fixed doctests that were
	incompatible with zope 2.3
2025-03-11  Simon Michael  
	* Extensions/mailin.py, CHANGES.txt, Diff.py, ZWikiPage.py, tests/testZWikiPage.py:
	mailin/mailout: don't add stray blank lines
	* CHANGES.txt: the margin was 78 actually
	just leave it
	* CHANGES.txt, Extensions/mailin.py:
	IssueNo0070: html-format quoted replies in mailed-in tracker
	issues, as is done with mailed-in comments
	* CHANGES.txt, ZWikiPage.py, tests/testZWikiPage.py:
	increase mailout wrap margin in from 70 to 77
	* CHANGES.txt: add IssueNo0087
	* Diff.py, ZWikiPage.py, tests/testZWikiPage.py:
	 - enable comment doctests
	 - unit test and fixes for comment/diff/mailout formatting issues
	   (IssueNo0087)
	 - line wrapping is now done in formatMailout instead of textDiff
	* CHANGES.txt: tweaks
2025-03-10  Simon Michael  
	* CHANGES.txt: 0012 -> 0112
	* tests/testZWikiPage.py: It's IssueNo0112, dammit
	* CHANGES.txt: cleanups
	* CHANGES.txt, Changelog:
	renamed Changelog to CHANGES.txt and made it similar to Zope's.  There is
	now also a ChangeLog file generated from cvs checkins (not kept in cvs).
	* Permissions.py:
	Changed 'Recycle (delete) wiki pages' to just 'Recycle wiki pages'
	* Changelog: note latest changes, list old permissions alongside new
	* tests/testDoctests.py: cleanups, support python 1.5
	* Makefile: add a rule to run unit tests against zope 2.3
	* ZWikiPage.py:  fix for IssueNo0012 from natesain
	 noted a text doctest failure with zope 2.3
	* ZWikiPage.py: make wiki_url more robust when testing
	* templates/zwikidotorg/editform, templates/zwikidotorg/standard_wiki_footer, templates/zwikidotorg/standard_wiki_header:
	update permission names
	* tests/testZWikiPage.py: added a test for IssueNo0012
	* tests/support.py: don't need to import os any more
	* SubscriberList.py, ZWikiPage.py, tests/__init__.py, tests/support.py, tests/testDoctests.py:
	 - test framework updates, cleanups
	 - added some useful mock objects to support most test operations without
	   requiring a zope app
	 - made _checkPermission a method to allow overriding
	 - some cleanup of the squirrely zwiki_username_or_ip
	 - updated SubscriberList's doctests and some of ZWikiPage's
	* tests/doctest.py, tests/doctest_zope.py:
	renamed doctest.py to doctest_zope.py to avoid confusion
	when using testrunner
	* Makefile: be less chatty when testing
2025-03-09  Simon Michael  
	* wwml.py: cleanup dummy doctests
	* SubscriberList.py: update isSubscriber test
	* ZWikiPage.py: redo _set_last_editor doctest
	* tests/testZWikiWeb.py, tests/testZWikiPage.py: cleanup
	* tests/__init__.py: docstring
	* tests/__init__.py: reword
	* tests/testZWikiWeb.py: consistency fix
	* Makefile, tests/refresh.txt, tests/testDoctests.py, tests/testZWikiWeb.py:
	more test cleanups.. they can now be run either with testrunner.py
	or just "python testZWikiPage.py"
	* Makefile, ZWikiWeb.py, tests/__init__.py, tests/testDoctests.py, tests/testZWikiPage.py, tests/testZWikiWeb.py:
	update the tests to the One True Way (python utilities/testrunner.py -d Products/ZWiki)
	fix a few import issues
	make the mailout formatting test pass
	* Changelog, ZWikiPage.py: redirectURL hack
	* SubscriberList.py: a doctest fix
	* tests/__init__.py, tests/simpletestrunner.py, tests/testDoctests.py, tests/testZWikiPage.py, tests/testZWikiWeb.py:
	rip out that old simpletestrunner stuff
2025-03-01  Simon Michael  
	* ZWikiPage.py:
	A 'redirectURL' parameter in the REQUEST will control where the user ends up
	after edit, append or comment.
	* Extensions/mailin.py:
	mailin: calling mailin in the context of a page should
	always use that page for posting, now it does
2025-02-28  Simon Michael  
	* ZWikiPage.py: typo
2025-02-25  Simon Michael  
	* Changelog, Defaults.py, Permissions.py, __init__.py:
	rename permissions, cleanups
	* ZWikiWeb.py: look in "templates/" instead of "wikis/"
	* templates/DEFAULTS/standard_wiki_page, templates/zwikidotorg/AnnoyingQuote, templates/zwikidotorg/FrontPage, templates/zwikidotorg/HelpPage, templates/zwikidotorg/JumpSearch, templates/zwikidotorg/Makefile, templates/zwikidotorg/RecentChanges, templates/zwikidotorg/RemoteWikiLinks, templates/zwikidotorg/RemoteWikiURL, templates/zwikidotorg/SearchPage, templates/zwikidotorg/StructuredTextRules, templates/zwikidotorg/TAGS, templates/zwikidotorg/TextFormattingRules, templates/zwikidotorg/UserOptions, templates/zwikidotorg/WikiName, templates/zwikidotorg/ZWiki, templates/zwikidotorg/backlinks, templates/zwikidotorg/editform, templates/zwikidotorg/index_html, templates/zwikidotorg/standard_wiki_footer, templates/zwikidotorg/standard_wiki_header, templates/zwikidotorg/standard_wiki_page, wikis/DEFAULTS/standard_wiki_page, wikis/zwikidotorg/AnnoyingQuote, wikis/zwikidotorg/FrontPage, wikis/zwikidotorg/HelpPage, wikis/zwikidotorg/JumpSearch, wikis/zwikidotorg/RecentChanges, wikis/zwikidotorg/RemoteWikiLinks, wikis/zwikidotorg/RemoteWikiURL, wikis/zwikidotorg/SearchPage, wikis/zwikidotorg/StructuredTextRules, wikis/zwikidotorg/TextFormattingRules, wikis/zwikidotorg/UserOptions, wikis/zwikidotorg/WikiName, wikis/zwikidotorg/ZWiki, wikis/zwikidotorg/backlinks, wikis/zwikidotorg/editform, wikis/zwikidotorg/index_html, wikis/zwikidotorg/standard_wiki_footer, wikis/zwikidotorg/standard_wiki_header, wikis/zwikidotorg/standard_wiki_page:
	rename wikis/ -> templates/
	* ZWikiPage.py: nothing much
	* Changelog, Extensions/mailin.py, SubscriberList.py: tweaks
	* Extensions/mailin.py:
	reversing the failed reversal of the zwiki_mailin.py -> mailin.py rename
	added: with subscribersonly flag, bounce mail from non-subscribers
	* Extensions/mailin.py:
	reversing the zwiki_mailin.py -> mailin.py rename
2025-02-22  Simon Michael  
	* Regexps.py, Changelog:
	make wikiname regular expressions a bit more international by using
	string.upper/lowercase
	* Changelog, Regexps.py, ZWikiPage.py:
	don't quote international characters any more (IssueNo0004)
	* Makefile: bump version
	* CatalogAwareness.py, tests/testDoctests.py, tests/testZWikiPage.py:
	disabled some troublesome tests
	* Extensions/mailin.py, Extensions/zwiki_mailin.py:
	renamed zwiki_mailin.py mailin.py
	* ZWikiPage.py: save a code snippet for upload size
	* __init__.py: bump version
	* Extensions/zwiki_mailin.py: cleanups
	mail loop protection
	tracker issue creation
	* Defaults.py: display only partial email addresses
	* Changelog: various updates
	stuck "ZWiki" back in to keep formatting sane on zwiki.org
2025-02-20  Simon Michael  
	* wikis/zwikidotorg/standard_wiki_header:
	Display subscriber counts in the header
	* wikis/zwikidotorg/standard_wiki_footer:
	italicize "Bookmarks:" for consistency
	* ZWikiPage.py:
	local paths within [] are now displayed completely instead of just the
	last component.
2025-02-19  Simon Michael  
	* wikis/zwikidotorg/editform: made default edit text area smaller
	* ZWikiPage.py:
	when using "comments only" mailout policy, don't display
	the "++added:" diff heading.
	* Diff.py:
	textDiff can now take verbose=0 to turn off diff chunk headings
	* ZWikiPage.py: fix: mailout plain non-heading comments too
	* Changelog: cleanup
	* Changelog, ZWikiPage.py:
	only comments (appended via the "comment" method) are now mailed out by
	default, or if there is a "mailout_policy" folder property with the value
	"comments". If mailout_policy is "edits", all edits are mailed out.
2025-02-15  Simon Michael  
	* ZWikiPage.py: - use bcc for mailout delivery
	- add x-beenthere and x-zwiki-version headers
	* wikis/zwikidotorg/standard_wiki_footer:
	enable comment headings by default for pages named IssueNo* also
2025-02-13  Simon Michael  
	* ZWikiPage.py:
	added some convenience code to disable mailout for general edits
2025-02-07  Simon Michael  
	* wikis/zwikidotorg/standard_wiki_footer:
	sync with current zwiki.org (heading checkbox on the right, less bgcolor)
	make headings on by default for 'IssueNo*' as well as discussion pages.
	* tests/testZWikiPage.py, wikis/zwikidotorg/standard_wiki_footer, Changelog, Makefile, ZWikiPage.py, __init__.py, version.txt:
	catching up
	* render_issuedtml tweaks
	* zwikidotorg template: fix scrolling to bottom of page after comment
	* added creator, creator_ip, creation_time page properties
	some test tweaks..
	bumped version no..
2025-11-30  Simon Michael  
	* wikis/DEFAULTS/standard_wiki_page:
	don't know if we'll use this.. stick this in cvs for now to match code
	* Makefile: *** empty log message ***
	* CatalogAwareness.py: cleanup
	* CatalogAwareness.py: *** empty log message ***
	* Makefile: add CatalogAwareness.py, update version
	* CatalogAwareness.py, Changelog, README.txt, ZWikiPage.py, ZWikiWeb.py, __init__.py, tests/testDoctests.py, tests/testZWikiPage.py, version.txt, wikis/zwikidotorg/UserOptions:
		* auto-cataloging no longer requires DTMLDocumentExt and adding a
		zwiki page no longer gives "AttributeError: index_object" if it's
		not present. (IssueNo0054). Zwiki uses the catalog specified by
		the SITE_CATALOG property, or "Catalog", or none.
		* the add zwiki web form complained "list.remove(x): x not in list"
		due to missing ZWiki/wikis/DEFAULTS, fixed  (IssueNo0043)
		* a new page type and render method (issuedtml, render_issuedtml)
		to support ZwikiTracker (experimental)
		* zwikidotorg template: UserOptions was not displaying the
		username field properly
2025-11-26  Simon Michael  
	* ZWikiPage.py: update last_editor even if we have no REQUEST
	a somewhat important change that got checked in earlier: move the
	responsibility for header and footer into the render_ methods.
	* Makefile: fix adding the download link
	* ZWikiPage.py: got it this time
	* Changelog, ZWikiPage.py: finish the previous fix
	* Diff.py, ZWikiPage.py, Changelog:
	don't commit to get diff for mail-out, since it generates an extra revision
	* wikis/zwikidotorg/standard_wiki_footer: removed BookMarks link
	* wikis/zwikidotorg/UserOptions: page_type = structuredtextdtml
	* Changelog, Makefile, README.txt, ZWikiPage.py:
		* included TextFormatter.py in tarball
		* updated README, removed mention of zwikiwebs.zexp
		* added download link to announcements
2025-11-24  Simon Michael  
	* Makefile:
	aha found the bug preventing my news announcements from being cataloged
	* Changelog, Makefile: ack! curses!
	* Makefile, tests/testZWikiPage.py: final tweaks
	* Makefile: updated for 0.9.7
	* wikis/zwikidotorg/HelpPage, wikis/zwikidotorg/HierarchalStructure, wikis/zwikidotorg/TextFormattingRules, wikis/zwikidotorg/WikiWikiWeb, wikis/zwikidotorg/ZWiki, wikis/zwikidotorg/ZopeDotOrg, wikis/zwikidotorg/append_with_heading, wikis/zwikidotorg/standard_wiki_footer, wikis/zwikidotorg/standard_wiki_header:
	sync with zwiki.org, remove cruft
	* ZWikiPage.py, Changelog:
	new comment method to replace append_with_heading
	* Extensions/zwiki_mailin.py:
	external helper method for WikiMail mail-in
	* Defaults.py, Permissions.py: move permission prefix to Defaults
	* Changelog:
	readability tweaks; better release before this gets any longer
	* Changelog: tweaks, added some credits
	* ZWikiPage.py: typo, python 1.5 compatibility fix
	* Changelog: readability tweaks
	* wikis/zwikidotorg/BookMarks, wikis/zwikidotorg/TimeZone, wikis/zwikidotorg/UserName, wikis/zwikidotorg/RecentChanges, wikis/zwikidotorg/UserOptions, wikis/zwikidotorg/standard_wiki_footer:
	removed BookMarks, UserName, TimeZone
	updated UserOptions
	* import/basic-0.9.5.zexp, import/classic-0.9.5.zexp, import/zwikidotorg-0.9.6.zexp:
	zwikidotorg is now maintained on the filesystem, archiving the others
	in misc
2025-11-23  Simon Michael  
	* Changelog: latest changes
	* run_all_tests: *** empty log message ***
	* Defaults.py, SubscriberList.py, ZWikiPage.py:
	cleanups and compatibility updates for zope 2.4 & stxNG
	* tests/testDoctests.py, tests/testZWikiPage.py, tests/testZWikiWeb.py:
	misc test tweaks & updates
	* wikis/zwikidotorg/AnnoyingQuote, wikis/zwikidotorg/BookMarks, wikis/zwikidotorg/FrontPage, wikis/zwikidotorg/HelpPage, wikis/zwikidotorg/HierarchalStructure, wikis/zwikidotorg/JumpSearch, wikis/zwikidotorg/RecentChanges, wikis/zwikidotorg/RemoteWikiLinks, wikis/zwikidotorg/RemoteWikiURL, wikis/zwikidotorg/SearchPage, wikis/zwikidotorg/StructuredTextRules, wikis/zwikidotorg/TextFormattingRules, wikis/zwikidotorg/TimeZone, wikis/zwikidotorg/UserName, wikis/zwikidotorg/UserOptions, wikis/zwikidotorg/WikiName, wikis/zwikidotorg/WikiWikiWeb, wikis/zwikidotorg/ZWiki, wikis/zwikidotorg/ZopeDotOrg, wikis/zwikidotorg/append_with_heading, wikis/zwikidotorg/backlinks, wikis/zwikidotorg/editform, wikis/zwikidotorg/index_html, wikis/zwikidotorg/standard_wiki_footer, wikis/zwikidotorg/standard_wiki_header, wikis/zwikidotorg/standard_wiki_page:
	move zwikidotorg template to filesystem
	* __init__.py, dtml/zwikiWebAdd.dtml, ZWikiWeb.py:
	read wiki templates from filesystem as well as zodb
2025-11-22  Simon Michael  
	* tests/__init__.py:
	forgot to check this in as part of the test module rename
	* Makefile, Makefile.sample: can check in actual Makefile now
	* Changelog, Defaults.py, Diff.py, Regexps.py, TextFormatter.py, ZWikiPage.py:
	added the TextFormatter module used by the diff stuff
	automatic cataloging support; currently, assumes use of
	DTMLDocumentExt product.
	the previous "rename test modules" checkin included new tests
	for automatic cataloging
	updated all doctests to pass in zope 2.4 - they may fail now in 2.3,
	not sure.
	various muckings-about with dtml, structured text version, etc.
	the zwikidotorg template changes referred to in Changelog have yet
	to be checked in
	yesss - I have trained emacs/pcl-cvs/cvs/ssh/sourceforge to play
	nicely again
	* tests/test_ZWikiPage.py, tests/test_ZWikiWeb.py, tests/test_doctests.py, tests/testDoctests.py, tests/testZWikiPage.py, tests/testZWikiWeb.py:
	rename test modules
	* emacs/zwiki-mode.el:
	moved zwiki-mode.el into /misc & out of cvs for now
2025-11-18  Simon Michael  
	* Changelog, Diff.py, ZWikiPage.py:
		* textDiff now reports more localised changes, instead of spraying
		out the whole page
		* long lines are wrapped before diffing, and long diff chunks are
		abbreviated, for better readability in email. Currently it
		displays up to 10 deleted lines and up to 50 added lines.
		* lasttext and the diff methods accept one or more revision arguments
		(counted backwards from the latest revision).
		* the old html-format diff has been demoted to oldDiff. The new one
		colourizes textDiff's output and adds whizzy navigation links for
		stepping through the edits. (Click on the page timestamp).
		* misc diff & mail-out tweaks & fixes; mail-outs now discard
		the comment heading if present
	here's one re last checkin:
		* improved default subscribe form's layout; remember email address
		via cookie if possible
2025-11-17  Simon Michael  
	* Defaults.py, Diff.py, ZWikiPage.py, __init__.py, version.txt:
	small changes that are too much hassle to diff right now, sorry
2025-10-31  Simon Michael  
	* Parents.py, ZWikiPage.py: rename modules
	* Defaults.py: tweaked help text
2025-10-23  Simon Michael  
	* Defaults.py, Diff.py, Parents.py, Permissions.py, Regexps.py, ZWikiDefaults.py, ZWikiDiff.py, ZWikiParents.py, ZWikiPermissions.py, ZWikiRegexes.py:
	renamed five files
	* ZWikiPage.py:
	reverted to older _renderHeaderOrFooter code to fix "bare"
	keyword used by ChatMode
2025-10-22  Simon Michael  
	* Changelog: note last 0.9.6 changes
	* import/zwikidotorg-0.9.5.zexp, import/zwikidotorg-0.9.6.zexp:
	added subscribe form link to footer
	added zwiki.org's append_with_heading method, along with
	commented-out example footer code to call it
	a few other tweaks from zwiki.org - don't display quote & search form
	by default, etc.
	* ZWikiDefaults.py: update subscribeform text
	* ZWikiDiff.py, ZWikiPage.py: move mail notification into edit,
	add textDiff for simple plaintext diff,
	send notification for all text edits
2025-10-18  Simon Michael  
	* __init__.py, version.txt: bump version number
	* Changelog, ZWikiPage.py: fix editTimestamp capitalization
	* Changelog, ZWikiPage.py, tests/test_doctests.py: make tests pass
2025-10-17  Simon Michael  
	* Changelog, ZWikiPage.py: more refactoring - file uploading etc.
	renamed some timestamp methods; made the errorConflictDialog public
	just in case
	* Changelog, ZWikiPage.py:
	ahar.. big page editing & creation refactoring
	* tests/test_doctests.py, Changelog, SubscriberList.py, ZWikiDefaults.py, ZWikiPage.py:
	more mail subscription work
	edit cleanups, factored out _delete, _editConflictDialog
2025-10-16  Simon Michael  
	* ZWikiPage.py: factored out mail subscription stuff
	fixed a bunch of problems that "make test" failed to report
2025-10-15  Simon Michael  
	* ZWikiPage.py: factored out renderHeaderOrFooter
	* ZWikiPage.py, tests/test_ZWikiPage.py, tests/test_ZWikiWeb.py:
	make tests pass
	disabled some broken tests
	factored out doLegacyFixups
	* Changelog, ZWikiDefaults.py, ZWikiDiff.py, ZWikiPage.py, ZWikiParents.py, ZWikiWeb.py, __init__.py:
	code & comment cleanups to help with refactoring
2025-10-13  Simon Michael  
	* Changelog, __init__.py: auto-import comment/doc updates
	* import/basic-0.9.5.zexp, import/basic.zexp, import/classic-0.9.5.zexp, import/classic.zexp, import/zwikidotorg-0.9.5.zexp, import/zwikidotorg.zexp:
	renamed zexps for 0.9.5 release. basic had a few updates too.
	* Changelog: 0.9.5 release
2001-10-06  Simon Michael  
	* tests/__init__.py, tests/test_ZWikiPage.py, tests/test_doctests.py:
	cleanups, catalog work in progress
	* Changelog, ZWikiPage.py, ZWikiPermissions.py, ZWikiWebs.zexp, __init__.py, dtml/zwikiWebAdd.dtml, import/basic.zexp, version.txt:
	closer zwikiwebs integration, misc. fixes, bump version no.
2025-08-08  Simon Michael  
	* tests/DocTests.py, tests/ZWikiPageTests.py, tests/ZWikiWebTests.py, tests/__init__.py, tests/test_ZWikiPage.py, tests/test_ZWikiWeb.py, tests/test_doctests.py:
	renamed test modules for consistency with something I saw.. CMF ?
	& to distinguish from other files.
	* ZWikiPage.py: remove an obsolete arg from _set_text
	_set_last_editor fix
2025-08-05  Simon Michael  
	* Changelog, ZWikiPage.py, __init__.py: zwiki_username_or_ip fix
	* Changelog, ZWikiWeb.py, tests/DocTests.py, tests/ZWikiPageTests.py, tests/ZWikiWebTests.py, tests/ZWikiWebsTests.py, tests/__init__.py:
	bring tests up to date
	* ZWikiAdd.py, ZWikiPage.py, ZWikiPage_icon.gif, ZWikiWeb.py, __init__.py, dtml/zwikiPageAdd.dtml, dtml/zwikiWebAdd.dtml, images/ZWikiPage_icon.gif, import/basic.zexp, import/classic.zexp, import/zwikidotorg.zexp, zwikipageAdd.dtml:
	refactored ZWikiPage/ZWikiAdd to ZWikiPage/ZWikiWeb
	added images & dtml subdirectories
	moved zwiki web creation into python product
	new zwikiwebs regime - individual zexps in /import
	form updates
	added autoimport feature to __init__
2025-08-04  Simon Michael  
	* Changelog: that's long enough
	* Changelog: bumped version no.
	* tests/ZWikiPageMgmtTests.py, tests/ZWikiPageTests.py, tests/__init__.py:
	merged ZWikiPageMgmtTests into ZWikiPageTests for now
	* tests/DocTests.py, tests/__init__.py, tests/simpletestrunner.py:
	import path tweaks
	* Changelog, ZWikiPage.py, tests/DocTests.py, tests/ZWikiPageMgmtTests.py, tests/ZWikiPageTests.py, tests/ZWikiWebsTests.py, tests/__init__.py, tests/doctest.py, tests/simpletestrunner.py, tests/test_all.py:
		* escaping remote wiki links with ! should now work
		* tests have been completely reorganized and updated
2025-08-03  Simon Michael  
	* ZWikiPage.py, ZWikiParents.py, doctest.py, tests/DocTests.py, tests/ZWikiPageMgmtTests.py, tests/ZWikiPageTests.py, tests/ZWikiWebsTests.py, tests/doctest.py, tests/test_ZWikiPage.py, tests/test_ZWikiWebs.py, tests/test_all.py:
	got CMF-style test framework working. ZWikiPage's run_tests
	method is a hack to make running the tests easier. Doctests
	are working but disabled for now, as are the old zwikiwebs
	tests, because the context they receive is not that useful
	(it needs to be more like a live zope context).
	* __init__.py, version.txt: bumped version number
	* Changelog, Tests.py, ZWikiAdd.py, ZWikiDefaults.py, ZWikiDiff.py, ZWikiPage.py, ZWikiParents.py, ZWikiPermissions.py, ZWikiRegexes.py, __init__.py, run_all_tests, tests/__init__.py, tests/refresh.txt, tests/test_ZWikiPage.py, tests/test_ZWikiWebs.py, tests/test_all.py, testsyntax.expect:
		* append didn't need the timeStamp argument
		* added custom __repr__ from CMFWiki
		* change to CMF-style test framework - in progress
		* refactored code into multiple modules, following CMFWiki.
		Encapsulated some functionality in mix-in classes.
2025-07-07  Simon Michael  
	* Changelog, ZWikiPage.py:
		* another stx workaround tweak: a single letter followed by a
		period is no longer mistaken for a numeric bullet
		* fix for 0.9.3's stx workaround: spurious html comments no
		longer appear in stx examples
		* exposed the "zwiki_username_or_ip" utility method, which given
		REQUEST returns a best guess for username (authenticated user,
		zwiki_username cookie, or ip address)
	* Changelog, ZWikiPage.py:
		* experimental "lasttext" and "diff" methods show the text of a page's
		last revision and a concise diff with the latest
		* bare/noheader/nofooter flags can now also be passed as keyword args
	* Makefile.sample: zope.org automation with curl
2025-05-23  Simon Michael  
	* Changelog, ZWikiPage.py:
	incorporated Ken's changes to inhibit wikilinks in certain places
2025-05-22  Simon Michael  
	* ZWikiPage.py: - some header/footer experimentation
	- added plainhtmldtml mode (DTML + HTML, nothing else)
	- append permission now works
	- tweaked the anti-javascript hack
	- tweaked the anti-decapitation kludge
2025-05-03  Simon Michael  
	* Changelog: wait - hold off this release until zwikiwebs updated
	* Changelog: *** empty log message ***
	* ZWikiPage.py:
	log a new last_editor name when upgrading pages to avoid confusion
	* Changelog, ZWikiPage.py:
		* creating/editing/deleting pages with eg spaces in the name has
		been broken for a while, it seems - made some fixes in this area
	* Changelog, __init__.py, version.txt: bumped version numbers
	* ZWikiPage.py: tweaked the edit conflict message further
	* Changelog, ZWikiPage.py:
		* made the edit conflict message more helpful
	* Changelog, ZWikiPage.py:
	tweaked last_editor semantics. We should always record a username
	even if it's blank.
	* Changelog, ZWikiPage.py:
		* relaxed edit conflict checking slightly: if your username & ip
		address match the last editor's, the timestamp will be ignored.
		This allows you to backtrack in your browser, edit and click
		Change again. However it may disable conflict checking amongst
		anonymous users coming through a proxy.
		* renamed the "username" property to "last_editor", added
		"last_editor_ip", made these read-only in the mgmt. interface.
		Existing zwiki pages are upgraded when viewed.
2025-05-02  Simon Michael  
	* Changelog, ZWikiPage.py:
	added some workarounds for known structured text issues:
		* stx workaround: trailing blank lines no longer cause unwanted
		headings
		* stx workaround: initial word plus period no longer becomes a
		numeric bullet
		* stx workaround: whitespace after :: no longer prevents example
		formatting
		* stx headings on first lines were broken - fixed
2025-04-30  Simon Michael  
	* Changelog, Tests.py, ZWikiPage.py:
		* fix for a 0.9.1 bug: with hierarchy display enabled, creating a
		new page from a top-level page gave "typeerror"
		* gopher: urls are now recognized
2025-04-27  Simon Michael  
	* Changelog, Tests.py, ZWikiPage.py, ZWikiWebs.zexp, __init__.py, version.txt:
	- dithering over wiki,page_path vs. wiki,page_url
	- added a bunch of wiki_{page,base}_url variants to get feedback
	- bumped version no.
2025-04-26  Simon Michael  
	* Tests.py, ZWikiPage.py, Changelog: - more test suite cleanups
	- rewrote sample zwikiwebs test
	* Tests.py: cleanups
	* Tests.py, ZWikiPage.py: reorganized & updated tests
	* Tests.py, ZWikiPage.py:
	replaced "context" & "ctx" in doctests with "zc"
	* Tests.py: more cleanup
	* Changelog, ZWikiPage.py:
		* fixed a potential DeleteMe error message caused by incorrect parents
		* allow standard_wiki_page to be defined as a folder property
		* added plainhtml render mode - HTML only, no wiki-linking
	* Tests.py: updated documentation comment
2025-04-25  Simon Michael  
	* ZWikiWebs.zexp:
	check in latest - the sample wikiwebs are more in line with 0.9.0 than 0.9.1 however
	* __init__.py, version.txt: bump version no.
	* Changelog, ZWikiPage.py:   	* allow non-wiki paths in [], eg
		[uploads/file.txt] => file.txt
		* image file names in [] are auto-inlined, eg
		[apple.png] => 
		* use uploaded image size to help "add file/image" decide whether to inline
		* reset parents when they have become outdated/confused
		* folder attribute "standard_page_type" overrides type of all new pages
	* GPL.txt, LICENSE.txt, README.txt: added license notes, GPL text
	* Changelog, ZWikiPage.py:
		* display new page name when creating a page
		* record username when creating a page
		* renamed wiki_page_url(), wiki_base_url() to page_path(),
		wiki_path(). The old names, used throughout existing dtml code,
		are supported but deprecated
		* made remotewikilinks more careful to avoid trailing punctuation
		* allow []-named pages in remote wiki links
		* disable structured text's [] footnote linking to avoid conflicts
2025-04-24  Simon Michael  
	* ZWikiWebs.zexp: oops, really snapshot zwikiwebs.zexp from zwiki.org
	* README.txt, ZWikiPage.py, __init__.py, version.txt:
	- bump version no.
	- snapshot zwikiwebs.zexp from zwiki.org
	* ZWikiPage.py, Changelog: 	* added append method
		* simple email notification (PageSubscribers)
		* made wikilinks absolute for greater robustness
		* header/footer layout tweaks
		* made last editor's authenticated username override username cookie
		* proxy role tweak
	* emacs/zwiki-mode.el:
	tweak for compatibility with zope.org's wiki version
2025-03-25  Simon Michael  
	* emacs/zwiki-mode.el: comments update
2025-02-22  Simon Michael  
	* ZWikiPage.py, Changelog:
	* changed/reverted wiki_{page,base}_url as per Christian Scholz for
	virtual hosting
	* allowed https: urls
	* allowed + and $ in remote wiki links
	* use uploads/ subfolder if present
2025-01-11  Simon Michael  
	* Tests.py, ZWikiPage.py: - refactored edit
	- added permissions for new functionality
	- renamed attic to recycle_bin
	- cleanups
	* ZWikiPage.py:
	- allow a file or image to be added when editing or creating a page.
	Ie, upload the file and append a link to it, or inline an image.
	Derived from Ken's file/image upload feature in WikiForNow.
	- add a __tests__ declaration to make doctest test methods beginning with _
	* Tests.py: comment cleanups
	* __init__.py, wwml.py: cleanups
2025-01-04  Simon Michael  
	* Changelog, Tests.py, ZWikiPage.py, ZWikiWebs.zexp, __init__.py, testsyntax.expect, version.txt:
	ZWiki 0.8.1 2025-01-04
		* added (experimental) page deletion
		* record IP address when username cookie is blank
		* make disabled javascript tags visible
		* added more unit tests
	ZWiki 0.8.0rc1 2025-12-14
		* log last editor's IP address if there is no username cookie
		* wrapped a bunch of long lines
		* unit tests - added support for ZUnit and DocTest, and a few
		initial tests. http://zwiki.org/zwikidir/Makefile.sample contains
		some useful recipes for automated testing.
		ZWikiWebs.zexp: incorporated latest zwiki.org tweaks, namely:
		* removed a spurious menu from the add zwiki web form
		* added UserOptions to the default BookMarks and removed it from
		the page footer
		* bookmarks, quote, search box and hierarchy may all be turned on
		or off in UserOptions
		* the default home page is now a user option
		* user options & search box tab ordering fixed
		* always show the editform even for write-protected pages, with
		appropriate header/footer color
		* page history is now accessible
		* simplified RecentChanges
2025-12-14  Simon Michael  
	* Makefile.sample, testsyntax.expect:
	- Makefile.sample contains some recipes that other zope product
	developers might find useful, for automated testing, releases, etc.
	- testsyntax.expect is a support script for syntax-checking python files
	* Tests.py, ZWikiPage.py, __init__.py, doctest.py, wwml.py:
	- log last editor's IP address if there is no username cookie
	- wrapped a bunch of long lines
	- debugged & cleaned up the doctest support. Added a hacked version of
	doctest.py that will recognize extension classes.
2025-11-22  Simon Michael  
	* Tests.py: a bug, caught by running the tests. Yay!
	Thought I did that before checkin. Never mind.
	* Tests.py: ZUnit 0.1.1 simplifications
	* Tests.py, ZWikiPage.py, __init__.py:
	- edit didn't need the submit argument
	- added ZUnit support & a couple of tests
2025-11-03  Simon Michael  
	* Changelog, README.txt, ZWikiPage.py, ZWikiWebs.zexp, __init__.py, version.txt:
	- a better workaround for non-rendering of initial lines containing
	":" in dtml modes
	- return to the page after clicking reparent
	- tweaked reparent to stamp username (to be fixed)
	- added warning of incompatibility with old dtml methods to readme
	- changelog, version no. update
2025-10-31  Simon Michael  
	* Changelog: separated items for stx rendering, added phil
	* README.txt, ZWikiPage.py: added quick installation instructions
	* ZWikiWebs.zexp: incorporate updates from ZWiki bugfixes
	* Changelog, README.txt, ZWikiPage.py: - doc updates
	- I'll just assume wiki_base_url should have the same fix as wiki_page_url
	* ZWikiPage.py, __init__.py:
	- disabled the (broken) new page dtml checking
	- made edit() distinguish and obey the add/change permissions
2025-10-30  Simon Michael  
	* ZWikiPage.py:
	fixed __len__ attribute error when editing a page created via mgmt ui, cleanups
	* ZWikiPage.py:
	dtml modes did not render the first line or two when they were they
	were blank or contained : . Another tricky one, and I haven't
	understood it. Seems to call for a step-through with debugger.
	* ZWikiPage.py, wwml.py:
	fixed a tricky bug which caused some remotewikilink breakage in classicwiki mode.
	we no longer do any hyperlinking in wwml.py, and I dropped a superfluous method.
2025-10-29  Simon Michael  
	* README.txt, ZWikiPage.py:
	removed the "and contributors" from the copyright.
	Call me paranoid.
	* __init__.py: *** empty log message ***
	* ZWikiPage.py: - two url-quoting fixes
	- made username handling a little more robust
	- tried setting last-modified header, without good results
	- a fix for manage_addZWikiPage()
	* emacs/zwiki-mode.el:
	my half-finished local/ftp/http wiki/zwiki power tool.
	* ZWikiWebs.zexp:
	many updates. With a bit of luck this sucker is ready for release.
2025-10-28  Simon Michael  
	* ZWikiPage.py: all kinds of changes, should check in more often.
	- simple-minded anti-javascript seatbelt
	- updates to built-in dtml defaults; reverted to not displaying hierarchy info
	- stamp pages with a username on edit, if provided
	- added wikilink & htmlquote utility methods for dtml
	- mucked around with context() & present_nesting() for a streamlined
	  context/title/contents display; call with enlarge_current=1 to activate
	* refresh.txt: better empty
	* ZWikiWebs.zexp, zwiki.zexp:
	the ZWikiWebs factory product replaces the old zwiki_examples
2025-10-02  Simon Michael  
	* ZWikiPage.py:
	A rising Zen lifts all boats.  Fixed a bunch of dtml document/method
	calls. This explains why I needed "with this" hack, with us since
	release 0.1 - no longer. WOOOOP!
	* __init__.py: minor edit
2025-10-01  Simon Michael  
	* __init__.py: product version != the cvs revision of this file
	* ZWikiPage.py: minor updates
	* ZWikiPage.py: _create_page clarification
	* ZWikiPage.py, __init__.py:
	a lot of cleanup, refactoring and consistency fixes, lightly tested.
	the code is more tricky but the api should be simpler and more powerful.
	edit is now the single entry point for changing/creating pages.
	standard_wiki_header, standard_wiki_footer, standard_wiki_page,
	editform, backlinks & titleprefix all now have built-in defaults,
	so dtml-method versions are optional.
2025-09-30  Simon Michael  
	* ZWikiPage.py, __init__.py: misc bugfixes
	* ZWikiPage.py:
	From the "just start typing" school of software development...
	- wikiwikiweb-style late page creation
	- added builtin defaults for editform & backlinks
	- updated the defaults to display parenting info
	* refresh.txt: Refresh rocks.
	* ZWikiPage.py:
	ok, that was easy, do ! line escapes earlier so structured text won't interfere
	* ZWikiPage.py: more cleanups
	* ZWikiPage.py:
	geoff asked for this - clean up the "ken's parenting code" comments.
2025-09-29  Simon Michael  
	* ZWikiPage.py:
	make new pages always acquire ownership from their parent folder
	to simplify security management
	* ZWikiPage.py: oops - intl_char_entities is needed
	* ZWikiPage.py: cleanups
	* ZWikiPage.py:
	Geoff's patch for more permissive remote links, + regexp & other cleanups
	* ZWikiPage.py:
	That wiki_page_url hack of mine didn't work (broke virtual hosting).
	I wanted to play with alternate acquisition paths, but now I don't.
	Let's go back to absolute_url, using Evan's patch which looks more
	tricky than I thought at first. Let me know if problems remain.
2025-09-28  Simon Michael  
	* ZWikiPage.py:
	2.2's enhanced security model makes Jim's anonymous-dtml hack unnecessary.
	This code won't run on zope < 2.2 so it should be ok to drop it.
	* __init__.py:
	forgot to mention: the url for the zwiki product icon now ends with .gif
	* Changelog, README.txt, ZWikiPage.py, __init__.py, version.txt:
	2.2-style product initialization from GTK + minor edits and bump version no.
	* Changelog: *** empty log message ***
	* ZWikiPage.py: comment clarification
2025-05-24  Simon Michael  
	* ZWikiPage.py:
	append ?noheaders=1 to url to disable standard_wiki_header & footer
	perhaps there's a smarter way which also checks the **kw ?
2025-05-14  Simon Michael  
	* ZWikiPage.py: add & to url regexp
2025-05-09  Simon Michael  
	* ZWikiPage.py: make new pages empty, if not overridden
2025-05-04  Simon Michael  
	* zwiki.zexp: documented permissions setup in index_html
	* Changelog, __init__.py, version.txt: update to release 0.6.1
	* ZWikiPage.py: darn! last-minute fix to really disable underlinedexpr
	forgot to check this in when I uploaded release 0.6
2025-05-02  Simon Michael  
	* Changelog: added 0.7 changes
	* ZWikiPage.py: /* incorporated jim's permission & validation patch
	/* got add & change zwiki page permissions working
	/* made builtin footer adapt to permissions
	/* added = to url regexp
	/* defined default page type in one place, changed to structuredtext
	* zwiki.zexp: re-organized & expanded to reflect new features
	* __init__.py: brought new() under 'add zwiki page' permission
	* ZWikiPage.py:
	made the built-in header/footer gray, to emphasize their bare-boned-ness
2025-04-30  Simon Michael  
	* ZWikiPage.py: - added utility methods wiki_base_url & wiki_page_url
	- changed ken's navigation stuff to use these instead of absolute_url
	  so that different acquisition paths can be used
2025-04-29  Simon Michael  
	* ZWikiPage.py: merged some work from DC (their 1.20 revision):
	- jim's http & ftp edit-conflict safety belts
	- regexp tweak - wikinames must start on a word boundary
	- ken's parenting & hierarchy mapping stuff
	* README.txt: *** empty log message ***
2025-04-16  Simon Michael  
	* ZWikiPage.py: support # in urls, try supporting numbers in wikinames
	* ZWikiPage.py:
	version update, minor cleanups, a start on supporting international-character links
	* __init__.py, version.txt: update version strings to 0.6
2025-04-15  Simon Michael  
	* Changelog, version.txt: release 0.5
	* ZWikiPage.py: disabled catalog, tweaked builtin wiki content
	* zwiki.zexp: moved the sample wiki content into CVS
2025-03-27  Simon Michael  
	* Changelog, README.txt, ZWikiPage.py, version.txt: 0.4 release
1999-12-10  Simon Michael  
	* Changelog, README.txt, ZWikiPage.py, ZWikiPage_icon.gif, __init__.py, version.txt, wwml.py, zwikipageAdd.dtml:
	initial cvs checkin
ZWiki/Defaults.py0100644000175000017500000000625507637410717013315 0ustar  simonsimon######################################################################
# flags, constants and built-in defaults for ZWiki
DEFAULT_PAGE_TYPE = 'stxprelinkhtml'    # default render type for new pages
DISABLE_JAVASCRIPT = 1                  # disable javascript in edits ?
LARGE_FILE_SIZE = 1024*1024             # unused ?
AUTO_UPGRADE = 1                        # upgrade old pages when viewed
MAX_NEW_LINES_DISPLAY = 200             # truncate each diff (and mailout)
MAX_OLD_LINES_DISPLAY = 20              # at this number of lines
PAGE_METATYPE = 'ZWiki Page'
# CMF stuff
PAGE_PORTALTYPE = 'Wiki Page'
WIKI_PORTALTYPE = WIKI_METATYPE = 'Wiki Folder'
default_regs = {
    'create': 'nonanon',
    'edit': 'nonanon',
    'comment': 'nonanon',
    'move': 'owners',
    }
# built-in defaults for UI methods
# there seems to be a difference between these and zodb-based DTML methods -
# the latter allow # in multi-line python expressions but these don't
from Globals import HTMLFile
default_standard_wiki_header = HTMLFile(
    'skins/default/standard_wiki_header', globals())
default_standard_wiki_footer = HTMLFile(
    'skins/default/standard_wiki_footer', globals())
default_backlinks = HTMLFile('skins/default/backlinks', globals())
default_contentspage = HTMLFile('skins/default/contentspage', globals())
default_diffform = HTMLFile('skins/default/diffform', globals())
default_editform = HTMLFile('skins/default/editform', globals())
default_subscribeform = HTMLFile('skins/default/subscribeform', globals())
# ZPT-based defaults
# with older zope versions ZPTs might not be available
try:
    # with zope 2.5.x < 2.5.1, need to import Zope first
    # (http://lists.zope.org/pipermail/zope-coders/2002-March/000953.html)
    # hmm can't do that.. how can we support PageTemplateFile with <2.5.1 ?
    #try:
    #    from Products.PageTemplates.PageTemplateFile import PageTemplateFile
    #except ImportError:
    #    import Zope
    #    from Products.PageTemplates.PageTemplateFile import PageTemplateFile
    #default_wikipage = PageTemplateFile('skins/default/wikipage',
    #                                    globals(),__name__='wikipage')
    from Products.PageTemplates.PageTemplateFile import PageTemplateFile
    #default_wikipage = PageTemplateFile('skins/default/wikipage',
    #                                    globals(),__name__='wikipage')
    #oh gawd
    #subclass it to make here & container simulate the zwiki page's context
    from Products.PageTemplates.Expressions import SecureModuleImporter
    class MyPageTemplateFile(PageTemplateFile):
        def pt_getContext(self):
            root = self.getPhysicalRoot()
            c = {'template': self,
                 'here': self.aq_inner.aq_parent,
                 'container': self.aq_inner.aq_parent.aq_parent,
                 'nothing': None,
                 'options': {},
                 'root': root,
                 'request': getattr(root, 'REQUEST', None),
                 'modules': SecureModuleImporter,
                 }
            return c
    default_wikipage = MyPageTemplateFile('skins/default/wikipage',
                                          globals(),__name__='wikipage')
except ImportError:
    default_wikipage = None
ZWiki/Diff.py0100644000175000017500000003263007637410717012412 0ustar  simonsimon######################################################################
# diff experiments
#
# todo
# separate out html vs. email diff methods
from string import join, split, atoi
from DocumentTemplate.DT_Util import html_quote
from struct import pack, unpack
from OFS.History import historicalRevision
from OFS import ndiff
# Tim Peters' ndiff is now python 2.1's difflib module
# use the former for compatibility with older zopes
import re
def ISJUNK(line, pat=re.compile(r"\s*$").match):
    return pat(line) is not None
from Defaults import MAX_OLD_LINES_DISPLAY, MAX_NEW_LINES_DISPLAY
###########################################################################
# CLASS ZwikiDiffMixin
# RESPONSIBILITIES
# - encapsulate ZWikiPage diff functionality in a separate file
# - generate human-readable diffs between page versions
# COLLABORATORS ZWikiPage
###########################################################################
class DiffSupport:
    """
    This mix-in class adds some diff methods to ZWikiPage.
    """
    ######################################################################
    # METHOD CATEGORY: diff
    ######################################################################
    def lasttext(self, versionsBack=1):
        """
        Return the text of the last or an earlier revision of this page.
        """
        versionsBack = int(versionsBack)
        try:
            # problem here - manage_change_history only gets the last
            # 20 revisions
            lastrevision = self.manage_change_history()[versionsBack]
            key = lastrevision['key']
            serial=apply(pack, ('>HHHH',)+tuple(map(atoi, split(key,'.'))))
            lastself=historicalRevision(self, serial)
            return lastself.text()
        except:
            # return '' if we don't have a version that old
            return ''
    def lastlog(self, versionsBack=0, withQuotes=0):
        """
        Get the log note from an earlier revision of this page.
        Just a quick helper for diff browsing.
        """
        versionsBack = int(versionsBack)
        try:
            # problem here - manage_change_history only gets the last
            # 20 revisions
            note = self.manage_change_history()[versionsBack]['description']
            match = re.search(r'"(.*)"',note)
            if match:
                if withQuotes:
                    return match.group()
                else:
                    return match.group(1)
            else:
                return ''
        except:
            # return '' if we don't have a version that old
            return ''
    def textDiff(self,revA=1,revB=0,a=None,b=None, verbose=1):
        """
        generate a plain text diff, optimized for human readability,
        between two revisions of this page, numbering back from the latest.
        Alternately, a and/or b texts can be specified.
        """
        revA, revB = int(revA), int(revB)
        a = a or self.lasttext(versionsBack=revA)
        b = b or self.lasttext(versionsBack=revB)
        a = split(a,'\n')
        b = split(b,'\n')
        cruncher=ndiff.SequenceMatcher(
            #isjunk=split,
            isjunk=ISJUNK,
            #isjunk=lambda x: x in " \\t", # requires newer difflib
            a=a,
            b=b)
        r = []
        for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
            if tag == 'replace':
                if verbose: r.append('??changed:')
                r = r + self._abbreviateDiffLines(a[alo:ahi],'-',
                                                  MAX_OLD_LINES_DISPLAY)
                r = r + self._abbreviateDiffLines(b[blo:bhi],'',
                                                  MAX_NEW_LINES_DISPLAY)
                r.append('')
            elif tag == 'delete':
                if verbose: r.append('--removed:')
                r = r + self._abbreviateDiffLines(a[alo:ahi],'-',
                                                  MAX_OLD_LINES_DISPLAY)
                r.append('')
            elif tag == 'insert':
                if verbose: r.append('++added:')
                r = r + self._abbreviateDiffLines(b[blo:bhi],'',
                                                  MAX_NEW_LINES_DISPLAY)
                r.append('')
            elif tag == 'equal':
                pass
            else:
                raise ValueError, 'unknown tag ' + `tag`
        return '\n' + join(r, '\n')
    def _abbreviateDiffLines(self,lines,prefix,maxlines=5):
        output = []
        if maxlines and len(lines) > maxlines:
            extra = len(lines) - maxlines
            for i in xrange(maxlines - 1):
                output.append(prefix + lines[i])
            output.append(prefix + "[%d more line%s...]" %
                          (extra, ((extra == 1) and '') or 's')) # not working
        else:
            for line in lines:
                output.append(prefix + line)
        return output
    # XXX should skip uninteresting transactions
    #if re.search(r'(/edit|/comment|/append|PUT)',lastrevision['description']):
    def diff(self,revA=1,revB=0,showSteps=0,REQUEST=None):
        """
        display a human-readable diff, formatted for web display,
        between two revisions of this page, numbering back from the latest.
        Also display some navigation links.
        """
        revA, revB = int(revA), int(revB)
        t = self.textDiff(revA=revA,revB=revB)
        t = html_quote(t)
        # careful, don't feed the regexps..
        t = re.sub(r'(?s)(\?\?changed.*?)\n((-.*?\n)+?)((-\[[0-9]+ more.*?\]\n)?)([^-].*?)((\[[0-9]+ more.*?\]\n)?)(?=()?\+\+added|()?--removed|()?\?\?changed|$)',
                   r'\1\n\2\4\6\7',
                   t)
        t = re.sub(r'(?s)(\+\+added.*?\n)(.*?)((\[[0-9]+ more.*?\]\n)?)(?=()?\+\+added|()?--removed|()?\?\?changed|$)',
                   r'\1\2\3',
                   t)
        # -[43 more lines...] should not get matched in \2
        t = re.sub(r'(?s)(\-\-removed.*?\n)((-.*?\n)+?)((-\[[0-9]+ more.*?\]\n)?)(?=()?\+\+added|()?--removed|()?\?\?changed|$)',
                   r'\1\2\4',
                   t)
        return self.diffform(revA,t,REQUEST=REQUEST)
    def prevDiff(self,currentRevision):
        """
        helpers for form buttons
        """
        return self.diff(int(currentRevision)+1,int(currentRevision))
    def nextDiff(self,currentRevision):
        """
        helpers for form buttons
        """
        return self.diff(int(currentRevision)-1,int(currentRevision)-2)
    def oldDiff(self,revA=1,revB=0):
        """
        display a zope-page-history-style html-formatted diff 
        between two revisions of this page, numbering back from the latest.
        """
        revA, revB = int(revA), int(revB)
        a=split(self.lasttext(versionsBack=revA),'\n')
        b=split(self.lasttext(versionsBack=revB),'\n')
        cruncher=ndiff.SequenceMatcher(isjunk=split, a=a, b=b)
        r = ['']
        for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
            if tag == 'replace':
                replace(a, alo, ahi, b, blo, bhi, r)
            elif tag == 'delete':
                dump('-', a, alo, ahi, r)
            elif tag == 'insert':
                dump('+', b, blo, bhi, r)
            elif tag == 'equal':
                pass     #dump(' ', a, alo, ahi, r)
            else:
                raise ValueError, 'unknown tag ' + `tag`
        r.append('
') diff = join(r, '\n') return '\n\n'+diff+'\n\n' # wikifornow stuff - roll em in, sort em out later def wfn_get_page_history(self, mode='condensed', batchsize=30, first=0, last=30): """\ Return history records for a page, culling according to mode param. 'complete': all records. 'condensed': Omit showing prior versions of page replaced subsequently and soon after by the same person using same (possibly empty) log entry Currently """ r = self._p_jar.db().history(self._p_oid, None, 5000) for i in range(len(r)): r[i]['tacked_on_index'] = i if mode == 'complete': pass elif mode == 'condensed': # Each entry may: # - either continue an existing session or start a new one, and # - either be a landmark or not. got = [] carrying = None prevdescr = None # Put in least-recent-first order: r.reverse() for entry in r: curdescr = split(entry['description'], '\012')[1:] # Handle prior retained stuff: if carrying: if carrying['user_name'] != entry['user_name']: # Different user: got.append(carrying) elif curdescr != prevdescr: # Different log entry: got.append(carrying) else: itime, ctime = entry['time'], carrying['time'] if type(itime) == FloatType: itime = entry['time'] = DateTime(itime) if type(ctime) == FloatType: ctime = carrying['time'] = DateTime(ctime) if (float(itime - ctime) * 60 * 24) > 30: # Enough time elapsed: # XXX klm "Enough time" should be configurable... got.append(carrying) # Old-session, if any, was handled - move forward: carrying = entry prevdescr = curdescr if carrying: # Retain final item got.append(carrying) # Put back in most-recent-first order: got.reverse() r = got else: raise ValueError, "Unknown mode '%s'" % mode for d in r: if type(d['time']) == FloatType: d['time'] = DateTime(d['time']) d['key']=join(map(str, unpack(">HHHH", d['serial'])),'.') r=r[first:first+batchsize+1] return r def wfn_history_copy_page_to_present(self, keys=[], REQUEST=None): """Create a new object copy with the contents of an historic copy.""" request=getattr(self, 'REQUEST', None) if not self.isAllowed('edit', request): raise 'Unauthorized', "You're not allowed to edit this page" self.manage_historyCopy(keys=keys) if REQUEST is not None: REQUEST.RESPONSE.redirect(self.wiki_page_url()) def wfn_history_compare_versions(self, keys=[], REQUEST=None): """Do history comparisons. Mostly stuff adapted from OFS.History - manage_historicalComparison() and manage_historyCompare(), with a bit of direct calling of html_diff.""" from OFS.History import historicalRevision, html_diff if not keys: raise HistorySelectionError, ( "No historical revision was selected.

") if len(keys) > 2: raise HistorySelectionError, ( "Only two historical revision can be compared

") serial=apply(pack, ('>HHHH',)+tuple(map(string.atoi, split(keys[-1],'.')))) rev1=historicalRevision(self, serial) if len(keys)==2: serial=apply(pack, ('>HHHH',)+tuple(map(string.atoi, split(keys[0],'.')))) rev2=historicalRevision(self, serial) else: rev2=self dt1=DateTime(rev1._p_mtime) dt2=DateTime(rev2._p_mtime) t1, t2 = rev1._st_data, rev2._st_data if t1 is None or t2 is None: t1, t2 = rev1.xread(), rev2.xread() top = self._manage_historyComparePage( self, REQUEST, dt1=dt1, dt2=dt2, historyComparisonResults=html_diff(t1, t2), manage_tabs=self.standard_wiki_header) bottom = self.standard_wiki_footer(self, REQUEST=REQUEST) return top + bottom ###################################################################### # FUNCTION CATEGORY: diff helper functions ###################################################################### def dump(tag, x, lo, hi, r): r1=[] r2=[] for i in xrange(lo, hi): r1.append(tag) r2.append(x[i]) r.append("\n" "

\n%s\n
\n" "
\n%s\n
\n" "\n" % (join(r1,'\n'), html_quote(join(r2, '\n')))) def replace(x, xlo, xhi, y, ylo, yhi, r): rx1=[] rx2=[] for i in xrange(xlo, xhi): rx1.append('-') rx2.append(x[i]) ry1=[] ry2=[] for i in xrange(ylo, yhi): ry1.append('+') ry2.append(y[i]) r.append("\n" "
\n%s\n%s\n
\n" "
\n%s\n%s\n
\n" "\n" % (join(rx1, '\n'), join(ry1, '\n'), html_quote(join(rx2, '\n')), html_quote(join(ry2, '\n')))) ZWiki/Fit.py0100644000175000017500000000626407641220301012247 0ustar simonsimon# fit (framework for interactive testing) support import sys, os, string, re from OFS.DTMLDocument import DTMLDocument from Utils import Popen3 from AccessControl import getSecurityManager, ClassSecurityInfo import Permissions # from tests/support.py: def pdir(path): return os.path.split(path)[0] ZWIKIDIR = pdir(os.path.abspath(__file__)) try: from fit.Parse import Parse from fit.Parse import ParseException from fit.Fixture import Fixture class FitSupport: """ Mix-in class for fit support """ security = ClassSecurityInfo() # no good, doesn't override #def _render(self, client=None, REQUEST={}, RESPONSE=None, **kw): # """ # Hack the render method to always run fit as the last step # """ # method = self._renderMethod() # t = apply(method,(self, REQUEST, RESPONSE), kw) # t = Fixture().doTables(Parse(t)) # return t def _runFitInternallyOn(self,text): try: fitTables = Parse(text) except ParseException: return text # reset class-variable counts # not working Fixture.rights = 0 Fixture.wrongs = 0 Fixture.ignores = 0 Fixture.exceptions = 0 Fixture.summary = {} # find fit in ZWiki product directory as well as PYTHONPATH oldpath = sys.path sys.path.insert(0,ZWIKIDIR) Fixture().doTables(fitTables) sys.path = oldpath return str(fitTables) def _runFitExternallyOn(self,text): oldpath = sys.path sys.path.insert(0,ZWIKIDIR) p = Popen3( '''PYTHONPATH=%s python -c \ "import fit.FileRunner; \ fit.FileRunner.FileRunner(('','-','-')).run()"''' % \ string.join(sys.path,':'), input=text) sys.path = oldpath return p.out #def _runFitOnClassFit(self,text): # """ # Run fit on tables with class="fit". # """ # # jiggery-pokery # t = text # t = re.sub(r'(?im) for the zwiki community. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. If you need to discuss an alternate licensing arrangement, contact me at zwiki.org. ZWiki/LocalizerSupport.py0100644000175000017500000001036007620370203015042 0ustar simonsimon# Copyright (C) 2002 Juan David Ibáñez Palomar # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ This module makes more easy to implement multilingual Zope products based on Localizer that also work when Localizer is not installed (they are monolingual in this situation). It provides dummy versions of some Localizer features. To internationalize your code copy and paste this module to your product directory and, in the modules you need it, just type: from LocalizerSupport import _, N_ from LocalizerSupport import LocalDTMLFile as DTMLFile from LocalizerSupport import LocalPageTemplateFile as PageTemplateFile Note that the 'as' keyword is new to Python 2.0, if to support older versions is a requirement you should type this instead: from LocalizerSupport import _, N_ from LocalizerSupport import LocalDTMLFile, LocalPageTemplateFile DTMLFile = LocalDTMLFile PageTemplateFile = LocalPageTemplateFile del LocalDTMLFile del LocalPageTemplateFile Another option is not to rename the classes, then you will have to change from 'DTMLFile' to 'LocalDTMLFile' and from 'PageTemplateFile' to 'LocalPageTemplateFile' wherever you need it. Note that Localizer requieres Python 2.1 or above, so the multilingual version of your product will also requiere Python 2.1 or above. Of course, you don't need to import the features you don't need. """ # The version information is relative to Localizer, it shouldn't # depend on the native product, as this module is supposed to be # copied as it is, without any other modification. __version__ = '0.9.1+' try: from Products.Localizer import Gettext _ = Gettext.translation(globals()) N_ = Gettext.dummy from Products.Localizer import LocalDTMLFile, LocalPageTemplateFile except (ImportError, NameError): # For Python code def _(s, language=None): return s N_ = _ # For DTML and Page Templates def gettext(self, message, language=None): """ """ return message # Document Template Markup Langyage (DTML) from Globals import DTMLFile class LocalDTMLFile(DTMLFile): def _exec(self, bound_data, args, kw): # Add our gettext first bound_data['gettext'] = self.gettext return apply(LocalDTMLFile.inheritedAttribute('_exec'), (self, bound_data, args, kw)) gettext = gettext # Zope Page Templates (ZPT) try: from Products.PageTemplates.PageTemplateFile import PageTemplateFile except ImportError: # If ZPT is not installed class LocalPageTemplateFile: pass else: class LocalPageTemplateFile(PageTemplateFile): def _exec(self, bound_data, args, kw): # Add our gettext first bound_data['gettext'] = self.gettext return apply(LocalPageTemplateFile.inheritedAttribute('_exec'), (self, bound_data, args, kw)) gettext = gettext # Dummy dtml-gettext tag from DocumentTemplate.DT_Util import InstanceDict, namespace, render_blocks class GettextTag: """ """ name = 'gettext' blockContinuations = () def __init__(self, blocks): tname, args, section = blocks[0] self.section = section.blocks def __call__(self, md): ns = namespace(md)[0] md._push(InstanceDict(ns, md)) message = render_blocks(self.section, md) md._pop(1) return message # Register the dtml-gettext tag from DocumentTemplate.DT_String import String if not String.commands.has_key('gettext'): String.commands['gettext'] = GettextTag ZWiki/Mail.py0100644000175000017500000005103007644614066012420 0ustar simonsimon# MailSupport mixin import string, re, sys, traceback from string import split,join,find,lower,rfind,atoi,strip from types import * import TextFormatter from Utils import html_unquote,DLOG from Defaults import AUTO_UPGRADE class MailSupport: """ This mixin class provides subscription and wikimail support. Responsibilities: manage a list of subscribers for both this page and it's folder, and expose these in the ZMI; also provide wikimail utilities and auto-upgrade support. A "subscriber" is a string which may be either an email address or a CMF member username. A list of these is kept in the page's and/or folder's subscriber_list property. For the moment, it's still called "email" in arguments to avoid breaking legacy dtml (eg subscribeform). """ subscriber_list = [] _properties=( {'id':'subscriber_list', 'type': 'lines', 'mode': 'w'}, ) ## private ########################################################### def _getSubscribers(self, parent=0): """ Return (a copy of!) this page's subscriber list. With parent flag, manage the parent folder's subscriber list instead. """ if AUTO_UPGRADE: self._upgradeSubscribers() if parent: if hasattr(self.folder(),'subscriber_list'): return self.folder().subscriber_list[:] else: return [] else: return self.subscriber_list[:] def _setSubscribers(self, subscriberlist, parent=0): """ Set this page's subscriber list. With parent flag, manage the parent folder's subscriber list instead. """ if AUTO_UPGRADE: self._upgradeSubscribers() if parent: self.folder().subscriber_list = subscriberlist else: self.subscriber_list = subscriberlist def _resetSubscribers(self, parent=0): """ Clear this page's subscriber list. With parent flag, manage the parent folder's subscriber list instead. """ self._setSubscribers([],parent) def _upgradeSubscribers(self): """ 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 attribute oldsubs = None if (hasattr(f, 'subscribers') and type(f.subscribers) is StringType): if f.subscribers: oldsubs = split(re.sub(r'[ \t]+',r'',f.subscribers),',') try: del f.subscribers except KeyError: DLOG('failed to delete self.folder().subscribers') changed = 1 # migrate a wikifornow _subscribers attribute oldsubs = None if hasattr(f, '_subscribers'): oldsubs = f._subscribers.keys() try: del f._subscribers except KeyError: DLOG('failed to delete self.folder()._subscribers') changed = 1 if not hasattr(f, 'subscriber_list'): f.subscriber_list = [] # copy old subscribers to subscriber_list, unless it's already # got some # XXX merge instead 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: DLOG('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 (hasattr(self, 'subscribers') and type(self.subscribers) is StringType): if self.subscribers: oldsubs = split(re.sub(r'[ \t]+',r'',self.subscribers),',') try: del self.subscribers except KeyError: DLOG('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 hasattr(self, '_subscribers'): oldsubs = self._subscribers.keys() try: del self._subscribers except KeyError: DLOG('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: DLOG('upgraded %s subscriber list' % (self.id())) ## page subscription api ############################################# def subscriberList(self, parent=0): """ Return a list of this page's subscribers With parent flag, manage the parent folder's subscriber list instead. """ return self._getSubscribers(parent) def subscriberCount(self, parent=0): """ Return the number of subscribers currently subscribed to this page With parent flag, count the parent folder's subscriber list instead. """ return len(self.subscriberList(parent)) def isSubscriber(self,email,parent=0): """ Is email currently subscribed to this page ? With parent flag, check the parent folder's subscriber list instead. Now that email may be an email address or a username, we must work a bit harder: - for a username, check whether it's subscribed as-is. Don't bother looking up it's email address. - for an email address, check whether it's subscribed as-is (case-insensitively), but also look for member username(s) which have that email address and check whether any of those are subscribed. """ subscriber = email if subscriber: email = self.emailAddressFrom(subscriber) if email: email = string.lower(email) usernames = self.usernamesFrom(subscriber) for sub in self.subscriberList(parent): if not sub: continue if ((email and (string.lower(sub) == email)) or (usernames and (sub in usernames))): return 1 return 0 def subscribe(self, email, REQUEST=None, parent=0): """ Add email as a subscriber to this page. With parent flag, add to the parent folder's subscriber list instead. """ subscriber = email if subscriber: if not self.isSubscriber(subscriber,parent): subs = self._getSubscribers(parent) subs.append(subscriber) self._setSubscribers(subs,parent) if REQUEST: REQUEST.RESPONSE.redirect( REQUEST['URL1']+'/subscribeform?email='+subscriber) def unsubscribe(self, email, REQUEST=None, parent=0): """ Remove email from this page's subscriber list. With parent flag, remove from the parent folder's subscriber list instead. Does not attempt to look up the username from an email address or vice-versa, so you must unsubscribe the correct one. """ subscriber = email if self.isSubscriber(subscriber,parent): sl = self.subscriberList(parent) for s in sl: if string.lower(s) == string.lower(subscriber): sl.remove(s) self._setSubscribers(sl,parent) if REQUEST: REQUEST.RESPONSE.redirect( REQUEST['URL1']+'/subscribeform?email='+subscriber) ## folder subscription api ########################################### def wikiSubscriberList(self): """whole-wiki version of subscriberList""" return self.subscriberList(parent=1) def wikiSubscriberCount(self): """whole-wiki version of subscriberCount""" return self.subscriberCount(parent=1) def isWikiSubscriber(self,email): """whole-wiki version of isSubscriber""" return self.isSubscriber(email,parent=1) def wikiSubscribe(self, email, REQUEST=None): """whole-wiki version of subscribe""" return self.subscribe(email,REQUEST,parent=1) def wikiUnsubscribe(self, email, REQUEST=None): """whole-wiki version of unsubscribe""" return self.unsubscribe(email,REQUEST,parent=1) ## misc api methods ################################################## def allSubscribers(self): """ List all subscribers to this page or the wiki. """ subs = [] subs.extend(self.subscriberList()) # copy, don't reference for s in self.wikiSubscriberList(): if not (s in subs): subs.append(s) return subs def allSubscriptionsFor(self, email): """ Return the ids of all pages to which a subscriber is subscribed ('whole_wiki' indicates a wiki subscription). """ subscriber = email # simple-minded implementation subscriptions = [] if self.isWikiSubscriber(subscriber): subscriptions.append('whole_wiki') # use catalog ? for id, page in self.folder().objectItems(spec='ZWiki Page'): if page.isSubscriber(subscriber): subscriptions.append(id) return subscriptions def otherPageSubscriptionsFor(self, email): """ Ack, this was too hard in DTML. Return the ids of all pages to which a subscriber is subscribed, excluding the current page and 'whole_wiki'. """ subscriber = email subs = self.allSubscriptionsFor(subscriber) thispage = self.id() if thispage in subs: subs.remove(thispage) if 'whole_wiki' in subs: subs.remove('whole_wiki') return subs ## utilities ################################################ def isMailoutEnabled(self): """ True if mailout has been configured """ if (hasattr(self,'MailHost') and (getattr(self.folder(),'mail_from',None) or getattr(self.folder(),'mail_replyto',None))): return 1 else: return 0 def isEmailAddress(self,s): """ True if s looks like an email address. """ if '@' in s: return 1 else: return 0 def isUsername(self,s): """ True if s looks like a username. """ return not self.isEmailAddress(s) def emailAddressFrom(self,subscriber): """ Convert subscriber to an email address, if needed. If subscriber is an email address, return as-is. Otherwise assume it's a username and try to look up the corresponding CMF member's email address. Otherwise return None. """ if self.isEmailAddress(subscriber): return subscriber else: folder = self.folder() try: user = folder.portal_membership.getMemberById(subscriber) member = folder.portal_memberdata.wrapUser(user) return member.email except AttributeError: return None def emailAddressesFrom(self,subscribers): """ Convert a list of subscribers to a list of email addresses. """ emails = [] for s in subscribers: e = self.emailAddressFrom(s) if e: emails.append(e) return emails def usernamesFrom(self,subscriber): """ Convert subscriber to username(s) if needed and return as a list. Ie if subscriber is a username, return that username; if subscriber is an email address, return the usernames of any CMF members with that email address. """ if self.isUsername(subscriber): return [subscriber] else: email = string.lower(subscriber) usernames = [] folder = self.folder() try: for user in folder.portal_membership.listMembers(): member = folder.portal_memberdata.wrapUser(user) if string.lower(member.email) == email: usernames.append(member.name) except AttributeError: pass return usernames def sendMailToSubscribers(self, text, REQUEST, subjectSuffix='', subject=''): """ Send mail to page subscribers. If a mailhost and mail_from property have been configured and there are subscribers to this page, email text to them. So as not to prevent page edits, catch any mail-sending errors (and log and try to forward them to an admin). """ recipients = self.emailAddressesFrom(self.allSubscribers()) if not recipients: return try: self.sendMailTo(recipients,text,REQUEST,subjectSuffix,subject) except: type, val, tb = sys.exc_info() err = string.join(traceback.format_exception(type,val,tb),'') DLOG('failed to send mail to %s: %s' % (recipients,err)) admin = getattr(self.folder(),'mail_admin',None) if admin: try: self.sendMailTo( [admin],text or '(no text)',REQUEST, subjectSuffix='ERROR, subscriber mailout failed') except: type, val, tb = sys.exc_info() err = string.join( traceback.format_exception(type,val,tb),'') DLOG('failed to send error report to admin: %s' % err) def sendMailTo(self, recipients, text, REQUEST, subjectSuffix='', subject=''): """ Send mail to the specified recipients. If a mailhost and mail_from property have been configured, attempt to email text to recipients. """ if not self.isMailoutEnabled(): return # ugLy temp hack # strip out the message heading typically prepended on *Discussion # pages mailouttext = self.formatMailout(text) # gather bits and pieces mhost=self.MailHost username = self.zwiki_username_or_ip(REQUEST) mail_from = getattr(self.folder(),'mail_from','') mail_replyto = getattr(self.folder(),'mail_replyto','') replytohdr = mail_replyto or mail_from listid = mail_from # primary recipient, alternatives: # 1. "To: ;" causes messy cc header in replies, while # 2. "To: replytohdr" sends a copy back to the wiki which # may be the cause of intermittent slow comments # XXX for now use 2 & allow it to be overridden tohdr = getattr(self.folder(),'mail_to','') or replytohdr if mail_from: if (not re.match(r'[0-9\.\s]*$',username) and not self.isEmailAddress(username)): fromhdr = '%s (%s)' % (mail_from,username) else: fromhdr = mail_from else: if self.isEmailAddress(username): fromhdr = username elif re.match(r'[0-9\.\s]*$',username): fromhdr = mail_replyto else: fromhdr = '%s (%s)' % (mail_replyto,username) pageurl = self.page_url() if getattr(self.folder(),'mail_page_name',1): pagename = '[%s] ' % self.title_or_id() else: pagename = '' signature = getattr(self.folder(), 'mail_signature', '--\nforwarded from %s' % pageurl) # if using mailin.py's checkrecipient option you can uncomment # this line to set the page name as Reply-to's real name: #replytohdr = replytohdr + ' (%s)'%self.title_or_id() # send message - XXX templatize this msg = """\ From: %s Reply-To: %s To: %s Bcc: %s Subject: %s X-BeenThere: %s X-Zwiki-Version: %s Precedence: bulk List-Id: %s <%s> List-Post: List-Subscribe: <%s/subscribeform> List-Unsubscribe: <%s/subscribeform> List-Archive: <%s> List-Help: <%s> %s %s """ \ % (fromhdr, replytohdr, tohdr, join(filter(lambda x:strip(x)!='', recipients), ', '), join([strip(getattr(self.folder(),'mail_subject_prefix', '')), #getattr(self.folder(),'title'))), #strip(self.id()), pagename, subject, strip(subjectSuffix)],''), listid, self.zwiki_version(), self.folder().title, listid, listid, pageurl, pageurl, pageurl, self.wiki_url(), mailouttext, signature, ) # testing support - messages with subject [test] or originating # from TestPage are not mailed out to subscribers if (subject == '[test]' or self.title_or_id() == 'TestPage'): # log and drop it DLOG('discarding test mailout:\n%s' % msg) # I tried redirecting to a test SMTP server but it blocked #DLOG('diverting test mailout to test server:\n%s' % msg) #try: # self.TestMailHost.send(msg) # DLOG('sent mailout to test server') # DLOG('TestMailHost info:', # self.TestMailHost.smtp_host, # self.TestMailHost.smtp_port) #except: # type, val, tb = sys.exc_info() # err = string.join( # traceback.format_exception(type,val,tb),'') # DLOG('failed to send mailout to test server:', # err, # self.TestMailHost.smtp_host, # self.TestMailHost.smtp_port) # instead, I sent as usual and hacked mailman to drop it # do add a X-No-Archive header #msg = re.sub(r'(?m)(List-Help:.*$)', # r'\1\nX-No-Archive: yes', # msg) #DLOG('sending mailout:\n%s' % msg) #mhost.send(msg) else: DLOG('sending mailout:\n%s' % msg) mhost.send(msg) def formatMailout(self, text): """ Format some text (usually a page diff) for email delivery. See testCommentFormatting/test_formatMailout for what's really going on here. """ if not text: return '' # strip zwiki comment heading and citation formatting, if present text = re.sub(r'(?i)(]*?>)?.*?
\n',r'',text) text = re.sub(r'(?sm)^
>(.*?)',r'>\1',text) # wrap (but don't fill or pad) long lines # ideally would like to fill each paragraph like emacs' fill-paragraph formatter = TextFormatter.TextFormatter(( {'width':78, 'fill':0, 'pad':0}, )) # work around textformatter foibles savenl = '' if text[0] == '\n' and text[1] != '\n': savenl = '\n'; text = text[1:] text=formatter.compose((text,)) text = savenl + text # textformatter chops off a trailing newline.. since textDiff adds # one, and we are usually formatting its output, just leave it be # strip leading newlines text = re.sub(r'(?s)^\n+',r'',text) # strip trailing newlines text = re.sub(r'(?s)\n+$',r'\n',text) # lose any html quoting text = html_unquote(text) return text ZWiki/Parents.py0100644000175000017500000005573307651060776013171 0ustar simonsimon###################################################################### # zwiki page parenting functionality import string from string import join from types import * from urllib import quote import Acquisition import Permissions import Globals from AccessControl import ClassSecurityInfo from Utils import flatten ########################################################################### # CLASS ZwikiParentsMixin # RESPONSIBILITIES help generate table of contents # COLLABORATORS ZWikiPage ########################################################################### class ParentsSupport: """ This mix-in class encapsulates ZWikiPage's page parenting functionality. I had to leave several related code fragments over in ZWikiPage.py but I think it's worth doing. """ ###################################################################### # CLASS VARIABLES ###################################################################### parents = [] _properties=( {'id':'parents', 'type': 'lines', 'mode': 'w'}, ) security = ClassSecurityInfo() ###################################################################### # METHOD CATEGORY: parenting ###################################################################### security.declareProtected(Permissions.Reparent, 'reparent') def reparent(self, parents=None, REQUEST=None, pagename=None): """ Reset parents property according to request. parents can also be passed in the pagename option. This is for the page management panel - it can't pass a parents argument because of the parents attribute. """ #XXX require at least a cookie like rename/delete ? #not yet.. fix IssueNo0010 and maybe reparent is harmless enough #if not (self._checkPermission(Permissions.Reparent, self) and # self.requestHasSomeId(REQUEST)): # raise 'Unauthorized', ( # _('You are not authorized to reparent this ZWiki Page.')) if pagename: parents = pagename if parents is None: parents = REQUEST.get('parents', None) if parents is None: self.parents = [] else: if type(parents) != ListType: parents = string.split(parents) self.parents = parents #self._setLastEditor(REQUEST) if REQUEST is not None: REQUEST.RESPONSE.redirect(REQUEST['URL1']) security.declareProtected(Permissions.View, 'get_ancestors') def get_ancestors(self): """Return a trimmed nesting structure indicating this page's ancestors. See the WikiNesting docstring for nestings structure description.""" container = self.folder() ancestors = {} offspring = {} tops = {} # Ancestors that have no parents. todo = {self.title_or_id(): None} while todo: doing = todo todo = {} for i in doing.keys(): if ancestors.has_key(i): continue # We've already collected this one. else: pagewiththisname = None if not hasattr(container, i): pagewiththisname = self.pageWithName(i) if not pagewiththisname: continue # Absent - ignore it. ancestors[i] = None #cf IssueNo0108 #obj = container[i] obj = pagewiththisname or getattr(container,i) if not hasattr(obj, 'parents'): continue parents = obj.parents if type(parents) != ListType: # SKWM parents = [] if parents: for p in parents: if offspring.has_key(p): offspring[p].append(i) else: offspring[p] = [i] todo[p] = None else: tops[i] = None # Ok, now go back down, unravelling each forebear only once: tops = tops.keys() tops.sort did = {}; got = [] for t in tops: got.append(descend_ancestors(t, ancestors, did, offspring)) return got security.declareProtected(Permissions.View, 'context') def context(self, REQUEST=None, with_siblings=0, enlarge_current=0): """Return HTML showing this page's parents and siblings.""" myid = self.title_or_id() if with_siblings: nesting = WikiNesting(self.folder()).get_up_and_back(myid) else: nesting = self.get_ancestors() if (len(nesting) == 0 or (len(nesting) == 1 and len(nesting[0]) == 1)) and not enlarge_current: return " " #SKWM XXX if self.folder().title: #titletxt = ' for %s' % (self.folder().title) titletxt = '%s ' % (self.folder().title) else: titletxt = '' #SKWM XXX #special case: if we are creating a new page, display it's title if REQUEST.has_key('page') and REQUEST['page'] is not myid: myid = REQUEST['page'] nesting = self.deepappend(nesting, myid) suppress_hyperlink=1 else: suppress_hyperlink=0 hierarchy = self.present_nesting(myid, nesting, self.wiki_url(), enlarge_current=enlarge_current, suppress_hyperlink=suppress_hyperlink) #SKWM XXX #detect when parents have gotten confused & reset if necessary if hierarchy == '
    \n
': self.parents = [] hierarchy = self.present_nesting(myid, nesting, self.wiki_url(), enlarge_current=enlarge_current, suppress_hyperlink=suppress_hyperlink) return '
    %s contents\n' % \ (self.page_url(), quote(self.title_or_id()), titletxt) + \ hierarchy + '\n
' security.declareProtected(Permissions.View, 'deepappend') def deepappend(self, nesting, pageid): """append a page to the very bottom of a nesting.""" if type(nesting[-1]) is type([]): nesting[-1] = self.deepappend(nesting[-1], pageid) return nesting else: if len(nesting) is 1: nesting.append(pageid) else: nesting[-1] = [nesting[-1],pageid] return nesting security.declareProtected(Permissions.View, 'offspring') def offspring(self, REQUEST=None): """Return a presentation of all my offspring.""" myid = self.title_or_id() nesting = WikiNesting(self.folder()) return self.present_nesting(myid, nesting.get_offspring([myid]), self.wiki_url()) # SKWM security.declareProtected(Permissions.View, 'offspringAsList') def offspringAsList(self, REQUEST=None): """ Return all my offspring's page names as a flat list. """ myid = self.title_or_id() nesting = WikiNesting(self.folder()) list = flatten(nesting.get_offspring([myid])) list.remove(myid) return list security.declareProtected(Permissions.View, 'offspringIdsAsList') def offspringIdsAsList(self, REQUEST=None): """ Return all my offspring's page ids as a flat list. """ #return map(lambda x:self.pageWithNameOrId(x).id(), # self.offspringAsList()) # python 1.5 compatibility sucks, drop it soon ? list = self.offspringAsList() for i in range(len(list)): list[i] = self.pageWithNameOrId(list[i]).id() return list security.declareProtected(Permissions.View, 'ancestorsAsList') def ancestorsAsList(self, REQUEST=None): """ Return the names of all my ancestor pages as a flat list, eldest first. If there are multiple lines of ancestry, return only the first. """ try: return flatten(self.get_ancestors()[0])[:-1] except: # XXX temp return [] security.declareProtected(Permissions.View, 'contents') def contents(self, REQUEST=None): """Present the nesting layout of the entire wiki, showing: - All the independent nodes, ie those without parents or children, and - All the branches in the wiki - from the possibly multiple roots.""" import string map = WikiNesting(self.folder()).get_map() singletons = [] combos = [] rel = self.wiki_url() # SKWM for i in map: if type(i) == StringType: try: linktitle = self.folder()[i].linkTitle() except: linktitle = '' singletons.append(\ '%s'\ % (rel, self.canonicalIdFrom(i), quote(i), linktitle, i)) else: combos.append(i) return self.contentspage( self.present_nesting(self.title_or_id(), combos, rel), singletons, REQUEST=REQUEST) map = contents # backwards compatibility security.declareProtected(Permissions.View, 'present_nesting') def present_nesting(self, myid, nesting, rel, did=None, _got=None, \ indent="",enlarge_current=0,suppress_hyperlink=0): # aaaah! dooo something... """ Present an HTML outline of a nesting structure. _got is used internally and should not be passed in except when recursing. See WikiNesting docstring for nesting structure description. This was a function, but I want to have more context available. """ if did is None: did = [] if _got is None: _got = ["
    "] recursing = 0 else: recursing = 1 for n in nesting: if type(n) == ListType: if n[0] == myid: # The entry is the current node - distinguish it. if enlarge_current: nodenm = '%s' % n[0] if suppress_hyperlink: _got.append('%s
  • %s' % (indent, nodenm)) else: _got.append('%s
  • %s' % (indent, rel, self.canonicalIdFrom(n[0]), nodenm)) else: nodenm = "%s" % n[0] try: linktitle = self.folder()[n[0]].linkTitle() except: linktitle = '' _got.append(\ '%s
  • %s <-- You are here.' % (indent, rel, self.canonicalIdFrom(n[0]), quote(n[0]), linktitle, nodenm)) else: nodenm = n[0] try: linktitle = self.folder()[n[0]].linkTitle() except: linktitle = '' _got.append(\ '%s
  • %s' % (indent, rel, self.canonicalIdFrom(n[0]), quote(n[0]), linktitle, nodenm)) if len(n) > 1: _got.append("
      ") for i in n[1:]: if type(i) == ListType: _got = self.present_nesting(myid, [i], rel, did=did, _got=_got, indent=indent+' ', enlarge_current=enlarge_current, suppress_hyperlink=suppress_hyperlink) else: if i == myid: # Distinguish that entry is for current node. if enlarge_current: inm = '' + i + '' if suppress_hyperlink: _got.append('%s
    • %s' % (indent, inm)) else: _got.append('%s
    • %s' % (indent, rel, self.canonicalIdFrom(i), inm)) else: inm = "" + i + "" try: linktitle = self.folder()[i].linkTitle() except: linktitle = '' _got.append(\ '%s
    • %s <-- You are here.' % (indent, rel, self.canonicalIdFrom(i), quote(i), linktitle, inm)) else: inm = i try: linktitle = self.folder()[i].linkTitle() except: linktitle = '' _got.append(\ '%s
    • %s' % (indent, rel, self.canonicalIdFrom(i), quote(i), linktitle, inm)) _got.append("
    ") else: # Parents whose children were omitted - indicate: _got[-1] = _got[-1] + " ..." else: if n == myid: # The entry is for the current node - distinguish. if enlarge_current: inm = '' + n + '' if suppress_hyperlink: _got.append('%s
  • %s' % (indent, inm)) else: _got.append('%s
  • %s' % (indent, rel, self.canonicalIdFrom(n), inm)) else: inm = "" + n + "" try: linktitle = self.folder()[n].linkTitle() except: linktitle = '' _got.append(\ '%s
  • %s <-- You are here.' % (indent, rel, self.canonicalIdFrom(n), quote(n), linktitle, inm)) else: inm = n try: linktitle = self.folder()[n].linkTitle() except: linktitle = '' _got.append(\ '%s
  • %s' % (indent, rel, self.canonicalIdFrom(n), quote(n), linktitle, inm)) if recursing: return _got else: _got.append("
") return join(_got, "\n") Globals.InitializeClass(ParentsSupport) # install permissions ########################################################################### # CLASS WikiNesting # RESPONSIBILITIES # COLLABORATORS ########################################################################### class WikiNesting(Acquisition.Implicit): """Given a wiki directory, generate nesting relationship from parents info. In a nesting, nodes are represented as: - Leaves: the string name of the page - Nodes with children: a list beginning with the parent node's name - Nodes with omitted children (for brevity): list with one string. """ # XXX We could make this a persistent object and minimize recomputes. # Put it in a standard place in the wiki's folder, or have the # wikis in a folder share an instance, but use a single # persistent one which need not recompute all the relationship # maps every time - just needs to compare all pages parents # settings with the last noticed parents settings, and adjust # the children, roots, and parents maps just for those that # changed. On this first cut we just recompute it all... ###################################################################### # METHOD CATEGORY: misc ###################################################################### def __init__(self, container): self.container = container self.set_nesting() def set_nesting(self): """Preprocess for easy derivation of nesting structure. We set: - .parentmap: {'node1': ['parent1', ...], ...} - .childmap: {'node1': ['child1', ...], ...} - .roots: {'root1': None, ...}""" pages = self.container.objectValues(spec='ZWiki Page') # XXX optimize pagenames = [] parentmap = {} # 'node': ['parent1', ...] childmap = {} # 'node': ['child1', ...] roots = {} for pg in pages: parents = pg.parents if type(parents) != ListType: # SKWM parents = [] pgnm = pg.title_or_id() pagenames.append(pgnm) # Parents is direct: parentmap[pgnm] = parents[:] # We have a root if node acknowledges no parents: if not parents: roots[pgnm] = None if not childmap.has_key(pgnm): childmap[pgnm] = [] # Register page as child for all its parents: for p in parents: # We can't just append, in case we're a persistent object # (which recognizes the need to update by an attr assignment). if childmap.has_key(p): pchildren = childmap[p] else: pchildren = [] if pgnm not in pchildren: pchildren.append(pgnm) childmap[p] = pchildren for k in parentmap.keys(): parentmap[k].sort() for k in childmap.keys(): childmap[k].sort() self.parentmap = parentmap self.childmap = childmap self.roots = roots def get_map(self): """Return nesting of entire wiki.""" did = {} got = [] roots = self.roots.keys() roots.sort() return self.get_offspring(roots) def get_offspring(self, pages, did=None): """Return nesting showing all offspring of a list of wiki pagenames. did is used for recursion, to prune already elaborated pages.""" if did is None: did = {} got = [] for p in pages: been_there = did.has_key(p) did[p] = None if self.childmap.has_key(p): children = self.childmap[p] if children: subgot = [p] if not been_there: subgot.extend(self.get_offspring(children, did=did)) got.append(subgot) else: got.append(p) else: got.append(p) return got def get_up_and_back(self, pagename): """Return nesting showing page containment and immediate offspring.""" # Go up, identifying all and topmost forbears: ancestors = {} # Ancestors of pagename tops = {} # Ancestors that have no parents todo = {pagename: None} parentmap = self.parentmap while todo: doing = todo todo = {} for i in doing.keys(): if ancestors.has_key(i): continue # We already took care of this one. else: ancestors[i] = None if parentmap.has_key(i): parents = parentmap[i] if parents: for p in parents: todo[p] = None else: tops[i] = None ancestors[pagename] = None # Finesse inclusion of page's offspring # Ok, now go back down, showing offspring of all intervening ancestors: tops = tops.keys() tops.sort did = {}; got = [] childmap = self.childmap for t in tops: got.append(descend_ancestors(t, ancestors, did, childmap)) return got ###################################################################### # FUNCTION CATEGORY: parenting helper functions ###################################################################### def descend_ancestors(page, ancestors, did, children): """Create nesting of ancestors leading to page. page is the name of the subject page. ancestors is a mapping whose keys are pages that are ancestors of page children is a mapping whose keys are pages children, and the values are the children's parents. Do not repeat ones we already did.""" got = [] for c in ((children.has_key(page) and children[page]) or []): if not ancestors.has_key(c): # We don't descend offspring that are not ancestors. got.append(c) elif ((children.has_key(c) and children[c]) or []): if did.has_key(c): # We only show offspring of ancestors once... got.append([c]) else: # ... and this is the first time. did[c] = None got.append(descend_ancestors(c, ancestors, did, children)) else: got.append(c) got.sort() # Terminals will come before composites. got.insert(0, page) return got ZWiki/Permissions.py0100644000175000017500000000310107574142342014040 0ustar simonsimon###################################################################### # ZWiki permissions Upload = 'Add Documents, Images, and Files' #zope's Create = Add = 'Add ZWiki Pages' # these two are created by zope based on AddWiki = 'Add ZWiki Webs' # meta_types in __init__.. keep in sync # ok to use them for CMF too ? FTPRead = FTP = 'FTP access' #zope's Comment = Append = 'Zwiki: Add comments to pages' ChangeType = 'Zwiki: Change page types' Regulate = ChangeRegs = 'Zwiki: Change regulations' Delete = 'Zwiki: Delete pages' Edit = Change = 'Zwiki: Edit pages' Rename = 'Zwiki: Rename pages' Reparent = 'Zwiki: Reparent pages' from AccessControl import Permissions View = Permissions.view # we assume this is good for CMF too #for reference: # # in 0.9.9: #Add = 'Add ZWiki Pages' #AddWiki = 'Add ZWiki Webs' #Append = 'Zwiki: Add comments to pages' #ChangeType = 'Zwiki: Change page types' #Change = 'Zwiki: Edit pages' #Reparent = 'Zwiki: Reparent pages' #Delete = 'Zwiki: Recycle pages' #ChangeRegs = 'Zwiki: Change regulations' #Upload = 'Add Documents, Images, and Files' # existing permission # # in 0.9.8: #Add = 'Add ZWiki Pages' #AddWiki = 'Add ZWikiwebs' #Append = 'Append to ZWiki Pages' #ChangeType = 'Change ZWiki Page Types' #Change = 'Change ZWiki Pages' #Reparent = 'Reparent ZWiki Pages' #Delete = 'Send ZWiki Pages to Recycle Bin' #Upload = 'Add Documents, Images, and Files' ZWiki/README.txt0100644000175000017500000000101207532351507012647 0ustar simonsimonThis zope product allows you to build wiki webs in zope. To install: 1. unpack ZWiki-x.x.x.tgz in your zope Products directory 2. restart zope 3. in the ZMI, add a ZWiki Web. For documentation and assistance, please see http://zwiki.org, or post on http://zwiki.org/GeneralDiscussion. All feedback, bug reports and other help appreciated. (c) 1999,2000,2001,2002 Simon Michael for the zwiki community. This product is released under the GNU GPL. All rights reserved, all disclaimers apply, etc. ZWiki/Regexps.py0100644000175000017500000001313607544233211013144 0ustar simonsimon###################################################################### # regular expressions used by ZWiki import re, string import Defaults # URLs/URIs (better regexps in urllib/urlparse ?) urlchars = r'[A-Za-z0-9/:@_%~#=&\.\-\?\+\$,]+' urlendchar = r'[A-Za-z0-9/]' url = r'["=]?((about|gopher|http|https|ftp|mailto|file):%s)' % \ (urlchars) # valid characters for zwiki page ids # These are the characters which are used to form safe page ids for both # free-form names and wiki names. They are the characters legal in both # zope ids and urls, excluding _ which we use for quoting. (See # canonicalIdsFrom). # You have a choice here - # 1. Don't allow international characters in ids. zwikiidcharsexpr = re.compile(r'[a-zA-Z0-9.-]') # 2. Allow (single-byte) international characters in page ids. # You also need to hack zope's OFS.ObjectManager.bad_id, eg: ## bad_id = re.compile(r'[^\xC0-\xFFa-zA-Z0-9-_~,.$# ]').search # what's the thread-safety issue noted there ? # extract zopeidchars from bad_id - hacky: #from OFS.ObjectManager import bad_id #try: # zopeidchars = re.sub(r'\^',r'',bad_id.__self__.pattern) # zopeidchars = re.sub(r'\\\(',r'(',zopeidchars) # zopeidchars = re.sub(r'\\\)',r')',zopeidchars) #except AttributeError: # # older zope uses ts_regex # zopeidchars = re.sub(r'\^',r'',bad_id.im_self.givenpat) # zopeidchars = re.sub(r'\\\,',r',',zopeidchars) # zopeidchars = re.sub(r'\\\.',r'.',zopeidchars) #zwikiidcharsexpr = re.compile(re.sub(r'[_~,$()# ]',r'',zopeidchars)) # also used in generating page ids # XXX NB this is affected by locale - may not be what we want spaceandlowerexpr = re.compile(r'\s+([%s])'%(string.lowercase)) # zwiki free-form links # zwiki uses [...] to define free-form page names. These can be almost # anything (on a single line). # group 1 should be what's inside the brackets bracketedexpr = r'\[([^\n\]]+)\]' # zwiki wiki links # Zwiki's WikiNames allow words of a single letter (APage, PageA) and # trailing digits (PageVersion002). # # WikiNames can also contain (single-byte) international characters, # usually depending on your locale. The code below tries to enable them # out of the box for most setups. You can set a specific (installed) # locale here with ZWIKI_LOCALE, or leave it as None to use the default # system locale. If no particular locale appears to be set, we hard-code # the characters and emulate the \b pattern. In this case word boundaries # may not be recognized quite as well. # Not hugely tested. Please report problems and successes at # http://zwiki.org/ZwikiTracker and # http://zwiki.org/InternationalCharactersInPageNames . ZWIKI_LOCALE = None import locale if ZWIKI_LOCALE: loc = locale.setlocale(locale.LC_ALL, ZWIKI_LOCALE) elif hasattr(locale,'getdefaultlocale'): loc = locale.getdefaultlocale() else: loc = (None,None) if loc != (None,None): U = string.uppercase L = string.lowercase wikiname1 = r'(?L)\b[%s]+[%s]+[%s][%s]*[0-9]*' % (U,L,U,U+L) wikiname2 = r'(?L)\b[%s][%s]+[%s][%s]*[0-9]*' % (U,U,L,U+L) else: U = 'A-Z\xc0-\xdf' L = 'a-z\xe0-\xff' b = '(?%s):(?P%s))' % \ (localwikilink, urlchars+urlendchar) anywikilinkexpr = re.compile(r'(%s|%s)' % (interwikilink,wikilink)) untitledwikilinkexpr = \ re.compile(r'[^/"]*)" title="">.*?') remotewikiurl = r'(?mi)RemoteWikiURL[:\s]*([^\s]+)\s*$' protected_line = r'(?m)^!(.*)$' # stx footnotes # handled by us so as to co-exist with our bracketed links # real stx allows refchars = r'[0-9_%s-]' % (string.letters) footnoteexpr = r'(?sm)^\.\. \[([^\n\]]+)\]' # for stripping javascript # XXX needs work, eg should not match # javascriptexpr = r'(?iL)<([^>\w]*script[^>]*)>' # \1 will be displayed # for stripping HTML header/footer # XXX strip these from the middle of pages too ? htmlheaderexpr = r'(?si)^(\s*' htmlfooterexpr = r'(?si)\s*\s*$' # sgml tags, including those containing dtml/python and multi-line # XXX needs more work, does not match all tags # # one badass regexp # #r'(?s)<((".*?")|[^">]+)*>' # takes exponential time #r'(?s)<((".*?")|[^">]+(?![^">]))*>' # avoids backtracking (see perlre) # to avoid matching casual angle bracket use, treat dtml separately # recognizing that stuff like
bgcolor="#ff2222" bgcolor="#ff6060" bgcolor="#ffbbbb" bgcolor="#ffdddd" bgcolor="#ffe0e0" bgcolor="#ffe0e0" bgcolor="#ffcc77" bgcolor="#e0f0e0" bgcolor="#e0e0e0" >
**Description:** " size="50" maxlength="200" style="font-weight:bold"> << ^^ >>
**Category:** **Severity:** **Status:**
Optional note:  
**Submitted by:** **at:** (unknown, > year 1 and 's' or ''"> month 1 and 's' or ''"> week 1 and 's' or ''"> day 1 and 's' or ''"> hour 1 and 's' or ''"> minute 1 and 's' or ''"> second 1 and 's' or ''"> ago)
**Details & comments:**
''') class TrackerSupport: """ This mix-in class adds some methods to ZWikiPage to support the zwiki tracker (see http://zwiki.org/ZwikiTracker) """ security = ClassSecurityInfo() security.declareProtected(Permissions.View, 'supportsIssueProperties') def supportsIssueProperties(self): """Does this page support issue properties management ?""" return re.search(r'(?i)issue',self.page_type) is not None security.declareProtected(Permissions.View, 'isIssue') def isIssue(self,client=None,REQUEST=None,RESPONSE=None,**kw): """ Return true if this page is a tracker issue. In the past pages with a special page type were issues. Now, any page named "IssueNo.." is an issue. (and, whose type supports issue properties ? No never mind that) Flexibility will be useful here so this method may be overridden with a DTML method (XXX or python script). """ if hasattr(self.folder(), 'isIssue'): return self.folder().isIssue(self,REQUEST) else: if (re.match(r'^IssueNo',self.title_or_id()) or self.page_type == 'issuedtml'): # backwards compatibility return 1 else: return 0 def render_issuedtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ Render an "issue" page. This is an ordinary zwiki page with some extra properties appropriate for a bug/issue. These are displayed above the main text of the page in a sort of trouble ticket layout and can be edited. based on render_stxdtmllinkhtml deprecated - this is now supported in stxprelinkdtmlfitissuehtml """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = self.applyLineEscapesIn(t) t = self.stxToHtml(t) self._prerendered = t or '\n' self.cook() if kw.get('pre_only',0): return # final render # dtml-evaluate both form and main page t = self.stxToHtml(apply(ISSUE_FORM.__call__,(self, REQUEST),kw)) + \ apply(DTMLDocument.__call__,(self, client, REQUEST, RESPONSE), kw) t = self.renderLinksIn(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t def createIssue(self, pageid, text=None, title='', category=None, severity=None, status=None, REQUEST=None): """ Convenience method for creating a ZwikiTracker issue page. Security notes: create will check for page creation permission. Sets title/category/severity/status properties without requiring Manage properties permission. As of 0.17, issue pages are named "IssueNoNNNN issue description". These arguments should be cleaned up to reflect that, eg title should come from pageid and should be called description. pageid should be called pagename. Also issues are now "ordinary" stxprelinkdtmlfitissuehtml pages. That page type is hardcoded here, if we add issue support to others we'd want to inherit from the parent page. We should be able to do without this method. """ self.create(pageid,text=text,REQUEST=REQUEST) issue = self.pageWithName(pageid) issue.manage_addProperty('category','issue_categories','selection') issue.manage_addProperty('severity','issue_severities','selection') issue.manage_addProperty('status','issue_statuses','selection') issue.manage_changeProperties(page_type='stxprelinkdtmlfitissuehtml', title=title, category=category, severity=severity, status=status ) self.reindex_object() #def changeProperties(self, REQUEST=None, **kw): # """ # Similar to manage_changeProperties, except redirects back to the # current page. Also restores the issue number which we previously # stripped from title. # # security issue: bypasses Manage properties permission # # Deprecated, useful for backwards compatibility or remove ? # """ # if REQUEST is None: # props={} # else: props=REQUEST # if kw: # for name, value in kw.items(): # props[name]=value # props['title'] = self.getId()[:11]+' '+props['title'] # propdict=self.propdict() # for name, value in props.items(): # if self.hasProperty(name): # if not 'w' in propdict[name].get('mode', 'wd'): # raise 'BadRequest', '%s cannot be changed' % name # self._updateProperty(name, value) # # self._setLastEditor(REQUEST) # self.reindex_object() # if REQUEST: # REQUEST.RESPONSE.redirect(self.page_url()) def changeIssueProperties(self, title=None, category=None, severity=None, status=None, log=None, REQUEST=None): """ Change an issue page's properties and redirect back there. Also, add a comment to the page describing what was done. It expects title to be the issue description, not the complete page name. Changing this will trigger a page rename, which may be slow. XXX security: allows title/category/severity/status properties to be set without Manage properties permission. XXX upgrade issue: calling this before upgrading an issue to a 0.17-style page id will mess up the id/title. """ comment = '' if title: title = self.getId()[:11]+' '+title if title != self.title_or_id(): comment += "Title: '%s' => '%s' \n" % (self.title_or_id(),title) self.rename(title,updatebacklinks=1,sendmail=0,REQUEST=REQUEST) if category: if category != self.category: comment += "Category: %s => %s \n" % (self.category,category) self.manage_changeProperties(category=category) if severity: if severity != self.severity: comment += "Severity: %s => %s \n" % (self.severity,severity) self.manage_changeProperties(severity=severity) if status: if status != self.status: comment += "Status: %s => %s \n" % (self.status,status) self.manage_changeProperties(status=status) log = log or 'property change' self.comment(text=comment, use_heading=1, subject_heading=log, REQUEST=REQUEST) self._setLastEditor(REQUEST) self.reindex_object() if REQUEST: REQUEST.RESPONSE.redirect(self.page_url()) def category_index(self): """helper method to facilitate sorting catalog results""" try: return 1 + self.issue_categories.index(self.category) except (AttributeError,ValueError): return 0 def severity_index(self): """helper method to facilitate sorting catalog results""" try: return 1 + self.issue_severities.index(self.severity) except (AttributeError,ValueError): return 0 def status_index(self): """helper method to facilitate sorting catalog results""" try: return 1 + self.issue_statuses.index(self.status) except (AttributeError,ValueError): return 0 ZWiki/UI.py0100644000175000017500000002476407654237706012074 0ustar simonsimon# UI-related methods import os, sys, re, string, time, math, traceback import string from string import split,join,find,lower,rfind,atoi,strip from Utils import DLOG from Defaults import DEFAULT_PAGE_TYPE, DISABLE_JAVASCRIPT, \ LARGE_FILE_SIZE, AUTO_UPGRADE, \ default_standard_wiki_header, default_standard_wiki_footer, \ default_editform, default_backlinks, default_subscribeform, \ default_wikipage, default_contentspage, default_diffform from AccessControl import getSecurityManager, ClassSecurityInfo import Permissions from LocalizerSupport import LocalDTMLFile, _, N_ from Globals import MessageDialog class UI: """ Holds most of ZWikiPage's UI-related methods. """ security = ClassSecurityInfo() # methods to generate standard forms & dialogs; most of these can be # overridden by a DTML method of the same name. # Return a placeholder if there is an error; useful if someone's # custom header has broken or if we are running tests. # XXX REQUEST & authentication not passed to page templates ? # make this a page method so tests/support.py can override it # and perhaps flesh it out later security.declareProtected(Permissions.View, 'wikipage') wikipage = default_wikipage security.declareProtected(Permissions.View, 'standard_wiki_header') def standard_wiki_header(self,client=None,REQUEST=None,RESPONSE=None,**kw): """ Return a default or custom wiki page header. May be overridden by a DTML method of the same name. """ try: if hasattr(self.folder(), 'standard_wiki_header'): return self.folder().standard_wiki_header(self,REQUEST) else: return default_standard_wiki_header(self,self,REQUEST) except: type, val, tb = sys.exc_info() err = string.join(traceback.format_exception(type,val,tb),'') return '\n\ncould not render this.\n\n'\ % err security.declareProtected(Permissions.View, 'standard_wiki_footer') def standard_wiki_footer(self,client=None,REQUEST=None,RESPONSE=None,**kw): """ Return a default or custom wiki page footer. May be overridden by a DTML method of the same name. """ try: if hasattr(self.folder(), 'standard_wiki_footer'): return self.folder().standard_wiki_footer(self,REQUEST) else: return default_standard_wiki_footer(self,self,REQUEST) except: type, val, tb = sys.exc_info() err = string.join(traceback.format_exception(type,val,tb),'') return 'could not render this.\n\n\n\n'\ % err security.declareProtected(Permissions.View, 'backlinks') def backlinks(self, REQUEST=None): """ Display a default or custom backlinks page. May be overridden by a page template or DTML method of the same name. """ form = getattr(self.folder(),'backlinks',default_backlinks) type = getattr(form,'meta_type','DEFAULT') if type in ('Page Template','Filesystem Page Template'): return apply(form.__of__(self),(self,REQUEST),{}) elif type == 'DTML Method': return form(self,REQUEST) elif type == 'DEFAULT': return form(self,self,REQUEST) # defaults are HTMLFiles else: return 'Bad form type - use a page template or dtml method.' security.declareProtected(Permissions.View, 'contentspage') def contentspage(self, hierarchy, singletons, REQUEST=None): """ Display a default or custom contents page. May be overridden by a page template or DTML method of the same name. Adds hierarchy and singletons to the template namespace for rendering. """ form = getattr(self.folder(),'contentspage',default_contentspage) type = getattr(form,'meta_type','DEFAULT') if type in ('Page Template','Filesystem Page Template'): return apply(form.__of__(self),(self,REQUEST), {'hierarchy':hierarchy,'singletons':singletons}) elif type == 'DTML Method': return form(self,REQUEST, getattr(REQUEST,'RESPONSE',None), #need this ? hierarchy=hierarchy,singletons=singletons) elif type == 'DEFAULT': return form(self,self,REQUEST, # defaults are HTMLFiles hierarchy=hierarchy,singletons=singletons) else: return 'Bad form type - use a page template or dtml method.' security.declareProtected(Permissions.View, 'diffform') def diffform(self, revA, difftext, REQUEST=None): """ Display a default or custom contents page. May be overridden by a page template or DTML method of the same name. Adds hierarchy and singletons to the template namespace for rendering. """ form = getattr(self.folder(),'diffform',default_diffform) type = getattr(form,'meta_type','DEFAULT') if type in ('Page Template','Filesystem Page Template'): return apply(form.__of__(self),(self,REQUEST), {'revA':revA,'difftext':difftext}) elif type == 'DTML Method': return form(self,REQUEST, revA=revA,difftext=difftext) elif type == 'DEFAULT': return form(self,self,REQUEST, # defaults are HTMLFiles revA=revA,difftext=difftext) else: return 'Bad form type - use a page template or dtml method.' security.declareProtected(Permissions.Change, 'editform') def editform(self, REQUEST=None, page=None, text=None, action='Change'): """ Display a default or custom form to edit or create the specified page. For new pages, initial text may be specified. May be overridden by a page template or DTML method of the same name. """ if hasattr(self,'wl_isLocked') and self.wl_isLocked(): return self.davLockDialog() # what are we going to do ? set up page, text & action accordingly if page is None: # no page specified - editing the current page page = self.title_or_id() text = self.read() #elif hasattr(self.folder(), page): elif self.pageWithName(page): # editing a different page text = self.pageWithName(page).read() else: # editing a brand-new page action = 'Create' text = '' # 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 ? # no way to restrict /editform currently form = getattr(self.folder(),'editform',default_editform) type = getattr(form,'meta_type','DEFAULT') if type in ('Page Template','Filesystem Page Template'): return apply(form.__of__(self),(self,REQUEST), {'page':page,'text':text,'action':action, 'id':page,'oldid':self.getId()}) elif type == 'DTML Method': return form(self,REQUEST, page=page,text=text,action=action, id=page,oldid=self.id()) elif type == 'DEFAULT': return form(self,self,REQUEST, # defaults are HTMLFiles page=page,text=text,action=action, id=page,oldid=self.id()) else: return 'Bad form type - use a page template or dtml method.' security.declareProtected(Permissions.View, 'subscribeform') def subscribeform(self, REQUEST=None): """ Display a default or custom mail subscription form. May be overridden by a page template or DTML method of the same name. """ form = getattr(self.folder(),'subscribeform',default_subscribeform) type = getattr(form,'meta_type','DEFAULT') if type in ('Page Template','Filesystem Page Template'): return apply(form.__of__(self),(self,REQUEST),{}) elif type == 'DTML Method': return form(self,REQUEST) elif type == 'DEFAULT': return form(self,self,REQUEST) # defaults are HTMLFiles else: return 'Bad form type - use a page template or dtml method.' security.declareProtected(Permissions.View, 'editConflictDialog') def editConflictDialog(self): """ web page displayed in edit conflict situations. """ titlestr=_('Edit conflict') return MessageDialog( title=titlestr, message=""" %s

%s. %s:

  1. %s
  2. %s
  3. %s
  4. %s
  5. %s.
%s,

%s. """ % ( titlestr, _("Someone else has saved this page while you were editing"), _("To resolve the conflict, do this"), _("Click your browser's back button"), _("Copy your recent edits to the clipboard"), _("Click your browser's refresh button"), _("Paste in your edits again, being mindful of the latest changes"), _("Click the Change button again"), _("or"), _("To discard your changes and start again, click OK"), ), action=self.page_url()+'/editform') security.declareProtected(Permissions.View, 'davLockDialog') def davLockDialog(self): """ web page displayed in webDAV lock conflict situations. """ titlestr=_('Page is locked') return MessageDialog( title=titlestr, message=""" %s

%s

%s """ % ( titlestr, _(""" This page has a webDAV lock. Someone is probably editing it with an external editor. You'll need to wait until they've finished and then try again. If you've just made some changes, you may want to back up and copy your version of the text for reference. """), _("To discard your changes and try again, click OK."), ), action=self.page_url()+'/editform') ZWiki/Utils.py0100644000175000017500000004541007644614521012637 0ustar simonsimon# misc utility functions from types import * from string import split,join,find,lower,rfind,atoi,strip import os, re from zLOG import LOG,ERROR,INFO,WARNING,BLATHER,DEBUG try: from StructuredText.DocumentClass import StructuredTextTable except ImportError: #older zope, won't be needing it pass try: v = open(os.path.join(SOFTWARE_HOME,'version.txt')).read() m = re.match(r'(?i)zope\s*([0-9]+)\.([0-9]+)\.([0-9]+)',v) ZOPEVERSION = (int(m.group(1)),int(m.group(2)),int(m.group(3))) except: # if we can't read version.txt, assume we're dealing with a # highly advanced specimen.. (cvs) ZOPEVERSION = (9,9,9) #DEBUGMODE = os.environ.get('Z_DEBUG_MODE',0) or \ # os.environ.get('ZWIKI_DEBUG',0) # just require STUPID_LOG_SEVERITY <= -100 to see these def DLOG(*args): tmp = [] for arg in args: tmp.append(str(arg)) LOG('ZWiki',BLATHER,' '.join(tmp)) # rendering helpers def thunk_substituter(func, text, allowed): """Return a function which takes one arg and passes it with other args to passed-in func. thunk_substituter passes in the value of it's parameter, 'allowed', and a dictionary {'lastend': int, 'inpre': bool, 'intag': bool}. This is for use in a re.sub situation, to get the 'allowed' parameter and the state dict into the callback. (The technical term really is "thunk". Honest.-)""" state = {'lastend':0,'inpre':0,'incode':0,'intag':0,'inanchor':0} return lambda arg, func=func, allowed=allowed, text=text, state=state: ( func(arg, allowed, state, text)) def within_literal(upto, after, state, text, rfind=rfind, lower=lower): """ Check text from state['lastend'] to upto for literal context: - within an enclosing '

' preformatted region '
' - within an enclosing '' code fragment '' - within a tag '<' body '>' - within an '' tag's contents '' We also update the state dict accordingly. """ # XXX This breaks on badly nested angle brackets and
, etc.
    lastend,inpre,incode,intag,inanchor = \
      state['lastend'], state['inpre'], state['incode'], state['intag'], \
      state['inanchor']
    newintag = newincode = newinpre = newinanchor = 0
    text = lower(text)
    # Check whether '
' is currently (possibly, still) prevailing.
    opening = rfind(text, '
', lastend, upto)
    if (opening != -1) or inpre:
        if opening != -1: opening = opening + 4
        else: opening = lastend
        if -1 == rfind(text, '
', opening, upto): newinpre = 1 state['inpre'] = newinpre # Check whether '' is currently (possibly, still) prevailing. opening = rfind(text, '', lastend, upto) if (opening != -1) or incode: if opening != -1: opening = opening + 5 # We must already be incode, start at beginning of this segment: else: opening = lastend if -1 == rfind(text, '', opening, upto): newincode = 1 state['incode'] = newincode # Determine whether we're (possibly, still) within a tag. opening = rfind(text, '<', lastend, upto) if (opening != -1) or intag: # May also be intag - either way, we skip past last : if opening != -1: opening = opening + 1 # We must already be intag, start at beginning of this segment: else: opening = lastend if -1 == rfind(text, '>', opening, upto): newintag = 1 state['intag'] = newintag # Check whether '' is currently (possibly, still) prevailing. #XXX make this more robust opening = rfind(text, '', opening, upto): newinanchor = 1 state['inanchor'] = newinanchor state['lastend'] = after return newinpre or newincode or newintag or newinanchor def withinSgmlOrDtml(span,text): """ report whether the span lies inside an sgml or dtml tag in text """ spans = sgmlAndDtmlSpansIn(text) for s in spans: if span[0] >= s[0] and span[1] <= s[1]: return 1 return 0 def sgmlAndDtmlSpansIn(text): """ return a list of spans (tuples) of all sgml and dtml tags in text """ pat = re.compile(dtmlorsgmlexpr) spans = [] lastpos = 0 while 1: m = pat.search(text,lastpos) if not m: break else: s = m.span() spans.append(s) lastpos = s[1] return spans #from DocumentTemplate.DT_Util import html_quote from cgi import escape def html_quote(s): return escape(str(s)) def html_unquote(s, character_entities=( (('&'), '&'), (('<'), '<' ), (('>'), '>' ), (('<'), '\213' ), (('>'), '\233' ), (('"'), '"'))): #" text=str(s) for re,name in character_entities: if find(text, re) >= 0: text=join(split(text,re),name) return text def flatten(seq): """ Translate a nested sequence into a flat list of string-terminals. We omit duplicates terminals in the process. """ got = [] pending = [seq] while pending: cur = pending.pop(0) if type(cur) == StringType: if cur not in got: got.append(cur) else: pending.extend(cur) return got # structured text customizations import StructuredText from Regexps import dtmlorsgmlexpr # override a couple of things in DocumentClass: class myDocumentClass(StructuredText.DocumentClass.DocumentClass): # 1. leave dtml alone def doc_sgml(self,s,expr=re.compile(dtmlorsgmlexpr).search): r = expr(s) if r: start,end = r.span() text = s[start:end] return (StructuredText.DocumentClass.StructuredTextSGML(text), start, end) # remove when ZopeIssue:432 is fixed text_types = [ 'doc_sgml', # make SGML (incl. DTML expressions) first priority 'doc_literal', 'doc_inner_link', 'doc_named_link', 'doc_href1', 'doc_href2', 'doc_strong', 'doc_emphasize', 'doc_underline', 'doc_sgml', 'doc_xref', ] # 2. allow + at table corners; makes emacs picture-mode editing easier def doc_table(self, paragraph, expr = re.compile(r'\s*[+|][-+]+[+|]').match): text = paragraph.getColorizableTexts()[0] m = expr(text) subs = paragraph.getSubparagraphs() if not (m): return None rows = [] spans = [] ROWS = [] COLS = [] indexes = [] ignore = [] TDdivider = re.compile(r'[-+]+').match THdivider = re.compile(r'[=+]+').match col = re.compile(r'\|').search innertable = re.compile(r'(?![-=])[+|]([-+]+|[=+]+)[+|](?![-=])').search text = strip(text) rows = split(text,'\n') foo = "" for row in range(len(rows)): rows[row] = strip(rows[row]) # have indexes store if a row is a divider # or a cell part for index in range(len(rows)): tmpstr = rows[index][1:len(rows[index])-1] if TDdivider(tmpstr): indexes.append("TDdivider") elif THdivider(tmpstr): indexes.append("THdivider") else: indexes.append("cell") for index in range(len(indexes)): if indexes[index] is "TDdivider" or indexes[index] is "THdivider": ignore = [] # reset ignore #continue # skip dividers tmp = strip(rows[index]) # clean the row up tmp = tmp[1:len(tmp)-1] # remove leading + trailing | offset = 0 # find the start and end of inner # tables. ignore everything between if innertable(tmp): tmpstr = strip(tmp) while innertable(tmpstr): start,end = innertable(tmpstr).span() if not (start,end-1) in ignore: ignore.append((start,end-1)) tmpstr = " " + tmpstr[end:] # find the location of column dividers # NOTE: |'s in inner tables do not count # as column dividers if col(tmp): while col(tmp): bar = 1 # true if start is not in ignore start,end = col(tmp).span() if not start+offset in spans: for s,e in ignore: if start+offset >= s or start+offset <= e: bar = None break if bar: # start is clean spans.append(start+offset) if not bar: foo = foo + tmp[:end] tmp = tmp[end:] offset = offset + end else: COLS.append((foo + tmp[0:start],start+offset)) foo = "" tmp = " " + tmp[end:] offset = offset + start if not offset+len(tmp) in spans: spans.append(offset+len(tmp)) COLS.append((foo + tmp,offset+len(tmp))) foo = "" ROWS.append(COLS) COLS = [] spans.sort() ROWS = ROWS[1:len(ROWS)] # find each column span cols = [] tmp = [] for row in ROWS: for c in row: tmp.append(c[1]) cols.append(tmp) tmp = [] cur = 1 tmp = [] C = [] for col in cols: for span in spans: if not span in col: cur = cur + 1 else: tmp.append(cur) cur = 1 C.append(tmp) tmp = [] for index in range(len(C)): for i in range(len(C[index])): ROWS[index][i] = (ROWS[index][i][0],C[index][i]) rows = ROWS # label things as either TableData or # Table header TD = [] TH = [] all = [] for index in range(len(indexes)): if indexes[index] is "TDdivider": TD.append(index) all.append(index) if indexes[index] is "THdivider": TH.append(index) all.append(index) TD = TD[1:] dividers = all[1:] #print "TD => ", TD #print "TH => ", TH #print "all => ", all, "\n" for div in dividers: if div in TD: index = all.index(div) for rowindex in range(all[index-1],all[index]): for i in range(len(rows[rowindex])): rows[rowindex][i] = (rows[rowindex][i][0], rows[rowindex][i][1], "td") else: index = all.index(div) for rowindex in range(all[index-1],all[index]): for i in range(len(rows[rowindex])): rows[rowindex][i] = (rows[rowindex][i][0], rows[rowindex][i][1], "th") # now munge the multi-line cells together # as paragraphs ROWS = [] COLS = [] for row in rows: for index in range(len(row)): if not COLS: COLS = range(len(row)) for i in range(len(COLS)): COLS[i] = ["",1,""] if TDdivider(row[index][0]) or THdivider(row[index][0]): ROWS.append(COLS) COLS = [] else: COLS[index][0] = COLS[index][0] + (row[index][0]) + "\n" COLS[index][1] = row[index][1] COLS[index][2] = row[index][2] # now that each cell has been munged together, # determine the cell's alignment. # Default is to center. Also determine the cell's # vertical alignment, top, middle, bottom. Default is # to middle rows = [] cols = [] for row in ROWS: for index in range(len(row)): topindent = 0 bottomindent = 0 leftindent = 0 rightindent = 0 left = [] right = [] text = row[index][0] text = split(text,'\n') text = text[:len(text)-1] align = "" valign = "" for t in text: t = strip(t) if not t: topindent = topindent + 1 else: break text.reverse() for t in text: t = strip(t) if not t: bottomindent = bottomindent + 1 else: break text.reverse() tmp = join(text[topindent:len(text)-bottomindent],"\n") pars = re.compile("\n\s*\n").split(tmp) for par in pars: if index > 0: par = par[1:] par = split(par, ' ') for p in par: if not p: leftindent = leftindent+1 else: break left.append(leftindent) leftindent = 0 par.reverse() for p in par: if not p: rightindent = rightindent + 1 else: break right.append(rightindent) rightindent = 0 left.sort() right.sort() if topindent == bottomindent: valign="middle" elif topindent < 1: valign="top" elif bottomindent < 1: valign="bottom" else: valign="middle" if left[0] < 1: align = "left" elif right[0] < 1: align = "right" elif left[0] > 1 and right[0] > 1: align="center" else: align="left" cols.append((row[index][0],row[index][1],align,valign,row[index][2])) rows.append(cols) cols = [] return StructuredTextTable(rows,text,subs,indent=paragraph.indent) myDocument = myDocumentClass() def flatten(list): newlist = [] for item in list: if type(item) == ListType: newlist.extend(flatten(item)) else: newlist.append(item) return newlist flatten2 = lambda l,f=lambda L,F : type(L) != type([]) and [L] or reduce(lambda a,b,F=F : a + F(b,F), L, []) :f(l,f) # flatten from WFN def flatten3(seq): """Translate a nested sequence into a flat list of string-terminals. We omit duplicates terminals in the process.""" got = [] pending = [seq] while pending: cur = pending.pop(0) if type(cur) == StringType: if cur not in got: got.append(cur) else: pending.extend(cur) return got # Boldly taken from tres seaver's PTK code. # Then lifted from ken manheimer's WFN code. def parseHeadersBody( body, headers=None ): """ Parse any leading 'RFC-822'-ish headers from an uploaded document, returning a tuple containing the headers in a dictionary and the stripped body. E.g.:: Title: Some title Creator: Tres Seaver Format: text/plain X-Text-Format: structured Overview This document ..... would be returned as:: { 'Title' : 'Some title' , 'Creator' : 'Tres Seaver' , 'Format' : 'text/plain' , 'text_format': 'structured' } as the headers, plus the body, starting with 'Overview' as the first line (the intervening blank line is a separator). Allow passing initial dictionary as headers. """ cr = re.compile( '^.*\r$' ) lines = map( lambda x, cr=cr: cr.match( x ) and x[:-1] or x , split( body, '\n' ) ) i = 0 if headers is None: headers = {} else: headers = headers.copy() hdrlist = [] for line in lines: if line and line[-1] == '\r': line = line[:-1] if not line: break tokens = split( line, ':' ) if len( tokens ) > 1: hdrlist.append( ( tokens[0], join( tokens[1:], ':' ) ) ) elif i == 0: return headers, body # no headers, just return those passed in. else: # continuation last, hdrlist = hdrlist[ -1 ], hdrlist[ :-1 ] hdrlist.append( ( last[ 0 ] , join( ( last[1], lstrip( line ) ), '\n' ) ) ) i = i + 1 for hdr in hdrlist: headers[ hdr[0] ] = hdr[ 1 ] return headers, join( lines[ i+1: ], '\n' ) #from python FAQ: import tempfile import os class Popen3: """ This is a deadlock-safe version of popen, that returns an object with errorlevel, out (a string) and err (a string). (capturestderr may not work under windows.) Example: print Popen3('grep spam','\n\nhere spam\n\n').out """ def __init__(self,command,input='',capturestderr=0): outfile=tempfile.mktemp() command="( %s ) > %s" % (command,outfile) if input: infile=tempfile.mktemp() open(infile,"w").write(input) command=command+" <"+infile if capturestderr: errfile=tempfile.mktemp() command=command+" 2>"+errfile self.errorlevel=os.system(command) >> 8 self.out=open(outfile,"r").read() os.remove(outfile) if input: os.remove(infile) if capturestderr: self.err=open(errfile,"r").read() os.remove(errfile) ZWiki/WWML.py0100644000175000017500000003543307627011210012314 0ustar simonsimon# WikiWikiWeb-style markup # contributed by Tres Seaver import re import string import urllib name_pattern_1 = r'[A-Z]+[a-z]+[A-Z][A-Za-z]' name_pattern_2 = r'[A-Z][A-Z]+[a-z][A-Za-z]' bracket_pattern = r'\[[\\\w.:_ ]+\]' full_wikilink_pattern = re.compile( r'!?(%s*|%s*|%s)' % ( name_pattern_1 , name_pattern_2 , bracket_pattern ) ) abbrev_wikilink_pattern = re.compile( r'!?(%s*|%s*)' % ( name_pattern_1 , name_pattern_2 ) ) strip_bracket_pattern = re.compile( r'^\[(.*)\]$' ) def Wiki_ize( self, text='', full_pattern=1 ): """ Transform a html page into a wiki page by changing WikiNames into hyperlinks; pass full_pattern=1 to pick up square bracket links, too (default not to avoid fighting with StructuredText). """ pattern = full_pattern and full_wikilink_pattern or abbrev_wikilink_pattern wikifier = _Wikifier( self ) text = pattern.sub( wikifier._replaceWikilink, text ) return text class _Wikifier : def __init__( self, other ) : self.other_ = other def _replaceWikilink( self, matchobj ): """replace an occurrence of wikilink_pattern with a suitable hyperlink """ # any matches preceded by ! should be left alone if re.match( '^!', matchobj.group( 0 ) ): return matchobj.group( 1 ) # discard enclosing [] if any wikiname = strip_bracket_pattern.sub( r'\1', matchobj.group( 1 ) ) # if something of this name exists, link to it; # otherwise, provide a "?" creation link if hasattr( self.other_.aq_parent, wikiname ): return '' + wikiname + '' else: return '%s?' \ % ( wikiname , urllib.quote( self.other_.id ) , urllib.quote( wikiname ) ) class WMMLTranslator : """ |FSM/translator for texts marked up using WikiWikiMarkupLanguage, as | defined at http://www.c2.org/cgi/wiki?TextFormattingRules: | |Paragraphs | | Don't Indent paragraphs | | Words wrap and fill as needed | | Use blank lines as separators | | Four or more minus signs make a horizontal rule | |Lists | | tab-* for first level | | tab-tab-* for second level, etc. | | Use * for bullet lists, 1. for numbered lists (mix at will) | | tab-Term:-tab Definition for definition lists | | One line for each item | | Other leading whitespace signals preformatted text, changes font. | |Fonts | | Indent with one or more spaces to use a monospace font: | | This is in monospace # note it is off the margin | |This is not # it is on the left margin there | |Indented Paragraphs (Quotes) | | tab-space-:-tab -- often used (with emphasis) for quotations. | (See SimulatingQuoteBlocks) | |Emphasis | | Use doubled single-quotes ('') for emphasis (usually italics) | | Use tripled single-quotes (''') for strong emphasis (usually bold) | | Use five single-quotes ('), or triples within doubles, for some other | kind of emphasis (BoldItalicInWiki), but be careful about the bugs in | the Wiki emphasis logic... | | At most one per line. But as many as you need in a paragraph; just use | multiple lines without intervening blank lines. | | Don't cross line boundaries | |References | | JoinCapitalizedWords to make local references | | [1], [2], [3], [4] refer to remote references. Click EditLinks on the | edit form to enter URLs | | Or precede URLs with "http:", "ftp:" or "mailto:" to create links | automatically as in: http://c2.com/ | | URLs ending with .gif are inlined if inserted as a remote reference | | ISBN 0-13-748310-4 links to a bookseller. (The pattern is: | "ISBN", optional colon, space, ten digits with optional hypens, | the whole thing optionally in square brackets. The last digit can be | an "X".) We are an AmazonAssociate. | | I S B N: 0123456789 becomes ISBN 0123456789 | [I S B N 0123456789] becomes ISBN 0123456789 | [I S B N: 123-456-789-X] becomes ISBN 123-456-789-X """ # # Patterns used to parse markup. # blankLine = re.compile( '^$' ) anyGroup = "(.*)" notTick = "([^']*)" minimalMunch = "(.*?)" strong = re.compile( "'''%s'''" % minimalMunch ) emphasis = re.compile( "''%s''" % minimalMunch ) forceBreak = re.compile( r"\\\\" ) hrule = re.compile( '^----[-]*%s' % anyGroup ) leadingSpaces = re.compile( '^( *)' ) spaceChunks = re.compile( ' {1,8}' ) nTabs = "([ \t]*)" tabList = re.compile( '^%s%s' % ( nTabs, anyGroup ) ) blockPrefix = r'^(=)' tableFormat = r'^\|(.*)\|' rowDivider = r'\|' bulletPrefix = r'^(\*)' digitPrefix = r'^([0-9][0-9]*[\.]?)' dictPrefix = "^([A-Za-z '-]*):" itemSuffix = r"[ \t]+" + anyGroup blockItem = re.compile( blockPrefix + itemSuffix ) tableRow = re.compile( tableFormat ) tableDef = re.compile( rowDivider ) bulletItem = re.compile( bulletPrefix + itemSuffix ) digitItem = re.compile( digitPrefix + itemSuffix ) dictItem = re.compile( dictPrefix + itemSuffix ) codePrefix = r'^([ \t]+)(?=[^*=|0] )' codeLine = re.compile( codePrefix ) httpPrefix = '(http:)' ftpPrefix = '(ftp:)' mailtoPrefix = '(mailto:)' urlSuffix = '([^ ]+)' httpURL = re.compile( httpPrefix + urlSuffix ) ftpURL = re.compile( ftpPrefix + urlSuffix ) mailtoURL = re.compile( mailtoPrefix + urlSuffix ) imageURL = re.compile( r'((?:http|ftp)://(?:.*?)\.(?:JPG|GIF|PNG|jpg|jpeg|gif|png))' ) # def __init__( self, other ) : def __init__( self ) : """ """ # self.other = other self.listStack = [] self.translatedLines = [] self.topList = self.translatedLines self.topCode = '' def nestingLevel( self ) : """ How deep is the nesting stack? """ return len( self.listStack ) def pushList( self, code ) : """ Start a new nested line list (e.g., for
    /
      /
      /
      ).
              """
              parentList   = self.topList
              self.topList = []
              self.topCode = code
              self.listStack.append( ( code, self.topList, parentList ) )
          def popList( self ) :
              """
              Finish current nested line list.
              """
              # First, pop the topmost record, and restore invariant (topList
              #  points to the list member of topmost).
              oldLevel                    = self.nestingLevel()
              if not oldLevel : return
              code, lines, self.topList   = self.listStack.pop()
              newLevel                    = self.nestingLevel()
              self.topCode = newLevel and self.listStack[ newLevel - 1 ][ 0 ] or ''
              # Next, insert popped record's lines as nested structure.
              if code == 'PRE' :
                  indent = ''
              else :
                  indent = '  '
              if code:
                  self.topList.append( '%s<%s>' % ( indent, code ) )
              for line in lines :
                  self.topList.append( '%s%s%s' % ( indent, indent, line ) )
              if code:
                  self.topList.append( '%s' % ( indent, code ) )
          def replaceEmphasis( self, matchObj ) :
              """
              Replace ''foo'' with foo.
              """
              return '%s' % matchObj.group(1)
          def replaceStrong( self, matchObj ) :
              """
              Replace '''foo''' with foo.
              """
              return '%s' % matchObj.group(1)
          def embedImage( self, matchObj ):
              return '' % matchObj.group(1)
          def replaceInlineURL( self, matchObj ):
              """
              Replace http://www.foo.com with
                   http://www.foo.com
                   (likewise ftp: and mailto: URL's).
              """
              return '%s%s' % ( matchObj.group( 1 )
                                                 , matchObj.group( 2 )
                                                 , matchObj.group( 1 )
                                                 , matchObj.group( 2 )
                                                 )
          def appendCodeLine( self, line ) :
              """
              """
              while self.nestingLevel() > 1 :
                  self.popList()
              if self.topCode != 'PRE' :
                  self.popList()
                  self.pushList( 'PRE' )
              self.topList.append( line )
          def mungeLine( self, line ) :
              """
              """
              #   Munge "simple" markup.
      #        line        = Wiki_ize( self.other, line, 1 )
              line        = self.blankLine.sub( '

      ', line ) line = self.strong.sub( self.replaceStrong, line ) line = self.emphasis.sub( self.replaceEmphasis, line ) line = self.imageURL.sub( self.embedImage, line ) # SKWM let the main routines take care of this later #line = self.httpURL.sub( self.replaceInlineURL, line ) #line = self.ftpURL.sub( self.replaceInlineURL, line ) #line = self.mailtoURL.sub( self.replaceInlineURL, line ) line = self.hrule.sub( '


      ', line ) line = self.forceBreak.sub( '
      ', line ) line = self.blankLine.sub( '

      ', line ) return line def parseLine( self, line ) : # Break off leading tabs and count. tabMatch = self.tabList.match( line ) numTabs = len( tabMatch.group( 1 ) ) line = tabMatch.group( 2 ) # Store line and compute new state. self.newState( numTabs, line ) def replaceTableItems( self, matchObj ) : """ """ row = self.tableDef.sub("", matchObj.group( 1 )) # return "%s" % row return "%s" % row def insertBlockItem( self, matchObj ) : """ """ return "%s
      " % matchObj.group( 2 ) def replaceListItem( self, matchObj ) : """ """ return "

    1. %s" % matchObj.group( 2 ) def replaceDictItem( self, matchObj ) : """ """ return "
      %s
      %s" % ( matchObj.group( 1 ), matchObj.group( 2 ) ) def lexListLine( self, line ) : line, nsub = self.tableRow.subn( self.replaceTableItems, line ) if nsub : return 'TABLE', self.mungeLine( line ) line, nsub = self.blockItem.subn( self.insertBlockItem, line ) if nsub : return 'BLOCKQUOTE', self.mungeLine( line ) line, nsub = self.bulletItem.subn( self.replaceListItem, line ) if nsub : return 'UL', self.mungeLine( line ) line, nsub = self.digitItem.subn( self.replaceListItem, line ) if nsub : return 'OL', self.mungeLine( line ) line, nsub = self.dictItem.subn( self.replaceDictItem, line ) if nsub : return 'DL', self.mungeLine( line ) return '', self.mungeLine( line ) def newState( self, numTabs, line ) : """ Decode new state (i.e., adjust the nesting stack), based on current state and inputs. """ level = self.nestingLevel() if self.topCode == 'PRE' : self.popList() if numTabs : newCode, line = self.lexListLine( line ) else : newCode = '' line = self.mungeLine( line ) if level < numTabs : difference = numTabs - level for i in range(difference): self.pushList( newCode ) elif level > numTabs : self.popList() if newCode != self.topCode : self.popList() if newCode : self.pushList( newCode ) self.topList.append( line ) def translate( self, lines ) : """ """ for line in lines : # PM: Turn leading spaces into tabs ... someone smarter than # me can figure out how to do this with a single regexp ... splitted = self.leadingSpaces.split(line) if len(splitted) == 3: line = self.spaceChunks.sub("\t", splitted[1]) + splitted[2] if self.codeLine.match( line ) : self.appendCodeLine( line ) else : self.parseLine( line ) return self.translatedLines __call__ = translate #def translate_WMML( self, text ) : # wt = WMMLTranslator( self ) def translate_WMML( text ) : wt = WMMLTranslator() lines = wt( string.split( str( text ), '\n' ) ) return string.join( lines, '\n' ) if __name__ == '__main__' : testString = \ """ This is the first paragraph. There should be a paragraph marker before it, and it should wrap nicely around. --------- That should have produced a horizontal rule. The last word should be ''emphasized''. The last word should be '''strong''' Can we do both ''emph'' and '''strong''' on the same line? How about two times: ''emph1'' followed by ''emph2''? * one bullet, with a WikiName * second bullet, with an UnknownWikiName 1. nested digit 2. nested digit 1. Top-level numbered list 2. Top-level numbered list * nested bullet * nested bullet 3. See if we keep the numbering here! Term: a definition ''Marked-up Term'': another definition, but with ''markup'' : a definition without a term This should be monospaced, and indented manually and now this is another normal paragraph. Here is some more pre-formatted text. * followed by a bullet Let's see if AutomaticURLLinking works yet: * http://www.palladion.com * ftp://www.neosoft.com/pub/users/t/tseaver * mailto:tseaver@palladion.com And [these words] should be linked, too. """ class FakeParent : def __init__( self, parent, **kw ) : self.aq_parent = parent for key, value in kw.items() : setattr( self, key, value ) grandparent = FakeParent( None, WikiName = 0 ) # print translate_WMML( FakeParent( grandparent, id = 'parent' ), testString ) print translate_WMML( testString ) ZWiki/ZWikiPage.py0100644000175000017500000035463407652244043013401 0ustar simonsimon""" The main Zwiki module. See README.txt. (c) 1999-2003 Simon Michael for the zwiki community. Wikiwikiweb formatting by Tres Seaver Parenting code and regulations by Ken Manheimer Initial Zope CMF integration by Chris McDonough Full credits are at http://zwiki.org/ZwikiContributors . This product is available under the GNU GPL. All rights reserved, all disclaimers apply, etc. """ __version__="$Revision: 0.347 $"[11:-2] import os, sys, re, string, time, math, traceback from DateTime import DateTime from string import split,join,find,lower,rfind,atoi,strip from types import * from AccessControl import getSecurityManager, ClassSecurityInfo import Acquisition from App.Common import absattr, rfc1123_date, aq_base import DocumentTemplate from urllib import quote, unquote import ZODB import Globals from Globals import MessageDialog, package_home from Products.MailHost.MailHost import MailBase from OFS.CopySupport import CopyError from OFS.content_types import guess_content_type from OFS.Document import Document from OFS.DTMLDocument import DTMLDocument import OFS.Image import StructuredText from WWML import translate_WMML from Products.ZWiki import __version__ from Defaults import DEFAULT_PAGE_TYPE, DISABLE_JAVASCRIPT, \ LARGE_FILE_SIZE, AUTO_UPGRADE, \ default_standard_wiki_header, default_standard_wiki_footer, \ default_editform, default_backlinks, default_subscribeform, \ default_wikipage import Permissions from Regexps import url, bracketedexpr, footnoteexpr, wikiname1, \ wikiname2, simplewikilink, wikilink, interwikilink, remotewikiurl, \ protected_line, javascriptexpr, dtmlorsgmlexpr, zwikiidcharsexpr, \ anywikilinkexpr, localwikilink, spaceandlowerexpr, \ untitledwikilinkexpr, htmlheaderexpr, htmlfooterexpr from Utils import thunk_substituter, within_literal, html_quote, \ html_unquote, DLOG, myDocument, ZOPEVERSION, withinSgmlOrDtml, \ parseHeadersBody #from Rendering import Rendering #from Editing import Editing from UI import UI from Parents import ParentsSupport from Diff import DiffSupport from Mail import MailSupport from CatalogAwareness import CatalogAwareness from Tracker import TrackerSupport, ISSUE_FORM from Regulations import RegulationsSupport from CMF import CMFAwareness from Fit import FitSupport # i18n support from LocalizerSupport import LocalDTMLFile, _, N_ DTMLFile = LocalDTMLFile del LocalDTMLFile class ZWikiPage( DTMLDocument, UI, ParentsSupport, DiffSupport, MailSupport, CatalogAwareness, TrackerSupport, RegulationsSupport, CMFAwareness, FitSupport, ): #Rendering, #Editing, """ A ZWikiPage is essentially a DTML Document which knows how to render itself in various wiki styles, and can function inside or outside a CMF site. A lot of utility methods are provided to support wiki-building. Mixins are used to organize functionality into distinct modules. Initialization, rendering, editing and miscellaneous methods remain in the base class. RESPONSIBILITIES: (old) - render itself - provide edit/create forms, backlinks, table of contents - accept edit/create requests, with authentication - store metadata such as permissions, time, last author, parents etc - manage subscriber lists for self & parent folder """ #XXX calculate dynamically ? ZWIKI_PAGE_TYPES = ( 'stxprelinkdtmlfitissuehtml', 'stxprelinkdtmlhtml', 'stxdtmllinkhtml', 'dtmlstxlinkhtml', 'stxprelinkhtml', 'stxlinkhtml', 'stxprelink', 'stxlink', 'wwmlprelink', 'wwmllink', 'prelinkdtmlhtml', 'dtmllinkhtml', 'prelinkhtml', 'linkhtml', 'dtmlhtml', 'html', 'textlink', 'plaintext', 'issuedtml', ) meta_type = "ZWiki Page" icon = "misc_/ZWiki/ZWikiPage_icon" creator = '' creator_ip = '' creation_time = '' last_editor = '' last_editor_ip = '' last_edit_time = '' last_log = '' page_type = DEFAULT_PAGE_TYPE _prerendered = '' # cached partially rendered text _prelinked = [] # cached links & rendered text regions _links = [] # cached unique links # properties visible in the ZMI # would rather append to the superclass' _properties here # DocumentTemplate.inheritedAttribute('_properties'),...) ? _properties=( {'id':'title', 'type': 'string', 'mode':'w'}, {'id':'page_type', 'type': 'selection', 'mode': 'w', 'select_variable': 'ZWIKI_PAGE_TYPES'}, # shows all types {'id':'creator', 'type': 'string', 'mode': 'r'}, {'id':'creator_ip', 'type': 'string', 'mode': 'r'}, {'id':'creation_time', 'type': 'string', 'mode': 'r'}, {'id':'last_editor', 'type': 'string', 'mode': 'r'}, {'id':'last_editor_ip', 'type': 'string', 'mode': 'r'}, {'id':'last_edit_time', 'type': 'string', 'mode': 'r'}, {'id':'last_log', 'type': 'string', 'mode': 'r'}, ) \ + ParentsSupport._properties \ + MailSupport._properties \ + CatalogAwareness._properties security = ClassSecurityInfo() security.declareObjectProtected('View') # set some permissions for superclass methods security.declareProtected(Permissions.Change, 'manage_upload') # needed ? security.declareProtected(Permissions.FTP, 'manage_FTPstat') security.declareProtected(Permissions.FTP, 'manage_FTPlist') # make sure this appears in the security screen security.declareProtected(Permissions.ChangeType, 'dummy') def dummy(self): pass ###################################################################### # initialization def __init__(self, source_string='', mapping=None, __name__=''): """ Initialise this instance, including it's CMF data if applicable. Ugly, but putting CMFAwareness before DTMLDocument in the inheritance order creates problems. """ if self.supportsCMF(): CMFAwareness.__init__(self, source_string=source_string, mapping=mapping, __name__=__name__, ) else: DTMLDocument.__init__(self, source_string=source_string, mapping=mapping, __name__=__name__, ) ###################################################################### # rendering security.declareProtected(Permissions.View, '__call__') def __call__(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ Render this zwiki page, upgrading it on the fly if needed Similar situation to __init__ """ if AUTO_UPGRADE: self.upgrade(REQUEST) if self.supportsCMF() and self.inCMF(): return apply(CMFAwareness.__call__, (self,client,REQUEST,RESPONSE),kw) else: body = apply(self._render,(client,REQUEST,RESPONSE),kw) if RESPONSE is not None: RESPONSE.setHeader('Content-Type', 'text/html') #RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime)) #causes browser caching problems ? return body def _render(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ Render the body of this zwiki page according to it's page_type """ method = self._renderMethod() return apply(method,(self, REQUEST, RESPONSE), kw) def _renderMethod(self): r = 'render_' + self.page_type if hasattr(self, r): method = getattr(self,r) else: method = self.render_plaintext return method def _preRender(self,clear_cache=0): """ Make sure any applicable pre-rendering for this page has been done each render_* method knows how to do it's own prerendering.. pass the pre_only flag in kw to preserve the standard dtml method signature if clear_cache is 1, blow away any cached data """ if clear_cache: self.clearCache() return apply(self._render,(self, {}, None), {'pre_only':1}) security.declareProtected(Permissions.View, 'clearCache') def clearCache(self,REQUEST=None): """ forcibly clear out any cached render data for this page """ self._prerendered = '' self._prelinked = [] self._links = [] if REQUEST: REQUEST.RESPONSE.redirect(self.page_url()) security.declareProtected(Permissions.View, 'addStandardLayoutTo') def addStandardLayoutTo(self,body,**kw): """ Add standard wiki page layout to the rendered page body. Tries these alternatives: 1. if a wikipage page template is found in the zodb (in this folder or acquired, use that 2. if a standard_wiki_header or standard_wiki_footer dtml method is found in the zodb, use that. If one is missing, use the default value for the other. Note the defaults for these methods were last updated just before 0.10.0. 3. otherwise, use the default page template from the filesystem. . Also checks for bare/noheader/nofooter keywords. """ #XXX let CMF handle skinning if self.supportsCMF() and self.inCMF(): return body REQUEST = getattr(self,'REQUEST',None) RESPONSE = getattr(REQUEST,'RESPONSE',None) folder = self.folder() if (hasattr(folder,'wikipage') and not hasattr(REQUEST,'bare') and not kw.has_key('bare') and getattr(folder.wikipage,'meta_type',None) == 'Page Template'): if kw: kw['body'] = body else: kw = {'body':body} return apply(folder.wikipage.__of__(self),(),kw) # XXX REQUEST ? elif ((hasattr(folder,'standard_wiki_header') and getattr(folder.standard_wiki_header, 'meta_type',None) == 'DTML Method') or (hasattr(folder,'standard_wiki_footer') and getattr(folder.standard_wiki_footer, 'meta_type',None) == 'DTML Method')): header = apply(self._renderHeaderOrFooter,('header',REQUEST,RESPONSE),kw) footer = apply(self._renderHeaderOrFooter,('footer',REQUEST,RESPONSE),kw) return header + body + footer elif (self.wikipage and not hasattr(REQUEST,'bare') and not kw.has_key('bare')): if kw: kw['body'] = body else: kw = {'body':body} #return apply(default_wikipage.__of__(self),(),kw) return apply(self.wikipage,(),kw) else: return body def _renderHeaderOrFooter(self, hdrOrFtr, REQUEST, RESPONSE, **kw): """ Generate the standard wiki header or footer for this page. Also check for various options which may disable this. hdrOrFtr should be 'header' or 'footer' """ # actually a flag in REQUEST is not much use, try a keyword arg # why doesn't work yet ? if (REQUEST is not None and not (hasattr(REQUEST,'bare') or hasattr(REQUEST,'no'+hdrOrFtr) or kw.has_key('bare') or kw.has_key('no'+hdrOrFtr)) and (hdrOrFtr == 'header' or hdrOrFtr == 'footer')): method = getattr(self,'standard_wiki_'+hdrOrFtr) return apply(method,(self, REQUEST, RESPONSE), kw) else: return '' # override cook so DTML will use our pre-rendered data if available import thread def cook(self, cooklock=thread.allocate_lock(), ): cooklock.acquire() try: self._v_blocks=self.parse(self._prerendered or self.read()) self._v_cooked=None finally: cooklock.release() # built-in render methods (page types) # # add new ones as dtml/python/external methods. I inlined all these # for maximum control. Perhaps these should become objects, but no # hurry; they still have the signature of a DTML method, which seems # possibly useful # # for 0.9.10, added pre-rendering (and pre-linking) # same goal as in WikiForNow, different implementation # each render method does as much pre-rendering as it can and caches # it in _prerendered; this is updated if needed, and also when the # page text or type is changed. # this allows flexibility for the different page types and # keeps the rendering code together # NB _prerendered == '' is assumed to mean this page has not been # pre-rendered, which may not be true # All render methods may be called with pre_only:1 in kw to do only # the pre-rendering # # Could this be simpler ? Perhaps. We are exploring a range of # combinations of rendering rules and strategies, while meeting the # constraints of DTML, Structured Text etc. and aiming for blazing # performance. It's a bit of a dance. security.declareProtected(Permissions.View, 'render_stxprelinkdtmlfitissuehtml') def render_stxprelinkdtmlfitissuehtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ Render the page with all available zwiki bells and whistles. That is: do structured text (with pre-formatting) and wiki links (with pre-linking), execute any DTML, execute any fit test tables, add an issue properties form (if indicated by the page name), and display the result as HTML. """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = self.applyLineEscapesIn(t) t = self.stxToHtml(t) self._prerendered = t or '\n' self.cook() # pre-parse DTML self._preLink() # pre-parse links if kw.get('pre_only',0): return # final render self._renderWithLinks() t = apply(DTMLDocument.__call__,(self, client, REQUEST, RESPONSE), kw) t = self._renderLateLinkTitles(t) if self.hasFitTests(): t = self.runFitTestsIn(t) if self.isIssue(): t = self.stxToHtml(apply(ISSUE_FORM.__call__,(self, REQUEST),kw)) + t t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_stxprelinkdtmlhtml') def render_stxprelinkdtmlhtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using structured text + wiki links + DTML + HTML As well as stx formatting, this one does pre-linking - as much as possible of the linking work - ahead of time. """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = self.applyLineEscapesIn(t) t = self.stxToHtml(t) self._prerendered = t or '\n' self.cook() # pre-parse DTML self._preLink() # pre-parse links if kw.get('pre_only',0): return # final render self._renderWithLinks() t = apply(DTMLDocument.__call__,(self, client, REQUEST, RESPONSE), kw) t = self._renderLateLinkTitles(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_stxdtmllinkhtml') def render_stxdtmllinkhtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using structured text + DTML + wiki links + HTML Behaviour change since 0.9.9: switches stx/dtml rendering order. This one caches the structured text formatting ahead of time. """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = self.applyLineEscapesIn(t) t = self.stxToHtml(t) self._prerendered = t or '\n' self.cook() if kw.get('pre_only',0): return # final render t = apply(DTMLDocument.__call__,(self, client, REQUEST, RESPONSE), kw) t = self.renderLinksIn(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_dtmlstxlinkhtml') def render_dtmlstxlinkhtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using DTML + structured text + wiki links + HTML This is the old DTML-first non-caching version used up till 0.9.9. """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') self._prerendered = t or '\n' self.cook() if kw.get('pre_only',0): return # final render t = apply(DTMLDocument.__call__,(self, client, REQUEST, RESPONSE), kw) t = self.applyLineEscapesIn(t) t = self.stxToHtml(t) t = self.renderLinksIn(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_stxprelinkhtml') def render_stxprelinkhtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using structured text + wiki links + HTML, prelinking """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = self.applyLineEscapesIn(t) t = self.stxToHtml(t) self._prerendered = t or '\n' self._preLink() if kw.get('pre_only',0): return # final render self._renderWithLinks() t = self._prerendered t = self._renderLateLinkTitles(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_stxlinkhtml') def render_stxlinkhtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using structured text + wiki links + HTML """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = self.applyLineEscapesIn(t) t = self.stxToHtml(t) or '\n' self._prerendered = t if kw.get('pre_only',0): return # final render t = self._prerendered t = self.renderLinksIn(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_stxprelink') def render_stxprelink(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using structured text + wiki links, prelinking """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = html_quote(t) t = self.applyLineEscapesIn(t) t = self.stxToHtml(t) self._prerendered = t or '\n' self._preLink() if kw.get('pre_only',0): return # final render self._renderWithLinks() t = self._prerendered t = self._renderLateLinkTitles(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_stxlink') def render_stxlink(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using structured text + wiki links """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = html_quote(t) t = self.applyLineEscapesIn(t) t = self.stxToHtml(t) self._prerendered = t or '\n' if kw.get('pre_only',0): return # final render t = self._prerendered t = self.renderLinksIn(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_prelinkdtmlhtml') def render_prelinkdtmlhtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using DTML + wiki links + HTML, prelinking """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = self.applyLineEscapesIn(t) self._prerendered = t or '\n' self.cook() self._preLink() if kw.get('pre_only',0): return # final render self._renderWithLinks() t = apply(DTMLDocument.__call__,(self, client, REQUEST, RESPONSE), kw) t = self._renderLateLinkTitles(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_dtmllinkhtml') def render_dtmllinkhtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using DTML + wiki links + HTML """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = self.applyLineEscapesIn(t) self._prerendered = t or '\n' self.cook() if kw.get('pre_only',0): return # final render t = apply(DTMLDocument.__call__,(self, client, REQUEST, RESPONSE), kw) t = self.renderLinksIn(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_prelinkhtml') def render_prelinkhtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using wiki links + HTML, prelinking """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = self.applyLineEscapesIn(t) self._prerendered = t or '\n' self._preLink() if kw.get('pre_only',0): return # final render self._renderWithLinks() t = self._prerendered t = self._renderLateLinkTitles(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_linkhtml') def render_linkhtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using wiki links + HTML """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = self.applyLineEscapesIn(t) self._prerendered = t or '\n' if kw.get('pre_only',0): return # final render t = self._prerendered t = self.renderLinksIn(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_dtmlhtml') def render_dtmlhtml(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using DTML + HTML, nothing else """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') self._prerendered = t or '\n' self.cook() if kw.get('pre_only',0): return # final render t = apply(DTMLDocument.__call__,(self, client, REQUEST, RESPONSE), kw) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_html') def render_html(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using HTML with no surprises """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') self._prerendered = t or '\n' if kw.get('pre_only',0): return # final render t = self._prerendered t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_wwmlprelink') def render_wwmlprelink(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page with WikiWikiWeb formatting + (z)wiki links, prelinking """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = html_quote(t) t = self.applyLineEscapesIn(t) t = translate_WMML(t) self._prerendered = t or '\n' self._preLink() if kw.get('pre_only',0): return # final render self._renderWithLinks() t = self._prerendered t = self._renderLateLinkTitles(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_wwmllink') def render_wwmllink(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using WikiWikiWeb formatting + (z)wiki links """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = html_quote(t) t = self.applyLineEscapesIn(t) t = translate_WMML(t) self._prerendered = t or '\n' if kw.get('pre_only',0): return # final render t = self._prerendered t = self.renderLinksIn(t) t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_textlink') def render_textlink(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using fixed-width plain text plus zwiki links """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = html_quote(t) self._prerendered = t or '\n' if kw.get('pre_only',0): return # final render t = self._prerendered t = self.renderLinksIn(t) t = "
      \n" + t + "\n
      \n" t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'render_plaintext') def render_plaintext(self, client=None, REQUEST={}, RESPONSE=None, **kw): """ render the page using fixed-width plain text with no surprises """ # pre render t = str(self.read()) if not self._prerendered: get_transaction().note('prerender') t = "
      \n" + html_quote(t) + "\n
      \n" self._prerendered = t or '\n' if kw.get('pre_only',0): return # final render t = self._prerendered t = apply(self.addStandardLayoutTo,(t,),kw) return t security.declareProtected(Permissions.View, 'stxToHtml') def stxToHtml(self, text): """ Render some structured text into html, with our customizations. """ text = str(text) if ZOPEVERSION < (2,4): # final single-line paragraph becomes a heading if there are # trailing blank lines - strip them text = re.sub(r'(?m)\n[\n\s]*$', r'\n', text) # an initial single word plus period becomes a numeric bullet - # prepend a temporary marker to prevent # XXX use locale/wikichars from Regexps.py instead of A-z text = re.sub(r'(?m)^([ \t]*)([A-z]\w*\.)', r'\1\2', text) # :: quoting fails if there is whitespace after the :: - remove it text = re.sub(r'(?m)::[ \t]+$', r'::', text) # suppress stx footnote handling so we can do it our way text = re.sub(footnoteexpr,r'![\1]',text) text = re.sub(r'(?m)\[',r'[',text) # generate html if ZOPEVERSION < (2,4): text = str(StructuredText.HTML(text,level=2)) else: text = StructuredText.HTMLNG( myDocument(StructuredText.Basic(str(text))), level=2) # clean up text = re.sub(r'(<|<)!--NOSTX--(>|>)', r'', text) # strip html & body added by some zope versions text = re.sub( r'(?sm)^\n(.*)\n\n',r'\1',text) return text def _renderLateLinkTitles(self,text): """ fill in titles for any untitled wiki links in text In pre-linking modes, we do this in a final pass after linking & dtml so that we can have fast-changing link titles without messing up our caching. See _renderWithLinks for more. # The regexp needs to match _renderLink's link format. We can assume the link will be all on one line and that there's no raw dtml lying around. """ return re.sub(untitledwikilinkexpr,self._replaceUntitledWikilink,text) def _replaceUntitledWikilink(self, match): """ Replace a untitledwikilink occurence with a suitably titled link. """ link = match.group() pagename = match.group('page') # old wiki page urls may need un-quoting to get the id ? page = self.pageWithName(unquote(pagename)) if page: linktitle = page.linkTitle() else: # this shouldn't normally happen now linktitle = "" newlink = re.sub(r' title="">', r' title="%s">' % (linktitle), link) return newlink security.declareProtected(Permissions.View, 'supportsStx') def supportsStx(self): """does this page do Structured Text formatting ?""" return re.search(r'(?i)(structuredtext|stx)', self.page_type) is not None security.declareProtected(Permissions.View, 'supportsWiki') def supportsWiki(self): """does this page do wiki linking ?""" return re.search(r'(?i)plain',self.page_type) is None security.declareProtected(Permissions.View, 'supportsHtml') def supportsHtml(self): """does this page render embedded HTML ?""" return not self.page_type in \ ['structuredtextonly','classicwiki','plaintext'] security.declareProtected(Permissions.View, 'supportsDtml') def supportsDtml(self): """does this page support embedded DTML ?""" return re.search(r'(?i)dtml',self.page_type) is not None security.declareProtected(Permissions.View, 'hasDynamicContent') def hasDynamicContent(self): """does this page contain dynamic content ?""" return (self.supportsDtml() and re.search(r'(?i)(\n\n?','', self._old_read()) def __repr__(self): return ("<%s %s at 0x%s>" % (self.__class__.__name__, `self.id()`, hex(id(self))[2:])) # methods for reliable dtml access to page & wiki url # these have been troublesome; they need to work with & without # virtual hosting. Tests needed. # Keep old versions around to help with debugging - see # http://zwiki.org/VirtualHostingSummary # XXX absolute_url uses REQUEST.SERVER_URL - would be nice for these # to work without requiring a live request security.declareProtected(Permissions.View, 'page_url') def page_url(self): """return the url path for this wiki page""" return self.page_url7() security.declareProtected(Permissions.View, 'wiki_url') def wiki_url(self): """return the base url path for this wiki""" return self.wiki_url7() security.declareProtected(Permissions.View, 'page_url1') def page_url1(self): """return the url path for the current wiki page""" o = self url = [] while hasattr(o,'id'): url.insert(0,absattr(o.id)) o = getattr(o,'aq_parent', None) return quote('/' + join(url[1:],'/')) security.declareProtected(Permissions.View, 'wiki_url1') def wiki_url1(self): """return the base url path for the current wiki""" # this code is buried somewhere in cvs return '?' security.declareProtected(Permissions.View, 'page_url2') def page_url2(self): """return the url path for the current wiki page""" return quote('/' + self.absolute_url(relative=1)) security.declareProtected(Permissions.View, 'wiki_url2') def wiki_url2(self): """return the base url path for the current wiki""" return quote('/' + self.folder().absolute_url(relative=1)) security.declareProtected(Permissions.View, 'page_url3') def page_url3(self): """return the url path for the current wiki page""" return self.REQUEST['BASE1'] + '/' + self.absolute_url(relative=1) security.declareProtected(Permissions.View, 'wiki_url3') def wiki_url3(self): """return the base url path for the current wiki""" return self.REQUEST['BASE1'] + '/' + self.folder().absolute_url(relative=1) security.declareProtected(Permissions.View, 'page_url4') def page_url4(self): """return the url path for the current wiki page""" return self.absolute_url(relative=0) security.declareProtected(Permissions.View, 'wiki_url4') def wiki_url4(self): """return the base url path for the current wiki""" return self.folder().absolute_url(relative=0) security.declareProtected(Permissions.View, 'page_url5') def page_url5(self): """return the url path for the current wiki page""" return self.absolute_url() security.declareProtected(Permissions.View, 'wiki_url5') def wiki_url5(self): """return the base url path for the current wiki""" return self.folder().absolute_url() security.declareProtected(Permissions.View, 'page_url6') def page_url6(self): """return the url path for the current wiki page""" return '/' + self.absolute_url(relative=1) security.declareProtected(Permissions.View, 'wiki_url6') def wiki_url6(self): """return the base url path for the current wiki""" return '/' + self.aq_inner._my_folder_orig().absolute_url(relative=1) def _my_folder_orig(self): """ Obtain parent folder, avoiding potential acquisition recursion. ken's original version - keep it around in case """ got = parent = self.folder() # Handle bizarred degenerate case - darnit, i've forgotten now where # i've seen this occur! while (got and hasattr(got, 'aq_parent') and got.aq_parent and (got is not got.aq_parent) and (aq_base(got) is aq_base(got.aq_parent))): parent = got got = got.aq_parent return parent security.declareProtected(Permissions.View, 'page_url7') def page_url7(self): """return the url path for the current wiki page""" return self.wiki_url() + '/' + quote(self.id()) security.declareProtected(Permissions.View, 'wiki_url7') def wiki_url7(self): """return the base url path for the current wiki""" try: return self.folder().absolute_url() except KeyError,AttributeError: return '' # makes debugging/testing easier security.declareProtected(Permissions.View, 'creationTime') def creationTime(self): """return our creation time as a DateTime object""" if self.creation_time: try: return DateTime(self.creation_time) except DateTime.SyntaxError: return None else: return None security.declareProtected(Permissions.View, 'lastEditTime') def lastEditTime(self): """return our last edit time as a DateTime object""" if self.last_edit_time: try: return DateTime(self.last_edit_time) except DateTime.SyntaxError: return None else: return None security.declareProtected(Permissions.View, 'folder') def folder(self): """ return this page's containing folder We used to use self.aq_parent everywhere, now self.aq_inner.aq_parent to ignore acquisition paths. Work for pages without a proper acquisition wrapper too. """ return getattr(getattr(self,'aq_inner',self),'aq_parent',None) security.declareProtected(Permissions.View, 'age') def age(self): """ return a string describing the approximate age of this page """ return self.asAgeString(self.creation_time) security.declareProtected(Permissions.View, 'lastEditInterval') def lastEditInterval(self): """ return a string describing the approximate interval since last edit """ return self.asAgeString(self.last_edit_time) security.declareProtected(Permissions.View, 'asAgeString') def asAgeString(self,time): """ return a string describing the approximate elapsed period since time time may be a DateTime or suitable string. Returns a blank string if there was a problem. Based on the dtml version in ZwikiTracker. """ if not time: return 'some time' if type(time) is StringType: time = DateTime(time) # didn't work on a page in CMF, perhaps due to skin acquisition magic #elapsed = self.ZopeTime() - time elapsed = self.getPhysicalRoot().ZopeTime() - time hourfactor=0.041666666666666664 minutefactor=0.00069444444444444447 secondsfactor=1.1574074074074073e-05 days=int(math.floor(elapsed)) weeks=days/7 months=days/30 years=days/365 hours=int(math.floor((elapsed-days)/hourfactor)) minutes=int(math.floor((elapsed-days-hourfactor*hours)/minutefactor)) seconds=int(round(( elapsed-days-hourfactor*hours-minutefactor*minutes)/secondsfactor)) if years: s = "%d year%s" % (years, years > 1 and 's' or '') elif months: s = "%d month%s" % (months, months > 1 and 's' or '') elif weeks: s = "%d week%s" % (weeks, weeks > 1 and 's' or '') elif days: s = "%d day%s" % (days, days > 1 and 's' or '') elif hours: s = "%d hour%s" % (hours, hours > 1 and 's' or '') elif minutes: s = "%d minute%s" % (minutes, minutes > 1 and 's' or '') else: s = "%d second%s" % (seconds, seconds > 1 and 's' or '') return s security.declareProtected(Permissions.View, 'linkTitle') def linkTitle(self,prettyprint=0): """ return a suitable value for the title attribute of links to this page with prettyprint=1, format it for use in the standard header. """ return self.linkTitleFrom(self.last_edit_time, self.last_editor, prettyprint=prettyprint) # please clean me up security.declareProtected(Permissions.View, 'linkTitleFrom') def linkTitleFrom(self,last_edit_time=None,last_editor=None,prettyprint=0): """ make a link title string from these last_edit_time and editor strings with prettyprint=1, format it for use in the standard header. """ interval = self.asAgeString(last_edit_time) if not prettyprint: s = "last edited %s ago" % (interval) else: try: assert self.REQUEST.AUTHENTICATED_USER.has_permission( 'View History',self) #XXX do timezone conversion ? lastlog = self.lastlog() if lastlog: lastlog = ' ('+lastlog+')' s = 'last edited %s ago' % \ (self.page_url(), lastlog, interval) except: s = 'last edited %s ago' % (interval) if (last_editor and not re.match(r'^[0-9\.\s]*$',last_editor)): # escape some things that might cause trouble in an attribute editor = re.sub(r'"',r'',last_editor) if not prettyprint: s = s + " by %s" % (editor) else: s = s + " by %s" % (editor) return s security.declareProtected(Permissions.Change, 'manage_edit') def manage_edit(self, data, title, REQUEST=None): """Do standard manage_edit kind of stuff, using our edit.""" #self.edit(text=data, title=title, REQUEST=REQUEST, check_conflict=0) #I think we need to bypass edit to provide correct permissions self.title = title self._setText(data,REQUEST) self._setLastEditor(REQUEST) self.reindex_object() if REQUEST: message="Content changed." return self.manage_main(self,REQUEST,manage_tabs_message=message) ###################################################################### # backwards compatibility security.declarePublic('upgradeAll') # check permissions at runtime def upgradeAll(self,pre_render=1,REQUEST=None): """ Clear cache, upgrade and pre-render all pages Normally pages are upgraded/pre-rendered as needed. An administrator may want to call this, particularly after a zwiki upgrade, to minimize later delays and to ensure all pages have been rendered by the latest code. Requires 'Manage properties' permission on the folder. Commit every so often an attempt to avoid memory/conflict errors. Has problems doing a complete run in large wikis, or when other page accesses are going on ? """ if not self._checkPermission('Manage properties',self.folder()): raise 'Unauthorized', ( _('You are not authorized to upgrade all pages.') + \ _('(folder -> Manage properties)')) try: pre_render = int(pre_render) except: pre_render = 0 if pre_render: DLOG('upgrading and prerendering all pages:') else: DLOG('upgrading all pages:') n = 0 for p in self.pages(): n = n + 1 p.upgrade(REQUEST) p.upgradeId(REQUEST) if pre_render: p._preRender(clear_cache=1) DLOG('page #%d %s'%(n,p.id())) if n % 10 == 0: DLOG('committing') get_transaction().commit() # last pages will get committed as this request ends DLOG('%d pages processed' % n) #security.declarePublic('upgradeId') security.declareProtected(Permissions.View, 'upgradeId') def upgradeId(self,REQUEST=None): """ Make sure a page's id conforms with it's title, renaming as needed. Does not leave a placeholder, so may break incoming links. Presently too slow for auto-upgrade, so let people call this directly or via upgradeAll (good luck :( ) updatebacklinks=1 is used even though it's slow, because it's less work than fixing up links by hand afterward. With legacy pages (not new ones), it may happen that there's a clash between two similarly-named pages mapping to the same canonical id. In this case we just log the error and move on. """ id, cid = self.getId(), self.canonicalId() if id != cid: oldtitle = title = self.title_or_id() # as a special case, preserve tracker issue numbers in the title m = re.match(r'IssueNo[0-9]+$',id) if m: title = '%s %s' % (m.group(),self.title) try: self.rename(title,updatebacklinks=1,sendmail=0,REQUEST=REQUEST) except CopyError: DLOG('failed to rename "%s" (%s) to "%s" (%s) - id clash ?' \ % (oldtitle,id,title,self.canonicalIdFrom(title))) #security.declarePublic('upgrade') security.declareProtected(Permissions.View, 'upgrade') def upgrade(self,REQUEST=None): """ Upgrade an old page instance (and possibly the parent folder). Called as needed, ie at view time (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 # don't acquire while doing this realself = self self = self.aq_base # migrate a WikiForNow _st_data attribute if hasattr(self, '_st_data'): self.raw = self._st_data del self._st_data changed = 1 # upgrade old page types # choose pre-formatting & pre-linking variants where possible changedpagetypes = { # early zwiki 'Structured Text' :'stxprelinkhtml', 'structuredtext_dtml':'stxprelinkdtmlfitissuehtml', 'HTML' :'prelinkhtml', 'html_dtml' :'prelinkdtmlhtml', 'Classic Wiki' :'wwmlprelink', 'Plain Text' :'plaintext', # pre-0.9.10 'stxprelinkdtml' :'stxprelinkdtmlfitissuehtml', 'structuredtextdtml' :'stxprelinkdtmlfitissuehtml', 'dtmlstructuredtext' :'stxprelinkdtmlfitissuehtml', 'structuredtext' :'stxprelinkhtml', 'structuredtextonly' :'stxprelink', 'classicwiki' :'wwmlprelink', 'htmldtml' :'prelinkdtmlhtml', # I want to reuse the 'html' type, so leave it be. Old 'html' # pages will lose their wikilinks; add to release notes and # let people fix manually. 'plainhtmldtml' :'dtmlhtml', 'plainhtml' :'html', # 0.17's stxprelinkdtmlfitissuehtml replaces these two 'stxprelinkdtmlhtml' :'stxprelinkdtmlfitissuehtml', 'issuedtml' :'stxprelinkdtmlfitissuehtml', } if self.page_type in changedpagetypes.keys(): self.page_type = changedpagetypes[self.page_type] # clear render cache; don't bother prerendering just now self.clearCache() changed = 1 # Early zwikipages have a username string property - delete it. # Once it's gone, the username method below is revealed and allows # old dtml to keep working. # Could attempt to transfer it to last_editor, then you'd need # to transfer timestamp as well or it looks confusing. Don't bother. if type(self.username) is StringType: delattr(self,'username') 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-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().ISO() changed = 1 elif type(self.last_edit_time) is not StringType: self.last_edit_time = self.last_edit_time.ISO() changed = 1 elif len(self.last_edit_time) != 19: try: self.last_edit_time = DateTime(self.last_edit_time).ISO() changed = 1 except DateTime.SyntaxError: # can't convert to ISO, 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.ISO() changed = 1 elif len(self.creation_time) != 19: try: self.creation_time = DateTime(self.creation_time).ISO() changed = 1 except DateTime.SyntaxError: # can't convert to ISO, just leave it be pass # _wikilinks is now _links #if hasattr(self.aq_base,'_wikilinks'): #XXX why doesn't this work if hasattr(self,'_wikilinks'): self._links = self._wikilinks.keys()[:] delattr(self,'_wikilinks') 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 for p in newprops.keys(): if not p in props: self._properties = self._properties + (newprops[p],) changed = 1 # install issue properties if needed, ie if this page is being # viewed as an issue for the first time # could do this in isIssue instead if (self.isIssue() and not 'severity' in props): realself.manage_addProperty('category','issue_categories','selection') realself.manage_addProperty('severity','issue_severities','selection') realself.manage_addProperty('status','issue_statuses','selection') realself.severity = 'normal' changed = 1 if changed: # bobobase_modification_time changed - put in a dummy user so # it's clear this was not an edit # no - you should be looking at last_edit_times, in which case # you don't want to see last_editor change for this. #self.last_editor_ip = '' #self.last_editor = 'UpGrade' # do a commit now so the current render will have the # correct bobobase_modification_time for display (many # headers/footers still show it) get_transaction().commit() # and log it DLOG('upgraded '+self.id()) # finally, MailSupport does a bit more (merge here ?) realself._upgradeSubscribers() # some CMF compatibility methods for standard ZWikiPage SearchableText = text view = __call__ # old API methods to help keep legacy DTML working # XXX permissions declarations needed ? Get rid of 'em ? wiki_page_url = page_url wiki_base_url = wiki_url editTimestamp = timeStamp checkEditTimeStamp = checkEditConflict doLegacyFixups = upgrade doSubscriberListFixups = MailSupport._upgradeSubscribers doSubscriberFixups = MailSupport._upgradeSubscribers # old render methods - these shouldn't be needed after # upgrade converts page types, but it may be disabled render_stxprelinkdtml = render_stxprelinkdtmlfitissuehtml render_structuredtextdtml = render_stxdtmllinkhtml render_dtmlstructuredtext = render_dtmlstxlinkhtml render_structuredtext = render_stxlinkhtml render_structuredtextonly = render_stxlink render_classicwiki = render_wwmllink render_htmldtml = render_dtmllinkhtml render_plainhtmldtml = render_dtmlhtml render_plainhtml = render_html security.declareProtected(Permissions.View, 'username') def username(self): # backwards compatibility for dtml which tries to display the old # username property. NB upgrade may not have been called yet. # XXX I think we can be less slavishly compatible at this # point. Just return last_editor ? for p in self._properties: if p['id'] == 'username': return username if hasattr(self,'last_editor') and self.last_editor: return self.last_editor if hasattr(self,'last_editor_ip') and self.last_editor_ip: return self.last_editor_ip return '' # actually the above is useful to keep around as security.declareProtected(Permissions.View, 'last_editor_or_ip') last_editor_or_ip = username # document_src is the standard zope accessor for raw content # inherited from DTMLDocument # text/src is a permission-free accessor for the content # XXX still need this ? document_src & src equivalent ? clean up security.declareProtected(Permissions.View, 'src') src = text Globals.InitializeClass(ZWikiPage) # install permissions # ZMI page creation form manage_addZWikiPageForm = DTMLFile('dtml/zwikiPageAdd', globals()) def manage_addZWikiPage(self, id, title='', file='', REQUEST=None, submit=None): """ Add a ZWiki Page object with the contents of file. Usually zwiki pages are created by clicking on ? (via create); this allows them to be added in the standard ZMI way. These should give mostly similar results; refactor the two methods together if possible. """ # create page and initialize in proper order, as in create. p = ZWikiPage(source_string='', __name__=id) newid = self._setObject(id,p) p = getattr(self,newid) p.title=title p._setCreator(REQUEST) p._setLastEditor(REQUEST) p._setOwnership(REQUEST) if hasattr(self,'standard_page_type'): p.page_type = self.standard_page_type else: p.page_type = DEFAULT_PAGE_TYPE text = file if type(text) is not StringType: text=text.read() p._setText(text or '',REQUEST) p.index_object() if p.usingRegulations(): p.setRegulations(REQUEST,new=1) if REQUEST is not None: try: u=self.DestinationURL() except: u=REQUEST['URL1'] if submit==" Add and Edit ": u="%s/%s" % (u,quote(id)) REQUEST.RESPONSE.redirect(u+'/manage_main') return '' #python notes # # "The first line should always be a short, concise summary of the #object's purpose. For brevity, it should not explicitly state the #object's name or type, since these are available by other means (except #if the name happens to be a verb describing a function's operation). #This line should begin with a capital letter and end with a period. # # If there are more lines in the documentation string, the second line #should be blank, visually separating the summary from the rest of the #description. The following lines should be one or more paragraphs #describing the object's calling conventions, its side effects, etc." # # "Data attributes override method attributes with the same name; to #avoid accidental name conflicts, which may cause hard-to-find bugs in #large programs, it is wise to use some kind of convention that #... #verbs for methods and nouns for data attributes." ZWiki/ZWikiWeb.py0100644000175000017500000001601207635012051013214 0ustar simonsimon###################################################################### # create wikis from templates from Globals import package_home from OFS.Folder import Folder from OFS.DTMLMethod import DTMLMethod from ZWikiPage import ZWikiPage from AccessControl import getSecurityManager from Defaults import DEFAULT_PAGE_TYPE import os, re, string, urllib # i18n support from LocalizerSupport import LocalDTMLFile DTMLFile = LocalDTMLFile del LocalDTMLFile # ZMI wiki creation form manage_addZWikiWebForm = DTMLFile('dtml/zwikiWebAdd', globals()) ###################################################################### # FUNCTION CATEGORY: wikiweb creation ###################################################################### def manage_addZWikiWeb(self, new_id, new_title='', wiki_type='zwikidotorg', REQUEST=None, enter=0): """ Create a new zwiki web of the specified type """ if not REQUEST: REQUEST = self.REQUEST # check for a configuration wizard if hasattr(self,wiki_type+'_config'): 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.addZWikiWebFromFs(new_id,new_title,wiki_type,REQUEST) elif wiki_type in self.listZodbWikis(): self.addZWikiWebFromZodb(new_id,new_title,wiki_type,REQUEST) else: messageDialog('unknown wiki type') if REQUEST is not None: if enter: # can't see why this doesn't work after addZWikiWebFromFs #REQUEST.RESPONSE.redirect(getattr(self,new_id).absolute_url()) REQUEST.RESPONSE.redirect(REQUEST['URL3']+'/'+new_id+'/') else: try: u=self.DestinationURL() except: u=REQUEST['URL1'] REQUEST.RESPONSE.redirect(u+'/manage_main?update_menu=1') #why do this ? #else: # return '' def addZWikiWebFromZodb(self,new_id, new_title='', wiki_type='zwikidotorg', REQUEST=None): """ Create a new zwiki web by cloning the specified template in /Control_Panel/Products/ZWiki """ # locate the specified wiki prototype # these are installed in /Control_Panel/Products/ZWiki prototype = self.getPhysicalRoot().Control_Panel.Products.ZWiki[wiki_type] # clone it self.manage_clone(prototype, new_id, REQUEST) wiki = getattr(self, new_id) wiki.manage_changeProperties(title=new_title) # could do stuff with ownership here # set it to low-privileged "nobody" by default ? def addZWikiWebFromFs(self, new_id, title='', wiki_type='zwikidotorg', REQUEST=None): """ Create a new zwiki web from the specified template on the filesystem """ ob = Folder() ob.id=str(new_id) ob.title=str(title) id = self._setObject(ob.id, ob) ob = getattr(self, id) dir = package_home(globals()) + os.sep + 'content' + os.sep + wiki_type filenames = os.listdir(dir) # hmm auto-cataloging is really slowing this down! for filename in filenames: if filename[-5:] == '.dtml': text = open(dir + os.sep + filename, 'r').read() _addDTMLMethod(ob, filename[:-5], title='', file=text) else: m = re.search(r'(.+)\.(.+)',filename) if m: text = open(dir + os.sep + filename, 'r').read() name, type = m.group(1), m.group(2) _addZWikiPage(ob,name,title='',page_type=type,file=text) # files without a suffix are ignored def _addDTMLMethod(self, id, title='', file=''): id=str(id) title=str(title) ob = DTMLMethod(source_string=file, __name__=id) ob.title = title username = getSecurityManager().getUser().getUserName() ob.manage_addLocalRoles(username, ['Owner']) #ob.setSubOwner('both') #? self._setObject(id, ob) def _addZWikiPage(self, id, title='', page_type=DEFAULT_PAGE_TYPE, file=''): """ helper function """ id=str(id) title=str(title) # we allow simplified page types for suffixes - convert if page_type == 'stxdtml': page_type = 'stxprelinkdtmlfitissuehtml' elif page_type == 'stx': page_type = 'stxprelinkhtml' elif page_type == 'htmldtml': page_type = 'prelinkdtmlhtml' elif page_type == 'html': page_type = 'prelinkhtml' # parse optional parents list m = re.match(r'(?si)(^#parents:(.*?)\n)?(.*)',file) if m.group(2): parents = string.split(string.strip(m.group(2)),',') else: parents = [] file = m.group(3) text = file # create zwiki page in this folder #ob = makeZWikiPage(id, title, file) ob = ZWikiPage(source_string=text, __name__=id) ob.title = title ob.page_type = page_type ob.parents = parents username = getSecurityManager().getUser().getUserName() ob.manage_addLocalRoles(username, ['Owner']) # the new page object is owned by the current authenticated user, if # any; not desirable for executable content. Remove any such # ownership so that the page will acquire it's owner from the parent # folder. ob._deleteOwnershipAfterAdd() # or _owner=UnownableOwner ? #ob.setSubOwner('both') # regulations setup ? #if ob.supportsCMF(): initPageMetadata(ob) self._setObject(id, ob) return getattr(self, id) #def _makeZWikiPage(id, title, file): # ob = ZWikiPage(source_string=file, __name__=id) # ob.title = title # ob.parents = [] # username = getSecurityManager().getUser().getUserName() # ob.manage_addLocalRoles(username, ['Owner']) # #ob.setSubOwner('both') #? # return ob # other CMFWiki stuff # initPageMetadata(ob) # for name, perm in ob._perms.items(): # pseudoperm = default_perms[name] # local_roles_map = ob._local_roles_map # roles_map = ob._roles_map # roles = (local_roles_map[name],) + roles_map[pseudoperm] # ob.manage_permission(perm, roles=roles) def listWikis(self): """ list all wiki templates available in the filesystem or zodb """ list = self.listFsWikis() for w in self.listZodbWikis(): if not w in list: list.append(w) list.sort() return list def listZodbWikis(self): """ list the wiki templates available in the ZODB """ list = self.getPhysicalRoot().Control_Panel.Products.ZWiki.objectIds() list.remove('Help') return list def listFsWikis(self): """ list the wiki templates available in the filesystem """ try: list = os.listdir(package_home(globals()) + os.sep + 'content') # developers may have content/CVS.. assume wiki templates # will never be called that: if 'CVS' in list: list.remove('CVS') if 'tracker' in list: list.remove('tracker') #XXX temp return list except OSError: return [] ZWiki/__init__.py0100644000175000017500000001452007654241606013275 0ustar simonsimon###################################################################### # product initialization __doc__=""" ZWikiPage product """ __version__='0.18.0' import os, re import Globals, OFS.Folder #from OFS.ObjectManager import customImporters import ZWikiPage, ZWikiWeb#, CMF, Parents, Regulations def initialize(context): """Initialize the ZWiki product. """ try: # register classes associated with the product context.registerClass( ZWikiPage.ZWikiPage, constructors = ( ZWikiPage.manage_addZWikiPageForm, ZWikiPage.manage_addZWikiPage), icon = 'images/ZWikiPage_icon.gif' ) #uncomment to allow zclass subclassing #context.createZClassForBase(ZWikiPage.ZWikiPage, # globals(), # nice_name=None) # not really registering Folder.. we are just adding # the "Add ZWiki Web" menu item context.registerClass( OFS.Folder.Folder, meta_type='ZWiki Web', #icon = 'images/ZWikiWeb_icon.gif' constructors = ( ZWikiWeb.manage_addZWikiWebForm, ZWikiWeb.manage_addZWikiWeb, ZWikiWeb.listWikis, ZWikiWeb.listZodbWikis, ZWikiWeb.listFsWikis, ZWikiWeb.addZWikiWebFromFs, ZWikiWeb.addZWikiWebFromZodb, ) ) # do CMF initialisation if installed try: import CMFInit CMFInit.initialize(context) except ImportError: pass # auto-install extra wiki templates to zodb #autoImport(context) except: import sys, traceback, string type, val, tb = sys.exc_info() sys.stderr.write(string.join(traceback.format_exception(type, val, tb), '')) del type, val, tb #def autoImport(context): # """Import any files in our import directory # into /Control_Panel/Products/PRODUCT. # Called at product startup and refresh. # XXX auto-refresh too ? # # How to handle versions & upgrades of imported content nicely ? # Don't want to overwrite anything the zope admin has in place. # Try to KISS.. # # Here's the plan for zwiki: sample wiki zexp filenames will end # in a version number corresponding to the zwiki release they # shipped with (eg). The imported id is based on this so newer # versions will import cleanly. The Add Zwiki Web form will use # the latest version of each sample wiki that it finds. The admin # can clean out old versions at will. # """ # # importdir = Globals.package_home(globals()) + os.sep + 'import' # if 0: #XXX not exists(importdir) # return # # # sneaky, or dumb, way to get to the zodb # obj = context.getProductHelp() # productfolder = obj.getPhysicalRoot().Control_Panel.Products['ZWiki'] # #XXX should find product name dynamically # # # based on manage_importObject # # locate a valid connection # connection=obj._p_jar # while connection is None: # obj=obj.aq_parent # connection=obj._p_jar # # # try to import any and all files found # # will fail if the object is already in the ZODB # files = os.listdir(importdir) # for filename in files: # filepath = importdir + os.sep + filename # # how do we get these to show in undo ? # #get_transaction().begin() # try: # ob=connection.importFile(filepath, customImporters=customImporters) # id = filename[:-5] # assume files are named something.zexp # productfolder._setObject(id, ob, set_owner=0) # # # try to make ownership implicit if possible # ob=productfolder._getOb(id) # ob.manage_changeOwnershipType(explicit=0) # #get_transaction().commit() # #XXX log it # # except: # #get_transaction().abort() # #XXX log it # pass # enable catalog awareness for common ZMI operations def manage_afterAdd(self, item, container): self._setCreator(getattr(self,'REQUEST',None)) # record creation info self.index_object() ZWikiPage.ZWikiPage.manage_afterAdd = manage_afterAdd def manage_afterClone(self, item): self._setCreator(getattr(self,'REQUEST',None)) # record creation info self.index_object() ZWikiPage.ZWikiPage.manage_afterClone = manage_afterClone def manage_beforeDelete(self, item, container): self.unindex_object() ZWikiPage.ZWikiPage.manage_beforeDelete = manage_beforeDelete #original_edit = ZWikiPage.ZWikiPage.manage_edit #def manage_edit(self,data,title,SUBMIT='Change',dtpref_cols='50', # dtpref_rows='20',REQUEST=None): # """Edit object an reindex""" # r = original_edit(self,data,title,SUBMIT,dtpref_cols,dtpref_rows,REQUEST) # self.reindex_object() # return r #ZWikiPage.ZWikiPage.manage_edit = manage_edit original_addProperty = ZWikiPage.ZWikiPage.manage_addProperty def manage_addProperty(self, id, value, type, REQUEST=None): """Add property and reindex""" r = original_addProperty(self,id,value,type,REQUEST) self.reindex_object() return r ZWikiPage.ZWikiPage.manage_addProperty = manage_addProperty original_delProperties = ZWikiPage.ZWikiPage.manage_delProperties def manage_delProperties(self, ids=None, REQUEST=None): """Delete properties and reindex""" r = original_delProperties(self, ids, REQUEST) self.reindex_object() return r ZWikiPage.ZWikiPage.manage_delProperties = manage_delProperties original_changeProperties = ZWikiPage.ZWikiPage.manage_changeProperties def manage_changeProperties(self, REQUEST=None, **kw): """Update properties and reindex""" #r = original_changeProperties(self, REQUEST, **kw) r = apply(original_changeProperties,(self, REQUEST), kw) self.reindex_object() return r ZWikiPage.ZWikiPage.manage_changeProperties = manage_changeProperties original_editProperties = ZWikiPage.ZWikiPage.manage_editProperties def manage_editProperties(self, REQUEST): """Edit Properties and reindex""" r = original_editProperties(self, REQUEST) self.reindex_object() return r ZWikiPage.ZWikiPage.manage_editProperties = manage_editProperties ZWiki/refresh.txt0100644000175000017500000000000007176421413013342 0ustar simonsimonZWiki/version.txt0100644000175000017500000000001507654241605013403 0ustar simonsimonZWiki-0-18-0 ZWiki/content/0042755000175000017500000000000007654241734012643 5ustar simonsimonZWiki/content/basic/0042755000175000017500000000000007654241734013724 5ustar simonsimonZWiki/content/basic/AnnoyingQuote.stx0100644000175000017500000000024407566640635017264 0ustar simonsimon#parents:ZWiki You can change the header quote by editing the last line of this page (or adding a comment). Don't add a final newline. AnnoyingQuote: Hello, world.ZWiki/content/basic/FrontPage.stx0100644000175000017500000000543207566640635016355 0ustar simonsimon**Welcome!** This is the front page of ZWiki's zwikidotorg wiki template. Here are the HelpPage, RecentChanges, UserOptions, AnnoyingQuote and contents. You'll find additional Zwiki documentation at ZWiki:ZwikiDocs, including ZWiki:ReleaseNotes, ZWiki:KnownIssues and the ZWiki:ZwikiTracker. ZWiki:GeneralDiscussion and the #zope IRC channel are good places to get help. "Edit":FrontPage/editform this page and replace it with something appropriate. Other things you may want to configure (or not): - **Permissions**
      To view this wiki, users need 'View' and 'Access content information' permissions on the folder. To create new pages they need 'Add ZWiki Page' permission; to upload files, 'Add Documents, Images, and Files'. Permissions to add comments, edit, reparent etc. all begin with 'Zwiki: ' and appear at the end of the permissions list. - **Mail out (subscription)**
      Set up a zope !MailHost object and add a 'mail_from' folder string property containing a valid From address. A "subscribe" link will appear in the page footer. You may want to add the ZWiki:MailHostFix to make Bcc work. - **Mail in**
      You'll need to set up a special mail alias, a zope helper method, a default discussion page and 'mail_to'/'mail_replyto' folder properties. Users will be able to reply to comments by mail. See ZWiki:WikiMail. - **External (client-side) editing**
      Install the ZWiki:ExternalEditor zope product and make sure users have the 'Use external editor' permission. EE's pencil icon will appear in the page footer. Users will probably be able to edit pages with their preferred editor after installing EE's client helper app. - **A bug tracker**
      An issue tracking database integrated with the wiki. See ZWiki:HowToInstallAZwikiTracker - **A more featureful front page**
      Eg showing latest comments from other pages. See ZWiki:FrontPage source for ideas - **Security**
      If you allow untrusted users to edit your wiki, you should be aware of the server-side trojan issue. Unless carefully configured Zwiki may allow users to create content (eg DTML or javascript) which will be executed when viewed by others. For example, DTML code could delete other zope objects to which it has access (restricted to the lowest of the page owner and the page viewer's privileges). Tips: make the wiki folder's owner a low-privileged user, set the standard_page_type folder property to a non-DTML page type, remove or disable editing on any DTML-enabled pages, deny 'Zwiki: Change page types' permission. Decide for yourself whether these steps are necessary, or keeping backups will suffice, or YAGNI applies. Enjoy your wiki! ZWiki/content/basic/HelpPage.stx0100644000175000017500000001306007652362075016144 0ustar simonsimon#parents:ZWiki This is the help page for Zwiki version 0.18. The links beginning with "!ZWiki:..." lead to additional documentation on zwiki.org. "What is this ?":#whatisthis
      "User interface":#ui
      "Navigation":#navigation
      "Email subscription":#email
      "Editing":#editing
        "Formatting rules":#rules
      "Finding out more":#more
      What is this ? This is the ZWiki version of a WikiWikiWeb (or wiki), which is a kind of collaborative website. Wikis try to put as little as possible in the way of people sharing useful content in a manageable way. You can edit any page, add comments, and create new pages on the spot. Intuitive text formatting rules are used to simplify the process of writing and linking pages. The idea is to keep the text that people edit simple, so it's readable as is, and, most importantly, editable by anyone, without sacrificing attractive presentation. A zwiki site like this one may also function as a simple mail list. User interface If you see "full", "simple", "minimal" links below, you can click them to choose the UI that suits you best (cookies required). If you can't see one of the features described here, try clicking "full". Many links and form elements have ZWiki:AccessKeys (keyboard shortcuts), which are convenient if your browser supports them. You can set your user name, time zone and other things in UserOptions. Navigation Each page has a unique name, which is used for the title, url and hyperlinks between pages; these are most often made by joining capitalized words together. See ZWiki:WikiName. Links like the last one (two wikinames separated by a colon) lead to another site. In full mode, pages are arranged in a hierarchy. - To return to the front page, click the site logo - To see which pages link to this one, click the page title - To search all pages, enter text in the search field and press enter - To list pages by last edit time, visit RecentChanges (or FastChanges). - To see previous edits, click the "last edited" link - To see the page hierarchy, click the "contents" link (full mode only) - To jump to a known page, enter the first part of it's name after the last / in your browser's url field. You can ignore capitalization and spacing. Email subscription If you see a "subscribe" link below, you can subscribe to a page or the whole wiki to receive comments by mail. If configured, subscribers may also send comments by mail. - To receive comments (not edits) by mail, click "subscribe"; if necessary, enter your email address and click "Change"; click the page or wiki subscribe button to toggle your subscription status (green means you are subscribed). - To send comments, just reply, or send mail to the wiki's mail address. Your comment will appear on the original comment's page, or on the page whose WikiName appears in your mail's subject, or on the site's default mail-in page. Editing You have full editing rights to any page, unless restricted by the site admin or page owner. SandBox is the place to play with wiki page editing. You could open it in another window to test the things you read here. - Add a comment using the form at the bottom of the page - Click "edit" to change the page's text; it will be formatted and linked according to the ZWiki:TextFormattingRules. You may also be able to upload a file or choose other options here. - Create new pages by adding their WikiName to an existing page and then clicking on the question mark beside it. - After you have configured a user name, in full mode you may also be able to reparent, rename and delete pages. Use the "manage this page" form at bottom or click the page title.
      Formatting rules in a nutshell: (It's possible for the site admin or a page editor to set different rules; these are the default currently used on most zwiki sites.) 0. WikiName's, [freeform page names in brackets], http://URLs , "structured text-style links":someurl and ZWiki:RemoteWikiLinks are made into hyperlinks 0. text beginning and ending with *, **, _ or ' is *italic*, **bold**, _underlined_ or 'monospaced' respectively 0. one or more non-blank lines are run together form a paragraph; blank lines separate paragraphs 0. a one-line "paragraph" followed by a more-indented paragraph makes a heading 0. a paragraph beginning with - or * or 0. and a space makes a bullet or numbered list item; a more-indented list item starts a sub-list 0. HTML tags may be used if necessary; on sites which permit it, DTML tags (server-side code) may also be used 0. Some or all of the above rules may be escaped, by putting ! at the beginning of a link or a line !LikeThis; by enclosing text in single quotes 'LikeThis' ; or by indenting text after a paragraph ending with a double colon :: LikeThis escapes and &dtml-too;
      Finding out more ZWiki, ZWiki:FrontPage, ZWiki:ZwikiDocs, ZWiki:ZwikiFAQ, ZWiki:KnownIssues, ZWiki:ZwikiTracker Check this site's software is up-to-date with ZWiki:ZwikiAnalyzer The latest version of this page is at ZWiki:HelpPage More about wiki: WikiWikiWeb:FrontPage, WikiWikiWeb:EvolutionOfaWikizen ZWiki/content/basic/RecentChanges.stxdtml0100644000175000017500000000664207634214257020057 0ustar simonsimon#parents:ZWiki nocatalog
      Recently changed pages in this wiki. Show pages
      >
      Page Note    By    Time
      " >&dtml-title_or_id;         &dtml-last_editor; anonymous    

      pages

      Recently changed pages in this wiki. Show pages
      valign="top">
      Page Note    By    Time
      " >&dtml-title_or_id; anonymous    

      pages ZWiki/content/basic/SearchPage.stxdtml0100644000175000017500000001012307566640631017340 0ustar simonsimon#parents:ZWiki we are doing a jump blank expr - jump to front page blank expr - let the search list all skip jumping, just do a search search for an id beginning with expr found one - jump there can't find url_quote no ids matched - fall through and do a search

      !Search for:
      Results:

      &dtml-title_or_id;

      hits []

      ( hits)

      Type a search expression and hit enter to search the entire wikiweb. The expression is case-insensitive and spaces are preserved. Leave it blank to list all pages.

      Search for:


      Some other likely search tools:

      all wikis indexed by Metawiki (regexps allowed):
      Google (advanced, help):
      USENET via groups.google.com (advanced, help):
      directory.google.com (ODP plus page ranking) (help):
      images.google.com (help):
      ZWiki/content/basic/UserOptions.stxdtml0100644000175000017500000001076007643417407017636 0ustar simonsimon#parents:ZWiki As this wiki is in a CMF site, zwiki user options have been disabled. this form calls itself. use path=wiki_url() if you want per-wiki cookies *Cookies configured.* *Cookies removed.* reload UserOptions since old cookie values are still in this REQUEST Set your site preferences below. You can also change your display mode by clicking the links at bottom-left.
      User name: (identifies your edits in RecentChanges etc.) Email address: (saves time when subscribing) Editform width: height: (textarea dimensions; width will be 100% if supported) Time zone: Your local time is (localizes page modification times) Bookmarks: ">
      (appear in footer; konqueror users, separate your bookmarks with , not space) Show page hierarchy ? (This wiki maintains a page hierarchy. You can use this or ignore it.) Show search field ? (except in minimal mode) Show AnnoyingQuote ? (in full mode) Show logo ?
      ZWiki/content/basic/WikiWikiWeb.stx0100644000175000017500000000035207566640627016652 0ustar simonsimon#parents:ZWiki The WikiWikiWeb is the original and still one of the largest and most active wikis. It contains a wealth of information about wiki and many other things. WikiWikiWeb:FrontPage RemoteWikiURL: http://c2.com/cgi/wiki? ZWiki/content/basic/ZWiki.stx0100644000175000017500000000063207566640627015523 0ustar simonsimonZwiki is the software which drives this wiki site. It has been developed by "Joyful Systems":http://joyful.com and "contributors":http://zwiki.org/ZWikiContributors around the world, and is available under the GPL. For more information, see http://zwiki.org . Zwiki is inspired by the original WikiWikiWeb and powered by the "Zope":http://zope.org web application server. RemoteWikiURL: http://zwiki.org/ ZWiki/content/basic/index_html.dtml0100644000175000017500000000007107566640626016737 0ustar simonsimonZWiki/content/basic/standard_error_message.dtml0100644000175000017500000000321707566640625021325 0ustar simonsimon A custom error handler - redirect to the most likely existing page. Assumes SearchPage exists. See http://www.zope.org/Members/JohnC/StandardErrorMessage < dtml-else> < dtml-call "RESPONSE.redirect(SearchPage.page_url()+'/editform?page='+search, lock=1)">

      I could not find any likely page matching "" ""

      Click here to

      /editform" method="POST" enctype="multipart/form-data">
      " method="POST" enctype="multipart/form-data">
      or visit the front page.
      ZWiki/content/tracker/0042755000175000017500000000000007654241734014276 5ustar simonsimonZWiki/content/tracker/FilterIssues.stxdtml0100644000175000017500000002456307636724363020351 0ustar simonsimon#parents:IssueTracker More advanced IssueTracker search options.
           Total
       
      dtml madness Catalog() won't sort on title for some reason; dtml-in will but seems unreliable. Need to use dtml-in sort when combining multiple catalog results. Try both at once I guess. bgcolor="#ff2222" bgcolor="#ff6060" bgcolor="#ffbbbb" bgcolor="#ffdddd" bgcolor="#ffe0e0" bgcolor="#ffe0e0" bgcolor="#ffcc77" bgcolor="#e0f0e0" bgcolor="#e0e0e0" >
      description category severity status age modified
      ' or ''"> " >&dtml-title_or_id; ' or ''"> ' or ''"> ' or ''"> > year 1 and 's' or ''"> month 1 and 's' or ''"> week 1 and 's' or ''"> day 1 and 's' or ''"> hour 1 and 's' or ''"> minute 1 and 's' or ''"> second 1 and 's' or ''"> year 1 and 's' or ''"> month 1 and 's' or ''"> week 1 and 's' or ''"> day 1 and 's' or ''"> hour 1 and 's' or ''"> minute 1 and 's' or ''"> second 1 and 's' or ''">
      issue listed
       
      Title contains any of:
      Comments contain any of:
      Categories: Severities: Statuses:
         
      ZWiki/content/tracker/IssueTracker.stxdtml0100644000175000017500000002527307636724363020333 0ustar simonsimon

      This is the issue tracker. The last ten issues modified are shown by default.
      &dtml-sequence-item;
      &dtml-sequence-item;
      &dtml-open;/&dtml-tracked;
      open/total
      issues
      dtml madness Catalog() won't sort on title for some reason; dtml-in will but seems unreliable. Need to use dtml-in sort when combining multiple catalog results. Try both at once I guess. bgcolor="#ff2222" bgcolor="#ff6060" bgcolor="#ffbbbb" bgcolor="#ffdddd" bgcolor="#ffe0e0" bgcolor="#ffe0e0" bgcolor="#ffcc77" bgcolor="#e0f0e0" bgcolor="#e0e0e0" >
      description category severity status age modified
      ' or ''">">&dtml-title_or_id; ' or ''"> ' or ''"> ' or ''"> > year 1 and 's' or ''"> month 1 and 's' or ''"> week 1 and 's' or ''"> day 1 and 's' or ''"> hour 1 and 's' or ''"> minute 1 and 's' or ''"> second 1 and 's' or ''"> could call lastEditInterval but would have to wake up objects year 1 and 's' or ''"> month 1 and 's' or ''"> week 1 and 's' or ''"> day 1 and 's' or ''"> hour 1 and 's' or ''"> minute 1 and 's' or ''"> second 1 and 's' or ''">
      issue listed
       
      **Add an issue:**
      Description:
      Category: Severity: Status:
      Details:
      (or, mail your issue to tracker@zwiki.org)
      some problem here - properties don't get set stick with createIssue for the moment ZWiki/dtml/0042755000175000017500000000000007654241734012131 5ustar simonsimonZWiki/dtml/zwikiPageAdd.dtml0100644000175000017500000000240207555304171015342 0ustar simonsimon <dtml-var "gettext('Add ZWiki Page')">

      You may create a new ZWiki Page object using the form below. You may also choose to upload an existing html file from your local computer by clicking the Browse button.

      "> " NAME="submit">
      ZWiki/dtml/zwikiWebAdd.dtml0100644000175000017500000000300607555304171015204 0ustar simonsimon <dtml-var "gettext('Add a ZWiki web')">

      " CHECKED >  

      " style="font-weight:strong">
      ZWiki/images/0042755000175000017500000000000007654241734012436 5ustar simonsimonZWiki/images/ZWikiPage_icon.gif0100644000175000017500000000032407333222257015752 0ustar simonsimonGIF89a„ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù,Q # diЍ#<ë®Bª¶n»>1zÓöãç3ž/@$ê|.$aЇš9B&•ÔÙJœ[`«·æI³aø1V!˾vú Ӽ”™Šú‹!;ZWiki/locale/0042755000175000017500000000000007654241737012433 5ustar simonsimonZWiki/locale/fr-CA.po0100644000175000017500000000522207503666232013651 0ustar simonsimon msgid "" msgstr "" "Project-Id-Version: Zwiki 0.9.10\n" "POT-Creation-Date: 2025-05-29 01:05+PST\n" "PO-Revision-Date: 2025-06-18 18:08+0000\n" "Last-Translator: Joanne Plouffe \n" "Language-Team: French-canadian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: 8bit\n" msgid " permission is required." msgstr "Votre permission est requise." msgid "Click the Change button again" msgstr "Cliquez encore le bouton Changer" msgid "Click your browser's back button" msgstr "Cliquez le bouton Précédent de votre navigateur" msgid "Click your browser's refresh button" msgstr "Cliquez le bouton Rafraîchir de votre navigateur" msgid "Copy your recent edits to the clipboard" msgstr "Copiez vos modifications dans le presse-papiers" msgid "Edit conflict!" msgstr "Éditez le conflit!" msgid "Paste in your edits again, being mindful of the latest changes" msgstr "Collez encore vos modifications, demeurant attentif aux plus récents changements" msgid "Someone else has saved this page while you were editing" msgstr "Quelqu'un d'autre a sauvegardé cette page pendant que vous éditiez" msgid "This page was renamed to" msgstr "Cette page fut renommée" msgid "To discard your recent edit and start again, click OK" msgstr "Pour supprimer vos changements récents et recommencer, cliquez OK" msgid "To resolve the conflict, do this" msgstr "Pour résoudre le conflit, faites ceci" msgid "You are not authorized to add ZWiki Pages here." msgstr "Vous n'êtes pas autorisé à ajouter ici des pages ZWiki." msgid "You are not authorized to add a link on this ZWiki Page." msgstr "Vous n'êtes pas autorisé à ajouter un lien sur cette page ZWiki." msgid "You are not authorized to add files larger than" msgstr "Vous n'êtes pas autorisé à ajouter des fichiers plus grands que" msgid "You are not authorized to change this ZWiki Page's type." msgstr "Vous n'êtes pas autorisé à changer ce type de page ZWiki." msgid "You are not authorized to delete this ZWiki Page." msgstr "Vous n'êtes pas autorisé à supprimer cette page ZWiki." msgid "You are not authorized to edit this ZWiki Page." msgstr "Vous n'êtes pas autorisé à éditer cette page ZWiki." msgid "You are not authorized to rename this ZWiki Page. " msgstr "Vous n'êtes pas autorisé à renommer cette page ZWiki. " msgid "You are not authorized to set this ZWiki Page's regulations." msgstr "Vous n'êtes pas autorisé à établir les règlements de cette page ZWiki." msgid "You are not authorized to upload files here." msgstr "Vous n'êtes pas autorisé à télécharger ici des fichiers." msgid "here" msgstr "ici" msgid "or" msgstr "ou" ZWiki/locale/en.po0100644000175000017500000000052207550410551013352 0ustar simonsimon msgid "" msgstr "" "Project-Id-Version: Zwiki 0.9.10\n" "POT-Creation-Date: 2025-05-29 01:05+PST\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ASCII\n" "Content-Transfer-Encoding: ENCODING\n" ZWiki/locale/es.po0100644000175000017500000001131507555304171013366 0ustar simonsimonmsgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2025-10-12 15:10+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Juan David Ibáñez Palomar \n" "Language-Team: spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Add ZWiki Page" msgstr "Añadir una página ZWiki" msgid "Id" msgstr "Id" msgid "Title" msgstr "Título" msgid "File" msgstr "Fichero" msgid "Add" msgstr "Añadir" msgid "Add and Edit" msgstr "Añadir y editar" msgid "" "You may create a new ZWiki Page object using the form below. You may also " "choose to upload an existing html file from your local computer by clicking " "the Browse button." msgstr "" "Puedes crear una nueva página ZWiki utilizando este formulario. También " "puedes cargar un fichero html desde tu máquina local pulsando el botón " "Browse." msgid "Add a ZWiki web" msgstr "Añadir una web ZWiki" msgid "Type" msgstr "Tipo" msgid "Create wiki" msgstr "Crear wiki" msgid "(and enter)" msgstr "(y pulsa el retorno de carro)" #: ZWikiPage.py:1796 msgid "You are not authorized to add ZWiki Pages here." msgstr "No estás autorizado a añadir páginas ZWiki aquí." #: ZWikiPage.py:2026 msgid "You are not authorized to set this ZWiki Page's regulations." msgstr "No estás autorizado a cambiar las regulaciones de esta página Zwiki." #: ZWikiPage.py:2038 msgid "You are not authorized to change this ZWiki Page's type." msgstr "No estás autorizado a cambiar este tipo de página ZWiki." #: ZWikiPage.py:2067 ZWikiPage.py:2091 msgid "You are not authorized to edit this ZWiki Page." msgstr "No estás autorizado a editar esta página ZWiki." #: ZWikiPage.py:2094 ZWikiPage.py:2118 msgid "You are not authorized to delete this ZWiki Page." msgstr "No estás autorizado a borrar esta página ZWiki." #: ZWikiPage.py:2167 #, fuzzy msgid "You are not authorized to rename this ZWiki Page." msgstr "No estas autorizado a renombrar esta página Zwiki. " #: ZWikiPage.py:2182 #, fuzzy, python-format msgid "This page was renamed to %s.\n" msgstr "Esta página ha sido renombrada a" #: ZWikiPage.py:2215 msgid "You are not authorized to upload files here." msgstr "No estás autorizado a cargar ficheros aquí." #: ZWikiPage.py:2219 msgid "You are not authorized to add a link on this ZWiki Page." msgstr "No estás autorizado a añadir un enlace en esta página ZWiki." #: ZWikiPage.py:2649 #, fuzzy msgid "Edit conflict" msgstr "¡Conflicto de edición!" #: ZWikiPage.py:2669 msgid "Someone else has saved this page while you were editing" msgstr "Otra persona guardo esta página mientras tú estabas editando" #: ZWikiPage.py:2670 msgid "To resolve the conflict, do this" msgstr "Para resolver el conflicto haz esto" #: ZWikiPage.py:2671 msgid "Click your browser's back button" msgstr "Pulsa el botón de ir atrás de tu navegador" #: ZWikiPage.py:2672 msgid "Copy your recent edits to the clipboard" msgstr "Copia tus cambios recientes en el portapapeles" #: ZWikiPage.py:2673 msgid "Click your browser's refresh button" msgstr "Pulsa el botón de refresco de tu navegador" #: ZWikiPage.py:2674 msgid "Paste in your edits again, being mindful of the latest changes" msgstr "Pega en tus ediciones otra vez, presta atención a los últimos cambios" #: ZWikiPage.py:2675 msgid "Click the Change button again" msgstr "Pulsa el botón Cambiar otra vez" #: ZWikiPage.py:2676 msgid "or" msgstr "o" #: ZWikiPage.py:2677 #, fuzzy msgid "To discard your changes and start again, click OK" msgstr "Para descartar tus cambios recientes y volver a empezar, pulsa en Ok" #: ZWikiPage.py:2685 msgid "Page is locked" msgstr "" #: ZWikiPage.py:2696 msgid "" "\n" " This page has a webDAV lock. Someone is probably editing it\n" " with an external editor. You'll need to wait until they've\n" " finished and then try again. If you've just made some changes,\n" " you may want to back up and copy your version of the text for\n" " reference.\n" " " msgstr "" #: ZWikiPage.py:2703 #, fuzzy msgid "To discard your changes and try again, click OK." msgstr "Para descartar tus cambios recientes y volver a empezar, pulsa en Ok" #: ZWikiPage.py:3090 #, fuzzy msgid "You are not authorized to upgrade all pages." msgstr "No estás autorizado a cargar ficheros aquí." #: ZWikiPage.py:3091 msgid "(folder -> Manage properties)" msgstr "" #~ msgid "Add a ZWiki web.." #~ msgstr "Añadir una web ZWiki.." #~ msgid " permission is required." #~ msgstr " permiso requerido." #~ msgid "You are not authorized to add files larger than" #~ msgstr "No estás autorizado a añadir ficheros más largos que" #~ msgid "here" #~ msgstr "aquí" ZWiki/locale/locale.pot0100644000175000017500000000641307555304171014405 0ustar simonsimon# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2025-10-12 15:10+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" msgid "Add ZWiki Page" msgstr "" msgid "Id" msgstr "" msgid "Title" msgstr "" msgid "File" msgstr "" msgid "Add" msgstr "" msgid "Add and Edit" msgstr "" msgid "" "You may create a new ZWiki Page object using the form below. You may also " "choose to upload an existing html file from your local computer by clicking " "the Browse button." msgstr "" msgid "Add a ZWiki web" msgstr "" msgid "Type" msgstr "" msgid "Create wiki" msgstr "" msgid "(and enter)" msgstr "" #: ZWikiPage.py:1796 msgid "You are not authorized to add ZWiki Pages here." msgstr "" #: ZWikiPage.py:2026 msgid "You are not authorized to set this ZWiki Page's regulations." msgstr "" #: ZWikiPage.py:2038 msgid "You are not authorized to change this ZWiki Page's type." msgstr "" #: ZWikiPage.py:2067 ZWikiPage.py:2091 msgid "You are not authorized to edit this ZWiki Page." msgstr "" #: ZWikiPage.py:2094 ZWikiPage.py:2118 msgid "You are not authorized to delete this ZWiki Page." msgstr "" #: ZWikiPage.py:2167 msgid "You are not authorized to rename this ZWiki Page." msgstr "" #: ZWikiPage.py:2182 #, python-format msgid "This page was renamed to %s.\n" msgstr "" #: ZWikiPage.py:2215 msgid "You are not authorized to upload files here." msgstr "" #: ZWikiPage.py:2219 msgid "You are not authorized to add a link on this ZWiki Page." msgstr "" #: ZWikiPage.py:2649 msgid "Edit conflict" msgstr "" #: ZWikiPage.py:2669 msgid "Someone else has saved this page while you were editing" msgstr "" #: ZWikiPage.py:2670 msgid "To resolve the conflict, do this" msgstr "" #: ZWikiPage.py:2671 msgid "Click your browser's back button" msgstr "" #: ZWikiPage.py:2672 msgid "Copy your recent edits to the clipboard" msgstr "" #: ZWikiPage.py:2673 msgid "Click your browser's refresh button" msgstr "" #: ZWikiPage.py:2674 msgid "Paste in your edits again, being mindful of the latest changes" msgstr "" #: ZWikiPage.py:2675 msgid "Click the Change button again" msgstr "" #: ZWikiPage.py:2676 msgid "or" msgstr "" #: ZWikiPage.py:2677 msgid "To discard your changes and start again, click OK" msgstr "" #: ZWikiPage.py:2685 msgid "Page is locked" msgstr "" #: ZWikiPage.py:2696 msgid "" "\n" " This page has a webDAV lock. Someone is probably editing it\n" " with an external editor. You'll need to wait until they've\n" " finished and then try again. If you've just made some changes,\n" " you may want to back up and copy your version of the text for\n" " reference.\n" " " msgstr "" #: ZWikiPage.py:2703 msgid "To discard your changes and try again, click OK." msgstr "" #: ZWikiPage.py:3090 msgid "You are not authorized to upgrade all pages." msgstr "" #: ZWikiPage.py:3091 msgid "(folder -> Manage properties)" msgstr "" ZWiki/locale/fr.po0100644000175000017500000000611107555304171013364 0ustar simonsimonmsgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2025-10-12 15:10+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: ENCODING\n" msgid "Add ZWiki Page" msgstr "" msgid "Id" msgstr "" msgid "Title" msgstr "" msgid "File" msgstr "" msgid "Add" msgstr "" msgid "Add and Edit" msgstr "" msgid "" "You may create a new ZWiki Page object using the form below. You may also " "choose to upload an existing html file from your local computer by clicking " "the Browse button." msgstr "" msgid "Add a ZWiki web" msgstr "" msgid "Type" msgstr "" msgid "Create wiki" msgstr "" msgid "(and enter)" msgstr "" #: ZWikiPage.py:1796 msgid "You are not authorized to add ZWiki Pages here." msgstr "" #: ZWikiPage.py:2026 msgid "You are not authorized to set this ZWiki Page's regulations." msgstr "" #: ZWikiPage.py:2038 msgid "You are not authorized to change this ZWiki Page's type." msgstr "" #: ZWikiPage.py:2067 ZWikiPage.py:2091 msgid "You are not authorized to edit this ZWiki Page." msgstr "" #: ZWikiPage.py:2094 ZWikiPage.py:2118 msgid "You are not authorized to delete this ZWiki Page." msgstr "" #: ZWikiPage.py:2167 msgid "You are not authorized to rename this ZWiki Page." msgstr "" #: ZWikiPage.py:2182 #, python-format msgid "This page was renamed to %s.\n" msgstr "" #: ZWikiPage.py:2215 msgid "You are not authorized to upload files here." msgstr "" #: ZWikiPage.py:2219 msgid "You are not authorized to add a link on this ZWiki Page." msgstr "" #: ZWikiPage.py:2649 msgid "Edit conflict" msgstr "" #: ZWikiPage.py:2669 msgid "Someone else has saved this page while you were editing" msgstr "" #: ZWikiPage.py:2670 msgid "To resolve the conflict, do this" msgstr "" #: ZWikiPage.py:2671 msgid "Click your browser's back button" msgstr "" #: ZWikiPage.py:2672 msgid "Copy your recent edits to the clipboard" msgstr "" #: ZWikiPage.py:2673 msgid "Click your browser's refresh button" msgstr "" #: ZWikiPage.py:2674 msgid "Paste in your edits again, being mindful of the latest changes" msgstr "" #: ZWikiPage.py:2675 msgid "Click the Change button again" msgstr "" #: ZWikiPage.py:2676 msgid "or" msgstr "" #: ZWikiPage.py:2677 msgid "To discard your changes and start again, click OK" msgstr "" #: ZWikiPage.py:2685 msgid "Page is locked" msgstr "" #: ZWikiPage.py:2696 msgid "" "\n" " This page has a webDAV lock. Someone is probably editing it\n" " with an external editor. You'll need to wait until they've\n" " finished and then try again. If you've just made some changes,\n" " you may want to back up and copy your version of the text for\n" " reference.\n" " " msgstr "" #: ZWikiPage.py:2703 msgid "To discard your changes and try again, click OK." msgstr "" #: ZWikiPage.py:3090 msgid "You are not authorized to upgrade all pages." msgstr "" #: ZWikiPage.py:3091 msgid "(folder -> Manage properties)" msgstr "" ZWiki/locale/ga.po0100644000175000017500000000615007555304171013347 0ustar simonsimonmsgid "" msgstr "" "Project-Id-Version: Zwiki 0.9.10\n" "POT-Creation-Date: 2025-10-12 15:10+CET\n" "PO-Revision-Date: 2025-05-28 18:20-0700\n" "Last-Translator: Simon Michael \n" "Language-Team: Irish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: 8bit\n" msgid "Add ZWiki Page" msgstr "" msgid "Id" msgstr "" msgid "Title" msgstr "" msgid "File" msgstr "" msgid "Add" msgstr "" msgid "Add and Edit" msgstr "" msgid "" "You may create a new ZWiki Page object using the form below. You may also " "choose to upload an existing html file from your local computer by clicking " "the Browse button." msgstr "" msgid "Add a ZWiki web" msgstr "" msgid "Type" msgstr "" msgid "Create wiki" msgstr "" msgid "(and enter)" msgstr "" #: ZWikiPage.py:1796 msgid "You are not authorized to add ZWiki Pages here." msgstr "" #: ZWikiPage.py:2026 msgid "You are not authorized to set this ZWiki Page's regulations." msgstr "" #: ZWikiPage.py:2038 msgid "You are not authorized to change this ZWiki Page's type." msgstr "" #: ZWikiPage.py:2067 ZWikiPage.py:2091 msgid "You are not authorized to edit this ZWiki Page." msgstr "" #: ZWikiPage.py:2094 ZWikiPage.py:2118 msgid "You are not authorized to delete this ZWiki Page." msgstr "" #: ZWikiPage.py:2167 msgid "You are not authorized to rename this ZWiki Page." msgstr "" #: ZWikiPage.py:2182 #, python-format msgid "This page was renamed to %s.\n" msgstr "" #: ZWikiPage.py:2215 msgid "You are not authorized to upload files here." msgstr "" #: ZWikiPage.py:2219 msgid "You are not authorized to add a link on this ZWiki Page." msgstr "" #: ZWikiPage.py:2649 #, fuzzy msgid "Edit conflict" msgstr "Eagarthoireacht i dtreis!" #: ZWikiPage.py:2669 msgid "Someone else has saved this page while you were editing" msgstr "" #: ZWikiPage.py:2670 msgid "To resolve the conflict, do this" msgstr "" #: ZWikiPage.py:2671 msgid "Click your browser's back button" msgstr "" #: ZWikiPage.py:2672 msgid "Copy your recent edits to the clipboard" msgstr "" #: ZWikiPage.py:2673 msgid "Click your browser's refresh button" msgstr "" #: ZWikiPage.py:2674 msgid "Paste in your edits again, being mindful of the latest changes" msgstr "" #: ZWikiPage.py:2675 msgid "Click the Change button again" msgstr "" #: ZWikiPage.py:2676 msgid "or" msgstr "" #: ZWikiPage.py:2677 msgid "To discard your changes and start again, click OK" msgstr "" #: ZWikiPage.py:2685 msgid "Page is locked" msgstr "" #: ZWikiPage.py:2696 msgid "" "\n" " This page has a webDAV lock. Someone is probably editing it\n" " with an external editor. You'll need to wait until they've\n" " finished and then try again. If you've just made some changes,\n" " you may want to back up and copy your version of the text for\n" " reference.\n" " " msgstr "" #: ZWikiPage.py:2703 msgid "To discard your changes and try again, click OK." msgstr "" #: ZWikiPage.py:3090 msgid "You are not authorized to upgrade all pages." msgstr "" #: ZWikiPage.py:3091 msgid "(folder -> Manage properties)" msgstr "" ZWiki/skins/0042755000175000017500000000000007654241743012320 5ustar simonsimonZWiki/skins/default/0042755000175000017500000000000007654241742013743 5ustar simonsimonZWiki/skins/default/backlinks.dtml0100644000175000017500000000721307637377511016571 0ustar simonsimon <dtml-var "aq_parent.title"><dtml-var title_or_id> backlinks

        Backlinks for &dtml-title_or_id;

      dontusecatalog

      Here are the pages which link to :

      You can also set this page's parent(s), to locate it within the contents hierarchy. Check the boxes or enter a new page name and click Reparent. A single parent is usually sufficient.

      1. CHECKED>  

      Other parent:

      Here are the pages which link to :

      You can also set this page's parent(s), to locate it within the contents hierarchy. Check the boxes or enter a new page name and click Reparent. A single parent is usually sufficient.

      1. CHECKED>  

      Other parent:

      ZWiki/skins/default/backlinks.pt0100644000175000017500000000472607641224562016252 0ustar simonsimon Sitename Pagename backlinks..

      Backlinks for Pagename..

      Here are the pages which link to thispage:

      You can also set this page's parent(s), to locate it within the contents hierarchy. Check the boxes or enter a new page name and click Reparent. A single parent is usually sufficient.

      1. blpage


      Other parent:

      ZWiki/skins/default/contentspage.dtml0100644000175000017500000000144307637377511017321 0ustar simonsimon <dtml-var "aq_parent.title"> wiki contents

        Wiki contents

      Singletons

      ZWiki/skins/default/contentspage.pt0100644000175000017500000000224507640716526017002 0ustar simonsimon Sitename wiki contents..

      Wiki contents

      page hierarchy..

      Singletons

      • singleton..
      ZWiki/skins/default/diffform.dtml0100644000175000017500000000253107640701557016415 0ustar simonsimon <dtml-var "aq_parent.title"><dtml-var id> diffs

        Diffs for &dtml-title_or_id;

      1">

      
      
      ZWiki/skins/default/diffform.pt0100644000175000017500000000344407640716302016076 0ustar simonsimon Sitename Pagename diffs..

      Diffs for Pagename..

      log note..
      diff..
      
      ZWiki/skins/default/editform.dtml0100644000175000017500000002733307641220277016435 0ustar simonsimon <dtml-var "aq_parent.title"> <dtml-if "action == 'Create'"> creating<dtml-else> editing</dtml-if> &dtml-id;

        Creating Editing &dtml-id;

      Optional note:   value=" Create " title="create this wiki page" value=" Save " title="change this wiki page" tabindex="3" style="font-weight:bold" accesskey="s">
      Upload a file or image:
      Render this page as:
      Attributes
      Name: Local Owner&dtml-plural;: &dtml-niceowners;
      Type: &dtml-page_type;
      Rename and Delete
      No Delete or Rename forms for FrontPage - it can not be removed. You can not rename or delete this page. do so.
      Rename to:
       
      Confirm delete  
      Regulations
      This is where wiki page and folder owners choose who can do which of the common wiki operations on the page. The operations include editing the page, creating new pages from it, commenting on it, and resituating it (renaming it, deleting, or changing its designated parents). You can set see the page regulations below.

      See RegulatingYourPages for more info.


      Regulations:
      Role categories: ... new pages from this one. Change the text. Append text to end. Rename, delete, reparent.
      Additional allowed users:
      Propagate to offspring: readonly onkeydown="this.blur(); return false;" >
      Who owns pages created
      from this one:
      DISABLED CHECKED > Only creator of new page
      DISABLED CHECKED> Only original page owner(s)
      DISABLED CHECKED> Both original page owner(s) and sub-page creators

      See HelpPage or TextFormattingRules for help
      ZWiki/skins/default/editform.pt0100644000175000017500000001064307641222337016113 0ustar simonsimon Creating/Editing Pagename

      Creating/Editing Pagename

      Optional note:
      Upload a file or image:
      Render this page as:

      See HelpPage or TextFormattingRules for help
      ZWiki/skins/default/standard_wiki_footer.dtml0100644000175000017500000001266207566640205021027 0ustar simonsimon

      experimental zwiki.org feature
      bgcolor="#dddddd" >
      full simple minimal       help , subscribe , edit
       
      with heading checked
      >
      manage this page:

      update backlinks
      leave placeholder
      ZWiki/skins/default/standard_wiki_header.dtml0100644000175000017500000001207707637373717020773 0ustar simonsimon <dtml-var "aq_parent.title"><dtml-var title_or_id>
      subscribers
      bgcolor="#f0f0f0" >
      added complicated checking so as not to hyperlink the title to nowhere when creating a page &dtml-title_or_id; in "simple" mode, show last editor & search in place of quote
       
      ZWiki/skins/default/subscribeform.dtml0100644000175000017500000000760407640714560017472 0ustar simonsimon <dtml-var "aq_parent.title"><dtml-var id> subscription

        Mail subscription

      You can subscribe to this page or to the whole wiki. Subscribers receive a copy of comments via email (but not general edits, unless enabled by the site admin). Or return to &dtml-id;.

      Your email address:

      bgcolor="#88ff88" bgcolor="#ff7777" > You are currently subscribed
      not subscribed
      to this page.

      bgcolor="#88ff88" bgcolor="#ff7777" > You are currently subscribed
      not subscribed
      to the whole wiki.

      You are subscribed to these other pages:
      You have no other page subscriptions.

      subscribers:



      wiki subscribers:



      ZWiki/skins/default/subscribeform.pt0100644000175000017500000001034307640734640017150 0ustar simonsimon <title tal:content="string:${container/title} ${here/title_or_id} subscription"> Sitename Pagename subscription..

      Mail subscription

      You can subscribe to this page or to the whole wiki. Subscribers receive a copy of comments via email (but not general edits, unless enabled by the site admin). Or return to Page name.

      Your email address:

      You are currently to this page.

      You are currently to the whole wiki.

      You are subscribed to these other pages:
      pagename

      subscribers:


      wiki subscribers:

      ZWiki/skins/default/wikipage.zpt0100644000175000017500000003527707652362553016315 0ustar simonsimon <span tal:replace="container/title">wiki name </span><span tal:replace="here/title_or_id">page name</span>
      N subscribers last edited n days ago by by so-and-so
      • context
        • page name
      or page name annoying quote
      or
       

      last edited n days ago by by so-and-so
      or:
      page name

      or:
      • context
        • page name
      page body goes here..


      full simple minimal     user's bookmarks or UserOptions RecentChanges   validate help (un)subscribe edit
      subject:
      subject:   with heading



      manage this page - leave placeholder update all pages
      ZWiki/skins/zwiki_cmf/0042755000175000017500000000000007654241743014302 5ustar simonsimonZWiki/skins/zwiki_cmf/editform.pt0100644000175000017500000000776607635012051016456 0ustar simonsimon yuck... editform is actually a method, and it calls us with (page, request, action=action, text=text, id=id, page=page, oldid=oldid)

      Creating Editing page

      In case master has no "header" macro.
       
      Optional note:  
      Upload a file or image:
      Render this page as:
      In case master has no "footer" macro.
      ZWiki/skins/zwiki_cmf/wikifolder_edit.py0100644000175000017500000000121207626477511020012 0ustar simonsimon## Script (Python) "wikifolder_edit" ##parameters=title, description, choice=' Change ' ##title=Edit a wikifolder context.edit( title=title, description=description) qst='portal_status_message=Wiki+Folder+changed.' if choice == ' Change and View ': target_action = context.getTypeInfo().getActionById( 'view' ) else: target_action = context.getTypeInfo().getActionById( 'edit' ) context.REQUEST.RESPONSE.redirect( '%s/%s?%s' % ( context.absolute_url() , target_action , qst ) ) ZWiki/skins/zwiki_cmf/wikifolder_edit_form.pt0100644000175000017500000000212607626477507021042 0ustar simonsimon

      Edit: My ID

      Title
      Description

      ZWiki/skins/zwiki_cmf/wikipage_footer.pt0100644000175000017500000000704207626477507020033 0ustar simonsimon


        user's bookmarks or UserOptions RecentChanges   help subscribe edit
      ZWiki/skins/zwiki_cmf/wikipage_header.pt0100644000175000017500000000163007626477506017761 0ustar simonsimon
      • context
        • page name
      N subscribers
      last edited n days ago by by so-and-so
      ZWiki/skins/zwiki_cmf/wikipage_icon.gif0100644000175000017500000000032407566637242017600 0ustar simonsimonGIF89a„ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù,Q # diЍ#<ë®Bª¶n»>1zÓöãç3ž/@$ê|.$aЇš9B&•ÔÙJœ[`«·æI³aø1V!˾vú Ӽ”™Šú‹!;ZWiki/skins/zwiki_cmf/wikipage_view.pt0100644000175000017500000000157407566637242017510 0ustar simonsimon
      In case master has no "header" macro.
      In case master has no "footer" macro.
      ZWiki/skins/zwiki_plone/0042755000175000017500000000000007654241744014653 5ustar simonsimonZWiki/skins/zwiki_plone/backlinks.pt0100644000175000017500000000444307643643413017156 0ustar simonsimon

      Backlinks for Pagename..

      Here are the pages which link to thispage:

      You can also set this page's parent(s), to locate it within the contents hierarchy. Check the boxes or enter a new page name and click Reparent. A single parent is usually sufficient.

      1. blpage

      Other parent:

      ZWiki/skins/zwiki_plone/contentspage.pt0100644000175000017500000000136007644033042017672 0ustar simonsimon

      Page Hierarchy

      page hierarchy..

      Singletons

      Singletons are pages that are not connected to other pages.
      • singleton..
      ZWiki/skins/zwiki_plone/diffform.pt0100644000175000017500000000274507644107102017003 0ustar simonsimon

      History for Pagename..

      log note..
      diff..
      
      ZWiki/skins/zwiki_plone/editform.pt0100644000175000017500000001046107643571756017035 0ustar simonsimon

      Creating Editing page

      In case master has no "header" macro.
      Optional note:
      Upload a file or image:
      Render this page as:
      In case master has no "footer" macro.
      ZWiki/skins/zwiki_plone/subscribeform.pt0100644000175000017500000000775507643655200020070 0ustar simonsimon

      Mail subscription

      You can subscribe to this page or to the whole wiki. Subscribers receive a copy of comments via email (but not general edits, unless enabled by the site admin). Or return to Page name.

      Your email address:
      (You are currently to this page.)
      (You are currently to the whole wiki.)

      You are subscribed to these other pages:
      pagename

      Subscribers


      Wiki Subscribers


      ZWiki/skins/zwiki_plone/wiki_icon.gif0100644000175000017500000000164407640676273017321 0ustar simonsimonGIF89a÷NNN¨oÿ ñï¶Wâã*çèEííqíí{‹‹‹îî†îî‡ïï‘ðð›ùâ¼ðð£òò­ôôÀõõËööÐøøÚùùâúúçúúëúúíýýôþþúÿÿÿ!ù,�H° Áƒ(D€p  ÐP‚ 2À‘ã‡B6Ð02$D-jÈ€a€† <` `£@rº �ÓÁ‚6¸ ¹3d€‚"Àt§Óœ-b°@ahQ¢7/T˜0¡éK¦¢"Шq§Æ¨:ª 6eÅ·;ZWiki/skins/zwiki_plone/wikifolder_edit.py0100644000175000017500000000121207640676275020366 0ustar simonsimon## Script (Python) "wikifolder_edit" ##parameters=title, description, choice=' Change ' ##title=Edit a wikifolder context.edit( title=title, description=description) qst='portal_status_message=Wiki+Folder+changed.' if choice == ' Change and View ': target_action = context.getTypeInfo().getActionById( 'view' ) else: target_action = context.getTypeInfo().getActionById( 'edit' ) context.REQUEST.RESPONSE.redirect( '%s/%s?%s' % ( context.absolute_url() , target_action , qst ) ) ZWiki/skins/zwiki_plone/wikifolder_edit_form.pt0100644000175000017500000000243007640676276021410 0ustar simonsimon

      Edit: My ID

      Title
      Description
      ZWiki/skins/zwiki_plone/wikipage_footer.pt0100644000175000017500000000406207654240017020363 0ustar simonsimon

      Subject:
      ZWiki/skins/zwiki_plone/wikipage_header.pt0100644000175000017500000000103107640676276020325 0ustar simonsimon
        The navigation trail
      N subscribers
      last edited n days ago by by so-and-so ZWiki/skins/zwiki_plone/wikipage_icon.gif0100644000175000017500000000167207640676276020162 0ustar simonsimonGIF89a÷!!!q¬í¨oÿ ñï¶W‹‹‹¢Éó©Ìô¯Ðõ´Ôö¶Ôõ»Ø÷¼ØöÂÛ÷ÃÜ÷ÈßøÉàøÊàøËáøÏâùÏãùÑäùÐäúÖçú×çú×èúÜêûÜëúÝëûÝìûâîûãîûèñüèòüíõüíõýîõýñ÷ýóøýÿÿÿ!ù,—H° Àœ€°!€‚N(`bD<<q"‰‹"jH äÂ’0Àrã ‰ _ ¨!€Í�Lœ(Áƒ6(±ó$‚¡¦™”(ÂC΢ H-ðAÃ… XK8È…M'¾
      In case master has no "header" macro.
      In case master has no "footer" macro.
      ZWiki/tests/0042755000175000017500000000000007654241745012335 5ustar simonsimonZWiki/tests/__init__.py0100644000175000017500000000324307456100626014433 0ustar simonsimon# Unit tests for ZWiki. These have been through a few contortions. # Working on simplifying them and conforming to the latest One True Way # (running at the command line via testrunner.py). # # Old notes: # # how to do authentication/permissions-related testing ? # # test functionality of all the different page types, with no, good & # bad dtml # # test edit's various cases # # test leading & trailing newline preservation # # test initial http-like-header: preservation # # test whether stx heading on first line works # # test all the various kinds of links - mailto, http, wikiname, # remotewikilink, stx, combinations.. # # test handling of large cookies, empty cookies, missing cookies by # UserOptions, editform, standard_wiki_header... # # test rendering without dtml methods and with various other versions # of the methods # # test with other zope versions (at least product startup). Test # with/without ZWikiWebs; ZUnit; doctest; other ZWiki versions; other # related products # # test zwiki_base,page_url with & without a SiteRoot # # test permissions # # the prerelease checklist on http://zwiki.org/ZWikiWebs # # check all version nos. are correct (special prerelease test ?) # # what's the best way to handle sometimes-applicable tests - like the # above, or testing virtual hosting only if SiteAccess is found # # tests that would catch eg the deepappend bug that broke page # creation from a top-level page, eg: visit both # TestPage/editform?page=NewPage and # TestPage/SubPage/editform?page=NewPage, in a basic wiki (simple # title) and a zwikidotorg wiki (with show_hierarchy enabled) # (SubPage should be a child of TestPage, add this to setup) ZWiki/tests/support.py0100644000175000017500000000665407634751764014436 0ustar simonsimon# common initialization and support classes for zwiki unit tests import string, re, os, sys import unittest # support older pyunit/python/zope # def outside if clause for imenu def assertEquals(self,first,second, msg=None): if first != second: raise AssertionError, (msg or '%s != %s' % (first, second)) if not hasattr(unittest.TestCase,'assertEquals'): unittest.TestCase.assertEqual = \ unittest.TestCase.assertEquals = assertEquals # allow INSTANCE_HOME products to be found with normal zope, testrunner, # python testModule.py, python1.5, python2... # NB testrunner clobbers INSTANCE_HOME, we assume it's the grandparent # of the current directory import os def pdir(path): return os.path.split(path)[0] thisProductsDir = pdir(pdir(pdir(os.path.abspath(__file__)))) import Products Products.__path__.insert(0,thisProductsDir) # needs to be imported early to set up Persistence.Persistent import ZODB # mock objects import OFS.ObjectManager, AccessControl.User, DateTime from ZPublisher.HTTPRequest import HTTPRequest from ZPublisher.HTTPResponse import HTTPResponse from Products import ZWiki from Products.ZWiki.ZWikiPage import ZWikiPage, ZOPEVERSION from Products.ZWiki.Mail import MailSupport class MockUser: def __init__(self,username='testuser'): self.username = username class aclusers: pass self.acl_users = aclusers() self.acl_users._nobody = 'nobody' def getUserName(self): return self.username class MockRequest(HTTPRequest): """ a mock HTTPRequest object for use in testing. like makerequest without the app dependency """ def __init__(self): resp = HTTPResponse(stdout=sys.stdout) environ={} environ['SERVER_NAME']='foo' environ['SERVER_PORT']='80' environ['REQUEST_METHOD'] = 'GET' environ['SCRIPT_NAME']='/foo/test' HTTPRequest.__init__(self,None,environ,resp) class MockZWikiPage(ZWikiPage): """ A mock ZWikiPage object for use in testing. We fake acquisition below, good enough for some tests; to get the real thing, use p = MockZWikiPage().aq_parent.TestPage . Problems: page methods like standard_wiki_header appear as attributes of the parent folder, breaking tests. Some zopish things don't work and are too much work to mock. """ def __init__(self, source_string='', mapping=None, __name__='TestPage', **vars): apply(ZWikiPage.__init__, (self,source_string,mapping,__name__),vars) self.REQUEST = MockRequest() self._folder = OFS.Folder.Folder() self._folder.aq_parent = None # no use (see testCreateWithFileUpload) self.aq_parent = self._folder self.aq_parent.__class__.manage_addFolder = OFS.Folder.manage_addFolder self.aq_parent._setObject(self.getId(),self,set_owner=0) self.aq_inner = self self.aq_base = self def wikipage(self,body=None): return '' def getPhysicalPath(self): return ('',) def folder(self): return self._folder def _checkPermission(self, permission, object): return 1 ZopeTime = DateTime.DateTime def cb_isMoveable(self): return 1 # MZP confuses the real isIssue def isIssue(self,client=None,REQUEST=None,RESPONSE=None,**kw): if (self.page_type == 'issuedtml' or re.match(r'^IssueNo[0-9]+',self.title_or_id())): return 1 else: return 0 ZWiki/tests/testCataloging.py0100644000175000017500000001474507636654732015671 0ustar simonsimonfrom support import * #from ZCatalog import ZCatalog,Vocabulary #from ZCatalog.Catalog import Catalog,CatalogError class CatalogingTests(unittest.TestCase): # cf /usr/lib/zope/lib/python/Products/ZCatalog/tests/testCatalog.py # # we have various ZMI and zwiki methods # for adding, deleting, editing etc. # Easier to write doctests method by method ? # # these assume a default catalog is present and being used # beware of existing catalog data screwing things up, maybe # we can create our own (& set SITE_CATALOG) in zc def setUp(self): pass def tearDown(self): pass # def testOkWithoutCatalog(self): # pass # def testOkWithoutSiteCatalogProperty(self): # pass # MZP doesn't support getPhysicalPath properly #def test_cataloging_path(self): # p = MockZWikiPage().aq_parent.TestPage # print 'p',p.url() # q = p.pageWithId('TestPage') # print 'q',q.url() #>>> # the doctest setup/teardown creates & destroys TestPage - #>>> # use another in case of confusion #>>> # check everything is clean before we start #>>> import ZODB, ZCatalog, ZCatalog.Catalog #>>> c = ZCatalog.Catalog.Catalog(id='CatalogTestPage') #[] #>>> Zc.Catalog(PrincipiaSearchSource="blah") #[] #>>> zc.Catalog(PrincipiaSearchSource="blob") #[] #>>> # add a page via ZMI #>>> ZWiki.manage_addZWikiPage('CatalogTestPage',title='test page',\ # file='blah') #'' #>>> zc.Catalog(id='CatalogTestPage')[0][0] #'CatalogTestPage' #>>> zc.Catalog(PrincipiaSearchSource="blah")[0][0] #'CatalogTestPage' #>>> zc.Catalog(PrincipiaSearchSource="blob") #[] #>>> # edit a page via ZMI #>>> zc.CatalogTestPage.manage_edit('blob','') #>>> zc.Catalog(PrincipiaSearchSource="blah") #[] #>>> zc.Catalog(PrincipiaSearchSource="blob")[0][0] #'CatalogTestPage' #>>> # delete a page via ZMI #>>> zc.manage_delObjects(['CatalogTestPage']) #>>> zc.Catalog(id='CatalogTestPage') #[] #def testCatalogDoctests(self): #XXX breaks on zwiki.org #>>> # the doctest setup/teardown creates & destroys TestPage - #>>> # use another in case of confusion #>>> # check everything is clean before we start #>>> zc.Catalog(id='CatalogTestPage') #[] #>>> zc.Catalog(PrincipiaSearchSource="blah") #[] #>>> zc.Catalog(PrincipiaSearchSource="blob") #[] #>>> # add a page via ZMI #>>> ZWiki.manage_addZWikiPage('CatalogTestPage',title='test page',\ # file='blah') #'' #>>> zc.Catalog(id='CatalogTestPage')[0][0] #'CatalogTestPage' #>>> zc.Catalog(PrincipiaSearchSource="blah")[0][0] #'CatalogTestPage' #>>> zc.Catalog(PrincipiaSearchSource="blob") #[] #>>> # edit a page via ZMI #>>> zc.CatalogTestPage.manage_edit('blob','') #>>> zc.Catalog(PrincipiaSearchSource="blah") #[] #>>> zc.Catalog(PrincipiaSearchSource="blob")[0][0] #'CatalogTestPage' #>>> # delete a page via ZMI #>>> zc.manage_delObjects(['CatalogTestPage']) #>>> zc.Catalog(id='CatalogTestPage') #[] #XXX breaks on zwiki.org # def testAutoCatalogingWithZMIMethods(self): # """ # shag this, let's go with doctest # it's easier to see what we're actually getting # # XXX not currently doc-tested - see CatalogAwareness.py # # even so there is a slight dilemma: # - use assert and get meaningful messages, or # - use the style below and always see what you got ? # nice to find an economical way to do both # # now I tell you what might also be nice - the ability to inline # doctests with real code, and have them executed along with the # rest when python is run in debug mode. # # >>> # the doctest setup/teardown creates & destroys TestPage - # >>> # use another in case of confusion # >>> # check everything is clean before we start # >>> zc.Catalog(id='CatalogTestPage') # [] # >>> zc.Catalog(PrincipiaSearchSource="blah") # [] # >>> zc.Catalog(PrincipiaSearchSource="blob") # [] # >>> # add a page via ZMI # >>> ZWiki.manage_addZWikiPage('CatalogTestPage',title='test page',\ # file='blah') # '' # >>> zc.Catalog(id='CatalogTestPage')[0][0] # 'CatalogTestPage' # >>> zc.Catalog(PrincipiaSearchSource="blah")[0][0] # 'CatalogTestPage' # >>> zc.Catalog(PrincipiaSearchSource="blob") # [] # >>> # edit a page via ZMI # >>> zc.CatalogTestPage.manage_edit('blob','') # >>> zc.Catalog(PrincipiaSearchSource="blah") # [] # >>> zc.Catalog(PrincipiaSearchSource="blob")[0][0] # 'CatalogTestPage' # >>> # delete a page via ZMI # >>> zc.manage_delObjects(['CatalogTestPage']) # >>> zc.Catalog(id='CatalogTestPage') # [] # >>> # who cleans this up if we don't ? # """ # pass # def testAutoCatalogingWithZWikiMethods(self): # """ # try this one in zunit # as noted above, these really expect to start with a clean Catalog # """ # zc = self.ZopeContext # assert not zc.Catalog(id='CatalogTestPage'), \ # 'uncreated page found in the catalog' # # zc.manage_addProduct['ZWiki'].manage_addZWikiPage('CatalogTestPage1') # zc.CatalogTestPage1.edit(page='CatalogTestPage',text='bleh') # assert zc.Catalog(id='CatalogTestPage'), \ # 'page not found in catalog after creation by edit' # assert zc.Catalog(PrincipiaSearchSource="bleh"), \ # 'page text not found in catalog after creation by edit' # assert not zc.Catalog(PrincipiaSearchSource="blib"), \ # 'dummy text found in catalog after creation by edit' # # zc.CatalogTestPage.append(text='blib') # assert zc.Catalog(PrincipiaSearchSource="blib"), \ # 'new text not found in catalog after append' # # zc.CatalogTestPage.edit(text='DeleteMe') # assert not hasattr(zc,'CatalogTestPage'), 'ack! page not deleted' # assert not zc.Catalog(id='CatalogTestPage'), \ # 'page still found in catalog after DeleteMe edit' def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(CatalogingTests)) return suite if __name__ == '__main__': unittest.TextTestRunner().run(test_suite()) ZWiki/tests/testMailin.py0100644000175000017500000002231307620302204014771 0ustar simonsimon# unit tests for the mailin external method from support import * import sys Extensions_dir = os.path.join(os.path.dirname(ZWiki.__file__),'Extensions') sys.path.append(Extensions_dir) import mailin THISPAGE = 'TestPage' assert(THISPAGE != mailin.DEFAULTPAGE) # avoid confusion TESTSENDER = 'sender' TESTTO = 'recipient' TESTDATE = 'date' TESTSUBJECT = 'subject' TESTBODY = 'mailin comment\n' LONGSUBJECT = """\ a long long long long long long long long long long long long long subject""" class TestMessage: def __init__(self,sender=TESTSENDER,to=TESTTO,cc='',bcc='', date=TESTDATE,subject=TESTSUBJECT,body=TESTBODY): self.sender,self.to,self.cc,self.bcc,self.date,self.subject,self.body=\ (sender,to,cc,bcc,date,subject,body) def __call__(self): s = """\ From: %s To: %s """ % (self.sender, self.to) if self.cc: s = s + "Cc: %s\n" + self.cc if self.bcc: s = s + "Bcc: %s\n" + self.bcc s = s + """\ Date: %s Subject: %s %s """ % (self.date, self.subject, self.body) return s __str__ = __call__ TESTMSG = str(TestMessage()) class MailinTests(unittest.TestCase): def setUp(self): self.p = MockZWikiPage(__name__=THISPAGE) # mock up the IssueTracker page, capture the REQUEST # hacks Folder class def IssueTracker(self,REQUEST=None): self.issuetracker_request = REQUEST self.p.aq_parent.__class__.IssueTracker = IssueTracker def tearDown(self): del self.p.aq_parent.__class__.IssueTracker del self.p # test the mailin delivery rules # from mailin.py: #"decide which of the recipients is us, as follows: # # 1. if there's only one, it's that one # 2. the first one whose address matches the folder's mail_from property # 3. the first one whose address matches MAILINADDREXP # 4. the first one def testRecipientIdentification(self): m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to='a' )),subscribersonly=0) self.assertEqual(m.recipient,('','a')) m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to='a, b' )),subscribersonly=0) self.assertEqual(m.recipient,('','a')) self.p.folder().mail_from = 'b' m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to='a, b, wiki@c.c' )),subscribersonly=0) self.assertEqual(m.recipient,('','b')) del self.p.folder().mail_from m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to='a@a.a, mailin@b.b' )),subscribersonly=0) self.assertEqual(m.recipient,('','mailin@b.b')) m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to='a@a.a, tracker@b.b' )),subscribersonly=0) self.assertEqual(m.recipient,('','tracker@b.b')) m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to='a@a.a, bugs@b.b' )),subscribersonly=0) self.assertEqual(m.recipient,('','bugs@b.b')) m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to='a@a.a, issues@b.b' )),subscribersonly=0) self.assertEqual(m.recipient,('','issues@b.b')) def testDestinationFromHardCodedDefaultPage(self): testmsg = str(TestMessage()) self.p.create('APage') self.p.create(mailin.DEFAULTPAGE) m = mailin.MailIn(self.p.aq_parent,testmsg,subscribersonly=0) self.assertEqual(m.destpagename,mailin.DEFAULTPAGE) def testDestinationFromDefaultPageProperty(self): testmsg = str(TestMessage()) self.p.create('APage') self.p.create('BPage') self.p.aq_parent.default_page = 'BPage' m = mailin.MailIn(self.p.aq_parent,testmsg,subscribersonly=0) self.assertEqual(m.destpagename,'BPage') def testDestinationFromFirstExistingPage(self): testmsg = str(TestMessage()) self.p.create('APage') self.p.aq_parent.default_page = 'NonExistentPage' m = mailin.MailIn(self.p.aq_parent,testmsg,subscribersonly=0) self.assertEqual(m.destpagename,THISPAGE) def testDestinationFromRealNameWithRecognizedEmail(self): testmsg = str(TestMessage(to='wiki@b.c (SomePage)')) m = mailin.MailIn(self.p.aq_parent,testmsg,subscribersonly=0) self.assertEqual(m.destpagename,'SomePage') def testDestinationFromRealNameWithAnyEmail(self): testmsg = str(TestMessage(to='a@b.c (SomePage)')) m = mailin.MailIn(self.p.aq_parent,testmsg,subscribersonly=0) self.assertEqual(m.destpagename,'SomePage') def testDestinationFromRealNameWithSubject(self): testmsg = str(TestMessage( to='a@b.c (SomePage)', subject='[AnotherPage]', )) m = mailin.MailIn(self.p.aq_parent,testmsg,subscribersonly=0) self.assertEqual(m.destpagename,'SomePage') def testDestinationFromWikiNameInSubject(self): # we don't do this any more m = mailin.MailIn(self.p.aq_parent, str(TestMessage(subject='SomePage')),subscribersonly=0) self.assertEqual(m.destpagename,THISPAGE) def testDestinationFromBracketedNameInSubject(self): m = mailin.MailIn(self.p.aq_parent, str(TestMessage(subject='[Some Page]')),subscribersonly=0) self.assertEqual(m.destpagename,'Some Page') def testDestinationFromMultipleBracketedNamesInSubject(self): m = mailin.MailIn( self.p.aq_parent, str(TestMessage(subject='[Fwd:][LIST][Some Page]')), subscribersonly=0) self.assertEqual(m.destpagename,'Some Page') def testDestinationWithBlankRealName(self): m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to='wiki@b.c ( )', subject='SomePage', )),subscribersonly=0) self.assertEqual(m.destpagename,THISPAGE) def testDestinationWithNoWordsInRealName(self): m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to='wiki@b.c (...)', subject='SomePage', )),subscribersonly=0) self.assertEqual(m.destpagename,THISPAGE) def testDestinationRealNameStripping(self): m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to='wiki@b.c ( SomePage\t)', )),subscribersonly=0) self.assertEqual(m.destpagename,'SomePage') def testDestinationWithQuotesInRealName(self): m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to="wiki@b.c ('SomePage')", )),subscribersonly=0) self.assertEqual(m.destpagename,'SomePage') m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to='wiki@b.c ("SomePage")', )),subscribersonly=0) self.assertEqual(m.destpagename,'SomePage') def testDestinationWithEmailInRealName(self): m = mailin.MailIn(self.p.aq_parent,str(TestMessage( to="wiki@b.c (wiki@b.c)", )),subscribersonly=0) self.assertEqual(m.destpagename,THISPAGE) def testDestinationFromPageContext(self): m = mailin.MailIn(self.p,str(TestMessage( to='a@b.c (SomePage)', subject='[SomePage] SomePage', )),subscribersonly=0) self.assertEqual(m.destpagename,THISPAGE) def testDestinationFromTrackerAddress(self): m = mailin.MailIn(self.p,str(TestMessage( to='bugs@b.c', )),subscribersonly=0) self.assertEqual(m.trackerissue,1) def testSubscriberMailin(self): old = self.p.text() self.p.subscribe(TESTSENDER) mailin.mailin(self.p,TESTMSG) self.assertEqual(1, len(re.findall(TESTBODY,self.p.text()))) def testNonSubscriberMailinFails(self): old = self.p.text() mailin.mailin(self.p,TESTMSG) self.assertEqual(old, self.p.read()) def testNonSubscriberMailinWithOpenPosting(self): old = self.p.text() mailin.mailin(self.p,TESTMSG,subscribersonly=0) self.assertEqual(1, len(re.findall(TESTBODY,self.p.text()))) def testMailinTrackerIssue(self): mailin.mailin(self.p,TESTMSG,trackerissue=1,subscribersonly=0) self.checkAddIssueRequest(self.p.aq_parent.issuetracker_request) # this works.. need a functional test which sends through mail def testMailinTrackerIssueLongSubject(self): longsubjmsg = str(TestMessage(subject=LONGSUBJECT)) mailin.mailin(self.p,longsubjmsg,trackerissue=1,subscribersonly=0) self.checkAddIssueRequest(self.p.aq_parent.issuetracker_request, newtitle=LONGSUBJECT) def checkAddIssueRequest(self,req,newtitle=TESTSUBJECT,newtext=TESTBODY): self.assert_(hasattr(req,'newtitle')) self.assertEqual(req.newtitle, newtitle) self.assert_(hasattr(req,'newtext')) self.assertEqual(req.newtext, newtext) self.assert_(hasattr(req,'submitted')) self.assertEqual(req.submitted, 1) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(MailinTests)) return suite def main(): unittest.TextTestRunner().run(test_suite()) if __name__ == '__main__': main() ZWiki/tests/testMailout.py0100644000175000017500000001341207642737162015215 0ustar simonsimonfrom support import * class MailoutTests(unittest.TestCase): def setUp(self): self.p = MockZWikiPage() # mock up and record mail sending # XXX hacks MockZWikiPage class self.p.mock_mailout_happened = 0 def mock_sendMailToSubscribers(self, text, REQUEST, subjectSuffix='', subject=''): self.mock_mailout_happened = 1 self.mock_mailout_text = text self.mock_mailout_REQUEST = REQUEST self.mock_mailout_subjectSuffix = subjectSuffix self.mock_mailout_subject = subject self.savemethod = self.p.__class__.sendMailToSubscribers self.p.__class__.sendMailToSubscribers = mock_sendMailToSubscribers def tearDown(self): self.p.__class__.sendMailToSubscribers = self.savemethod # see also testCommentFormatting def testMailoutCommentWithOrWithoutHeading(self): self.p.comment(text='comment',username='me',time='now',use_heading=1) self.assertEquals(self.p.mock_mailout_happened,1) self.p.comment(text='comment',username='me',time='now',use_heading=0) self.assertEquals(self.p.mock_mailout_happened,1) #def testMailoutCommentWithOrWithoutSubjectField(self): # need to call the real sendMailToSubscribers # self.p.comment(text='comment',username='me',time='now', # subject_heading='[test]') # self.assertEquals(self.p.mock_mailout_happened,1) # self.p.comment(text='comment',username='me',time='now') # self.assertEquals(self.p.mock_mailout_happened,1) def test_TextFormatter(self): # what's textformatter really doing ? from Products.ZWiki.TextFormatter import TextFormatter formatter = TextFormatter(( {'width':78, 'fill':0, 'pad':0}, )) self.assertEqual(formatter.compose(('',)),'') self.assertEqual(formatter.compose(('sometext',)),'sometext') self.assertEqual(formatter.compose(('sometext\n',)),'sometext') self.assertEqual(formatter.compose(('sometext\n\n',)),'sometext\n') self.assertEqual(formatter.compose(('sometext\n\n\n',)),'sometext\n\n') self.assertEqual(formatter.compose(('\nsometext',)),'\n sometext') self.assertEqual(formatter.compose(('\n\nsometext',)),'\n\nsometext') # chops a trailing newline and inserts a space after a single # leading newline # see also testZWikiPage.testCommentFormatting def test_formatMailout(self): self.assertEqual(self.p.formatMailout(' '),'') self.assertEqual( self.p.formatMailout( 'subject --author, some time GMT
      \nsome text\n'), 'some text') self.assertEqual( self.p.formatMailout( 'long long long long subject --long author, some time GMT might get wrapped
      \nsome text\n'), 'some text') class SubscriptionTests(unittest.TestCase): def test_isSubscriber(self): sl = MockZWikiPage().aq_parent.TestPage sl._setSubscribers(['a','b']) self.assert_(sl.isSubscriber('a')) self.assert_(not sl.isSubscriber('c')) self.assert_(not sl.isSubscriber('')) self.assert_(not sl.isSubscriber(' ')) def test_subscribe(self): sl = MockZWikiPage().aq_parent.TestPage sl._setSubscribers(['spamandeggs']) self.assertEquals(sl.subscriberList(),['spamandeggs']) sl._resetSubscribers() self.assertEquals(sl.subscriberList(),[]) self.assertEquals(sl.subscriberCount(),0) sl.subscribe('1@test.com') self.assertEquals(sl.subscriberList(),['1@test.com']) self.assertEquals(MockZWikiPage.subscriber_list,[]) # ! (IssueNo0161) self.assertEquals(sl.subscriberCount(),1) self.assert_(sl.isSubscriber('1@test.com')) self.assert_(not sl.isSubscriber('2@test.com')) sl.subscribe('2@test.com') self.assertEquals(sl.subscriberList(),['1@test.com', '2@test.com']) self.assertEquals(sl.subscriberCount(),2) self.assert_(sl.isSubscriber('2@test.com')) sl.subscribe('1@test.com') self.assertEquals(sl.subscriberList(),['1@test.com', '2@test.com']) sl.unsubscribe('1@test.com') self.assertEquals(sl.subscriberList(),['2@test.com']) sl.unsubscribe('1@test.com') self.assertEquals(sl.subscriberList(),['2@test.com']) sl.unsubscribe('2@TesT.cOm') self.assertEquals(sl.subscriberList(),[]) def test_allSubscribers(self): sl = MockZWikiPage().aq_parent.TestPage sl._resetSubscribers() sl._resetSubscribers(parent=1) self.assertEquals(sl.allSubscribers(),[]) def test_allSubscriptionsFor(self): p = MockZWikiPage().aq_parent.TestPage p._resetSubscribers() p._resetSubscribers(parent=1) p.subscribe('a@a.a') p.subscribe('b@b.b') p.wikiSubscribe('b@b.b') self.assertEquals(p.allSubscriptionsFor('a@a.a'),['TestPage']) self.assertEquals(p.allSubscriptionsFor('b@b.b'),['whole_wiki', 'TestPage']) self.assertEquals(p.allSubscriptionsFor('c@c.c'),[]) def test_otherSubscriptionsFor(self): thispage = MockZWikiPage(__name__='ThisPage').aq_parent.ThisPage thispage.create('ThatPage') thatpage = thispage.aq_parent.ThatPage thispage.subscribe('me') thatpage.subscribe('me') thatpage.wikiSubscribe('me') self.assertEquals(thispage.otherPageSubscriptionsFor('me'),['ThatPage']) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(MailoutTests)) suite.addTest(unittest.makeSuite(SubscriptionTests)) return suite if __name__ == '__main__': unittest.TextTestRunner().run(test_suite()) ZWiki/tests/testRendering.py0100644000175000017500000001645107635012045015512 0ustar simonsimonfrom support import * class RenderingTests(unittest.TestCase): def setUp(self): self.p = MockZWikiPage().aq_parent.TestPage def test_render_stxprelinkdtmlfitissuehtml(self): self.p.edit(text='! PageOne PageTwo\n',type='stxprelinkdtmlfitissuehtml') self.assertEquals(self.p._render(bare=1),'

      PageOne PageTwo

      \n') def test_render_stxprelinkdtmlhtml(self): self.p.edit(text='! PageOne PageTwo\n',type='stxprelinkdtmlhtml') self.assertEquals(self.p._render(bare=1),'

      PageOne PageTwo

      \n') def test_render_stxdtmllinkhtml(self): self.p.edit(text='! PageOne PageTwo\n',type='stxdtmllinkhtml') self.assertEquals(self.p._render(bare=1),'

      PageOne PageTwo

      \n') def test_render_dtmlstxlinkhtml(self): self.p.edit(text='! PageOne PageTwo\n',type='dtmlstxlinkhtml') self.assertEquals(self.p._render(bare=1),'

      PageOne PageTwo

      \n') def test_render_stxprelinkhtml(self): self.p.edit(text='! PageOne PageTwo\n',type='stxprelinkhtml') self.assertEquals(self.p._render(bare=1),'

      PageOne PageTwo

      \n') def test_render_stxlinkhtml(self): self.p.edit(text='! PageOne PageTwo\n',type='stxlinkhtml') self.assertEquals(self.p._render(bare=1),'

      PageOne PageTwo

      \n') def test_render_stxprelink(self): self.p.edit(text='! PageOne PageTwo\n',type='stxprelink') self.assertEquals(self.p._render(bare=1),'

      PageOne PageTwo

      \n') self.p.edit(text='"link containing WikiName":url\n',type='stxprelink') self.assertEquals(self.p._render(bare=1), '

      link containing WikiName

      \n') def test_render_stxlink(self): self.p.edit(text='! PageOne PageTwo\n',type='stxlink') self.assertEquals(self.p._render(bare=1),'

      PageOne PageTwo

      \n') self.p.edit(text='"link text":url\n',type='stxlink') self.assertEquals(self.p._render(bare=1), '

      link text

      \n') def test_render_prelinkdtmlhtml(self): self.p.edit(text='! PageOne PageTwo\n',type='prelinkdtmlhtml') self.assertEquals(self.p._render(bare=1),' PageOne PageTwo\n') def test_render_dtmllinkhtml(self): self.p.edit(text='! PageOne PageTwo\n',type='dtmllinkhtml') self.assertEquals(self.p._render(bare=1),' PageOne PageTwo\n') def test_render_prelinkhtml(self): self.p.edit(text='! PageOne PageTwo\n',type='prelinkhtml') self.assertEquals(self.p._render(bare=1),' PageOne PageTwo\n') def test_render_linkhtml(self): self.p.edit(text='! PageOne PageTwo\n',type='linkhtml') self.assertEquals(self.p._render(bare=1),' PageOne PageTwo\n') def test_render_dtmlhtml(self): self.p.edit(text='! PageOne PageTwo\n',type='dtmlhtml') self.assertEquals(self.p._render(bare=1),'! PageOne PageTwo\n') def test_render_html(self): self.p.edit(text='! PageOne PageTwo\n',type='html') self.assertEquals(self.p._render(bare=1),'! PageOne PageTwo\n') def test_render_wwmlprelink(self): self.p.edit(text='! PageOne PageTwo\n',type='wwmlprelink') self.assertEquals(self.p._render(bare=1),' PageOne PageTwo\n

      ') def test_render_wwmllink(self): self.p.edit(text='! PageOne PageTwo\n',type='wwmllink') self.assertEquals(self.p._render(bare=1),' PageOne PageTwo\n

      ') def test_render_textlink(self): self.p.edit(text='! PageOne PageTwo\n',type='plaintext') self.assertEquals(self.p._render(bare=1), '

      \n! PageOne PageTwo\n\n
      \n') def test_render_plaintext(self): self.p.edit(text='! PageOne PageTwo\n',type='plaintext') self.assertEquals(self.p._render(bare=1), '
      \n! PageOne PageTwo\n\n
      \n') def test__renderLink(self): self.assertEquals( self.p._renderLink('[unbalanced (]')[-53:], 'page=unbalanced%20%28" title="create this page">?') def test_renderLinksIn(self): self.assertEquals(self.p.renderLinksIn('nolink'),'nolink') self.assertEquals(self.p.renderLinksIn('http://a.b.c/d'), 'http://a.b.c/d') self.assertEquals(self.p.renderLinksIn('mailto://a@b.c'), 'mailto://a@b.c') self.assertEquals(self.p.renderLinksIn('TestPage'), 'TestPage') self.assertEquals(self.p.renderLinksIn('NewTestPage'), 'NewTestPage?') self.assertEquals(self.p.renderLinksIn('!TestPage'),'TestPage') self.assertEquals(self.p.renderLinksIn('[newpage]'), '[newpage]?') # a problem with escaping remote wiki links was reported self.p.edit(text='RemoteWikiURL: URL/') self.assertEquals(self.p.renderLinksIn('TestPage:REMOTEPAGE'), 'TestPage:REMOTEPAGE') self.assertEquals(self.p.renderLinksIn('!TestPage:REMOTEPAGE'), 'TestPage:REMOTEPAGE') self.assertEquals(self.p.renderLinksIn('[ ]'), '[ ]?') # do display the brackets prior to page creation self.assertEquals(self.p.renderLinksIn('[newpage]'), '[newpage]?') # don't link wikinames inside ... self.assertEquals(self.p.renderLinksIn('WikiName'), 'WikiName') # do link wikinames after with no closing self.assertEquals(self.p.renderLinksIn('WikiName'), 'WikiName?') # old def test_render_structuredtextdtml(self): self.p.edit(type='structuredtextdtml') self.p.edit(text='link') if ZOPEVERSION < (2,4): # with zope2.3/python1.5 there's an extra newline self.assertEquals(self.p(bare=1), '

      link

      \n\n') else: self.assertEquals(self.p(bare=1), '

      link

      \n') self.p.edit(text="'<>'") if ZOPEVERSION < (2,4): # with zope2.3/python1.5 they don't get quoted (eh?) self.assertEquals(self.p(bare=1),'

      <>

      \n\n') else: self.assertEquals(self.p(bare=1),'

      <>

      \n') def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(RenderingTests)) return suite def main(): unittest.TextTestRunner().run(test_suite()) if __name__ == '__main__': main() ZWiki/tests/testZWikiPage.py0100644000175000017500000005374707652360636015453 0ustar simonsimon# unit tests for ZWikiPage from support import * import OFS.Image class ZMITests(unittest.TestCase): #def testZMIAddPageForm(self): # ZWikiPage.manage_addZWikiPageForm(client=zc, # REQUEST=zc.REQUEST) #def testZMIAddWebForm(self): # ZWikiPage.manage_addZWikiPageForm(client=zc, # REQUEST=zc.REQUEST) #def testZMIAddAndDelete(self): # # sure this is the best way to get at manage_add ? # zc.manage_addProduct['ZWiki'].manage_addZWikiPage('TestPageB', \ # title='test page', \ # file='blah blah') # assert hasattr(zc,'TestPageB'), 'page not created' # assert zc.TestPageB.text() == 'blah blah', 'wrong text' # assert zc.TestPageB.page_type == 'structuredtext', \ # 'wrong default page type' # zc.manage_delObjects(['TestPageB']) # assert not hasattr(zc,'TestPageB'), 'page not deleted' pass class ZWikiPageTests(unittest.TestCase): def test_canonicalIdFrom(self): p = MockZWikiPage() self.assertEquals(p.canonicalIdFrom('WikiName'),'WikiName') self.assertEquals(p.canonicalIdFrom('ZWikiWikiNames2'),'ZWikiWikiNames2') self.assertEquals(p.canonicalIdFrom('a page with !'),'APageWith') self.assertEquals(p.canonicalIdFrom('a_page'),'APage') self.assertEquals(p.canonicalIdFrom('Test\xc3Page'),'Test_c3Page') # A tilde self.assertEquals(p.canonicalIdFrom('\xc3Page'),'X_c3Page') self.assertEquals(p.canonicalIdFrom('_c3Page'),'C3Page') def test_manage_addZWikiPage(self): from Products.ZWiki.ZWikiPage import manage_addZWikiPage from OFS.Folder import Folder f = Folder() manage_addZWikiPage(f,'ZmiTestPage') assert hasattr(f,'ZmiTestPage') def testEdit(self): p = MockZWikiPage(__name__='TestPage') p.edit(text='something') self.assertEqual(p.read(),'something') p.edit(text='') self.assertEqual(p.read(),'') def testCreateFileOrImage(self): #p = MockZWikiPage(__name__='TestPage') p = MockZWikiPage().aq_parent.TestPage f = p.aq_parent file = OFS.Image.Pdata('test file data') # our test page/folder initially has no uploads attr. self.assert_(not hasattr(p,'uploads')) # calling with an unnamed file should do nothing much self.assertEqual(p._createFileOrImage(file),(None, None, None)) # ditto for a blank filename file.filename = '' self.assertEqual(p._createFileOrImage(file),(None, None, None)) # here, a file object of unknown type should be created name = 'testfile' file.filename = name id, content_type,size = p._createFileOrImage(file) self.assertEqual(str(getattr(f,id)),'test file data') self.assertEqual(content_type ,'application/octet-stream') self.assertEqual(size,14) # a text file name = 'testfile.txt' file.filename = name id, content_type,size = p._createFileOrImage(file) self.assertEqual(str(f[id]),'test file data') self.assertEqual(content_type,'text/plain') # an image name = 'testfile.gif' file.filename = name id, content_type,size = p._createFileOrImage(file) # evaluating an Image gives its html tag self.assertEqual(str(f[id])[-29:] , '="testfile.gif" border="0" />') self.assertEqual(content_type,'image/gif') def testEditWithFileUpload(self): #p = MockZWikiPage(__name__='TestPage') p = MockZWikiPage().aq_parent.TestPage f = p.aq_parent # add a file to a page file = OFS.Image.Pdata('test file data') file.filename = 'edittestfile' p.REQUEST.file = file p.edit(REQUEST=p.REQUEST) # the new file should exist self.assert_(hasattr(f,'edittestfile')) # with the right data self.assertEqual(str(f['edittestfile']),'test file data') # and a link should have been added to the page self.assertEqual(p.read(),'\n\nedittestfile\n') # a file with blank filename should be ignored p.REQUEST.file.filename = '' old = p.read() p.edit(REQUEST=p.REQUEST) self.assert_(p.read() == old) ## ditto, with an image p.edit(text='') file = OFS.Image.Pdata('test file data') file.filename = 'edittestimage.jpg' p.REQUEST.file = file p.edit(REQUEST=p.REQUEST) self.assert_(hasattr(f,'edittestimage.jpg')) self.assertEqual(f['edittestimage.jpg'].content_type,'image/jpeg') self.assertEqual(p.read(),'\n\n\n') # images should not be inlined if dontinline is set p.REQUEST.file.filename = 'edittestimage.png' p.REQUEST.dontinline = 1 p.edit(REQUEST=p.REQUEST) self.assertEqual(p.read(), '\n\n\n\n\nedittestimage.png\n') def test__handleDeleteMe_redirectToParent(self): p = MockZWikiPage() p.parents = ['chickens','dogs'] p._recycle = lambda x: None req = MockRequest() class MyMockResponse: def redirect(self, url): self.redirectedto = url req.RESPONSE = MyMockResponse() # redirect to wiki url if no existing parents r = p._handleDeleteMe('DeleteMe',REQUEST=req) self.assertEqual(r,1) self.assert_(hasattr(req.RESPONSE,'redirectedto')) self.assertEqual(req.RESPONSE.redirectedto, p.wiki_url()+'/') ## redirect to first existing parent #p.create(page='Dogs') ## was: ##r = p._handleDeleteMe('DeleteMe',REQUEST=req) ## started breaking when I introduced page titles. ## incorrect anyway, should be something like: #r = p.aq_parent.Dogs._handleDeleteMe('DeleteMe',REQUEST=req) ## but the mock page won't support this ## disable the lot for now #self.assertEqual(r,1) #self.assert_(hasattr(req.RESPONSE,'redirectedto')) #self.assertEqual(req.RESPONSE.redirectedto, # p.wiki_url()+'/'+'Dogs') # can't test _recycle easily, because manage_pasteObject needs an app #def testDeleteMe(self): # #When we see a first line beginning with "DeleteMe": # #- move to recycle_bin # #- redirect to first parent or default page # # p = MockZWikiPage(__name__='TestPage') # f = p.aq_parent # self.assert_(hasattr(f,'TestPage')) # self.assert_(not hasattr(f,'recycle_bin')) # # #deleteme's not at the beginning shouldn't do anything # p.edit(text=p.read()+'\nDeleteMe') # self.assertEqual(p.read(),'\nDeleteMe') # # #deleteme at the beginning will send it to recycle_bin # p.edit(text='DeleteMe, with comments\n') # self.assert_(not hasattr(f,'TestPage')) # self.assert_(hasattr(f,'recycle_bin')) # self.assertEqual(f.recycle_bin.TestPage.read(),'\nDeleteMe') # same problem as above #def test__recycle(self): # p = MockZWikiPage(__name__='TestPage') # f = p.aq_parent # self.assert_(hasattr(f,'TestPage')) # p._recycle() # self.assert_(not hasattr(f,'TestPage')) # self.assert_(hasattr(f,'recycle_bin')) # self.assertEqual(f.recycle_bin.TestPage.read(),'\nDeleteMe') #def test_delete(self): #p = MockZWikiPage().aq_parent.TestPage #f = p.folder() #self.assert_(hasattr(f,'TestPage')) #p.delete() #self.assert_(not hasattr(f,'TestPage')) #self.assert_(hasattr(f,'recycle_bin')) #self.assert_(hasattr(f.recycle_bin,'TestPage')) # failed to make this test work #def test_delete_leaves_no_orphans(self): # p = MockZWikiPage(__name__='Page') # f = p.folder() # # create() would give real zwiki pages, build these by hand # child = MockZWikiPage(__name__='Child') # f._setObject('Child',child,set_owner=0) # child.parents = ['Page'] # grandchild = MockZWikiPage(__name__='GrandChild') # f._setObject('GrandChild',grandchild,set_owner=0) # grandchild.parents = ['Child'] # child._recycle = lambda x: None # child.REQUEST.cookies['zwiki_username'] = 'someusername' # child.delete(REQUEST=child.REQUEST) # self.assertEquals(grandchild.parents,['Page']) def testEditLastEditorStamping(self): # Username stamping p = MockZWikiPage(__name__='TestPage') f = p.aq_parent p.last_editor = '-' # if no username available, IP address should be recorded p.REQUEST.set('REMOTE_ADDR', '1.2.3.4') p.append(text='.',REQUEST=p.REQUEST) self.assertEqual(p.last_editor,'1.2.3.4') self.assertEqual(p.last_editor_ip,'1.2.3.4') # use the zwiki_username cookie if available p.REQUEST.cookies['zwiki_username'] = 'cookiename' p.append(text='.',REQUEST=p.REQUEST) self.assertEqual(p.last_editor,'cookiename') self.assertEqual(p.last_editor_ip,'1.2.3.4') # if we are authenticated, use that by preference p.REQUEST.set('AUTHENTICATED_USER', MockUser('authusername')) p.append(text='.',REQUEST=p.REQUEST) self.assertEqual(p.last_editor,'authusername') self.assertEqual(p.last_editor_ip,'1.2.3.4') # don't record editor if nothing is actually changed p.REQUEST.set('AUTHENTICATED_USER', MockUser('differentusername')) p.REQUEST.set('REMOTE_ADDR', '5.6.7.8') p.edit(text=p.read(),REQUEST=p.REQUEST) self.assertEqual(p.last_editor,'authusername') self.assertEqual(p.last_editor_ip,'1.2.3.4') def test_create(self): #p = MockZWikiPage() p = MockZWikiPage().aq_parent.TestPage f = p.aq_parent # create a blank page p.create('TestPage1',text='') self.assert_(hasattr(f,'TestPage1')) self.assertEqual(f.TestPage1.text(),'') # create a classicwiki page with some text p.create('TestPage2',text='test page data',type='classicwiki') self.assert_(hasattr(f,'TestPage1')) self.assertEqual(f.TestPage2.read(),'test page data') self.assertEqual(f.TestPage2.page_type,'classicwiki') # having trouble making MZP support this # _handleFileUpload -> _checkPermission(,MZP.folder()) loops # because page.aq_parent.aq_parent == page # even if you set it to None, somewhere it comes back # def testCreateWithFileUpload(self): # p = MockZWikiPage().aq_parent.TestPage # f = p.folder() # f.aq_parent = None # # # upload a file while creating a page # # this capability broke - fix if ever needed # # and most of this is tested above I think # file = OFS.Image.Pdata('test file data') # file.filename = 'test_file' # p.REQUEST.file = file # p.create('TestPage3',text='test page data',REQUEST=p.REQUEST) # # the new file should exist # self.assert_(hasattr(f,'test_file')) # # with the right data # self.assertEqual(str(f['test_file']),'test file data') # # and a link should have been added to the new wiki page # self.assertEqual(f.TestPage3.read(), # 'test page data\n\ntest_file\n') # # # ditto with an image # file.filename = 'test_image.gif' # p.REQUEST.file = file # f.TestPage.create('TestPage4',text='test page data',REQUEST=p.REQUEST) # self.assert_(hasattr(f,'test_image.gif')) # self.assertEqual(f['test_image.gif'].content_type,'image/gif') # self.assertEqual(f.TestPage4.read(), # 'test page data\n\n\n') # # # images should not be inlined if dontinline is set # file.filename = 'test_image.JPG' # p.REQUEST.dontinline = 1 # f.TestPage.create('TestPage5',text='',REQUEST=p.REQUEST) # self.assertEqual(f.TestPage5.read(), # '\n\ntest_image.JPG\n') def testIssueNo0112StxDoubleHtmlTag(self): p = MockZWikiPage() p.edit(type='structuredtext') text = p() self.assertEquals( len(re.findall(r'(?i)...
      --me, now
      \ncomment') #NB the \n's are \012's with zope < 2.4 def testCommentFormatting(self): USER = "me" TIME = "now" COMMENT = """\ short lines aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa long lines bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb quoted lines > cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc > cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc long quoted lines > dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dd > dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dd """ SHOULDWRITE = """\ ... --me, now
      short lines aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa long lines bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb quoted lines
      > cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc
      > cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc long quoted lines
      > dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dd
      > dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dd """ SHOULDDIFF = """\ ... --me, now
      short lines aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa long lines bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb quoted lines
      > cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc
      > cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc long quoted lines
      > dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dd
      > dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dd """ SHOULDMAILOUT = """\ short lines aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa long lines bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb bbbb quoted lines > cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc > cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc cccc long quoted lines > dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dd > dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dddd dd """ COMMENT2 = "bah\n" SHOULDWRITE2 = "\n\nbah\n" SHOULDDIFF2 = "\n\nbah\n\n" SHOULDMAILOUT2 = "bah\n" from Products.ZWiki.Diff import DiffSupport p = MockZWikiPage().aq_parent.TestPage # simulate acquisition # test formatting at each stage, using a comment heading oldtext = p.read() p.comment(text=COMMENT,username=USER,time=TIME,use_heading=1) self.assertEqual(p.read(), SHOULDWRITE) diff = DiffSupport().textDiff(a=oldtext,b=p.read(),verbose=0) self.assertEqual(diff,SHOULDDIFF) self.assertEqual(p.formatMailout(diff), SHOULDMAILOUT) # without heading p.edit(text='') oldtext = p.read() p.comment(text=COMMENT2,username=USER,time=TIME,use_heading=0) self.assertEqual(p.read(), SHOULDWRITE2) diff = DiffSupport().textDiff(a=oldtext,b=p.read(),verbose=0) self.assertEqual(diff,SHOULDDIFF2) self.assertEqual(p.formatMailout(diff), SHOULDMAILOUT2) # with edits mailout policy (so edit does the mailout, not comment) p.folder().mailout_policy = 'edits' p.edit(text='') oldtext = p.read() p.comment(text=COMMENT,username=USER,time=TIME,use_heading=1) self.assertEqual(p.read(), SHOULDWRITE) diff = DiffSupport().textDiff(a=oldtext,b=p.read(),verbose=0) self.assertEqual(diff,SHOULDDIFF) self.assertEqual(p.formatMailout(diff), SHOULDMAILOUT) def test__setText(self): p = MockZWikiPage().aq_parent.TestPage p.edit(text='\n\ntest\n\n') self.assertEqual(p.read(),'\ntest\n') def test__setLastEditor(self): p = MockZWikiPage().aq_parent.TestPage p.REQUEST.set('REMOTE_ADDR', '1.2.3.4') p.REQUEST.set('AUTHENTICATED_USER', MockUser('user')) p._setLastEditor(REQUEST=p.REQUEST) self.assertEqual(p.last_editor,'user') self.assertEqual(p.last_editor_ip,'1.2.3.4') def test__setCreator(self): p = MockZWikiPage().aq_parent.TestPage r = MockRequest() u = MockUser('test user') r.set('AUTHENTICATED_USER',u) r.set('REMOTE_ADDR','4.3.2.1') p._setCreator() self.assert_(p.creation_time) self.assertEqual(p.creator_ip,'') self.assertEqual(p.creator,'') p._setCreator(r) self.assertEqual(p.creator_ip,'4.3.2.1') self.assertEqual(p.creator,'test user') def test_text(self): p = MockZWikiPage().aq_parent.TestPage # ensure we don't lose first lines to DTML's decapitate() p._setText(r'first: line\n\nsecond line\n') self.assertEqual(p.text(),'first: line\\n\\nsecond line\\n') # ensure none of these reveal the antidecapkludge p.edit(type='htmldtml') p._setText('test text') self.assertEqual(p.text(),'test text') from Products.ZWiki.ZWikiPage import ZOPEVERSION if ZOPEVERSION >= (2,4): self.assertEqual(p.read(),'test text') self.assertEqual(p.__str__(),'test text') else: # what ? self.assertEqual(p.read(),'test text') self.assertEqual(p.__str__(),'test text') def test_asAgeString(self): p = MockZWikiPage().aq_parent.TestPage self.assertEqual(p.asAgeString(p.last_edit_time),'some time') # MZP doesn't support manage_renameObject #def test_rename_leaves_no_orphans(self): # p = MockZWikiPage('parent page') # c = p.create('child page') # p.rename('new parent name') # self.assertEquals(['new parent name'],c.parents) #def test_upgrade_ids(self): # p = MockZWikiPage(__name__='SomeId').aq_parent.SomeId # p.title = 'something elSe!' # p.upgrade() # self.assertEqual(p.id(),'SomethingElse') def test_pageIdsStartingWith(self): p = MockZWikiPage().aq_parent.TestPage p.create('TestPage2') self.assertEqual(p.pageIdsStartingWith('Test'), ['TestPage','TestPage2']) def test_pageNamesStartingWith(self): p = MockZWikiPage().aq_parent.TestPage p.title = 'Test Page' p.create('Test Page 2') self.assertEqual(p.pageNamesStartingWith('Test'), ['Test Page','Test Page 2']) def test_firstPageIdStartingWith(self): p = MockZWikiPage().aq_parent.TestPage p.create('TestPage2') self.assertEqual(p.firstPageIdStartingWith('Test'),'TestPage') def test_firstPageNameStartingWith(self): p = MockZWikiPage().aq_parent.TestPage p.title = 'Test Page' p.create('Test Page 2') self.assertEqual(p.firstPageNameStartingWith('Test'),'Test Page') def test_pageWithId(self): p = MockZWikiPage().aq_parent.TestPage self.assertEqual(p.pageWithId('nosuchid'),None) self.assertEqual(p.pageWithId('TestPage'),p) self.assertEqual(p.pageWithId('testpage'),None) self.assertEqual(p.pageWithId('testpage',ignore_case=1),p) def test_pageWithName(self): p = MockZWikiPage().aq_parent.TestPage p.title = 'Test page' self.assertEqual(p.pageWithName(p.title),p) def test_pageWithFuzzyName(self): p = MockZWikiPage().aq_parent.TestPage p.title = 'Test page' self.assertEqual(p.pageWithFuzzyName('Test page'),p) self.assertEqual(p.pageWithFuzzyName(' Test page\t'),p) self.assertEqual(p.pageWithFuzzyName('TestPage'),p) self.assertEqual(p.pageWithFuzzyName('TEST Page'),p) self.assertEqual(p.pageWithFuzzyName('Testpage'),p) self.assertEqual(p.pageWithFuzzyName('test'),None) self.assertEqual(p.pageWithFuzzyName('test',allow_partial=1),p) def test_backlinksFor(self): p = MockZWikiPage().aq_parent.TestPage p.title = 'Test Page' p.create('PageTwo',text='[Test Page]') p.create('PageThree',text='TestPage') self.assertEqual(len(p.backlinksFor('Test Page')),2) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(ZMITests)) suite.addTest(unittest.makeSuite(ZWikiPageTests)) return suite def main(): unittest.TextTestRunner().run(test_suite()) if __name__ == '__main__': main() ZWiki/tests/testZWikiWeb.py0100644000175000017500000000641007631622066015270 0ustar simonsimon# unit tests for wiki templates from support import * class ZWikiWebsTests(unittest.TestCase): TEMPLATES = ( ('zwikidotorg', (('AnnoyingQuote', 'structuredtext'), ('BookMarks', 'structuredtext'), ('FrontPage', 'structuredtext'), ('HelpPage', 'structuredtext'), ('HierarchalStructure', 'structuredtext'), ('JumpSearch', 'htmldtml'), ('RecentChanges', 'htmldtml'), ('RemoteWikiLinks', 'structuredtext'), ('RemoteWikiURL', 'structuredtext'), ('SearchPage', 'htmldtml'), ('StructuredTextRules', 'structuredtext'), ('TextFormattingRules', 'structuredtext'), ('TimeZone', 'structuredtext'), ('UserName', 'structuredtext'), ('UserOptions', 'structuredtextdtml'), ('WikiName', 'structuredtext'), ('WikiWikiWeb', 'structuredtext'), ('ZWiki', 'structuredtext'), ('ZopeDotOrg', 'structuredtext'), )), ) # add parents #I think I've had enough # # def testAddZwikiWebForm(self): # from Products.ZWiki.ZWikiWeb import * # manage_addZWikiWebForm()#client=zc, # #REQUEST=REQUEST, # #PARENTS=PARENTS) # def testAddZwikiWeb(self): # p = MockZWikiPage(__name__='TestPage') # f = p.aq_parent # req = p.REQUEST # wikitype = 'zwikidotorg' # req['REMOTE_ADDR'] = '1.2.3.4' # req['new_id'] = wikitype # req['new_title'] = wikitype + ' wiki' # req['wiki_type'] = wikitype # # req['SERVER_URL'] required # self.root.manage_addProduct['ZWiki'].ZWikiWebAddForm( # client=self.root,\ # REQUEST=req) # def testWikiTemplates(self): # """ # test the sample wikis' content. # """ # zc = self.ZopeContext # # # for each sample wikiweb, # for wikitype, pages in self.TEMPLATES: # # # create one # # fake form input # zc.REQUEST['new_id'] = wikitype # zc.REQUEST['new_title'] = wikitype + ' wiki' # zc.REQUEST['wiki_type'] = wikitype # manage_addZWikiWeb(zc, # wikitype, # new_title=wikitype+' wiki', # wiki_type=wikitype, # REQUEST=zc.REQUEST) # # # verify that it exists and that all pages listed above # # are present and correct # assert hasattr(zc, wikitype), \ # wikitype+" wiki was not created" # for page, type in pages: # assert hasattr(zc[wikitype],page), \ # wikitype+"/"+page+" does not exist" # assert zc[wikitype][page].page_type == type, \ # wikitype+"/"+page+"'s type is not "+type def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(ZWikiWebsTests)) return suite def main(): unittest.TextTestRunner().run(test_suite()) if __name__ == '__main__': main()