]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/wiki.py
wiki: Show when a page was last edited and by whom
[ipfire.org.git] / src / backend / wiki.py
CommitLineData
181d08f3
MT
1#!/usr/bin/python3
2
3import logging
4import markdown2
6ac7e934 5import os.path
181d08f3
MT
6import re
7
8from . import misc
9from .decorators import *
10
11# Used to automatically link some things
12link_patterns = (
13 # Find bug reports
14 (re.compile(r"(?:#(\d+))", re.I), r"https://bugzilla.ipfire.org/show_bug.cgi?id=\1"),
15
16 # Email Addresses
17 (re.compile(r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)"), r"mailto:\1"),
18
19 # CVE Numbers
20 (re.compile(r"(?:CVE)[\s\-](\d{4}\-\d+)"), r"https://cve.mitre.org/cgi-bin/cvename.cgi?name=\1"),
21)
22
23class Wiki(misc.Object):
24 def _get_pages(self, query, *args):
25 res = self.db.query(query, *args)
26
27 for row in res:
28 yield Page(self.backend, row.id, data=row)
29
6ac7e934
MT
30 def get_page_title(self, page, default=None):
31 doc = self.get_page(page)
32 if doc:
33 return doc.title
34
35 return default
36
181d08f3
MT
37 def get_page(self, page, revision=None):
38 page = Page.sanitise_page_name(page)
39 assert page
40
41 if revision:
42 res = self.db.get("SELECT * FROM wiki WHERE page = %s \
43 AND timestamp = %s", page, revision)
44 else:
45 res = self.db.get("SELECT * FROM wiki WHERE page = %s \
46 ORDER BY timestamp DESC LIMIT 1", page)
47
48 if res:
49 return Page(self.backend, res.id, data=res)
50
51 def get_recent_changes(self):
52 return self._get_pages("SELECT * FROM wiki \
53 WHERE timestamp >= NOW() - INTERVAL '4 weeks' ORDER BY timestamp DESC")
54
55 def create_page(self, page, author, markdown):
56 page = Page.sanitise_page_name(page)
57
58 res = self.db.get("INSERT INTO wiki(page, author_id, markdown) \
59 VALUES(%s, %s, %s) RETURNING id", page, author.id, markdown)
60
61 if res:
62 return self.get_page_by_id(res.id)
63
64 def delete_page(self, page, author):
65 # Do nothing if the page does not exist
66 if not self.get_page(page):
67 return
68
69 # Just creates a blank last version of the page
70 self.create_page(page, author, None)
71
72 @staticmethod
73 def _split_url(url):
74 parts = list(e for e in url.split("/") if e)
75
76 num_parts = len(parts)
77 for i in range(num_parts):
78 yield "/".join(parts[:i])
79
80 def make_breadcrumbs(self, url):
81 for part in self._split_url(url):
82 title = self.get_page_title(part, os.path.basename(part))
83
84 yield ("/%s" % part, title)
85
86
87class Page(misc.Object):
88 def init(self, id, data=None):
89 self.id = id
90 self.data = data
91
92 def __lt__(self, other):
93 if isinstance(other, self.__class__):
94 if self.page == other.page:
95 return self.timestamp < other.timestamp
96
97 return self.page < other.page
98
99 @staticmethod
100 def sanitise_page_name(page):
101 if not page:
102 return "/"
103
104 # Make sure that the page name does NOT end with a /
105 if page.endswith("/"):
106 page = page[:-1]
107
108 # Make sure the page name starts with a /
109 if not page.startswith("/"):
110 page = "/%s" % page
111
112 # Remove any double slashes
113 page = page.replace("//", "/")
114
115 return page
116
117 @property
118 def url(self):
119 return "/%s" % self.page
120
121 @property
122 def page(self):
123 return self.data.page
124
125 @property
126 def title(self):
127 return self._title or self.page[1:]
128
129 @property
130 def _title(self):
131 if not self.markdown:
132 return
133
134 # Find first H1 headline in markdown
135 markdown = self.markdown.splitlines()
136
137 m = re.match(r"^# (.*)( #)?$", markdown[0])
138 if m:
139 return m.group(1)
140
3b05ef6e
MT
141 @lazy_property
142 def author(self):
143 if self.data.author_uid:
144 return self.backend.accounts.get_by_uid(self.data.author_uid)
145
181d08f3
MT
146 def _render(self, text):
147 logging.debug("Rendering %s" % self)
148
149 return markdown2.markdown(text, link_patterns=link_patterns,
150 extras=["footnotes", "link-patterns", "wiki-tables"])
151
152 @property
153 def markdown(self):
154 return self.data.markdown
155
156 @property
157 def html(self):
158 return self.data.html or self._render(self.markdown)
159
160 @property
161 def timestamp(self):
162 return self.data.timestamp
163
164 def was_deleted(self):
165 return self.markdown is None
166
167 @lazy_property
168 def breadcrumbs(self):
169 return self.backend.wiki.make_breadcrumbs(self.page)
170
171 def get_latest_revision(self):
172 return self.backend.wiki.get_page(self.page)
091ac36b
MT
173
174 # Sidebar
175
176 @lazy_property
177 def sidebar(self):
178 parts = self.page.split("/")
179
180 while parts:
181 sidebar = self.backend.wiki.get_page(os.path.join(*parts, "sidebar"))
182 if sidebar:
183 return sidebar
184
185 parts.pop()