// This is an FAQ module. Defines tags to edit and show questions in a // Frequently Asked Questions Fashion. constant cvs_version="$Id: faq_pike.txt,v 1.1 2003/05/24 14:58:19 kiwi Exp $"; inherit "module"; inherit "roxenlib"; #include #define srcencode(x) replace(x, ({ "&", "<", ">", "\"" }),({ "&", "<", ">", """ })) #define REDIR "not_query+"?r="+time()+"&a=edit&faqname="+http_encode_url(v->faqname)+"\">" #define DBFIELDS "(\ ordernr INT UNSIGNED NOT NULL, KEY(ordernr),\ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,\ created INT UNSIGNED NOT NULL,\ modified TIMESTAMP NOT NULL, \ question VARCHAR(128) NOT NULL,\ answer BLOB NOT NULL, \ faqname VARCHAR(16) NOT NULL, KEY(faqname)\ )" string version; string help() { return "This tag is used to display an FAQ previously created with the " "<editfaq> tag. Currently the following arguments are " "recognised:

" "
faq[=FAQ name]" "
Which FAQ to use. If not present, default will be used." "
foldtext" "
The text displayed as the link which will hide all answers. " "The default is \"Hide answers\"." "
unfoldtext" "
The text displayed as the link which will show all answers. " "The default is: \"Show all answers\"" "
nofoldtext" "
If present the show/hide all links won't be shown at all." "
help" "
Show this help text.
"; } array register_module() { return ({ MODULE_PARSER, "Frequently Asked Questions module", "This is an FAQ module. Defines tags to edit and show questions in a " "Frequently Asked Questions fashion. The module stores its questions a " "SQL database. MySQL was used for development but using a different " "SQL server should be possible. It is used with the <showfaq> and " "<editfaq> tags. For more information call the tags with help " "as one of the arguments.",0,1 }); } void create() { defvar("sqlurl", "mysql://localhost/faq", "SQL Database URL", TYPE_STRING, "The URL specifing what SQL database to use." ); } mapping query_tag_callers() { return ([ "showfaq": tag_showfaq, "editfaq": tag_editfaq ]); } object get_sql() { object db; array err = catch { db = Sql.sql(QUERY(sqlurl)); }; if(err) { report_error("FAQ Module: Failed to get SQL object.\n"+ describe_backtrace(err)); return 0; } err = catch { db->query("create table faq "+ DBFIELDS); }; return db; } void start() { sscanf(cvs_version, "%*s.pike,v %s %*s", version); version = sprintf("

FAQ Module " "by " "david@hedbor.org version %s.

", version); } string tag_showfaq(string t, mapping args, object id) { mapping v = id->variables; array res; string list="

    ", out = ""; object db; int i; if(args->help) return help(); db = get_sql(); if(!db) return "Error: Couldn't connect to database server. See error log for " "details."; if(!args->unfoldtext) args->unfoldtext = "Show all answers"; if(!args->foldtext) args->foldtext = "Hide answers"; if(!args->faq) args->faq = "default"; array err = catch { res = db->query("select * from faq where faqname = '"+db->quote(args->faq)+ "' order by ordernr"); }; if(!res || !sizeof(res) || err) { report_error("FAQ Module: Failed to fetch FAQ entries from database.\n"+ "SQL Server Error: "+(db->error()||"None")); return "Error: Couldn't fetch entries from database."; } foreach(res, mapping faq) { ++i; if(v->all || v->num == faq->id) out += sprintf("

    %d. %s

    \n
    %s\n", faq->id, i, faq->question, faq->answer); if(v->all) list += sprintf("
  1. %s\n", id->not_query, faq->id, faq->question); else list += sprintf("
  2. %s\n", id->not_query, faq->id, faq->question); } if(strlen(out)) out = "

    "+out+"
    "; list += "
"; if(!args->nofoldtext) { if(v->all) list += "

not_query)+"\">"+ args->foldtext+""; else list += "

