changeset 806:bcdfbb11da9f

merged current 1.6 branch
author Franz Pletz <fpletz AT franz-pletz DOT org>
date Mon, 05 Jun 2006 19:23:26 +0200
parents 943a9756bcbe (current diff) 7697385bbb7f (diff)
children dc31818ae861
files MoinMoin/multiconfig.py MoinMoin/wikixml/xsltutil.py docs/CHANGES wiki/htdocs/applets/index.html
diffstat 24 files changed, 353 insertions(+), 246 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Sat May 27 16:19:45 2006 +0200
+++ b/.hgtags	Mon Jun 05 19:23:26 2006 +0200
@@ -13,3 +13,4 @@
 9aebec40e7f9095832874120c596019c939510d7 1.5.3-rc2
 e5bd284ca29e5ce69a4593c45ba4cf4d1b74183d 1.5.3
 cdfb01bec1224c9ac9c83ef9bae75ed48ca66340 1.6a
+fbe43f9574f1221baea7b1f4077476fdbc155d8f SOC2006-START
--- a/Makefile	Sat May 27 16:19:45 2006 +0200
+++ b/Makefile	Mon Jun 05 19:23:26 2006 +0200
@@ -33,7 +33,6 @@
 
 # Create documentation
 epydoc: patchlevel
-	@MoinMoin/version.py update
 	@epydoc -o ../html -n MoinMoin -u http://moinmoin.wikiwikiweb.de MoinMoin
 
 # Create new underlay directory from MoinMaster
@@ -60,12 +59,13 @@
 # Create patchlevel module
 patchlevel:
 	@echo -e patchlevel = "\"`hg identify`\"\n" >MoinMoin/patchlevel.py
+	@MoinMoin/version.py update
 
 # Report translations status
 check-i18n:
 	MoinMoin/i18n/check_i18n.py
 
-# Update the current tree from `tla my-default-archive`
+# Update the workdir from the default pull repo
 update:
 	hg pull -u
 	$(MAKE) patchlevel
--- a/MoinMoin/__init__.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/__init__.py	Mon Jun 05 19:23:26 2006 +0200
@@ -1,6 +1,6 @@
 # -*- coding: iso-8859-1 -*-
 """
-MoinMoin Version 1.5.3 219ffcdc211a tip
+MoinMoin Version 1.6.0alpha 43b158d3cf22+ tip
 
 @copyright: 2000-2006 by Jürgen Hermann <jh@web.de>
 @license: GNU GPL, see COPYING for details.
--- a/MoinMoin/config.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/config.py	Mon Jun 05 19:23:26 2006 +0200
@@ -2,7 +2,7 @@
 """
     MoinMoin - site-wide configuration defaults (NOT per single wiki!)
 
