Dateianhang 'Page.py'
Herunterladen 1 """
2 MoinMoin - Page class
3
4 Copyright (c) 2000, 2001, 2002 by Jürgen Hermann <jh@web.de>
5 All rights reserved, see COPYING for details.
6
7 $Id: Page.py,v 1.139 2003/01/24 21:31:14 jhermann Exp $
8 """
9
10 # Imports
11 import cStringIO, os, re, sys, string, urllib
12 from MoinMoin import caching, config, user, util, wikiutil, webapi
13 from MoinMoin.i18n import _
14
15
16 #############################################################################
17 ### Page - Manage a page associated with a WikiName
18 #############################################################################
19 class Page:
20 """ An immutable wiki page.
21
22 To change a page's content, use the PageEditor class.
23 """
24
25 _SPLIT_RE = re.compile('([%s])([%s])' % (config.lowerletters, config.upperletters))
26
27
28 def __init__(self, page_name, **keywords):
29 """ Create page object.
30
31 Note that this is a 'lean' operation, since the text for the page
32 is loaded on demand. Thus, things like `Page(name).link_to()` are
33 efficient.
34
35 **page_name** -- WikiName of the page
36 **keywords** --
37 date: date of older revision
38 formatter: formatter instance
39 """
40 self.page_name = page_name
41 self.prev_date = keywords.get('date')
42 self._raw_body = None
43
44 if keywords.has_key('formatter'):
45 self.formatter = keywords.get('formatter')
46 self.default_formatter = 0
47 else:
48 self.default_formatter = 1
49
50
51 def split_title(self, force=0):
52 """ Return a string with the page name split by spaces, if
53 the user wants that.
54 """
55 if not force and not user.current.wikiname_add_spaces: return self.page_name
56
57 # look for the end of words and the start of a new word,
58 # and insert a space there
59 return self._SPLIT_RE.sub(r'\1 \2', self.page_name)
60
61
62 def _text_filename(self):
63 """The name of the page file, possibly of an older page"""
64 if self.prev_date:
65 return os.path.join(config.backup_dir, wikiutil.quoteFilename(self.page_name) + "." + self.prev_date)
66 else:
67 return os.path.join(config.text_dir, wikiutil.quoteFilename(self.page_name))
68
69
70 def _tmp_filename(self):
71 """The name of the temporary file used while saving"""
72 return os.path.join(config.text_dir, ('#' + wikiutil.quoteFilename(self.page_name) + '.' + `os.getpid()` + '#'))
73
74
75 def _last_modified(self, request):
76 if not self.exists():
77 return None
78
79 result = None
80
81 lastedited = wikiutil.getPagePath(self.page_name, 'last-edited', check_create=0)
82 if os.path.exists(lastedited):
83 from MoinMoin import editlog
84
85 log = editlog.EditLog(request, filename=lastedited)
86 if log.next():
87 result = _("(last edited %(time)s by %(editor)s)") % {
88 'time': user.current.getFormattedDateTime(log.ed_time),
89 'editor': log.getEditor(),
90 }
91 del log
92
93 return result or _("(last modified %s)") % user.current.getFormattedDateTime(
94 os.path.getmtime(self._text_filename()))
95
96
97 def isWritable(self):
98 """True if page can be changed"""
99 return os.access(self._text_filename(), os.W_OK) or not self.exists()
100
101
102 def exists(self):
103 """True if the page exists"""
104 return os.path.exists(self._text_filename())
105
106
107 def size(self):
108 """Return page size, 0 for non-existent pages"""
109 if self._raw_body is not None:
110 return len(self._raw_body)
111
112 try:
113 return os.path.getsize(self._text_filename())
114 except EnvironmentError, e:
115 import errno
116 if e.errno == errno.ENOENT: return 0
117 raise
118
119
120 def get_raw_body(self):
121 """Load the raw markup from the page file"""
122 if self._raw_body is None:
123 # try to open file
124 try:
125 file = open(self._text_filename(), 'rb')
126 except IOError, er:
127 import errno
128 if er.errno == errno.ENOENT:
129 # just doesn't exist, return empty text (note that we
130 # never store empty pages, so this is detectable and also
131 # safe when passed to a function expecting a string)
132 return ""
133 else:
134 raise er
135
136 # read file content and make sure it is closed properly
137 try:
138 self.set_raw_body(file.read())
139 finally:
140 file.close()
141
142 return self._raw_body
143
144
145 def set_raw_body(self, body):
146 """Set the raw body text (prevents loading from disk)"""
147 self._raw_body = body
148
149
150 def url(self, querystr=None):
151 """ Return an URL for this page.
152 """
153 url = "%s/%s" % (webapi.getScriptname(), wikiutil.quoteWikiname(self.page_name))
154 if querystr: url = "%s?%s" % (url, string.replace(querystr, '&', '&'))
155 return url
156
157
158 def link_to(self, text=None, querystr=None, anchor=None, **kw):
159 """ Return HTML markup that links to this page.
160
161 See wikiutil.link_tag() for possible keyword parameters.
162 """
163 text = text or self.split_title()
164 fmt = getattr(self, 'formatter', None)
165 url = wikiutil.quoteWikiname(self.page_name)
166 if querystr: url = "%s?%s" % (url, string.replace(querystr, '&', '&'))
167 if anchor: url = "%s#%s" % (url, urllib.quote_plus(anchor))
168 if self.exists():
169 return wikiutil.link_tag(url, text, formatter=fmt, **kw)
170 elif user.current.show_nonexist_qm:
171 return wikiutil.link_tag(url,
172 '?', 'nonexistent', formatter=fmt, **kw) + text
173 else:
174 return wikiutil.link_tag(url, text, 'nonexistent', formatter=fmt, **kw)
175
176
177 def getSubscribers(self, request, **kw):
178 """ Get all subscribers of this page.
179 Return dict with email lists per language.
180
181 include_self == 1: include current user (default: 0)
182 return_users == 1: return user instances (default: 0)
183 """
184 include_self = kw.get('include_self', 0)
185 return_users = kw.get('return_users', 0)
186
187 # extract categories of this page
188 pageList = self.getCategories(request)
189
190 # add current page name for list matching
191 pageList.append(self.page_name)
192
193 # get email addresses of the all wiki user which have a profile stored;
194 # add the address only if the user has subscribed to the page and
195 # the user is not the current editor
196 userlist = user.getUserList()
197 emails = {}
198 for uid in userlist:
199 if uid == request.user.id and not include_self: continue # no self notification
200 subscriber = user.User(request, uid)
201 if not subscriber.email: continue # skip empty email address
202
203 if subscriber.isSubscribedTo(pageList):
204 lang = subscriber.language or 'en'
205 if not emails.has_key(lang): emails[lang] = []
206 if return_users:
207 emails[lang].append(subscriber)
208 else:
209 emails[lang].append(subscriber.email)
210
211 return emails
212
213
214 def send_page(self, request, msg=None, **keywords):
215 """ Send the formatted page to stdout.
216
217 **form** -- CGI-Form
218 **msg** -- if given, display message in header area
219 **keywords** --
220 content_only: 1 to omit page header and footer
221 count_hit: add an event to the log
222 """
223 request.clock.start('send_page')
224 request.clock.start('send_page_import_log')
225 import cgi
226 from MoinMoin.util import pysupport
227
228 # determine modes
229 print_mode = request.form.has_key('action') and request.form['action'].value == 'print'
230 content_only = keywords.get('content_only', 0)
231 self.hilite_re = keywords.get('hilite_re', None)
232 if msg is None: msg = ""
233
234 # count hit?
235 if keywords.get('count_hit', 0):
236 request.getEventLogger().add('VIEWPAGE', {'pagename': self.page_name})
237
238 request.clock.stop('send_page_import_log')
239
240 # load the text
241 request.clock.start('send_page_get_raw')
242 body = self.get_raw_body()
243 request.clock.stop('send_page_get_raw')
244
245 request.clock.start('send_page_formatter_pi')
246 # if necessary, load the default formatter
247 if self.default_formatter:
248 from MoinMoin.formatter.text_html import Formatter
249 self.formatter = Formatter(request, store_pagelinks=1)
250 self.formatter.setPage(self)
251
252 # default is wiki markup
253 pi_format = config.default_markup or "wiki"
254 pi_redirect = None
255 pi_formtext = []
256 pi_formfields = []
257 wikiform = None
258
259 # check for XML content
260 if body and body[:5] == '<?xml':
261 pi_format = "xslt"
262
263 # check processing instructions
264 while body and body[0] == '#':
265 # extract first line
266 try:
267 line, body = string.split(body, '\n', 1)
268 except ValueError:
269 line = body
270 body = ''
271
272 # skip comments (lines with two hash marks)
273 if line[1] == '#': continue
274
275 # parse the PI
276 verb, args = string.split(line[1:]+' ', ' ', 1)
277 verb = string.lower(verb)
278 args = string.strip(args)
279
280 # check the PIs
281 if verb == "format":
282 # markup format
283 pi_format = string.lower(args)
284 elif verb == "redirect":
285 # redirect to another page
286 # note that by including "action=show", we prevent
287 # endless looping (see code in "cgimain") or any
288 # cascaded redirection
289 pi_redirect = args
290 if request.form.has_key('action') or request.form.has_key('redirect') or content_only: continue
291
292 webapi.http_redirect(request, '%s/%s?action=show&redirect=%s' % (
293 webapi.getScriptname(),
294 wikiutil.quoteWikiname(pi_redirect),
295 urllib.quote_plus(self.page_name, ''),))
296 return
297 elif verb == "deprecated":
298 # deprecated page, append last backup version to current contents
299 # (which should be a short reason why the page is deprecated)
300 msg = '%s<b>%s</b><br>%s' % (
301 wikiutil.getSmiley('/!\\', self.formatter),
302 _('The backupped content of this page is deprecated and will not be included in search results!'),
303 msg)
304
305 oldversions = wikiutil.getBackupList(config.backup_dir, self.page_name)
306 if oldversions:
307 oldfile = oldversions[0]
308 olddate = os.path.basename(oldfile)[len(wikiutil.quoteFilename(self.page_name))+1:]
309 oldpage = Page(self.page_name, date=olddate)
310 body = body + oldpage.get_raw_body()
311 del oldfile
312 del olddate
313 del oldpage
314 elif verb == "pragma":
315 # store a list of name/value pairs for general use
316 try:
317 key, val = string.split(args, ' ', 1)
318 except (ValueError, TypeError):
319 pass
320 else:
321 request.setPragma(key, val)
322 elif verb == "form":
323 # ignore form PIs on non-form pages
324 if not wikiutil.isFormPage(self.page_name):
325 continue
326
327 # collect form definitions
328 if not wikiform:
329 from MoinMoin import wikiform
330 pi_formtext.append('<table border="1" cellspacing="1" cellpadding="3">\n'
331 '<form method="POST" action="%s">\n'
332 '<input type="hidden" name="action" value="formtest">\n' % self.url())
333 pi_formtext.append(wikiform.parseDefinition(request, args, pi_formfields))
334 else:
335 # unknown PI ==> end PI parsing
336 break
337
338 request.clock.stop('send_page_formatter_pi')
339
340 request.clock.start('send_page_doc_hdr')
341 # start document output
342 doc_leader = self.formatter.startDocument(self.page_name)
343 if not content_only:
344 # send the document leader
345 webapi.http_headers(request)
346 request.write(doc_leader)
347
348 # send the page header
349 if self.default_formatter:
350 page_needle = self.page_name
351 if config.allow_subpages and string.count(page_needle, '/'):
352 page_needle = '/' + string.split(page_needle, '/')[-1]
353 link = '%s/%s?action=fullsearch&value=%s&literal=1&case=1&context=40' % (
354 webapi.getScriptname(),
355 wikiutil.quoteWikiname(self.page_name),
356 urllib.quote_plus(page_needle, ''))
357 title = self.split_title()
358 if self.prev_date:
359 msg = "<b>%s</b><br>%s" % (
360 _('Version as of %(date)s') % {'date':
361 user.current.getFormattedDateTime(os.path.getmtime(self._text_filename()))},
362 msg)
363 if request.form.has_key('redirect'):
364 redir = request.form['redirect'].value
365 msg = '%s<b>%s</b><br>%s' % (
366 wikiutil.getSmiley('/!\\', self.formatter),
367 _('Redirected from page "%(page)s"') % {'page':
368 wikiutil.link_tag(wikiutil.quoteWikiname(redir) + "?action=show", redir)},
369 msg)
370 if pi_redirect:
371 msg = '%s<b>%s</b><br>%s' % (
372 wikiutil.getSmiley('<!>', self.formatter),
373 _('This page redirects to page "%(page)s"') % {'page': pi_redirect},
374 msg)
375 wikiutil.send_title(request, title, link=link, msg=msg,
376 pagename=self.page_name, print_mode=print_mode,
377 allow_doubleclick=1)
378
379 # page trail?
380 if not print_mode and user.current.valid:
381 user.current.addTrail(self.page_name)
382 trail = user.current.getTrail()
383 if trail and user.current.show_page_trail:
384 delim = '>'
385 if string.lower(config.charset) == 'iso-8859-1':
386 delim = '»'
387 print '<font face="Verdana" size="-1">%s %s %s</font><hr>' % (
388 string.join(
389 map(lambda p: Page(p).link_to(), trail[:-1]),
390 " %s " % delim),
391 delim, cgi.escape(trail[-1]))
392
393 # user-defined form preview?
394 if pi_formtext:
395 pi_formtext.append('<input type="hidden" name="fieldlist" value="%s">\n' %
396 string.join(pi_formfields, "|"))
397 pi_formtext.append('</form></table>\n')
398 pi_formtext.append(_(
399 '<p><small>If you submit this form, the submitted values'
400 ' will be displayed.\nTo use this form on other pages, insert a\n'
401 '<br><br><b><tt> '
402 '[[Form("%(pagename)s")]]'
403 '</tt></b><br><br>\n'
404 'macro call.</b></small></p>\n'
405 ) % {'pagename': self.page_name[:-len(config.page_form_ending)]})
406 print string.join(pi_formtext, '')
407
408 request.clock.stop('send_page_doc_hdr')
409
410 request.clock.start('send_page_ld_parser')
411 # try to load the parser
412 Parser = pysupport.importName("MoinMoin.parser." + pi_format, "Parser")
413 if Parser is None:
414 # default to plain text formatter (i.e. show the page source)
415 del Parser
416 from parser.plain import Parser
417 request.clock.stop('send_page_ld_parser')
418
419 request.clock.start('send_page_doc_content')
420 # new page?
421 if not self.exists() and self.default_formatter and not content_only:
422 self._emptyPageText(request)
423 else:
424 # parse the text and send the page content
425 #Parser(body, request).format(self.formatter)
426 self._send_page_content(request, Parser, body)
427
428 # check for pending footnotes
429 if getattr(request, 'footnotes', None):
430 from MoinMoin.macro.FootNote import emit_footnotes
431 print self.formatter.linebreak(0)
432 print emit_footnotes(request, self.formatter)
433
434 request.clock.stop('send_page_doc_content')
435
436 request.clock.start('send_page_doc_ftr')
437 # end document output
438 doc_trailer = self.formatter.endDocument()
439 if not content_only:
440 # send the page footer
441 if self.default_formatter and not print_mode:
442 wikiutil.send_footer(request, self.page_name, self._last_modified(request),
443 print_mode=print_mode)
444
445 request.write(doc_trailer)
446
447 # cache the pagelinks
448 if self.default_formatter and self.exists():
449 arena = "pagelinks"
450 key = wikiutil.quoteFilename(self.page_name)
451 cache = caching.CacheEntry(arena, key)
452 if cache.needsUpdate(self._text_filename()):
453 links = self.formatter.pagelinks
454 links.sort()
455 cache.update(string.join(links, '\n'))
456
457 request.clock.stop('send_page_doc_ftr')
458 request.clock.stop('send_page')
459
460
461 def _send_page_content(self, request, Parser, body):
462
463 formatter_name = str(self.formatter.__class__).\
464 replace('MoinMoin.formatter.', '').\
465 replace('.Formatter', '')
466
467 # if no caching
468 if (self.prev_date or self.hilite_re or
469 (not getattr(Parser, 'caching', None)) or
470 (not formatter_name in config.caching_formats)):
471 # parse the text and send the page content
472 Parser(body, request).format(self.formatter)
473 return
474
475 import marshal
476
477 #try cache
478 from MoinMoin import caching, wikimacro
479 arena = 'Page.py'
480 key = wikiutil.quoteFilename(self.page_name) + '.' + formatter_name
481 cache = caching.CacheEntry(arena, key)
482 code = None
483
484 # render page
485 if cache.needsUpdate(self._text_filename()):
486 from MoinMoin.formatter.text_python import Formatter
487 formatter = Formatter(request, self.formatter)
488
489 #redirect stdout
490 import cStringIO, sys
491 buffer = cStringIO.StringIO()
492 stdout = sys.stdout
493 sys.stdout = buffer
494
495 parser = Parser(body, request)
496 parser.format(formatter)
497
498 # reset stdout
499 sys.stdout = stdout
500 text = buffer.getvalue()
501 buffer.close()
502
503 #print text
504
505 text = text.replace('"', r'\"')
506 # now we split the text at the code position markers <<<>>>:
507 text = text.split('<<<>>>', len(formatter.code_fragments))
508 # we generate python source code by inserting print statements
509 # and putting the code fragments into place:
510 source = ['print """', text[0]]
511 for i in range(0,len(text)-1):
512 source.extend(['"""', formatter.code_fragments[i], 'print """', text[i+1]])
513 source.extend(['"""', ''])
514 # putting together the code pieces, inserting newlines:
515 source = '\n'.join(source)
516 #print source
517 code = compile(source, self.page_name, 'exec')
518 cache.update(marshal.dumps(code))
519 # offer link to refresh page cache
520 else:
521 refresh = wikiutil.link_tag(
522 wikiutil.quoteWikiname(self.page_name) +
523 "?action=refresh&arena=%s&key=%s" % (arena, key),
524 ("RefreshCache")) + (' for this page (cached %(date)s)' % \
525 {'date': self.formatter.request.user.getFormattedDateTime
526 (cache.mtime())} + '<br>')
527 self.formatter.request.add2footer('RefreshCache', refresh)
528
529 # send page
530 formatter = self.formatter
531 parser = Parser(body, request)
532 parser.formatter = formatter
533 macro_obj = wikimacro.Macro(parser)
534
535 if not code:
536 code = marshal.loads(cache.content())
537
538 exec code
539
540 def _emptyPageText(self, request):
541 from MoinMoin.action import LikePages
542
543 # generate the default page content for new pages
544 print wikiutil.link_tag(wikiutil.quoteWikiname(self.page_name)+'?action=edit',
545 _("Create this page"))
546
547 # look for template pages
548 templates = filter(lambda page, u = wikiutil: u.isTemplatePage(page),
549 wikiutil.getPageList(config.text_dir))
550 if templates:
551 print self.formatter.paragraph(1)
552 print self.formatter.text(_('Alternatively, use one of these templates:'))
553 print self.formatter.paragraph(0)
554
555 # send list of template pages
556 print self.formatter.bullet_list(1)
557 for page in templates:
558 print self.formatter.listitem(1)
559 print wikiutil.link_tag("%s?action=edit&template=%s" % (
560 wikiutil.quoteWikiname(self.page_name),
561 wikiutil.quoteWikiname(page)),
562 page)
563 print self.formatter.listitem(0)
564 print self.formatter.bullet_list(0)
565
566 print self.formatter.paragraph(1)
567 print self.formatter.text(_('To create your own templates, ' +
568 'add a page with a name ending in Template.'))
569 print self.formatter.paragraph(0)
570
571 # list similar pages that already exist
572 start, end, matches = LikePages.findMatches(self.page_name, request)
573 if matches and not isinstance(matches, type('')):
574 print self.formatter.rule()
575 print _('<p>The following pages with similar names already exist...</p>')
576 LikePages.showMatches(self.page_name, request, start, end, matches)
577
578
579 def getPageLinks(self, request):
580 """Get a list of the links on this page"""
581 if not self.exists(): return []
582
583 arena = "pagelinks"
584 key = wikiutil.quoteFilename(self.page_name)
585 cache = caching.CacheEntry(arena, key)
586 if cache.needsUpdate(self._text_filename()):
587 # this is normally never called, but is here to fill the cache
588 # in existing wikis; thus, we do a "null" send_page here, which
589 # is not efficient, but reduces code duplication
590 stdout = sys.stdout
591 sys.stdout = cStringIO.StringIO()
592 try:
593 try:
594 request.mode_getpagelinks = 1
595 Page(self.page_name).send_page(request, content_only=1)
596 except:
597 import traceback
598 traceback.print_exc()
599 cache.update('')
600 finally:
601 request.mode_getpagelinks = 0
602 sys.stdout = stdout
603 if hasattr(request, '_fmt_hd_counters'):
604 del request._fmt_hd_counters
605
606 return filter(None, string.split(cache.content(), '\n'))
607
608
609 def getCategories(self, request):
610 """ Return a list of categories this page belongs to.
611 """
612 return wikiutil.filterCategoryPages(self.getPageLinks(request))
Gespeicherte Dateianhänge
Um Dateianhänge in eine Seite einzufügen sollte unbedingt eine Angabe wie attachment:dateiname benutzt werden, wie sie auch in der folgenden Liste der Dateien erscheint. Es sollte niemals die URL des Verweises ("laden") kopiert werden, da sich diese jederzeit ändern kann und damit der Verweis auf die Datei brechen würde.Sie dürfen keine Anhänge an diese Seite anhängen!