Compare commits

..

14 Commits

Author SHA1 Message Date
d3bd5f79cc HandlerFeedGenerator: Don't escape title again 2022-08-20 12:57:54 +02:00
995a980d49 HandlerPageEdit: Add 'frompage' GET parameter to use a page as a template 2022-08-20 12:41:30 +02:00
2ee760d9ca submodules: cpp-httplib: Update 2022-08-20 12:31:15 +02:00
ffeea8cfd1 submodules: exile.h: Update 2022-08-20 12:31:15 +02:00
a81963181a RevisionDaoSqlite: Fix cases where we got pageid instead of the page name 2022-08-20 12:31:15 +02:00
d18c0669ce handlers: HandlerPageEdit: Use RevisionRenderer 2022-08-20 12:31:15 +02:00
ecd45a61c8 HandlerPageView: Use RevisionRenderer 2022-08-20 12:30:47 +02:00
2b1c3c71b7 HandlerFeedGenerator: Use RevisionRenderer 2022-08-20 12:30:47 +02:00
a1042720a7 Add RevisionRenderer 2022-08-20 12:30:47 +02:00
6dbe8d34dc Add DynamicContentFactory 2022-08-20 10:24:23 +02:00
51b259f385 HandlerPageView: Set 'pagetitle' dynamic variable 2022-08-17 22:41:15 +02:00
0cad11004f HandlerPageView: Drop partial caches
Anonymous access is the common case, this is already cached.

For other cases this logic cannot be justified as there
is hardly a practical gain for that extra complexity.
2022-08-17 21:57:19 +02:00
2102cf4e6b Add [cmd:allowinclude]
Pages must specify whether they want to be included or not.

Otherwise there too many surprises possibe, enforcing access
restrictions will get more complicated
2022-08-17 21:54:34 +02:00
86890660f4 HandlerPageView: Set 'createdon' dynamic variable 2022-08-17 19:35:52 +02:00
11 changed files with 171 additions and 88 deletions

View File

@ -129,9 +129,8 @@ std::optional<Revision> RevisionDaoSqlite::getCurrentForPage(std::string pagenam
try try
{ {
auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, " auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, "
"strftime('%s',creationtime), page, revisionid FROM revision WHERE page = (SELECT id FROM " "strftime('%s',creationtime), page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND page.lastrevision = revision.revisionid";
"page WHERE name = ? ) AND revisionid = (SELECT lastrevision FROM page WHERE name = ?)"; query << pagename;
query << pagename << pagename;
query >> query >>
std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision); std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision);
} }
@ -155,7 +154,7 @@ std::optional<Revision> RevisionDaoSqlite::getRevisionForPage(std::string pagena
auto query = auto query =
*db *db
<< "SELECT (SELECT username FROM user WHERE id = author), comment, content, strftime('%s',creationtime), " << "SELECT (SELECT username FROM user WHERE id = author), comment, content, strftime('%s',creationtime), "
"page, revisionid FROM revision WHERE page = (SELECT id FROM page WHERE name = ? ) AND revisionid = ?"; "page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND revisionid = ? ";
query << pagename << revision; query << pagename << revision;
query >> query >>
std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision); std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision);

View File

@ -0,0 +1,28 @@
#ifndef DYNAMICCONTENTFACTORY_H
#define DYNAMICCONTENTFACTORY_H
#include "dynamiccontent.h"
class DynamicContentFactory
{
private:
Template *templ;
Database *db;
UrlProvider *urlProvider;
public:
DynamicContentFactory(Template &templ, Database &db, UrlProvider &urlProvider)
{
this->templ = &templ;
this->db = &db;
this->urlProvider = &urlProvider;
}
template <class T> inline std::shared_ptr<T> createDynamicContent()
{
return std::make_shared<T>(*this->templ, *this->db, *this->urlProvider);
}
};
#endif // DYNAMICCONTENTFACTORY_H_INCLUDED

