--- /dev/null
+#!/usr/bin/python3
+
+import logging
+import markdown2
+import re
+
+from . import misc
+from .decorators import *
+
+# Used to automatically link some things
+link_patterns = (
+ # Find bug reports
+ (re.compile(r"(?:#(\d+))", re.I), r"https://bugzilla.ipfire.org/show_bug.cgi?id=\1"),
+
+ # Email Addresses
+ (re.compile(r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)"), r"mailto:\1"),
+
+ # CVE Numbers
+ (re.compile(r"(?:CVE)[\s\-](\d{4}\-\d+)"), r"https://cve.mitre.org/cgi-bin/cvename.cgi?name=\1"),
+)
+
+class Wiki(misc.Object):
+ def _get_pages(self, query, *args):
+ res = self.db.query(query, *args)
+
+ for row in res:
+ yield Page(self.backend, row.id, data=row)
+
+ def get_page(self, page, revision=None):
+ page = Page.sanitise_page_name(page)
+ assert page
+
+ if revision:
+ res = self.db.get("SELECT * FROM wiki WHERE page = %s \
+ AND timestamp = %s", page, revision)
+ else:
+ res = self.db.get("SELECT * FROM wiki WHERE page = %s \
+ ORDER BY timestamp DESC LIMIT 1", page)
+
+ if res:
+ return Page(self.backend, res.id, data=res)
+
+ def get_recent_changes(self):
+ return self._get_pages("SELECT * FROM wiki \
+ WHERE timestamp >= NOW() - INTERVAL '4 weeks' ORDER BY timestamp DESC")
+
+ def create_page(self, page, author, markdown):
+ page = Page.sanitise_page_name(page)
+
+ res = self.db.get("INSERT INTO wiki(page, author_id, markdown) \
+ VALUES(%s, %s, %s) RETURNING id", page, author.id, markdown)
+
+ if res:
+ return self.get_page_by_id(res.id)
+
+ def delete_page(self, page, author):
+ # Do nothing if the page does not exist
+ if not self.get_page(page):
+ return
+
+ # Just creates a blank last version of the page
+ self.create_page(page, author, None)
+
+ @staticmethod
+ def _split_url(url):
+ parts = list(e for e in url.split("/") if e)
+
+ num_parts = len(parts)
+ for i in range(num_parts):
+ yield "/".join(parts[:i])
+
+ def make_breadcrumbs(self, url):
+ for part in self._split_url(url):
+ title = self.get_page_title(part, os.path.basename(part))
+
+ yield ("/%s" % part, title)
+
+
+class Page(misc.Object):
+ def init(self, id, data=None):
+ self.id = id
+ self.data = data
+
+ def __lt__(self, other):
+ if isinstance(other, self.__class__):
+ if self.page == other.page:
+ return self.timestamp < other.timestamp
+
+ return self.page < other.page
+
+ @staticmethod
+ def sanitise_page_name(page):
+ if not page:
+ return "/"
+
+ # Make sure that the page name does NOT end with a /
+ if page.endswith("/"):
+ page = page[:-1]
+
+ # Make sure the page name starts with a /
+ if not page.startswith("/"):
+ page = "/%s" % page
+
+ # Remove any double slashes
+ page = page.replace("//", "/")
+
+ return page
+
+ @property
+ def url(self):
+ return "/%s" % self.page
+
+ @property
+ def page(self):
+ return self.data.page
+
+ @property
+ def title(self):
+ return self._title or self.page[1:]
+
+ @property
+ def _title(self):
+ if not self.markdown:
+ return
+
+ # Find first H1 headline in markdown
+ markdown = self.markdown.splitlines()
+
+ m = re.match(r"^# (.*)( #)?$", markdown[0])
+ if m:
+ return m.group(1)
+
+ def _render(self, text):
+ logging.debug("Rendering %s" % self)
+
+ return markdown2.markdown(text, link_patterns=link_patterns,
+ extras=["footnotes", "link-patterns", "wiki-tables"])
+
+ @property
+ def markdown(self):
+ return self.data.markdown
+
+ @property
+ def html(self):
+ return self.data.html or self._render(self.markdown)
+
+ @property
+ def timestamp(self):
+ return self.data.timestamp
+
+ def was_deleted(self):
+ return self.markdown is None
+
+ @lazy_property
+ def breadcrumbs(self):
+ return self.backend.wiki.make_breadcrumbs(self.page)
+
+ def get_latest_revision(self):
+ return self.backend.wiki.get_page(self.page)
from . import nopaste
from . import people
from . import ui_modules
+from . import wiki
class Application(tornado.web.Application):
def __init__(self, config, **kwargs):
(r"/users/(\w+)/sip", people.SIPHandler),
] + authentication_handlers)
+ # wiki.ipfire.org
+ self.add_handlers(r"wiki(\.dev)?\.ipfire\.org",
+ authentication_handlers + [
+
+ # Deliver static files (CSS, etc.)
+ #(r"/(static/.*)", tornado.web.StaticFileHandler),
+
+ (r"/search", wiki.SearchHandler),
+ (r"([A-Za-z0-9\-_\/]+)?", wiki.PageHandler),
+ ])
+
# ipfire.org
self.add_handlers(r"ipfire\.org", [
(r".*", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org" })
--- /dev/null
+#!/usr/bin/python3
+
+import tornado.web
+
+from . import auth
+from . import base
+
+class PageHandler(auth.CacheMixin, base.BaseHandler):
+ @tornado.web.removeslash
+ def get(self, page):
+ page = self.backend.wiki.get_page(page)
+
+ # If the page does not exist, we send 404
+ if not page or page.was_deleted():
+ raise tornado.web.HTTPError(404)
+
+ # Fetch the latest revision
+ latest_revision = page.get_latest_revision()
+
+ # Render page
+ self.render("wiki/page.html", page=page, latest_revision=latest_revision)
+
+ @tornado.web.authenticated
+ def post(self, page):
+ content = self.get_argument("content", None)
+
+ # Delete the page if content is empty
+ if not content:
+ with self.db.transaction():
+ self.backend.wiki.delete_page(page, self.current_user)
+
+ return self.redirect("/")
+
+ # Create a new page in the database
+ page = self.backend.wiki.create_page(page, self.current_user, content)
+
+ # Redirect
+ self.redirect(page.url)
+
+
+class SearchHandler(auth.CacheMixin, base.BaseHandler):
+ @base.blacklisted
+ def get(self):
+ q = self.get_argument("q")
+
+ pages = self.backend.wiki.search(q, limit=50)
+ if not pages:
+ raise tornado.web.HTTPError(404, "Nothing found")
+
+ self.render("wiki/search-results.html", q=q, pages=pages)