-    @copyright: 2005 by Thomas Waldmann (MoinMoin:ThomasWaldmann)
+    @copyright: 2005-2006 by Thomas Waldmann (MoinMoin:ThomasWaldmann)
     @license: GNU GPL, see COPYING for details.
 """
 import re
@@ -13,10 +13,8 @@
 
 # Charset - we support only 'utf-8'. While older encodings might work,
 # we don't have the resources to test them, and there is no real
-# benefit for the user.
-# IMPORTANT: use only lowercase 'utf-8'!
+# benefit for the user. IMPORTANT: use only lowercase 'utf-8'!
 charset = 'utf-8'
-##charset = 'iso-8859-1'
 
 # Invalid characters - invisible characters that should not be in page
 # names. Prevent user confusion and wiki abuse, e.g u'\u202aFrontPage'.
@@ -38,48 +36,8 @@
 umask = 0770
 url_schemas = []
 
-smileys = {
-    "X-(":  (15, 15, 0, "angry.png"),
-    ":D":   (15, 15, 0, "biggrin.png"),
-    "<:(":  (15, 15, 0, "frown.png"),
-    ":o":   (15, 15, 0, "redface.png"),
-    ":(":   (15, 15, 0, "sad.png"),
-    ":)":   (15, 15, 0, "smile.png"),
-    "B)":   (15, 15, 0, "smile2.png"),
-    ":))":  (15, 15, 0, "smile3.png"),
-    ";)":   (15, 15, 0, "smile4.png"),
-    "/!\\": (15, 15, 0, "alert.png"),
-    "<!>":  (15, 15, 0, "attention.png"),
-    "(!)":  (15, 15, 0, "idea.png"),
-
-    # copied 2001-11-16 from http://pikie.darktech.org/cgi/pikie.py?EmotIcon
-    ":-?":  (15, 15, 0, "tongue.png"),
-    ":\\":  (15, 15, 0, "ohwell.png"),
-    ">:>":  (15, 15, 0, "devil.png"),
-    "|)":   (15, 15, 0, "tired.png"),
-    
-    # some folks use noses in their emoticons
-    ":-(":  (15, 15, 0, "sad.png"),
-    ":-)":  (15, 15, 0, "smile.png"),
-    "B-)":  (15, 15, 0, "smile2.png"),
-    ":-))": (15, 15, 0, "smile3.png"),
-    ";-)":  (15, 15, 0, "smile4.png"),
-    "|-)":  (15, 15, 0, "tired.png"),
-    
-    # version 1.0
-    "(./)":  (20, 15, 0, "checkmark.png"),
-    "{OK}":  (14, 12, 0, "thumbs-up.png"),
-    "{X}":   (16, 16, 0, "icon-error.png"),
-    "{i}":   (16, 16, 0, "icon-info.png"),
-    "{1}":   (15, 13, 0, "prio1.png"),
-    "{2}":   (15, 13, 0, "prio2.png"),
-    "{3}":   (15, 13, 0, "prio3.png"),
-
-    # version 1.3.4 (stars)
-    # try {*}{*}{o}
-    "{*}":   (15, 15, 0, "star_on.png"),
-    "{o}":   (15, 15, 0, "star_off.png"),
-}
+smileys = (r"X-( :D <:( :o :( :) B) :)) ;) /!\ <!> (!) :-? :\ >:> |) " +
+           r":-( :-) B-) :-)) ;-) |-) (./) {OK} {X} {i} {1} {2} {3} {*} {o}").split()
 
 # unicode: set the char types (upper, lower, digits, spaces)
 from MoinMoin.util.chartypes import _chartypes
--- a/MoinMoin/converter/text_html_text_moin_wiki.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/converter/text_html_text_moin_wiki.py	Mon Jun 05 19:23:26 2006 +0200
@@ -1182,9 +1182,9 @@
         # Smiley
         elif src and (self.request.cfg.url_prefix in src or '../' in src) and "img/" in src: # XXX this is dirty!
             filename = src.split("/")[-1]
-            for name, data in config.smileys.iteritems():
-                if data[3] == filename:
-                    self.text.extend([self.white_space, name, self.white_space])
+            for markup, data in self.request.theme.icons.iteritems():
+                if data[1] == filename:
+                    self.text.extend([self.white_space, markup, self.white_space])
                     return
                 else:
                     pass #print name, data, filename, alt
@@ -1193,7 +1193,7 @@
         elif src and src.startswith("http") and wikiutil.isPicture(src): # matches http: and https: !
             self.text.extend([self.white_space, src, self.white_space])
         else:
-            raise ConvertError("Strange image src: '%s'" % src)
+            raise ConvertError("Strange image src: '%s' alt == '%r'" % (src, alt))
 
 
 def parse(request, text):
--- a/MoinMoin/formatter/text_docbook.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/formatter/text_docbook.py	Mon Jun 05 19:23:26 2006 +0200
@@ -451,9 +451,9 @@
         if kw.has_key('src'):
             image.setAttribute('fileref', kw['src'])
         if kw.has_key('width'):
-            image.setAttribute('width', kw['width'])
+            image.setAttribute('width', str(kw['width']))
         if kw.has_key('height'):
-            image.setAttribute('depth', kw['height'])
+            image.setAttribute('depth', str(kw['height']))
         imagewrap.appendChild(image)
 
         title = ''
@@ -472,11 +472,7 @@
         return ""        
  
     def smiley(self, text):
-        w, h, b, img = config.smileys[text.strip()]
-        href = img
-        if not href.startswith('/'):
-            href = self.request.theme.img_url(img)
-        return self.image(src=href, alt=text, width=str(w), height=str(h))
+        return self.request.theme.make_icon(text)
 
     def icon(self, type):
         return '' # self.request.theme.make_icon(type)
--- a/MoinMoin/formatter/text_html.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/formatter/text_html.py	Mon Jun 05 19:23:26 2006 +0200
@@ -1055,12 +1055,7 @@
     def icon(self, type):
         return self.request.theme.make_icon(type)
 
-    def smiley(self, text):
-        w, h, b, img = config.smileys[text.strip()]
-        href = img
-        if not href.startswith('/'):
-            href = self.request.theme.img_url(img)
-        return self.image(src=href, alt=text, width=str(w), height=str(h))
+    smiley = icon
 
     def image(self, src=None, **kw):
         """Creates an inline image with an <img> element.
--- a/MoinMoin/formatter/text_python.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/formatter/text_python.py	Mon Jun 05 19:23:26 2006 +0200
@@ -171,6 +171,8 @@
             return self.__insert_code('request.write(%s.icon(%r))' %
                                       (self.__formatter, type))
 
+    smiley = icon
+
     def macro(self, macro_obj, name, args):
         if self.__is_static(macro_obj.get_dependencies(name)):
             return macro_obj.execute(name, args)
--- a/MoinMoin/macro/ShowSmileys.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/macro/ShowSmileys.py	Mon Jun 05 19:23:26 2006 +0200
@@ -14,7 +14,7 @@
 from MoinMoin.util.dataset import TupleDataset, Column
 from MoinMoin.widget.browser import DataBrowserWidget
 
-COLUMNS = 3
+COLUMNS = 4
 
 Dependencies = ['user'] # different users have different themes and different user prefs (text/gfx)
 
@@ -29,28 +29,20 @@
         data.columns.extend([
             Column('markup', label=_('Markup')),
             Column('image', label=_('Display'), align='center'),
-            Column('filename', label=_('Filename')),
             Column('', label=''),
         ])
     data.columns[-1].hidden = 1
 
     # iterate over smileys, in groups of size COLUMNS
-    smileys = config.smileys.items()
-    smileys.sort()
+    smileys = config.smileys
     for idx in range(0, len(smileys), COLUMNS):
         row = []
         for off in range(COLUMNS):
             if idx+off < len(smileys):
-                markup, smiley = smileys[idx+off]
-                img = fmt.smiley(markup)
-                row.extend([
-                    fmt.code(1) + fmt.text(markup) + fmt.code(0),
-                    img,
-                    fmt.code(1) + smiley[3] + fmt.code(0),
-                    '',
-                ])
+                markup = smileys[idx+off]
+                row.extend([fmt.code(1) + fmt.text(markup) + fmt.code(0), fmt.smiley(markup), '', ])
             else:
-                row.extend(['&nbsp;'] * 4)
+                row.extend(['&nbsp;'] * 3)
         data.addRow(tuple(row))
 
     # display table
--- a/MoinMoin/macro/__init__.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/macro/__init__.py	Mon Jun 05 19:23:26 2006 +0200
@@ -48,14 +48,6 @@
         cfg.macro_names = lnames # remember it
         return lnames
 