View File

@ -5,8 +5,12 @@ std::string DynamicContentIncludePage::render()
auto revisionDao = this->database->createRevisionDao(); auto revisionDao = this->database->createRevisionDao();
auto rev = revisionDao->getCurrentForPage(this->argument); auto rev = revisionDao->getCurrentForPage(this->argument);
if(rev) if(rev)
{
/* Quite dirty */
if(rev->content.find("[cmd:allowinclude]1[") != std::string::npos)
{ {
return rev->content; return rev->content;
} }
}
return {}; return {};
} }

View File

@ -1,5 +1,6 @@
#include "handlerfeedgenerator.h" #include "handlerfeedgenerator.h"
#include "../parser.h" #include "../parser.h"
#include "../revisionrenderer.h"
std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetchEntries( std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetchEntries(
std::vector<std::string> categories) std::vector<std::string> categories)
{ {
@ -67,6 +68,8 @@ std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGene
subtitle = "All pages"; subtitle = "All pages";
} }
RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider };
for(const EntryRevisionPair &entry : entries) for(const EntryRevisionPair &entry : entries)
{ {
const Page &page = entry.first; const Page &page = entry.first;
@ -82,13 +85,12 @@ std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGene
std::string entryurl = std::string entryurl =
this->urlProvider->combine({this->urlProvider->rootUrl(), this->urlProvider->page(page.name)}); this->urlProvider->combine({this->urlProvider->rootUrl(), this->urlProvider->page(page.name)});
TemplatePage atomentry = this->templ->getPage("feeds/atomentry"); TemplatePage atomentry = this->templ->getPage("feeds/atomentry");
atomentry.setVar("entrytitle", utils::html_xss(page.title)); atomentry.setVar("entrytitle", page.title);
atomentry.setVar("entryurl", utils::html_xss(entryurl)); atomentry.setVar("entryurl", utils::html_xss(entryurl));
atomentry.setVar("entryid", utils::html_xss(entryurl)); atomentry.setVar("entryid", utils::html_xss(entryurl));
atomentry.setVar("entrypublished", entryPublished); atomentry.setVar("entrypublished", entryPublished);
atomentry.setVar("entryupdated", entryUpdated); atomentry.setVar("entryupdated", entryUpdated);
Parser parser; atomentry.setVar("entrycontent", utils::html_xss(revisionRenderer.renderContent(current, page.title)));
atomentry.setVar("entrycontent", utils::html_xss(parser.parse(*pageDao, *this->urlProvider, current.content)));
stream << atomentry.render(); stream << atomentry.render();
} }
stream << atomfooter; stream << atomfooter;

View File

