changeset 4376:eda647742453

merged moin/1.8
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sun, 05 Oct 2008 00:38:07 +0200
parents 4e55366ea37a (current diff) a5df4e239f2a (diff)
children 00b1307bd9c2
files .hgtags MoinMoin/__init__.py MoinMoin/action/cache.py MoinMoin/action/diff.py MoinMoin/action/info.py MoinMoin/action/newaccount.py MoinMoin/action/recoverpass.py MoinMoin/config/multiconfig.py MoinMoin/i18n/MoinMoin.pot MoinMoin/i18n/__init__.py MoinMoin/i18n/ar.MoinMoin.po MoinMoin/i18n/bg.MoinMoin.po MoinMoin/i18n/ca.MoinMoin.po MoinMoin/i18n/cs.MoinMoin.po MoinMoin/i18n/da.MoinMoin.po MoinMoin/i18n/de.MoinMoin.po MoinMoin/i18n/el.MoinMoin.po MoinMoin/i18n/en.MoinMoin.po MoinMoin/i18n/es.MoinMoin.po MoinMoin/i18n/fa.MoinMoin.po MoinMoin/i18n/fi.MoinMoin.po MoinMoin/i18n/fr.MoinMoin.po MoinMoin/i18n/gl.MoinMoin.po MoinMoin/i18n/he.MoinMoin.po MoinMoin/i18n/hi.MoinMoin.po MoinMoin/i18n/hr.MoinMoin.po MoinMoin/i18n/hu.MoinMoin.po MoinMoin/i18n/id.MoinMoin.po MoinMoin/i18n/it.MoinMoin.po MoinMoin/i18n/ja.MoinMoin.po MoinMoin/i18n/ko.MoinMoin.po MoinMoin/i18n/ku.MoinMoin.po MoinMoin/i18n/lt.MoinMoin.po MoinMoin/i18n/lv.MoinMoin.po MoinMoin/i18n/mk.MoinMoin.po MoinMoin/i18n/mn.MoinMoin.po MoinMoin/i18n/nb.MoinMoin.po MoinMoin/i18n/nl.MoinMoin.po MoinMoin/i18n/pl.MoinMoin.po MoinMoin/i18n/pt-br.MoinMoin.po MoinMoin/i18n/pt.MoinMoin.po MoinMoin/i18n/ro.MoinMoin.po MoinMoin/i18n/ru.MoinMoin.po MoinMoin/i18n/sl.MoinMoin.po MoinMoin/i18n/sr.MoinMoin.po MoinMoin/i18n/sv.MoinMoin.po MoinMoin/i18n/tr.MoinMoin.po MoinMoin/i18n/uk.MoinMoin.po MoinMoin/i18n/vi.MoinMoin.po MoinMoin/i18n/zh-tw.MoinMoin.po MoinMoin/i18n/zh.MoinMoin.po MoinMoin/script/server/standalone.py MoinMoin/theme/__init__.py MoinMoin/user.py MoinMoin/userprefs/changepass.py MoinMoin/userprefs/oid.py MoinMoin/version.py MoinMoin/wikiutil.py moin.spec wiki/underlay.tar
diffstat 29 files changed, 286 insertions(+), 140 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Sat Oct 04 19:01:19 2008 +0200
+++ b/.hgtags	Sun Oct 05 00:38:07 2008 +0200
@@ -27,3 +27,5 @@
 ae9bf455eec6a080dd6aafacdaf30fb881b1ca68 1.7.2
 2907390f9d4630f5459506d3d3352c96f75e67b3 1.8.0beta1
 117a21659358defd880baaccf5c73e7b1a71ea1e 1.8.0beta2
+df1ba1ddf05061d6451797869c159b3c60319470 1.8.0beta3
+4186e90ead069aad4b739f278eb8599dc00ff6a4 1.8.0rc1
--- a/MoinMoin/action/cache.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/action/cache.py	Sun Oct 05 00:38:07 2008 +0200
@@ -27,8 +27,6 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-import hmac, sha
-
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
@@ -39,6 +37,7 @@
 from MoinMoin import config, caching
 from MoinMoin.util import filesys
 from MoinMoin.action import AttachFile
+from MoinMoin.support.python_compatibility import hmac_new
 
 action_name = __name__.split('.')[-1]
 
@@ -98,7 +97,7 @@
         raise AssertionError('cache_key called with unsupported parameters')
 
     hmac_data = hmac_data.encode('utf-8')
-    key = hmac.new(secret, hmac_data, sha).hexdigest()
+    key = hmac_new(secret, hmac_data).hexdigest()
     return key
 
 
--- a/MoinMoin/action/diff.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/action/diff.py	Sun Oct 05 00:38:07 2008 +0200
@@ -119,12 +119,12 @@
 
     # Revision list starts from 2...
     if oldrev == min(revlist):
-        disable_prev = u' disabled="true"'
+        disable_prev = u' disabled="disabled"'
     else:
         disable_prev = u''
 
     if newrev == max(revlist):
-        disable_next = u' disabled="true"'
+        disable_next = u' disabled="disabled"'
     else:
         disable_next = u''
 