-def _make_index_key(index_letters, additional_html=""):
-    index_letters.sort()
-    links = map(lambda ch:
-                    '<a href="#%s">%s</a>' %
-                    (wikiutil.quoteWikinameURL(ch), ch.replace('~', 'Others')),
-                index_letters)
-    return "<p>%s%s</p>" % (' | '.join(links), additional_html)
-
 
 #############################################################################
 ### Macros - Handlers for [[macroname]] markup
@@ -223,7 +215,21 @@
         html = u'\n'.join(html)
         return self.formatter.rawHTML(html)
 
-    def _macro_WordIndex(self, args):
+    def _make_index(self, args, word_re=u'.+'):
+        """ make an index page (used for TitleIndex and WordIndex macro)
+
+            word_re is a regex used for splitting a pagename into fragments
+            matched by it (used for WordIndex). For TitleIndex, we just match
+            the whole page name, so we only get one fragment that is the same
+            as the pagename.
+
+            TODO: later this can get a macro on its own, more powerful and less
+                  special than WordIndex and TitleIndex.
+                  It should be able to filter for specific mimetypes, maybe match
+                  pagenames by regex (replace PageList?), etc.
+
+                  it should use the formatter asap
+        """
         _ = self._
         allpages = int(self.form.get('allpages', [0])[0]) != 0
         # Get page list readable by current user
@@ -235,8 +241,9 @@
             def filter(name):
                 return not wikiutil.isSystemPage(self.request, name)
             pages = self.request.rootpage.getPageList(filter=filter)
+        
+        word_re = re.compile(word_re, re.UNICODE)
         map = {}
-        word_re = re.compile(u'[%s][%s]+' % (config.chars_upper, config.chars_lower), re.UNICODE)
         for name in pages:
             for word in word_re.findall(name):
                 try:
@@ -245,8 +252,12 @@
                 except KeyError:
                     map[word] = [name]
 
+        # Sort ignoring case
         all_words = map.keys()
-        all_words.sort()
+        tmp = [(word.upper(), word) for word in all_words]
+        tmp.sort()
+        all_words = [item[1] for item in tmp]
+
         index_letters = []
         current_letter = None
         html = []
@@ -254,88 +265,74 @@
             letter = wikiutil.getUnicodeIndexGroup(word)
             if letter != current_letter:
                 #html.append(self.formatter.anchordef()) # XXX no text param available!