@ -23,6 +23,7 @@ SOFTWARE.
#include "../request.h" #include "../request.h"
#include "../parser.h" #include "../parser.h"
#include "../revisionrenderer.h"
bool HandlerPageEdit::canAccess([[maybe_unused]] std::string page) bool HandlerPageEdit::canAccess([[maybe_unused]] std::string page)
{ {
return effectivePermissions(page).canEdit(); return effectivePermissions(page).canEdit();
@ -41,7 +42,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
return errorResponse("No permission", "You don't have permission to create new pages"); return errorResponse("No permission", "You don't have permission to create new pages");
} }
auto revisiondao = this->database->createRevisionDao(); auto revisiondao = this->database->createRevisionDao();
auto revision = this->database->createRevisionDao()->getCurrentForPage(pagename); auto revision = revisiondao->getCurrentForPage(pagename);
std::string body; std::string body;
unsigned int current_revision = 0; unsigned int current_revision = 0;
@ -50,6 +51,15 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
body = revision->content; body = revision->content;
current_revision = revision->revision; current_revision = revision->revision;
} }
std::string from = r.get("frompage");
if(!from.empty())
{
if(!effectivePermissions(from).canRead())
{
return this->errorResponse("Permission denied", "No access permissions, so you can't use this page as a template");
}
body = revisiondao->getCurrentForPage(from)->content;
}
if(r.getRequestMethod() == "POST") if(r.getRequestMethod() == "POST")
{ {
if(r.post("do") == "submit") if(r.post("do") == "submit")
@ -115,12 +125,16 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
{ {
std::string newContent = r.post("content"); std::string newContent = r.post("content");
Parser parser; Parser parser;
std::string title = parser.extractCommand("pagetitle", newContent);
TemplatePage templatePage = this->templ->getPage("page_creation_preview"); TemplatePage templatePage = this->templ->getPage("page_creation_preview");
templatePage.setVar("actionurl", urlProvider->editPage(pagename)); templatePage.setVar("actionurl", urlProvider->editPage(pagename));
templatePage.setVar("preview_content", parser.parse(pageDao, *this->urlProvider, newContent));
RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider };
templatePage.setVar("preview_content", revisionRenderer.renderContent(newContent));
templatePage.setVar("content", newContent); templatePage.setVar("content", newContent);
setPageVars(templatePage, pagename); setPageVars(templatePage, pagename);
templatePage.setVar("title", createPageTitle("Preview: " + pagename)); templatePage.setVar("title", createPageTitle("Preview: " + title));
templatePage.setVar("comment", r.post("comment")); templatePage.setVar("comment", r.post("comment"));
Response response; Response response;
response.setBody(templatePage.render()); response.setBody(templatePage.render());

View File

@ -27,6 +27,7 @@ SOFTWARE.
#include "../dynamic/dynamiccontentincludepage.h" #include "../dynamic/dynamiccontentincludepage.h"
#include "../dynamic/dynamiccontentsetvar.h" #include "../dynamic/dynamiccontentsetvar.h"
#include "../dynamic/dynamiccontentgetvar.h" #include "../dynamic/dynamiccontentgetvar.h"
#include "../revisionrenderer.h"
bool HandlerPageView::canAccess(std::string page) bool HandlerPageView::canAccess(std::string page)
{ {
@ -90,19 +91,20 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
std::optional<Revision> revision; std::optional<Revision> revision;
std::string templatepartname; std::string templatepartname;
auto revisionDao = this->database->createRevisionDao();
try try
{ {
if(revisionid > 0) if(revisionid > 0)
{ {
if(!effectivePermissions(pagename).canSeePageHistory()) if(!effectivePermissions(pagename).canSeePageHistory())
{ {
auto current = this->database->createRevisionDao()->getCurrentForPage(pagename); auto current = revisionDao->getCurrentForPage(pagename);
if(current && current->revision > revisionid) if(current && current->revision > revisionid)
{ {
return errorResponse("Error", "You are not allowed to view older revisions of this page"); return errorResponse("Error", "You are not allowed to view older revisions of this page");
} }
} }
revision = this->database->createRevisionDao()->getRevisionForPage(pagename, revisionid); revision = revisionDao->getRevisionForPage(pagename, revisionid);
if(!revision) if(!revision)
{ {
return errorResponse("Revision not found", "No such revision found"); return errorResponse("Revision not found", "No such revision found");
@ -122,7 +124,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
return r; return r;
} }
} }
revision = this->database->createRevisionDao()->getCurrentForPage(pagename); revision = revisionDao->getCurrentForPage(pagename);
templatepartname = "page_view"; templatepartname = "page_view";
} }
} }
@ -132,82 +134,20 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
return errorResponse("Database error", "While trying to fetch revision, a database error occured"); return errorResponse("Database error", "While trying to fetch revision, a database error occured");
} }
TemplatePage page = this->templ->getPage(templatepartname);
Parser parser; Parser parser;
Response result; Response result;
result.setStatus(200); result.setStatus(200);
std::string indexcontent;
std::string parsedcontent;
std::map<std::string, std::string> dynamicVarsMap; RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider };
std::function<std::string(std::string_view, std::string_view)> dynamicParseCallback =
[&](std::string_view key, std::string_view value) -> std::string
{
if(key == "dynamic:postlist")
{
std::shared_ptr<DynamicContentPostList> postlist = createDynamic<DynamicContentPostList>();
postlist->setArgument(std::string(value));
return postlist->render();
}
if(key == "dynamic:includepage")
{
if((effectivePermissions(std::string(value)).canRead()))
{
std::shared_ptr<DynamicContentIncludePage> includePage = createDynamic<DynamicContentIncludePage>();
includePage->setArgument(std::string(value));
return parser.parseDynamics(includePage->render(), dynamicParseCallback);
}
}
if(key == "dynamic:setvar")
{
std::shared_ptr<DynamicContentSetVar> setVar = createDynamic<DynamicContentSetVar>();
setVar->setMap(dynamicVarsMap);
setVar->setArgument(std::string(value));
return setVar->render();
}
if(key == "dynamic:getvar")
{
std::shared_ptr<DynamicContentGetVar> getVar = createDynamic<DynamicContentGetVar>();
getVar->setMap(dynamicVarsMap);
getVar->setArgument(std::string(value));
return getVar->render();
}
return std::string{};
};
std::string resolvedContent = parser.parseDynamics(revision->content, dynamicParseCallback);
if(revisionid > 0)
{
indexcontent = createIndexContent(parser, resolvedContent);
parsedcontent = parser.parse(pageDao, *this->urlProvider, resolvedContent);
}
else
{
std::string cachekeyindexcontent = "page:indexcontent:" + pagename;
std::string cachekeyparsedcontent = "page:parsedcontent:" + pagename;
auto cachedindexcontent = this->cache->get(cachekeyindexcontent);
auto cachedparsedcontent = this->cache->get(cachekeyparsedcontent);
if(cachedindexcontent)
{
indexcontent = *cachedindexcontent;
}
else
{
indexcontent = createIndexContent(parser, resolvedContent);
this->cache->put(cachekeyindexcontent, indexcontent);
}
if(cachedparsedcontent)
{
parsedcontent = *cachedparsedcontent;
}
else
{
parsedcontent = parser.parse(pageDao, *this->urlProvider, resolvedContent);
this->cache->put(cachekeyparsedcontent, parsedcontent);
}
}
std::string revisionstr = std::to_string(revision->revision);
std::string customtitle = parser.extractCommand("pagetitle", revision->content); std::string customtitle = parser.extractCommand("pagetitle", revision->content);
std::string parsedcontent = revisionRenderer.renderContent(revision.value(), customtitle);
/* TODO: Dynamic includes not considered, probably fine in practise */
std::string indexcontent = createIndexContent(parser, revision->content);
std::string revisionstr = std::to_string(revision->revision);
TemplatePage page = this->templ->getPage(templatepartname);
page.setVar("content", parsedcontent); page.setVar("content", parsedcontent);
page.setVar("index", indexcontent); page.setVar("index", indexcontent);
page.setVar("editedby", revision->author); page.setVar("editedby", revision->author);