@@ -134,13 +134,13 @@
     if request.user.may.revert(pagename):
         revert_html = """
  <td style="border:0">
-  <span style="text-align:center">
-   <form action="%s" method="get">
+  <form action="%s" method="get">
+   <div style="text-align:center">
     <input name="action" value="revert" type="hidden">
     <input name="rev" value="%d" type="hidden">
     <input value="%s" type="submit"%s>
-   </form>
-  </span>
+   </div>
+  </form>
  </td>
  """ % (page_url, rev2, _("Revert to this revision"), disable_next)
 
@@ -149,25 +149,25 @@
 <table class="diff">
 <tr>
  <td style="border:0">
-  <span style="text-align:left">
-   <form action="%s" method="get">
+  <form action="%s" method="get">
+   <div style="text-align:left">
     <input name="action" value="diff" type="hidden">
     <input name="rev1" value="%d" type="hidden">
     <input name="rev2" value="%d" type="hidden">
     <input value="%s" type="submit"%s>
-   </form>
-  </span>
+   </div>
+  </form>
  </td>
  %s
  <td style="border:0">
-  <span style="text-align:right">
-   <form action="%s" method="get">
+  <form action="%s" method="get">
+   <div style="text-align:right">
     <input name="action" value="diff" type="hidden">
     <input name="rev1" value="%d" type="hidden">
     <input name="rev2" value="%d" type="hidden">
     <input value="%s" type="submit"%s>
-   </form>
-  </span>
+   </div>
+  </form>
  </td>
 </tr>
 </table>
--- a/MoinMoin/action/info.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/action/info.py	Sun Oct 05 00:38:07 2008 +0200
@@ -32,8 +32,8 @@
                       f.text(_("Page size: %d") % page.size()),
                       f.paragraph(0))
 
