thalassa/cms/database.cpp
2026-03-19 06:23:52 +05:00

1917 lines
55 KiB
C++

#include <inifile/inifile.hpp>
#include <scriptpp/conffile.hpp>
#include <scriptpp/scrmsg.hpp>
#include <scriptpp/cmd.hpp>
#include <stdio.h>
#include <time.h> // XXX for ctime -- remove once ctime is removed
enum { too_long_for_filename = 128 };
#include "dbsubst.hpp"
#include "filters.hpp"
#include "fpublish.hpp" // for enum file_publish_methods
#include "arrindex.hpp"
#include "dbforum.hpp"
#include "forumgen.hpp"
#include "database.hpp"
static bool svec_has_elem(const ScriptVector &v, const ScriptVariable &el)
{
int i;
for(i = 0; i < v.Length(); i++) {
if(v[i] == el)
return true;
}
return false;
}
bool ListItemData::HasTag(const ScriptVariable &t) const
{
return svec_has_elem(tags, t);
}
bool ListItemData::HasFlag(const ScriptVariable &f) const
{
return svec_has_elem(flags, f);
}
bool CommentData::HasFlag(const ScriptVariable &f) const
{
return svec_has_elem(flags, f);
}
/* We need this structure for the ``set''-based lists only */
struct SetListIndex {
ScriptVariable set_id, tag;
ScriptVector items;
SetListIndex *next;
};
/////////////////////////////////
// BlockGroups must be defined before the Database's destructor
struct BlockGroupData {
ScriptVariable id;
ScriptVariable begin, end, block_templ;
struct BlockData *first;
BlockGroupData *next_grp;
BlockGroupData(const char *i, const char *b, const char *e,
const char *bt)
: id(i), begin(b), end(e), block_templ(bt), first(0), next_grp(0) {}
~BlockGroupData();
};
struct BlockData {
ScriptVariable id;
int weight;
ScriptVariable tag, title, body;
BlockData *next;
BlockData(const char *i,
int w, const char *tg, const char *tit, const char *b)
: id(i), weight(w), tag(tg), title(tit), body(b), next(0)
{}
};
BlockGroupData::~BlockGroupData()
{
if(next_grp)
delete next_grp;
while(first) {
BlockData *tmp = first;
first = first->next;
delete tmp;
}
}
/////////////////////////////////
Database::Database()
: subst(0), filtmaker(0), current_list_data(0), current_list_item_data(0),
first_block_group(0), first_set_list(0)
{
inifile = new IniFileParser;
// subst object is to be created after loading the inifile,
// because some of the "variables" are initialized by calling
// methods of the database, so these methods must already
// be prepared to return their respective values
}
Database::~Database()
{
if(subst)
delete subst;
delete inifile;
if(filtmaker)
delete filtmaker;
while(first_set_list) {
SetListIndex *tmp = first_set_list;
first_set_list = first_set_list->next;
delete tmp;
}
if(first_block_group)
delete first_block_group;
}
void Database::SetOptSelector(const ScriptVariable &whatfor,
const ScriptVariable &sel)
{
if(whatfor.IsInvalid() || whatfor == "") {
default_opt_selector = sel;
return;
}
int i;
for(i = 0; i+1 < opt_selectors.Length(); i += 2) {
if(opt_selectors[i] == whatfor) {
opt_selectors[i+1] = sel;
return;
}
}
opt_selectors.AddItem(whatfor);
opt_selectors.AddItem(sel);
}
bool Database::Load(const char *filename)
{
bool ok = inifile->Load(filename);
if(subst)
delete subst;
subst = new DatabaseSubstitution(this);
if(default_opt_selector.IsValid())
inifile->SetTextParameter("general", 0, "opt_selector",
default_opt_selector.c_str());
return ok;
}
void Database::MakeErrorMessage(class ScriptVariable &msg) const
{
msg = "";
int el = inifile->GetLastErrorLine();
if(el != -1)
msg += ScriptNumber(el) + ": ";
else
msg += " ";
msg += inifile->GetLastErrorDescription();
}
bool Database::GetExtraFiles(ScriptVector &ef)
{
const char *tmp = inifile->GetTextParameter("general", 0, "inifiles", 0);
if(!tmp)
return false;
ScriptVector v((*subst)(tmp), " ,\t\r\n");
if(v.Length() < 1)
return false;
ef = v;
inifile->SetTextParameter("general", 0, "inifiles", "");
return true;
}
ScriptVariable Database::GetHtmlSnippet(const ScriptVariable &name) const
{
return inifile->GetTextParameter("html", 0, name.c_str(), "");
}
ScriptVariable Database::BuildGenericPart(const ScriptVariable &templ) const
{
return (*subst)(templ);
}
#if 0
ScriptVariable Database::GetSourcePrefix() const
{
if(source_prefix.IsInvalid())
const_cast<Database*>(this)->source_prefix =
inifile->GetTextParameter("general", 0, "srcdir", "");
return source_prefix;
}
#endif
ScriptVariable Database::GetFilePrefix() const
{
if(file_prefix.IsInvalid()) {
ScriptVariable fp =
inifile->GetTextParameter("general", 0, "rootdir", "html");
fp = (*subst)(fp);
if(fp.Range(-1,1)[0] != '/')
fp += '/';
const_cast<Database*>(this)->file_prefix = fp;
}
return file_prefix;
}
ScriptVariable Database::GetSpoolDir() const
{
return (*subst)(inifile->
GetTextParameter("general", 0, "spooldir", "_spool"));
}
void Database::SetFilePrefix(ScriptVariable s)
{
file_prefix = (s.Range(-1,1)[0] == '/') ? s : s + '/';
}
ScriptVariable Database::GetBaseUri() const
{
if(base_uri.IsInvalid()) {
const char *tmp =
inifile->GetTextParameter("general", 0, "base_uri", "/");
if(tmp)
const_cast<Database*>(this)->base_uri = (*subst)(tmp);
}
return base_uri;
}
ScriptVariable Database::GetBaseUrl() const
{
return (*subst)(inifile->GetTextParameter("general", 0, "base_url", ""));
}
ScriptVariable Database::GetOption(const ScriptVariable &section,
const ScriptVariable &name) const
{
const char *tmp;
int sel_idx = -1;
int i;
for(i = 0; i+1 < opt_selectors.Length(); i += 2) {
if(opt_selectors[i] == section) {
sel_idx = i + 1;
break;
}
}
if(sel_idx == -1) {
tmp = inifile->GetModifiedTextParameter("general", 0,
"opt_selector", section.c_str(), "");
ScriptVector &opsel = const_cast<Database*>(this)->opt_selectors;
opsel.AddItem(section);
opsel.AddItem(tmp);
sel_idx = opsel.Length() - 1; // the last, which is just added
}
// NB: now sel_idx is valid, as well as opt_selectors[sel_idx]
const char *sstr = section.c_str();
const char *nstr = name.c_str();
const char *selstr = opt_selectors[sel_idx].c_str();
tmp = selstr && *selstr ?
inifile->GetModifiedTextParameter("options", sstr, nstr, selstr, 0)
:
inifile->GetTextParameter("options", sstr, nstr, 0);
if(!tmp)
return ScriptVariableInv();
return tmp;
}
int Database::GetGenfiles(class ScriptVector &names) const
{
return GetSectionNames("genfile", names);
}
int Database::GetPages(class ScriptVector &names) const
{
return GetSectionNames("page", names);
}
int Database::GetLists(class ScriptVector &names) const
{
return GetSectionNames("list", names);
}
int Database::GetSets(class ScriptVector &names) const
{
return GetSectionNames("pageset", names);
}
int Database::GetCollections(class ScriptVector &names) const
{
return GetSectionNames("collection", names);
}
int Database::GetBinaries(class ScriptVector &names) const
{
return GetSectionNames("binary", names);
}
int Database::GetAliasSections(ScriptVector &names) const
{
return GetSectionNames("aliases", names);
}
int Database::GetSectionNames(const char *gn, class ScriptVector &names) const
{
int cnt = inifile->GetSectionCount(gn);
int i;
names.Clear();
for(i = 0; i < cnt; i++)
names[i] = inifile->GetSectionName(gn, i);
return cnt;
}
static int extract_chmod(IniFileParser *ini, const char *grp, const char *id)
{
const char *tmp = ini->GetTextParameter(grp, id, "chmod", 0);
if(!tmp)
return 0;
long mode;
bool ok = ScriptVariable(tmp).GetLong(mode, 8);
return ok ? mode : 0;
}
bool Database::BuildGenericFile(const ScriptVariable &id,
ScriptVariable &path, int &chmod_value,
ScriptVariable &cont) const
{
const char *nm =
inifile->GetTextParameter("genfile", id.c_str(), "path", 0);
path = nm ? (*subst)(nm) : id;
chmod_value = extract_chmod(inifile, "genfile", id.c_str());
const char *cn =
inifile->GetTextParameter("genfile", id.c_str(), "content", 0);
if(!cn)
return false;
cont = (*subst)(cn);
return true;
}
/* pages */
ScriptVariable Database::GetPageFilename(const ScriptVariable &name) const
{
const char *path =
inifile->GetTextParameter("page", name.c_str(), "path", 0);
if(path && *path) {
ScriptVariable pt(path);
pt = (*subst)(pt);
pt.Trim();
if(pt == "" || pt == "." || pt == "-")
return ScriptVariableInv();
return GetFilePrefix() + pt;
}
return GetFilePrefix() + name;
}
int Database::GetPageChmod(const ScriptVariable &pg_id) const
{
return extract_chmod(inifile, "page", pg_id.c_str());
}
ScriptVariable Database::BuildPage(const ScriptVariable &name) const
{
const char *nc = name.c_str();
const char *tmpl = inifile->GetTextParameter("page", nc, "template", 0);
if(!tmpl) {
const char *res = inifile->GetTextParameter("page", nc, "body", 0);
if(!res)
return ScriptVariableInv();
return (*subst)(res);
}
return ExpandTemplate(tmpl, "page", nc);
}
static bool boolean_value_from_string(const char *str)
{
return str && ScriptVariable(str).Trim().Tolower() == "yes";
}
static bool boolean_value_from_scrvar(ScriptVariable s)
{
return s.IsValid() && s.Trim().Tolower() == "yes";
}
/* lists */
static list_source_type srctype_by_name(ScriptVariable s)
{
s.Trim();
s.Tolower();
if(s == "ini")
return listsrc_ini;
if(s == "set")
return listsrc_set;
return listsrc_unknown;
}
static void reverse_script_vector(ScriptVector &v)
{
int i, j;
for(i = 0, j = v.Length()-1; i < j; i++, j--) {
ScriptVariable t = v[i];
v[i] = v[j];
v[j] = t;
}
}
bool Database::IsListEmbedded(const ScriptVariable &list_id) const
{
const char *lsid = list_id.c_str();
const char *tmp = inifile->GetTextParameter("list", lsid, "embedded", 0);
return boolean_value_from_string(tmp);
}
bool Database::GetListData(const ScriptVariable &list_id, ListData &data) const
{
data.id = list_id;
data.embedded = IsListEmbedded(list_id);
const char *lsid = list_id.c_str();
const char *srcstr = inifile->GetTextParameter("list", lsid, "source", 0);
if(srcstr) {
ScriptWordVector v(srcstr);
data.srctype = srctype_by_name(v[0]);
if(data.srctype == listsrc_unknown)
return false;
data.srcname = v.Length() < 2 ? list_id : v[1];
data.tag = v.Length() < 3 ? "" : v[2];
if(data.srctype == listsrc_set &&
(data.tag.IsInvalid() || data.tag == ""))
{
return false;
}
} else {
#if 0
data.srctype = listsrc_ini;
data.srcname = list_id;
#else
return false;
#endif
}
const char *tmp;
tmp = inifile->GetTextParameter("list", lsid, "reverse", 0);
data.reverse = boolean_value_from_string(tmp);
tmp = inifile->GetTextParameter("list", lsid, "reverse_source", 0);
bool reverse_source = boolean_value_from_string(tmp);
data.list_header =
inifile->GetTextParameter("list", lsid, "list_header", "");
data.list_footer =
inifile->GetTextParameter("list", lsid, "list_footer", "");
data.list_item_templ =
inifile->GetTextParameter("list", lsid, "list_item_template", "");
if(data.embedded) {
data.items_per_listpage = 0;
data.main_listpage_name.Invalidate();
data.listpage_name_templ.Invalidate();
} else {
data.items_per_listpage =
inifile->GetIntegerParameter("list",lsid, "items_per_listpage", 0);
tmp = inifile->GetTextParameter("list", lsid, "main_listpage_name", 0);
data.main_listpage_name = tmp ? ScriptVariable(tmp) : list_id;
tmp = inifile->GetTextParameter("list",lsid, "listpage_name_templ", 0);
data.listpage_name_templ =
tmp ? ScriptVariable(tmp) : list_id + "_p%n%";
}
if(data.srctype == listsrc_ini) {
ScriptVariable auxp =
inifile->GetTextParameter("list", lsid, "aux_params", "");
data.aux_params.Clear();
data.aux_params = ScriptVector(auxp, " ,\t\r\n");
}
tmp = inifile->GetTextParameter("list", lsid, "pages", 0);
data.pages = boolean_value_from_string(tmp);
if(data.pages) {
data.itempage_templ =
inifile->GetTextParameter("list", lsid, "itempage_template", "");
data.itempage_tail_templ =
inifile->GetTextParameter("list",lsid,"itempage_tail_template","");
data.comments_conf =
inifile->GetTextParameter("list", lsid, "comments", "");
}
switch(data.srctype) {
case listsrc_ini:
GetSectionNames(data.srcname.c_str(), data.items);
break;
case listsrc_set:
ScanSetTagItems(data.srcname, data.tag, data.items);
break;
default:
return false;
}
if(reverse_source)
reverse_script_vector(data.items);
int last_only =
inifile->GetIntegerParameter("list", lsid, "last_items_only", 0);
if(last_only > 0 && data.items.Length() > last_only) {
data.items.Remove(0, data.items.Length() - last_only);
}
return true;
}
void Database::GetListNavigationInfo(const ScriptVariable &name,
int &items_per_page, ScriptVariable &set_id, ScriptVariable &tag) const
{
const char *lsid = name.c_str();
set_id = "";
tag = "";
const char *srcstr = inifile->GetTextParameter("list", lsid, "source", 0);
if(srcstr) {
ScriptWordVector v(srcstr);
if(v[0] == "set") {
set_id = v[1];
tag = v[2];
}
}
items_per_page =
inifile->GetIntegerParameter("list", lsid, "items_per_listpage", 0);
}
bool Database::GetSetListIndexInfo(const ScriptVariable &set_id,
const ScriptVariable &tag,
const ScriptVariable &item_id,
int &index,
ScriptVariable &prev,
ScriptVariable &next) const
{
SetListIndex *sli =
const_cast<Database*>(this)->ProvideSetListIndex(set_id, tag);
if(!sli)
return false;
int i;
int len = sli->items.Length();
for(i = 0; i < len; i++)
if(sli->items[i] == item_id) {
index = i + 1; // they are 1-based
prev = i > 0 ? sli->items[i-1] : ScriptVariableInv();
next = i < len-1 ? sli->items[i+1] : ScriptVariableInv();
return true;
}
return false;
}
// XXX this function should perhaps disappear!
// instead, the database should provide a method to generate textual date
static void fill_date_from_unixtime(ListItemData &data)
{
if(data.unixtime > 0) {
time_t tt = data.unixtime;
data.date = ScriptVariable(asctime(gmtime(&tt)));
data.date.Trim();
data.date += " UTC";
// XXX we need more flexibility here!
} else {
data.date =
ScriptVariable("NODATE[") + data.item_id + "]";
}
}
bool Database::GetListItemData(const ListData &lsdata, int idx,
ListItemData &data) const
{
const ScriptVariable &ls_name = lsdata.srcname;
const ScriptVariable &item_id = lsdata.items[idx];
ScriptVariableInv inval;
data.make_separate_directory = false;
// for ``ini-sourced'' lists the opposit isn't supported
data.item_id = item_id;
data.index = idx+1; // remember they are 1-based
data.prev_id = idx > 0 ? lsdata.items[idx-1] : inval;
data.next_id = idx+1 < lsdata.items.Length() ? lsdata.items[idx+1] : inval;
const char *lsc = ls_name.c_str();
const char *idc = item_id.c_str();
const char *utime_str = inifile->GetTextParameter(lsc, idc, "unixtime", 0);
if(!utime_str || !*utime_str) {
data.unixtime = 0;
} else {
long long t;
bool ok = ScriptVariable(utime_str).GetLongLong(t, 10);
data.unixtime = ok ? t : -1;
}
const char *date_str = inifile->GetTextParameter(lsc, idc, "date", 0);
if(date_str)
data.date = (*subst)(date_str);
else
fill_date_from_unixtime(data);
data.title = (*subst)(inifile->GetTextParameter(lsc, idc, "title", ""));
data.descr = (*subst)(inifile->GetTextParameter(lsc, idc, "descr", ""));
ScriptVariable tagsstr =
(*subst)(inifile->GetTextParameter(lsc, idc, "tags", ""));
data.tags.Clear();
data.tags = ScriptVector(tagsstr, ",", " \t\r\n");
data.comments = inifile->GetTextParameter(lsc, idc, "comments", "");
int i;
for(i = 0; i < lsdata.aux_params.Length(); i++) {
const char *ap =
inifile->GetTextParameter(lsc, idc,
lsdata.aux_params[i].c_str(), 0);
if(!ap)
continue;
data.aux_params.AddItem(lsdata.aux_params[i]);
data.aux_params.AddItem((*subst)(ap));
}
data.text = (*subst)(inifile->GetTextParameter(lsc, idc, "text", ""));
return true; // XXXXXXX well... is this really okay?
}
ScriptVariable
Database::GetListPgpath(const ScriptVariable &ls_id, int num) const
{
if(num < 1) {
const char *tmp =
inifile->GetTextParameter("list", ls_id.c_str(),
"main_listpage_name", 0);
if(tmp)
return (*subst)(tmp);
}
ScriptVariable res =
inifile->GetTextParameter("list", ls_id.c_str(),
"listpage_name_templ", "");
if(res == "") {
res = ls_id + "_p" + ScriptNumber(num) + ".html";
} else {
ScriptMacroprocessor sub_sub(subst);
add_index_macros(sub_sub, num);
res = sub_sub(res);
}
return res;
}
ScriptVariable
Database::GetListFilename(const ScriptVariable &ls_id, int num) const
{
return GetFilePrefix() + GetListPgpath(ls_id, num);
}
ScriptVariable
Database::GetListHref(const ScriptVariable &ls_id, int num) const
{
return GetBaseUri() + GetListPgpath(ls_id, num);
}
ScriptVariable Database::GetListItemFilename(const ScriptVariable &ls_id,
const ScriptVariable &pg_id,
int pg_idx) const
{
ScriptVariable tmp =
inifile->GetTextParameter("list", ls_id.c_str(), "itempage_name", "");
if(tmp == "")
tmp = ls_id + "_" + pg_id + "%_idx%.html";
ScriptMacroprocessor sub_sub(subst);
add_index_macros(sub_sub, pg_idx);
return GetFilePrefix() + sub_sub(tmp);
}
ScriptVariable Database::GetListItemCommentmapFname(
const ScriptVariable &ls_id, const ScriptVariable &pg_id) const
{
const char *tmp =
inifile->GetTextParameter("list", ls_id.c_str(), "commentmap", 0);
if(!tmp)
return ScriptVariableInv();
return GetFilePrefix() + (*subst)(tmp);
}
ScriptVariable Database::GetListItemHref(const ScriptVariable &ls_id,
const ScriptVariable &pg_id,
int pg_idx) const
{
ScriptVariable tmp = inifile->GetTextParameter("list", ls_id.c_str(),
"itempage_name", "");
ScriptMacroprocessor sub_sub(subst);
add_index_macros(sub_sub, pg_idx);
return GetBaseUri() + sub_sub(tmp);
}
// IMPORTANT: there must be a single object for substitutions inside the
// database, being created within the constructor
// It must be able to "know" and "forget" the ListData/ListItem structs
ScriptVariable Database::BuildListHead(const ListData &lsd) const
{
//SetListData(&lsd);
ScriptVariable res = (*subst)(lsd.list_header);
//ForgetListCmtData();
return res;
}
ScriptVariable Database::BuildListTail(const ListData &lsd) const
{
//SetListData(&lsd);
ScriptVariable res = (*subst)(lsd.list_footer);
//ForgetListCmtData();
return res;
}
ScriptVariable Database::BuildListItem(const ListData &lsd,
const ListItemData &itd) const
{
return BuildListItemPart(lsd, itd, lsd.list_item_templ);
}
ScriptVariable Database::BuildListItemPart(const ListData &lsd,
const ListItemData &itd,
const ScriptVariable &templ) const
{
//SetListData(&lsd, &itd);
ScriptVariable res = (*subst)(templ);
//ForgetListCmtData();
return res;
}
void Database::BuildListItemPage(const ListData &lsd, const ListItemData &itd,
ScriptVariable &main, ScriptVariable &tail,
ForumGenerator **fgp) const
{
main = BuildListItemPart(lsd, itd, lsd.itempage_templ);
tail = BuildListItemPart(lsd, itd, lsd.itempage_tail_templ);
*fgp = GetComments(lsd.comments_conf);
}
#if 0
void Database::FillArrayDataForList(const ListData &lsd, ArrayData &ad) const
{
int itemcnt = lsd.items.Length();
int perpage = lsd.items_per_listpage;
ad.item_count = itemcnt;
ad.items_per_page = perpage;
ad.page_count = perpage <= 0 ? 1 : (itemcnt ? (itemcnt-1)/perpage+1 : 0);
ad.reverse = lsd.reverse;
ad.href_array.Clear();
ScriptMacroprocessor sub_sub(subst);
ScriptVariable v_idx; // NB: no zero index is used here!
sub_sub.AddMacro(new ScriptMacroScrVar("idx", &v_idx));
sub_sub.AddMacro(new ScriptMacroScrVar("idx0", &v_idx));
sub_sub.AddMacro(new ScriptMacroScrVar("_idx", &v_idx));
int i;
for(i = 1; i <= ad.page_count; i++) {
v_idx = ScriptNumber(i);
ad.href_array[i] = GetBaseUri() + sub_sub(lsd.listpage_name_templ);
}
if(ad.reverse) {
ad.href_first = ScriptVariableInv();
ad.href_last = GetBaseUri() + lsd.main_listpage_name;
} else {
ad.href_first = GetBaseUri() + lsd.main_listpage_name;
ad.href_last = ScriptVariableInv();
}
ad.current_page = -1;
}
void Database::FillArrayDataForListItem(const ListData &lsd,
const ListItemData &itd,
ForumGenerator *fgp,
ArrayData &ad) const
{
int pgcnt;
bool reverse;
ad.href_array.Clear();
if(!fgp || !fgp->GetArrayInfo(pgcnt, reverse) || pgcnt < 2) {
ad.item_count = 0;
ad.items_per_page = 0;
ad.page_count = 0;
ad.current_page = -1;
return;
}
ad.item_count = pgcnt; // XXXX do we really need these two fields?!
ad.items_per_page = 1;
ad.page_count = pgcnt;
ad.reverse = reverse;
int i;
for(i = 1; i <= pgcnt; i++)
ad.href_array[i] = GetListItemHref(lsd.id, itd.item_id, i);
ad.href_first = GetListItemHref(lsd.id, itd.item_id, 0);
ad.href_last = ScriptVariableInv();
ad.current_page = -1;
}
#endif
#if 0
ScriptVariable Database::BuildListSegment(const ListData &lsd,
const ScriptVariable &tag,
const ScriptVariable &templ) const
{
ScriptVariable res("");
int i;
for(i = 0; i < lsd.items.Length(); i++) {
ListItemData itd;
if(!GetListItemData(lsd, i, itd))
continue;
if(itd.HasTag(tag))
res += BuildListItem(lsd, itd, templ);
}
return res;
}
#endif
static int method_by_name(const char *name)
{
ScriptVariable s(name);
s.Trim();
s.Tolower();
if(s == "copy")
return fpm_copy;
if(s == "link")
return fpm_link;
if(s == "symlink")
return fpm_symlink;
return fpm_none;
}
static int symlinks_mode_by_name(const char *name)
{
ScriptVariable s(name);
s.Trim();
s.Tolower();
if(s == "follow")
return fpm_slnk_follow;
if(s == "preserve")
return fpm_slnk_preserve;
#if 0
// XXX perhaps this should differ from the unknown case?
if(s == "" || s == "ignore")
return 0;
#endif
return 0;
}
static int
extract_publish_method(IniFileParser *ini, const char *grp, const char *id)
{
const char *tmp;
int res;
tmp = ini->GetTextParameter(grp, id, "publish_method", "none");
res = method_by_name(tmp);
tmp = ini->GetTextParameter(grp, id, "publish_recursive", 0);
if(boolean_value_from_string(tmp))
res |= fpm_recursive;
tmp = ini->GetTextParameter(grp, id, "publish_hidden", 0);
if(boolean_value_from_string(tmp))
res |= fpm_includehidden;
tmp = ini->GetTextParameter(grp, id, "publish_symlinks", "");
res |= symlinks_mode_by_name(tmp);
long mode = extract_chmod(ini, grp, id);
if(mode)
res |= (mode & fpm_mode_mask);
return res;
}
ScriptVariable Database::GetSetSourceDir(const ScriptVariable &set_id) const
{
const char *tmp =
inifile->GetTextParameter("pageset", set_id.c_str(), "sourcedir", 0);
#if 0
if(!tmp)
return GetSourcePrefix() + set_id;
if(*tmp == '/')
return tmp;
return GetSourcePrefix() + tmp;
#endif
if(!tmp)
return set_id;
return tmp;
}
static int extract_make_subdirs_field(const char *s)
{
if(!s)
return pageset_subdir_bysource;
ScriptVariable sv(s);
sv.Trim();
sv.Tolower();
if(sv == "always")
return pageset_subdir_always;
if(sv == "never")
return pageset_subdir_never;
return pageset_subdir_bysource;
}
bool Database::
GetSetData(const ScriptVariable &set_id, PageSetData &data) const
{
const char *tmp;
ScriptVariable str;
data.id = set_id;
data.source_dir = GetSetSourceDir(set_id);
const char *idc = set_id.c_str();
#if 0 // from now on, these fields may be modified with the pg ``type''
data.page_templ =
inifile->GetTextParameter("pageset", idc, "page_template", "");
data.page_tail_templ =
inifile->GetTextParameter("pageset", idc, "page_tail_template", "");
#endif
#if 0
tmp = inifile->GetTextParameter("pageset", idc, "auxfiles", 0);
data.aux_files.Clear();
if(tmp)
data.aux_files = ScriptWordVector(tmp);
#endif
data.publish_method = extract_publish_method(inifile, "pageset", idc);
tmp = inifile->GetTextParameter("pageset", idc, "make_subdirs", 0);
data.make_subdirs = extract_make_subdirs_field(tmp);
tmp = inifile->GetTextParameter("pageset", idc, "setdirname", 0);
data.setdirname = tmp ? ScriptVariable(tmp) : set_id;
tmp = inifile->GetTextParameter("pageset", idc, "pagedirname", 0);
data.pagedirname = tmp ? ScriptVariable(tmp) : "%[li:id]";
tmp = inifile->GetTextParameter("pageset", idc, "indexfilename", 0);
data.indexfilename = tmp ? ScriptVariable(tmp) : "index.html";
tmp = inifile->GetTextParameter("pageset", idc, "compagename", 0);
data.compagename = tmp ? ScriptVariable(tmp) : "c%idx%.html";
tmp = inifile->GetTextParameter("pageset", idc, "pagefilename", 0);
data.pagefilename =
tmp ? ScriptVariable(tmp) : "%[li:id]%[_idx].html";
data.comments_conf =
inifile->GetTextParameter("pageset", idc, "comments", "");
tmp = inifile->GetTextParameter("pageset", idc, "commentmap", 0);
data.cmap_filename = tmp ? ScriptVariable(tmp) : ScriptVariableInv();
tmp = inifile->GetModifiedTextParameter("pageset", idc, "commentmap",
"nodir", 0);
data.cmap_filename_nodir =
tmp ? ScriptVariable(tmp) : ScriptVariableInv();
data.page_ids.Clear();
return true;
}
void Database::ScanSetDirectory(PageSetData &data) const
{
ReadDir dir(data.source_dir.c_str());
const char *nm;
while((nm = dir.Next())) {
if(*nm == '.' || *nm == '_')
continue;
FileStat st((data.source_dir + "/" + nm).c_str());
if(!st.Exists() || (!st.IsDir() && !st.IsRegularFile()))
continue;
// X-X-X implement sorting here! X-X-X really?...
// perhaps no, because it should be done upon dates not ids
// *NO* actually, all lists must already be built inside the
// database, it is senseless to sort every time instead of
// maintaining the lists in the sorted state
data.page_ids.AddItem(nm);
}
}
bool Database::ScanSetTagItems(const ScriptVariable &set_id,
const ScriptVariable &tag, ScriptVector &items) const
{
SetListIndex *tmp =
const_cast<Database*>(this)->ProvideSetListIndex(set_id, tag);
if(!tmp)
return false;
items = tmp->items;
return true;
#if 0
ScriptVariable sd = GetSetSourceDir(set_id);
ReadStream f;
if(!f.FOpen((sd + "/" + tag).c_str()))
return false;
items.Clear();
ScriptVector line;
while(f.ReadLine(line, 2, ":", " \t\r\n")) {
if(line.Length() < 2)
continue;
long idx;
if(!line[0].GetLong(idx, 10))
continue;
// REMEMBER, indices are from 1, not 0, but the items indexed from 0
if(idx < 1 || idx > THALASSA_LIST_INDEX_LIMIT)
continue;
items[idx-1] = line[1];
}
return true;
#endif
}
FilterChainSet *Database::MakeFormatFilter(const HeadedTextMessage &msg) const
{
ProvideFilterChainMaker();
return filtmaker->MakeChainSet(msg.GetHeaders());
}
bool Database::GetSetItemData(const PageSetData &setd, int idx,
ListItemData *itd) const
{
if(idx >= setd.page_ids.Length())
return false;
return GetSetItemDataById(setd.id, setd.page_ids[idx], itd);
}
#if 0
static void read_set_item_navigation_hints(const char *path,
ListItemData *itd)
{
ReadStream nf;
if(nf.FOpen(path)) {
ScriptVariable wholefile;
nf.ReadUntilEof(wholefile);
nf.FClose();
ScriptVector strs(wholefile, "\n");
if(strs.Length() > 0) {
itd->nav_hints_count = strs.Length();
itd->nav_hints = new NavigationHints[strs.Length()];
int i;
for(i = 0; i < strs.Length(); i++) {
ScriptTokenVector nav(strs[i], ":", " \t\r\n");
itd->nav_hints[i].key = nav[0];
long ix;
itd->nav_hints[i].index =
(nav[1].GetLong(ix, 10) && ix >= 0) ? ix : -1;
itd->nav_hints[i].prev = nav[2];
itd->nav_hints[i].cur = nav[3];
itd->nav_hints[i].next = nav[4];
}
itd->index = itd->nav_hints[0].index;
itd->prev_id = itd->nav_hints[0].prev;
itd->next_id = itd->nav_hints[0].next;
}
}
}
#endif
static void scan_set_item_dir_for_files(ScriptVariable path, ScriptVector &v)
{
v.Clear();
ReadDir dir(path.c_str());
const char *nm;
while((nm = dir.Next())) {
if(*nm == '.' || *nm == '_')
continue;
ScriptVariable nms(nm);
if(nms == PAGESET_MAIN_FILE_NAME)
continue;
FileStat st((path + "/" + nms).c_str());
if(!st.Exists() || !st.IsRegularFile())
continue;
v.AddItem(nm);
}
}
bool Database::
GetSetItemSource(const ScriptVariable &set_id, const ScriptVariable &page_id,
ScriptVariable &srcd, ScriptVariable &fname, bool &dedic_dir) const
{
static ScriptVariable saved_set_id(0), set_src_path(0);
if(saved_set_id != set_id) {
saved_set_id = set_id;
set_src_path = GetSetSourceDir(set_id);
}
srcd = set_src_path + "/" + page_id;
FileStat st(srcd.c_str());
if(!st.Exists())
return false;
if(st.IsRegularFile()) {
fname = srcd;
dedic_dir = false;
} else {
fname = srcd + "/" PAGESET_MAIN_FILE_NAME;
dedic_dir = true;
}
return true;
}
bool Database::GetSetItemDataById(const ScriptVariable &set_id,
const ScriptVariable &page_id,
ListItemData *itd) const
{
itd->item_id = page_id;
ScriptVariable srcd, fname;
bool it_is_dir;
bool ok = GetSetItemSource(set_id, page_id, srcd, fname, it_is_dir);
if(!ok) {
itd->title = fname + ": file doesn't exist";
return false;
}
itd->make_separate_directory = it_is_dir;
FILE *s = fopen(fname.c_str(), "r");
if(!s) {
itd->title = fname + ": couldn't open file";
return false;
}
int c;
HeadedTextMessage parser(false);
while((c = fgetc(s)) != EOF) {
if(!parser.FeedChar(c)) // ok, int
break;
}
fclose(s);
long teaser_len = -1;
FilterChainSet *filt = MakeFormatFilter(parser);
int i;
const ScriptVector& hdr = parser.GetHeaders();
for(i = 0; i < hdr.Length()-1; i+=2) {
if(hdr[i] == "id" || hdr[i] == "encoding" || hdr[i] == "format") {
// no storing
continue;
}
if(hdr[i] == "unixtime") {
bool ok = hdr[i+1].GetLongLong(itd->unixtime, 10);
if(!ok)
itd->unixtime = -1;
continue;
}
if(hdr[i] == "type") {
itd->pgtype = hdr[i+1]; // no conversion here, it's a enum id
continue;
}
if(hdr[i] == "flags") {
itd->flags.Clear();
itd->flags = ScriptVector(hdr[i+1], ",", " \t\r\n");
continue;
}
if(hdr[i] == "comments") {
itd->comments = hdr[i+1]; // no conversion here, it's a enum id
continue;
}
if(hdr[i] == "teaser_len") {
bool ok = hdr[i+1].GetLong(teaser_len, 10);
if(!ok)
teaser_len = -1;
continue;
}
// don't forget there's also filt->ConvertData
// for the data actually generated by the system
// NOT entered by users
if(hdr[i] == "descr") {
itd->descr = filt->ConvertContent(hdr[i+1]);
continue;
}
// the rest of fields are converted as Userdata
ScriptVariable val = filt->ConvertUserdata(hdr[i+1]);
if(hdr[i] == "date") {
itd->date = val;
continue;
}
if(hdr[i] == "title") {
itd->title = val;
continue;
}
if(hdr[i] == "tags") {
itd->tags.Clear();
itd->tags = ScriptVector(val, ",", " \t\r\n");
continue;
}
// okay, no fields for this parameter in the structure
// just store it in the aux_params vector
itd->aux_params.AddItem(hdr[i]);
itd->aux_params.AddItem(val);
}
if(itd->date == "")
fill_date_from_unixtime(*itd);
itd->text = filt->ConvertContent(parser.GetBody());
if(teaser_len > 0 && (itd->descr.IsInvalid() || itd->descr == "")) {
ScriptVariable b = parser.GetBody();
//parser.GetBody() = ""; // optimization
itd->descr = filt->ConvertContent(b.Range(0, teaser_len).Get());
}
delete filt;
if(it_is_dir)
scan_set_item_dir_for_files(srcd, itd->files);
else
itd->files.Clear();
return true;
}
void Database::GetSetItemFilenames(const PageSetData &setd, bool separ_dir,
ScriptVariable &dir, ScriptVariable &idxfl,
ScriptVariable &href, ScriptVariable &cmap) const
{
if(separ_dir) {
href = (*subst)(setd.setdirname + "/" + setd.pagedirname);
dir = GetFilePrefix() + href;
idxfl = dir + "/" + setd.indexfilename;
} else {
ScriptVariable d = (*subst)(setd.setdirname);
ScriptMacroprocessor sub_sub(subst);
add_index_macros(sub_sub, 0); // XXX really 0, not -1 ?
ScriptVariable p = sub_sub(setd.pagefilename);
dir = GetFilePrefix() + d;
idxfl = dir + "/" + p;
href = d + "/" + p;
}
if(href[0] != '/')
href.Range(0,0).Replace("/");
ScriptVariable m =
separ_dir ? setd.cmap_filename : setd.cmap_filename_nodir;
cmap = m.IsValid() ? dir + "/" + (*subst)(m) : ScriptVariableInv();
}
void Database::
GetSetSubitemFilename(const PageSetData &setd, bool separ_dir, int subidx,
ScriptVariable &fname, ScriptVariable &href) const
{
ScriptMacroprocessor sub_sub(subst);
add_index_macros(sub_sub, subidx);
ScriptVariable dir;
ScriptVariable locpart;
if(separ_dir) {
dir = GetFilePrefix() +
sub_sub(setd.setdirname + "/" + setd.pagedirname);
locpart = sub_sub(setd.compagename);
} else {
dir = GetFilePrefix() + sub_sub(setd.setdirname);
locpart = sub_sub(setd.pagefilename);
}
fname = dir + "/" + locpart;
href = locpart;
}
void Database::
BuildSetItemPage(const PageSetData &psd, const ListItemData &itd,
ScriptVariable &main, ScriptVariable &tail,
ForumGenerator **fgp) const
{
const char *idc = psd.id.c_str();
const char *ts = itd.pgtype.c_str();
ScriptVariable page_templ =
inifile->GetModifiedTextParameter("pageset", idc,
"page_template", ts, "");
ScriptVariable page_tail_templ =
inifile->GetModifiedTextParameter("pageset", idc,
"page_tail_template", ts, "");
//SetListData(0, &itd);
main = (*subst)(page_templ);
tail = (*subst)(page_tail_templ);
if(itd.comments == "enabled" || itd.comments == "readonly")
*fgp = GetComments(psd.comments_conf);
else
*fgp = new EmptyForumGenerator(this);
//ForgetListCmtData();
}
bool Database::GetCollectionData(const ScriptVariable &id,
ScriptVariable &srcdir, ScriptVariable &dstdir,
int &method) const
{
const char *idc = id.c_str();
srcdir = (*subst)(
inifile->GetTextParameter("collection", idc, "sourcedir", idc));
dstdir = GetFilePrefix() + (*subst)(
inifile->GetTextParameter("collection", idc, "destdir", idc));
method = extract_publish_method(inifile, "collection", idc);
return (method & fpm_method_mask) != fpm_none;
}
bool Database::GetBinaryData(const ScriptVariable &id,
ScriptVariable &src, ScriptVariable &dst,
int &method) const
{
const char *idc = id.c_str();
src = (*subst)(inifile->GetTextParameter("binary", idc, "source", idc));
dst = GetFilePrefix() +
(*subst)(inifile->GetTextParameter("binary", idc, "dest", idc));
method = extract_publish_method(inifile, "binary", idc);
return (method & fpm_method_mask) != fpm_none;
}
void Database::GetAliases(const ScriptVariable &id, ScriptVector &result) const
{
result.Clear();
const char *tmp =
inifile->GetTextParameter("aliases", id.c_str(), "aliases", 0);
if(!tmp)
return;
ScriptVector lines((*subst)(tmp), "\n", " \t\r");
int n = lines.Length();
int i;
for(i = 0; i < n; i++) {
ScriptVector toks(lines[i], ":", " \t\r");
if(toks.Length() < 2) // XXX we simply ignore ill-formed lines
continue; // this might be a bad idea
result.AddItem(toks[0]);
result.AddItem(toks[1]);
}
}
void Database::GetAliasDirs(const ScriptVariable &id, ScriptVector &res) const
{
const char *s =
inifile->GetTextParameter("aliases", id.c_str(), "force_dirs", "");
res = ScriptVector(s, ",", " \t\r\n");
}
ScriptVariable Database::GetAliasDirFilename(const ScriptVariable &id) const
{
return inifile->GetTextParameter("aliases", id.c_str(),
"dir_file_name", ".htaccess");
}
ScriptVariable Database::BuildAliasDirFile(const ScriptVariable &id,
const ScriptVariable &target_uri) const
{
ScriptVariable templ = inifile->GetTextParameter("aliases", id.c_str(),
"dir_file_template", "");
ScriptMacroprocessor sub_sub(subst);
sub_sub.AddMacro(new ScriptMacroConst("target", target_uri));
return sub_sub.Process(templ);
}
#if 0
bool Database::GetTextMaybeFile(const char *g, const char *gs, const char *p,
ScriptVariable &res) const
{
res = inifile->GetTextParameter(g, gs, p, "");
if(res.Length() >= too_long_for_filename)
return true;
res.Trim(" \r\t"); // don't strip end-of-line!
if(res[0] != '>')
return true;
res.Range(0,1).Erase();
res.Trim();
#if 0
res = GetSourcePrefix() + res;
#endif
ReadStream s;
if(!s.FOpen(res.c_str())) {
res += ": couldn't open file";
return false;
}
s.ReadUntilEof(res);
s.FClose();
return true;
}
#endif
ScriptVariable
Database::ExpandTemplate(const char *templ, const char *g, const char *s) const
{
ScriptVariable body =
inifile->GetTextParameter("template", templ, "body", "");
ScriptVariable par =
inifile->GetTextParameter("template", templ, "params", "");
ScriptWordVector paramsv(par);
ScriptMacroprocessor sub_sub(subst);
int i;
for(i = 0; i < paramsv.Length(); i++) {
ScriptVariable val =
inifile->GetTextParameter(g, s, paramsv[i].c_str(), "");
sub_sub.AddMacro(new ScriptMacroConst(paramsv[i], (*subst)(val)));
}
//ScriptSubstitutionDictionary dv(dict, false);
//return subst->Substitute(body, &dv);
return sub_sub.Process(body);
}
void Database::ProvideFilterChainMaker() const
{
if(filtmaker)
return;
const char *enc = inifile->GetTextParameter("format", 0, "encoding", 0);
const char *tags = inifile->GetTextParameter("format", 0, "tags", "");
const char *attrs =
inifile->GetTextParameter("format", 0, "tag_attributes", 0);
if(!attrs)
attrs = "a=href img=src img=alt";
ScriptVariable enc_sv = enc ? (*subst)(enc) : ScriptVariableInv();
ScriptVariable tags_sv = (*subst)(tags);
ScriptVariable attrs_sv = (*subst)(attrs);
const_cast<Database*>(this)->filtmaker =
new FilterChainMaker(enc ? enc_sv.c_str() : 0,
tags_sv.c_str(), attrs_sv.c_str());
}
SetListIndex *Database::
ProvideSetListIndex(const ScriptVariable &set_id, const ScriptVariable &tag)
{
SetListIndex *tmp;
for(tmp = first_set_list; tmp; tmp = tmp->next)
if(tmp->set_id == set_id && tmp->tag == tag)
return tmp;
ScriptVariable sd = GetSetSourceDir(set_id);
if(sd.IsInvalid() || sd == "")
return 0;
ReadStream f;
if(!f.FOpen((sd + "/_" + tag).c_str()))
return 0;
tmp = new SetListIndex;
tmp->next = first_set_list;
tmp->set_id = set_id;
tmp->tag = tag;
first_set_list = tmp;
tmp->items.Clear();
ScriptVariable line;
while(f.ReadLine(line)) {
line.Trim();
if(line == "")
continue;
tmp->items.AddItem(line);
}
return tmp;
}
bool Database::
GetIndexBarStyle(const ScriptVariable &name, IndexBarStyle &s) const
{
const char *sn = name.c_str();
if(!sn || !*sn)
return false;
s.begin = inifile->GetTextParameter("indexbar", sn, "begin", "");
s.end = inifile->GetTextParameter("indexbar", sn, "end", "");
s.textbreak = inifile->GetTextParameter("indexbar", sn, "break", "");
s.link = inifile->GetTextParameter("indexbar", sn, "link", "");
s.greylink = inifile->GetTextParameter("indexbar", sn, "greylink", "");
s.curpos = inifile->GetTextParameter("indexbar", sn, "curpos", "");
s.textprev = inifile->GetTextParameter("indexbar", sn, "textprev", "");
s.textnext = inifile->GetTextParameter("indexbar", sn, "textnext", "");
s.textfirst = inifile->GetTextParameter("indexbar", sn, "textfirst", "");
s.textlast = inifile->GetTextParameter("indexbar", sn, "textlast", "");
s.tailsize = inifile->GetIntegerParameter("indexbar", sn, "tailsize", 2);
return s.link.IsValid() && s.link != "";
}
#if 0
ScriptVariable
Database::BuildArrayIndex(const IndexBarStyle &style,
const ScriptVariable &anchor) const
{
if(!array_data)
return ScriptVariableInv();
IndexInformation ixi;
ixi.anchor = anchor;
return build_array_index(&ixi, array_data);
}
#endif
ArrayData* Database::InstallArrayData(ArrayData *p)
{
ArrayData *tmp = array_data;
array_data = p;
return tmp;
}
void Database::SetMacroData(const ListData *ld, const ListItemData *lid) const
{
subst->SetData(ld, lid);
const_cast<Database*>(this)->current_list_data = ld;
const_cast<Database*>(this)->current_list_item_data = lid;
}
void Database::ForgetMacroData() const
{
subst->ForgetData();
const_cast<Database*>(this)->current_list_data = 0;
const_cast<Database*>(this)->current_list_item_data = 0;
}
void Database::SaveMacroRealm(Database::MacroRealm &buf) const
{
buf.ld = current_list_data;
buf.lid = current_list_item_data;
}
void Database::RestoreMacroRealm(const Database::MacroRealm &buf) const
{
SetMacroData(buf.ld, buf.lid);
}
static inline bool is_whitespace(int c)
{
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
ScriptVariable
Database::BuildMenu(const ScriptVariable &id, const ScriptVariable &cur) const
{
const char *idc = id.c_str();
if(!idc || !*idc)
return ScriptVariableInv();
const char *items = inifile->GetTextParameter("menu", idc, "items", 0);
if(!items)
return ScriptVariableInv();
while(*items && is_whitespace(*items))
items++;
if(!*items)
return ScriptVariableInv();
char delim[2] = { 0, 0 };
delim[0] = *items;
items++;
ScriptVector items_v((*subst)(items), delim, " \t\r\n");
int len = items_v.Length();
len = (len + 3) / 4; // count of items, full or partial
ScriptVariable link_templ =
inifile->GetTextParameter("menu", idc, "link", "");
ScriptVariable curpos_templ =
inifile->GetTextParameter("menu", idc, "curpos", "");
ScriptVariable tag_templ =
inifile->GetTextParameter("menu", idc, "tag", "");
ScriptVariable break_templ =
inifile->GetTextParameter("menu", idc, "break", "");
const char *sep_cstr =
inifile->GetTextParameter("menu", idc, "separator", "");
ScriptVariable separator = (*subst)(sep_cstr);
ScriptVariable result =
(*subst)(inifile->GetTextParameter("menu", idc, "begin", ""));
int i;
for(i = 0; i < len; i++) {
if(i > 0)
result += separator;
ScriptVariable uri = items_v[i*4+1];
ScriptVariable *t = 0;
if(uri.IsInvalid())
uri = "";
uri.Trim();
if(uri == "") {
ScriptVariable lab = items_v[i*4];
lab.Trim();
t = lab == "" ? &break_templ : &tag_templ;
} else
if(cur.IsValid() && cur != "" && cur == items_v[i*4 + 3]) {
t = &curpos_templ;
} else {
t = &link_templ;
}
result += subst->Process(*t, items_v, i * 4, 4);
}
result += (*subst)(inifile->GetTextParameter("menu", idc, "end", ""));
return result;
}
///////////////////////////////////////////////////////////////
// comments infrastructure
ScriptVariable Database::GetCommentsPath(ScriptVariable conf) const
{
ScriptVector cflines((*subst)(conf), "\n", " \t\r");
ScriptWordVector cfw(cflines[0]);
if(cfw.Length() < 2)
return ScriptVariableInv();
return cfw[1];
}
ForumGenerator *Database::GetComments(ScriptVariable conf) const
{
ScriptVector cflines((*subst)(conf), "\n", " \t\r");
ScriptWordVector cfw(cflines[0]);
if(cfw.Length() < 2) {
return new ErrorForumGenerator(this,
ScriptVariable("comments: style and path must be specified"));
}
ScriptVariable style = cfw[0];
ScriptVariable path = cfw[1];
ScriptVector auxparamdict;
int i;
for(i = 1 /* NB: 1, sic! */; i < cflines.Length(); i++) {
ScriptVariable::Substring w1;
cflines[i].Whole().FetchWord(w1);
ScriptVariable name = w1.Get();
if(name.IsInvalid())
continue;
name.Trim();
if(name == "")
continue;
ScriptVariable value = w1.After().Get();
if(value.IsInvalid())
continue;
value.Trim();
auxparamdict.AddItem(name);
auxparamdict.AddItem(value);
}
ForumData *fd = new ForumData;
const char *st = style.c_str();
static const char cmtst[] = "commentstyle";
fd->type = (*subst)(inifile->GetTextParameter(cmtst, st, "type", ""));
fd->top = inifile->GetTextParameter(cmtst, st, "top_template", "");
fd->bottom = inifile->GetTextParameter(cmtst, st, "bottom_template", "");
#if 0
fd->indent = inifile->GetTextParameter(cmtst, st, "indent_template", "");
fd->unindent =
inifile->GetTextParameter(cmtst, st, "unindent_template", "");
#endif
fd->comment_templ =
inifile->GetTextParameter(cmtst, st, "comment_template", "");
fd->tail =
inifile->GetTextParameter(cmtst, st, "comment_tail_template", "");
fd->no_comments_templ =
inifile->GetTextParameter(cmtst, st, "no_comments", "");
ScriptVariable s;
s = (*subst)(inifile->GetTextParameter(cmtst, st, "reverse", "no"));
fd->reverse = boolean_value_from_scrvar(s);
s = (*subst)(inifile->GetTextParameter(cmtst,st,"hidden_hold_place","no"));
fd->hidden_hold_place = boolean_value_from_scrvar(s);
s = (*subst)(inifile->GetTextParameter(cmtst, st, "perpage", "0"));
long ppg;
bool ok = s.GetLong(ppg, 10);
fd->per_page = ok ? ppg : 0;
#if 0
ScriptVariable custom_params =
inifile->GetTextParameter(cmtst, st, "custom_params", "");
ScriptVector cpvec(custom_params, " \t\n,");
#else
ScriptVector cpvec;
#endif
if(fd->type == "tree") {
cpvec.AddItem("indent_template");
cpvec.AddItem("unindent_template");
} else
if(fd->type == "thread") {
// to be determined...
}
for(i = 0; i < cpvec.Length(); i++) {
const char *s =
inifile->GetTextParameter(cmtst, st, cpvec[i].c_str(), 0);
if(!s || !*s)
continue;
fd->custom_params.AddItem(cpvec[i]);
fd->custom_params.AddItem(s);
}
CommentDir cmtdir(path, auxparamdict);
CommentTree *tree = cmtdir.GetTree();
if(!tree) {
delete fd;
return new ErrorForumGenerator(this,
ScriptVariable("error scanning the comment tree"));
}
cmtdir.ReleaseTree();
if(fd->type == "list")
return new PlainForumGenerator(this, fd, tree);
if(fd->type == "tree") {
if(fd->per_page == 0)
return new SingleTreeForumGenerator(this, fd, tree);
return new ForestForumGenerator(this, fd, tree);
}
if(fd->type == "thread")
return new ThreadPageForumGenerator(this, fd, tree);
// still here?!
ScriptVariable errmsg("unsupported comment style ");
errmsg += fd->type;
delete fd;
delete tree;
return new ErrorForumGenerator(this, errmsg);
}
#if 0
ScriptVariable Database::BuildCommentSection(ForumGenerator *fg,
const ScriptVariable &href, ScriptVariable &cmtmap) const
{
if(!fg)
return ScriptVariableInv();
ScriptVariable res;
bool ok;
ok = fg->NextPage(href, res);
if(!ok) {
cmtmap = fg->MakeCommentMap();
//CancelComments(fg);
return ScriptVariableInv();
}
cmtmap = ScriptVariableInv();
return res;
}
#endif
ScriptVariable Database::
BuildCommentPart(const CommentData &cdt, const ScriptVariable &templ) const
{
subst->SetCmtData(&cdt);
ScriptVariable res = (*subst)(templ);
subst->SetCmtData(0);
return res;
}
#if 0
void Database::CancelComments(Database::comment_iter *ci) const
{
delete *(ForumGenerator**)ci;
*ci = 0;
}
#endif
/////////////////////////////////////////
// blocks infrastructure
static void
add_block(BlockGroupData **first_group, const char *grp_id,
const char *blk_id, int weight, const char *tag,
const char *title, const char *body)
{
BlockGroupData **grpp = first_group;
while(*grpp && (*grpp)->id != grp_id)
grpp = &((*grpp)->next_grp);
if(!*grpp)
*grpp = new BlockGroupData(grp_id, "", "", "%blk:title% %blk:body%");
// now it is guaranteed that *grpp points to the desired group object
BlockData **blkp = &((*grpp)->first);
while(*blkp && (*blkp)->weight < weight)
blkp = &((*blkp)->next);
BlockData *bd = new BlockData(blk_id, weight, tag, title, body);
bd->next = *blkp;
*blkp = bd;
}
void Database::BuildBlockData()
{
if(first_block_group)
return;
static const char blkg[] = "blockgroup";
ScriptVector groups;
GetSectionNames(blkg, groups);
BlockGroupData *last = 0;
int i;
for(i = 0; i < groups.Length(); i++) {
const char *gn = groups[i].c_str();
const char *begin = inifile->GetTextParameter(blkg, gn, "begin", "");
const char *end = inifile->GetTextParameter(blkg, gn, "end", "");
const char *blkt =
inifile->GetTextParameter(blkg, gn, "block_template", "");
BlockGroupData *bgd = new BlockGroupData(gn, begin, end, blkt);
if(last)
last->next_grp = bgd;
else
first_block_group = bgd;
last = bgd;
}
static const char block[] = "block";
ScriptVector blocknames;
GetSectionNames(block, blocknames);
for(i = 0; i < blocknames.Length(); i++) {
const char *bn = blocknames[i].c_str();
const char *grpid =
inifile->GetTextParameter(block, bn, "group", "=NOGRP=");
int weight =
inifile->GetIntegerParameter(block, bn, "weight", 0);
const char *tag = inifile->GetTextParameter(block, bn, "tag", "");
const char *title = inifile->GetTextParameter(block, bn, "title", "");
const char *body = inifile->GetTextParameter(block, bn, "body", "");
add_block(&first_block_group, grpid, bn, weight, tag, title, body);
}
}
ScriptVariable Database::
BuildBlocks(const ScriptVariable &group, const ScriptVariable &aux) const
{
const_cast<Database*>(this)->BuildBlockData();
BlockGroupData *grp = first_block_group;
while(grp && grp->id != group)
grp = grp->next_grp;
if(!grp)
return "";
ScriptVariable empty;
ScriptVector auxvec(aux.IsValid() ? aux : empty, ",", " \t\r\n");
ScriptVariable res;
ScriptMacroprocessor sub_sub(subst);
ScriptVector valvec;
valvec.AddItem("title");
valvec.AddItem(""); // [1]
valvec.AddItem("body");
valvec.AddItem(""); // [3]
valvec.AddItem("id");
valvec.AddItem(""); // [5]
valvec.AddItem("tag");
valvec.AddItem(""); // [7]
sub_sub.AddMacro(new ScriptMacroDictionary("blk", valvec, false));
BlockData *blk;
for(blk = grp->first; blk; blk = blk->next) {
if(blk->tag.IsInvalid() || blk->tag == "" ||
(current_list_item_data &&
(svec_has_elem(current_list_item_data->tags, blk->tag))) ||
(svec_has_elem(auxvec, blk->tag)))
{
if(res == "")
res += (*subst)(grp->begin); // may be sub_sub?
valvec[1] = (*subst)(blk->title);
valvec[3] = (*subst)(blk->body);
valvec[5] = blk->id;
valvec[7] = blk->tag;
res += sub_sub.Process(grp->block_templ);
}
}
if(res != "")
res += (*subst)(grp->end);
return res;
}