//| {{{ comments.pike
//|
//| @author Pontus Östlund This module listens on the SiteBuilder event hook and should always
be connected to a SiteBuilder module. Provides commenting functionality to Roxen CMS pages. Comment id ISO date when the comment was added The autor of the comment The comment authors email The comment authors website The path to the page to which the comment belong The IP address of the comment author Is the comment visible or not (y or n) The actual comment The comment authors internal username if authenticated The comment authors internal user id if authenticated Is the comment by the page author (y or n)
List comments to path path. Can cointain globs to list
comments for all pages below a given directory structure and can be
a list of comma separated paths. If no path is given the current path
is used.
Include comments that is set to invisble The title of the page the comment belongs to The ID of the comment to emit The path of the CMS page to add the comment to. If not given
the current path will be used. The name of the comment author The email of the comment author The website URL of the comment author Add the comment as this user. The value should be either a user id
or a users handle (username). If omitted, the currently logged on user is
used. If no authentication is required to add a comment and no author name
is required either, i.e anonymous comments are allowed, this value will be
added to the \"author\" column in the database table. The default value of this can be set under \"settings\" in the module
tab The id of the comment to update The name of the comment author The email of the comment author The website URL of the comment author The ID of the comment to delete The path of the page to count the comments for. The number of days commenting is allowed is set in the module settings Commenting has been disabled Override the expiration value in the module settings. Per default a user who has write permission to the page also has
admin permissions to all comments beloning to that page The protection point to test. If there is no protection point on the
given path, the path is searched towards the root for the controlling
protection point. The identity to check the permission for. If omitted, the currently
logged on user is used. A comment ID to check against. This can be useful in environments where
only authenticated users exist which can give the user the ability to edit
its own comments
As of now this module should only be used in controlled environments, i.e where
users are authenticated, unless you whish to drown in spam comments. Spam
filtering will be added in a later version...
"
"If set to zero the if plugin will always return false, i.e "
"commenting will always be enabled."
))
);
defvar("anon_user",
Variable.String(
"Guest", 0, "Anonymous comment author",
"If non-authorized users are allowed to add comments and the "
"\"author\" field is optional, i.e anonymous comments are allowed, "
"this will be the default name in the \"author\" db table column. "
"This value can be overridden by setting the attribute "
"guest in <comment-add></comment-add>"
)
);
defvar("dump_file",
Variable.String(
"comments", 0, "Dump file",
"You can make a backup file of the comments DB and the data will"
"be stored to this file."
)
);
defvar("db_name",
Variable.DatabaseChoice(
"comment_" + (conf ? Roxen.short_name(conf->name):""), 0,
"Comments database",
"The database where we store page comments"
)->set_configuration_pointer(conf)
);
defvar("sitebuilder",
ChooseSiteVariable(
conf, VAR_INITIAL,
DLOCALE(842,"SiteBuilder"),
DLOCALE(843,"The SiteBuilder to connect to.")
)
);
visible_chooser = CHOOSE_SITE;
} // }}}
//| {{{ start
void start(int when, Configuration _conf)
{
db_name = query("db_name");
form_expires = query("form_expires");
anon_user = query("anon_user");
module_dependencies(conf, ({ "sitebuilder" }));
connect_hook();
} // }}}
//| {{{ ready_to_receive_requests
void ready_to_receive_requests(Configuration conf)
{
connect_hook();
} // }}}
//| {{{ query_action_buttons
mapping(string:function) query_action_buttons()
{
Site s = site();
if(!s || s->frontend_mode())
return ([]);
return ([
LOCALE(0, "Create Dump") : create_comments_dump,
LOCALE(0, "Load Dump") : load_comments_dump,
LOCALE(0, "Clear") : clear
]);
} // }}}
//| {{{ connect_hook
void connect_hook()
{
Site s = site();
if (!s) {
report_warning(module_name + ": Not connected to CMS Main Module.\n");
hook = 0;
return;
}
init_db();
hook = s && s->set_event_hooks("comments", 0, sb_after_hook, 0);
} // }}}
//| {{{ clear
//| Truncates the comments table
void clear()
{
get_db()->query("TRUNCATE TABLE comments");
} // }}}
//| {{{ sb_after_hook
void sb_after_hook(string operation, string path, RequestID id,
void|mapping info, object obj)
{
if ((< "dir_change_flat", "file_change" >)[operation]) {
DEBUG("Skipping operation: %s", operation);
return;
}
mixed err = catch
{
object wa = id->misc->wa;
mapping md = obj->md;
switch (operation)
{
//| Handle external visibility
//| This is per se not neccessary since hidden pages comments
//| will be discarded by the Workarea notifcation, but if we
//| set the visibility flag directly in the database we will get
//| smaller datasets when querying the database and thus fewer
//| iterations and so on...
case "set_metadata":
array old_external_use =
info->old_md && info->old_md->external_use;
array new_external_use =
info->new_md && info->new_md->external_use;
if (!equal(new_external_use, old_external_use)) {
if (new_external_use[1] == VISIBLE_ALWAYS)
commit_action[path] = set_comments_visible;
else if (new_external_use[1] == VISIBLE_NEVER)
commit_action[path] = set_comments_invisible;
}
break;
//| The page is permanently deleted, just clear the comments
case "purge":
clear_comments(path);
break;
case "undelete":
commit_action[path] = md->external_use[1] == VISIBLE_ALWAYS ?
set_comments_visible :
set_comments_invisible;
break;
//| Page is moved/renamed
case "move":
rollback_action[info->dst] = clear_comments;
copy_comments(path, info->dst);
break;
case "discard":
if (has_index(rollback_action, path))
rollback_action[path](path);
break;
case "commit":
string type = info && info->commit_type;
DEBUG("Commit type: %s (%s)", type, path);
switch (type)
{
case "delete":
set_comments_invisible(path);
break;
//| Why set_comments_visible on a newly created page?
//| If we rename/move a Sitebuilder page we copy the
//| comments to the orginal page and set them invisble.
//| When the new renamed/moved page the is being checked
//| in it will have the operation "create" so we need to
//| set the copied comments to "visible".
case "create":
set_comments_visible(path);
break;
case "undelete":
case "edit":
if (has_index(commit_action, path))
commit_action[path](path);
break;
}
m_delete(commit_action, path);
m_delete(rollback_action, path);
break;
}
};
if (err) {
cmt_error(
"\nERROR: clearing/updating comments failed. "
"This event was not recorded.\n"
" Reason: " + describe_error(err) + "\n"
);
DEBUG(describe_backtrace(err));
}
/*
DEBUG("sb_after_hook(%O, %O, %O, %O, %O, %O, %O)\n",
operation, path, id, info, obj, rollback_action[path],
commit_action[path]);
*/
} // }}}
//| {{{ status
string status()
{
if (Site s = site()) {
int n = get_db()->query("SELECT COUNT(id) as n FROM comments")[0]->n;
n = (int)n;
string cmts = sprintf(
"There %s %s comment%s in the database",
(n != 1 ? "are" : "is"), (n > 0 ? (string)n : "no") ,
(n != 1 ? "s" : "")
);
return LOCALE(844,"Connected to") + ": " +
Roxen.html_encode_string(search(sitebuilders(), s)
->query_name()) + "
" + cmts;
}
else
return "" + LOCALE(845,"Not connected") + "";
} // }}}
//| {{{ init_db
void init_db()
{
mapping perms = DBManager.get_permission_map()[db_name];
if (!get_db()) {
if (perms && perms[conf->name] == DBManager.NONE) {
cmt_error("No permission to read Form database: %s\n", db_name);
return;
}
report_notice("No comments database present. Creating \"%s\".\n",
db_name);
if(!DBManager.get_group("platform")) {
DBManager.create_group("platform",
"Roxen platform",
"Various databases used by the Roxen "
"Platform modules",
""
);
}
DBManager.create_db(db_name, 0, 1, "platform");
DBManager.set_permission(db_name, conf, DBManager.WRITE);
perms = DBManager.get_permission_map()[db_name];
DBManager.is_module_db(0, db_name,
"Used by the Comments Module to "
"store its data.");
if (!get_db()) {
cmt_error("Unable to create Comments database.\n");
return;
}
}
if (perms && perms[conf->name] == DBManager.WRITE)
setup_tables();
} // }}}
//| {{{ get_db
Sql.Sql get_db()
{
return DBManager.get(db_name, conf);
} // }}}
//| {{{ setup_tables
void setup_tables()
{
if (Sql.Sql db = get_db()) {
db->query(#"
CREATE TABLE IF NOT EXISTS `comments` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`path` VARCHAR(255) DEFAULT NULL,
`date` DATETIME DEFAULT NULL,
`visible` ENUM('y','n') DEFAULT 'y',
`body` BLOB,
`author` VARCHAR(255) DEFAULT NULL,
`email` VARCHAR(255) DEFAULT NULL,
`url` VARCHAR(255) DEFAULT NULL,
`ip` VARCHAR(255) DEFAULT NULL,
`owner` ENUM('y','n') DEFAULT 'n',
`username` VARCHAR(50) DEFAULT NULL,
`userid` INT(11) UNSIGNED DEFAULT NULL,
PRIMARY KEY (`id`)
)"
);
DBManager.is_module_table(this_object(), db_name, "comments", 0);
}
else cmt_error("Couldn't get DB connection");
} // }}}
//| {{{ create_comments_dump
//|
//| Dump the comments database to a flat file
string|void create_comments_dump()
{
string file;
if (Sql.Sql db = get_db()) {
file = query("dump_file");
if (!sizeof(file)) {
report_warning("No dump file specified. Leaving..");
return;
}
file = site()->storage + file;
object(Stdio.FILE) fh = Stdio.FILE();
if (fh->open(file + ".t", "ctw")) {
if (!_dump(fh)) {
fh->close();
rm (file + ".t");
cmt_error(LOCALE(198, "Error writing dump") + "\n");
return;
}
else {
fh->close();
if (!mv(file + ".t", file)) {
cmt_error(
LOCALE(0, "Error renaming newly written file to "
"dump '%s': %s"
)+"\n", file, strerror(errno())
);
return;
}
}
}
else {
cmt_error(LOCALE(200, "Error opening file '%s' for write: ") +
"%s\n", file, strerror(fh->errno()));
return;
}
}
else
return "No database present";
report_notice("Comments DB dump written to %s", file);
} // }}}
//| {{{ load_comments_dump
//|
//| Load the comments dump file into the database
string|void load_comments_dump()
{
string file;
if (Sql.Sql db = get_db()) {
file = query("dump_file");
if (!sizeof(file)) {
report_warning("No dump file specified. Leaving..");
return;
}
file = site()->storage + file;
if (!Stdio.exist(file)) {
report_warning("The dump file %s doesn't exist", file);
return;
}
object(Stdio.FILE) fh = Stdio.FILE();
if (!fh->open(file, "r")) {
cmt_error("Couldn't read dump file %s: %s",
file, strerror(fh->errno()));
return;
}
String.SplitIterator sp = fh->line_iterator(1);
if (sizeof(sp)) {
do {
if (mixed e = catch{ db->query(utf8_to_string(sp->value()));})
cmt_error("Error importing comments dump:\n%s",
describe_error(e));
} while (sp->next());
}
}
else return "No database present";
} // }}}
//| {{{ _dump
//|
//| Write the DB content to the dump file
int(0..1) _dump(Stdio.FILE f)
{
Sql.Sql db = get_db();
if (!db) {
cmt_error("No database available");
return 0;
}
string tbl = "";
array(mapping(string:string)) r = db->query("SELECT * FROM comments");
if (!sizeof(r)) {
report_notice("No comments to dump");
return 0;
}
map(r, lambda(mapping m) {
foreach (glob("*.*", indices(m)), string key)
m_delete(m, key);
} );
foreach (r, mapping m) {
array cols = ({}), vals = ({});
foreach (m; string key; mixed val) {
cols += ({ key });
vals += ({ "'" + db->quote((string)val) + "'" });
}
f->write(string_to_utf8(sprintf(
"INSERT INTO comments (%s) VALUES (%s);\n", cols*",", vals*","
)));
}
return 1;
} // }}}
//| {{{ copy_comments
//|
//| If someone is moving a Sitebuilder page to a new location we want the
//| comments to be copied to. We're setting them to not visible and later
//| when the page is commited the comments visibility is set to 'y'
void copy_comments(string from, string to)
{
DEBUG("Copying comments from /%s to /%s", from, to);
string sql =
"INSERT INTO comments "
" (date, path, visible, body, author, email, ip, url)"
" SELECT date, %s, visible, body, author, email, ip, url "
" FROM comments WHERE path = %s";
mixed err = catch { get_db()->query(sql, prefix(to), prefix(from)); };
if (err)
DEBUG(describe_error(err));
} // }}}
//| {{{ prefix
//|
//| Paths in the sb_after_hook has no beginning / so we check if it's there
//| and if not we add one
string prefix(string in)
{
return (has_prefix(in, "/") ? in : "/" + in);
} // }}}
//| {{{ clear_comments
//|
//| Delete all comments from path "path"
void clear_comments(string path)
{
path = prefix(path);
DEBUG("Clearing comments for path '%s'", path);
string sql = "DELETE FROM comments WHERE path = %s";
if (path) get_db()->query(sql, path);
} // }}}
//| {{{ set_comments_invisible
//|
//| Hide the comments for path "path"
void set_comments_invisible(string path)
{
path = prefix(path);
DEBUG("Setting comments invisble for path '%s'", path);
string sql = "UPDATE comments SET visible = 'n' WHERE path = %s";
if (path) get_db()->query(sql, path);
} // }}}
//| {{{ set_comments_visible
//|
//| Set invisible comments for path "path visible.
void set_comments_visible(string path)
{
path = prefix(path);
DEBUG("Setting comments visble for path '%s'", path);
string sql = "UPDATE comments SET visible = 'y' WHERE path = %s";
if (path) get_db()->query(sql, path);
} // }}}
//| {{{ translate_glob
string translate_glob(string in)
{
string out = replace(in, ({ "%", "_" }), ({ "\\%", "\\_" }));
return replace(out, ({ "*", "?" }), ({ "%", "_" }));
} // }}}
//| {{{ get_publish_date
//| Get the publish date for the current page.
//| This takes "visibility" settings in consideration
string get_publish_date(RequestID id)
{
object sbobj = id->misc->sbobj;
if (!sbobj) {
cmt_error("Couldn't get sbobj for %O", id);
return 0;
}
mapping md = sbobj->metadata(0, 1, -1)->md;
if (!md) {
cmt_error("Found no metadata for %O", id);
return 0;
}
VCLogEntry vlog;
//| Happens on new pages that are unpublished
if (catch { vlog = get_log(sbobj)[-1]; })
return 0;
return md->external_use &&
sizeof(md->external_use) &&
md->external_use[0] &&
replace(
Calendar.ISO.Second(md->external_use[0] )->iso_name(),"T"," "
) || vlog->date;
} // }}}
//| {{{ get_log
array(VCLogEntry) get_log(object/*SBObj*/obj)
{
array(VCLogEntry) logs;
if (catch { logs = obj->log(0, 0, 0, 1, 1, 0, 0)->get(0); } )
return 0;
return logs;
} // }}}
//| {{{ get_perm
mapping(string:int|AC.Identity) get_perm(RequestID id, string|void _path,
string|void _handle)
{
int ppid;
string path = _path || "";
if (has_prefix(path, "/")) path = path[1..];
if (path[sizeof(path)-1..] == "/") path = path[..sizeof(path)-2];
ppid = id->misc->sb->ac_find_file_pp(path);
object mac = id->misc->wa && id->misc->wa->mac;
if (!mac)
RXML.run_error("Sitebuilder \"comment\" tags used without a site");
AC.AC_DB db = id->misc->sb && id->misc->sb->get_ac_module() &&
id->misc->sb->get_ac_module()->online_acdb;
if (!db) RXML.run_error("No AC connection");
string handle = _handle || mac->id_get_handle(id);
RequestID auth_id =
(handle != mac->id_get_handle(id)) &&
(handle != mac->id_get_id(id)) && id;
int priv;
if (mac->id_is_privileged(id))
priv = 1;
int everyone;
if (mac->id_is_everyone(id))
everyone = 1;
AC.Identity identity;
AC.AC_DB.lock lock = db->lock();
if (!handle) {
if (priv)
identity = db->identities->privileged();
else if (everyone)
identity = db->identities->everyone();
}
else if (sizeof(handle) &&
!(identity = db->identities->find_by_handle(handle, auth_id)))
{
int idid = (int)handle;
if (idid) identity = db->identities->get(idid, 0);
}
lock = 0;
if (!identity) {
DEBUG("Unknown identity '%s'.\n", handle);
RXML.run_error("Unknown identity '%s'.\n", handle);
}
int perm = mac->id_get_perm(identity->id(), ppid);
return ([ "identity" : identity, "permission" : perm ]);
} // }}}
//| {{{ TagEmitCommentsPlugin
class TagEmitCommentsPlugin
{
inherit RXML.Tag;
constant name = "emit";
constant plugin_name = "comments";
mapping(string:RXML.Type) req_arg_types = ([]);
mapping(string:RXML.Type) opt_arg_types = ([
"invisible" : RXML.t_text(RXML.PXml),
"path" : RXML.t_text(RXML.PXml)
]);
array get_dataset(mapping args, RequestID id)
{
Sql.Sql db = get_db();
if (!args->path)
args->path = id->misc->localpath;
args->path = prefix(args->path);
object wa = id->misc->wa;
object sbobj = id->misc->sbobj;
array paths = args->path/",";
paths = map(map(map(paths, String.trim_all_whites), db->quote),
translate_glob);
array w = ({});
foreach (paths, string path)
w += ({ "path LIKE '" + path + "'" });
string sql = "SELECT * FROM comments WHERE " + (w * " OR ");
if (!args->invisible)
sql += "AND visible = 'y' ";
sql += "ORDER BY id ";
array(mapping) res = db->query(sql);
//| Remove "." syntax indices in the DB resultset
map(res, lambda(mapping m) {
foreach (glob("*.*", indices(m)), string key)
m_delete(m, key);
});
//| Should be enough!
int limit = 100000 || (int)args->maxrows;
mapping sb_cache = ([]);
mapping skip = ([]);
string|mapping titles;
mapping(string:string) title;
mapping(string:string) lang;
array(mapping(string:string)) comments = ({});
foreach (res, mapping m) {
if (has_index(skip, m->path))
continue;
if (!sb_cache[m->path])
sb_cache[m->path] = wa->sbobj_va(m->path, id);
sbobj = sb_cache[m->path];
if (!sbobj) {
DEBUG("Missing file ignored: %O", m->path);
skip[m->path] = 1;
continue;
}
if (!sbobj->exists(id)) {
DEBUG("Deleted file ignored: %O", m->path);
skip[m->path] = 1;
continue;
}
// Hidden by workflow?
if(!sbobj->is_valid_op(id, "metadata", 0, 0)) {
DEBUG("Can not get metadata for %O\n", m->path);
skip[m->path] = 1;
continue;
}
mapping|object md = sbobj->metadata(id, 0);
if (mappingp(md))
error("Unexpected error from metadata(): %s\n",
Sitebuilder.error_msg(md));
md = md->md;
if(!get_current_visibility(sbobj, md->external_use)) {
DEBUG("Time published file ignored %O\n", m->path);
skip[m->path] = 1;
continue;
}
titles = md->title;
if (mappingp(titles)) {
array(string) langs = indices(titles);
lang = ([ "languages" : langs * "," ]);
string pref_lang = ((id->misc->sb_lang & langs) + ({ "" }) )[0];
title = ([ "page-title" : (titles[pref_lang] ||
titles[md["original-language"]]) ]);
}
else if (stringp(titles))
title = ([ "page-title" : titles ]);
if (title) m += title;
if (lang) m+= lang;
title = 0;
lang = 0;
m_delete(m, "titles");
comments += ({ m });
if (sizeof(comments) >= limit)
break;
}
return comments;
}
} // }}}
//| {{{ TagBlogAddComment
class TagCommentAdd
{
inherit RXML.Tag;
constant name = "comment-add";
mapping(string:RXML.Type) req_arg_types = ([]);
mapping(string:RXML.Type) opt_arg_types = ([
"path" : RXML.t_text(RXML.PXml),
"url" : RXML.t_text(RXML.PXml),
"author" : RXML.t_text(RXML.PXml),
"email" : RXML.t_text(RXML.PXml),
"identity" : RXML.t_text(RXML.PXml),
"guest" : RXML.t_text(RXML.PXml)
]);
class Frame
{
inherit RXML.Frame;
array do_return(RequestID id)
{
string body = String.trim_all_whites(content) || "";
body = Roxen.html_decode_string(body);
args->path = args->path ? prefix(args->path) :
prefix(id->misc->localpath);
string handle;
if (args->identity)
handle = args->identity;
VCLogEntry log = get_log(id->misc->sbobj)[-1];
mapping mperm = get_perm(id, args->path, handle);
int uid = mperm->identity->id() || 0;
handle = (string)mperm->identity->handle();
string owner = (uid == log->userid) ? "y" : "n";
if (!args->author || (args->author && !strlen(args->author))) {
//! Everyone
if (uid == 1)
args->author = args->guest || anon_user;
else
args->author = mperm->identity->name();
}
if (args->url) {
if (!has_prefix(args->url, "http"))
args->url = "http://" + args->url;
}
string sql = #"
INSERT INTO comments(
path, date, body, author, email, url, owner, userid,
username
) VALUES (
%s, NOW(), %s, %s, %s, %s, %s, %d, %s
)";
mixed e = catch {
get_db()->query(
sql, args->path, body, args->author, args->email,
args->url || "", owner, uid, handle);
};
if (e) {
cmt_error("Error adding comment:\%s", describe_error(e));
DEBUG("mperms(%O),\nhandle(%O)", mperm, handle);
}
result = (e ? "0" : "1");
return 0;
}
}
} // }}}
//| {{{ TagIfCommentFormExpiredPlugin
class TagIfCommentFormExpiredPlugin
{
inherit RXML.Tag;
constant name = "if";
constant plugin_name = "comment-form-expired";
mapping(string:RXML.Type) opt_arg_types = ([
"days" : RXML.t_text(RXML.PXml)
]);
int eval(string a, RequestID id, mapping args)
{
int exp = (int)args->days || form_expires;
if (exp == 0) return 0;
string|Calendar.TimeRange pub = get_publish_date(id);
if (!pub) return 0;
pub = Calendar.parse("%Y-%M-%D%c%h:%m:%s", pub)->day();
Calendar.TimeRange now = Calendar.Second(time(1))->day();
int diff = pub->distance(now)->number_of_days();
return diff >= exp ? 1 : 0;
}
} // }}}
//| {{{ TagIfCommentAdminPermission
class TagIfCommentAdminPermission
{
inherit RXML.Tag;
constant name = "if";
constant plugin_name = "comment-admin-permission";
mapping(string:RXML.Type) opt_arg_types = ([
"identity" : RXML.t_text(RXML.PXml),
"id" : RXML.t_text(RXML.PXml)
]);
int eval(string a, RequestID id, mapping args)
{
int admin = 0;
string path = a || 0;
string handle = args->identity || 0;
mapping mperm = get_perm(id, path, handle);
if (args->id) args->id = (int)args->id;
if (mperm->permission == 2) admin = 1;
if (args->id && !admin && mperm->identity->id() != 1) {
mapping c = get_db()->query("SELECT userid FROM comments "
"WHERE id = %d LIMIT 1", args->id)[0];
if ((int)c->userid == mperm->identity->id())
admin = 1;
}
return admin;
}
} // }}}
//| {{{ TagCommentFormExpires
class TagCommentFormExpires
{
inherit RXML.Tag;
constant name = "comment-form-expires";
mapping(string:RXML.Type) opt_arg_types = ([
"days" : RXML.t_text(RXML.PXml),
"type" : RXML.t_text(RXML.PXml)
]);
class Frame
{
inherit RXML.Frame;
array do_return(RequestID id)
{
int exp = (int)args->days || form_expires;
if (exp == 0) {
result = RXML.nil;
return 0;
}
if (!args->type) {
result = (string)exp;
return 0;
}
string|Calendar.TimeRange pub = get_publish_date(id);
if (pub)
pub = Calendar.parse("%Y-%M-%D%c%h:%m:%s", pub)
->day()->add(exp);
else
pub = Calendar.Second(time(1))->day()->add(exp);
switch (args->type) {
case "days":
Calendar.TimeRange now = Calendar.Second(time(1))->day();
Calendar.TimeRange diff = now->distance(pub);
result = (string)diff->number_of_days();
break;
case "date":
result = pub->format_ext_ymd();
break;
}
return 0;
}
}
} // }}}
//| {{{ TagCommentDelete
class TagCommentDelete
{
inherit RXML.Tag;
constant name = "comment-delete";
mapping(string:RXML.Type) req_arg_types = ([
"id" : RXML.t_text(RXML.PXml)
]);
class Frame
{
inherit RXML.Frame;
array do_return(RequestID id)
{
string sql = "DELETE FROM comments WHERE id = %d";
mixed e = catch { get_db()->query(sql, (int)args->id); };
result = e ? "0" : "1";
return 0;
}
}
} // }}}
//| {{{ TagCommentsCount
class TagCommentsCount
{
inherit RXML.Tag;
constant name = "comments-count";
mapping(string:RXML.Type) req_arg_types = ([
"path" : RXML.t_text(RXML.PXml)
]);
class Frame
{
inherit RXML.Frame;
array do_return(RequestID id)
{
string sql = "SELECT COUNT(id) AS num FROM comments "
"WHERE path = %s AND visible = 'y'";
array(mapping) res;
mixed err = catch { res = get_db()->query(sql, args->path); };
result = err ? "0" : res[0]->num;
return 0;
}
}
} // }}}
//| {{{ TagEmitCommentPlugin
class TagEmitCommentPlugin
{
inherit RXML.Tag;
constant name = "emit";
constant plugin_name = "comment";
mapping(string:RXML.Type) req_arg_types = ([
"id" : RXML.t_text(RXML.PXml)
]);
array get_dataset(mapping args, RequestID id)
{
string sql = "SELECT * FROM comments WHERE id = %d LIMIT 1";
array(mapping(string:string)) res;
res = get_db()->query(sql, (int)args->id);
if (!res || sizeof(res) == 0)
RXML.run_error("The requested comment doesn't exist");
return res;
}
} // }}}
//| {{{ TagCommentUpdate
class TagCommentUpdate
{
inherit RXML.Tag;
constant name = "comment-update";
mapping(string:RXML.Type) req_arg_types = ([
"id" : RXML.t_text(RXML.PXml)
]);
mapping(string:RXML.Type) opt_arg_types = ([
"author" : RXML.t_text(RXML.PXml),
"email" : RXML.t_text(RXML.PXml),
"url" : RXML.t_text(RXML.PXml)
]);
class Frame
{
inherit RXML.Frame;
array do_return(RequestID id)
{
string body = String.trim_all_whites(content) || "";
mapping in_args = ([]);
array in_values = ({});
in_args += ([ "body" : Roxen.html_decode_string(body) ]);
if (args->author)
in_args += ([ "author" : args->author ]);
if (args->email)
in_args += ([ "email" : args->email ]);
if (args->url) {
if (!has_prefix(args->url, "http"))
args->url = "http://" + args->url;
in_args += ([ "url" : args->url ]);
}
string sql = "UPDATE comments SET ";
foreach (indices(in_args), string key) {
sql += sprintf("%s = %%s,", key);
in_values += ({ (string)in_args[key] });
}
sql = sql[0..strlen(sql)-2] + " WHERE id = %d";
in_values += ({ (int)args->id });
mixed e = catch { get_db()->query(sql, @in_values); };
if (e)
cmt_error("Error when updateing:\n%s", describe_error(e));
result = (e ? "0" : "1");
return 0;
}
}
} // }}}
constant loglocation = "../logs/localdebug/";
//| {{{ DEBUG
void DEBUG(mixed ... args)
{
#ifdef CMT_DEBUG
if (!Stdio.exist(loglocation) && !Stdio.is_dir(loglocation)) {
if (!mkdir(loglocation)) {
cmt_error("Couldn't create logdir: %s", loglocation);
return;
}
}
Stdio.File fh = Stdio.File(combine_path(loglocation, "comments.log"),"wac");
fh->write("Comments: " + args[0] + "\r\n", @args[1..] );
fh->close();
#endif
} //}}}
//| {{{ TAGDOCUMENTATION
//|
//| ============================================================================
TAGDOCUMENTATION;
#ifdef manual
//| Shared entites for emit#comments and emit#comment
constant CMT_ENT = ([
"&_.id;" :
"Latest comments
Modifier for how many days the form is supposed to live. This overrides the setting in the module settings