]> git.ipfire.org Git - ipfire.org.git/blame - src/web/wiki.py
wiki: Automatically redirect from /start 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
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
81 # XXX check valid mimetypes
82
83 with self.db.transaction():
84 file = self.backend.wiki.upload(path, filename, data,
85 mimetype=mimetype, author=self.current_user,
86 address=self.get_remote_ip())
87
88 except TypeError as e:
89 raise e
90
3b33319e 91 self.redirect("%s/_files" % path)
f2cfd873
MT
92
93
d64a1e35
MT
94class ActionWatchHandler(auth.CacheMixin, base.BaseHandler):
95 @tornado.web.authenticated
372ef119 96 @base.ratelimit(minutes=60, requests=180)
9db2e89f 97 def get(self, path, action):
76433782
MT
98 if path is None:
99 path = "/"
100
d64a1e35
MT
101 page = self.backend.wiki.get_page(path)
102 if not page:
103 raise tornado.web.HTTPError(404, "Page does not exist: %s" % path)
104
516da0a9
MT
105 # Check permissions
106 if not self.backend.wiki.check_acl(path, self.current_user):
107 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
108
d64a1e35
MT
109 with self.db.transaction():
110 if action == "watch":
111 page.add_watcher(self.current_user)
112 elif action == "unwatch":
113 page.remove_watcher(self.current_user)
114
115 # Redirect back to page
116 self.redirect(page.url)
117
118
2901b734
MT
119class ActionRenderHandler(auth.CacheMixin, base.BaseHandler):
120 def check_xsrf_cookie(self):
121 pass # disabled
122
123 @tornado.web.authenticated
372ef119 124 @base.ratelimit(minutes=5, requests=180)
2901b734 125 def post(self, path):
76433782
MT
126 if path is None:
127 path = "/"
128
2901b734
MT
129 content = self.get_argument("content")
130
131 # Render the content
132 html = self.backend.wiki.render(path, content)
133
134 self.finish(html)
135
136
f2cfd873
MT
137class FilesHandler(auth.CacheMixin, base.BaseHandler):
138 @tornado.web.authenticated
139 def get(self, path):
76433782
MT
140 if path is None:
141 path = "/"
142
11afe905
MT
143 # Check permissions
144 if not self.backend.wiki.check_acl(path, self.current_user):
145 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
146
f2cfd873
MT
147 files = self.backend.wiki.get_files(path)
148
149 self.render("wiki/files/index.html", path=path, files=files)
150
151
568b265b 152class FileHandler(base.BaseHandler):
8cb0bea4
MT
153 @property
154 def action(self):
155 return self.get_argument("action", None)
156
f2cfd873 157 def get(self, path):
11afe905
MT
158 # Check permissions
159 if not self.backend.wiki.check_acl(path, self.current_user):
160 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
161
162 # Fetch the file
f2cfd873
MT
163 file = self.backend.wiki.get_file_by_path(path)
164 if not file:
165 raise tornado.web.HTTPError(404, "Could not find %s" % path)
166
8cb0bea4
MT
167 # Render detail page
168 if self.action == "detail":
1fbe97b0
MT
169 page = None
170
531846f7
MT
171 for breadcrumb, title in self.backend.wiki.make_breadcrumbs(path):
172 page = self.backend.wiki.get_page(breadcrumb)
173 if page:
174 break
175
176 self.render("wiki/files/detail.html", page=page, file=file)
8cb0bea4
MT
177 return
178
79dd9a0f
MT
179 size = self.get_argument_int("s", None)
180
181 # Check if image should be resized
182 if file.is_image() and size:
183 blob = file.get_thumbnail(size)
184 else:
185 blob = file.blob
186
f2cfd873
MT
187 # Set headers
188 self.set_header("Content-Type", file.mimetype or "application/octet-stream")
79dd9a0f 189 self.set_header("Content-Length", len(blob))
f2cfd873 190
568b265b
MT
191 # Set expires
192 self.set_expires(3600)
193
79dd9a0f
MT
194 # Deliver content
195 self.finish(blob)
f2cfd873
MT
196
197
181d08f3 198class PageHandler(auth.CacheMixin, base.BaseHandler):
d398ca08
MT
199 @property
200 def action(self):
201 return self.get_argument("action", None)
202
a446dcb9
MT
203 def write_error(self, status_code, **kwargs):
204 # Render a custom page for 404
205 if status_code == 404:
206 self.render("wiki/404.html", **kwargs)
207 return
208
209 # Otherwise raise this to one layer above
210 super().write_error(status_code, **kwargs)
211
181d08f3 212 @tornado.web.removeslash
5bc8e262 213 def get(self, path):
76433782
MT
214 if path is None:
215 path = "/"
216
11afe905 217 # Check permissions
5bc8e262
MT
218 if not self.backend.wiki.check_acl(path, self.current_user):
219 raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
11afe905 220
7d699684
MT
221 # Check if we are asked to render a certain revision
222 revision = self.get_argument("revision", None)
223
224 # Fetch the wiki page
5bc8e262 225 page = self.backend.wiki.get_page(path, revision=revision)
181d08f3 226
c21ffadb
MT
227 # Diff
228 if self.action == "diff":
229 # Get both revisions
230 a = self.get_argument("a")
231 b = self.get_argument("b")
232
233 # Fetch both versions of the page
5bc8e262
MT
234 a = self.backend.wiki.get_page(path, revision=a)
235 b = self.backend.wiki.get_page(path, revision=b)
c21ffadb
MT
236 if not a or not b:
237 raise tornado.web.HTTPError(404)
238
239 # Cannot render a diff for the identical page
240 if a == b:
241 raise tornado.web.HTTPError(400)
242
243 # Make sure that b is newer than a
244 if a > b:
245 a, b = b, a
246
247 self.render("wiki/diff.html", page=page, a=a, b=b)
248 return
249
7d699684
MT
250 # Revisions
251 elif self.action == "revisions":
252 self.render("wiki/revisions.html", page=page)
253 return
d398ca08 254
181d08f3
MT
255 # If the page does not exist, we send 404
256 if not page or page.was_deleted():
9ff59d70
MT
257 # Handle /start links which were in the format of DokuWiki
258 if path.endswith("/start"):
259 # Strip /start from path
260 path = path[:-6] or "/"
261
262 # Redirect user to page if it exists
263 page = self.backend.wiki.page_exists(path)
264 if page:
265 self.redirect(path)
266
181d08f3
MT
267 raise tornado.web.HTTPError(404)
268
269 # Fetch the latest revision
270 latest_revision = page.get_latest_revision()
271
272 # Render page
273 self.render("wiki/page.html", page=page, latest_revision=latest_revision)
274
181d08f3
MT
275
276class SearchHandler(auth.CacheMixin, base.BaseHandler):
6172b90a 277 @base.ratelimit(minutes=5, requests=25)
181d08f3
MT
278 def get(self):
279 q = self.get_argument("q")
280
11afe905 281 pages = self.backend.wiki.search(q, account=self.current_user, limit=50)
181d08f3 282
df80be2c 283 self.render("wiki/search-results.html", q=q, pages=pages)
6ac7e934
MT
284
285
f9db574a
MT
286class RecentChangesHandler(auth.CacheMixin, base.BaseHandler):
287 def get(self):
11afe905 288 recent_changes = self.backend.wiki.get_recent_changes(self.current_user, limit=50)
f9db574a
MT
289
290 self.render("wiki/recent-changes.html", recent_changes=recent_changes)
291
292
2f23c558
MT
293class WatchlistHandler(auth.CacheMixin, base.BaseHandler):
294 @tornado.web.authenticated
295 def get(self):
296 pages = self.backend.wiki.get_watchlist(self.current_user)
297
298 self.render("wiki/watchlist.html", pages=pages)
299
300
c21ffadb
MT
301class WikiDiffModule(ui_modules.UIModule):
302 differ = difflib.Differ()
303
304 def render(self, a, b):
305 diff = self.differ.compare(
306 a.markdown.splitlines(),
307 b.markdown.splitlines(),
308 )
309
310 return self.render_string("wiki/modules/diff.html", diff=diff)
311
312
f9db574a 313class WikiListModule(ui_modules.UIModule):
27ac1524
MT
314 def render(self, pages, link_revision=False, show_breadcrumbs=True,
315 show_author=True, show_changes=False):
7d699684 316 return self.render_string("wiki/modules/list.html", link_revision=link_revision,
27ac1524
MT
317 pages=pages, show_breadcrumbs=show_breadcrumbs,
318 show_author=show_author, show_changes=show_changes)
f9db574a
MT
319
320
6ac7e934 321class WikiNavbarModule(ui_modules.UIModule):
40e481b9
MT
322 @property
323 def path(self):
324 """
325 Returns the path of the page (without any actions)
326 """
327 path = self.request.path.split("/")
328
329 if path and path[-1].startswith("_"):
330 path.pop()
331
332 return "/".join(path)
333
67573803 334 def render(self, suffix=None):
f2cfd873
MT
335 _ = self.locale.translate
336
40e481b9
MT
337 # Make the path
338 page = self.request.path.split("/")
67573803 339
40e481b9
MT
340 # Drop the action bit
341 if page and page[-1].startswith("_"):
342 page.pop()
343
344 page = "/".join(page)
345
346 breadcrumbs = self.backend.wiki.make_breadcrumbs(page)
347 title = self.backend.wiki.get_page_title(page)
348
349 if self.request.path.endswith("/_edit"):
350 suffix = _("Edit")
351 elif self.request.path.endswith("/_files"):
352 suffix = _("Files")
6ac7e934
MT
353
354 return self.render_string("wiki/modules/navbar.html",
40e481b9 355 breadcrumbs=breadcrumbs, page=page, page_title=title, suffix=suffix)