tw-public@0: # -*- coding: iso-8859-1 -*- tw-public@0: """ tw-public@0: MoinMoin - Wiki Utility Functions tw-public@0: tw-public@0: @copyright: 2000 - 2004 by Jürgen Hermann tw-public@0: @license: GNU GPL, see COPYING for details. tw-public@0: """ tw-public@0: tw@102: import os, re, difflib, urllib, cgi tw-public@0: tw-public@0: from MoinMoin import util, version, config tw-public@0: from MoinMoin.util import pysupport tw-public@0: tw-public@0: # Exceptions tw-public@0: class InvalidFileNameError(Exception): tw-public@0: """ Called when we find an invalid file name """ tw-public@0: pass tw-public@0: tw-public@0: # constants for page names tw-public@0: PARENT_PREFIX = "../" tw-public@0: PARENT_PREFIX_LEN = len(PARENT_PREFIX) tw-public@0: CHILD_PREFIX = "/" tw-public@0: CHILD_PREFIX_LEN = len(CHILD_PREFIX) tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### Getting data from user/Sending data to user tw-public@0: ############################################################################# tw-public@0: tw-public@0: def decodeWindowsPath(text): tw-public@0: """ Decode Windows path names correctly. This is needed because many CGI tw-public@0: servers follow the RFC recommendation and re-encode the path_info variable tw-public@0: according to the file system semantics. tw-public@0: tw-public@0: @param text: the text to decode, string tw-public@0: @rtype: unicode tw-public@0: @return: decoded text tw-public@0: """ tw-public@0: tw-public@0: import locale tw-public@0: import codecs tw-public@0: cur_charset = locale.getdefaultlocale()[1] tw-public@0: try: tw-public@0: return unicode(text, 'utf-8') tw-public@0: except UnicodeError: tw-public@0: try: tw-public@0: return unicode(text, cur_charset, 'replace') tw-public@0: except LookupError: tw-public@0: return unicode(text, 'iso-8859-1', 'replace') tw-public@0: tw-public@0: def decodeUnknownInput(text): tw-public@0: """ Decode unknown input, like text attachments tw-public@0: tw-public@0: First we try utf-8 because it has special format, and it will decode tw-public@0: only utf-8 files. Then we try config.charset, then iso-8859-1 using tw-public@0: 'replace'. We will never raise an exception, but may return junk tw-public@0: data. tw-public@0: tw-public@0: WARNING: Use this function only for data that you view, not for data tw-public@0: that you save in the wiki. tw-public@0: tw-public@0: @param text: the text to decode, string tw-public@0: @rtype: unicode tw-public@0: @return: decoded text (maybe wrong) tw-public@0: """ tw-public@0: # Shortcut for unicode input tw-public@0: if isinstance(text, unicode): tw-public@0: return text tw-public@0: tw-public@0: try: tw-public@0: return unicode(text, 'utf-8') tw-public@0: except UnicodeError: tw-public@0: if config.charset not in ['utf-8', 'iso-8859-1']: tw-public@0: try: tw-public@0: return unicode(text, config.charset) tw-public@0: except UnicodeError: tw-public@0: pass tw-public@0: return unicode(text, 'iso-8859-1', 'replace') tw-public@0: tw-public@0: tw-public@0: def decodeUserInput(s, charsets=[config.charset]): tw-public@0: """ tw-public@0: Decodes input from the user. tw-public@0: tw-public@0: @param s: the string to unquote tw-public@0: @param charset: the charset to assume the string is in tw-public@0: @rtype: unicode tw-public@0: @return: the unquoted string as unicode tw-public@0: """ tw-public@0: for charset in charsets: tw-public@0: try: tw-public@0: return s.decode(charset) tw-public@0: except UnicodeError: tw-public@0: pass tw-public@0: raise UnicodeError('The string %r cannot be decoded.' % s) tw-public@0: tw-public@0: tw@101: # this is a thin wrapper around urllib (urllib only handles str, not unicode) tw@101: # with py <= 2.4.1, it would give incorrect results with unicode tw@101: # with py == 2.4.2, it crashes with unicode, if it contains non-ASCII chars tw@101: def url_quote(s, safe='/', want_unicode=False): tw@101: """ tw@101: Wrapper around urllib.quote doing the encoding/decoding as usually wanted: tw@101: tw@101: @param s: the string to quote (can be str or unicode, if it is unicode, tw@101: config.charset is used to encode it before calling urllib) tw@101: @param safe: just passed through to urllib tw@101: @param want_unicode: for the less usual case that you want to get back tw@101: unicode and not str, set this to True tw@101: Default is False. tw@101: """ tw@132: if isinstance(s, unicode): tw@101: s = s.encode(config.charset) tw@132: elif not isinstance(s, str): tw@132: s = str(s) tw@101: s = urllib.quote(s, safe) tw@101: if want_unicode: tw@101: s = s.decode(config.charset) # ascii would also work tw@101: return s tw@101: tw@101: def url_quote_plus(s, safe='/', want_unicode=False): tw@101: """ tw@101: Wrapper around urllib.quote_plus doing the encoding/decoding as usually wanted: tw@101: tw@101: @param s: the string to quote (can be str or unicode, if it is unicode, tw@101: config.charset is used to encode it before calling urllib) tw@101: @param safe: just passed through to urllib tw@101: @param want_unicode: for the less usual case that you want to get back tw@101: unicode and not str, set this to True tw@101: Default is False. tw@101: """ tw@132: if isinstance(s, unicode): tw@101: s = s.encode(config.charset) tw@132: elif not isinstance(s, str): tw@132: s = str(s) tw@101: s = urllib.quote_plus(s, safe) tw@101: if want_unicode: tw@101: s = s.decode(config.charset) # ascii would also work tw@101: return s tw@101: tw@101: def url_unquote(s, want_unicode=True): tw@101: """ tw@101: Wrapper around urllib.unquote doing the encoding/decoding as usually wanted: tw@101: tw@101: @param s: the string to unquote (can be str or unicode, if it is unicode, tw@101: config.charset is used to encode it before calling urllib) tw@101: @param want_unicode: for the less usual case that you want to get back tw@101: str and not unicode, set this to False. tw@101: Default is True. tw@101: """ tw@132: if isinstance(s, unicode): tw@101: s = s.encode(config.charset) # ascii would also work tw@101: s = urllib.unquote(s) tw@101: if want_unicode: tw@101: s = s.decode(config.charset) tw@101: return s tw@101: tw@175: def parseQueryString(qstr, want_unicode=True): tw@102: """ Parse a querystring "key=value&..." into a dict. tw@102: """ tw@102: is_unicode = isinstance(qstr, unicode) tw@102: if is_unicode: tw@102: qstr = qstr.encode(config.charset) tw@102: values = {} tw@102: for key, value in cgi.parse_qs(qstr).items(): tw@102: if len(value) < 2: tw@102: v = ''.join(value) tw@102: if want_unicode: tw@175: try: tw@175: v = unicode(v, config.charset) tw@175: except UnicodeDecodeError: tw@175: v = unicode(v, 'iso-8859-1', 'replace') tw@102: values[key] = v tw@102: return values tw@102: tw@102: def makeQueryString(qstr=None, want_unicode=False, **kw): tw@102: """ Make a querystring from arguments. tw@102: tw@102: kw arguments overide values in qstr. tw@102: tw@102: If a string is passed in, it's returned verbatim and tw@102: keyword parameters are ignored. tw@102: tw@102: @param qstr: dict to format as query string, using either ascii or unicode tw@132: @param kw: same as dict when using keywords, using ascii or unicode tw@102: @rtype: string tw@102: @return: query string ready to use in a url tw@102: """ tw@102: if qstr is None: tw@102: qstr = {} tw@102: if isinstance(qstr, type({})): tw@102: qstr.update(kw) tw@102: items = ['%s=%s' % (url_quote_plus(key, want_unicode=want_unicode), url_quote_plus(value, want_unicode=want_unicode)) for key, value in qstr.items()] tw@102: qstr = '&'.join(items) tw@102: return qstr tw@102: tw@101: tw-public@0: # FIXME: better name would be quoteURL, as this is useful for any tw-public@0: # string, not only wiki names. tw-public@0: def quoteWikinameURL(pagename, charset=config.charset): tw-public@0: """ Return a url encoding of filename in plain ascii tw-public@0: tw-public@0: Use urllib.quote to quote any character that is not always safe. tw-public@0: tw-public@0: @param pagename: the original pagename (unicode) tw-public@0: @charset: url text encoding, 'utf-8' recommended. Other charsert tw-public@0: might not be able to encode the page name and raise tw-public@0: UnicodeError. (default config.charset ('utf-8')). tw-public@0: @rtype: string tw-public@0: @return: the quoted filename, all unsafe characters encoded tw-public@0: """ tw-public@0: pagename = pagename.replace(u' ', u'_') tw-public@0: pagename = pagename.encode(charset) tw-public@0: return urllib.quote(pagename) tw-public@0: tw-public@0: tw-public@0: def escape(s, quote=0): tw-public@0: """ Escape possible html tags tw-public@0: tw-public@0: Replace special characters '&', '<' and '>' by SGML entities. tw-public@0: (taken from cgi.escape so we don't have to include that, even if we tw-public@0: don't use cgi at all) tw-public@0: tw-public@0: FIXME: should return string or unicode? tw-public@0: tw-public@0: @param s: (unicode) string to escape tw-public@0: @param quote: bool, should transform '\"' to '"' tw-public@0: @rtype: (unicode) string tw-public@0: @return: escaped version of s tw-public@0: """ tw-public@0: if not isinstance(s, (str, unicode)): tw-public@0: s = str(s) tw-public@0: tw-public@0: # Must first replace & tw-public@0: s = s.replace("&", "&") tw-public@0: tw-public@0: # Then other... tw-public@0: s = s.replace("<", "<") tw-public@0: s = s.replace(">", ">") tw-public@0: if quote: tw-public@0: s = s.replace('"', """) tw-public@0: return s tw-public@0: tw@156: def make_breakable(text, maxlen): tw@156: """ make a text breakable by inserting spaces into nonbreakable parts tw@156: """ tw@156: text = text.split(" ") tw@156: newtext = [] tw@156: for part in text: tw@156: if len(part) > maxlen: tw@156: while part: tw@156: newtext.append(part[:maxlen]) tw@156: part = part[maxlen:] tw@156: else: tw@156: newtext.append(part) tw@156: return " ".join(newtext) tw-public@0: tw-public@0: ######################################################################## tw-public@0: ### Storage tw-public@0: ######################################################################## tw-public@0: tw-public@0: # FIXME: These functions might be moved to storage module, when we have tw-public@0: # one. Then they will be called transparently whenever a page is saved. tw-public@0: tw-public@0: # Precompiled patterns for file name [un]quoting tw-public@0: UNSAFE = re.compile(r'[^a-zA-Z0-9_]+') tw-public@0: QUOTED = re.compile(r'\(([a-fA-F0-9]+)\)') tw-public@0: tw-public@0: tw-public@0: # FIXME: better name would be quoteWikiname tw-public@0: def quoteWikinameFS(wikiname, charset=config.charset): tw-public@0: """ Return file system representation of a Unicode WikiName. tw-public@0: tw-public@0: Warning: will raise UnicodeError if wikiname can not be encoded using tw-public@0: charset. The default value of config.charset, 'utf-8' can encode any tw-public@0: character. tw-public@0: tw-public@0: @param wikiname: Unicode string possibly containing non-ascii characters tw-public@0: @param charset: charset to encode string tw-public@0: @rtype: string tw-public@0: @return: quoted name, safe for any file system tw-public@0: """ tw-public@0: wikiname = wikiname.replace(u' ', u'_') # " " -> "_" tw-public@0: filename = wikiname.encode(charset) tw-public@0: tw-public@0: quoted = [] tw-public@0: location = 0 tw-public@0: for needle in UNSAFE.finditer(filename): tw-public@0: # append leading safe stuff tw-public@0: quoted.append(filename[location:needle.start()]) tw-public@0: location = needle.end() tw-public@0: # Quote and append unsafe stuff tw-public@0: quoted.append('(') tw-public@0: for character in needle.group(): tw-public@0: quoted.append('%02x' % ord(character)) tw-public@0: quoted.append(')') tw-public@0: tw-public@0: # append rest of string tw-public@0: quoted.append(filename[location:]) tw-public@0: return ''.join(quoted) tw-public@0: tw-public@0: tw-public@0: # FIXME: better name would be unquoteFilename tw-public@0: def unquoteWikiname(filename, charsets=[config.charset]): tw-public@0: """ Return Unicode WikiName from quoted file name. tw-public@0: tw-public@0: We raise an InvalidFileNameError if we find an invalid name, so the tw-public@0: wiki could alarm the admin or suggest the user to rename a page. tw-public@0: Invalid file names should never happen in normal use, but are rather tw-public@0: cheap to find. tw-public@0: tw-public@0: This function should be used only to unquote file names, not page tw-public@0: names we receive from the user. These are handled in request by tw-public@0: urllib.unquote, decodePagename and normalizePagename. tw-public@0: tw-public@0: Todo: search clients of unquoteWikiname and check for exceptions. tw-public@0: tw-public@0: @param filename: string using charset and possibly quoted parts tw-public@0: @param charset: charset used by string tw-public@0: @rtype: Unicode String tw-public@0: @return: WikiName tw-public@0: """ tw-public@0: ### Temporary fix start ### tw-public@0: # From some places we get called with Unicode strings tw-public@0: if isinstance(filename, type(u'')): tw-public@0: filename = filename.encode(config.charset) tw-public@0: ### Temporary fix end ### tw-public@0: tw-public@0: parts = [] tw-public@0: start = 0 tw-public@0: for needle in QUOTED.finditer(filename): tw-public@0: # append leading unquoted stuff tw-public@0: parts.append(filename[start:needle.start()]) tw-public@0: start = needle.end() tw-public@0: # Append quoted stuff tw-public@0: group = needle.group(1) tw-public@0: # Filter invalid filenames tw-public@0: if (len(group) % 2 != 0): tw-public@0: raise InvalidFileNameError(filename) tw-public@0: try: tw-public@0: for i in range(0, len(group), 2): tw-public@0: byte = group[i:i+2] tw-public@0: character = chr(int(byte, 16)) tw-public@0: parts.append(character) tw-public@0: except ValueError: tw-public@0: # byte not in hex, e.g 'xy' tw-public@0: raise InvalidFileNameError(filename) tw-public@0: tw-public@0: # append rest of string tw-public@0: if start == 0: tw-public@0: wikiname = filename tw-public@0: else: tw-public@0: parts.append(filename[start:len(filename)]) tw-public@0: wikiname = ''.join(parts) tw-public@0: tw-public@0: # This looks wrong, because at this stage "()" can be both errors tw-public@0: # like open "(" without close ")", or unquoted valid characters in tw-public@0: # the file name. FIXME: check this. tw-public@0: # Filter invalid filenames. Any left (xx) must be invalid tw-public@0: #if '(' in wikiname or ')' in wikiname: tw-public@0: # raise InvalidFileNameError(filename) tw-public@0: tw-public@0: wikiname = decodeUserInput(wikiname, charsets) tw-public@0: wikiname = wikiname.replace(u'_', u' ') # "_" -> " " tw-public@0: return wikiname tw-public@0: tw-public@0: # time scaling tw-public@0: def timestamp2version(ts): tw-public@0: """ Convert UNIX timestamp (may be float or int) to our version tw-public@0: (long) int. tw-public@0: We don't want to use floats, so we just scale by 1e6 to get tw-public@0: an integer in usecs. tw-public@0: """ tw-public@0: return long(ts*1000000L) # has to be long for py 2.2.x tw-public@0: tw-public@0: def version2timestamp(v): tw-public@0: """ Convert version number to UNIX timestamp (float). tw-public@0: This must ONLY be used for display purposes. tw-public@0: """ tw-public@0: return v/1000000.0 tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### InterWiki tw-public@0: ############################################################################# tw-public@0: tw-public@0: def split_wiki(wikiurl): tw-public@0: """ tw-public@0: Split a wiki url. tw-public@0: tw-public@0: @param wikiurl: the url to split tw-public@0: @rtype: tuple tw-public@0: @return: (tag, tail) tw-public@0: """ tw-public@0: # !!! use a regex here! tw-public@0: try: tw-public@0: wikitag, tail = wikiurl.split(":", 1) tw-public@0: except ValueError: tw-public@0: try: tw-public@0: wikitag, tail = wikiurl.split("/", 1) tw-public@0: except ValueError: tw-public@0: wikitag = None tw-public@0: tail = None tw-public@0: tw-public@0: return (wikitag, tail) tw-public@0: tw-public@0: tw-public@0: def join_wiki(wikiurl, wikitail): tw-public@0: """ tw-public@0: Add a page name to an interwiki url. tw-public@0: tw-public@0: @param wikiurl: wiki url, maybe including a $PAGE placeholder tw-public@0: @param wikitail: page name tw-public@0: @rtype: string tw-public@0: @return: generated URL of the page in the other wiki tw-public@0: """ tw-public@0: if wikiurl.find('$PAGE') == -1: tw-public@0: return wikiurl + wikitail tw-public@0: else: tw-public@0: return wikiurl.replace('$PAGE', wikitail) tw-public@0: tw-public@0: tw-public@0: def resolve_wiki(request, wikiurl): tw-public@0: """ tw-public@0: Resolve an interwiki link. tw-public@0: tw-public@0: @param request: the request object tw-public@0: @param wikiurl: the InterWiki:PageName link tw-public@0: @rtype: tuple tw-public@0: @return: (wikitag, wikiurl, wikitail, err) tw-public@0: """ tw-public@0: # load map (once, and only on demand) tw-public@0: try: tw-public@0: _interwiki_list = request.cfg._interwiki_list tw-public@0: except AttributeError: tw-public@0: _interwiki_list = {} tw-public@0: lines = [] tw-public@0: tw-public@0: # order is important here, the local intermap file takes tw-public@0: # precedence over the shared one, and is thus read AFTER tw-public@0: # the shared one tw-public@0: intermap_files = request.cfg.shared_intermap tw-public@0: if not isinstance(intermap_files, type([])): tw-public@0: intermap_files = [intermap_files] tw-public@0: intermap_files.append(os.path.join(request.cfg.data_dir, "intermap.txt")) tw-public@0: tw-public@0: for filename in intermap_files: tw-public@0: if filename and os.path.isfile(filename): tw-public@0: f = open(filename, "r") tw-public@0: lines.extend(f.readlines()) tw-public@0: f.close() tw-public@0: tw-public@0: for line in lines: tw-public@0: if not line or line[0] == '#': continue tw-public@0: try: tw-public@0: line = "%s %s/InterWiki" % (line, request.getScriptname()) tw-public@0: wikitag, urlprefix, trash = line.split(None, 2) tw-public@0: except ValueError: tw-public@0: pass tw-public@0: else: tw-public@0: _interwiki_list[wikitag] = urlprefix tw-public@0: tw-public@0: del lines tw-public@0: tw-public@0: # add own wiki as "Self" and by its configured name tw-public@0: _interwiki_list['Self'] = request.getScriptname() + '/' tw-public@0: if request.cfg.interwikiname: tw-public@0: _interwiki_list[request.cfg.interwikiname] = request.getScriptname() + '/' tw-public@0: tw-public@0: # save for later tw-public@0: request.cfg._interwiki_list = _interwiki_list tw-public@0: tw-public@0: # split wiki url tw-public@0: wikitag, tail = split_wiki(wikiurl) tw-public@0: tw-public@0: # return resolved url tw-public@0: if wikitag and _interwiki_list.has_key(wikitag): tw-public@0: return (wikitag, _interwiki_list[wikitag], tail, False) tw-public@0: else: tw-public@0: return (wikitag, request.getScriptname(), "/InterWiki", True) tw-public@0: tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### Page types (based on page names) tw-public@0: ############################################################################# tw-public@0: tw-public@0: def isSystemPage(request, pagename): tw-public@0: """ Is this a system page? Uses AllSystemPagesGroup internally. tw-public@0: tw-public@0: @param request: the request object tw-public@0: @param pagename: the page name tw-public@0: @rtype: bool tw-public@0: @return: true if page is a system page tw-public@0: """ tw-public@0: return (request.dicts.has_member('SystemPagesGroup', pagename) or tw-public@0: isTemplatePage(request, pagename) or tw-public@0: isFormPage(request, pagename)) tw-public@0: tw-public@0: tw-public@0: def isTemplatePage(request, pagename): tw-public@0: """ Is this a template page? tw-public@0: tw-public@0: @param pagename: the page name tw-public@0: @rtype: bool tw-public@0: @return: true if page is a template page tw-public@0: """ tw-public@0: filter = re.compile(request.cfg.page_template_regex, re.UNICODE) tw-public@0: return filter.search(pagename) is not None tw-public@0: tw-public@0: tw-public@0: def isFormPage(request, pagename): tw-public@0: """ Is this a form page? tw-public@0: tw-public@0: @param pagename: the page name tw-public@0: @rtype: bool tw-public@0: @return: true if page is a form page tw-public@0: """ tw-public@0: filter = re.compile(request.cfg.page_form_regex, re.UNICODE) tw-public@0: return filter.search(pagename) is not None tw-public@0: tw-public@0: def isGroupPage(request, pagename): tw-public@0: """ Is this a name of group page? tw-public@0: tw-public@0: @param pagename: the page name tw-public@0: @rtype: bool tw-public@0: @return: true if page is a form page tw-public@0: """ tw-public@0: filter = re.compile(request.cfg.page_group_regex, re.UNICODE) tw-public@0: return filter.search(pagename) is not None tw-public@0: tw-public@0: tw-public@0: def filterCategoryPages(request, pagelist): tw-public@0: """ Return category pages in pagelist tw-public@0: tw-public@0: WARNING: DO NOT USE THIS TO FILTER THE FULL PAGE LIST! Use tw-public@0: getPageList with a filter function. tw-public@0: tw-public@0: If you pass a list with a single pagename, either that is returned tw-public@0: or an empty list, thus you can use this function like a `isCategoryPage` tw-public@0: one. tw-public@0: tw-public@0: @param pagelist: a list of pages tw-public@0: @rtype: list tw-public@0: @return: only the category pages of pagelist tw-public@0: """ tw-public@0: func = re.compile(request.cfg.page_category_regex, re.UNICODE).search tw-public@0: return filter(func, pagelist) tw-public@0: tw-public@0: tw-public@0: # TODO: we may rename this to getLocalizedPage because it returns page tw-public@0: # that have translations. tw-public@0: def getSysPage(request, pagename): tw-public@0: """ Get a system page according to user settings and available translations. tw-public@0: tw-public@0: We include some special treatment for the case that is the tw-public@0: currently rendered page, as this is the case for some pages used very tw-public@0: often, like FrontPage, RecentChanges etc. - in that case we reuse the tw-public@0: already existing page object instead creating a new one. tw-public@0: tw-public@0: @param request: the request object tw-public@0: @param pagename: the name of the page tw-public@0: @rtype: Page object tw-public@0: @return: the page object of that system page, using a translated page, tw-public@0: if it exists tw-public@0: """ tw-public@0: from MoinMoin.Page import Page tw-public@0: i18n_name = request.getText(pagename, formatted=False) tw-public@0: pageobj = None tw-public@0: if i18n_name != pagename: tw-public@0: if request.page and i18n_name == request.page.page_name: tw-public@0: # do not create new object for current page tw-public@0: i18n_page = request.page tw-public@0: if i18n_page.exists(): tw-public@0: pageobj = i18n_page tw-public@0: else: tw-public@0: i18n_page = Page(request, i18n_name) tw-public@0: if i18n_page.exists(): tw-public@0: pageobj = i18n_page tw-public@0: tw-public@0: # if we failed getting a translated version of , tw-public@0: # we fall back to english tw-public@0: if not pageobj: tw-public@0: if request.page and pagename == request.page.page_name: tw-public@0: # do not create new object for current page tw-public@0: pageobj = request.page tw-public@0: else: tw-public@0: pageobj = Page(request, pagename) tw-public@0: return pageobj tw-public@0: tw-public@0: tw-public@0: def getFrontPage(request): tw-public@0: """ Convenience function to get localized front page tw-public@0: tw-public@0: @param request: current request tw-public@0: @rtype: Page object tw@35: @return localized page_front_page, if there is a translation tw-public@0: """ tw-public@0: return getSysPage(request, request.cfg.page_front_page) tw-public@0: tw-public@0: tw-public@0: def getHomePage(request, username=None): tw-public@0: """ tw-public@0: Get a user's homepage, or return None for anon users and tw-public@0: those who have not created a homepage. tw-public@0: tw-public@0: DEPRECATED - try to use getInterwikiHomePage (see below) tw-public@0: tw-public@0: @param request: the request object tw-public@0: @param username: the user's name tw-public@0: @rtype: Page tw-public@0: @return: user's homepage object - or None tw-public@0: """ tw-public@0: from MoinMoin.Page import Page tw-public@0: # default to current user tw-public@0: if username is None and request.user.valid: tw-public@0: username = request.user.name tw-public@0: tw-public@0: # known user? tw-public@0: if username: tw-public@0: # Return home page tw-public@0: page = Page(request, username) tw-public@0: if page.exists(): tw-public@0: return page tw-public@0: tw-public@0: return None tw-public@0: tw-public@0: tw-public@0: def getInterwikiHomePage(request, username=None): tw-public@0: """ tw-public@0: Get a user's homepage. tw-public@0: tw-public@0: cfg.user_homewiki influences behaviour of this: tw-public@0: 'Self' does mean we store user homepage in THIS wiki. tw-public@0: When set to our own interwikiname, it behaves like with 'Self'. tw-public@0: tw-public@0: 'SomeOtherWiki' means we store user homepages in another wiki. tw-public@0: tw-public@0: @param request: the request object tw-public@0: @param username: the user's name tw-public@0: @rtype: tuple (or None for anon users) tw-public@0: @return: (wikiname, pagename) tw-public@0: """ tw-public@0: # default to current user tw-public@0: if username is None and request.user.valid: tw-public@0: username = request.user.name tw-public@0: if not username: tw-public@0: return None # anon user tw-public@0: tw-public@0: homewiki = request.cfg.user_homewiki tw-public@0: if homewiki == request.cfg.interwikiname: tw-public@0: homewiki = 'Self' tw-public@0: tw-public@0: return homewiki, username tw-public@0: tw-public@0: tw-public@0: def AbsPageName(request, context, pagename): tw-public@0: """ tw-public@0: Return the absolute pagename for a (possibly) relative pagename. tw-public@0: tw-public@0: @param context: name of the page where "pagename" appears on tw-public@0: @param pagename: the (possibly relative) page name tw-public@0: @rtype: string tw-public@0: @return: the absolute page name tw-public@0: """ tw-public@0: if pagename.startswith(PARENT_PREFIX): tw-public@0: pagename = '/'.join(filter(None, context.split('/')[:-1] + [pagename[PARENT_PREFIX_LEN:]])) tw-public@0: elif pagename.startswith(CHILD_PREFIX): tw-public@0: pagename = context + '/' + pagename[CHILD_PREFIX_LEN:] tw-public@0: return pagename tw-public@0: tw-public@0: def pagelinkmarkup(pagename): tw-public@0: """ return markup that can be used as link to page """ tw-public@0: from MoinMoin.parser.wiki import Parser tw-public@0: if re.match(Parser.word_rule + "$", pagename): tw-public@0: return pagename tw-public@0: else: tw-public@0: return u'["%s"]' % pagename tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### Plugins tw-public@0: ############################################################################# tw-public@0: nirs@53: class PluginError(Exception): nirs@53: """ Base class for plugin errors """ nirs@53: nirs@53: class PluginMissingError(PluginError): nirs@53: """ Raised when a plugin is not found """ nirs@53: nirs@53: class PluginAttributeError(PluginError): nirs@53: """ Raised when plugin does not contain an attribtue """ nirs@53: nirs@53: tw-public@0: def importPlugin(cfg, kind, name, function="execute"): tw-public@0: """ Import wiki or builtin plugin tw-public@0: nirs@53: Returns function from a plugin module name. If name can not be nirs@53: imported, raise PluginMissingError. If function is missing, raise nirs@53: PluginAttributeError. tw-public@0: tw-public@0: kind may be one of 'action', 'formatter', 'macro', 'processor', tw-public@0: 'parser' or any other directory that exist in MoinMoin or tw-public@0: data/plugin tw-public@0: tw-public@0: Wiki plugins will always override builtin plugins. If you want nirs@53: specific plugin, use either importWikiPlugin or importBuiltinPlugin nirs@53: directly. tw-public@0: tw-public@0: @param cfg: wiki config instance tw-public@0: @param kind: what kind of module we want to import tw-public@0: @param name: the name of the module tw-public@0: @param function: the function name nirs@53: @rtype: any object tw-public@0: @return: "function" of module "name" of kind "kind", or None tw-public@0: """ nirs@51: try: nirs@53: return importWikiPlugin(cfg, kind, name, function) nirs@53: except PluginMissingError: nirs@53: return importBuiltinPlugin(kind, name, function) nirs@53: tw-public@0: tw-public@0: def importWikiPlugin(cfg, kind, name, function): nirs@53: """ Import plugin from the wiki data directory tw-public@0: nirs@53: See importPlugin docstring. nirs@53: """ nirs@53: if not name in wikiPlugins(kind, cfg): nirs@53: raise PluginMissingError nirs@53: moduleName = '%s.plugin.%s.%s' % (cfg.siteid, kind, name) nirs@53: return importNameFromPlugin(moduleName, function) nirs@51: tw-public@0: nirs@53: def importBuiltinPlugin(kind, name, function): nirs@53: """ Import builtin plugin from MoinMoin package nirs@53: nirs@53: See importPlugin docstring. tw-public@0: """ nirs@53: if not name in builtinPlugins(kind): nirs@53: raise PluginMissingError nirs@53: moduleName = 'MoinMoin.%s.%s' % (kind, name) nirs@53: return importNameFromPlugin(moduleName, function) nirs@53: nirs@53: nirs@53: def importNameFromPlugin(moduleName, name): nirs@53: """ Return name from plugin module nirs@53: nirs@53: Raise PluginAttributeError if name does not exists. nirs@53: """ nirs@53: module = __import__(moduleName, globals(), {}, [name]) nirs@51: try: nirs@53: return getattr(module, name) nirs@51: except AttributeError: nirs@53: raise PluginAttributeError tw-public@0: nirs@51: tw-public@0: def builtinPlugins(kind): tw-public@0: """ Gets a list of modules in MoinMoin.'kind' tw-public@0: tw-public@0: @param kind: what kind of modules we look for tw-public@0: @rtype: list tw-public@0: @return: module names tw-public@0: """ tw-public@0: modulename = "MoinMoin." + kind nirs@51: return pysupport.importName(modulename, "modules") tw-public@0: tw-public@0: tw-public@0: def wikiPlugins(kind, cfg): tw-public@0: """ Gets a list of modules in data/plugin/'kind' nirs@51: nirs@51: Require valid plugin directory. e.g missing 'parser' directory or nirs@51: missing '__init__.py' file will raise errors. tw-public@0: tw-public@0: @param kind: what kind of modules we look for tw-public@0: @rtype: list tw-public@0: @return: module names tw-public@0: """ tw-public@0: # Wiki plugins are located in wikiconfig.plugin module tw-public@0: modulename = '%s.plugin.%s' % (cfg.siteid, kind) nirs@51: return pysupport.importName(modulename, "modules") tw-public@0: tw-public@0: tw-public@0: def getPlugins(kind, cfg): tw-public@0: """ Gets a list of plugin names of kind tw-public@0: tw-public@0: @param kind: what kind of modules we look for tw-public@0: @rtype: list tw-public@0: @return: module names tw-public@0: """ tw-public@0: # Copy names from builtin plugins - so we dont destroy the value tw-public@0: all_plugins = builtinPlugins(kind)[:] tw-public@0: tw-public@0: # Add extension plugins without duplicates tw-public@0: for plugin in wikiPlugins(kind, cfg): tw-public@0: if plugin not in all_plugins: tw-public@0: all_plugins.append(plugin) tw-public@0: tw-public@0: return all_plugins tw-public@0: tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### Parsers tw-public@0: ############################################################################# tw-public@0: tw-public@0: def getParserForExtension(cfg, extension): tw-public@0: """ tw-public@0: Returns the Parser class of the parser fit to handle a file tw-public@0: with the given extension. The extension should be in the same tw-public@0: format as os.path.splitext returns it (i.e. with the dot). tw-public@0: Returns None if no parser willing to handle is found. tw-public@0: The dict of extensions is cached in the config object. tw-public@0: tw-public@0: @param cfg: the Config instance for the wiki in question tw-public@0: @param extension: the filename extension including the dot tw-public@0: @rtype: class, None tw-public@0: @returns: the parser class or None tw-public@0: """ tw-public@0: if not hasattr(cfg, '_EXT_TO_PARSER'): tw-public@0: import types tw-public@0: etp, etd = {}, None tw-public@0: for pname in getPlugins('parser', cfg): nirs@51: try: nirs@51: Parser = importPlugin(cfg, 'parser', pname, 'Parser') tw@104: except PluginMissingError: nirs@51: continue nirs@51: if hasattr(Parser, 'extensions'): nirs@51: exts = Parser.extensions nirs@51: if type(exts) == types.ListType: nirs@51: for ext in Parser.extensions: nirs@51: etp[ext] = Parser nirs@51: elif str(exts) == '*': nirs@51: etd = Parser tw-public@0: cfg._EXT_TO_PARSER = etp tw-public@0: cfg._EXT_TO_PARSER_DEFAULT = etd tw-public@0: tw-public@0: return cfg._EXT_TO_PARSER.get(extension, cfg._EXT_TO_PARSER_DEFAULT) tw-public@0: tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### Misc tw-public@0: ############################################################################# tw-public@0: tw-public@0: def parseAttributes(request, attrstring, endtoken=None, extension=None): tw-public@0: """ tw-public@0: Parse a list of attributes and return a dict plus a possible tw-public@0: error message. tw-public@0: If extension is passed, it has to be a callable that returns tw-public@0: None when it was not interested into the token, '' when all was OK tw-public@0: and it did eat the token, and any other string to return an error tw-public@0: message. tw-public@0: tw-public@0: @param request: the request object tw-public@0: @param attrstring: string containing the attributes to be parsed tw-public@0: @param endtoken: token terminating parsing tw-public@0: @param extension: extension function - tw-public@0: gets called with the current token, the parser and the dict tw-public@0: @rtype: dict, msg tw-public@0: @return: a dict plus a possible error message tw-public@0: """ tw-public@0: import shlex, StringIO tw-public@0: tw-public@0: _ = request.getText tw-public@0: tw-public@0: parser = shlex.shlex(StringIO.StringIO(attrstring)) tw-public@0: parser.commenters = '' tw-public@0: msg = None tw-public@0: attrs = {} tw-public@0: tw-public@0: while not msg: tw-public@0: try: tw-public@0: key = parser.get_token() tw-public@0: except ValueError, err: tw-public@0: msg = str(err) tw-public@0: break tw-public@0: if not key: break tw-public@0: if endtoken and key == endtoken: break tw-public@0: tw-public@0: # call extension function with the current token, the parser, and the dict tw-public@0: if extension: tw-public@0: msg = extension(key, parser, attrs) tw-public@0: if msg == '': continue tw-public@0: if msg: break tw-public@0: tw-public@0: try: tw-public@0: eq = parser.get_token() tw-public@0: except ValueError, err: tw-public@0: msg = str(err) tw-public@0: break tw-public@0: if eq != "=": tw-public@0: msg = _('Expected "=" to follow "%(token)s"') % {'token': key} tw-public@0: break tw-public@0: tw-public@0: try: tw-public@0: val = parser.get_token() tw-public@0: except ValueError, err: tw-public@0: msg = str(err) tw-public@0: break tw-public@0: if not val: tw-public@0: msg = _('Expected a value for key "%(token)s"') % {'token': key} tw-public@0: break tw-public@0: tw-public@0: key = escape(key) # make sure nobody cheats tw-public@0: tw-public@0: # safely escape and quote value tw-public@0: if val[0] in ["'", '"']: tw-public@0: val = escape(val) tw-public@0: else: tw-public@0: val = '"%s"' % escape(val, 1) tw-public@0: tw-public@0: attrs[key.lower()] = val tw-public@0: tw-public@0: return attrs, msg or '' tw-public@0: tw-public@0: tw-public@0: def taintfilename(basename): tw-public@0: """ tw-public@0: Make a filename that is supposed to be a plain name secure, i.e. tw-public@0: remove any possible path components that compromise our system. tw-public@0: tw-public@0: @param basename: (possibly unsafe) filename tw-public@0: @rtype: string tw-public@0: @return: (safer) filename tw-public@0: """ tw-public@0: for x in (os.pardir, ':', '/', '\\', '<', '>'): tw-public@0: basename = basename.replace(x, '_') tw-public@0: tw-public@0: return basename tw-public@0: tw-public@0: tw-public@0: def mapURL(request, url): tw-public@0: """ tw-public@0: Map URLs according to 'cfg.url_mappings'. tw-public@0: tw-public@0: @param url: a URL tw-public@0: @rtype: string tw-public@0: @return: mapped URL tw-public@0: """ tw-public@0: # check whether we have to map URLs tw-public@0: if request.cfg.url_mappings: tw-public@0: # check URL for the configured prefixes tw-public@0: for prefix in request.cfg.url_mappings.keys(): tw-public@0: if url.startswith(prefix): tw-public@0: # substitute prefix with replacement value tw-public@0: return request.cfg.url_mappings[prefix] + url[len(prefix):] tw-public@0: tw-public@0: # return unchanged url tw-public@0: return url tw-public@0: tw-public@0: tw-public@0: def getUnicodeIndexGroup(name): tw-public@0: """ tw-public@0: Return a group letter for `name`, which must be a unicode string. tw-public@0: Currently supported: Hangul Syllables (U+AC00 - U+D7AF) tw-public@0: tw-public@0: @param name: a string tw-public@0: @rtype: string tw-public@0: @return: group letter or None tw-public@0: """ tw-public@0: c = name[0] tw-public@0: if u'\uAC00' <= c <= u'\uD7AF': # Hangul Syllables tw-public@0: return unichr(0xac00 + (int(ord(c) - 0xac00) / 588) * 588) tw-public@0: else: tw@27: return c.upper() # we put lower and upper case words into the same index group tw-public@0: tw-public@0: tw-public@0: def isStrictWikiname(name, word_re=re.compile(ur"^(?:[%(u)s][%(l)s]+){2,}$" % {'u':config.chars_upper, 'l':config.chars_lower})): tw-public@0: """ tw-public@0: Check whether this is NOT an extended name. tw-public@0: tw-public@0: @param name: the wikiname in question tw-public@0: @rtype: bool tw-public@0: @return: true if name matches the word_re tw-public@0: """ tw-public@0: return word_re.match(name) tw-public@0: tw-public@0: tw-public@0: def isPicture(url): tw-public@0: """ tw-public@0: Is this a picture's url? tw-public@0: tw-public@0: @param url: the url in question tw-public@0: @rtype: bool tw-public@0: @return: true if url points to a picture tw-public@0: """ tw-public@0: extpos = url.rfind(".") tw@198: return extpos > 0 and url[extpos:].lower() in ['.gif', '.jpg', '.jpeg', '.png', '.bmp', '.ico', ] tw-public@0: tw-public@0: tw-public@0: def link_tag(request, params, text=None, formatter=None, on=None, **kw): tw-public@0: """ Create a link. tw-public@0: tw-public@0: @param request: the request object tw-public@0: @param params: parameter string appended to the URL after the scriptname/ tw-public@0: @param text: text / inner part of the ... link - does NOT get tw-public@0: escaped, so you can give HTML here and it will be used verbatim tw-public@0: @param formatter: the formatter object to use tw-public@0: @keyword on: opening/closing tag only tw-public@0: @keyword attrs: additional attrs (HTMLified string) tw-public@0: @rtype: string tw-public@0: @return: formatted link tag tw-public@0: """ tw-public@0: css_class = kw.get('css_class', None) tw-public@0: if text is None: tw-public@0: text = params # default tw-public@0: if formatter: tw-public@0: url = "%s/%s" % (request.getScriptname(), params) tw-public@0: if on != None: tw-public@0: return formatter.url(on, url, css_class, **kw) tw-public@0: return (formatter.url(1, url, css_class, **kw) + tw-public@0: formatter.rawHTML(text) + tw-public@0: formatter.url(0)) tw-public@0: if on != None and not on: tw-public@0: return '' alex@157: tw-public@0: attrs = '' tw-public@0: if kw.has_key('attrs'): tw-public@0: attrs += ' ' + kw['attrs'] tw-public@0: if css_class: tw-public@0: attrs += ' class="%s"' % css_class tw-public@0: result = '' % (attrs, request.getScriptname(), params) tw-public@0: if on: tw-public@0: return result tw-public@0: else: tw-public@0: return "%s%s" % (result, text) tw-public@0: tw-public@0: tw-public@0: def linediff(oldlines, newlines, **kw): tw-public@0: """ tw-public@0: Find changes between oldlines and newlines. tw-public@0: tw-public@0: @param oldlines: list of old text lines tw-public@0: @param newlines: list of new text lines tw-public@0: @keyword ignorews: if 1: ignore whitespace tw-public@0: @rtype: list tw-public@0: @return: lines like diff tool does output. tw-public@0: """ tw-public@0: false = lambda s: None tw-public@0: if kw.get('ignorews', 0): tw-public@0: d = difflib.Differ(false) tw-public@0: else: tw-public@0: d = difflib.Differ(false, false) tw-public@0: tw-public@0: lines = list(d.compare(oldlines,newlines)) tw-public@0: tw-public@0: # return empty list if there were no changes tw-public@0: changed = 0 tw-public@0: for l in lines: tw-public@0: if l[0] != ' ': tw-public@0: changed = 1 tw-public@0: break tw-public@0: if not changed: return [] tw-public@0: tw-public@0: if not "we want the unchanged lines, too": tw-public@0: if "no questionmark lines": tw-public@0: lines = filter(lambda line : line[0]!='?', lines) tw-public@0: return lines tw-public@0: tw-public@0: tw-public@0: # calculate the hunks and remove the unchanged lines between them tw-public@0: i = 0 # actual index in lines tw-public@0: count = 0 # number of unchanged lines tw-public@0: lcount_old = 0 # line count old file tw-public@0: lcount_new = 0 # line count new file tw-public@0: while i < len(lines): tw-public@0: marker = lines[i][0] tw-public@0: if marker == ' ': tw-public@0: count = count + 1 tw-public@0: i = i + 1 tw-public@0: lcount_old = lcount_old + 1 tw-public@0: lcount_new = lcount_new + 1 tw-public@0: elif marker in ['-', '+']: tw-public@0: if (count == i) and count > 3: tw-public@0: lines[:i-3] = [] tw-public@0: i = 4 tw-public@0: count = 0 tw-public@0: elif count > 6: tw-public@0: # remove lines and insert new hunk indicator tw-public@0: lines[i-count+3:i-3] = ['@@ -%i, +%i @@\n' % tw-public@0: (lcount_old, lcount_new)] tw-public@0: i = i - count + 8 tw-public@0: count = 0 tw-public@0: else: tw-public@0: count = 0 tw-public@0: i = i + 1 tw-public@0: if marker == '-': lcount_old = lcount_old + 1 tw-public@0: else: lcount_new = lcount_new + 1 tw-public@0: elif marker == '?': tw-public@0: lines[i:i+1] = [] tw-public@0: tw-public@0: # remove unchanged lines a the end tw-public@0: if count > 3: tw-public@0: lines[-count+3:] = [] tw-public@0: tw-public@0: return lines tw-public@0: tw-public@0: tw-public@0: def pagediff(request, pagename1, rev1, pagename2, rev2, **kw): tw-public@0: """ tw-public@0: Calculate the "diff" between two page contents. tw-public@0: tw-public@0: @param pagename1: name of first page tw-public@0: @param rev1: revision of first page tw-public@0: @param pagename2: name of second page tw-public@0: @param rev2: revision of second page tw-public@0: @keyword ignorews: if 1: ignore pure-whitespace changes. tw-public@0: @rtype: list tw-public@0: @return: lines of diff output tw-public@0: """ tw-public@0: from MoinMoin.Page import Page tw-public@0: lines1 = Page(request, pagename1, rev=rev1).getlines() tw-public@0: lines2 = Page(request, pagename2, rev=rev2).getlines() tw-public@0: tw-public@0: lines = linediff(lines1, lines2, **kw) tw-public@0: return lines tw-public@0: tw-public@0: tw-public@0: ############################################################################# tw-public@0: ### Page header / footer tw-public@0: ############################################################################# tw-public@0: tw-public@0: # FIXME - this is theme code, move to theme tw-public@0: # Could be simplified by using a template tw-public@0: tw-public@0: def send_title(request, text, **keywords): tw-public@0: """ tw-public@0: Output the page header (and title). tw-public@0: tw-public@0: TODO: check all code that call us and add page keyword for the tw-public@0: current page being rendered. tw-public@0: tw-public@0: @param request: the request object tw-public@0: @param text: the title text tw-public@0: @keyword link: URL for the title tw-public@0: @keyword msg: additional message (after saving) tw-public@0: @keyword pagename: 'PageName' tw-public@0: @keyword page: the page instance that called us. tw-public@0: @keyword print_mode: 1 (or 0) tw-public@0: @keyword editor_mode: 1 (or 0) tw-public@0: @keyword media: css media type, defaults to 'screen' tw-public@0: @keyword allow_doubleclick: 1 (or 0) tw-public@0: @keyword html_head: additional code tw-public@0: @keyword body_attr: additional attributes tw-public@0: @keyword body_onload: additional "onload" JavaScript code tw-public@0: """ tw-public@0: from MoinMoin.Page import Page tw-public@0: _ = request.getText tw-public@0: tw-public@0: if keywords.has_key('page'): tw-public@0: page = keywords['page'] tw-public@0: pagename = page.page_name tw-public@0: else: tw-public@0: pagename = keywords.get('pagename', '') tw-public@0: page = Page(request, pagename) tw-public@0: tw-public@0: scriptname = request.getScriptname() tw-public@0: pagename_quoted = quoteWikinameURL(pagename) tw-public@0: tw-public@0: # get name of system pages tw-public@0: page_front_page = getFrontPage(request).page_name tw-public@0: page_help_contents = getSysPage(request, 'HelpContents').page_name tw-public@0: page_title_index = getSysPage(request, 'TitleIndex').page_name tw-public@0: page_site_navigation = getSysPage(request, 'SiteNavigation').page_name tw-public@0: page_word_index = getSysPage(request, 'WordIndex').page_name tw-public@0: page_user_prefs = getSysPage(request, 'UserPreferences').page_name tw-public@0: page_help_formatting = getSysPage(request, 'HelpOnFormatting').page_name tw-public@0: page_find_page = getSysPage(request, 'FindPage').page_name tw-public@0: home_page = getInterwikiHomePage(request) # XXX sorry theme API change!!! Either None or tuple (wikiname,pagename) now. tw-public@0: page_parent_page = getattr(page.getParentPage(), 'page_name', None) tw-public@0: tw-public@0: # Prepare the HTML element tw-public@0: user_head = [request.cfg.html_head] tw-public@0: tw-public@0: # include charset information - needed for moin_dump or any other case tw-public@0: # when reading the html without a web server tw-public@0: user_head.append('''\n''' % config.charset) tw-public@0: tw-public@0: meta_keywords = request.getPragma('keywords') tw-public@0: meta_desc = request.getPragma('description') tw-public@0: if meta_keywords: tw-public@0: user_head.append('\n' % escape(meta_keywords, 1)) tw-public@0: if meta_desc: tw-public@0: user_head.append('\n' % escape(meta_desc, 1)) tw-public@0: tw-public@0: # search engine precautions / optimization: tw-public@0: # if it is an action or edit/search, send query headers (noindex,nofollow): tw-public@0: if request.query_string: tw-public@0: user_head.append(request.cfg.html_head_queries) tw-public@0: elif request.request_method == 'POST': tw-public@0: user_head.append(request.cfg.html_head_posts) tw-public@0: # if it is a special page, index it and follow the links - we do it tw-public@0: # for the original, English pages as well as for (the possibly tw-public@0: # modified) frontpage: tw-public@0: elif pagename in [page_front_page, request.cfg.page_front_page, tw-public@0: page_title_index, 'TitleIndex', tw-public@0: page_find_page, 'FindPage', tw-public@0: page_site_navigation, 'SiteNavigation', tw-public@0: 'RecentChanges',]: tw-public@0: user_head.append(request.cfg.html_head_index) tw-public@0: # if it is a normal page, index it, but do not follow the links, because tw-public@0: # there are a lot of illegal links (like actions) or duplicates: tw-public@0: else: tw-public@0: user_head.append(request.cfg.html_head_normal) tw-public@0: tw-public@0: if keywords.has_key('pi_refresh') and keywords['pi_refresh']: tw-public@0: user_head.append('' % keywords['pi_refresh']) tw-public@0: tw-public@0: # output buffering increases latency but increases throughput as well tw-public@0: output = [] tw-public@0: # later: tw-public@0: output.append(""" tw-public@0: tw-public@0: tw-public@0: %s tw-public@0: %s tw-public@0: %s tw-public@0: """ % ( tw-public@0: ''.join(user_head), tw-public@0: request.theme.html_head({ nirs@128: 'page': page, tw-public@0: 'title': escape(text), tw-public@0: 'sitename': escape(request.cfg.html_pagetitle or request.cfg.sitename), tw-public@0: 'print_mode': keywords.get('print_mode', False), tw-public@0: 'media': keywords.get('media', 'screen'), tw@194: }), tw@194: keywords.get('html_head', ''), tw-public@0: )) tw-public@0: tw-public@0: # Links tw-public@0: output.append('\n' % (scriptname, quoteWikinameURL(page_front_page))) tw-public@0: if pagename: tw-public@0: output.append('\n' % ( tw-public@0: _('Wiki Markup'), scriptname, pagename_quoted,)) tw-public@0: output.append('\n' % ( tw-public@0: _('Print View'), scriptname, pagename_quoted,)) tw-public@0: tw-public@0: # !!! currently disabled due to Mozilla link prefetching, see tw-public@0: # http://www.mozilla.org/projects/netlib/Link_Prefetching_FAQ.html tw-public@0: #~ all_pages = request.getPageList() tw-public@0: #~ if all_pages: tw-public@0: #~ try: tw-public@0: #~ pos = all_pages.index(pagename) tw-public@0: #~ except ValueError: tw-public@0: #~ # this shopuld never happend in theory, but let's be sure tw-public@0: #~ pass tw-public@0: #~ else: tw-public@0: #~ request.write('\n' % (request.getScriptname(), quoteWikinameURL(all_pages[0])) tw-public@0: #~ if pos > 0: tw-public@0: #~ request.write('\n' % (request.getScriptname(), quoteWikinameURL(all_pages[pos-1]))) tw-public@0: #~ if pos+1 < len(all_pages): tw-public@0: #~ request.write('\n' % (request.getScriptname(), quoteWikinameURL(all_pages[pos+1]))) tw-public@0: #~ request.write('\n' % (request.getScriptname(), quoteWikinameURL(all_pages[-1]))) tw-public@0: tw-public@0: if page_parent_page: tw-public@0: output.append('\n' % (scriptname, quoteWikinameURL(page_parent_page))) tw-public@0: tw-public@0: # write buffer because we call AttachFile tw-public@0: request.write(''.join(output)) tw-public@0: output = [] tw-public@0: tw-public@0: if pagename: tw-public@0: from MoinMoin.action import AttachFile tw-public@0: AttachFile.send_link_rel(request, pagename) tw-public@0: tw-public@0: output.extend([ tw-public@0: '\n' % (scriptname, quoteWikinameURL(page_find_page)), tw-public@0: '\n' % (scriptname, quoteWikinameURL(page_title_index)), tw-public@0: '\n' % (scriptname, quoteWikinameURL(page_word_index)), tw-public@0: '\n' % (scriptname, quoteWikinameURL(page_help_formatting)), tw-public@0: ]) tw@124: tw-public@0: output.append("\n") tw-public@0: request.write(''.join(output)) tw-public@0: output = [] tw-public@0: request.flush() tw-public@0: tw-public@0: # start the tw-public@0: bodyattr = [] tw-public@0: if keywords.has_key('body_attr'): tw-public@0: bodyattr.append(' ') tw-public@0: bodyattr.append(keywords['body_attr']) tw-public@0: tw-public@0: # Add doubleclick edit action tw-public@0: if (pagename and keywords.get('allow_doubleclick', 0) and tw-public@0: not keywords.get('print_mode', 0) and tw-public@0: request.user.edit_on_doubleclick): tw-public@0: if request.user.may.write(pagename): # separating this gains speed tw@102: querystr = escape(makeQueryString({'action': 'edit'})) tw@33: # TODO: remove escape=0 in 2.0 tw-public@0: url = page.url(request, querystr, escape=0) tw-public@0: bodyattr.append(''' ondblclick="location.href='%s'"''' % url) tw-public@0: tw-public@0: # Set body to the user interface language and direction tw-public@0: bodyattr.append(' %s' % request.theme.ui_lang_attr()) tw-public@0: tw-public@0: body_onload = keywords.get('body_onload', '') tw-public@0: if body_onload: tw-public@0: bodyattr.append(''' onload="%s"''' % body_onload) tw-public@0: output.append('\n\n' % ''.join(bodyattr)) tw-public@0: tw-public@0: # Output ----------------------------------------------------------- tw-public@0: tw-public@0: theme = request.theme tw-public@0: tw-public@0: # If in print mode, start page div and emit the title tw-public@0: if keywords.get('print_mode', 0): tw-public@0: d = {'title_text': text, 'title_link': None, 'page': page,} tw-public@0: request.themedict = d tw-public@0: output.append(theme.startPage()) tw@165: output.append(theme.interwiki(d)) tw-public@0: output.append(theme.title(d)) tw-public@0: tw-public@0: # In standard mode, emit theme.header tw-public@0: else: tw-public@0: # prepare dict for theme code: tw-public@0: d = { tw-public@0: 'theme': theme.name, tw-public@0: 'script_name': scriptname, tw-public@0: 'title_text': text, tw-public@0: 'title_link': keywords.get('link', ''), tw-public@0: 'logo_string': request.cfg.logo_string, tw-public@0: 'site_name': request.cfg.sitename, tw-public@0: 'page': page, tw-public@0: 'pagesize': pagename and page.size() or 0, tw-public@0: 'last_edit_info': pagename and page.lastEditInfo() or '', tw-public@0: 'page_name': pagename or '', tw-public@0: 'page_find_page': page_find_page, tw-public@0: 'page_front_page': page_front_page, tw-public@0: 'home_page': home_page, tw-public@0: 'page_help_contents': page_help_contents, tw-public@0: 'page_help_formatting': page_help_formatting, tw-public@0: 'page_parent_page': page_parent_page, tw-public@0: 'page_title_index': page_title_index, tw-public@0: 'page_word_index': page_word_index, tw-public@0: 'page_user_prefs': page_user_prefs, tw-public@0: 'user_name': request.user.name, tw-public@0: 'user_valid': request.user.valid, tw-public@0: 'user_prefs': (page_user_prefs, request.user.name)[request.user.valid], tw-public@0: 'msg': keywords.get('msg', ''), tw-public@0: 'trail': keywords.get('trail', None), tw-public@0: # Discontinued keys, keep for a while for 3rd party theme developers tw-public@0: 'titlesearch': 'use self.searchform(d)', tw-public@0: 'textsearch': 'use self.searchform(d)', tw-public@0: 'navibar': ['use self.navibar(d)'], tw-public@0: 'available_actions': ['use self.request.availableActions(page)'], tw-public@0: } tw-public@0: tw-public@0: # add quoted versions of pagenames tw-public@0: newdict = {} tw-public@0: for key in d: tw-public@0: if key.startswith('page_'): tw-public@0: if not d[key] is None: tw-public@0: newdict['q_'+key] = quoteWikinameURL(d[key]) tw-public@0: else: tw-public@0: newdict['q_'+key] = None tw-public@0: d.update(newdict) tw-public@0: request.themedict = d tw-public@0: tw-public@0: # now call the theming code to do the rendering tw-public@0: if keywords.get('editor_mode', 0): tw-public@0: output.append(theme.editorheader(d)) tw-public@0: else: tw-public@0: output.append(theme.header(d)) tw-public@0: tw-public@0: # emit it tw-public@0: request.write(''.join(output)) tw-public@0: output = [] tw-public@0: request.flush() tw-public@0: tw-public@0: tw-public@0: def send_footer(request, pagename, **keywords): tw-public@0: """ tw-public@0: Output the page footer. tw-public@0: tw-public@0: @param request: the request object tw-public@0: @param pagename: WikiName of the page tw-public@0: @keyword editable: true, when page is editable (default: true) tw-public@0: @keyword showpage: true, when link back to page is wanted (default: false) tw-public@0: @keyword print_mode: true, when page is displayed in Print mode tw-public@0: """ tw-public@0: d = request.themedict tw-public@0: theme = request.theme tw-public@0: tw-public@0: # Emit end of page in print mode, or complete footer in standard mode tw-public@0: if keywords.get('print_mode', 0): tw-public@0: request.write(theme.pageinfo(d['page'])) tw-public@0: request.write(theme.endPage()) tw-public@0: else: tw-public@0: # This is used only by classic now, kill soon tw-public@0: d['footer_fragments'] = request._footer_fragments tw-public@0: request.write(theme.footer(d, **keywords)) tw-public@0: tw-public@0: tw-public@0: ######################################################################## tw-public@0: ### Tickets - used by RenamePage and DeletePage tw-public@0: ######################################################################## tw-public@0: tw-public@0: def createTicket(tm = None): tw-public@0: """Create a ticket using a site-specific secret (the config)""" tw-public@0: import sha, time, types tw-public@0: ticket = tm or "%010x" % time.time() tw-public@0: digest = sha.new() tw-public@0: digest.update(ticket) tw-public@0: tw-public@0: cfgvars = vars(config) tw-public@0: for var in cfgvars.values(): tw-public@0: if type(var) is types.StringType: tw-public@0: digest.update(repr(var)) tw-public@0: tw-public@0: return "%s.%s" % (ticket, digest.hexdigest()) tw-public@0: tw-public@0: tw-public@0: def checkTicket(ticket): tw-public@0: """Check validity of a previously created ticket""" tw-public@0: timestamp = ticket.split('.')[0] tw-public@0: ourticket = createTicket(timestamp) tw-public@0: return ticket == ourticket tw-public@0: tw-public@0: