1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - MoinSupport library (derived from EventAggregatorSupport) 4 5 @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk> 6 @copyright: 2000-2004 Juergen Hermann <jh@web.de>, 7 2004 by Florian Festi, 8 2006 by Mikko Virkkil, 9 2005-2008 MoinMoin:ThomasWaldmann, 10 2007 MoinMoin:ReimarBauer. 11 @license: GNU GPL (v2 or later), see COPYING.txt for details. 12 """ 13 14 from DateSupport import * 15 from ItemSupport import ItemDirectoryStore 16 from MoinMoin.parser import text_moin_wiki 17 from MoinMoin.Page import Page 18 from MoinMoin.util import lock 19 from MoinMoin import config, search, wikiutil 20 from StringIO import StringIO 21 from shlex import shlex 22 import re 23 import time 24 import os 25 26 # Moin 1.9 request parameters. 27 28 try: 29 from MoinMoin.support.werkzeug.datastructures import MultiDict 30 except ImportError: 31 pass 32 33 __version__ = "0.3" 34 35 # Extraction of shared fragments. 36 37 marker_regexp_str = r"([{]{3,}|[}]{3,})" 38 marker_regexp = re.compile(marker_regexp_str, re.MULTILINE | re.DOTALL) # {{{... or }}}... 39 40 # Extraction of headings. 41 42 heading_regexp = re.compile(r"^(?P<level>=+)(?P<heading>.*?)(?P=level)$", re.UNICODE | re.MULTILINE) 43 44 # Category extraction from pages. 45 46 category_regexp = None 47 48 # Simple content parsing. 49 50 verbatim_regexp = re.compile(ur'(?:' 51 ur'<<Verbatim\((?P<verbatim>.*?)\)>>' 52 ur'|' 53 ur'\[\[Verbatim\((?P<verbatim2>.*?)\)\]\]' 54 ur'|' 55 ur'!(?P<verbatim3>.*?)(\s|$)?' 56 ur'|' 57 ur'`(?P<monospace>.*?)`' 58 ur'|' 59 ur'{{{(?P<preformatted>.*?)}}}' 60 ur')', re.UNICODE) 61 62 # Category discovery. 63 64 def getCategoryPattern(request): 65 global category_regexp 66 67 try: 68 return request.cfg.cache.page_category_regexact 69 except AttributeError: 70 71 # Use regular expression from MoinMoin 1.7.1 otherwise. 72 73 if category_regexp is None: 74 category_regexp = re.compile(u'^%s$' % ur'(?P<all>Category(?P<key>(?!Template)\S+))', re.UNICODE) 75 return category_regexp 76 77 def getCategories(request): 78 79 """ 80 From the AdvancedSearch macro, return a list of category page names using 81 the given 'request'. 82 """ 83 84 # This will return all pages with "Category" in the title. 85 86 cat_filter = getCategoryPattern(request).search 87 return request.rootpage.getPageList(filter=cat_filter) 88 89 def getCategoryMapping(category_pagenames, request): 90 91 """ 92 For the given 'category_pagenames' return a list of tuples of the form 93 (category name, category page name) using the given 'request'. 94 """ 95 96 cat_pattern = getCategoryPattern(request) 97 mapping = [] 98 for pagename in category_pagenames: 99 name = cat_pattern.match(pagename).group("key") 100 if name != "Category": 101 mapping.append((name, pagename)) 102 mapping.sort() 103 return mapping 104 105 def getCategoryPages(pagename, request): 106 107 """ 108 Return the pages associated with the given category 'pagename' using the 109 'request'. 110 """ 111 112 query = search.QueryParser().parse_query('category:%s' % pagename) 113 results = search.searchPages(request, query, "page_name") 114 return filterCategoryPages(results, request) 115 116 def filterCategoryPages(results, request): 117 118 "Filter category pages from the given 'results' using the 'request'." 119 120 cat_pattern = getCategoryPattern(request) 121 pages = [] 122 for page in results.hits: 123 if not cat_pattern.match(page.page_name): 124 pages.append(page) 125 return pages 126 127 def getAllCategoryPages(category_names, request): 128 129 """ 130 Return all pages belonging to the categories having the given 131 'category_names', using the given 'request'. 132 """ 133 134 pages = [] 135 pagenames = set() 136 137 for category_name in category_names: 138 139 # Get the pages and page names in the category. 140 141 pages_in_category = getCategoryPages(category_name, request) 142 143 # Visit each page in the category. 144 145 for page_in_category in pages_in_category: 146 pagename = page_in_category.page_name 147 148 # Only process each page once. 149 150 if pagename in pagenames: 151 continue 152 else: 153 pagenames.add(pagename) 154 155 pages.append(page_in_category) 156 157 return pages 158 159 def getPagesForSearch(search_pattern, request): 160 161 """ 162 Return result pages for a search employing the given 'search_pattern' and 163 using the given 'request'. 164 """ 165 166 query = search.QueryParser().parse_query(search_pattern) 167 results = search.searchPages(request, query, "page_name") 168 return filterCategoryPages(results, request) 169 170 # WikiDict functions. 171 172 def getWikiDict(pagename, request): 173 174 """ 175 Return the WikiDict provided by the given 'pagename' using the given 176 'request'. 177 """ 178 179 if pagename and Page(request, pagename).exists() and request.user.may.read(pagename): 180 if hasattr(request.dicts, "dict"): 181 return request.dicts.dict(pagename) 182 else: 183 return request.dicts[pagename] 184 else: 185 return None 186 187 # Searching-related functions. 188 189 def getPagesFromResults(result_pages, request): 190 191 "Return genuine pages for the given 'result_pages' using the 'request'." 192 193 return [Page(request, page.page_name) for page in result_pages] 194 195 # Region/section parsing. 196 197 def getRegions(s, include_non_regions=False): 198 199 """ 200 Parse the string 's', returning a list of explicitly declared regions. 201 202 If 'include_non_regions' is specified as a true value, fragments will be 203 included for text between explicitly declared regions. 204 """ 205 206 regions = [] 207 marker = None 208 is_block = True 209 210 # Start a region for exposed text, if appropriate. 211 212 if include_non_regions: 213 regions.append("") 214 215 for match_text in marker_regexp.split(s): 216 217 # Capture section text. 218 219 if is_block: 220 if marker or include_non_regions: 221 regions[-1] += match_text 222 223 # Handle section markers. 224 225 else: 226 227 # Close any open sections, returning to exposed text regions. 228 229 if marker: 230 231 # Add any marker to the current region, regardless of whether it 232 # successfully closes a section. 233 234 regions[-1] += match_text 235 236 if match_text.startswith("}") and len(marker) == len(match_text): 237 marker = None 238 239 # Start a region for exposed text, if appropriate. 240 241 if include_non_regions: 242 regions.append("") 243 244 # Without a current marker, start a new section. 245 246 else: 247 marker = match_text 248 regions.append("") 249 250 # Add the marker to the new region. 251 252 regions[-1] += match_text 253 254 # The match text alternates between text between markers and the markers 255 # themselves. 256 257 is_block = not is_block 258 259 return regions 260 261 def getFragmentsFromRegions(regions): 262 263 """ 264 Return fragments from the given 'regions', each having the form 265 (format, attributes, body text). 266 """ 267 268 fragments = [] 269 270 for region in regions: 271 format, attributes, body, header, close = getFragmentFromRegion(region) 272 fragments.append((format, attributes, body)) 273 274 return fragments 275 276 def getFragmentFromRegion(region): 277 278 """ 279 Return a fragment for the given 'region' having the form (format, 280 attributes, body text, header, close), where the 'header' is the original 281 declaration of the 'region' or None if no explicit region is defined, and 282 'close' is the closing marker of the 'region' or None if no explicit region 283 is defined. 284 """ 285 286 if region.startswith("{{{"): 287 288 body = region.lstrip("{") 289 level = len(region) - len(body) 290 body = body.rstrip("}").lstrip() 291 292 # Remove any prelude and process metadata. 293 294 if body.startswith("#!"): 295 296 try: 297 declaration, body = body.split("\n", 1) 298 except ValueError: 299 declaration = body 300 body = "" 301 302 arguments = declaration[2:] 303 304 # Get any parser/format declaration. 305 306 if arguments and not arguments[0].isspace(): 307 details = arguments.split(None, 1) 308 if len(details) == 2: 309 format, arguments = details 310 else: 311 format = details[0] 312 arguments = "" 313 else: 314 format = None 315 316 # Get the attributes/arguments for the region. 317 318 attributes = parseAttributes(arguments, False) 319 320 # Add an entry for the format in the attribute dictionary. 321 322 if format and not attributes.has_key(format): 323 attributes[format] = True 324 325 return format, attributes, body, level * "{" + declaration + "\n", level * "}" 326 327 else: 328 return None, {}, body, level * "{" + "\n", level * "}" 329 330 else: 331 return None, {}, region, None, None 332 333 def getFragments(s, include_non_regions=False): 334 335 """ 336 Return fragments for the given string 's', each having the form 337 (format, arguments, body text). 338 339 If 'include_non_regions' is specified as a true value, fragments will be 340 included for text between explicitly declared regions. 341 """ 342 343 return getFragmentsFromRegions(getRegions(s, include_non_regions)) 344 345 # Heading extraction. 346 347 def getHeadings(s): 348 349 """ 350 Return tuples of the form (level, title, span) for headings found within the 351 given string 's'. The span is itself a (start, end) tuple indicating the 352 matching region of 's' for a heading declaration. 353 """ 354 355 headings = [] 356 357 for match in heading_regexp.finditer(s): 358 headings.append( 359 (len(match.group("level")), match.group("heading"), match.span()) 360 ) 361 362 return headings 363 364 # Region/section attribute parsing. 365 366 def parseAttributes(s, escape=True): 367 368 """ 369 Parse the section attributes string 's', returning a mapping of names to 370 values. If 'escape' is set to a true value, the attributes will be suitable 371 for use with the formatter API. If 'escape' is set to a false value, the 372 attributes will have any quoting removed. 373 """ 374 375 attrs = {} 376 f = StringIO(s) 377 name = None 378 need_value = False 379 lex = shlex(f) 380 lex.wordchars += "-" 381 382 for token in lex: 383 384 # Capture the name if needed. 385 386 if name is None: 387 name = escape and wikiutil.escape(token) or strip_token(token) 388 389 # Detect either an equals sign or another name. 390 391 elif not need_value: 392 if token == "=": 393 need_value = True 394 else: 395 attrs[name.lower()] = escape and "true" or True 396 name = wikiutil.escape(token) 397 398 # Otherwise, capture a value. 399 400 else: 401 # Quoting of attributes done similarly to wikiutil.parseAttributes. 402 403 if token: 404 if escape: 405 if token[0] in ("'", '"'): 406 token = wikiutil.escape(token) 407 else: 408 token = '"%s"' % wikiutil.escape(token, 1) 409 else: 410 token = strip_token(token) 411 412 attrs[name.lower()] = token 413 name = None 414 need_value = False 415 416 # Handle any name-only attributes at the end of the collection. 417 418 if name and not need_value: 419 attrs[name.lower()] = escape and "true" or True 420 421 return attrs 422 423 def strip_token(token): 424 425 "Return the given 'token' stripped of quoting." 426 427 if token[0] in ("'", '"') and token[-1] == token[0]: 428 return token[1:-1] 429 else: 430 return token 431 432 # Macro argument parsing. 433 434 def parseMacroArguments(args): 435 436 """ 437 Interpret the arguments. To support commas in labels, the label argument 438 should be quoted. For example: 439 440 "label=No, thanks!" 441 """ 442 443 try: 444 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 445 except AttributeError: 446 parsed_args = args.split(",") 447 448 pairs = [] 449 for arg in parsed_args: 450 if arg: 451 pair = arg.split("=", 1) 452 if len(pair) < 2: 453 pairs.append((None, arg)) 454 else: 455 pairs.append(tuple(pair)) 456 457 return pairs 458 459 def parseDictEntry(entry, unqualified=None): 460 461 """ 462 Return the parameters specified by the given dict 'entry' string. The 463 optional 'unqualified' parameter can be used to indicate parameters that 464 need not be specified together with a keyword and can therefore be populated 465 in the given order as such unqualified parameters are encountered. 466 467 NOTE: This is similar to parseMacroArguments but employs space as a 468 NOTE: separator and attempts to assign unqualified parameters. 469 """ 470 471 parameters = {} 472 unqualified = unqualified or () 473 474 try: 475 parsed_args = entry and wikiutil.parse_quoted_separated(entry, separator=None, name_value=False) or [] 476 except AttributeError: 477 parsed_args = entry.split() 478 479 for arg in parsed_args: 480 try: 481 argname, argvalue = arg.split("=", 1) 482 483 # Detect unlikely parameter names. 484 485 if not argname.isalpha(): 486 raise ValueError 487 488 parameters[argname] = argvalue 489 490 # Unqualified parameters are assumed to be one of a recognised set. 491 492 except ValueError: 493 for argname in unqualified: 494 if not parameters.has_key(argname): 495 parameters[argname] = arg 496 break 497 498 return parameters 499 500 # Request-related classes and associated functions. 501 502 class Form: 503 504 """ 505 A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x 506 environment. 507 """ 508 509 def __init__(self, request): 510 self.request = request 511 self.form = request.values 512 513 def has_key(self, name): 514 return not not self.form.getlist(name) 515 516 def get(self, name, default=None): 517 values = self.form.getlist(name) 518 if not values: 519 return default 520 else: 521 return values 522 523 def __getitem__(self, name): 524 return self.form.getlist(name) 525 526 def __setitem__(self, name, value): 527 try: 528 self.form.setlist(name, value) 529 except TypeError: 530 self._write_enable() 531 self.form.setlist(name, value) 532 533 def __delitem__(self, name): 534 try: 535 del self.form[name] 536 except TypeError: 537 self._write_enable() 538 del self.form[name] 539 540 def _write_enable(self): 541 self.form = self.request.values = MultiDict(self.form) 542 543 def keys(self): 544 return self.form.keys() 545 546 def items(self): 547 return self.form.lists() 548 549 class ActionSupport: 550 551 """ 552 Work around disruptive MoinMoin changes in 1.9, and also provide useful 553 convenience methods. 554 """ 555 556 def get_form(self): 557 return get_form(self.request) 558 559 def _get_selected(self, value, input_value): 560 561 """ 562 Return the HTML attribute text indicating selection of an option (or 563 otherwise) if 'value' matches 'input_value'. 564 """ 565 566 return input_value is not None and value == input_value and 'selected="selected"' or '' 567 568 def _get_selected_for_list(self, value, input_values): 569 570 """ 571 Return the HTML attribute text indicating selection of an option (or 572 otherwise) if 'value' matches one of the 'input_values'. 573 """ 574 575 return value in input_values and 'selected="selected"' or '' 576 577 def get_option_list(self, value, values): 578 579 """ 580 Return a list of HTML element definitions for options describing the 581 given 'values', selecting the option with the specified 'value' if 582 present. 583 """ 584 585 options = [] 586 for available_value in values: 587 selected = self._get_selected(available_value, value) 588 options.append('<option value="%s" %s>%s</option>' % ( 589 escattr(available_value), selected, wikiutil.escape(available_value))) 590 return options 591 592 def _get_input(self, form, name, default=None): 593 594 """ 595 Return the input from 'form' having the given 'name', returning either 596 the input converted to an integer or the given 'default' (optional, None 597 if not specified). 598 """ 599 600 value = form.get(name, [None])[0] 601 if not value: # true if 0 obtained 602 return default 603 else: 604 return int(value) 605 606 def get_form(request): 607 608 "Work around disruptive MoinMoin changes in 1.9." 609 610 if hasattr(request, "values"): 611 return Form(request) 612 else: 613 return request.form 614 615 class send_headers_cls: 616 617 """ 618 A wrapper to preserve MoinMoin 1.8.x (and earlier) request behaviour in a 619 1.9.x environment. 620 """ 621 622 def __init__(self, request): 623 self.request = request 624 625 def __call__(self, headers): 626 for header in headers: 627 parts = header.split(":") 628 self.request.headers.add(parts[0], ":".join(parts[1:])) 629 630 def get_send_headers(request): 631 632 "Return a function that can send response headers." 633 634 if hasattr(request, "http_headers"): 635 return request.http_headers 636 elif hasattr(request, "emit_http_headers"): 637 return request.emit_http_headers 638 else: 639 return send_headers_cls(request) 640 641 def escattr(s): 642 return wikiutil.escape(s, 1) 643 644 def getPathInfo(request): 645 if hasattr(request, "getPathinfo"): 646 return request.getPathinfo() 647 else: 648 return request.path 649 650 def getHeader(request, header_name, prefix=None): 651 652 """ 653 Using the 'request', return the value of the header with the given 654 'header_name', using the optional 'prefix' to obtain protocol-specific 655 headers if necessary. 656 657 If no value is found for the given 'header_name', None is returned. 658 """ 659 660 if hasattr(request, "getHeader"): 661 return request.getHeader(header_name) 662 elif hasattr(request, "headers"): 663 return request.headers.get(header_name) 664 else: 665 return request.env.get((prefix and prefix + "_" or "") + header_name.upper()) 666 667 def writeHeaders(request, mimetype, metadata, status=None): 668 669 """ 670 Using the 'request', write resource headers using the given 'mimetype', 671 based on the given 'metadata'. If the optional 'status' is specified, set 672 the status header to the given value. 673 """ 674 675 send_headers = get_send_headers(request) 676 677 # Define headers. 678 679 headers = ["Content-Type: %s; charset=%s" % (mimetype, config.charset)] 680 681 # Define the last modified time. 682 # NOTE: Consider using request.httpDate. 683 684 latest_timestamp = metadata.get("last-modified") 685 if latest_timestamp: 686 headers.append("Last-Modified: %s" % latest_timestamp.as_HTTP_datetime_string()) 687 688 if status: 689 headers.append("Status: %s" % status) 690 691 send_headers(headers) 692 693 # Page access functions. 694 695 def getPageURL(page): 696 697 "Return the URL of the given 'page'." 698 699 request = page.request 700 return request.getQualifiedURL(page.url(request, relative=0)) 701 702 def getFormat(page): 703 704 "Get the format used on the given 'page'." 705 706 return page.pi["format"] 707 708 def getMetadata(page): 709 710 """ 711 Return a dictionary containing items describing for the given 'page' the 712 page's "created" time, "last-modified" time, "sequence" (or revision number) 713 and the "last-comment" made about the last edit. 714 """ 715 716 request = page.request 717 718 # Get the initial revision of the page. 719 720 revisions = page.getRevList() 721 722 if not revisions: 723 return {} 724 725 event_page_initial = Page(request, page.page_name, rev=revisions[-1]) 726 727 # Get the created and last modified times. 728 729 initial_revision = getPageRevision(event_page_initial) 730 731 metadata = {} 732 metadata["created"] = initial_revision["timestamp"] 733 latest_revision = getPageRevision(page) 734 metadata["last-modified"] = latest_revision["timestamp"] 735 metadata["sequence"] = len(revisions) - 1 736 metadata["last-comment"] = latest_revision["comment"] 737 738 return metadata 739 740 def getPageRevision(page): 741 742 "Return the revision details dictionary for the given 'page'." 743 744 # From Page.edit_info... 745 746 if hasattr(page, "editlog_entry"): 747 line = page.editlog_entry() 748 else: 749 line = page._last_edited(page.request) # MoinMoin 1.5.x and 1.6.x 750 751 # Similar to Page.mtime_usecs behaviour... 752 753 if line: 754 timestamp = line.ed_time_usecs 755 mtime = wikiutil.version2timestamp(long(timestamp)) # must be long for py 2.2.x 756 comment = line.comment 757 else: 758 mtime = 0 759 comment = "" 760 761 # Give the time zone as UTC. 762 763 return {"timestamp" : DateTime(time.gmtime(mtime)[:6] + ("UTC",)), "comment" : comment} 764 765 # Page parsing and formatting of embedded content. 766 767 def getPageParserClass(request): 768 769 "Using 'request', return a parser class for the current page's format." 770 771 return getParserClass(request, getFormat(request.page)) 772 773 def getParserClass(request, format): 774 775 """ 776 Return a parser class using the 'request' for the given 'format', returning 777 a plain text parser if no parser can be found for the specified 'format'. 778 """ 779 780 try: 781 return wikiutil.searchAndImportPlugin(request.cfg, "parser", format or "plain") 782 except wikiutil.PluginMissingError: 783 return wikiutil.searchAndImportPlugin(request.cfg, "parser", "plain") 784 785 def getFormatterClass(request, format): 786 787 """ 788 Return a formatter class using the 'request' for the given output 'format', 789 returning a plain text formatter if no formatter can be found for the 790 specified 'format'. 791 """ 792 793 try: 794 return wikiutil.searchAndImportPlugin(request.cfg, "formatter", format or "plain") 795 except wikiutil.PluginMissingError: 796 return wikiutil.searchAndImportPlugin(request.cfg, "formatter", "plain") 797 798 def formatText(text, request, fmt, inhibit_p=True, parser_cls=None): 799 800 """ 801 Format the given 'text' using the specified 'request' and formatter 'fmt'. 802 Suppress line anchors in the output, and fix lists by indicating that a 803 paragraph has already been started. 804 """ 805 806 if not parser_cls: 807 parser_cls = getPageParserClass(request) 808 parser = parser_cls(text, request, line_anchors=False) 809 810 old_fmt = request.formatter 811 request.formatter = fmt 812 try: 813 if isinstance(parser, text_moin_wiki.Parser): 814 return redirectedOutput(request, parser, fmt, inhibit_p=inhibit_p) 815 else: 816 return redirectedOutput(request, parser, fmt) 817 finally: 818 request.formatter = old_fmt 819 820 def redirectedOutput(request, parser, fmt, **kw): 821 822 "A fixed version of the request method of the same name." 823 824 buf = StringIO() 825 request.redirect(buf) 826 try: 827 parser.format(fmt, **kw) 828 if hasattr(fmt, "flush"): 829 buf.write(fmt.flush(True)) 830 finally: 831 request.redirect() 832 text = buf.getvalue() 833 buf.close() 834 return text 835 836 # Finding components for content types. 837 838 def getParsersForContentType(cfg, mimetype): 839 840 """ 841 Find parsers that support the given 'mimetype', constructing a dictionary 842 mapping content types to lists of parsers that is then cached in the 'cfg' 843 object. A list of suitable parsers is returned for 'mimetype'. 844 """ 845 846 if not hasattr(cfg.cache, "MIMETYPE_TO_PARSER"): 847 available = {} 848 849 for name in wikiutil.getPlugins("parser", cfg): 850 851 # Import each parser in order to inspect supported content types. 852 853 try: 854 parser_cls = wikiutil.importPlugin(cfg, "parser", name, "Parser") 855 except wikiutil.PluginMissingError: 856 continue 857 858 # Attempt to determine supported content types. 859 # NOTE: Extensions and /etc/mime.types (or equivalent) could also be 860 # NOTE: used. 861 862 if hasattr(parser_cls, "input_mimetypes"): 863 for input_mimetype in parser_cls.input_mimetypes: 864 if not available.has_key(input_mimetype): 865 available[input_mimetype] = [] 866 available[input_mimetype].append(parser_cls) 867 868 # Support some basic parsers. 869 870 elif name == "text_moin_wiki": 871 available["text/moin-wiki"] = [parser_cls] 872 available["text/moin"] = [parser_cls] 873 elif name == "text_html": 874 available["text/html"] = [parser_cls] 875 available["application/xhtml+xml"] = [parser_cls] 876 877 cfg.cache.MIMETYPE_TO_PARSER = available 878 879 return cfg.cache.MIMETYPE_TO_PARSER.get(mimetype, []) 880 881 # Textual representations. 882 883 def getSimpleWikiText(text): 884 885 """ 886 Return the plain text representation of the given 'text' which may employ 887 certain Wiki syntax features, such as those providing verbatim or monospaced 888 text. 889 """ 890 891 # NOTE: Re-implementing support for verbatim text and linking avoidance. 892 893 return "".join([s for s in verbatim_regexp.split(text) if s is not None]) 894 895 def getEncodedWikiText(text): 896 897 "Encode the given 'text' in a verbatim representation." 898 899 return "<<Verbatim(%s)>>" % text 900 901 def getPrettyTitle(title): 902 903 "Return a nicely formatted version of the given 'title'." 904 905 return title.replace("_", " ").replace("/", u" ? ") 906 907 # User interface functions. 908 909 def getParameter(request, name, default=None): 910 911 """ 912 Using the given 'request', return the value of the parameter with the given 913 'name', returning the optional 'default' (or None) if no value was supplied 914 in the 'request'. 915 """ 916 917 return get_form(request).get(name, [default])[0] 918 919 def getQualifiedParameter(request, prefix, argname, default=None): 920 921 """ 922 Using the given 'request', 'prefix' and 'argname', retrieve the value of the 923 qualified parameter, returning the optional 'default' (or None) if no value 924 was supplied in the 'request'. 925 """ 926 927 argname = getQualifiedParameterName(prefix, argname) 928 return getParameter(request, argname, default) 929 930 def getQualifiedParameterName(prefix, argname): 931 932 """ 933 Return the qualified parameter name using the given 'prefix' and 'argname'. 934 """ 935 936 if not prefix: 937 return argname 938 else: 939 return "%s-%s" % (prefix, argname) 940 941 # Page-related functions. 942 943 def getPrettyPageName(page): 944 945 "Return a nicely formatted title/name for the given 'page'." 946 947 title = page.split_title(force=1) 948 return getPrettyTitle(title) 949 950 def linkToPage(request, page, text, query_string=None, anchor=None, **kw): 951 952 """ 953 Using 'request', return a link to 'page' with the given link 'text' and 954 optional 'query_string' and 'anchor'. 955 """ 956 957 text = wikiutil.escape(text) 958 return page.link_to_raw(request, text, query_string, anchor, **kw) 959 960 def linkToResource(url, request, text, query_string=None, anchor=None): 961 962 """ 963 Using 'request', return a link to 'url' with the given link 'text' and 964 optional 'query_string' and 'anchor'. 965 """ 966 967 if anchor: 968 url += "#%s" % anchor 969 970 if query_string: 971 query_string = wikiutil.makeQueryString(query_string) 972 url += "?%s" % query_string 973 974 formatter = request.page and getattr(request.page, "formatter", None) or request.html_formatter 975 976 output = [] 977 output.append(formatter.url(1, url)) 978 output.append(formatter.text(text)) 979 output.append(formatter.url(0)) 980 return "".join(output) 981 982 def getFullPageName(parent, title): 983 984 """ 985 Return a full page name from the given 'parent' page (can be empty or None) 986 and 'title' (a simple page name). 987 """ 988 989 if parent: 990 return "%s/%s" % (parent.rstrip("/"), title) 991 else: 992 return title 993 994 # Content storage support. 995 996 class ItemStore(ItemDirectoryStore): 997 998 "A page-specific item store." 999 1000 def __init__(self, page, item_dir="items", lock_dir="item_locks"): 1001 1002 "Initialise an item store for the given 'page'." 1003 1004 item_dir_path = tuple(item_dir.split("/")) 1005 lock_dir_path = tuple(lock_dir.split("/")) 1006 ItemDirectoryStore.__init__(self, page.getPagePath(*item_dir_path), page.getPagePath(*lock_dir_path)) 1007 self.page = page 1008 1009 def can_write(self): 1010 1011 """ 1012 Return whether the user associated with the request can write to the 1013 page owning this store. 1014 """ 1015 1016 user = self.page.request.user 1017 return user and user.may.write(self.page.page_name) 1018 1019 def can_read(self): 1020 1021 """ 1022 Return whether the user associated with the request can read from the 1023 page owning this store. 1024 """ 1025 1026 user = self.page.request.user 1027 return user and user.may.read(self.page.page_name) 1028 1029 # High-level methods. 1030 1031 def append(self, item): 1032 1033 "Append the given 'item' to the store." 1034 1035 if not self.can_write(): 1036 return 1037 1038 ItemDirectoryStore.append(self, item) 1039 1040 def __len__(self): 1041 1042 """ 1043 Return the number of the next item (which should also be the number of 1044 items). 1045 """ 1046 1047 if not self.can_read(): 1048 return 0 1049 1050 return ItemDirectoryStore.__len__(self) 1051 1052 def __getitem__(self, number): 1053 1054 "Return the item with the given 'number'." 1055 1056 if not self.can_read(): 1057 raise IndexError, number 1058 1059 return ItemDirectoryStore.__getitem__(self, number) 1060 1061 # vim: tabstop=4 expandtab shiftwidth=4