-                html.append(u'<a name="%s"><h3>%s</h3></a>' % (
+                html.append(u'<a name="%s"><h2>%s</h2></a>' % (
                     wikiutil.quoteWikinameURL(letter), letter.replace('~', 'Others')))
                 current_letter = letter
             if letter not in index_letters:
                 index_letters.append(letter)
-
-            html.append(self.formatter.strong(1))
-            html.append(word)
-            html.append(self.formatter.strong(0))
+            links = map[word]
+            if len(links) and links[0] != word: # show word fragment as on WordIndex
+                html.append(self.formatter.strong(1))
+                html.append(word)
+                html.append(self.formatter.strong(0))
+            
             html.append(self.formatter.bullet_list(1))
-            links = map[word]
             links.sort()
             last_page = None
             for name in links:
                 if name == last_page:
                     continue
                 html.append(self.formatter.listitem(1))
-                html.append(Page(self.request, name).link_to(self.request))
+                html.append(Page(self.request, name).link_to(self.request, attachment_indicator=1))
                 html.append(self.formatter.listitem(0))
             html.append(self.formatter.bullet_list(0))
         
+        def _make_index_key(index_letters, additional_html=''):
+            index_letters.sort()
+            def letter_link(ch):
+                return '<a href="#%s">%s</a>' % (wikiutil.quoteWikinameURL(ch), ch.replace('~', 'Others'))
+            links = [letter_link(letter) for letter in index_letters]
+            return "<p>%s%s</p>" % (' | '.join(links), additional_html)
+
         qpagename = wikiutil.quoteWikinameURL(self.formatter.page.page_name)
+        allpages_txt = (_('Include system pages'), _('Exclude system pages'))[allpages]
         index = _make_index_key(index_letters, u"""<br>
 <a href="%s?allpages=%d">%s</a>
-""" % (qpagename, not allpages, (_('Include system pages'), _('Exclude system pages'))[allpages]) )
+""" % (qpagename, not allpages, allpages_txt) )
+        # ?action=titleindex and ?action=titleindex&mimetype=text/xml removed
+
         return u'%s%s' % (index, u''.join(html)) 
 
 
     def _macro_TitleIndex(self, args):
-        _ = self._
-        html = []
-        index_letters = []
-        allpages = int(self.form.get('allpages', [0])[0]) != 0
-        # Get page list readable by current user
-        # Filter by isSystemPage if needed
-        if allpages:
-            # TODO: make this fast by caching full page list
-            pages = self.request.rootpage.getPageList()
-        else:
-            def filter(name):
-                return not wikiutil.isSystemPage(self.request, name)
-            pages = self.request.rootpage.getPageList(filter=filter)
+        return self._make_index(args)
 
-        # Sort ignoring case
-        tmp = [(name.upper(), name) for name in pages]
-        tmp.sort()
-        pages = [item[1] for item in tmp]
-                
-        current_letter = None
-        for name in pages:
-            letter = wikiutil.getUnicodeIndexGroup(name)
-            if letter not in index_letters:
-                index_letters.append(letter)
-            if letter != current_letter:
-                html.append(u'<a name="%s"><h3>%s</h3></a>' % (
-                    wikiutil.quoteWikinameURL(letter), letter.replace('~', 'Others')))
-                current_letter = letter
-            else:
-                html.append(u'<br>')
-            html.append(u'%s\n' % Page(self.request, name).link_to(self.request, attachment_indicator=1))
-
-        # add rss link
-        index = ''
-        if 0: # if wikixml.ok: # XXX currently switched off (not implemented)
-            from MoinMoin import wikixml
-            index = (index + self.formatter.url(1, 
-                wikiutil.quoteWikinameURL(self.formatter.page.page_name) + "?action=rss_ti", do_escape=0) +
-                     self.formatter.icon("rss") +
-                     self.formatter.url(0))
-
-        qpagename = wikiutil.quoteWikinameURL(self.formatter.page.page_name)
-        index = index + _make_index_key(index_letters, u"""<br>
-<a href="%s?allpages=%d">%s</a>&nbsp;|
-<a href="%s?action=titleindex">%s</a>&nbsp;|
-<a href="%s?action=titleindex&amp;mimetype=text/xml">%s</a>
-""" % (qpagename, not allpages, (_('Include system pages'), _('Exclude system pages'))[allpages],
-       qpagename, _('Plain title index'),
-       qpagename, _('XML title index')) )
-
-        return u'%s%s' % (index, u''.join(html)) 
+    def _macro_WordIndex(self, args):
+        word_re = u'[%s][%s]+' % (config.chars_upper, config.chars_lower)
+        return self._make_index(args, word_re=word_re)
 
 
+    def _macro_PageList(self, needle):
+        from MoinMoin import search
+        _ = self._
+        literal = 0
+        case = 0
+
+        # If called with empty or no argument, default to regex search for .+, the full page list.
+        if not needle:
+            needle = 'regex:.+'
+
+        # With whitespace argument, return same error message as FullSearch
+        elif needle.isspace():
+            err = _('Please use a more selective search term instead of {{{"%s"}}}') %  needle
+            return '<span class="error">%s</span>' % err
+            
+        # Return a title search for needle, sorted by name.
+        query = search.QueryParser(literal=literal, titlesearch=1, case=case).parse_query(needle)
+        results = search.searchPages(self.request, query)
+        results.sortByPagename()
+        return results.pageList(self.request, self.formatter)
+        
     def _macro_InterWiki(self, args):
         from StringIO import StringIO
 
@@ -381,30 +378,6 @@
         icon = args.lower()
         return self.formatter.icon(icon)
 
-    def _macro_PageList(self, needle):
-        from MoinMoin import search
-        _ = self._
-        literal=0
-        case=0
-
-        # If called with empty or no argument, default to regex search for .+,
-        # the full page list.
-        if not needle:
-            needle = 'regex:.+'
-
-        # With whitespace argument, return same error message as FullSearch
-        elif needle.isspace():
-            err = _('Please use a more selective search term instead of '
-                    '{{{"%s"}}}') %  needle
-            return '<span class="error">%s</span>' % err
-            
-        # Return a title search for needle, sorted by name.
-        query = search.QueryParser(literal=literal, titlesearch=1,
-                                   case=case).parse_query(needle)
-        results = search.searchPages(self.request, query)
-        results.sortByPagename()
-        return results.pageList(self.request, self.formatter)
-        
     def _macro_TemplateList(self, args):
         _ = self._
         try:
--- a/MoinMoin/multiconfig.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/multiconfig.py	Mon Jun 05 19:23:26 2006 +0200
@@ -164,7 +164,7 @@
 class DefaultConfig:
     """ default config values """
     
-    # All acl_right lines must use unicode!
+    # All acl_rights_* lines must use unicode!
     acl_rights_default = u"Trusted:read,write,delete,revert Known:read,write,delete,revert All:read,write"
     acl_rights_before = u""
     acl_rights_after = u""
@@ -190,20 +190,27 @@
     bang_meta = 1
     caching_formats = ['text_html']
     changed_time_fmt = '%H:%M'
+
     # chars_{upper,lower,digits,spaces} see MoinMoin/util/chartypes.py
+
     # if you have gdchart, add something like
     # chart_options = {'width = 720, 'height': 540}
     chart_options = None
+    
     config_check_enabled = 0
     cookie_domain = None # use '.domain.tld" for a farm with hosts in that domain
     cookie_path = None   # use '/wikifarm" for a farm with pathes below that path
     cookie_lifetime = 12 # 12 hours from now
+    
     data_dir = './data/'
     data_underlay_dir = './underlay/'
+    
     date_fmt = '%Y-%m-%d'
     datetime_fmt = '%Y-%m-%d %H:%M:%S'
+    
     default_markup = 'wiki'
     docbook_html_dir = r"/usr/share/xml/docbook/stylesheet/nwalsh/html/" # correct for debian sarge
+    
     editor_default = 'text' # which editor is called when nothing is specified
     editor_ui = 'freechoice' # which editor links are shown on user interface
     editor_force = False
@@ -249,6 +256,7 @@
                # A non-existing hack key should ever mean False, None, "", [] or {}!
 
     hosts_deny = []
+    
     html_head = ''
     html_head_queries = '''<meta name="robots" content="noindex,nofollow">\n'''
     html_head_posts   = '''<meta name="robots" content="noindex,nofollow">\n'''
@@ -318,6 +326,7 @@
         'view':        ("%(q_page_name)s", _("View"), "view"),
         'up':          ("%(q_page_parent_page)s", _("Up"), "up"),
         }
+    
     refresh = None # (minimum_delay, type), e.g.: (2, 'internal')
     rss_cache = 60 # suggested caching time for RecentChanges RSS, in seconds
     shared_intermap = None # can be string or list of strings (filenames)
@@ -349,8 +358,10 @@
     
     theme_default = 'modern'
     theme_force = False
+    
     trail_size = 5
     tz_offset = 0.0 # default time zone offset in hours from UTC
+    
     user_autocreate = False # do we auto-create user profiles
     user_email_unique = True # do we check whether a user's email is unique?
 
@@ -386,6 +397,7 @@
         # id -> username for page info and recent changes, but it
         # is not usable for the user any more:
     ]
+    
     user_checkbox_defaults = {'mailto_author':       0,
                               'edit_on_doubleclick': 0,
                               'remember_last_visit': 0,
@@ -398,6 +410,7 @@
                               'remember_me':         1,
                               'want_trivial':        0,
                              }
+    
     # don't let the user change those
     # user_checkbox_disable = ['disabled', 'want_trivial']
     user_checkbox_disable = []
@@ -411,18 +424,13 @@
         ('name', _('Name'), "text", "36", _("(Use Firstname''''''Lastname)")),
         ('aliasname', _('Alias-Name'), "text", "36", ''),
         ('password', _('Password'), "password", "36", ''),
-        ('password2', _('Password repeat'), "password", "36", _('(Only when changing passwords)')),
+        ('password2', _('Password repeat'), "password", "36", _('(Only for password change or new account)')),
         ('email', _('Email'), "text", "36", ''),
         ('css_url', _('User CSS URL'), "text", "40", _('(Leave it empty for disabling user CSS)')),
         ('edit_rows', _('Editor size'), "text", "3", ''),
-        ##('theme', _('Preferred theme'), [self._theme_select()])
-        ##('', _('Editor Preference'), [self._editor_default_select()])
-        ##('', _('Editor shown on UI'), [self._editor_ui_select()])
-        ##('', _('Time zone'), [self._tz_select()])
-        ##('', _('Date format'), [self._dtfmt_select()])
-        ##('', _('Preferred language'), [self._lang_select()])
     ]
-    user_form_defaults = { # key: default
+    
+    user_form_defaults = { # key: default - do NOT remove keys from here!
         'name': '',
         'aliasname': '',
         'password': '',
@@ -431,6 +439,7 @@
         'css_url': '',
         'edit_rows': "20",
     }
+    
     # don't let the user change those, but show them:
     #user_form_disable = ['name', 'aliasname', 'email',]
     user_form_disable = []
--- a/MoinMoin/parser/text_moin_wiki.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/parser/text_moin_wiki.py	Mon Jun 05 19:23:26 2006 +0200
@@ -96,7 +96,7 @@
         'dl_rule': dl_rule,
         'url_rule': url_rule,
         'word_rule': word_rule,
-        'smiley': u'|'.join(map(re.escape, config.smileys.keys()))}
+        'smiley': u'|'.join(map(re.escape, config.smileys))}
 
     # Don't start p before these 
     no_new_p_before = ("heading rule table tableZ tr td "
--- a/MoinMoin/server/standalone.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/server/standalone.py	Mon Jun 05 19:23:26 2006 +0200
@@ -382,6 +382,78 @@
         shutil.copyfileobj(source, outputfile, length=self.bufferSize)
 
 
+try:
+    from tlslite.api import TLSSocketServerMixIn, X509, X509CertChain, SessionCache, parsePEMKey, TLSError
+    from tlslite.TLSConnection import TLSConnection
+except ImportError:
+    pass
+else:
+    class SecureRequestRedirect(BaseHTTPServer.BaseHTTPRequestHandler):
+        def handle(self):
+            self.close_connection = 1
+            try:
+                self.raw_requestline = self.rfile.readline()
+            except socket.error:
+                return
+            if self.parse_request():
+                host = self.headers.get('Host', socket.gethostname())
+                path = self.path
+            else:
+                host = '%s:%s' % (socket.gethostname(), 
+                        self.request.getsockname()[1])
+                path = '/'
+                
+            self.requestline = 'ERROR: Redirecting to https://%s%s' % (host, path)
+            self.request_version = 'HTTP/1.1'
+            self.command = 'GET'
+            self.send_response(301, 'Document Moved')
+            self.send_header('Date', self.date_time_string())
+            self.send_header('Location', 'https://%s%s' % (host, path))
+            self.send_header('Connection', 'close')
+            self.send_header('Content-Length', '0')
+            self.wfile.write('\r\n')
+            
+    class SecureThreadPoolServer(TLSSocketServerMixIn, ThreadPoolServer):
+        def __init__(self, config):
+            ThreadPoolServer.__init__(self, config)
+            
+            cert = open(config.ssl_certificate).read()
+            x509 = X509()
+            x509.parse(cert)
+            self.certChain = X509CertChain([x509])
+            
+            priv = open(config.ssl_privkey).read()
+            self.privateKey = parsePEMKey(priv, private=True)
+            
+            self.sessionCache = SessionCache()
+            
+        def finish_request(self, sock, client_address):
+            # Peek into the packet, if it starts with GET or POS(T) then
+            # redirect, otherwise let TLSLite handle the connection.
+            peek = sock.recv(3, socket.MSG_PEEK).lower()
+            if peek == 'get' or peek == 'pos':
+                SecureRequestRedirect(sock, client_address, self)
+                return
+            tls_connection = TLSConnection(sock)
+            if self.handshake(tls_connection) == True:
+                self.RequestHandlerClass(tls_connection, client_address, self)
+            else:
+                # This will probably fail because the TLSConnection has 
+                # already written SSL stuff to the socket. But not sure what
+                # else we should do.
+                SecureRequestRedirect(sock, client_address, self)
+                
+        def handshake(self, tls_connection):
+            try:
+                tls_connection.handshakeServer(certChain = self.certChain,
+                                               privateKey = self.privateKey,
+                                               sessionCache = self.sessionCache)
+                tls_connection.ignoreAbruptClose = True
+                return True
+            except:
+                return False
+
+                
 def memoryProfileDecorator(func, profile):
     """ Return a profiled function """
     def profiledFunction(*args, **kw):
--- a/MoinMoin/theme/__init__.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/theme/__init__.py	Mon Jun 05 19:23:26 2006 +0200
@@ -64,6 +64,50 @@
         # search forms
         'searchbutton': ("[?]",                  "moin-search.png", 12, 12),
         'interwiki':  ("[%(wikitag)s]",          "moin-inter.png",  16, 16),
+    
+        # smileys (this is CONTENT, but good looking smileys depend on looking
+        # adapted to the theme background color and theme style in general)
+        #vvv    ==      vvv  this must be the same for GUI editor converter
+        'X-(':        ("X-(",                    'angry.png',       15, 15),
+        ':D':         (":D",                     'biggrin.png',     15, 15),
+        '<:(':        ("<:(",                    'frown.png',       15, 15),
+        ':o':         (":o",                     'redface.png',     15, 15),
+        ':(':         (":(",                     'sad.png',         15, 15),
+        ':)':         (":)",                     'smile.png',       15, 15),
+        'B)':         ("B)",                     'smile2.png',      15, 15),
+        ':))':        (":))",                    'smile3.png',      15, 15),
+        ';)':         (";)",                     'smile4.png',      15, 15),
+        '/!\\':       ("/!\\",                   'alert.png',       15, 15),
+        '<!>':        ("<!>",                    'attention.png',   15, 15),
+        '(!)':        ("(!)",                    'idea.png',        15, 15),
+
+        # copied 2001-11-16 from http://pikie.darktech.org/cgi/pikie.py?EmotIcon
+        ':-?':        (":-?",                    'tongue.png',      15, 15),
+        ':\\':        (":\\",                    'ohwell.png',      15, 15),
+        '>:>':        (">:>",                    'devil.png',       15, 15),
+        '|)':         ("|)",                     'tired.png',       15, 15),
+        
+        # some folks use noses in their emoticons
+        ':-(':        (":-(",                    'sad.png',         15, 15),
+        ':-)':        (":-)",                    'smile.png',       15, 15),
+        'B-)':        ("B-)",                    'smile2.png',      15, 15),
+        ':-))':       (":-))",                   'smile3.png',      15, 15),
+        ';-)':        (";-)",                    'smile4.png',      15, 15),
+        '|-)':        ("|-)",                    'tired.png',       15, 15),
+        
+        # version 1.0
+        '(./)':       ("(./)",                   'checkmark.png',   20, 15),
+        '{OK}':       ("{OK}",                   'thumbs-up.png',   14, 12),
+        '{X}':        ("{X}",                    'icon-error.png',  16, 16),
+        '{i}':        ("{i}",                    'icon-info.png',   16, 16),
+        '{1}':        ("{1}",                    'prio1.png',       15, 13),
+        '{2}':        ("{2}",                    'prio2.png',       15, 13),
+        '{3}':        ("{3}",                    'prio3.png',       15, 13),
+
+        # version 1.3.4 (stars)
+        # try {*}{*}{o}
+        '{*}':        ("{*}",                    'star_on.png',     15, 15),
+        '{o}':        ("{o}",                    'star_off.png',    15, 15),
     }
     del _
 
@@ -406,7 +450,7 @@
         @return: alt (unicode), href (string), width, height (int)
         """
         if icon in self.icons:
-            alt, filename, w, h = self.icons[icon]
+            alt, icon, w, h = self.icons[icon]
         else:
             # Create filenames to icon data mapping on first call, then
             # cache in class for next calls.
@@ -417,19 +461,18 @@
                 self.__class__.iconsByFile = d
 
             # Try to get icon data by file name
-            filename = icon.replace('.gif','.png')
-            if filename in self.iconsByFile:
-                alt, filename, w, h = self.iconsByFile[filename]
+            if icon in self.iconsByFile:
+                alt, icon, w, h = self.iconsByFile[icon]
             else:
-                alt, filename, w, h = '', icon, '', ''
+                alt, icon, w, h = '', icon, '', ''
                 
-        return alt, self.img_url(filename), w, h
+        return alt, self.img_url(icon), w, h
    
     def make_icon(self, icon, vars=None):
         """
         This is the central routine for making <img> tags for icons!
-        All icons stuff except the top left logo, smileys and search
-        field icons are handled here.
+        All icons stuff except the top left logo and search field icons are
+        handled here.
         
         @param icon: icon id (dict key)
         @param vars: ...
--- a/MoinMoin/user.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/user.py	Mon Jun 05 19:23:26 2006 +0200
@@ -137,8 +137,8 @@
     @return: user name that can be used in acl lines
     """
     name = name.replace('_', ' ') # we treat _ as a blank
-    username_allowedchars = "'@." # ' for names like O'Brian or email addresses.
-                                  # "," and ":" must not be allowed (ACL delimiters).
+    username_allowedchars = "'@.-" # ' for names like O'Brian or email addresses.
+                                   # "," and ":" must not be allowed (ACL delimiters).
     # Strip non alpha numeric characters (except username_allowedchars), keep white space
     name = ''.join([c for c in name if c.isalnum() or c.isspace() or c in username_allowedchars])
 
@@ -223,11 +223,8 @@
         self.auth_attribs = kw.get('auth_attribs', ())
                                        
         # create some vars automatically
-        for tuple in self._cfg.user_form_fields:
-            key = tuple[0]
-            default = self._cfg.user_form_defaults.get(key, '')
-            setattr(self, key, default)
-       
+        self.__dict__.update(self._cfg.user_form_defaults)
+
         if name:
             self.name = name
         elif auth_username: # this is needed for user_autocreate
--- a/MoinMoin/wikixml/marshal.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/wikixml/marshal.py	Mon Jun 05 19:23:26 2006 +0200
@@ -24,12 +24,12 @@
         Default is to omit all properties starting with an underscore.
 
         TAG_MAP is a translation table for tag names, and is empty by
-        default. It can also be used to surpress certain properties by
+        default. It can also be used to suppress certain properties by
         mapping a tag name to `None`.
     """
 
-    # Convenience: Standard-XML-Deklaration
-    XML_DECL = '<?xml version="1.0" encoding="ISO-8859-1"?>\n'
+    # Convenience: Standard XML declaration
+    XML_DECL = '<?xml version="1.0" encoding="utf-8"?>\n'
 
     # Container Tags
     ROOT_CONTAINER = "data"
@@ -65,18 +65,15 @@
             content = "<none/>"
 
         elif isinstance(data, types.StringType):
-            # String
             content = (data.replace("&", "&amp;") # Must be done first!
                            .replace("<", "&lt;")
                            .replace(">", "&gt;"))
 
         elif isinstance(data, types.DictionaryType):
-            # Dictionary
             for key, value in data.items():
                 add_content(self.__toXML(key, value))
 
         elif isinstance(data, types.ListType) or isinstance(data, types.TupleType):
-            # List or Tuple
             for item in data:
                 add_content(self.__toXML(self.ITEM_CONTAINER, item))
 
@@ -87,7 +84,6 @@
             add_content(self.__toXML(self.ROOT_CONTAINER, data.__dict__))
 
         else:
-            # Everything else
             content = (str(data).replace("&", "&amp;") # Must be done first!
                                 .replace("<", "&lt;")
                                 .replace(">", "&gt;"))
--- a/MoinMoin/wikixml/util.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/wikixml/util.py	Mon Jun 05 19:23:26 2006 +0200
@@ -70,4 +70,3 @@
         self.endElementNS((self.xmlns['rdf'], 'RDF'), 'rdf:RDF')
         XMLGenerator.endDocument(self)
 
-
--- a/MoinMoin/wikixml/xsltutil.py	Sat May 27 16:19:45 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - XSLT Utilities
-
-    @copyright: 2001, 2003 by Jürgen Hermann <jh@web.de>
-    @license: GNU GPL, see COPYING for details.
-"""
-
-## currently empty, due to adaption to 4Suite 1.0a1
-## keep this file for later!
-
--- a/MoinMoin/xmlrpc/__init__.py	Sat May 27 16:19:45 2006 +0200
+++ b/MoinMoin/xmlrpc/__init__.py	Mon Jun 05 19:23:26 2006 +0200
@@ -27,12 +27,13 @@
 
 modules = pysupport.getPackageModules(__file__)
 
-import sys, time, xmlrpclib
+import os, sys, time, xmlrpclib
 
 from MoinMoin import config, user, wikiutil
 from MoinMoin.Page import Page
 from MoinMoin.PageEditor import PageEditor
 from MoinMoin.logfile import editlog
+from MoinMoin.action import AttachFile
 
 _debug = 0
 
@@ -397,6 +398,71 @@
                  self._outstr(results.formatContext(hit, 180, 1)))
                 for hit in results.hits]
 
