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