-        import sha
-        digest = sha.new(page.get_raw_body().encode(config.charset)).hexdigest().upper()
+        from MoinMoin.support.python_compatibility import hash_new
+        digest = hash_new('sha1', page.get_raw_body().encode(config.charset)).hexdigest().upper()
         request.write(f.paragraph(1),
                       f.rawHTML('%(label)s <tt>%(value)s</tt>' % {
                           'label': _("SHA digest of this page's content is:"),
--- a/MoinMoin/action/newaccount.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/action/newaccount.py	Sun Oct 05 00:38:07 2008 +0200
@@ -56,7 +56,7 @@
 
     pw_checker = request.cfg.password_checker
     if pw_checker:
-        pw_error = pw_checker(theuser.name, password)
+        pw_error = pw_checker(request, theuser.name, password)
         if pw_error:
             return _("Password not acceptable: %s") % pw_error
 
--- a/MoinMoin/action/recoverpass.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/action/recoverpass.py	Sun Oct 05 00:38:07 2008 +0200
@@ -173,7 +173,7 @@
             pw_checker = request.cfg.password_checker
             pw_error = None
             if pw_checker:
-                pw_error = pw_checker(name, newpass)
+                pw_error = pw_checker(request, name, newpass)
                 if pw_error:
                     msg = _("Password not acceptable: %s") % pw_error
             if not pw_error:
--- a/MoinMoin/config/_tests/test_multiconfig.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/config/_tests/test_multiconfig.py	Sun Oct 05 00:38:07 2008 +0200
@@ -32,7 +32,7 @@
             py.test.skip("password_checker is disabled in the configuration, not testing it")
         else:
             for pw, result in self.tests_builtin:
-                pw_error = pw_checker(self.username, pw)
+                pw_error = pw_checker(self.request, self.username, pw)
                 print "%r: %s" % (pw, pw_error)
                 assert result == (pw_error is None)
 
--- a/MoinMoin/config/multiconfig.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/config/multiconfig.py	Sun Oct 05 00:38:07 2008 +0200
@@ -571,7 +571,8 @@
         Since each configured plugin path has unique plugins, we load the
         plugin packages as "moin_plugin_<sha1(path)>.plugin".
         """
-        import imp, sha
+        import imp
+        from MoinMoin.support.python_compatibility import hash_new
 
         plugin_dirs = [self.plugin_dir] + self.plugin_dirs
         self._plugin_modules = []
@@ -581,7 +582,7 @@
             imp.acquire_lock()
             try:
                 for pdir in plugin_dirs:
-                    csum = 'p_%s' % sha.new(pdir).hexdigest()
+                    csum = 'p_%s' % hash_new('sha1', pdir).hexdigest()
                     modname = '%s.%s' % (self.siteid, csum)
                     # If the module is not loaded, try to load it
                     if not modname in sys.modules:
@@ -640,7 +641,7 @@
     # the options dictionary.
 
 
-def _default_password_checker(request, username, password):
+def _default_password_checker(cfg, request, username, password):
     """ Check if a password is secure enough.
         We use a built-in check to get rid of the worst passwords.
 
@@ -652,18 +653,19 @@
         @return: None if there is no problem with the password,
                  some string with an error msg, if the password is problematic.
     """
+    _ = request.getText
     try:
         # in any case, do a very simple built-in check to avoid the worst passwords
         if len(password) < 6:
-            raise ValueError("Password too short.")
+            raise ValueError(_("Password is too short."))
         if len(set(password)) < 4:
-            raise ValueError("Password has not enough different characters.")
+            raise ValueError(_("Password has not enough different characters."))
 
         username_lower = username.lower()
         password_lower = password.lower()
         if username in password or password in username or \
            username_lower in password_lower or password_lower in username_lower:
-            raise ValueError("Password too easy (containment).")
+            raise ValueError(_("Password is too easy (password contains name or name contains password)."))
 
         keyboards = (ur"`1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./", # US kbd
                      ur"^1234567890ß´qwertzuiopü+asdfghjklöä#yxcvbnm,.-", # german kbd
@@ -672,10 +674,10 @@
             rev_kbd = kbd[::-1]
             if password in kbd or password in rev_kbd or \
                password_lower in kbd or password_lower in rev_kbd:
-                raise ValueError("Password too easy (kbd sequence)")
+                raise ValueError(_("Password is too easy (keyboard sequence)."))
         return None
     except ValueError, err:
-        return str(err)
+        return unicode(err)
 
 
 class DefaultExpression(object):
--- a/MoinMoin/i18n/__init__.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/i18n/__init__.py	Sun Oct 05 00:38:07 2008 +0200
@@ -329,7 +329,7 @@
     # is available on this wiki...
     lang = get_browser_language(request)
     if not lang:
-        available = wikiLanguages()
+        available = wikiLanguages() or ["en"]
         # Or return the wiki default language...
         if request.cfg.language_default in available:
             lang = request.cfg.language_default
--- a/MoinMoin/log.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/log.py	Sun Oct 05 00:38:07 2008 +0200
@@ -93,6 +93,22 @@
 configured = False
 fallback_config = False
 
+import warnings
+
+# 'CacheNeedsUpdate' string exception in Page.py is supported for backwards compat reasons:
+warnings.filterwarnings('ignore', r'catching of string exceptions is deprecated', module='MoinMoin.Page')
+
+# subprocess was added in python 2.4 - we can't use it as long as we do not require 2.4:
+warnings.filterwarnings('ignore', r'The popen\d? module is deprecated.  Use the subprocess module.')
+
+
+def _log_warning(message, category, filename, lineno, file=None, line=None):
+    # for warnings, we just want to use the logging system, not stderr or other files
+    msg = "%s:%s: %s: %s" % (filename, lineno, category.__name__, message)
+    logger = getLogger(__name__)
+    logger.warning(msg) # Note: the warning will look like coming from here,
+                        # but msg contains info about where it really comes from
+
 
 def load_config(conf_fname=None):
     """ load logging config from conffile """
@@ -106,6 +122,7 @@
             configured = True
             l = getLogger(__name__)
             l.info('using logging configuration read from "%s"' % conf_fname)
+            warnings.showwarning = _log_warning
         except Exception, err: # XXX be more precise
             err_msg = str(err)
     if not configured:
@@ -118,6 +135,7 @@
         if err_msg:
             l.warning('load_config for "%s" failed with "%s".' % (conf_fname, err_msg))
         l.warning('using logging configuration read from built-in fallback in MoinMoin.log module!')
+        warnings.showwarning = _log_warning
 
 
 def getLogger(name):
--- a/MoinMoin/macro/FootNote.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/macro/FootNote.py	Sun Oct 05 00:38:07 2008 +0200
@@ -11,10 +11,9 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-import sha
-
 from MoinMoin import config, wikiutil
 from MoinMoin.parser.text_moin_wiki import Parser as WikiParser
+from MoinMoin.support.python_compatibility import hash_new
 
 Dependencies = ["time"] # footnote macro cannot be cached
 
@@ -35,7 +34,7 @@
         idx = request.footnote_ctr
         request.footnote_ctr += 1
 
-        shahex = sha.new(args.encode(config.charset)).hexdigest()
+        shahex = hash_new('sha1', args.encode(config.charset)).hexdigest()
         backlink_id = "fndef-%s-%d" % (shahex, idx)
         fwdlink_id = "fnref-%s" % shahex
 
--- a/MoinMoin/parser/_ParserBase.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/parser/_ParserBase.py	Sun Oct 05 00:38:07 2008 +0200
@@ -24,9 +24,10 @@
 
 """
 
-import re, sha
+import re
 
 from MoinMoin import config, wikiutil
+from MoinMoin.support.python_compatibility import hash_new
 
 class FormatTextBase:
     pass
@@ -250,7 +251,7 @@
 
         result = [] # collects output
 
-        self._code_id = sha.new(self.raw.encode(config.charset)).hexdigest()
+        self._code_id = hash_new('sha1', self.raw.encode(config.charset)).hexdigest()
         result.append(formatter.code_area(1, self._code_id, self.parsername, self.show_nums, self.num_start, self.num_step))
 
         self.lastpos = 0
--- a/MoinMoin/parser/text_creole.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/parser/text_creole.py	Sun Oct 05 00:38:07 2008 +0200
@@ -228,10 +228,11 @@
 #        return self.formatter.smiley(node.content)
 
     def header_emit(self, node):
-        import sha
+        from MoinMoin.support.python_compatibility import hash_new
+
         pntt = '%s%s%d' % (self.formatter.page.page_name,
             self.get_text(node), node.level)
-        ident = "head-%s" % sha.new(pntt.encode(config.charset)).hexdigest()
+        ident = "head-%s" % hash_new('sha1', pntt.encode(config.charset)).hexdigest()
         return ''.join([
             self.formatter.heading(1, node.level, id=ident),
             self.formatter.text(node.content or ''),
--- a/MoinMoin/parser/text_python.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/parser/text_python.py	Sun Oct 05 00:38:07 2008 +0200
@@ -8,10 +8,11 @@
 """
 
 import StringIO
-import keyword, token, tokenize, sha
+import keyword, token, tokenize
 
 from MoinMoin import config, wikiutil
 from MoinMoin.parser._ParserBase import parse_start_step
+from MoinMoin.support.python_compatibility import hash_new
 
 _KEYWORD = token.NT_OFFSET + 1
 _TEXT = token.NT_OFFSET + 2
@@ -62,7 +63,7 @@
 
         self.result = [] # collects output
 
-        self._code_id = sha.new(self.raw.encode(config.charset)).hexdigest()
+        self._code_id = hash_new('sha1', self.raw.encode(config.charset)).hexdigest()
         self.result.append(formatter.code_area(1, self._code_id, 'ColorizedPython', self.show_num, self.num_start, self.num_step))
         self.formatter = formatter
         self.result.append(formatter.code_line(1))
--- a/MoinMoin/script/migration/text_moin158_wiki.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/script/migration/text_moin158_wiki.py	Sun Oct 05 00:38:07 2008 +0200
@@ -748,7 +748,7 @@
 
     def _heading_repl(self, word):
         """Handle section headings."""
-        import sha
+        from MoinMoin.support.python_compatibility import hash_new
 
         h = word.strip()
         level = 1
@@ -768,7 +768,7 @@
         if self.titles[pntt] > 1:
             unique_id = '-%d' % self.titles[pntt]
         result = self._closeP()
-        result += self.formatter.heading(1, depth, id="head-"+sha.new(pntt.encode(config.charset)).hexdigest()+unique_id)
+        result += self.formatter.heading(1, depth, id="head-"+hash_new('sha1', pntt.encode(config.charset)).hexdigest()+unique_id)
 
         return (result + self.formatter.text(title_text) +
                 self.formatter.heading(0, depth))
--- a/MoinMoin/script/migration/text_moin160a_wiki.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/script/migration/text_moin160a_wiki.py	Sun Oct 05 00:38:07 2008 +0200
@@ -767,7 +767,7 @@
 
     def _heading_repl(self, word):
         """Handle section headings."""
-        import sha
+        from MoinMoin.support.python_compatibility import hash_new
 
         h = word.strip()
         level = 1
@@ -786,7 +786,7 @@
         if self.titles[pntt] > 1:
             unique_id = '-%d' % self.titles[pntt]
         result = self._closeP()
-        result += self.formatter.heading(1, depth, id="head-"+sha.new(pntt.encode(config.charset)).hexdigest()+unique_id)
+        result += self.formatter.heading(1, depth, id="head-"+hash_new('sha1', pntt.encode(config.charset)).hexdigest()+unique_id)
 
         return (result + self.formatter.text(title_text) +
                 self.formatter.heading(0, depth))
--- a/MoinMoin/script/migration/wikiutil160a.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/script/migration/wikiutil160a.py	Sun Oct 05 00:38:07 2008 +0200
@@ -1598,10 +1598,9 @@
 
 def createTicket(request, tm=None):
     """Create a ticket using a site-specific secret (the config)"""
-    import sha
+    from MoinMoin.support.python_compatibility import hash_new
     ticket = tm or "%010x" % time.time()
-    digest = sha.new()
-    digest.update(ticket)
+    digest = hash_new('sha1', ticket)
 
     varnames = ['data_dir', 'data_underlay_dir', 'language_default',
                 'mail_smarthost', 'mail_from', 'page_front_page',
--- a/MoinMoin/script/server/standalone.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/script/server/standalone.py	Sun Oct 05 00:38:07 2008 +0200
@@ -102,7 +102,7 @@
                 Config.group = self.options.group
             if self.options.port:
                 Config.port = self.options.port
-            if self.options.interface:
+            if self.options.interface is not None: # needs to work for "" value also
                 Config.interface = self.options.interface
             if self.options.debug:
                 Config.debug = True
@@ -130,3 +130,4 @@
     port = 8080
     interface = 'localhost'
     debug = False
+
--- a/MoinMoin/search/results.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/search/results.py	Sun Oct 05 00:38:07 2008 +0200
@@ -333,7 +333,7 @@
                 if page.attachment:
                     querydict = {
                         'action': 'AttachFile',
-                        'do': 'get',
+                        'do': 'view',
                         'target': page.attachment,
                     }
                 elif page.page.rev and page.page.rev != page.page.getRevList()[0]:
@@ -416,7 +416,7 @@
                     fmt_context = ""
                     querydict = {
                         'action': 'AttachFile',
-                        'do': 'get',
+                        'do': 'view',
                         'target': page.attachment,
                     }
                 elif page.page_name.startswith('FS/'): # XXX FS hardcoded
--- a/MoinMoin/security/textcha.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/security/textcha.py	Sun Oct 05 00:38:07 2008 +0200
@@ -89,8 +89,8 @@
                 self.answer_re = re.compile(self.answer_regex, re.U|re.I)
             except KeyError:
                 # this question does not exist, thus there is no answer
-                self.answer_regex = ur"[^.]*" # this shall never match!
-                self.answer_re = re.compile(self.answer_regex, re.U|re.I)
+                self.answer_regex = ur"[Never match for cheaters]"
+                self.answer_re = None
                 logging.warning(u"TextCha: Non-existing question '%s'. User '%s' trying to cheat?" % (
                                 self.question, self.user_info))
             except re.error:
@@ -116,7 +116,11 @@
     def check_answer(self, given_answer):
         """ check if the given answer to the question is correct """
         if self.is_enabled():
-            success = self.answer_re.match(given_answer.strip()) is not None
+            if self.answer_re is not None:
+                success = self.answer_re.match(given_answer.strip()) is not None
+            else:
+                # someone trying to cheat!?
+                success = False
             success_status = success and u"success" or u"failure"
             logging.info(u"TextCha: %s (u='%s', a='%s', re='%s', q='%s')" % (
                              success_status,
--- a/MoinMoin/support/python_compatibility.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/support/python_compatibility.py	Sun Oct 05 00:38:07 2008 +0200
@@ -81,13 +81,22 @@
 although it may not be 100% compatible.
 """
 try:
-    from hashlib import new as hash_new
-except (NameError,  ImportError):
+    import hashlib, hmac
+    hash_new = hashlib.new
+    def hmac_new(key, msg, digestmod=hashlib.sha1):
+        return hmac.new(key, msg, digestmod)
+
+except (NameError, ImportError):
+    import sha
     def hash_new(name, string=''):
         if name in ('SHA1', 'sha1'):
-            import sha
             return sha.new(string)
         elif name in ('MD5', 'md5'):
             import md5
             return md5.new(string)
         raise ValueError("unsupported hash type")
+
+    def hmac_new(key, msg, digestmod=sha):
+        import hmac
+        return hmac.new(key, msg, digestmod)
+
--- a/MoinMoin/theme/__init__.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/theme/__init__.py	Sun Oct 05 00:38:07 2008 +0200
@@ -1093,10 +1093,19 @@
 
         html = self._cache.get('editbar')
         if html is None:
-            # Remove empty items and format as list
-            items = ''.join(['<li>%s</li>' % item
-                             for item in self.editbarItems(page) if item])
-            html = u'<ul class="editbar">%s</ul>\n' % items
+            # Remove empty items and format as list. The item for showing inline comments
+            # is hidden by default. It gets activated through javascript only if inline
+            # comments exist on the page.
+            items = []
+            for item in self.editbarItems(page):
+                if item:
+                    if 'nbcomment' in item:
+                        # hiding the complete list item is cosmetically better than just
+                        # hiding the contents (e.g. for sidebar themes).
+                        items.append('<li class="toggleCommentsButton" style="display:none;">%s</li>' % item)
+                    else:
+                        items.append('<li>%s</li>' % item)
+            html = u'<ul class="editbar">%s</ul>\n' % ''.join(items)
             self._cache['editbar'] = html
 
         return html
@@ -1143,7 +1152,7 @@
                 # we just use <a> to get same style as other links, but we add some dummy
                 # link target to get correct mouseover pointer appearance. return false
                 # keeps the browser away from jumping to the link target::
-                editbar_actions.append('<a href="#" class="toggleCommentsButton" style="display:none;" onClick="toggleComments();return false;">%s</a>' % _('Comments'))
+                editbar_actions.append('<a href="#" class="nbcomment" onClick="toggleComments();return false;">%s</a>' % _('Comments'))
             elif editbar_item == 'Edit':
                 editbar_actions.append(self.editorLink(page))
             elif editbar_item == 'Info':
--- a/MoinMoin/user.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/user.py	Sun Oct 05 00:38:07 2008 +0200
@@ -22,7 +22,9 @@
 # add names here to hide them in the cgitb traceback
 unsafe_names = ("id", "key", "val", "user_data", "enc_password", "recoverpass_key")
 
-import os, time, sha, codecs, hmac, base64
+import os, time, codecs, base64
+
+from MoinMoin.support.python_compatibility import hash_new, hmac_new
 
 from MoinMoin import config, caching, wikiutil, i18n, events
 from MoinMoin.util import timefuncs, filesys, random_string
@@ -154,7 +156,7 @@
     if salt is None:
         salt = random_string(20)
     assert isinstance(salt, str)
-    hash = sha.new(pwd)
+    hash = hash_new('sha1', pwd)
     hash.update(salt)
 
     return '{SSHA}' + base64.encodestring(hash.digest() + salt).rstrip()
@@ -514,7 +516,7 @@
         password = password.encode('utf-8')
 
         if epwd[:5] == '{SHA}':
-            enc = '{SHA}' + base64.encodestring(sha.new(password).digest()).rstrip()
+            enc = '{SHA}' + base64.encodestring(hash_new('sha1', password).digest()).rstrip()
             if epwd == enc:
                 data['enc_password'] = encodePassword(password)
                 return True, True
@@ -523,7 +525,7 @@
         if epwd[:6] == '{SSHA}':
             data = base64.decodestring(epwd[6:])
             salt = data[20:]
-            hash = sha.new(password)
+            hash = hash_new('sha1', password)
             hash.update(salt)
             return hash.digest() == data[:20], False
 
@@ -983,7 +985,7 @@
     def generate_recovery_token(self):
         key = random_string(64, "abcdefghijklmnopqrstuvwxyz0123456789")
         msg = str(int(time.time()))
-        h = hmac.new(key, msg, sha).hexdigest()
+        h = hmac_new(key, msg).hexdigest()
         self.recoverpass_key = key
         self.save()
         return msg + '-' + h
@@ -1001,7 +1003,7 @@
         if stamp + 12*60*60 < time.time():
             return False
         # check hmac
-        h = hmac.new(self.recoverpass_key, str(stamp), sha).hexdigest()
+        h = hmac_new(self.recoverpass_key, str(stamp)).hexdigest()
         if h != parts[1]:
             return False
         self.recoverpass_key = ""
--- a/MoinMoin/userprefs/changepass.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/userprefs/changepass.py	Sun Oct 05 00:38:07 2008 +0200
@@ -54,7 +54,7 @@
 
         pw_checker = request.cfg.password_checker
         if pw_checker:
-            pw_error = pw_checker(request.user.name, password)
+            pw_error = pw_checker(request, request.user.name, password)
             if pw_error:
                 return 'error', _("Password not acceptable: %s") % pw_error
 
--- a/MoinMoin/userprefs/oid.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/userprefs/oid.py	Sun Oct 05 00:38:07 2008 +0200
@@ -9,7 +9,8 @@
 from MoinMoin import wikiutil, user
 from MoinMoin.widget import html
 from MoinMoin.userprefs import UserPrefBase
-import sha
+from MoinMoin.support.python_compatibility import hash_new
+
 try:
     from MoinMoin.auth.openidrp import OpenIDAuth
     from MoinMoin.util.moinoid import MoinOpenIDStore
@@ -45,7 +46,7 @@
             return
         openids = self.request.user.openids[:]
         for oid in self.request.user.openids:
-            name = "rm-%s" % sha.new(oid).hexdigest()
+            name = "rm-%s" % hash_new('sha1', oid).hexdigest()
             if name in self.request.form:
                 openids.remove(oid)
         if not openids and len(self.request.cfg.auth) == 1:
@@ -168,7 +169,7 @@
         _ = self.request.getText
         form = self._make_form()
         for oid in self.request.user.openids:
-            name = "rm-%s" % sha.new(oid).hexdigest()
+            name = "rm-%s" % hash_new('sha1', oid).hexdigest()
             form.append(html.INPUT(type="checkbox", name=name, id=name))
             form.append(html.LABEL(for_=name).append(html.Text(oid)))
             form.append(html.BR())
--- a/MoinMoin/util/moinoid.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/util/moinoid.py	Sun Oct 05 00:38:07 2008 +0200
@@ -5,7 +5,6 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-from sha import sha
 from random import randint
 import time
 
@@ -15,6 +14,7 @@
 from openid.store import nonce
 
 from MoinMoin import caching
+from MoinMoin.support.python_compatibility import hash_new
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
@@ -53,7 +53,7 @@
 
     def key(self, url):
         '''return cache key'''
-        return sha(url).hexdigest()
+        return hash_new('sha1', url).hexdigest()
 
     def storeAssociation(self, server_url, association):
         ce = caching.CacheEntry(self.request, 'openid', self.key(server_url),
@@ -104,7 +104,7 @@
 
     def useNonce(self, server_url, timestamp, salt):
         val = ''.join([str(server_url), str(timestamp), str(salt)])
-        csum = sha(val).hexdigest()
+        csum = hash_new('sha1', val).hexdigest()
         ce = caching.CacheEntry(self.request, 'openid-nonce', csum,
                                 scope='farm', use_pickle=False)
         if ce.exists():
--- a/MoinMoin/wikiutil.py	Sat Oct 04 19:01:19 2008 +0200
+++ b/MoinMoin/wikiutil.py	Sun Oct 05 00:38:07 2008 +0200
@@ -2480,7 +2480,7 @@
                              action you call when posting the form.
     """
 
-    import sha
+    from MoinMoin.support.python_compatibility import hash_new
     if tm is None:
         tm = "%010x" % time.time()
 
@@ -2497,7 +2497,7 @@
             action = 'None'
 
     secret = request.cfg.secrets['wikiutil/tickets']
-    digest = sha.new(secret)
+    digest = hash_new('sha1', secret)
 
     ticket = "%s.%s.%s" % (tm, pagename, action)
     digest.update(ticket)
--- a/docs/CHANGES	Sat Oct 04 19:01:19 2008 +0200
+++ b/docs/CHANGES	Sun Oct 05 00:38:07 2008 +0200
@@ -32,12 +32,35 @@
     editor_default = 'text'  # internal default, just for completeness
 
 
-Current:
+Version 1.8.0rc1:
+  Bug Fixes:
+    * Search results: link to 'view' rendering of found attachments.
+    * Fixed moin server standalone argument processing for --interface="".
+    * mointwisted: fixed Twisted start script.
+    * Fix dbw_hide_buttons javascript.
+    * Logging:
+      * Use logging framework for messages emitted by warnings module (e.g.
+        DeprecationWarning), silence some specific warnings.
+      * Removed superfluous linefeeds in timing log output.
+
+
+Version 1.8.0beta3:
   Bug Fixes:
     * Jabber bot can now be configured to use an authentication realm which
       is different from the server's hostname; the xmpp_node configuration
       parameter can now contain a full JID and the xmpp_resource parameter
-      is no longer supported
+      is no longer supported.
+    * Added translation to the password checker result messages.
+    * Bug fix for language not installed (MoinMoinBugs/WikiLanguageNotDefined).
+    * diff action: make output valid html 4.01
+    * Fixed editbar hidden comment link cosmetics for sidebar themes (hide the
+      complete list element).
+    * Standalone server: fix serverClass argument processing, announce used
+      serverClass in log output.
+
+  Other changes:
+    * Speed up javascript comments processing on IE by getElementsByClassName()
+
 
 Version 1.8.0beta2:
   Bug Fixes:
--- a/wiki/htdocs/common/js/common.js	Sat Oct 04 19:01:19 2008 +0200
+++ b/wiki/htdocs/common/js/common.js	Sun Oct 05 00:38:07 2008 +0200
@@ -1,4 +1,4 @@
-        //
+//
 // MoinMoin commonly used JavaScript functions
 //
 
@@ -67,51 +67,51 @@
 
 // use this instead of assigning to window.onload directly:
 function addLoadEvent(func) {
-  // alert("addLoadEvent " + func)
-  var oldonload = window.onload;
-  if (typeof window.onload != 'function') {
-    window.onload = func;
-  } else {
-    window.onload = function() {
-      oldonload();
-      func();
+    // alert("addLoadEvent " + func)
+    var oldonload = window.onload;
+    if (typeof window.onload != 'function') {
+        window.onload = func;
+    } else {
+        window.onload = function() {
+            oldonload();
+            func();
+        }
     }
-  }
 }
 
 // copy from fckeditor browser check code (fckeditor.js:298, function : FCKeditor_IsCompatibleBrowser)
 function can_use_gui_editor() {
-	var sAgent = navigator.userAgent.toLowerCase() ;
+    var sAgent = navigator.userAgent.toLowerCase() ;
 
-	// Internet Explorer 5.5+
-	if ( /*@cc_on!@*/false && sAgent.indexOf("mac") == -1 )
-	{
-		var sBrowserVersion = navigator.appVersion.match(/MSIE (.\..)/)[1] ;
-		return ( sBrowserVersion >= 5.5 ) ;
-	}
+    // Internet Explorer 5.5+
+    if ( /*@cc_on!@*/false && sAgent.indexOf("mac") == -1 )
+    {
+        var sBrowserVersion = navigator.appVersion.match(/MSIE (.\..)/)[1] ;
+        return ( sBrowserVersion >= 5.5 ) ;
+    }
 
-	// Gecko (Opera 9 tries to behave like Gecko at this point).
-	if ( navigator.product == "Gecko" && navigator.productSub >= 20030210 && !( typeof(opera) == 'object' && opera.postError ) )
-		return true ;
+    // Gecko (Opera 9 tries to behave like Gecko at this point).
+    if ( navigator.product == "Gecko" && navigator.productSub >= 20030210 && !( typeof(opera) == 'object' && opera.postError ) )
+        return true ;
 
-	// Opera 9.50+
-	if ( window.opera && window.opera.version && parseFloat( window.opera.version() ) >= 9.5 )
-		return true ;
+    // Opera 9.50+
+    if ( window.opera && window.opera.version && parseFloat( window.opera.version() ) >= 9.5 )
+        return true ;
 
 /*
   // disable safari : until fck devteam fix http://dev.fckeditor.net/ticket/2333
   
-	// Adobe AIR
-	// Checked before Safari because AIR have the WebKit rich text editor
-	// features from Safari 3.0.4, but the version reported is 420.
-	if ( sAgent.indexOf( ' adobeair/' ) != -1 )
-		return ( sAgent.match( / adobeair\/(\d+)/ )[1] >= 1 ) ;	// Build must be at least v1
+    // Adobe AIR
+    // Checked before Safari because AIR have the WebKit rich text editor
+    // features from Safari 3.0.4, but the version reported is 420.
+    if ( sAgent.indexOf( ' adobeair/' ) != -1 )
+        return ( sAgent.match( / adobeair\/(\d+)/ )[1] >= 1 ) ; // Build must be at least v1
 
-	// Safari 3+
-	if ( sAgent.indexOf( ' applewebkit/' ) != -1 )
-		return ( sAgent.match( / applewebkit\/(\d+)/ )[1] >= 522 ) ;	// Build must be at least 522 (v3)
+    // Safari 3+
+    if ( sAgent.indexOf( ' applewebkit/' ) != -1 )
+        return ( sAgent.match( / applewebkit\/(\d+)/ )[1] >= 522 ) ;    // Build must be at least 522 (v3)
 */
-	return false ;
+    return false ;
 
 }
 
@@ -174,37 +174,30 @@
     }
 }
 
+// for long documents with many comments this is expensive to calculate,
+// thus we keep it here:
+comments = null;
+
 function toggleComments() {
-    // Toggle visibility of every tag with class == *comment*
-    var all = document.getElementsByTagName('*');
-    for (i = 0; i < all.length; i++){
-        el = all[i];
-        if ( el.className.indexOf('comment') >= 0 ){
-            if ( el.style.display != 'none' ) {
-                el.style.display = 'none';
-            } else {
-                el.style.display = '';
-            }
+    // Toggle visibility of every tag with class "comment"
+    for (i = 0; i < comments.length; i++){
+        el = comments[i];
+        if ( el.style.display != 'none' ) {
+            el.style.display = 'none';
+        } else {
+            el.style.display = '';
         }
     }
 }
 
 function show_toggleComments() {
-    // Show edit bar item "ToggleComments" if inline comments exist on this page
-    var all = document.getElementsByTagName('*');
-    var count = 0;
-    for (i = 0; i < all.length; i++){
-        el = all[i];
-        if ( el.className.indexOf('comment') >= 0 ){
-            count++;
-        }
-    }
-    if (count > 0) {
-        for (i = 0; i < all.length; i++){
-            el = all[i];
-            if ( el.className == 'toggleCommentsButton' ){
-                el.style.display = 'inline';
-            }
+    // Show edit bar item for toggling inline comments on/off only if inline comments exist on the page
+    comments = getElementsByClassName('comment', null, document);
+    if (comments.length > 0) {
+        var buttons = getElementsByClassName('toggleCommentsButton', null, document);
+        for (i = 0; i < buttons.length; i++){
+            el = buttons[i];
+            el.style.display = '';
         }
     }
 }
@@ -218,7 +211,7 @@
 
     // login focus
     if (document.forms['loginform']) {
-    	document.forms['loginform'].elements['name'].focus();
+        document.forms['loginform'].elements['name'].focus();
     }
     
     // Page view stuff
@@ -301,16 +294,98 @@
 function dbw_hide_buttons() {
     var form;
     var elem;
+    var name;
 
     for (var fidx = 0; fidx < document.forms.length; fidx++) {
         form = document.forms[fidx];
         for (var eidx = 0; eidx < form.elements.length; eidx++) {
             elem = form.elements[eidx];
             name = elem.name;
-			if (name) {
-				if (name.substr(0,4) == 'dbw.' && name.substr(-7) == '.submit')
-					elem.style.display = 'none';
-			}
+            if (name) {
+                if (name.substr(0,4) == 'dbw.' && name.substr(-7) == '.submit')
+                    elem.style.display = 'none';
+            }
         }
     }
 }
+
+/*  getElementsByClassName
+    Developed by Robert Nyman, http://www.robertnyman.com
+    Code/licensing: http://code.google.com/p/getelementsbyclassname/ (MIT license)
+    Version: 1.0.1
+*/  
+var getElementsByClassName = function (className, tag, elm){
+    if (document.getElementsByClassName) {
+        getElementsByClassName = function (className, tag, elm) {
+            elm = elm || document;
+            var elements = elm.getElementsByClassName(className),
+                nodeName = (tag)? new RegExp("\\b" + tag + "\\b", "i") : null,
+                returnElements = [],
+                current;
+            for(var i=0, il=elements.length; i<il; i+=1){
+                current = elements[i];
+                if(!nodeName || nodeName.test(current.nodeName)) {
+                    returnElements.push(current);
+                }
+            }
+            return returnElements;
+        };
+    }
+    else if (document.evaluate) {
+        getElementsByClassName = function (className, tag, elm) {
+            tag = tag || "*";
+            elm = elm || document;
+            var classes = className.split(" "),
+                classesToCheck = "",
+                xhtmlNamespace = "http://www.w3.org/1999/xhtml",
+                namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
+                returnElements = [],
+                elements,
+                node;
+            for(var j=0, jl=classes.length; j<jl; j+=1){
+                classesToCheck += "[contains(concat(' ', @class, ' '), ' " + classes[j] + " ')]";
+            }
+            try {
+                elements = document.evaluate(".//" + tag + classesToCheck, elm, namespaceResolver, 0, null);
+            }
+            catch (e) {
+                elements = document.evaluate(".//" + tag + classesToCheck, elm, null, 0, null);
+            }
+            while ((node = elements.iterateNext())) {
+                returnElements.push(node);
+            }
+            return returnElements;
+        };
+    }
+    else {
+        getElementsByClassName = function (className, tag, elm) {
+            tag = tag || "*";
+            elm = elm || document;
+            var classes = className.split(" "),
+                classesToCheck = [],
+                elements = (tag === "*" && elm.all)? elm.all : elm.getElementsByTagName(tag),
+                current,
+                returnElements = [],
+                match;
+            for(var k=0, kl=classes.length; k<kl; k+=1){
+                classesToCheck.push(new RegExp("(^|\\s)" + classes[k] + "(\\s|$)"));
+            }
+            for(var l=0, ll=elements.length; l<ll; l+=1){
+                current = elements[l];
+                match = false;
+                for(var m=0, ml=classesToCheck.length; m<ml; m+=1){
+                    match = classesToCheck[m].test(current.className);
+                    if (!match) {
+                        break;
+                    }
+                }
+                if (match) {
+                    returnElements.push(current);
+                }
+            }
+            return returnElements;
+        };
+    }
+    return getElementsByClassName(className, tag, elm);
+};
+