+    def xmlrpc_listAttachments(self, pagename):
+        """ Get all attachments associated with pagename
+        
+        @param pagename: pagename (utf-8)
+        @rtype: list
+        @return: a list of utf-8 attachment names
+        """    
+        pagename = self._instr(pagename)
+        # User may read page?
+        if not self.request.user.may.read(pagename):
+            return self.notAllowedFault()
+        
+        result = AttachFile._get_files(self.request, pagename)
+        return result
+        
+    def xmlrpc_getAttachment(self, pagename, attachname):
+        """ Get attachname associated with pagename
+        
+        @param pagename: pagename (utf-8)
+        @param attachname: attachment name (utf-8)
+        @rtype base64
+        @return base64 data
+        """
+        pagename = self._instr(pagename)
+        # User may read page?
+        if not self.request.user.may.read(pagename):
+            return self.notAllowedFault()
+        
+        filename = wikiutil.taintfilename(filename)
+        filename = AttachFile.getFilename(self.request, pagename, attachname)
+        if not os.path.isfile(filename):
+            return self.noSuchPageFault()
+        return self._outlob(open(filename, 'rb').read())
+        
+    def xmlrpc_putAttachment(self, pagename, attachname, data):
+        """ Set attachname associated with pagename to data
+        
+        @param pagename: pagename (utf-8)
+        @param attachname: attachment name (utf-8)
+        @param data: file data (base64)
+        @rtype boolean
+        @return True if attachment was set
+        """
+        pagename = self._instr(pagename)
+        # User may read page?
+        if not self.request.user.may.read(pagename):
+            return self.notAllowedFault()
+
+        if not self.request.cfg.xmlrpc_putpage_enabled:
+            return xmlrpclib.Boolean(0)
+        if self.request.cfg.xmlrpc_putpage_trusted_only and not self.request.user.trusted:
+            return xmlrpclib.Fault(1, "You are not allowed to edit this page")
+        # also check ACLs
+        if not self.request.user.may.write(pagename):
+            return xmlrpclib.Fault(1, "You are not allowed to edit this page")
+        
+        attachname = wikiutil.taintfilename(attachname)
+        filename = AttachFile.getFilename(self.request, pagename, attachname)
+        if os.path.exists(filename) and not os.path.isfile(filename):
+            return self.noSuchPageFault()
+        open(filename, 'wb+').write(data.data)
+        os.chmod(filename, 0666 & config.umask)
+        AttachFile._addLogEntry(self.request, 'ATTNEW', pagename, filename)
+        return xmlrpclib.Boolean(1)
+
     def process(self):
         """ xmlrpc v1 and v2 dispatcher """
         try:
