]> git.ipfire.org Git - ipfire.org.git/blame - src/web/wiki.py
docs: Refactor showing pages
[ipfire.org.git] / src / web / wiki.py
CommitLineData
181d08f3
MT
1#!/usr/bin/python3
2
c21ffadb 3import difflib
181d08f3
MT
4import tornado.web
5
181d08f3 6from . import base
6ac7e934 7from . import ui_modules
181d08f3 8
da24ac0a 9class ActionEditHandler(base.BaseHandler):
b6e8b28f 10 @tornado.web.authenticated
3587f73a 11 def get(self, path):
76433782
MT
12 if path is None:
13 path = "/"
14
3587f73a
MT
15 # Check permissions
16 if not self.backend.wiki.check_acl(path, self.current_user):
17 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
18
19 # Fetch the wiki page
20 page = self.backend.wiki.get_page(path)
21
22 # Empty page if it was deleted
23 if page and page.was_deleted():
24 page = None
b6e8b28f 25
3587f73a 26 # Render page
ec606db5 27 self.render("wiki/edit.html", page=page, path=path)
3587f73a
MT
28
29 @tornado.web.authenticated
30 def post(self, path):
76433782
MT
31 if path is None:
32 path = "/"
33
b6e8b28f
MT
34 # Check permissions
35 if not self.backend.wiki.check_acl(path, self.current_user):
36 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
37
38 content = self.get_argument("content", None)
39 changes = self.get_argument("changes")
40
41 # Create a new page in the database
42 with self.db.transaction():
43 page = self.backend.wiki.create_page(path,
44 self.current_user, content, changes=changes, address=self.get_remote_ip())
45
d64a1e35
MT
46 # Add user as a watcher if wanted
47 watch = self.get_argument("watch", False)
48 if watch:
49 page.add_watcher(self.current_user)
50
b6e8b28f
MT
51 # Redirect back
52 if page.was_deleted():
53 self.redirect("/")
54 else:
55 self.redirect(page.url)
56
57 def on_finish(self):
58 """
59 Updates the search index after the page has been edited
60 """
61 # This is being executed in the background and after
62 # the response has been set to the client
63 with self.db.transaction():
64 self.backend.wiki.refresh()
65
66
da24ac0a 67class ActionUploadHandler(base.BaseHandler):
f2cfd873 68 @tornado.web.authenticated
372ef119 69 @base.ratelimit(minutes=60, requests=24)
f2cfd873
MT
70 def post(self):
71 path = self.get_argument("path")
72
11afe905
MT
73 # Check permissions
74 if not self.backend.wiki.check_acl(path, self.current_user):
75 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
76
f2cfd873
MT
77 try:
78 filename, data, mimetype = self.get_file("file")
79
ff14dea3
MT
80 # Use filename from request if any
81 filename = self.get_argument("filename", filename)
82
f2cfd873
MT
83 # XXX check valid mimetypes
84
85 with self.db.transaction():
86 file = self.backend.wiki.upload(path, filename, data,
87 mimetype=mimetype, author=self.current_user,
88 address=self.get_remote_ip())
89
90 except TypeError as e:
91 raise e
92
3b33319e 93 self.redirect("%s/_files" % path)
f2cfd873
MT
94
95
da24ac0a 96class ActionDeleteHandler(base.BaseHandler):
b26c705a
MT
97 @tornado.web.authenticated
98 def get(self, path):
99 # Check permissions
100 if not self.backend.wiki.check_acl(path, self.current_user):
101 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
102
103 # Fetch the file
104 file = self.backend.wiki.get_file_by_path(path)
105 if not file:
106 raise tornado.web.HTTPError(404, "Could not find %s" % path)
107
108 self.render("wiki/confirm-delete.html", file=file)
109
110 @tornado.web.authenticated
111 @base.ratelimit(minutes=60, requests=24)
112 def post(self, path):
113 # Check permissions
114 if not self.backend.wiki.check_acl(path, self.current_user):
115 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
116
117 # Fetch the file
118 file = self.backend.wiki.get_file_by_path(path)
119 if not file:
120 raise tornado.web.HTTPError(404, "Could not find %s" % path)
121
122 with self.db.transaction():
123 file.delete(self.current_user)
124
125 self.redirect("%s/_files" % file.path)
126
127
da24ac0a 128class ActionRestoreHandler(base.BaseHandler):
d4c68c5c
MT
129 @tornado.web.authenticated
130 @base.ratelimit(minutes=60, requests=24)
131 def post(self):
132 path = self.get_argument("path")
133
134 # Check permissions
135 if not self.backend.wiki.check_acl(path, self.current_user):
136 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
137
138 # Check if we are asked to render a certain revision
139 revision = self.get_argument("revision", None)
9f1cfab7 140 comment = self.get_argument("comment", None)
d4c68c5c
MT
141
142 # Fetch the wiki page
143 page = self.backend.wiki.get_page(path, revision=revision)
144
145 with self.db.transaction():
146 page = page.restore(
9f1cfab7
MT
147 author=self.current_user,
148 address=self.get_remote_ip(),
149 comment=comment,
d4c68c5c
MT
150 )
151
152 # Redirect back to page
153 self.redirect(page.page)
154
b26c705a 155
da24ac0a 156class ActionWatchHandler(base.BaseHandler):
d64a1e35 157 @tornado.web.authenticated
372ef119 158 @base.ratelimit(minutes=60, requests=180)
9db2e89f 159 def get(self, path, action):
76433782
MT
160 if path is None:
161 path = "/"
162
d64a1e35
MT
163 page = self.backend.wiki.get_page(path)
164 if not page:
165 raise tornado.web.HTTPError(404, "Page does not exist: %s" % path)
166
516da0a9
MT
167 # Check permissions
168 if not self.backend.wiki.check_acl(path, self.current_user):
169 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
170
d64a1e35
MT
171 with self.db.transaction():
172 if action == "watch":
173 page.add_watcher(self.current_user)
174 elif action == "unwatch":
175 page.remove_watcher(self.current_user)
176
177 # Redirect back to page
178 self.redirect(page.url)
179
180
da24ac0a 181class ActionRenderHandler(base.BaseHandler):
2901b734
MT
182 def check_xsrf_cookie(self):
183 pass # disabled
184
185 @tornado.web.authenticated
372ef119 186 @base.ratelimit(minutes=5, requests=180)
2901b734 187 def post(self, path):
76433782
MT
188 if path is None:
189 path = "/"
190
2901b734
MT
191 content = self.get_argument("content")
192
193 # Render the content
194 html = self.backend.wiki.render(path, content)
195
196 self.finish(html)
197
198
da24ac0a 199class FilesHandler(base.BaseHandler):
f2cfd873
MT
200 @tornado.web.authenticated
201 def get(self, path):
76433782
MT
202 if path is None:
203 path = "/"
204
11afe905
MT
205 # Check permissions
206 if not self.backend.wiki.check_acl(path, self.current_user):
207 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
208
f2cfd873
MT
209 files = self.backend.wiki.get_files(path)
210
211 self.render("wiki/files/index.html", path=path, files=files)
212
213
568b265b 214class FileHandler(base.BaseHandler):
8cb0bea4
MT
215 @property
216 def action(self):
217 return self.get_argument("action", None)
218
f2cfd873 219 def get(self, path):
11afe905
MT
220 # Check permissions
221 if not self.backend.wiki.check_acl(path, self.current_user):
222 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
223
ff14dea3
MT
224 # Check if we are asked to render a certain revision
225 revision = self.get_argument("revision", None)
226
11afe905 227 # Fetch the file
ff14dea3 228 file = self.backend.wiki.get_file_by_path(path, revision=revision)
f2cfd873
MT
229 if not file:
230 raise tornado.web.HTTPError(404, "Could not find %s" % path)
231
8cb0bea4
MT
232 # Render detail page
233 if self.action == "detail":
1fbe97b0
MT
234 page = None
235
531846f7
MT
236 for breadcrumb, title in self.backend.wiki.make_breadcrumbs(path):
237 page = self.backend.wiki.get_page(breadcrumb)
238 if page:
239 break
240
241 self.render("wiki/files/detail.html", page=page, file=file)
8cb0bea4
MT
242 return
243
79dd9a0f
MT
244 size = self.get_argument_int("s", None)
245
246 # Check if image should be resized
8a62e589 247 if size and file.is_bitmap_image():
79dd9a0f
MT
248 blob = file.get_thumbnail(size)
249 else:
250 blob = file.blob
251
f2cfd873
MT
252 # Set headers
253 self.set_header("Content-Type", file.mimetype or "application/octet-stream")
79dd9a0f 254 self.set_header("Content-Length", len(blob))
f2cfd873 255
568b265b
MT
256 # Set expires
257 self.set_expires(3600)
258
79dd9a0f
MT
259 # Deliver content
260 self.finish(blob)
f2cfd873
MT
261
262
da24ac0a 263class PageHandler(base.BaseHandler):
d398ca08
MT
264 @property
265 def action(self):
266 return self.get_argument("action", None)
267
a446dcb9
MT
268 def write_error(self, status_code, **kwargs):
269 # Render a custom page for 404
270 if status_code == 404:
271 self.render("wiki/404.html", **kwargs)
272 return
273
274 # Otherwise raise this to one layer above
275 super().write_error(status_code, **kwargs)
276
181d08f3 277 @tornado.web.removeslash
5bc8e262 278 def get(self, path):
76433782
MT
279 if path is None:
280 path = "/"
281
11afe905 282 # Check permissions
5bc8e262
MT
283 if not self.backend.wiki.check_acl(path, self.current_user):
284 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
11afe905 285
7d699684
MT
286 # Check if we are asked to render a certain revision
287 revision = self.get_argument("revision", None)
288
289 # Fetch the wiki page
5bc8e262 290 page = self.backend.wiki.get_page(path, revision=revision)
181d08f3 291
c21ffadb
MT
292 # Diff
293 if self.action == "diff":
294 # Get both revisions
295 a = self.get_argument("a")
296 b = self.get_argument("b")
297
298 # Fetch both versions of the page
5bc8e262
MT
299 a = self.backend.wiki.get_page(path, revision=a)
300 b = self.backend.wiki.get_page(path, revision=b)
c21ffadb
MT
301 if not a or not b:
302 raise tornado.web.HTTPError(404)
303
304 # Cannot render a diff for the identical page
305 if a == b:
306 raise tornado.web.HTTPError(400)
307
308 # Make sure that b is newer than a
309 if a > b:
310 a, b = b, a
311
312 self.render("wiki/diff.html", page=page, a=a, b=b)
313 return
314
d4c68c5c
MT
315 # Restore
316 elif self.action == "restore":
317 self.render("wiki/confirm-restore.html", page=page)
318 return
319
7d699684
MT
320 # Revisions
321 elif self.action == "revisions":
322 self.render("wiki/revisions.html", page=page)
323 return
d398ca08 324
181d08f3
MT
325 # If the page does not exist, we send 404
326 if not page or page.was_deleted():
9ff59d70
MT
327 # Handle /start links which were in the format of DokuWiki
328 if path.endswith("/start"):
329 # Strip /start from path
330 path = path[:-6] or "/"
331
332 # Redirect user to page if it exists
333 page = self.backend.wiki.page_exists(path)
334 if page:
335 self.redirect(path)
336
181d08f3
MT
337 raise tornado.web.HTTPError(404)
338
339 # Fetch the latest revision
340 latest_revision = page.get_latest_revision()
341
342 # Render page
343 self.render("wiki/page.html", page=page, latest_revision=latest_revision)
344
181d08f3 345
da24ac0a 346class SearchHandler(base.BaseHandler):
6172b90a 347 @base.ratelimit(minutes=5, requests=25)
181d08f3
MT
348 def get(self):
349 q = self.get_argument("q")
350
11afe905 351 pages = self.backend.wiki.search(q, account=self.current_user, limit=50)
181d08f3 352
df80be2c 353 self.render("wiki/search-results.html", q=q, pages=pages)
6ac7e934
MT
354
355
da24ac0a 356class RecentChangesHandler(base.BaseHandler):
f9db574a 357 def get(self):
11afe905 358 recent_changes = self.backend.wiki.get_recent_changes(self.current_user, limit=50)
f9db574a
MT
359
360 self.render("wiki/recent-changes.html", recent_changes=recent_changes)
361
362
da24ac0a 363class TreeHandler(base.BaseHandler):
86368c12
MT
364 def get(self):
365 self.render("wiki/tree.html", pages=self.backend.wiki)
366
367
da24ac0a 368class WatchlistHandler(base.BaseHandler):
2f23c558
MT
369 @tornado.web.authenticated
370 def get(self):
371 pages = self.backend.wiki.get_watchlist(self.current_user)
372
373 self.render("wiki/watchlist.html", pages=pages)
374
375
c21ffadb
MT
376class WikiDiffModule(ui_modules.UIModule):
377 differ = difflib.Differ()
378
379 def render(self, a, b):
380 diff = self.differ.compare(
381 a.markdown.splitlines(),
382 b.markdown.splitlines(),
383 )
384
385 return self.render_string("wiki/modules/diff.html", diff=diff)
386
387
f9db574a 388class WikiListModule(ui_modules.UIModule):
27ac1524
MT
389 def render(self, pages, link_revision=False, show_breadcrumbs=True,
390 show_author=True, show_changes=False):
7d699684 391 return self.render_string("wiki/modules/list.html", link_revision=link_revision,
27ac1524
MT
392 pages=pages, show_breadcrumbs=show_breadcrumbs,
393 show_author=show_author, show_changes=show_changes)