]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/wiki.py
people: Try to store address better in LDAP
[ipfire.org.git] / src / backend / wiki.py
CommitLineData
181d08f3
MT
1#!/usr/bin/python3
2
3import logging
6ac7e934 4import os.path
181d08f3
MT
5import re
6
7from . import misc
9523790a 8from . import util
181d08f3
MT
9from .decorators import *
10
181d08f3
MT
11class Wiki(misc.Object):
12 def _get_pages(self, query, *args):
13 res = self.db.query(query, *args)
14
15 for row in res:
16 yield Page(self.backend, row.id, data=row)
17
d398ca08
MT
18 def _get_page(self, query, *args):
19 res = self.db.get(query, *args)
20
21 if res:
22 return Page(self.backend, res.id, data=res)
23
6ac7e934
MT
24 def get_page_title(self, page, default=None):
25 doc = self.get_page(page)
26 if doc:
27 return doc.title
28
0b62a7f9 29 return default or os.path.basename(page)
6ac7e934 30
181d08f3
MT
31 def get_page(self, page, revision=None):
32 page = Page.sanitise_page_name(page)
33 assert page
34
35 if revision:
d398ca08 36 return self._get_page("SELECT * FROM wiki WHERE page = %s \
181d08f3
MT
37 AND timestamp = %s", page, revision)
38 else:
d398ca08 39 return self._get_page("SELECT * FROM wiki WHERE page = %s \
181d08f3
MT
40 ORDER BY timestamp DESC LIMIT 1", page)
41
f9db574a 42 def get_recent_changes(self, limit=None):
181d08f3 43 return self._get_pages("SELECT * FROM wiki \
f9db574a
MT
44 WHERE timestamp >= NOW() - INTERVAL '4 weeks' \
45 ORDER BY timestamp DESC LIMIT %s", limit)
181d08f3 46
495e9dc4 47 def create_page(self, page, author, content, changes=None, address=None):
181d08f3
MT
48 page = Page.sanitise_page_name(page)
49
495e9dc4 50 return self._get_page("INSERT INTO wiki(page, author_uid, markdown, changes, address) \
df01767e 51 VALUES(%s, %s, %s, %s, %s) RETURNING *", page, author.uid, content or None, changes, address)
181d08f3 52
495e9dc4 53 def delete_page(self, page, author, **kwargs):
181d08f3
MT
54 # Do nothing if the page does not exist
55 if not self.get_page(page):
56 return
57
58 # Just creates a blank last version of the page
495e9dc4 59 self.create_page(page, author=author, content=None, **kwargs)
181d08f3 60
3168788e
MT
61 def make_breadcrumbs(self, url):
62 # Split and strip all empty elements (double slashes)
181d08f3
MT
63 parts = list(e for e in url.split("/") if e)
64
3168788e 65 ret = []
b1bf7d48 66 for part in ("/".join(parts[:i]) for i in range(1, len(parts))):
3168788e 67 ret.append(("/%s" % part, self.get_page_title(part, os.path.basename(part))))
181d08f3 68
3168788e 69 return ret
181d08f3 70
9523790a
MT
71 def search(self, query, limit=None):
72 query = util.parse_search_query(query)
73
74 res = self._get_pages("SELECT wiki.* FROM wiki_search_index search_index \
75 LEFT JOIN wiki ON search_index.wiki_id = wiki.id \
76 WHERE search_index.document @@ to_tsquery('english', %s) \
77 ORDER BY ts_rank(search_index.document, to_tsquery('english', %s)) DESC \
78 LIMIT %s", query, query, limit)
79
80 return list(res)
81
82 def refresh(self):
83 """
84 Needs to be called after a page has been changed
85 """
86 self.db.execute("REFRESH MATERIALIZED VIEW wiki_search_index")
87
181d08f3
MT
88
89class Page(misc.Object):
90 def init(self, id, data=None):
91 self.id = id
92 self.data = data
93
94 def __lt__(self, other):
95 if isinstance(other, self.__class__):
96 if self.page == other.page:
97 return self.timestamp < other.timestamp
98
99 return self.page < other.page
100
101 @staticmethod
102 def sanitise_page_name(page):
103 if not page:
104 return "/"
105
106 # Make sure that the page name does NOT end with a /
107 if page.endswith("/"):
108 page = page[:-1]
109
110 # Make sure the page name starts with a /
111 if not page.startswith("/"):
112 page = "/%s" % page
113
114 # Remove any double slashes
115 page = page.replace("//", "/")
116
117 return page
118
119 @property
120 def url(self):
db8448d9 121 return self.page
181d08f3
MT
122
123 @property
124 def page(self):
125 return self.data.page
126
127 @property
128 def title(self):
129 return self._title or self.page[1:]
130
131 @property
132 def _title(self):
133 if not self.markdown:
134 return
135
136 # Find first H1 headline in markdown
137 markdown = self.markdown.splitlines()
138
139 m = re.match(r"^# (.*)( #)?$", markdown[0])
140 if m:
141 return m.group(1)
142
3b05ef6e
MT
143 @lazy_property
144 def author(self):
145 if self.data.author_uid:
146 return self.backend.accounts.get_by_uid(self.data.author_uid)
147
181d08f3
MT
148 def _render(self, text):
149 logging.debug("Rendering %s" % self)
150
574794da
MT
151 patterns = (
152 (r"\[\[([\w\d\/]+)(?:\|([\w\d\s]+))\]\]", r"/\1", r"\2", None, None),
153 (r"\[\[([\w\d\/\-]+)\]\]", r"/\1", r"\1", self.backend.wiki.get_page_title, r"\1"),
154 )
155
156 for pattern, link, title, repl, args in patterns:
157 replacements = []
158
159 for match in re.finditer(pattern, text):
160 l = match.expand(link)
161 t = match.expand(title)
162
163 if callable(repl):
164 t = repl(match.expand(args)) or t
165
166 replacements.append((match.span(), t or l, l))
167
168 # Apply all replacements
169 for (start, end), t, l in reversed(replacements):
170 text = text[:start] + "[%s](%s)" % (t, l) + text[end:]
171
045ea3db
MT
172 # Borrow this from the blog
173 return self.backend.blog._render_text(text, lang="markdown")
181d08f3
MT
174
175 @property
176 def markdown(self):
177 return self.data.markdown
178
179 @property
180 def html(self):
181 return self.data.html or self._render(self.markdown)
182
183 @property
184 def timestamp(self):
185 return self.data.timestamp
186
187 def was_deleted(self):
188 return self.markdown is None
189
190 @lazy_property
191 def breadcrumbs(self):
192 return self.backend.wiki.make_breadcrumbs(self.page)
193
194 def get_latest_revision(self):
7d699684
MT
195 revisions = self.get_revisions()
196
197 # Return first object
198 for rev in revisions:
199 return rev
200
201 def get_revisions(self):
202 return self.backend.wiki._get_pages("SELECT * FROM wiki \
203 WHERE page = %s ORDER BY timestamp DESC", self.page)
091ac36b 204
d398ca08
MT
205 @property
206 def changes(self):
207 return self.data.changes
208
091ac36b
MT
209 # Sidebar
210
211 @lazy_property
212 def sidebar(self):
213 parts = self.page.split("/")
214
215 while parts:
528340a8 216 sidebar = self.backend.wiki.get_page("%s/sidebar" % os.path.join(parts))
091ac36b
MT
217 if sidebar:
218 return sidebar
219
220 parts.pop()