--- a/docs/CHANGES	Sat May 27 16:19:45 2006 +0200
+++ b/docs/CHANGES	Mon Jun 05 19:23:26 2006 +0200
@@ -94,6 +94,7 @@
       specify 'wiki', it will use a cache directory per wiki and if you specify
       'item', it will use a cache directory per item (== per page).
       Creating a CacheEntry without explicit scope is DEPRECATED.
+    * smileys moved from MoinMoin.config to MoinMoin.theme
 
   New Features:
 
@@ -103,6 +104,11 @@
     * You can have a common cache_dir for your farm (will save a bit space
       and cpu time as it shares some stuff).
       You need to set "cache_dir = '/some/farm/cachedir' in your farmconfig.
+    * Added XMLRPC methods for attachment handling. Thanks to Matthew Gilbert.
+    * Added TLS/SSL support to the standalone server. Thanks to Matthew Gilbert.
+      To use TLS/SSL support you must also install the TLSLite library
+      (http://trevp.net/tlslite/). Version 0.3.8 was used for development and
+      testing.
 
   Bugfixes:
     * on action "info" page, "revert" link will not be displayed for empty page
@@ -111,6 +117,15 @@
     * fix deletion of empty Interwiki links ([wiki:MoinMaster: mm entry page])
     * fix mod_python attachment upload bug (thanks to Nick Phillips)
     * fix show_version to show it in the same way as SystemInfo
+    * allow "-" in usernames (fixes "Invalid user name" msg)
+    * fixed smiley caching bug (smileys didn't change theme)
+    * fixed backtrace when user removed css_url entry from user_form_fields
+
+  Other changes:
+    * we use (again) the same browser compatibility check as FCKeditor uses
+      internally, too. So if GUI editor invocation is broken due to browser
+      compatibility issues or a wrong browser version check, please file a bug
+      at FCKeditor development or browser development.
 
 Version 1.5.current:
   Developer notes:
--- a/wiki/config/mailimportconf.py	Sat May 27 16:19:45 2006 +0200
+++ b/wiki/config/mailimportconf.py	Mon Jun 05 19:23:26 2006 +0200
@@ -3,5 +3,5 @@
 # This secret has to be known by the wiki server
 mailimport_secret = u"foo"
 
-# Only needed for wiki farms
-mailimport_url = u"http://localhost:81/?action=xmlrpc2"
+# The target wiki URL 
+mailimport_url = u"http://localhost/?action=xmlrpc2"
--- a/wiki/htdocs/applets/index.html	Sat May 27 16:19:45 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
-<html>
-  <body bgcolor="#FFFFFF" text="#000000">
-    this is /wiki/applets/index.html of moin-enterprise
-  </body>
-</html>
-
-      
--- a/wiki/htdocs/common/js/common.js	Sat May 27 16:19:45 2006 +0200
+++ b/wiki/htdocs/common/js/common.js	Mon Jun 05 19:23:26 2006 +0200
@@ -17,26 +17,33 @@
 }
 
 function can_use_gui_editor() {
-    var sAgent = navigator.userAgent.toLowerCase();
- 
-    if (sAgent.indexOf("msie") != -1 && sAgent.indexOf("mac") == -1 &&
-        sAgent.indexOf("opera") == -1 ) {
-        // Internet Explorer
-        var sBrowserVersion = navigator.appVersion.match(/MSIE (.\..)/)[1];
-        return ( sBrowserVersion >= 5.5 );
-    } else if (navigator.product == "Gecko" && 
-               navigator.productSub >= 20030210) {
-        // Gecko
-        return true;
+    var sAgent = navigator.userAgent.toLowerCase() ;
+
+    // Internet Explorer
+    if ( sAgent.indexOf("msie") != -1 && sAgent.indexOf("mac") == -1 && sAgent.indexOf("opera") == -1 )
+    {   
+        var sBrowserVersion = navigator.appVersion.match(/MSIE (.\..)/)[1] ;
+        return ( sBrowserVersion >= 5.5 ) ;
     }
-    // else if (sAgent.indexOf("safari") != -1 ) {
-    //    // Safari - build must be at least 312 (1.3)
-    //    return (sAgent.match( /safari\/(\d+)/ )[1] >= 312 );
-    // } 
-    else {
-        // Unknown browser, assume gui editor is not compatible
-        return false;
+    
+    // Gecko
+    if ( navigator.product == "Gecko" && navigator.productSub >= 20030210 )
+        return true ;
+
+    // Opera
+    if ( this.EnableOpera )
+    {   
+        var aMatch = sAgent.match( /^opera\/(\d+\.\d+)/ ) ;
+        if ( aMatch && aMatch[1] >= 9.0 )
+            return true ;
     }
+    
+    // Safari
+    if ( this.EnableSafari && sAgent.indexOf( 'safari' ) != -1 )
+        return ( sAgent.match( /safari\/(\d+)/ )[1] >= 312 ) ;  // Build must be at least 312 (1.3)
+
+    return false ;
+
 }
 
 
--- a/wiki/htdocs/robots.txt	Sat May 27 16:19:45 2006 +0200
+++ b/wiki/htdocs/robots.txt	Mon Jun 05 19:23:26 2006 +0200
@@ -1,1 +1,6 @@
-# You can define rules for nice robots here
\ No newline at end of file
+# if you want to add own robot rules, do it BEFORE the final rule matching *
+
+User-agent: *
+Crawl-delay: 20
+Disallow:
+