View File

@ -148,7 +148,7 @@ std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const s
std::string result; std::string result;
// we don't care about commands, but we nevertheless replace them with empty strings // we don't care about commands, but we nevertheless replace them with empty strings
std::regex tagfinder( std::regex tagfinder(
R"(\[(b|i|u|li||ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:rename|cmd:redirect|cmd:pagetitle|category|dynamic:postlist|dynamic:includepage|dynamic:getvar|dynamic:setvar)*?\]((\s|\S)*?)\[/\1])"); R"(\[(b|i|u|li||ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:rename|cmd:redirect|cmd:pagetitle|cmd:allowinclude|category|dynamic:postlist|dynamic:includepage|dynamic:getvar|dynamic:setvar)*?\]((\s|\S)*?)\[/\1])");
result = utils::regex_callback_replacer( result = utils::regex_callback_replacer(
tagfinder, content, tagfinder, content,
[&](std::smatch &match) [&](std::smatch &match)

67
revisionrenderer.cpp Normal file
View File

@ -0,0 +1,67 @@
#include "revisionrenderer.h"
#include "templatepage.h"
#include "dynamic/dynamiccontentpostlist.h"
#include "dynamic/dynamiccontentincludepage.h"
#include "dynamic/dynamiccontentgetvar.h"
#include "dynamic/dynamiccontentsetvar.h"
#include "parser.h"
#include "htmllink.h"
std::string RevisionRenderer::dynamicCallback(std::string_view key, std::string_view value)
{
if(key == "dynamic:postlist")
{
auto postlist = this->dynamicContentFactory.createDynamicContent<DynamicContentPostList>();
postlist->setArgument(std::string(value));
return postlist->render();
}
if(key == "dynamic:includepage")
{
auto includePage = this->dynamicContentFactory.createDynamicContent<DynamicContentIncludePage>();
includePage->setArgument(std::string(value));
return parser.parseDynamics(includePage->render(), std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2));
}
if(key == "dynamic:setvar")
{
auto setVar = this->dynamicContentFactory.createDynamicContent<DynamicContentSetVar>();
setVar->setMap(dynamicVarsMap);
setVar->setArgument(std::string(value));
return setVar->render();
}
if(key == "dynamic:getvar")
{
auto getVar = this->dynamicContentFactory.createDynamicContent<DynamicContentGetVar>();
getVar->setMap(dynamicVarsMap);
getVar->setArgument(std::string(value));
return getVar->render();
}
return std::string{};
}
std::string RevisionRenderer::renderContent(std::string content)
{
dynamicVarsMap["pagetitle"] = parser.extractCommand("pagetitle", content);
dynamicVarsMap["createdon"] = utils::toISODate(time(NULL));
std::string resolvedContent = parser.parseDynamics(content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2));
return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent);
}
std::string RevisionRenderer::renderContent(const Revision &r, std::string_view customTitle)
{
auto revisionDao = this->db->createRevisionDao();
auto firstRevision = revisionDao->getRevisionForPage(r.page, 1);
if(!firstRevision)
{
throw std::runtime_error("Could not get first revision for page, which is odd. Solar flares?");
}
dynamicVarsMap["createdon"] = utils::toISODate(firstRevision.value().timestamp);
dynamicVarsMap["pagetitle"] = customTitle;
std::string resolvedContent = parser.parseDynamics(r.content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2));
return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent);
}

