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