not_query)+"?all=1\">"+ args->unfoldtext+""; } return list + out + version; } string edit_form(object id, object db) { mapping v = id->variables; int ok; string out = sprintf("

Edit FAQ Message

" "" "", srcencode(id->not_query), srcencode(v->faqname)); if(v->id) { array res, err; err = catch { res = db->query("select * from faq where id="+v->id); }; if(!err && res && sizeof(res)) { ok = 1; out += sprintf("" "Question: " "" "

Answer:
" "", srcencode(v->id), srcencode(res[0]->question), srcencode(res[0]->answer)); } } if(!ok) out += ("Question: " "

Answer:
" ""); return out +"

"; } string tag_editfaq(string t, mapping args, object id) { mapping v = id->variables; array res,err; string list="
    ", out = ""; object db; int i; if(args->help) return "This tag doesn't take any arguments. It might be a good idea to use " "this tag only on a password protected page (Roxen's builtin auth db, " "htaccess or any other method) to avoid unauthorized people from editing " "the FAQs."; if(!(db = get_sql())) return "Error: Couldn't connect to database server. See error log for " "details."; switch(v->a) { case "edit": if(!strlen(v->faqname)) return "Sorry, the specified FAQ name isn't valid. It has to be " "one character or longer!"+version; err = catch { res = db->query("select * from faq where faqname='"+ db->quote(v->faqname)+"' order by ordernr"); }; if(err) { report_error("FAQ Module: Failed to fetch FAQ entries from database.\n"+ "SQL Server Error: "+(db->error()||"None") +"\n"); return "Error: Couldn't fetch entries from database."; } out = sprintf("

    Edit Questions for %s

    ", srcencode(v->faqname)); foreach(res||({}), mapping faq) { i++; out += sprintf("
    %d: %s (unique ID: %s)
    %s

    " "Move Up - " "Move Down - " "Edit - " "Delete" "


    ", i, faq->question, faq->id, faq->answer, srcencode(id->not_query), faq->id, srcencode(v->faqname), faq->ordernr, srcencode(id->not_query), faq->id, srcencode(v->faqname), faq->ordernr, srcencode(id->not_query), faq->id, srcencode(v->faqname), srcencode(id->not_query), faq->id, srcencode(v->faqname) ); } return sprintf("%s

    " "Enter new Question%s", out, srcencode(id->not_query), srcencode(v->faqname), version); case "dn": catch { res = db->query("select id,ordernr from faq where faqname='"+ db->quote(v->faqname)+"' and ordernr > "+ v->on +" order by ordernr limit 1"); if(res && sizeof(res)) { db->query(sprintf("update faq set ordernr=%s where id=%s", res[0]->ordernr, v->id)); db->query(sprintf("update faq set ordernr=%s where id=%s", v->on, res[0]->id)); } }; return REDIR; case "d": catch { db->query("delete from faq where faqname='"+db->quote(v->faqname)+"' " "and id="+v->id); }; return REDIR; case "up": catch { res = db->query("select id,ordernr from faq where faqname='"+ db->quote(v->faqname)+"' and ordernr < "+ v->on +" order by ordernr DESC limit 1"); if(res && sizeof(res)) { db->query(sprintf("update faq set ordernr=%s where id=%s", res[0]->ordernr, v->id)); db->query(sprintf("update faq set ordernr=%s where id=%s", v->on, res[0]->id)); } }; return REDIR; case "e1": return edit_form(id, db) + version; case "e2": catch { if(v->id) db->query(sprintf("update faq set question='%s',answer='%s' " "where id=%s", db->quote(v->question), db->quote(v->answer), v->id)); else { res = db->query("select max(ordernr) as max from faq where " "faqname='"+db->quote(v->faqname)+"'"); db->query(sprintf("insert into faq set question='%s',answer='%s'," "faqname='%s',ordernr=%d", db->quote(v->question), db->quote(v->answer), db->quote(v->faqname), (int)(res[0]->max) +1)); } }; return REDIR; default: err = catch { res = db->query("select distinct faqname from faq order by faqname"); }; if(!res || !sizeof(res)) { report_error("FAQ Module: Failed to fetch FAQ list from database.\n"+ "SQL Server Error: "+(db->error()||"None") +"\n"); return "Failed to fetch FAQ list from database " "(no entries or error).
    SQL Server Error: "+ (db->error()||"None") +"

    "; } out = sprintf("

    Please select FAQ to edit:

    " "
    ", id->not_query); } if(strlen(out)) return out + version; return ""; }