29
revisionrenderer.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef REVISIONRENDERER_H
#define REVISIONRENDERER_H
#include "revision.h"
#include "templatepage.h"
#include "dynamic/dynamiccontentfactory.h"
#include "iparser.h"
#include "parser.h"
class RevisionRenderer
{
private:
DynamicContentFactory dynamicContentFactory;
Database *db;
UrlProvider *urlProvider;
std::map<std::string, std::string> dynamicVarsMap;
std::string dynamicCallback(std::string_view key, std::string_view value);
Parser parser;
public:
RevisionRenderer(Template &templ, Database &db, UrlProvider &urlProvider) :dynamicContentFactory(templ, db, urlProvider)
{
this->db = &db;
this->urlProvider = &urlProvider;
}
std::string renderContent(std::string content);
std::string renderContent(const Revision &r, std::string_view customTitle);
};
#endif // REVISIONRENDERER_H

@ -1 +1 @@
Subproject commit b324921c1aeff2976544128e4bb2a0979a4aa595 Subproject commit d92c31446687cfa336a6332b1015b4fe289fbdec

@ -1 +1 @@
Subproject commit f2ca26010a2bb6d9e270d6ade2e8789c02ac3b31 Subproject commit e711a1d53a9210f8f562f774901e5e044d20e67a