From 92326261d16d7a4d02f97d70f9bf3e0290af7d2e Mon Sep 17 00:00:00 2001 From: Dries Buytaert Date: Sun, 15 Sep 2002 13:00:12 +0000 Subject: [PATCH] - Commited Marco's new comment module and forum module!!! --- includes/theme.inc | 18 - modules/comment.module | 1656 +++++++++++++++++++++++--------- modules/comment/comment.module | 1656 +++++++++++++++++++++++--------- modules/forum.module | 694 ++++++++++++- modules/forum/forum.module | 694 ++++++++++++- update.php | 46 +- 6 files changed, 3725 insertions(+), 1039 deletions(-) diff --git a/includes/theme.inc b/includes/theme.inc index 146561014ed..108347da74c 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -62,24 +62,6 @@ class BaseTheme { print $output; } - - function comment_controls($threshold = 1, $mode = 3, $order = 1) { - return form_item(t("Comment viewing options"), comment_mode($mode) . comment_order($order) . comment_threshold($threshold) ." ", t("Select your prefered way to display the comments and click 'Update settings' to active your changes.")); - } - - function comment($comment, $link = 0) { - $output = "cid\">"; - $output .= "
"; - $output .= ""; - $output .= " "; - $output .= " "; - $output .= " "; - $output .= " "; - $output .= "
". check_output($comment->subject) ."
". comment_moderation($comment) ."
". t("by %a on %b", array("%a" => format_name($comment), "%b" => format_date($comment->timestamp))) ."
". check_output($comment->comment, 1) ."
$link
"; - $output .= "

"; - print $output; - } - function box($subject, $content, $region = "main") { $output = "". check_output($subject) ."
". check_output($content) ."

"; print $output; diff --git a/modules/comment.module b/modules/comment.module index 1d8930f21bd..72b690b069d 100644 --- a/modules/comment.module +++ b/modules/comment.module @@ -1,93 +1,58 @@ "Flat list - collapsed", 2 => "Flat list - expanded", 3 => "Threaded list - collapsed", 4 => "Threaded list - expanded"); -$GLOBALS["corder"] = array(1 => "Date - newest first", 2 => "Date - oldest first"); +$GLOBALS["cmodes"] = array(1 => t("Flat list - collapsed"), 2 => t("Flat list - expanded"), 3 => t("Threaded list - collapsed"), 4 => t("Threaded list - expanded")); +$GLOBALS["corder"] = array(1 => t("Date - newest first"), 2 => t("Date - oldest first")); function comment_help() { $output .= "

The comment module enables users to submit posts that are directly associated with a piece of content. These associated posts are called comments. Comments may be threaded, which means that Drupal keeps track of multiple subconversations around a piece of content. Threading helps to keep the comment conversation more organized. Users are presented with several ways to view the comment conversation, and if desired, users may easily choose a flat presentation of comments instead of threaded. Further, users may choose to order their comments view by newest first or by oldest first. Finally, users may view a folded list or an expanded list of comments. Folded limits the comment display to subject only. Drupal remembers the comment view preference of each user whenever he changes a view setting.

"; + $output .= "

Users may also choose to view a maximum number of comments; if there are more comments, there will be a navigator to browse through pages.

"; $output .= "

Since a busy site generates lots of comments, Drupal takes care to present a personalized view of comments for each user. The home page lists displays the number of read and unread comments for a given post for the current user. Also, the tracker module (when installed) displays all recent comments on the site. Finally, comments which the user has not yet read are highlighted with a red star (this graphic may depend on the current theme).

"; $output .= "

Comments behave like other user submissions in Drupal. Specifically, ". la("filters", array("mod" => "system", "type" => "filter")) ." like smileys and HTML work fine if the administrator has enabled them. Also, throttles are usually enabled to prevent a single user from spamming the web site with too many comments in a short period of time.

"; - $output .= "

Administrators may control which persons are allowed to submit and administer comments. These controls appear in the ". la("user permissions", array("mod" => "user", "op" => "permission")) ." administration page. Additionally, administrators may edit or search through comments on the ". la("comments admininistration page", array("mod" => "comment")) .", as well as set the default display view for new users.

"; + $output .= "

Administrators may control which persons are allowed to submit and administer comments. These controls appear in the ". la("user permissions", array("mod" => "user", "op" => "permission")) ." administration page. Additionally, administrators may edit or search through comments on the ". la("comments admininistration page", array("mod" => "comment")) .", as well as set the default display view for new users. Administrators can also state whether a certain role will have their comments published immediatly, or just put in a queue to be reviewed.

"; + $output .= "

If you really have a lot of comments, you can enable moderation. You have to give some roles permission to moderate, then setup some \"moderation votes\"; these votes will appear to moderators on a select box near the comment. You also have to assign, for every role and every vote, a value, which can be either positive or negative; use the moderation matrix to do this. This way some roles will have more \"weight\" in their moderation, if any, if you want. If you set a value to 0, that vote won't be available to that role. When a user moderates, that value will be added or subtracted to the score of that comment. Finally, you have to setup the comment filters: these will be the threshold values users will see on the comment control panel.

"; return $output; } -function comment_system($field){ +function comment_system($field) { $system["description"] = t("Enables user to comment on content (nodes)."); return $system[$field]; } -function comment_settings($mode, $order, $threshold) { - global $user; +function comment_conf_options() { + global $cmodes, $corder; - if ($user->uid) { - $user = user_save($user, array("mode" => $mode, "sort" => $order, "threshold" => $threshold)); + $output .= form_select("Default display mode", "comment_default_mode", variable_get("comment_default_mode", 4), $cmodes, "The default mode in which comments are displayed."); + $output .= form_select("Default display order", "comment_default_order", variable_get("comment_default_order", 1), $corder, "The default order in which comments are displayed."); + $output .= form_textfield("Default comments per page", "comment_default_per_page", variable_get("comment_default_per_page", "50"), 5, 5, "Default number of comments for each page; more comments are distributed in several pages."); + + $result = db_query("SELECT fid, filter FROM moderation_filters"); + while ($filter = db_fetch_object($result)) { + $thresholds[$filter->fid] = ($filter->filter); } + + $output .= form_select("Default threshold", "comment_default_threshold", variable_get("comment_default_threshold", 0), $thresholds, "The default threshold in which comments are displayed."); + + $output .= form_select("Preview comment", "comment_preview", variable_get("comment_preview", 1), array("optional", "required"), "Must users preview comments before submitting?"); + $output .= form_select("New comment form", "comment_new_form", variable_get("comment_new_form", 0), array("disabled", "enabled"), "New comment form in the node page?"); + $output .= form_select("Comment controls", "comment_controls", variable_get("comment_controls", 0), array("above comments", "below comments", "above and below"), "Position of the comment controls box."); + + return $output; } -function comment_num_all($nid) { - $comment = db_fetch_object(db_query("SELECT COUNT(c.nid) AS number FROM node n LEFT JOIN comments c ON n.nid = c.nid WHERE n.nid = '$nid' GROUP BY n.nid")); - return $comment->number ? $comment->number : 0; -} - -function comment_num_new($nid) { - global $user; - - if ($user->uid) { - - /* - ** Retrieve the timestamp at which the current user last viewed - ** the specified node and use this timestamp to find the number - ** of new comments. - */ - - $history = db_fetch_object(db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '%s'", $nid)); - $comment = db_fetch_object(db_query("SELECT COUNT(c.nid) AS number FROM node n LEFT JOIN comments c ON n.nid = c.nid WHERE n.nid = '%s' AND timestamp > '". ($history->timestamp ? $history->timestamp : 0) ."' GROUP BY n.nid", $nid)); - - return $comment->number ? $comment->number : 0; - } - else { - return 0; - } - -} - -function comment_tag_new($nid) { - global $user; - - if ($user->uid) { - $nid = check_query($nid); - - $result = db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '$nid'"); - if (db_fetch_object($result)) { - db_query("UPDATE history SET timestamp = '". time() ."' WHERE uid = '$user->uid' AND nid = '$nid'"); - } - else { - db_query("INSERT INTO history (uid, nid, timestamp) VALUES ('$user->uid', '$nid', '". time() ."')"); - } - } -} - -function comment_is_new($comment) { - global $user; - static $date; - - if (!$date[$comment->nid]) { - if ($user->uid) { - $history = db_fetch_object(db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '$comment->nid'")); - $date[$comment->nid] = $history->timestamp ? $history->timestamp : 0; - } - else { - $date[$comment->nid] = time(); - } - } - - if ($comment->timestamp > $date[$comment->nid]) { - return 1; - } - else { - return 0; +function comment_user($type, $edit, &$user) { + switch ($type) { + case "view_public": + return form_item(t("Signature"), check_output($user->signature, 1)); + case "view_private": + return form_item(t("Signature"), check_output($user->signature, 1)); + case "edit_form": + // when user tries to edit his own data + return form_textarea(t("Signature"), "signature", $edit["signature"], 70, 3, t("Your signature will be publicly displayed at the end of your comments.") ."
". t("Allowed HTML tags") .": ". htmlspecialchars(variable_get("allowed_html", ""))); + case "edit_validate": + // validate user data editing + return array("signature" => filter($edit["signature"])); } } @@ -128,7 +93,7 @@ function comment_form($edit) { $form .= form_hidden("pid", $edit["pid"]); $form .= form_hidden("nid", $edit["nid"]); - if (!$edit["comment"]) { + if (!$edit["comment"] && variable_get("comment_preview", 1)) { $form .= form_submit(t("Preview comment")); } else { @@ -136,13 +101,13 @@ function comment_form($edit) { $form .= form_submit(t("Post comment")); } - return form($form); + return form($form, "post", drupal_url(array("mod" => "comment", "op" => "reply", "id" => $edit["nid"]), "module")); } function comment_edit($cid) { global $user; - $comment = db_fetch_object(db_query("SELECT c.*, u.name, u.uid FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '$cid'")); + $comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name, u.data FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%d' AND c.status != 2", $cid)); if (comment_access("edit", $comment)) { comment_preview(object2array($comment)); @@ -153,15 +118,24 @@ function comment_reply($pid, $nid) { global $theme; if (user_access("access comments")) { + + /* + ** Show comment + */ + if ($pid) { - $comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%s'", $pid)); - comment_view($comment, t("reply to this comment")); + $comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name, u.data FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%d' AND c.status = 0", $pid)); + comment_view($comment); } else { node_view(node_load(array("nid" => $nid))); $pid = 0; } + /* + ** If possible, show reply form + */ + if (node_comment_mode($nid) == 1) { $theme->box(t("Reply"), t("This discussion is closed: you can't post new comments.")); } @@ -185,29 +159,24 @@ function comment_preview($edit) { } /* - ** Attach the user information: + ** Attach the user and time information: */ $comment->uid = $user->uid; $comment->name = $user->name; - - /* - ** Attach the time: - */ - $comment->timestamp = time(); /* ** Preview the comment: */ - comment_view($comment, t("reply to this comment")); + comment_view($comment); $theme->box(t("Reply"), comment_form($edit)); if ($edit["pid"]) { - $comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%s'", $edit["pid"])); - comment_view($comment, t("reply to this comment")); + $comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name, u.data FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%d' AND c.status = 0", $edit["pid"])); + comment_view($comment); } else { node_view(node_load(array("nid" => $edit["nid"]))); @@ -216,10 +185,9 @@ function comment_preview($edit) { } function comment_post($edit) { - global $theme, $user; + global $user; - $context->nid = $edit["nid"]; - if (user_access("post comments", $context) && node_comment_mode($edit["nid"]) == 2) { + if (user_access("post comments") && node_comment_mode($edit["nid"]) == 2) { /* ** Validate the comment's subject. If not specified, extract @@ -234,15 +202,20 @@ function comment_post($edit) { $edit["comment"] = filter($edit["comment"]); + if ($edit["comment"] == "") { + return array(t("Empty comment"), t("The comment you submitted is empty.")); + } + /* ** Check for duplicate comments. Note that we have to use the ** validated/filtered data to perform such check. */ - $duplicate = db_result(db_query("SELECT COUNT(cid) FROM comments WHERE pid = '%s' AND nid = '%s' AND subject = '%s' AND comment = '%s'", $edit["pid"], $edit["nid"], $edit["subject"], $edit["comment"]), 0); + $duplicate = db_result(db_query("SELECT COUNT(cid) FROM comments WHERE pid = '%d' AND nid = '%d' AND subject = '%s' AND comment = '%s'", $edit["pid"], $edit["nid"], $edit["subject"], $edit["comment"]), 0); if ($duplicate != 0) { watchdog("warning", "comment: duplicate '". $edit["subject"] ."'"); + return array(t("Duplicate comment"), t("The comment you submitted has already been inserted.")); } else { @@ -254,7 +227,13 @@ function comment_post($edit) { ** user. */ - db_query("UPDATE comments SET subject = '%s', comment = '%s' WHERE cid = '%s' AND uid = '$user->uid'", $edit["subject"], $edit["comment"], $edit["cid"]); + db_query("UPDATE comments SET subject = '%s', comment = '%s' WHERE cid = '%d' AND uid = '$user->uid'", $edit["subject"], $edit["comment"], $edit["cid"]); + + /* + ** Fire a hook + */ + + module_invoke_all("comment", "update", $edit); /* ** Add entry to the watchdog log: @@ -274,7 +253,20 @@ function comment_post($edit) { ** Add the comment to database: */ - db_query("INSERT INTO comments (nid, pid, uid, subject, comment, hostname, timestamp) VALUES ('%s', '%s', '$user->uid', '%s', '%s', '%s', '%s')", $edit["nid"], $edit["pid"], $edit["subject"], $edit["comment"], getenv("REMOTE_ADDR"), time()); + $status = user_access("post comments without approval") ? 0 : 1; + $roles = variable_get("comment_roles", array()); + $score = $roles[$user->rid] ? $roles[$user->rid] : 0; + $users = serialize(array(0 => $score)); + + $edit["cid"] = db_next_id("comments"); + + db_query("INSERT INTO comments (cid, nid, pid, uid, subject, comment, hostname, timestamp, status, score, users) VALUES ('%d', '%d', '%d', '$user->uid', '%s', '%s', '%s', '%s', '%s', $score, '%s')", $edit["cid"], $edit["nid"], $edit["pid"], $edit["subject"], $edit["comment"], getenv("REMOTE_ADDR"), time(), $status, $users); + + /* + ** Tell the other modules a new comment has been submitted: + */ + + module_invoke_all("comment", "insert", $edit); /* ** Add entry to the watchdog log: @@ -289,92 +281,25 @@ function comment_post($edit) { cache_clear(); + /* + ** TODO: we'd prefer to invalidate the page or pages with the newly + ** inserted comment. + ** + ** db_query("DELETE FROM cache WHERE cid LIKE '%s'", "%id=".$edit["nid"]."%"); + */ } } + else { + watchdog("error", "comment: unauthorized comment submitted or comment submitted to a closed node '". $edit["subject"] ."'"); + return array(t("Error"), t("You are not authorized to post comments, or this node doesn't accept new comments.")); + } /* - ** Redirect the user the node he commented on: + ** Redirect the user the node he commented on, or explain queue */ - drupal_goto(drupal_url(array("id" => $edit["nid"]), "node")); - -} - -function comment_num_replies($id) { - - $result = db_query("SELECT COUNT(cid) FROM comments WHERE pid = '$id'"); - return ($result) ? db_result($result, 0) : 0; - -} - -function comment_moderation($comment) { - global $user; - - // XXX: disabled for now - return ""; - - $values = array("--", "1", "2", "3", "4", "5"); - - $moderate = db_fetch_object(db_query("SELECT * FROM moderate WHERE cid = '$comment->cid' AND uid = '$user->uid'")); - - foreach ($values as $key => $value) { - $options .= " \n"; - } - - $output .= "
". ($comment->score ? $comment->score : "--") ." / $comment->votes"; - - return $output; -} - -function comment_threshold($threshold) { - // XXX: disabled for now - return ""; - - for ($i = 0; $i < 6; $i++) $options .= " "; - return "\n"; -} - -function comment_mode($mode) { - global $cmodes; - - foreach ($cmodes as $key => $value) $options .= " \n"; - return "\n"; -} - -function comment_order($order) { - global $corder; - - foreach ($corder as $key=>$value) $options .= " \n"; - return "\n"; -} - -function comment_query($nid, $order, $pid = -1) { - - $query .= "SELECT c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.nid = '$nid'"; - - if ($pid >= 0) { - $query .= " AND pid = '$pid'"; - } - - $query .= " GROUP BY c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name"; - - if ($order == 1) { - $query .= " ORDER BY c.timestamp DESC"; - } - else if ($order == 2) { - $query .= " ORDER BY c.timestamp"; - } - - return db_query($query); - -} - -function comment_visible($comment, $threshold = 0) { - if ($comment->votes == 0 || $comment->score >= $threshold) { - return 1; - } - else { - return 0; + if ($status == 1) { + return array(t("Comment queued"), t("Your comment has been queued for moderation by site administrators and will be published after approval.")); } } @@ -383,66 +308,923 @@ function comment_links($comment, $return = 1) { $links = array(); + /* + ** If we are viewing just this comment, we link back to the node + */ + if ($return) { $links[] = l("type;\">". t("return") ."", array("id" => $comment->nid), "node", $comment->cid); } + /* + ** Admin link + */ + if (user_access("administer comments")) { $links[] = la("type;\">". t("administer") ."", array("mod" => "comment", "op" => "edit", "id" => $comment->cid)); } /* - ** Here we should check if this node has read-only comments, but we - ** already check on submit and this way we save a query. It's just - ** a cosmetic issue. otherwise just uncomment the next line and - ** related bracket some lines below. + ** Possibly show edit and reply links */ - //if (node_comment_mode($comment->nid)) { - if (user_access("post comments")) { - if (comment_access("edit", $comment)) { - $links[] = lm("type\">". t("edit your comment") ."", array("mod" => "comment", "op" => "edit", "id" => $comment->cid), "", array("title" => t("Make changes to your comment."))); - } - else { + if (node_comment_mode($comment->nid) == 2) { + if (user_access("post comments")) { + if (comment_access("edit", $comment)) { + $links[] = lm("type\">". t("edit your comment") ."", array("mod" => "comment", "op" => "edit", "id" => $comment->cid), "", array("title" => t("Make changes to your comment."))); + } $links[] = lm("type;\">". t("reply to this comment") ."", array("mod" => "comment", "op" => "reply", "id" => $comment->nid, "pid" => $comment->cid), "", array("title" => t("Reply to this comment."))); } + else { + $links[] = comment_theme_invoke("comment_post_forbidden"); + } + } + + if ($moderation = comment_moderation_form($comment)) { + $links[] = $moderation; } - //} return $theme->links($links); } -function comment_view($comment, $folded = 0) { - global $theme, $id; +function comment_view($comment, $links = "", $visible = 1) { + + /* + ** Switch to folded/unfolded view of the comment + */ if (comment_is_new($comment)) { - $comment->subject = "$comment->subject *"; + $comment->new = 1; } - if ($folded) { - $theme->comment($comment, $folded); + print "cid\">\n"; + + if ($visible) { + comment_theme_invoke("comment", $comment, $links); } else { - print "
  • ". l(check_output($comment->subject), array("id" => $comment->nid, "cid" => $comment->cid), "node", $comment->cid, array("title" => t("Read comment."))) ." ". t("by") ." ". format_name($comment) ."
  • \n"; + comment_theme_invoke("comment_folded", $comment); + } +} + + +function comment_render($node, $cid = 0) { + global $user, $theme, $mode, $order, $threshold, $comment_page; + + if (user_access("access comments")) { + + /* + ** Pre-process variables: + */ + + $nid = $node->nid; + if (empty($nid)) { + $nid = 0; + } + + if (empty($mode)) { + $mode = $user->mode ? $user->mode : variable_get("comment_default_mode", 4); + } + + if (empty($order)) { + $order = $user->sort ? $user->sort : variable_get("comment_default_order", 1); + } + + if (empty($threshold)) { + $threshold = $user->uid ? $user->threshold : variable_get("comment_default_threshold", 0); + } + $threshold_min = db_result(db_query("SELECT minimum FROM moderation_filters WHERE fid = '%d'", $threshold)); + + if (empty($comment_page)) { + $comment_page = 1; + } + + $comments_per_page = $user->comments_per_page ? $user->comments_per_page : variable_get("comment_default_per_page", "50"); + + print "
    \n"; + + + if ($cid) { + + /* + ** Single comment view + */ + + print "
    "comment"), "module") ."\">\n"; + print form_hidden("nid", $nid); + + $result = db_query("SELECT c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name, u.data, c.score, c.users FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%d' AND c.status = 0 GROUP BY c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name, u.data, c.score, c.users", $cid); + + if ($comment = db_fetch_object($result)) { + comment_view($comment, comment_links($comment)); + } + + if ((user_access("administer comments") || comment_user_can_moderate($node)) && $user->uid != $comment->uid && !(comment_already_moderated($user->uid, $comment->users))) { + print "
    ". form_submit(t("Moderate comment")) ."

    "; + } + print "
    "; + } + else { + + /* + ** Multiple comments view + */ + + $query .= "SELECT c.cid as cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name, u.data, c.score, c.users FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.nid = '". check_query($nid) ."' AND c.status = 0"; + + if ($cid) { + $query .= " AND pid = '". check_query($cid) ."'"; + } + + $query .= " GROUP BY c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name, u.data, c.score, c.users"; + + if ($order == 1) { + $query .= " ORDER BY c.timestamp DESC"; + } + else if ($order == 2) { + $query .= " ORDER BY c.timestamp"; + } + + /* + ** Start a form, to use with comment control and moderation + */ + + $result = db_query($query); + $comment_num = db_num_rows($result); + + if ($comment_num && ((variable_get("comment_controls", 0) == 0) || (variable_get("comment_controls", 0) == 2))) { + print "
    "comment"), "module") ."\">\n"; + $theme->box(t("Control panel"), comment_theme_invoke("comment_controls", $threshold, $mode, $order, $nid, $comment_page, $comment_num, $comments_per_page)); + print form_hidden("nid", $nid); + print "
    "; + } + + print "
    "comment"), "module") ."\">\n"; + print form_hidden("nid", $nid); + + if ($comment_num) { + if ($mode == 1) { + /* + ** Flat collapsed + */ + + while ($comment = db_fetch_object($result)) { + $comments[$comment->cid] = $comment; + } + comment_theme_invoke("comment_flat_collapsed", $comments, $threshold_min); + } + else if ($mode == 2) { + /* + ** Flat expanded + ** + ** We page using PHP, not using SQL because otherwise we'd + ** have to use two queries; one for each comment and one for + ** the paged comments. In method 1-3 we take all results + ** anyway, wheras in method 4 we need every result to create + ** proper pages. It is here where we lose more, in fact for + ** higher pages we transfer unneeded data from the db and + ** the web server. + ** + ** TODO: the comment above is a bit cryptic. Mind to make it + ** a bit more verbose/explanatory? + */ + + $comment_num = 0; + $page = 1; + while ($comment = db_fetch_object($result)) { + if ($page == $comment_page) { + $comments[$comment->cid] = $comment; + } + $comment_num++; + if ($comment_num == $comments_per_page) { + if ($page == $comment_page) { + break; + } + else { + $comment_num = 0; + $page++; + } + } + + if ($user->uid != $comment->uid && !(comment_already_moderated($user->uid, $comment->users))) { + $show_moderate_button = 1; + } + } + + comment_theme_invoke("comment_flat_expanded", $comments, $threshold_min); + + if (comment_user_can_moderate($node) && $show_moderate_button) { + print "
    ". form_submit(t("Moderate comments")) ."

    "; + } + } + else if ($mode == 3) { + /* + ** Threaded collapsed + */ + + while ($comment = db_fetch_object($result)) { + $comments[$comment->cid] = $comment; + } + if ($comments) { + comment_theme_invoke("comment_thread_min", $comments, $threshold_min); + } + } + else { + /* + ** Threaded expanded + */ + + while ($comment = db_fetch_object($result)) { + $comments[$comment->cid] = $comment; + + if ($user->uid != $comment->uid && !(comment_already_moderated($user->uid, $comment->users))) { + $show_moderate_button = 1; + } + } + + /* + ** Build the comment structure + */ + + $structure = comment_thread_structure($comments, 0, 0, array()); + + $comment_num = 0; + $page = 1; + foreach ($structure as $cid => $depth) { + if ($page == $comment_page) { + comment_theme_invoke("comment_thread_max", $comments[$cid], $threshold_min, $depth - 1); + } + $comment_num++; + if ($comment_num == $comments_per_page) { + if ($page == $comment_page) { + break; + } + else { + $comment_num = 0; + $page++; + } + } + } + + if (comment_user_can_moderate($node) && $show_moderate_button) { + print "
    ". form_submit(t("Moderate comments")) ."

    "; + } + } + } + + print "
    "; + + if ($comment_num && ((variable_get("comment_controls", 0) == 1) || (variable_get("comment_controls", 0) == 2))) { + print "
    "comment"), "module") ."\">\n"; + $theme->box(t("Control panel"), comment_theme_invoke("comment_controls", $threshold, $mode, $order, $nid, $comment_page, $comment_num, $comments_per_page)); + print form_hidden("nid", $nid); + print "
    "; + } + } + + /* + ** If enabled, show new comment form + */ + + if (user_access("post comments") && node_comment_mode($nid) == 2 && variable_get("comment_new_form", 0)) { + $theme->box("Post new comment", comment_form(array("nid" => $nid))); + } + + /* + ** Tag the node's comments as being read: + */ + + comment_tag_new($nid); + } +} + +function comment_perm() { + return array("access comments", "post comments", "administer comments", "moderate comments", "post comments without approval", "administer moderation"); +} + +function comment_link($type, $node = 0, $main = 0) { + + if ($type == "admin" && user_access("administer comments")) { + $links[] = la(t("comments"), array("mod" => "comment")); + } + + if ($type == "node" && $node->comment) { + + if ($main) { + + /* + ** Main page: display the number of comments that have been posted. + */ + + if (user_access("access comments")) { + $all = comment_num_all($node->nid); + $new = comment_num_new($node->nid); // array! + + if ($all) { + $links[] = l(format_plural($all, 'comment', 'comments'), array("id" => $node->nid), "node", "comment", array("title" => t('Jump to first comment of this posting.'))); + + if ($new["count_new"]) { + $links[] = l($new["count_new"] ." ". t("new"), array("id" => $node->nid), "node", $new["id_first_new"], array("title" => t('Jump to first NEW comment of this posting.'))); + } + } + else { + if (user_access("post comments")) { + $links[] = lm(t("add new comment"), array("mod" => "comment", "op" => "reply", "id" => $node->nid), "", array("title" => t("Add a new comment to this page."))); + } + else { + $links[] = comment_theme_invoke("comment_post_forbidden"); + } + } + } + } + else { + /* + ** Node page: add a "post comment" link if the user is allowed to + ** post comments and if this node is not read-only + */ + + if ($node->comment == 2) { + if (user_access("post comments")) { + $links[] = lm(t("add new comment"), array("mod" => "comment", "op" => "reply", "id" => $node->nid), "comment", array("title" => t("Share your thoughts and opinions related to this posting."))); + } + else { + $links[] = comment_theme_invoke("comment_post_forbidden"); + } + } + else { + $links[] = t("This discussion is closed: you can't post new comments."); + } + } + } + + return $links ? $links : array(); +} + +function comment_page() { + global $theme, $op, $edit, $id, $pid, $cid; + + switch ($op) { + case "edit": + $theme->header(); + comment_edit(check_query($id)); + $theme->footer(); + break; + case t("Moderate comments"): + case t("Moderate comment"): + comment_moderate($edit); + drupal_goto(drupal_url(array("id" => $edit["nid"]))); + break; + case "reply": + $theme->header(); + comment_reply(check_query($pid), check_query($id)); + $theme->footer(); + break; + case t("Preview comment"): + $theme->header(); + comment_preview($edit); + $theme->footer(); + break; + case t("Post comment"): + list($error_title, $error_body) = comment_post($edit); + if ($error_body) { + $theme->header(); + $theme->box($error_title, $error_body); + $theme->footer(); + } + else { + drupal_goto(drupal_url(array("id" => $edit["nid"]), "node")); + } + break; + case t("Update settings"): + global $mode, $order, $threshold, $comments_per_page; + comment_settings(check_query($mode), check_query($order), check_query($threshold), check_query($comments_per_page)); + drupal_goto(drupal_url(array("id" => $edit["nid"], "mode" => $mode, "order" => $order, "threshold" => $threshold, "comments_per_page" => $comments_per_page), "node")); + break; + } +} + +/** +*** admin functions +**/ + +function comment_node_link($node) { + + if (user_access("administer comments") && comment_num_all($node->nid)) { + + /* + ** Edit comments: + */ + + $result = db_query("SELECT c.cid, c.subject, u.uid, u.name FROM comments c LEFT JOIN users u ON u.uid = c.uid WHERE nid = '%d' AND c.status = 0 ORDER BY c.timestamp", $node->nid); + + $output .= "

    ". t("Edit comments") ."

    "; + $output .= ""; + $output .= " "; + + while ($comment = db_fetch_object($result)) { + $output .= ""; + } + + $output .= "
    titleauthoroperations
    ". l($comment->subject, array("id" => $node->nid, "cid" => $comment->cid), "node", $comment->cid) ."". format_name($comment) ."". l(t("view comment"), array("id" => $node->nid, "cid" => $comment->cid), "node", $comment->cid) ."". la(t("edit comment"), array("mod" => "comment", "op" => "edit", "id" => $comment->cid)) ."". la(t("delete comment"), array("mod" => "comment", "op" => "delete", "id" => $comment->cid)) ."
    "; + + return $output; + } +} + +function comment_admin_edit($id) { + + $result = db_query("SELECT c.*, u.name, u.uid FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%d' AND c.status != 2", $id); + $comment = db_fetch_object($result); + + // if a comment is "deleted", it's deleted + if ($comment) { + $form .= form_item(t("Author"), format_name($comment)); + $form .= form_textfield(t("Subject"), "subject", $comment->subject, 70, 128); + $form .= form_textarea(t("Comment"), "comment", $comment->comment, 70, 15); + $form .= form_select(t("Status"), "status", $comment->status, array("published", "not published")); + $form .= form_hidden("cid", $id); + $form .= form_submit(t("Submit")); + $form .= form_submit(t("Delete")); + + return form($form); + } +} + +function comment_delete($edit) { + + if ($edit["confirm"]) { + db_query("UPDATE comments SET status = 2 WHERE cid = '%d'", $edit["cid"]); + watchdog("special", "comment: deleted comment #". $edit["cid"]); + $output = "Comment deleted."; + } + else { + $output .= form_item(t("Confirm deletion"), ""); + $output .= form_hidden("cid", $edit["cid"]); + $output .= form_hidden("confirm", 1); + $output .= form_submit(t("Delete")); + $output = form($output); + } + + return $output; +} + +function comment_save($id, $edit) { + db_query("UPDATE comments SET subject = '%s', comment = '%s', status = '%s' WHERE cid = '%d'", filter($edit["subject"]), filter($edit["comment"]), $edit["status"], $id); + watchdog("special", "comment: modified '". $edit["subject"] ."'"); + return "Comment updated."; +} + +function comment_admin_overview($status = 0, $comment_page = 0) { + global $comment_settings; + + $comments_per_page = 50; + + /* + ** Save location to come back here after a edit/delete + */ + + $comment_settings["status"] = $status; + $comment_settings["comment_page"] = $comment_page; + session_register("comment_settings"); + + + /* + ** Now render the page + */ + + $start = $comment_page * $comments_per_page; + + $output .= $status ? "

    Offline comments

    \n" : "

    Online comments

    "; + $result = db_query("SELECT c.*, u.name, u.uid FROM comments c LEFT JOIN users u ON u.uid = c.uid WHERE c.status = '%d' ORDER BY timestamp DESC LIMIT $start, $comments_per_page", $status); + + $output .= "\n"; + $output .= " \n"; + while ($comment = db_fetch_object($result)) { + $output .= " \n"; + } + + if ($comment_page > 0) { + // show previous + $prev_link = la(t("previous comments"), array("mod" => "comment", "status" => $status, "comment_page" => ($comment_page - 1))); + } + + if (db_num_rows($result) == $comments_per_page) { + // show next + $next_link = la(t("next comments"), array("mod" => "comment", "status" => $status, "comment_page" => ($comment_page + 1))); + } + + $output .= ""; + $output .= "
    subjectauthordatepublishedoperations
    ". l(check_output($comment->subject), array("id" => $comment->nid, "cid" => $comment->cid, "pid" => $comment->pid), "node", $comment->cid, array("title" => htmlentities($comment->comment))) ."". format_name($comment) ."". format_date($comment->timestamp, "small") ."". ($comment->status == 0 ? "yes" : "no") ."". la(t("edit comment"), array("mod" => "comment", "op" => "edit", "id" => $comment->cid)) ."". la(t("delete comment"), array("mod" => "comment", "op" => "delete", "id" => $comment->cid)) ."
    $prev_link$next_link
    \n"; + + return $output; +} + +function comment_mod_matrix($edit) { + global $tid, $rid; + + $output .= "

    Moderators/vote values matrix

    "; + + if ($rid) { + db_query("DELETE FROM moderation_roles"); + foreach ($rid as $role_id => $votes) { + foreach ($votes as $mid => $value) { + $sql[] = "('$mid', '$role_id', '$value')"; + } + } + db_query("INSERT INTO moderation_roles (mid, rid, value) VALUES ". implode(", ", $sql)); + } + + $result = db_query("SELECT r.rid, r.name FROM role r, permission p WHERE r.rid = p.rid AND p.perm LIKE '%moderate comments%'"); + $role_names = array(); + while ($role = db_fetch_object($result)) { + $role_names[$role->rid] = $role->name; + } + + $result = db_query("SELECT rid, mid, value FROM moderation_roles"); + while ($role = db_fetch_object($result)) { + $mod_roles[$role->rid][$role->mid] = $role->value; + } + + $output .= ""; + $output .= " "; + + $result = db_query("SELECT mid, vote FROM moderation_votes ORDER BY weight"); + while ($vote = db_fetch_object($result)) { + $output .= ""; + foreach (array_keys($role_names) as $rid) { + $output .= ""; + } + $output .= ""; + } + $output .= "
    votes". implode("", array_values($role_names)) ."
    $vote->votemid]\" size=\"4\" value=\"". $mod_roles[$rid][$vote->mid] ."\" />
    "; + $output .= "
    ". form_submit(t("Submit votes")); + + return form($output); +} + +function comment_mod_roles($edit) { + + $output .= "

    Initial comment scores

    "; + + if ($edit) { + variable_set("comment_roles", $edit); + } + + $start_values = variable_get("comment_roles", array()); + + $result = db_query("SELECT r.rid, r.name FROM role r, permission p WHERE r.rid = p.rid AND p.perm LIKE '%post comments%'"); + + $output .= ""; + $output .= " "; + + while ($role = db_fetch_object($result)) { + $output .= ""; + $output .= ""; + } + + $output .= "
    user roleinitial score
    $role->namerid]\" size=\"4\" value=\"". $start_values[$role->rid] ."\" />
    "; + $output .= "
    ". form_submit(t("Save scores")); + + return form($output); +} + +function comment_mod_votes($edit) { + global $op, $mid, $tid; + + if ($op == t("Save vote")) { + db_query("UPDATE moderation_votes SET vote = '%s', weight = '%d' WHERE mid = '%d'", $edit["vote"], $edit["weight"], $mid); + $mid = 0; + } + else if ($op == t("Delete vote")) { + db_query("DELETE FROM moderation_votes WHERE mid = '%d'", $mid); + db_query("DELETE FROM moderation_roles WHERE mid = '%d'", $mid); + $mid = 0; + } + else if ($op == t("Add new vote")) { + db_query("INSERT INTO moderation_votes (mid, vote, weight) VALUES (NULL, '%s', '%d')", $edit["vote"], $edit["weight"]); + $mid = 0; + } + + $output .= "

    Moderation votes overview

    "; + $output .= ""; + $output .= " "; + + $result = db_query("SELECT mid, vote, weight FROM moderation_votes ORDER BY weight"); + while ($vote = db_fetch_object($result)) { + $output .= " "; + } + $output .= "
    votesweightoperations
    $vote->vote$vote->weight". la(t("edit"), array("mod" => "comment", "op" => "votes", "mid" => $vote->mid)) ."
    "; + + if ($mid) { + $vote = db_fetch_object(db_query("SELECT vote, weight FROM moderation_votes WHERE mid = '%d'", $mid)); + } + + $output .= "

    Add new moderation option

    "; + $form .= form_textfield(t("Vote"), "vote", $vote->vote, 32, 64, t("The name of this vote. Example: 'offtopic', 'excellent', 'sucky'.")); + $form .= form_textfield(t("Weight"), "weight", $vote->weight, 32, 64, t("Used to order votes; heavier sink.")); + if ($mid) { + $form .= form_submit(t("Save vote")); + $form .= form_submit(t("Delete vote")); + } + else { + $form .= form_submit(t("Add new vote")); + } + + $output .= form($form); + + return $output; +} + +function comment_mod_filters($edit) { + global $op, $fid, $tid; + + if ($op == t("Save filter")) { + db_query("UPDATE moderation_filters SET filter = '%s', minimum = '%d' WHERE fid = '%d'", $edit["filter"], $edit["minimum"], $fid); + $fid = 0; + } + else if ($op == t("Delete filter")) { + db_query("DELETE FROM moderation_filters WHERE fid = '%d'", $fid); + $fid = 0; + } + else if ($op == t("Add new filter")) { + db_query("INSERT INTO moderation_filters (fid, filter, minimum) VALUES (NULL, '%s', '%d')", $edit["filter"], $edit["minimum"]); + $fid = 0; + } + + $output .= "

    Comment filters overview

    "; + $output .= ""; + $output .= " "; + + $result = db_query("SELECT fid, filter, minimum FROM moderation_filters ORDER BY minimum"); + while ($filter = db_fetch_object($result)) { + $output .= " "; + } + $output .= "
    nameminimum scoreoperations
    $filter->filter$filter->minimum". la(t("edit"), array("mod" => "comment", "op" => "filters", "fid" => $filter->fid)) ."
    "; + + if ($fid) { + $filter = db_fetch_object(db_query("SELECT filter, fid, minimum FROM moderation_filters WHERE fid = '%d'", $fid)); + } + + $output .= "

    Add new filter

    "; + $form .= form_textfield(t("Filter name"), "filter", $filter->filter, 32, 64, t("The name of this filter. Example: 'good comments', '+1 comments', 'everything'.")); + $form .= form_textfield(t("Minimum score"), "minimum", $filter->minimum, 32, 64, t("Show all comments whose score is larger or equal to the provided minimal score. Range: -127 + 128")); + if ($fid) { + $form .= form_submit(t("Save filter")); + $form .= form_submit(t("Delete filter")); + } + else { + $form .= form_submit(t("Add new filter")); + } + + $output .= form($form); + + return $output; +} + + +function comment_admin() { + global $op, $id, $edit, $mod, $keys, $order, $status, $comment_page, $comment_settings; + + if (user_access("administer comments")) { + + $links[] = la(t("online comments"), array("mod" => "comment", "status" => 0)); + $links[] = la(t("offline comments"), array("mod" => "comment", "status" => 1)); + $links[] = la(t("search comments"), array("mod" => "comment", "op" => "search")); + if (user_access("administer moderation")) { + $links[] = la(t("moderation votes"), array("mod" => "comment", "op" => "votes")); + $links[] = la(t("moderation matrix"), array("mod" => "comment", "op" => "matrix")); + $links[] = la(t("comment filters"), array("mod" => "comment", "op" => "filters")); + $links[] = la(t("initial comment scores"), array("mod" => "comment", "op" => "roles")); + } + $links[] = la(t("help"), array("mod" => "comment", "op" => "help")); + + print "". implode(" | ", $links) ."
    \n"; + + switch ($op) { + case "help": + print comment_help(); + break; + case "edit": + print comment_admin_edit($id); + break; + case "search": + print search_type("comment", drupal_url(array("mod" => "comment", "op" => "search"), "admin")); + break; + case "votes": + case t("Add new vote"): + case t("Delete vote"): + case t("Save vote"): + if (user_access("administer moderation")) { + print comment_mod_votes($edit); + } + break; + case "roles": + case t("Save scores"): + if (user_access("administer moderation")) { + print comment_mod_roles($edit); + } + break; + case "matrix": + case t("Submit votes"): + if (user_access("administer moderation")) { + print comment_mod_matrix($edit); + } + break; + case "filters": + case t("Add new filter"): + case t("Delete filter"): + case t("Save filter"): + if (user_access("administer moderation")) { + print comment_mod_filters($edit); + } + break; + case "delete": + print comment_delete(array("cid" => $id)); + break; + case t("Delete"): + print status(comment_delete($edit)); + if (session_is_registered("comment_settings")) { + $status = $comment_settings["status"]; + $comment_page = $comment_settings["comment_page"]; + } + print comment_admin_overview($status, $comment_page); + break; + case t("Submit"): + print status(comment_save(check_query($id), $edit)); + if (session_is_registered("comment_settings")) { + $status = $comment_settings["status"]; + $comment_page = $comment_settings["comment_page"]; + } + print comment_admin_overview($status, $comment_page); + break; + default: + print comment_admin_overview($status, $comment_page); + } + } + else { + print message_access(); + } +} + +/* +** Renderer or visualization functions this can be optionally +** overridden by themes. +*/ + +function comment_mode_form($mode) { + global $cmodes; + + foreach ($cmodes as $key => $value) { + $options .= " \n"; + } + + return "\n"; +} + +function comment_order_form($order) { + global $corder; + + foreach ($corder as $key=>$value) { + $options .= " \n"; + } + + return "\n"; +} + +function comment_per_page_form($comments_per_page) { + for ($i = 10; $i < 100; $i = $i + 20) { + $options .= " "; + } + return "\n"; +} + +function comment_threshold($threshold) { + $result = db_query("SELECT fid, filter FROM moderation_filters"); + $options .= " "; + while ($filter = db_fetch_object($result)) { + $filters .= " "; + } + + return "\n"; +} + +function comment_controls($threshold = 1, $mode = 3, $order = 1, $nid, $page = 0, $comment_num = 0, $comments_per_page = 50) { + static $output; + + if (!$output) { + $output .= comment_mode_form($mode); + $output .= comment_order_form($order); + $output .= comment_per_page_form($comments_per_page); + $output .= comment_threshold($threshold); + + $output .= " ". form_submit(t("Update settings")); + + $output = form_item(t("Comment viewing options"), $output, t("Select your prefered way to display the comments and click 'Update settings' to active your changes.")); + + if (($mode == 2 || $mode == 4) && $comment_num > $comments_per_page) { + if ($page > 1) { + $p[] = l(t("previous"), array("id" => $nid, "comment_page" => $page - 1)); + } + for ($n = 1; $n <= ceil($comment_num / $comments_per_page); $n++) { + $p[] = ($n == $page) ? "»$n«" : l($n, array("id" => $nid, "comment_page" => $n)); + } + if ($page < ceil($comment_num / $comments_per_page)) { + $p[] = l(t("next"), array("id" => $nid, "comment_page" => $page + 1)); + } + $output .= form_item(t("Browse %a comments", array("%a" => $comment_num)), implode(" • ", $p), t("There are more than %a comments in this node. Use these links to navigate through them.", array("%a" => $comments_per_page))); + } + } + + return $output; +} + +function comment_moderation_form($comment) { + global $comment_votes, $op, $user, $node; + static $votes; + + if ($op == "reply") { + // preview comment: + $output .= " "; + } + else if ((user_access("administer comments") || comment_user_can_moderate($node)) && $user->uid != $comment->uid && !(comment_already_moderated($user->uid, $comment->users))) { + // comment hasn't been moderated yet: + + if (!isset($votes)) { + $result = db_query("SELECT v.mid, v.vote, r.value FROM moderation_votes v, moderation_roles r WHERE v.mid = r.mid AND r.rid = '%d' ORDER BY weight", $user->rid); + $votes = array(); + while ($vote = db_fetch_object($result)) { + if ($vote->value != 0) { + $votes[] = $vote; + } + } + } + + $options .= " \n"; + if ($votes) { + foreach ($votes as $vote) { + $options .= " \n"; + } + } + + if (user_access("administer comments")) { + $options .= " \n"; + $options .= " \n"; + } + $output .= "\n"; + } + + return $output; +} + +function comment($comment, $link = 0) { + $output .= "cid\">"; + $output .= "
    "; + $output .= ""; + $output .= " "; + $output .= " "; + $output .= " "; + $output .= " "; + $output .= "
    ". check_output($comment->subject) . ($comment->new ? " *" : "") ."
    ". $comment->moderation ."
    ". t("by %a on %b", array("%a" => format_name($comment), "%b" => format_date($comment->timestamp))) ."
    ". check_output($comment->comment, 1) ."
    $link
    "; + $output .= "

    "; + print $output; +} + +function comment_folded($comment) { + print l(check_output($comment->subject), array("id" => $comment->nid, "cid" => $comment->cid), "node", $comment->cid) ." ". t("by") . " " . format_name($comment) ."

    "; +} + +function comment_flat_collapsed($comments, $threshold) { + foreach ($comments as $comment) { + if (comment_visible($comment, $threshold)) { + print comment_view($comment, "", 0); + } + } +} + +function comment_flat_expanded($comments, $threshold) { + foreach ($comments as $comment) { + comment_view($comment, comment_links($comment, 0), comment_visible($comment, $threshold)); } } function comment_thread_min($comments, $threshold, $pid = 0) { - global $user; + // this is an inner loop, so it's worth some optimization + // from slower to faster foreach ($comments as $comment) { - if ($comment->pid == $pid) { + #for ($n=0; $npid == $pid) && (comment_visible($comment, $threshold))) { print "

    "; } } } -function comment_thread_max($comments, $threshold, $pid = 0, $level = 0) { - global $user; - +function comment_thread_max($comment, $threshold, $level = 0) { /* ** We had quite a few browser specific issues: expanded comments below ** the top level got truncated on the right hand side. A range of @@ -452,128 +1234,215 @@ function comment_thread_max($comments, $threshold, $pid = 0, $level = 0) { ** in terms of speed and size. */ - foreach ($comments as $comment) { - if ($comment->pid == $pid) { - if ($level) { - print "
     \n"; - } - comment_view($comment, comment_links($comment, 0)); - if ($level) { - print "
    \n"; - } + if ($level) { + print "
     \n"; + } - comment_thread_max($comments, $threshold, $comment->cid, $level + 1); - } + comment_view($comment, comment_links($comment, 0), comment_visible($comment, $threshold)); + + if ($level) { + print "
    \n"; } } -function comment_render($nid, $cid = 0) { - global $user, $theme, $mode, $order, $threshold; +function comment_post_forbidden() { + global $user; + if ($user->uid) { + return t("You can't post comments."); + } + else { + return t("Please %a, or %b, to add comments.", array("%a" => lm(t("login"), array("mod" => "user")), "%b" => lm(t("register"), array("mod" => "user", "op" => "register")))); + } +} - if (user_access("access comments")) { +/** +*** misc functions: helpers, privates, history, search +**/ - /* - ** Pre-process variables: - */ - if (empty($nid)) { - $nid = 0; +function comment_visible($comment, $threshold = 0) { + if ($comment->score >= $threshold) { + return 1; + } + else { + return 0; + } +} + +function comment_moderate() { + global $moderation, $user; + + if ($moderation) { + $result = db_query("SELECT mid, value FROM moderation_roles WHERE rid = '%d'", $user->rid); + while ($mod = db_fetch_object($result)) { + $votes[$mod->mid] = $mod->value; } - if (empty($mode)) { - $mode = $user->uid ? $user->mode : variable_get("default_comment_mode", 4); - } + $node = node_load(array("nid" => db_result(db_query("SELECT nid FROM comments WHERE cid = '%d'", key($moderation))))); - if (empty($order)) { - $order = $user->uid ? $user->sort : variable_get("default_comment_order", 1); - } + if (user_access("administer comments") || comment_user_can_moderate($node)) { + foreach ($moderation as $cid => $vote) { + if ($vote) { + if (($vote == 'offline') && (user_access("administer comments"))) { + db_query("UPDATE comments SET status = 1 WHERE cid = '%s'", $cid); + watchdog("special", "comment: unpublished comment #". $cid); - if (empty($threshold)) { - // $threshold = $user->uid ? $user->threshold : variable_get("default_comment_threshold", 3); - $threshold = 0; - } + /* + ** Fire a hook + */ - print "\n"; - print "
    "comment"), "module") ."\">\n"; - print form_hidden("nid", $nid); + module_invoke_all("comment", "unpublish", $cid); + } + else { + $comment = db_fetch_object(db_query("SELECT * FROM comments WHERE cid = '%d'", $cid)); + $users = unserialize($comment->users); + if ($user->uid != $comment->uid && !(comment_already_moderated($user->uid, $comment->users))) { + $users[$user->uid] = $vote; + $tot_score = 0; + foreach ($users as $uid => $vote) { + if ($uid) { + $tot_score = $tot_score + $votes[$vote]; + } + else { + // vote 0 is the start value + $tot_score = $tot_score + $vote; + } + } + $new_score = round($tot_score / count($users)); + db_query("UPDATE comments SET score = '$new_score', users = '%s' WHERE cid = '%d'", serialize($users), $cid); - /* - ** Render control panel: - */ + /* + ** Fire a hook + */ - if (comment_num_all($nid)) { - $theme->box(t("Control panel"), $theme->comment_controls($threshold, $mode, $order)); - } - - if ($cid) { - $result = db_query("SELECT c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '$cid' GROUP BY c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name"); - if ($comment = db_fetch_object($result)) { - comment_view($comment, comment_links($comment)); - } - } - - if ($cid) { - $result = comment_query($nid, $order, $cid); - } - else { - $result = comment_query($nid, $order); - } - - if ($mode == 1) { - if (db_num_rows($result)) { - print "\n"; - print " \n"; - while ($comment = db_fetch_object($result)) { - if (comment_visible($comment, $threshold)) { - print " \n"; + module_invoke_all("comment", "moderate", $cid, $vote); + } } } - print "
    ". t("Subject") ."". t("Author") ."". t("Date") ."
    ". l(check_output($comment->subject), array("id" => $comment->nid, "cid" => $comment->cid), "node", $comment->cid) ."". format_name($comment) ."". format_date($comment->timestamp, "small") ."
    \n"; } } - else if ($mode == 2) { - while ($comment = db_fetch_object($result)) { - comment_view($comment, (comment_visible($comment, $threshold) ? comment_links($comment, 0) : 0)); - } - } - else if ($mode == 3) { - while ($comment = db_fetch_object($result)) { - $comments[] = $comment; - } + } +} - if ($comments) { - if ($cid) { - comment_thread_min($comments, $threshold, $cid, 1); - } - else { - comment_thread_min($comments, $threshold); - } - } - } - else { - while ($comment = db_fetch_object($result)) { - $comments[] = $comment; - } +function comment_settings($mode, $order, $threshold, $comments_per_page) { + global $user; - if ($comments) { - if ($cid) { - comment_thread_max($comments, $threshold, $cid, 1); - } - else { - comment_thread_max($comments, $threshold, 0, 0); - } - } - } + if ($user->uid) { + $user = user_save($user, array("mode" => $mode, "sort" => $order, "threshold" => $threshold, "comments_per_page" => $comments_per_page)); + } +} - print "
    "; +function comment_num_all($nid) { + $comment = db_fetch_object(db_query("SELECT COUNT(c.nid) AS number FROM node n LEFT JOIN comments c ON n.nid = c.nid WHERE n.nid = '%d' AND c.status = 0 GROUP BY n.nid", $nid)); + return $comment->number ? $comment->number : 0; +} +function comment_num_replies($id) { + $result = db_query("SELECT COUNT(cid) FROM comments WHERE pid = '%d' AND status = 0", $id); + return ($result) ? db_result($result, 0) : 0; +} + +/** + * get number of new comments and id of first new + * + * @param $nid node-id to count comments for + * @param $timestamp time to count from (defaults to time of last user access to node) + * + * @return array("count_new" => $count_new, "id_first_new" => $id_first_new) + */ +function comment_num_new($nid, $timestamp = 0) { + global $user; + + if ($user->uid) { /* - ** Tag the node's comments as being read: + ** Retrieve the timestamp at which the current user last viewed the + ** specified node. */ - comment_tag_new($nid); + if (!$timestamp) { + $history = db_fetch_object(db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '%d'", $nid)); + $timestamp = $history->timestamp ? $history->timestamp : 0; + } + + /* + ** Use the timestamp to retrieve the number of new comments and the + ** ID of first new comment. + */ + + $result = db_fetch_array(db_query("SELECT COUNT(c.cid) AS count_new, MIN(c.cid) AS id_first_new FROM node n LEFT JOIN comments c ON n.nid = c.nid WHERE n.nid = '%d' AND timestamp > '%d' AND c.status = 0", $nid, $timestamp)); + + return $result; } + else { + return array(0, 0); + } + +} + +function comment_tag_new($nid) { + global $user; + + if ($user->uid) { + $nid = check_query($nid); + + $result = db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '%d'", $nid); + if (db_fetch_object($result)) { + db_query("UPDATE history SET timestamp = '%d' WHERE uid = '$user->uid' AND nid = '%d'", time(), $nid); + } + else { + db_query("INSERT INTO history (uid, nid, timestamp) VALUES ('$user->uid', '%d', '%d')", $nid, time()); + } + } +} + +function comment_is_new($comment) { + global $user; + static $date; + + if (!$date[$comment->nid]) { + if ($user->uid) { + $history = db_fetch_object(db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '%d'", $comment->nid)); + $date[$comment->nid] = $history->timestamp ? $history->timestamp : 0; + } + else { + $date[$comment->nid] = time(); + } + } + + if ($comment->timestamp > $date[$comment->nid]) { + return 1; + } + else { + return 0; + } +} + +function comment_thread_structure($comments, $pid, $depth, $structure) { + $depth++; + + foreach ($comments as $key => $comment) { + if ($comment->pid == $pid) { + $structure[$comment->cid] = $depth; + $structure = comment_thread_structure($comments, $comment->cid, $depth, $structure); + } + } + + return $structure; +} + +function comment_user_can_moderate($node) { + global $user; + return (user_access("moderate comments")); + // TODO: || (($user->uid == $node->uid) && user_access("moderate comments in owned node"))); +} + +function comment_already_moderated($uid, $users) { + $comment_users = unserialize($users); + if (!$comment_users) { + $comment_users = array(); + } + return in_array($uid, array_keys($comment_users)); } function comment_search($keys) { @@ -602,198 +1471,11 @@ function comment_search($keys) { ** identifier which is currently used byt the comment module. */ - $find = do_search(array("keys" => $keys, "type" => "comment", "select" => "select s.lno as lno, c.nid as nid, c.subject as title, c.timestamp as created, u.uid as uid, u.name as name, s.count as count FROM search_index s, comments c LEFT JOIN users u ON c.uid = u.uid WHERE s.lno = c.cid AND s.type = 'comment' AND s.word like '%'")); + $find = do_search(array("keys" => $keys, "type" => "comment", "select" => "select s.lno as lno, c.nid as nid, c.subject as title, c.timestamp as created, u.uid as uid, u.name as name, s.count as count FROM search_index s, comments c LEFT JOIN users u ON c.uid = u.uid WHERE s.lno = c.cid AND s.type = 'comment' AND c.status = 0 AND s.word like '%'")); return $find; } -function comment_perm() { - return array("access comments", "post comments", "administer comments"); -} - -function comment_link($type, $node = 0, $main = 0) { - - if ($type == "admin" && user_access("administer comments")) { - $links[] = la(t("comments"), array("mod" => "comment")); - } - - if ($type == "node" && $node->comment) { - - if ($main) { - - /* - ** Main page: display the number of comments that have been posted. - */ - - if (user_access("access comments")) { - $all = comment_num_all($node->nid); - $new = comment_num_new($node->nid); - - $links[] = l(format_plural($all, "comment", "comments") . ($new ? ", $new ". t("new") : ""), array("id" => $node->nid), "node", "comment", array("title" => t("View this posting and all of its comments."))); - } - } - else { - /* - ** Node page: add a "post comment" link if the user is allowed to - ** post comments and if this node is not read-only - */ - - if (user_access("post comments")) { - if ($node->comment == 2) { - $links[] = lm(t("add new comment"), array("mod" => "comment", "op" => "reply", "id" => $node->nid), "comment", array("title" => t("Share your thoughts and opinions related to this posting."))); - } - else { - $links[] = t("This discussion is closed: you can't post new comments."); - } - } - } - } - - return $links ? $links : array(); -} - -function comment_node_link($node) { - - if (user_access("administer comments") && comment_num_all($node->nid)) { - - /* - ** Edit comments: - */ - - $result = db_query("SELECT c.cid, c.subject, u.uid, u.name FROM comments c LEFT JOIN users u ON u.uid = c.uid WHERE nid = '$node->nid' ORDER BY c.timestamp"); - - $output .= "

    ". t("Edit comments") ."

    "; - $output .= ""; - $output .= " "; - - while ($comment = db_fetch_object($result)) { - $output .= ""; - } - - $output .= "
    titleauthoroperations
    ". l($comment->subject, array("id" => $node->nid, "cid" => $comment->cid), "node", $comment->cid) ."". format_name($comment) ."". l(t("view comment"), array("id" => $node->nid, "cid" => $comment->cid), $comment->cid) ."". la(t("edit comment"), array("mod" => "comment", "op" => "edit", "id" => $comment->cid)) ."". la(t("delete comment"), array("mod" => "comment", "op" => "delete", "id" => $comment->cid)) ."
    "; - - return $output; - } -} - - -function comment_save($id, $edit) { - db_query("UPDATE comments SET subject = '%s', comment = '%s' WHERE cid = '$id'", filter($edit["subject"]), filter($edit["comment"])); - watchdog("special", "comment: modified '". $edit["subject"] ."'"); -} - -function comment_page() { - global $theme, $op, $edit, $id, $pid, $cid; - - switch ($op) { - case "edit": - $theme->header(); - comment_edit(check_query($id)); - $theme->footer(); - break; - case "reply": - $theme->header(); - comment_reply(check_query($pid), check_query($id)); - $theme->footer(); - break; - case t("Preview comment"): - $theme->header(); - comment_preview($edit); - $theme->footer(); - break; - case t("Post comment"): - comment_post($edit); - break; - case t("Update settings"): - global $mode, $order, $threshold; - comment_settings(check_query($mode), check_query($order), check_query($threshold)); - drupal_goto(drupal_url(array("id" => $edit["nid"], "mode" => $mode, "order" => $order), "node")); - break; - default: - } -} - -function comment_admin_edit($id) { - - $result = db_query("SELECT c.*, u.name, u.uid FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '$id'"); - $comment = db_fetch_object($result); - - $form .= form_item(t("Author"), format_name($comment)); - $form .= form_textfield(t("Subject"), "subject", $comment->subject, 70, 128); - $form .= form_textarea(t("Comment"), "comment", $comment->comment, 70, 15); - $form .= form_hidden("cid", $id); - $form .= form_submit(t("Submit")); - $form .= form_submit(t("Delete")); - - return form($form); -} - -function comment_admin_overview() { - $result = db_query("SELECT c.*, u.name, u.uid FROM comments c LEFT JOIN users u ON u.uid = c.uid ORDER BY timestamp DESC LIMIT 50"); - - $output .= "\n"; - $output .= " \n"; - while ($comment = db_fetch_object($result)) { - $output .= " \n"; - } - $output .= "
    subjectauthordateoperations
    ". l(check_output($comment->subject), array("id" => $comment->nid, "cid" => $comment->cid, "pid" => $comment->pid), "node", $comment->cid) ."". format_name($comment) ."". format_date($comment->timestamp, "small") ."". la(t("edit comment"), array("mod" => comment, "op" => edit, "id" => $comment->cid)) ."". la(t("delete comment"), array("mod" => "comment", "op" => "delete", "id" => $comment->cid)) ."
    \n"; - - return $output; -} - -function comment_delete($edit) { - - if ($edit["confirm"]) { - db_query("DELETE FROM comments WHERE cid = '%s'", $edit["cid"]); - watchdog("special", "comment: deleted comment #". $edit["cid"]); - } - else { - $output .= form_item(t("Confirm deletion"), ""); - $output .= form_hidden("cid", $edit["cid"]); - $output .= form_hidden("confirm", 1); - $output .= form_submit(t("Delete")); - $output = form($output); - } - - return $output; -} - -function comment_admin() { - global $op, $id, $edit, $mod, $keys, $order; - - if (user_access("administer comments")) { - - print "". la(t("overview"), array("mod" => "comment")) ." | ". la(t("search comment"), array("mod" => "comment", "op" => "search")) ." | ". la(t("help"), array("mod" => "comment", "op" => "help")) ."
    \n"; - - switch ($op) { - case "help": - print comment_help(); - break; - case "edit": - print comment_admin_edit($id); - break; - case "search": - print search_type("comment", drupal_url(array("mod" => "comment", "op" => "search"), "admin")); - break; - case "delete": - print comment_delete(array("cid" => $id)); - break; - case t("Delete"): - print comment_delete($edit); - break; - case t("Submit"): - print status(comment_save(check_query($id), $edit)); - print comment_admin_overview(); - break; - default: - print comment_admin_overview(); - } - } - else { - print message_access(); - } -} - function comment_update_index() { /* @@ -811,7 +1493,25 @@ function comment_update_index() { ** last run date for the comments update. */ - return array("last_update" => "comment_cron_last", "node_type" => "comment", "select" => "SELECT c.cid as lno, c.subject as text1, c.comment as text2 FROM comments c WHERE timestamp > ". variable_get("comment_cron_last", 1)); + return array("last_update" => "comment_cron_last", "node_type" => "comment", "select" => "SELECT c.cid as lno, c.subject as text1, c.comment as text2 FROM comments c WHERE c.status = 0 AND timestamp > ". variable_get("comment_cron_last", 1)); } -?> \ No newline at end of file +// backward compatibility with some themes +function comment_moderation() { +} + +// this will go away as soon as theme_invoke is committed +function comment_theme_invoke() { + global $theme; + + $args = func_get_args(); + $function = array_shift($args); + + if (method_exists($theme, $function)) { + return call_user_method_array($function, $theme, $args); + } + else { + return call_user_func_array($function, $args); + } +} +?> diff --git a/modules/comment/comment.module b/modules/comment/comment.module index 1d8930f21bd..72b690b069d 100644 --- a/modules/comment/comment.module +++ b/modules/comment/comment.module @@ -1,93 +1,58 @@ "Flat list - collapsed", 2 => "Flat list - expanded", 3 => "Threaded list - collapsed", 4 => "Threaded list - expanded"); -$GLOBALS["corder"] = array(1 => "Date - newest first", 2 => "Date - oldest first"); +$GLOBALS["cmodes"] = array(1 => t("Flat list - collapsed"), 2 => t("Flat list - expanded"), 3 => t("Threaded list - collapsed"), 4 => t("Threaded list - expanded")); +$GLOBALS["corder"] = array(1 => t("Date - newest first"), 2 => t("Date - oldest first")); function comment_help() { $output .= "

    The comment module enables users to submit posts that are directly associated with a piece of content. These associated posts are called comments. Comments may be threaded, which means that Drupal keeps track of multiple subconversations around a piece of content. Threading helps to keep the comment conversation more organized. Users are presented with several ways to view the comment conversation, and if desired, users may easily choose a flat presentation of comments instead of threaded. Further, users may choose to order their comments view by newest first or by oldest first. Finally, users may view a folded list or an expanded list of comments. Folded limits the comment display to subject only. Drupal remembers the comment view preference of each user whenever he changes a view setting.

    "; + $output .= "

    Users may also choose to view a maximum number of comments; if there are more comments, there will be a navigator to browse through pages.

    "; $output .= "

    Since a busy site generates lots of comments, Drupal takes care to present a personalized view of comments for each user. The home page lists displays the number of read and unread comments for a given post for the current user. Also, the tracker module (when installed) displays all recent comments on the site. Finally, comments which the user has not yet read are highlighted with a red star (this graphic may depend on the current theme).

    "; $output .= "

    Comments behave like other user submissions in Drupal. Specifically, ". la("filters", array("mod" => "system", "type" => "filter")) ." like smileys and HTML work fine if the administrator has enabled them. Also, throttles are usually enabled to prevent a single user from spamming the web site with too many comments in a short period of time.

    "; - $output .= "

    Administrators may control which persons are allowed to submit and administer comments. These controls appear in the ". la("user permissions", array("mod" => "user", "op" => "permission")) ." administration page. Additionally, administrators may edit or search through comments on the ". la("comments admininistration page", array("mod" => "comment")) .", as well as set the default display view for new users.

    "; + $output .= "

    Administrators may control which persons are allowed to submit and administer comments. These controls appear in the ". la("user permissions", array("mod" => "user", "op" => "permission")) ." administration page. Additionally, administrators may edit or search through comments on the ". la("comments admininistration page", array("mod" => "comment")) .", as well as set the default display view for new users. Administrators can also state whether a certain role will have their comments published immediatly, or just put in a queue to be reviewed.

    "; + $output .= "

    If you really have a lot of comments, you can enable moderation. You have to give some roles permission to moderate, then setup some \"moderation votes\"; these votes will appear to moderators on a select box near the comment. You also have to assign, for every role and every vote, a value, which can be either positive or negative; use the moderation matrix to do this. This way some roles will have more \"weight\" in their moderation, if any, if you want. If you set a value to 0, that vote won't be available to that role. When a user moderates, that value will be added or subtracted to the score of that comment. Finally, you have to setup the comment filters: these will be the threshold values users will see on the comment control panel.

    "; return $output; } -function comment_system($field){ +function comment_system($field) { $system["description"] = t("Enables user to comment on content (nodes)."); return $system[$field]; } -function comment_settings($mode, $order, $threshold) { - global $user; +function comment_conf_options() { + global $cmodes, $corder; - if ($user->uid) { - $user = user_save($user, array("mode" => $mode, "sort" => $order, "threshold" => $threshold)); + $output .= form_select("Default display mode", "comment_default_mode", variable_get("comment_default_mode", 4), $cmodes, "The default mode in which comments are displayed."); + $output .= form_select("Default display order", "comment_default_order", variable_get("comment_default_order", 1), $corder, "The default order in which comments are displayed."); + $output .= form_textfield("Default comments per page", "comment_default_per_page", variable_get("comment_default_per_page", "50"), 5, 5, "Default number of comments for each page; more comments are distributed in several pages."); + + $result = db_query("SELECT fid, filter FROM moderation_filters"); + while ($filter = db_fetch_object($result)) { + $thresholds[$filter->fid] = ($filter->filter); } + + $output .= form_select("Default threshold", "comment_default_threshold", variable_get("comment_default_threshold", 0), $thresholds, "The default threshold in which comments are displayed."); + + $output .= form_select("Preview comment", "comment_preview", variable_get("comment_preview", 1), array("optional", "required"), "Must users preview comments before submitting?"); + $output .= form_select("New comment form", "comment_new_form", variable_get("comment_new_form", 0), array("disabled", "enabled"), "New comment form in the node page?"); + $output .= form_select("Comment controls", "comment_controls", variable_get("comment_controls", 0), array("above comments", "below comments", "above and below"), "Position of the comment controls box."); + + return $output; } -function comment_num_all($nid) { - $comment = db_fetch_object(db_query("SELECT COUNT(c.nid) AS number FROM node n LEFT JOIN comments c ON n.nid = c.nid WHERE n.nid = '$nid' GROUP BY n.nid")); - return $comment->number ? $comment->number : 0; -} - -function comment_num_new($nid) { - global $user; - - if ($user->uid) { - - /* - ** Retrieve the timestamp at which the current user last viewed - ** the specified node and use this timestamp to find the number - ** of new comments. - */ - - $history = db_fetch_object(db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '%s'", $nid)); - $comment = db_fetch_object(db_query("SELECT COUNT(c.nid) AS number FROM node n LEFT JOIN comments c ON n.nid = c.nid WHERE n.nid = '%s' AND timestamp > '". ($history->timestamp ? $history->timestamp : 0) ."' GROUP BY n.nid", $nid)); - - return $comment->number ? $comment->number : 0; - } - else { - return 0; - } - -} - -function comment_tag_new($nid) { - global $user; - - if ($user->uid) { - $nid = check_query($nid); - - $result = db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '$nid'"); - if (db_fetch_object($result)) { - db_query("UPDATE history SET timestamp = '". time() ."' WHERE uid = '$user->uid' AND nid = '$nid'"); - } - else { - db_query("INSERT INTO history (uid, nid, timestamp) VALUES ('$user->uid', '$nid', '". time() ."')"); - } - } -} - -function comment_is_new($comment) { - global $user; - static $date; - - if (!$date[$comment->nid]) { - if ($user->uid) { - $history = db_fetch_object(db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '$comment->nid'")); - $date[$comment->nid] = $history->timestamp ? $history->timestamp : 0; - } - else { - $date[$comment->nid] = time(); - } - } - - if ($comment->timestamp > $date[$comment->nid]) { - return 1; - } - else { - return 0; +function comment_user($type, $edit, &$user) { + switch ($type) { + case "view_public": + return form_item(t("Signature"), check_output($user->signature, 1)); + case "view_private": + return form_item(t("Signature"), check_output($user->signature, 1)); + case "edit_form": + // when user tries to edit his own data + return form_textarea(t("Signature"), "signature", $edit["signature"], 70, 3, t("Your signature will be publicly displayed at the end of your comments.") ."
    ". t("Allowed HTML tags") .": ". htmlspecialchars(variable_get("allowed_html", ""))); + case "edit_validate": + // validate user data editing + return array("signature" => filter($edit["signature"])); } } @@ -128,7 +93,7 @@ function comment_form($edit) { $form .= form_hidden("pid", $edit["pid"]); $form .= form_hidden("nid", $edit["nid"]); - if (!$edit["comment"]) { + if (!$edit["comment"] && variable_get("comment_preview", 1)) { $form .= form_submit(t("Preview comment")); } else { @@ -136,13 +101,13 @@ function comment_form($edit) { $form .= form_submit(t("Post comment")); } - return form($form); + return form($form, "post", drupal_url(array("mod" => "comment", "op" => "reply", "id" => $edit["nid"]), "module")); } function comment_edit($cid) { global $user; - $comment = db_fetch_object(db_query("SELECT c.*, u.name, u.uid FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '$cid'")); + $comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name, u.data FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%d' AND c.status != 2", $cid)); if (comment_access("edit", $comment)) { comment_preview(object2array($comment)); @@ -153,15 +118,24 @@ function comment_reply($pid, $nid) { global $theme; if (user_access("access comments")) { + + /* + ** Show comment + */ + if ($pid) { - $comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%s'", $pid)); - comment_view($comment, t("reply to this comment")); + $comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name, u.data FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%d' AND c.status = 0", $pid)); + comment_view($comment); } else { node_view(node_load(array("nid" => $nid))); $pid = 0; } + /* + ** If possible, show reply form + */ + if (node_comment_mode($nid) == 1) { $theme->box(t("Reply"), t("This discussion is closed: you can't post new comments.")); } @@ -185,29 +159,24 @@ function comment_preview($edit) { } /* - ** Attach the user information: + ** Attach the user and time information: */ $comment->uid = $user->uid; $comment->name = $user->name; - - /* - ** Attach the time: - */ - $comment->timestamp = time(); /* ** Preview the comment: */ - comment_view($comment, t("reply to this comment")); + comment_view($comment); $theme->box(t("Reply"), comment_form($edit)); if ($edit["pid"]) { - $comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%s'", $edit["pid"])); - comment_view($comment, t("reply to this comment")); + $comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name, u.data FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%d' AND c.status = 0", $edit["pid"])); + comment_view($comment); } else { node_view(node_load(array("nid" => $edit["nid"]))); @@ -216,10 +185,9 @@ function comment_preview($edit) { } function comment_post($edit) { - global $theme, $user; + global $user; - $context->nid = $edit["nid"]; - if (user_access("post comments", $context) && node_comment_mode($edit["nid"]) == 2) { + if (user_access("post comments") && node_comment_mode($edit["nid"]) == 2) { /* ** Validate the comment's subject. If not specified, extract @@ -234,15 +202,20 @@ function comment_post($edit) { $edit["comment"] = filter($edit["comment"]); + if ($edit["comment"] == "") { + return array(t("Empty comment"), t("The comment you submitted is empty.")); + } + /* ** Check for duplicate comments. Note that we have to use the ** validated/filtered data to perform such check. */ - $duplicate = db_result(db_query("SELECT COUNT(cid) FROM comments WHERE pid = '%s' AND nid = '%s' AND subject = '%s' AND comment = '%s'", $edit["pid"], $edit["nid"], $edit["subject"], $edit["comment"]), 0); + $duplicate = db_result(db_query("SELECT COUNT(cid) FROM comments WHERE pid = '%d' AND nid = '%d' AND subject = '%s' AND comment = '%s'", $edit["pid"], $edit["nid"], $edit["subject"], $edit["comment"]), 0); if ($duplicate != 0) { watchdog("warning", "comment: duplicate '". $edit["subject"] ."'"); + return array(t("Duplicate comment"), t("The comment you submitted has already been inserted.")); } else { @@ -254,7 +227,13 @@ function comment_post($edit) { ** user. */ - db_query("UPDATE comments SET subject = '%s', comment = '%s' WHERE cid = '%s' AND uid = '$user->uid'", $edit["subject"], $edit["comment"], $edit["cid"]); + db_query("UPDATE comments SET subject = '%s', comment = '%s' WHERE cid = '%d' AND uid = '$user->uid'", $edit["subject"], $edit["comment"], $edit["cid"]); + + /* + ** Fire a hook + */ + + module_invoke_all("comment", "update", $edit); /* ** Add entry to the watchdog log: @@ -274,7 +253,20 @@ function comment_post($edit) { ** Add the comment to database: */ - db_query("INSERT INTO comments (nid, pid, uid, subject, comment, hostname, timestamp) VALUES ('%s', '%s', '$user->uid', '%s', '%s', '%s', '%s')", $edit["nid"], $edit["pid"], $edit["subject"], $edit["comment"], getenv("REMOTE_ADDR"), time()); + $status = user_access("post comments without approval") ? 0 : 1; + $roles = variable_get("comment_roles", array()); + $score = $roles[$user->rid] ? $roles[$user->rid] : 0; + $users = serialize(array(0 => $score)); + + $edit["cid"] = db_next_id("comments"); + + db_query("INSERT INTO comments (cid, nid, pid, uid, subject, comment, hostname, timestamp, status, score, users) VALUES ('%d', '%d', '%d', '$user->uid', '%s', '%s', '%s', '%s', '%s', $score, '%s')", $edit["cid"], $edit["nid"], $edit["pid"], $edit["subject"], $edit["comment"], getenv("REMOTE_ADDR"), time(), $status, $users); + + /* + ** Tell the other modules a new comment has been submitted: + */ + + module_invoke_all("comment", "insert", $edit); /* ** Add entry to the watchdog log: @@ -289,92 +281,25 @@ function comment_post($edit) { cache_clear(); + /* + ** TODO: we'd prefer to invalidate the page or pages with the newly + ** inserted comment. + ** + ** db_query("DELETE FROM cache WHERE cid LIKE '%s'", "%id=".$edit["nid"]."%"); + */ } } + else { + watchdog("error", "comment: unauthorized comment submitted or comment submitted to a closed node '". $edit["subject"] ."'"); + return array(t("Error"), t("You are not authorized to post comments, or this node doesn't accept new comments.")); + } /* - ** Redirect the user the node he commented on: + ** Redirect the user the node he commented on, or explain queue */ - drupal_goto(drupal_url(array("id" => $edit["nid"]), "node")); - -} - -function comment_num_replies($id) { - - $result = db_query("SELECT COUNT(cid) FROM comments WHERE pid = '$id'"); - return ($result) ? db_result($result, 0) : 0; - -} - -function comment_moderation($comment) { - global $user; - - // XXX: disabled for now - return ""; - - $values = array("--", "1", "2", "3", "4", "5"); - - $moderate = db_fetch_object(db_query("SELECT * FROM moderate WHERE cid = '$comment->cid' AND uid = '$user->uid'")); - - foreach ($values as $key => $value) { - $options .= " \n"; - } - - $output .= "
    ". ($comment->score ? $comment->score : "--") ." / $comment->votes"; - - return $output; -} - -function comment_threshold($threshold) { - // XXX: disabled for now - return ""; - - for ($i = 0; $i < 6; $i++) $options .= " "; - return "\n"; -} - -function comment_mode($mode) { - global $cmodes; - - foreach ($cmodes as $key => $value) $options .= " \n"; - return "\n"; -} - -function comment_order($order) { - global $corder; - - foreach ($corder as $key=>$value) $options .= " \n"; - return "\n"; -} - -function comment_query($nid, $order, $pid = -1) { - - $query .= "SELECT c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.nid = '$nid'"; - - if ($pid >= 0) { - $query .= " AND pid = '$pid'"; - } - - $query .= " GROUP BY c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name"; - - if ($order == 1) { - $query .= " ORDER BY c.timestamp DESC"; - } - else if ($order == 2) { - $query .= " ORDER BY c.timestamp"; - } - - return db_query($query); - -} - -function comment_visible($comment, $threshold = 0) { - if ($comment->votes == 0 || $comment->score >= $threshold) { - return 1; - } - else { - return 0; + if ($status == 1) { + return array(t("Comment queued"), t("Your comment has been queued for moderation by site administrators and will be published after approval.")); } } @@ -383,66 +308,923 @@ function comment_links($comment, $return = 1) { $links = array(); + /* + ** If we are viewing just this comment, we link back to the node + */ + if ($return) { $links[] = l("type;\">". t("return") ."", array("id" => $comment->nid), "node", $comment->cid); } + /* + ** Admin link + */ + if (user_access("administer comments")) { $links[] = la("type;\">". t("administer") ."", array("mod" => "comment", "op" => "edit", "id" => $comment->cid)); } /* - ** Here we should check if this node has read-only comments, but we - ** already check on submit and this way we save a query. It's just - ** a cosmetic issue. otherwise just uncomment the next line and - ** related bracket some lines below. + ** Possibly show edit and reply links */ - //if (node_comment_mode($comment->nid)) { - if (user_access("post comments")) { - if (comment_access("edit", $comment)) { - $links[] = lm("type\">". t("edit your comment") ."", array("mod" => "comment", "op" => "edit", "id" => $comment->cid), "", array("title" => t("Make changes to your comment."))); - } - else { + if (node_comment_mode($comment->nid) == 2) { + if (user_access("post comments")) { + if (comment_access("edit", $comment)) { + $links[] = lm("type\">". t("edit your comment") ."", array("mod" => "comment", "op" => "edit", "id" => $comment->cid), "", array("title" => t("Make changes to your comment."))); + } $links[] = lm("type;\">". t("reply to this comment") ."", array("mod" => "comment", "op" => "reply", "id" => $comment->nid, "pid" => $comment->cid), "", array("title" => t("Reply to this comment."))); } + else { + $links[] = comment_theme_invoke("comment_post_forbidden"); + } + } + + if ($moderation = comment_moderation_form($comment)) { + $links[] = $moderation; } - //} return $theme->links($links); } -function comment_view($comment, $folded = 0) { - global $theme, $id; +function comment_view($comment, $links = "", $visible = 1) { + + /* + ** Switch to folded/unfolded view of the comment + */ if (comment_is_new($comment)) { - $comment->subject = "$comment->subject *"; + $comment->new = 1; } - if ($folded) { - $theme->comment($comment, $folded); + print "cid\">\n"; + + if ($visible) { + comment_theme_invoke("comment", $comment, $links); } else { - print "
  • ". l(check_output($comment->subject), array("id" => $comment->nid, "cid" => $comment->cid), "node", $comment->cid, array("title" => t("Read comment."))) ." ". t("by") ." ". format_name($comment) ."
  • \n"; + comment_theme_invoke("comment_folded", $comment); + } +} + + +function comment_render($node, $cid = 0) { + global $user, $theme, $mode, $order, $threshold, $comment_page; + + if (user_access("access comments")) { + + /* + ** Pre-process variables: + */ + + $nid = $node->nid; + if (empty($nid)) { + $nid = 0; + } + + if (empty($mode)) { + $mode = $user->mode ? $user->mode : variable_get("comment_default_mode", 4); + } + + if (empty($order)) { + $order = $user->sort ? $user->sort : variable_get("comment_default_order", 1); + } + + if (empty($threshold)) { + $threshold = $user->uid ? $user->threshold : variable_get("comment_default_threshold", 0); + } + $threshold_min = db_result(db_query("SELECT minimum FROM moderation_filters WHERE fid = '%d'", $threshold)); + + if (empty($comment_page)) { + $comment_page = 1; + } + + $comments_per_page = $user->comments_per_page ? $user->comments_per_page : variable_get("comment_default_per_page", "50"); + + print "
    \n"; + + + if ($cid) { + + /* + ** Single comment view + */ + + print "
    "comment"), "module") ."\">\n"; + print form_hidden("nid", $nid); + + $result = db_query("SELECT c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name, u.data, c.score, c.users FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%d' AND c.status = 0 GROUP BY c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name, u.data, c.score, c.users", $cid); + + if ($comment = db_fetch_object($result)) { + comment_view($comment, comment_links($comment)); + } + + if ((user_access("administer comments") || comment_user_can_moderate($node)) && $user->uid != $comment->uid && !(comment_already_moderated($user->uid, $comment->users))) { + print "
    ". form_submit(t("Moderate comment")) ."

    "; + } + print "
    "; + } + else { + + /* + ** Multiple comments view + */ + + $query .= "SELECT c.cid as cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name, u.data, c.score, c.users FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.nid = '". check_query($nid) ."' AND c.status = 0"; + + if ($cid) { + $query .= " AND pid = '". check_query($cid) ."'"; + } + + $query .= " GROUP BY c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name, u.data, c.score, c.users"; + + if ($order == 1) { + $query .= " ORDER BY c.timestamp DESC"; + } + else if ($order == 2) { + $query .= " ORDER BY c.timestamp"; + } + + /* + ** Start a form, to use with comment control and moderation + */ + + $result = db_query($query); + $comment_num = db_num_rows($result); + + if ($comment_num && ((variable_get("comment_controls", 0) == 0) || (variable_get("comment_controls", 0) == 2))) { + print "
    "comment"), "module") ."\">\n"; + $theme->box(t("Control panel"), comment_theme_invoke("comment_controls", $threshold, $mode, $order, $nid, $comment_page, $comment_num, $comments_per_page)); + print form_hidden("nid", $nid); + print "
    "; + } + + print "
    "comment"), "module") ."\">\n"; + print form_hidden("nid", $nid); + + if ($comment_num) { + if ($mode == 1) { + /* + ** Flat collapsed + */ + + while ($comment = db_fetch_object($result)) { + $comments[$comment->cid] = $comment; + } + comment_theme_invoke("comment_flat_collapsed", $comments, $threshold_min); + } + else if ($mode == 2) { + /* + ** Flat expanded + ** + ** We page using PHP, not using SQL because otherwise we'd + ** have to use two queries; one for each comment and one for + ** the paged comments. In method 1-3 we take all results + ** anyway, wheras in method 4 we need every result to create + ** proper pages. It is here where we lose more, in fact for + ** higher pages we transfer unneeded data from the db and + ** the web server. + ** + ** TODO: the comment above is a bit cryptic. Mind to make it + ** a bit more verbose/explanatory? + */ + + $comment_num = 0; + $page = 1; + while ($comment = db_fetch_object($result)) { + if ($page == $comment_page) { + $comments[$comment->cid] = $comment; + } + $comment_num++; + if ($comment_num == $comments_per_page) { + if ($page == $comment_page) { + break; + } + else { + $comment_num = 0; + $page++; + } + } + + if ($user->uid != $comment->uid && !(comment_already_moderated($user->uid, $comment->users))) { + $show_moderate_button = 1; + } + } + + comment_theme_invoke("comment_flat_expanded", $comments, $threshold_min); + + if (comment_user_can_moderate($node) && $show_moderate_button) { + print "
    ". form_submit(t("Moderate comments")) ."

    "; + } + } + else if ($mode == 3) { + /* + ** Threaded collapsed + */ + + while ($comment = db_fetch_object($result)) { + $comments[$comment->cid] = $comment; + } + if ($comments) { + comment_theme_invoke("comment_thread_min", $comments, $threshold_min); + } + } + else { + /* + ** Threaded expanded + */ + + while ($comment = db_fetch_object($result)) { + $comments[$comment->cid] = $comment; + + if ($user->uid != $comment->uid && !(comment_already_moderated($user->uid, $comment->users))) { + $show_moderate_button = 1; + } + } + + /* + ** Build the comment structure + */ + + $structure = comment_thread_structure($comments, 0, 0, array()); + + $comment_num = 0; + $page = 1; + foreach ($structure as $cid => $depth) { + if ($page == $comment_page) { + comment_theme_invoke("comment_thread_max", $comments[$cid], $threshold_min, $depth - 1); + } + $comment_num++; + if ($comment_num == $comments_per_page) { + if ($page == $comment_page) { + break; + } + else { + $comment_num = 0; + $page++; + } + } + } + + if (comment_user_can_moderate($node) && $show_moderate_button) { + print "
    ". form_submit(t("Moderate comments")) ."

    "; + } + } + } + + print "
    "; + + if ($comment_num && ((variable_get("comment_controls", 0) == 1) || (variable_get("comment_controls", 0) == 2))) { + print "
    "comment"), "module") ."\">\n"; + $theme->box(t("Control panel"), comment_theme_invoke("comment_controls", $threshold, $mode, $order, $nid, $comment_page, $comment_num, $comments_per_page)); + print form_hidden("nid", $nid); + print "
    "; + } + } + + /* + ** If enabled, show new comment form + */ + + if (user_access("post comments") && node_comment_mode($nid) == 2 && variable_get("comment_new_form", 0)) { + $theme->box("Post new comment", comment_form(array("nid" => $nid))); + } + + /* + ** Tag the node's comments as being read: + */ + + comment_tag_new($nid); + } +} + +function comment_perm() { + return array("access comments", "post comments", "administer comments", "moderate comments", "post comments without approval", "administer moderation"); +} + +function comment_link($type, $node = 0, $main = 0) { + + if ($type == "admin" && user_access("administer comments")) { + $links[] = la(t("comments"), array("mod" => "comment")); + } + + if ($type == "node" && $node->comment) { + + if ($main) { + + /* + ** Main page: display the number of comments that have been posted. + */ + + if (user_access("access comments")) { + $all = comment_num_all($node->nid); + $new = comment_num_new($node->nid); // array! + + if ($all) { + $links[] = l(format_plural($all, 'comment', 'comments'), array("id" => $node->nid), "node", "comment", array("title" => t('Jump to first comment of this posting.'))); + + if ($new["count_new"]) { + $links[] = l($new["count_new"] ." ". t("new"), array("id" => $node->nid), "node", $new["id_first_new"], array("title" => t('Jump to first NEW comment of this posting.'))); + } + } + else { + if (user_access("post comments")) { + $links[] = lm(t("add new comment"), array("mod" => "comment", "op" => "reply", "id" => $node->nid), "", array("title" => t("Add a new comment to this page."))); + } + else { + $links[] = comment_theme_invoke("comment_post_forbidden"); + } + } + } + } + else { + /* + ** Node page: add a "post comment" link if the user is allowed to + ** post comments and if this node is not read-only + */ + + if ($node->comment == 2) { + if (user_access("post comments")) { + $links[] = lm(t("add new comment"), array("mod" => "comment", "op" => "reply", "id" => $node->nid), "comment", array("title" => t("Share your thoughts and opinions related to this posting."))); + } + else { + $links[] = comment_theme_invoke("comment_post_forbidden"); + } + } + else { + $links[] = t("This discussion is closed: you can't post new comments."); + } + } + } + + return $links ? $links : array(); +} + +function comment_page() { + global $theme, $op, $edit, $id, $pid, $cid; + + switch ($op) { + case "edit": + $theme->header(); + comment_edit(check_query($id)); + $theme->footer(); + break; + case t("Moderate comments"): + case t("Moderate comment"): + comment_moderate($edit); + drupal_goto(drupal_url(array("id" => $edit["nid"]))); + break; + case "reply": + $theme->header(); + comment_reply(check_query($pid), check_query($id)); + $theme->footer(); + break; + case t("Preview comment"): + $theme->header(); + comment_preview($edit); + $theme->footer(); + break; + case t("Post comment"): + list($error_title, $error_body) = comment_post($edit); + if ($error_body) { + $theme->header(); + $theme->box($error_title, $error_body); + $theme->footer(); + } + else { + drupal_goto(drupal_url(array("id" => $edit["nid"]), "node")); + } + break; + case t("Update settings"): + global $mode, $order, $threshold, $comments_per_page; + comment_settings(check_query($mode), check_query($order), check_query($threshold), check_query($comments_per_page)); + drupal_goto(drupal_url(array("id" => $edit["nid"], "mode" => $mode, "order" => $order, "threshold" => $threshold, "comments_per_page" => $comments_per_page), "node")); + break; + } +} + +/** +*** admin functions +**/ + +function comment_node_link($node) { + + if (user_access("administer comments") && comment_num_all($node->nid)) { + + /* + ** Edit comments: + */ + + $result = db_query("SELECT c.cid, c.subject, u.uid, u.name FROM comments c LEFT JOIN users u ON u.uid = c.uid WHERE nid = '%d' AND c.status = 0 ORDER BY c.timestamp", $node->nid); + + $output .= "

    ". t("Edit comments") ."

    "; + $output .= ""; + $output .= " "; + + while ($comment = db_fetch_object($result)) { + $output .= ""; + } + + $output .= "
    titleauthoroperations
    ". l($comment->subject, array("id" => $node->nid, "cid" => $comment->cid), "node", $comment->cid) ."". format_name($comment) ."". l(t("view comment"), array("id" => $node->nid, "cid" => $comment->cid), "node", $comment->cid) ."". la(t("edit comment"), array("mod" => "comment", "op" => "edit", "id" => $comment->cid)) ."". la(t("delete comment"), array("mod" => "comment", "op" => "delete", "id" => $comment->cid)) ."
    "; + + return $output; + } +} + +function comment_admin_edit($id) { + + $result = db_query("SELECT c.*, u.name, u.uid FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '%d' AND c.status != 2", $id); + $comment = db_fetch_object($result); + + // if a comment is "deleted", it's deleted + if ($comment) { + $form .= form_item(t("Author"), format_name($comment)); + $form .= form_textfield(t("Subject"), "subject", $comment->subject, 70, 128); + $form .= form_textarea(t("Comment"), "comment", $comment->comment, 70, 15); + $form .= form_select(t("Status"), "status", $comment->status, array("published", "not published")); + $form .= form_hidden("cid", $id); + $form .= form_submit(t("Submit")); + $form .= form_submit(t("Delete")); + + return form($form); + } +} + +function comment_delete($edit) { + + if ($edit["confirm"]) { + db_query("UPDATE comments SET status = 2 WHERE cid = '%d'", $edit["cid"]); + watchdog("special", "comment: deleted comment #". $edit["cid"]); + $output = "Comment deleted."; + } + else { + $output .= form_item(t("Confirm deletion"), ""); + $output .= form_hidden("cid", $edit["cid"]); + $output .= form_hidden("confirm", 1); + $output .= form_submit(t("Delete")); + $output = form($output); + } + + return $output; +} + +function comment_save($id, $edit) { + db_query("UPDATE comments SET subject = '%s', comment = '%s', status = '%s' WHERE cid = '%d'", filter($edit["subject"]), filter($edit["comment"]), $edit["status"], $id); + watchdog("special", "comment: modified '". $edit["subject"] ."'"); + return "Comment updated."; +} + +function comment_admin_overview($status = 0, $comment_page = 0) { + global $comment_settings; + + $comments_per_page = 50; + + /* + ** Save location to come back here after a edit/delete + */ + + $comment_settings["status"] = $status; + $comment_settings["comment_page"] = $comment_page; + session_register("comment_settings"); + + + /* + ** Now render the page + */ + + $start = $comment_page * $comments_per_page; + + $output .= $status ? "

    Offline comments

    \n" : "

    Online comments

    "; + $result = db_query("SELECT c.*, u.name, u.uid FROM comments c LEFT JOIN users u ON u.uid = c.uid WHERE c.status = '%d' ORDER BY timestamp DESC LIMIT $start, $comments_per_page", $status); + + $output .= "\n"; + $output .= " \n"; + while ($comment = db_fetch_object($result)) { + $output .= " \n"; + } + + if ($comment_page > 0) { + // show previous + $prev_link = la(t("previous comments"), array("mod" => "comment", "status" => $status, "comment_page" => ($comment_page - 1))); + } + + if (db_num_rows($result) == $comments_per_page) { + // show next + $next_link = la(t("next comments"), array("mod" => "comment", "status" => $status, "comment_page" => ($comment_page + 1))); + } + + $output .= ""; + $output .= "
    subjectauthordatepublishedoperations
    ". l(check_output($comment->subject), array("id" => $comment->nid, "cid" => $comment->cid, "pid" => $comment->pid), "node", $comment->cid, array("title" => htmlentities($comment->comment))) ."". format_name($comment) ."". format_date($comment->timestamp, "small") ."". ($comment->status == 0 ? "yes" : "no") ."". la(t("edit comment"), array("mod" => "comment", "op" => "edit", "id" => $comment->cid)) ."". la(t("delete comment"), array("mod" => "comment", "op" => "delete", "id" => $comment->cid)) ."
    $prev_link$next_link
    \n"; + + return $output; +} + +function comment_mod_matrix($edit) { + global $tid, $rid; + + $output .= "

    Moderators/vote values matrix

    "; + + if ($rid) { + db_query("DELETE FROM moderation_roles"); + foreach ($rid as $role_id => $votes) { + foreach ($votes as $mid => $value) { + $sql[] = "('$mid', '$role_id', '$value')"; + } + } + db_query("INSERT INTO moderation_roles (mid, rid, value) VALUES ". implode(", ", $sql)); + } + + $result = db_query("SELECT r.rid, r.name FROM role r, permission p WHERE r.rid = p.rid AND p.perm LIKE '%moderate comments%'"); + $role_names = array(); + while ($role = db_fetch_object($result)) { + $role_names[$role->rid] = $role->name; + } + + $result = db_query("SELECT rid, mid, value FROM moderation_roles"); + while ($role = db_fetch_object($result)) { + $mod_roles[$role->rid][$role->mid] = $role->value; + } + + $output .= ""; + $output .= " "; + + $result = db_query("SELECT mid, vote FROM moderation_votes ORDER BY weight"); + while ($vote = db_fetch_object($result)) { + $output .= ""; + foreach (array_keys($role_names) as $rid) { + $output .= ""; + } + $output .= ""; + } + $output .= "
    votes". implode("", array_values($role_names)) ."
    $vote->votemid]\" size=\"4\" value=\"". $mod_roles[$rid][$vote->mid] ."\" />
    "; + $output .= "
    ". form_submit(t("Submit votes")); + + return form($output); +} + +function comment_mod_roles($edit) { + + $output .= "

    Initial comment scores

    "; + + if ($edit) { + variable_set("comment_roles", $edit); + } + + $start_values = variable_get("comment_roles", array()); + + $result = db_query("SELECT r.rid, r.name FROM role r, permission p WHERE r.rid = p.rid AND p.perm LIKE '%post comments%'"); + + $output .= ""; + $output .= " "; + + while ($role = db_fetch_object($result)) { + $output .= ""; + $output .= ""; + } + + $output .= "
    user roleinitial score
    $role->namerid]\" size=\"4\" value=\"". $start_values[$role->rid] ."\" />
    "; + $output .= "
    ". form_submit(t("Save scores")); + + return form($output); +} + +function comment_mod_votes($edit) { + global $op, $mid, $tid; + + if ($op == t("Save vote")) { + db_query("UPDATE moderation_votes SET vote = '%s', weight = '%d' WHERE mid = '%d'", $edit["vote"], $edit["weight"], $mid); + $mid = 0; + } + else if ($op == t("Delete vote")) { + db_query("DELETE FROM moderation_votes WHERE mid = '%d'", $mid); + db_query("DELETE FROM moderation_roles WHERE mid = '%d'", $mid); + $mid = 0; + } + else if ($op == t("Add new vote")) { + db_query("INSERT INTO moderation_votes (mid, vote, weight) VALUES (NULL, '%s', '%d')", $edit["vote"], $edit["weight"]); + $mid = 0; + } + + $output .= "

    Moderation votes overview

    "; + $output .= ""; + $output .= " "; + + $result = db_query("SELECT mid, vote, weight FROM moderation_votes ORDER BY weight"); + while ($vote = db_fetch_object($result)) { + $output .= " "; + } + $output .= "
    votesweightoperations
    $vote->vote$vote->weight". la(t("edit"), array("mod" => "comment", "op" => "votes", "mid" => $vote->mid)) ."
    "; + + if ($mid) { + $vote = db_fetch_object(db_query("SELECT vote, weight FROM moderation_votes WHERE mid = '%d'", $mid)); + } + + $output .= "

    Add new moderation option

    "; + $form .= form_textfield(t("Vote"), "vote", $vote->vote, 32, 64, t("The name of this vote. Example: 'offtopic', 'excellent', 'sucky'.")); + $form .= form_textfield(t("Weight"), "weight", $vote->weight, 32, 64, t("Used to order votes; heavier sink.")); + if ($mid) { + $form .= form_submit(t("Save vote")); + $form .= form_submit(t("Delete vote")); + } + else { + $form .= form_submit(t("Add new vote")); + } + + $output .= form($form); + + return $output; +} + +function comment_mod_filters($edit) { + global $op, $fid, $tid; + + if ($op == t("Save filter")) { + db_query("UPDATE moderation_filters SET filter = '%s', minimum = '%d' WHERE fid = '%d'", $edit["filter"], $edit["minimum"], $fid); + $fid = 0; + } + else if ($op == t("Delete filter")) { + db_query("DELETE FROM moderation_filters WHERE fid = '%d'", $fid); + $fid = 0; + } + else if ($op == t("Add new filter")) { + db_query("INSERT INTO moderation_filters (fid, filter, minimum) VALUES (NULL, '%s', '%d')", $edit["filter"], $edit["minimum"]); + $fid = 0; + } + + $output .= "

    Comment filters overview

    "; + $output .= ""; + $output .= " "; + + $result = db_query("SELECT fid, filter, minimum FROM moderation_filters ORDER BY minimum"); + while ($filter = db_fetch_object($result)) { + $output .= " "; + } + $output .= "
    nameminimum scoreoperations
    $filter->filter$filter->minimum". la(t("edit"), array("mod" => "comment", "op" => "filters", "fid" => $filter->fid)) ."
    "; + + if ($fid) { + $filter = db_fetch_object(db_query("SELECT filter, fid, minimum FROM moderation_filters WHERE fid = '%d'", $fid)); + } + + $output .= "

    Add new filter

    "; + $form .= form_textfield(t("Filter name"), "filter", $filter->filter, 32, 64, t("The name of this filter. Example: 'good comments', '+1 comments', 'everything'.")); + $form .= form_textfield(t("Minimum score"), "minimum", $filter->minimum, 32, 64, t("Show all comments whose score is larger or equal to the provided minimal score. Range: -127 + 128")); + if ($fid) { + $form .= form_submit(t("Save filter")); + $form .= form_submit(t("Delete filter")); + } + else { + $form .= form_submit(t("Add new filter")); + } + + $output .= form($form); + + return $output; +} + + +function comment_admin() { + global $op, $id, $edit, $mod, $keys, $order, $status, $comment_page, $comment_settings; + + if (user_access("administer comments")) { + + $links[] = la(t("online comments"), array("mod" => "comment", "status" => 0)); + $links[] = la(t("offline comments"), array("mod" => "comment", "status" => 1)); + $links[] = la(t("search comments"), array("mod" => "comment", "op" => "search")); + if (user_access("administer moderation")) { + $links[] = la(t("moderation votes"), array("mod" => "comment", "op" => "votes")); + $links[] = la(t("moderation matrix"), array("mod" => "comment", "op" => "matrix")); + $links[] = la(t("comment filters"), array("mod" => "comment", "op" => "filters")); + $links[] = la(t("initial comment scores"), array("mod" => "comment", "op" => "roles")); + } + $links[] = la(t("help"), array("mod" => "comment", "op" => "help")); + + print "". implode(" | ", $links) ."
    \n"; + + switch ($op) { + case "help": + print comment_help(); + break; + case "edit": + print comment_admin_edit($id); + break; + case "search": + print search_type("comment", drupal_url(array("mod" => "comment", "op" => "search"), "admin")); + break; + case "votes": + case t("Add new vote"): + case t("Delete vote"): + case t("Save vote"): + if (user_access("administer moderation")) { + print comment_mod_votes($edit); + } + break; + case "roles": + case t("Save scores"): + if (user_access("administer moderation")) { + print comment_mod_roles($edit); + } + break; + case "matrix": + case t("Submit votes"): + if (user_access("administer moderation")) { + print comment_mod_matrix($edit); + } + break; + case "filters": + case t("Add new filter"): + case t("Delete filter"): + case t("Save filter"): + if (user_access("administer moderation")) { + print comment_mod_filters($edit); + } + break; + case "delete": + print comment_delete(array("cid" => $id)); + break; + case t("Delete"): + print status(comment_delete($edit)); + if (session_is_registered("comment_settings")) { + $status = $comment_settings["status"]; + $comment_page = $comment_settings["comment_page"]; + } + print comment_admin_overview($status, $comment_page); + break; + case t("Submit"): + print status(comment_save(check_query($id), $edit)); + if (session_is_registered("comment_settings")) { + $status = $comment_settings["status"]; + $comment_page = $comment_settings["comment_page"]; + } + print comment_admin_overview($status, $comment_page); + break; + default: + print comment_admin_overview($status, $comment_page); + } + } + else { + print message_access(); + } +} + +/* +** Renderer or visualization functions this can be optionally +** overridden by themes. +*/ + +function comment_mode_form($mode) { + global $cmodes; + + foreach ($cmodes as $key => $value) { + $options .= " \n"; + } + + return "\n"; +} + +function comment_order_form($order) { + global $corder; + + foreach ($corder as $key=>$value) { + $options .= " \n"; + } + + return "\n"; +} + +function comment_per_page_form($comments_per_page) { + for ($i = 10; $i < 100; $i = $i + 20) { + $options .= " "; + } + return "\n"; +} + +function comment_threshold($threshold) { + $result = db_query("SELECT fid, filter FROM moderation_filters"); + $options .= " "; + while ($filter = db_fetch_object($result)) { + $filters .= " "; + } + + return "\n"; +} + +function comment_controls($threshold = 1, $mode = 3, $order = 1, $nid, $page = 0, $comment_num = 0, $comments_per_page = 50) { + static $output; + + if (!$output) { + $output .= comment_mode_form($mode); + $output .= comment_order_form($order); + $output .= comment_per_page_form($comments_per_page); + $output .= comment_threshold($threshold); + + $output .= " ". form_submit(t("Update settings")); + + $output = form_item(t("Comment viewing options"), $output, t("Select your prefered way to display the comments and click 'Update settings' to active your changes.")); + + if (($mode == 2 || $mode == 4) && $comment_num > $comments_per_page) { + if ($page > 1) { + $p[] = l(t("previous"), array("id" => $nid, "comment_page" => $page - 1)); + } + for ($n = 1; $n <= ceil($comment_num / $comments_per_page); $n++) { + $p[] = ($n == $page) ? "»$n«" : l($n, array("id" => $nid, "comment_page" => $n)); + } + if ($page < ceil($comment_num / $comments_per_page)) { + $p[] = l(t("next"), array("id" => $nid, "comment_page" => $page + 1)); + } + $output .= form_item(t("Browse %a comments", array("%a" => $comment_num)), implode(" • ", $p), t("There are more than %a comments in this node. Use these links to navigate through them.", array("%a" => $comments_per_page))); + } + } + + return $output; +} + +function comment_moderation_form($comment) { + global $comment_votes, $op, $user, $node; + static $votes; + + if ($op == "reply") { + // preview comment: + $output .= " "; + } + else if ((user_access("administer comments") || comment_user_can_moderate($node)) && $user->uid != $comment->uid && !(comment_already_moderated($user->uid, $comment->users))) { + // comment hasn't been moderated yet: + + if (!isset($votes)) { + $result = db_query("SELECT v.mid, v.vote, r.value FROM moderation_votes v, moderation_roles r WHERE v.mid = r.mid AND r.rid = '%d' ORDER BY weight", $user->rid); + $votes = array(); + while ($vote = db_fetch_object($result)) { + if ($vote->value != 0) { + $votes[] = $vote; + } + } + } + + $options .= " \n"; + if ($votes) { + foreach ($votes as $vote) { + $options .= " \n"; + } + } + + if (user_access("administer comments")) { + $options .= " \n"; + $options .= " \n"; + } + $output .= "\n"; + } + + return $output; +} + +function comment($comment, $link = 0) { + $output .= "cid\">"; + $output .= "
    "; + $output .= ""; + $output .= " "; + $output .= " "; + $output .= " "; + $output .= " "; + $output .= "
    ". check_output($comment->subject) . ($comment->new ? " *" : "") ."
    ". $comment->moderation ."
    ". t("by %a on %b", array("%a" => format_name($comment), "%b" => format_date($comment->timestamp))) ."
    ". check_output($comment->comment, 1) ."
    $link
    "; + $output .= "

    "; + print $output; +} + +function comment_folded($comment) { + print l(check_output($comment->subject), array("id" => $comment->nid, "cid" => $comment->cid), "node", $comment->cid) ." ". t("by") . " " . format_name($comment) ."

    "; +} + +function comment_flat_collapsed($comments, $threshold) { + foreach ($comments as $comment) { + if (comment_visible($comment, $threshold)) { + print comment_view($comment, "", 0); + } + } +} + +function comment_flat_expanded($comments, $threshold) { + foreach ($comments as $comment) { + comment_view($comment, comment_links($comment, 0), comment_visible($comment, $threshold)); } } function comment_thread_min($comments, $threshold, $pid = 0) { - global $user; + // this is an inner loop, so it's worth some optimization + // from slower to faster foreach ($comments as $comment) { - if ($comment->pid == $pid) { + #for ($n=0; $npid == $pid) && (comment_visible($comment, $threshold))) { print "

    "; } } } -function comment_thread_max($comments, $threshold, $pid = 0, $level = 0) { - global $user; - +function comment_thread_max($comment, $threshold, $level = 0) { /* ** We had quite a few browser specific issues: expanded comments below ** the top level got truncated on the right hand side. A range of @@ -452,128 +1234,215 @@ function comment_thread_max($comments, $threshold, $pid = 0, $level = 0) { ** in terms of speed and size. */ - foreach ($comments as $comment) { - if ($comment->pid == $pid) { - if ($level) { - print "
     \n"; - } - comment_view($comment, comment_links($comment, 0)); - if ($level) { - print "
    \n"; - } + if ($level) { + print "
     \n"; + } - comment_thread_max($comments, $threshold, $comment->cid, $level + 1); - } + comment_view($comment, comment_links($comment, 0), comment_visible($comment, $threshold)); + + if ($level) { + print "
    \n"; } } -function comment_render($nid, $cid = 0) { - global $user, $theme, $mode, $order, $threshold; +function comment_post_forbidden() { + global $user; + if ($user->uid) { + return t("You can't post comments."); + } + else { + return t("Please %a, or %b, to add comments.", array("%a" => lm(t("login"), array("mod" => "user")), "%b" => lm(t("register"), array("mod" => "user", "op" => "register")))); + } +} - if (user_access("access comments")) { +/** +*** misc functions: helpers, privates, history, search +**/ - /* - ** Pre-process variables: - */ - if (empty($nid)) { - $nid = 0; +function comment_visible($comment, $threshold = 0) { + if ($comment->score >= $threshold) { + return 1; + } + else { + return 0; + } +} + +function comment_moderate() { + global $moderation, $user; + + if ($moderation) { + $result = db_query("SELECT mid, value FROM moderation_roles WHERE rid = '%d'", $user->rid); + while ($mod = db_fetch_object($result)) { + $votes[$mod->mid] = $mod->value; } - if (empty($mode)) { - $mode = $user->uid ? $user->mode : variable_get("default_comment_mode", 4); - } + $node = node_load(array("nid" => db_result(db_query("SELECT nid FROM comments WHERE cid = '%d'", key($moderation))))); - if (empty($order)) { - $order = $user->uid ? $user->sort : variable_get("default_comment_order", 1); - } + if (user_access("administer comments") || comment_user_can_moderate($node)) { + foreach ($moderation as $cid => $vote) { + if ($vote) { + if (($vote == 'offline') && (user_access("administer comments"))) { + db_query("UPDATE comments SET status = 1 WHERE cid = '%s'", $cid); + watchdog("special", "comment: unpublished comment #". $cid); - if (empty($threshold)) { - // $threshold = $user->uid ? $user->threshold : variable_get("default_comment_threshold", 3); - $threshold = 0; - } + /* + ** Fire a hook + */ - print "\n"; - print "
    "comment"), "module") ."\">\n"; - print form_hidden("nid", $nid); + module_invoke_all("comment", "unpublish", $cid); + } + else { + $comment = db_fetch_object(db_query("SELECT * FROM comments WHERE cid = '%d'", $cid)); + $users = unserialize($comment->users); + if ($user->uid != $comment->uid && !(comment_already_moderated($user->uid, $comment->users))) { + $users[$user->uid] = $vote; + $tot_score = 0; + foreach ($users as $uid => $vote) { + if ($uid) { + $tot_score = $tot_score + $votes[$vote]; + } + else { + // vote 0 is the start value + $tot_score = $tot_score + $vote; + } + } + $new_score = round($tot_score / count($users)); + db_query("UPDATE comments SET score = '$new_score', users = '%s' WHERE cid = '%d'", serialize($users), $cid); - /* - ** Render control panel: - */ + /* + ** Fire a hook + */ - if (comment_num_all($nid)) { - $theme->box(t("Control panel"), $theme->comment_controls($threshold, $mode, $order)); - } - - if ($cid) { - $result = db_query("SELECT c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '$cid' GROUP BY c.cid, c.pid, c.nid, c.subject, c.comment, c.timestamp, u.uid, u.name"); - if ($comment = db_fetch_object($result)) { - comment_view($comment, comment_links($comment)); - } - } - - if ($cid) { - $result = comment_query($nid, $order, $cid); - } - else { - $result = comment_query($nid, $order); - } - - if ($mode == 1) { - if (db_num_rows($result)) { - print "\n"; - print " \n"; - while ($comment = db_fetch_object($result)) { - if (comment_visible($comment, $threshold)) { - print " \n"; + module_invoke_all("comment", "moderate", $cid, $vote); + } } } - print "
    ". t("Subject") ."". t("Author") ."". t("Date") ."
    ". l(check_output($comment->subject), array("id" => $comment->nid, "cid" => $comment->cid), "node", $comment->cid) ."". format_name($comment) ."". format_date($comment->timestamp, "small") ."
    \n"; } } - else if ($mode == 2) { - while ($comment = db_fetch_object($result)) { - comment_view($comment, (comment_visible($comment, $threshold) ? comment_links($comment, 0) : 0)); - } - } - else if ($mode == 3) { - while ($comment = db_fetch_object($result)) { - $comments[] = $comment; - } + } +} - if ($comments) { - if ($cid) { - comment_thread_min($comments, $threshold, $cid, 1); - } - else { - comment_thread_min($comments, $threshold); - } - } - } - else { - while ($comment = db_fetch_object($result)) { - $comments[] = $comment; - } +function comment_settings($mode, $order, $threshold, $comments_per_page) { + global $user; - if ($comments) { - if ($cid) { - comment_thread_max($comments, $threshold, $cid, 1); - } - else { - comment_thread_max($comments, $threshold, 0, 0); - } - } - } + if ($user->uid) { + $user = user_save($user, array("mode" => $mode, "sort" => $order, "threshold" => $threshold, "comments_per_page" => $comments_per_page)); + } +} - print "
    "; +function comment_num_all($nid) { + $comment = db_fetch_object(db_query("SELECT COUNT(c.nid) AS number FROM node n LEFT JOIN comments c ON n.nid = c.nid WHERE n.nid = '%d' AND c.status = 0 GROUP BY n.nid", $nid)); + return $comment->number ? $comment->number : 0; +} +function comment_num_replies($id) { + $result = db_query("SELECT COUNT(cid) FROM comments WHERE pid = '%d' AND status = 0", $id); + return ($result) ? db_result($result, 0) : 0; +} + +/** + * get number of new comments and id of first new + * + * @param $nid node-id to count comments for + * @param $timestamp time to count from (defaults to time of last user access to node) + * + * @return array("count_new" => $count_new, "id_first_new" => $id_first_new) + */ +function comment_num_new($nid, $timestamp = 0) { + global $user; + + if ($user->uid) { /* - ** Tag the node's comments as being read: + ** Retrieve the timestamp at which the current user last viewed the + ** specified node. */ - comment_tag_new($nid); + if (!$timestamp) { + $history = db_fetch_object(db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '%d'", $nid)); + $timestamp = $history->timestamp ? $history->timestamp : 0; + } + + /* + ** Use the timestamp to retrieve the number of new comments and the + ** ID of first new comment. + */ + + $result = db_fetch_array(db_query("SELECT COUNT(c.cid) AS count_new, MIN(c.cid) AS id_first_new FROM node n LEFT JOIN comments c ON n.nid = c.nid WHERE n.nid = '%d' AND timestamp > '%d' AND c.status = 0", $nid, $timestamp)); + + return $result; } + else { + return array(0, 0); + } + +} + +function comment_tag_new($nid) { + global $user; + + if ($user->uid) { + $nid = check_query($nid); + + $result = db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '%d'", $nid); + if (db_fetch_object($result)) { + db_query("UPDATE history SET timestamp = '%d' WHERE uid = '$user->uid' AND nid = '%d'", time(), $nid); + } + else { + db_query("INSERT INTO history (uid, nid, timestamp) VALUES ('$user->uid', '%d', '%d')", $nid, time()); + } + } +} + +function comment_is_new($comment) { + global $user; + static $date; + + if (!$date[$comment->nid]) { + if ($user->uid) { + $history = db_fetch_object(db_query("SELECT timestamp FROM history WHERE uid = '$user->uid' AND nid = '%d'", $comment->nid)); + $date[$comment->nid] = $history->timestamp ? $history->timestamp : 0; + } + else { + $date[$comment->nid] = time(); + } + } + + if ($comment->timestamp > $date[$comment->nid]) { + return 1; + } + else { + return 0; + } +} + +function comment_thread_structure($comments, $pid, $depth, $structure) { + $depth++; + + foreach ($comments as $key => $comment) { + if ($comment->pid == $pid) { + $structure[$comment->cid] = $depth; + $structure = comment_thread_structure($comments, $comment->cid, $depth, $structure); + } + } + + return $structure; +} + +function comment_user_can_moderate($node) { + global $user; + return (user_access("moderate comments")); + // TODO: || (($user->uid == $node->uid) && user_access("moderate comments in owned node"))); +} + +function comment_already_moderated($uid, $users) { + $comment_users = unserialize($users); + if (!$comment_users) { + $comment_users = array(); + } + return in_array($uid, array_keys($comment_users)); } function comment_search($keys) { @@ -602,198 +1471,11 @@ function comment_search($keys) { ** identifier which is currently used byt the comment module. */ - $find = do_search(array("keys" => $keys, "type" => "comment", "select" => "select s.lno as lno, c.nid as nid, c.subject as title, c.timestamp as created, u.uid as uid, u.name as name, s.count as count FROM search_index s, comments c LEFT JOIN users u ON c.uid = u.uid WHERE s.lno = c.cid AND s.type = 'comment' AND s.word like '%'")); + $find = do_search(array("keys" => $keys, "type" => "comment", "select" => "select s.lno as lno, c.nid as nid, c.subject as title, c.timestamp as created, u.uid as uid, u.name as name, s.count as count FROM search_index s, comments c LEFT JOIN users u ON c.uid = u.uid WHERE s.lno = c.cid AND s.type = 'comment' AND c.status = 0 AND s.word like '%'")); return $find; } -function comment_perm() { - return array("access comments", "post comments", "administer comments"); -} - -function comment_link($type, $node = 0, $main = 0) { - - if ($type == "admin" && user_access("administer comments")) { - $links[] = la(t("comments"), array("mod" => "comment")); - } - - if ($type == "node" && $node->comment) { - - if ($main) { - - /* - ** Main page: display the number of comments that have been posted. - */ - - if (user_access("access comments")) { - $all = comment_num_all($node->nid); - $new = comment_num_new($node->nid); - - $links[] = l(format_plural($all, "comment", "comments") . ($new ? ", $new ". t("new") : ""), array("id" => $node->nid), "node", "comment", array("title" => t("View this posting and all of its comments."))); - } - } - else { - /* - ** Node page: add a "post comment" link if the user is allowed to - ** post comments and if this node is not read-only - */ - - if (user_access("post comments")) { - if ($node->comment == 2) { - $links[] = lm(t("add new comment"), array("mod" => "comment", "op" => "reply", "id" => $node->nid), "comment", array("title" => t("Share your thoughts and opinions related to this posting."))); - } - else { - $links[] = t("This discussion is closed: you can't post new comments."); - } - } - } - } - - return $links ? $links : array(); -} - -function comment_node_link($node) { - - if (user_access("administer comments") && comment_num_all($node->nid)) { - - /* - ** Edit comments: - */ - - $result = db_query("SELECT c.cid, c.subject, u.uid, u.name FROM comments c LEFT JOIN users u ON u.uid = c.uid WHERE nid = '$node->nid' ORDER BY c.timestamp"); - - $output .= "

    ". t("Edit comments") ."

    "; - $output .= ""; - $output .= " "; - - while ($comment = db_fetch_object($result)) { - $output .= ""; - } - - $output .= "
    titleauthoroperations
    ". l($comment->subject, array("id" => $node->nid, "cid" => $comment->cid), "node", $comment->cid) ."". format_name($comment) ."". l(t("view comment"), array("id" => $node->nid, "cid" => $comment->cid), $comment->cid) ."". la(t("edit comment"), array("mod" => "comment", "op" => "edit", "id" => $comment->cid)) ."". la(t("delete comment"), array("mod" => "comment", "op" => "delete", "id" => $comment->cid)) ."
    "; - - return $output; - } -} - - -function comment_save($id, $edit) { - db_query("UPDATE comments SET subject = '%s', comment = '%s' WHERE cid = '$id'", filter($edit["subject"]), filter($edit["comment"])); - watchdog("special", "comment: modified '". $edit["subject"] ."'"); -} - -function comment_page() { - global $theme, $op, $edit, $id, $pid, $cid; - - switch ($op) { - case "edit": - $theme->header(); - comment_edit(check_query($id)); - $theme->footer(); - break; - case "reply": - $theme->header(); - comment_reply(check_query($pid), check_query($id)); - $theme->footer(); - break; - case t("Preview comment"): - $theme->header(); - comment_preview($edit); - $theme->footer(); - break; - case t("Post comment"): - comment_post($edit); - break; - case t("Update settings"): - global $mode, $order, $threshold; - comment_settings(check_query($mode), check_query($order), check_query($threshold)); - drupal_goto(drupal_url(array("id" => $edit["nid"], "mode" => $mode, "order" => $order), "node")); - break; - default: - } -} - -function comment_admin_edit($id) { - - $result = db_query("SELECT c.*, u.name, u.uid FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '$id'"); - $comment = db_fetch_object($result); - - $form .= form_item(t("Author"), format_name($comment)); - $form .= form_textfield(t("Subject"), "subject", $comment->subject, 70, 128); - $form .= form_textarea(t("Comment"), "comment", $comment->comment, 70, 15); - $form .= form_hidden("cid", $id); - $form .= form_submit(t("Submit")); - $form .= form_submit(t("Delete")); - - return form($form); -} - -function comment_admin_overview() { - $result = db_query("SELECT c.*, u.name, u.uid FROM comments c LEFT JOIN users u ON u.uid = c.uid ORDER BY timestamp DESC LIMIT 50"); - - $output .= "\n"; - $output .= " \n"; - while ($comment = db_fetch_object($result)) { - $output .= " \n"; - } - $output .= "
    subjectauthordateoperations
    ". l(check_output($comment->subject), array("id" => $comment->nid, "cid" => $comment->cid, "pid" => $comment->pid), "node", $comment->cid) ."". format_name($comment) ."". format_date($comment->timestamp, "small") ."". la(t("edit comment"), array("mod" => comment, "op" => edit, "id" => $comment->cid)) ."". la(t("delete comment"), array("mod" => "comment", "op" => "delete", "id" => $comment->cid)) ."
    \n"; - - return $output; -} - -function comment_delete($edit) { - - if ($edit["confirm"]) { - db_query("DELETE FROM comments WHERE cid = '%s'", $edit["cid"]); - watchdog("special", "comment: deleted comment #". $edit["cid"]); - } - else { - $output .= form_item(t("Confirm deletion"), ""); - $output .= form_hidden("cid", $edit["cid"]); - $output .= form_hidden("confirm", 1); - $output .= form_submit(t("Delete")); - $output = form($output); - } - - return $output; -} - -function comment_admin() { - global $op, $id, $edit, $mod, $keys, $order; - - if (user_access("administer comments")) { - - print "". la(t("overview"), array("mod" => "comment")) ." | ". la(t("search comment"), array("mod" => "comment", "op" => "search")) ." | ". la(t("help"), array("mod" => "comment", "op" => "help")) ."
    \n"; - - switch ($op) { - case "help": - print comment_help(); - break; - case "edit": - print comment_admin_edit($id); - break; - case "search": - print search_type("comment", drupal_url(array("mod" => "comment", "op" => "search"), "admin")); - break; - case "delete": - print comment_delete(array("cid" => $id)); - break; - case t("Delete"): - print comment_delete($edit); - break; - case t("Submit"): - print status(comment_save(check_query($id), $edit)); - print comment_admin_overview(); - break; - default: - print comment_admin_overview(); - } - } - else { - print message_access(); - } -} - function comment_update_index() { /* @@ -811,7 +1493,25 @@ function comment_update_index() { ** last run date for the comments update. */ - return array("last_update" => "comment_cron_last", "node_type" => "comment", "select" => "SELECT c.cid as lno, c.subject as text1, c.comment as text2 FROM comments c WHERE timestamp > ". variable_get("comment_cron_last", 1)); + return array("last_update" => "comment_cron_last", "node_type" => "comment", "select" => "SELECT c.cid as lno, c.subject as text1, c.comment as text2 FROM comments c WHERE c.status = 0 AND timestamp > ". variable_get("comment_cron_last", 1)); } -?> \ No newline at end of file +// backward compatibility with some themes +function comment_moderation() { +} + +// this will go away as soon as theme_invoke is committed +function comment_theme_invoke() { + global $theme; + + $args = func_get_args(); + $function = array_shift($args); + + if (method_exists($theme, $function)) { + return call_user_method_array($function, $theme, $args); + } + else { + return call_user_func_array($function, $args); + } +} +?> diff --git a/modules/forum.module b/modules/forum.module index a7dd3b2902b..3f8f230e3e9 100644 --- a/modules/forum.module +++ b/modules/forum.module @@ -2,7 +2,7 @@ // $Id$ function forum_system($field){ - $system["description"] = t("Allows threaded discussions about general topics."); + $system["description"] = t("Enable threaded discussions about general topics."); return $system[$field]; } @@ -17,16 +17,49 @@ function forum_access($op, $node) { if ($op == "view") { return $node->status; } + if ($op == "create") { + return user_access("post content"); + } +} + +function forum_perm() { + return array("post forum topics without approval"); +} + +function forum_conf_options() { + foreach (taxonomy_get_vocabularies("forum") as $vid => $voc) { + $vocs[$vid] = $voc->name; + } + $output .= ""; + $output .= form_select("Forum vocabulary", "forum_nav_vocabulary", variable_get("forum_nav_vocabulary", ""), $vocs, t("The taxonomy vocabulary that will be uses as the navigation tree.")); + $output .= _taxonomy_term_select("Containers", "forum_containers", variable_get("forum_containers", array()), variable_get("forum_nav_vocabulary", ""), t("You can choose vocabularies which will not have topics, but will be just containers for other forums."), 1, t("")); + + $output .= form_textfield("Topic icons path", "forum_topic_icon_path", variable_get("forum_topic_icon_path", "images/forum/topics/"), 30, 255, "The path to the topic icons. Leave blank to disable icons."); + $output .= form_textfield("Folder icons path", "forum_folder_icon_path", variable_get("forum_folder_icon_path", "images/forum/folder/"), 30, 255, "Path to the default, hot, new and closed folder icons. Leave blank to disable icons."); + $number = array(5 => 5, 10 => 10, 15 => 15, 20 => 20, 25 => 25, 30 => 30, 35 => 35, 40 => 40, 50 => 50, 60 => 60, 80 => 80, 100 => 100, 10000=>10000); + $output .= form_select("Hot topic threshold", "forum_hot_topic", variable_get("forum_hot_topic", 15), $number, "The number of posts a topic must have to be considered hot."); + $number = array(10 => 10, 25 => 25, 50 => 50, 75 => 75, 100 => 100); + $output .= form_select("Topics per page", "forum_per_page", variable_get("forum_per_page", 25), $number, "The default number of topics displayed per page; links to browse older messages are automatically being displayed."); + $forder = array(1 => "Date - newest first", 2 => "Date - oldest first", 3 => "Posts - most active first", 4=> "Posts - least active first"); + $output .= form_select("Default order", "forum_order", variable_get("forum_order", 1), $forder, "The default display order for topics."); + $output .= form_textfield("Number of topics in block", "forum_block_num", variable_get("forum_block_num", "5"), 5, 5, "The number of topics in the active topics block."); + $output .= form_select("Internal caching", "forum_cache", variable_get("forum_cache", 0), array("disabled", "enabled"), "Cache internal datastructures for both anonymous and autheticated users. When enabled, the forum data won't be 100% up to date with regards regardto the number of posts, the number of replies and the last topic being posted. Enable when you have busy forum."); + + return $output; } function forum_save($op, $node) { - if ($op == "approve") { return array("status" => 1); } if ($op == "create") { - return array("body" => filter($node->body), "teaser" => filter($node->teaser)); + if (user_access("post forum topics without approval")) { + $moderation = array("body" => filter($node->body), "moderate" => 0, "status" => 1); + } else { + $moderation = array("body" => filter($node->body), "moderate" => 1, "status" => 0); + } + return array_merge($moderation, array("tid", "icon_num", "shadow")); } if ($op == "decline") { @@ -34,69 +67,421 @@ function forum_save($op, $node) { } if ($op == "update") { - return array("body" => filter($node->body), "teaser" => filter($node->teaser)); + // if she changed the term, we could have to leave a shadow + $old_terms = explode(",", $node->old_container); + foreach ($node->taxonomy as $term) { + if ($term && (!in_array($term, $old_terms)) && $node->shadow) { + $shadow = $term; + break; + } + } + return array("body" => filter($node->body), "tid", "icon_num", "shadow" => $shadow, "old_container"); } } -function forum_link($type) { +function forum_load($node) { + $forum = db_fetch_object(db_query("SELECT * FROM forum WHERE nid = '%d' AND shadow = 0", $node->nid)); + + return $forum; +} + +function forum_block() { + if (user_access("access content")) { + $result = db_query("SELECT n.nid, n.title, n.body, GREATEST(n.created, MAX(c.timestamp)) AS sort FROM node n, forum f LEFT JOIN comments c ON c.nid = n.nid WHERE n.type = 'forum' AND n.nid = f.nid AND f.shadow = 0 AND n.status = 1 GROUP BY n.nid ORDER BY sort DESC LIMIT ". variable_get("forum_block_num", "5")); + while ($node = db_fetch_object($result)) { + $content .= "
  • ".l(check_output($node->title), array("id" => $node->nid), "node", "", array("title" => substr(strip_tags($node->body), 0, 100)."..."))."
  • \n"; + } + + if ($content) { + $content .= "

    ".lm(t("browse forums"), array("mod" => "forum"))."

    "; + } + } + $blocks[0][subject] = t("Forum active topics"); + $blocks[0][content] = $content; + $blocks[0][info] = t("Forum active topics"); + + return $blocks; +} + +function forum_link($type, $node) { if ($type == "page" && user_access("access content")) { - $links[] = lm(t("forum"), array("mod" => "forum"), "", array("title" => t("Read and participate in the discussion forums."))); + $links[] = lm(t("forum"), array("mod" => "forum")); } - if ($type == "menu.create" && user_access("administer nodes")) { - $links[] = lm(t("create forum"), array("mod" => "node", "op" => "add", "type" => "forum"), "", array("title" => t("Add a new discussion forum."))); + if ($type == "menu.create" && user_access("post content")) { + // I had complains that having this link leads people not to choose a forum, and always post in the first forum. + #$links[] = lm(t("create forum topic"), array("mod" => "node", "op" => "add", "type" => "forum"), "", array("title" => t("Start a new forum topic."))); + } + + if ($type == "node" && $node->type == "forum") { + // get previous and next topic + + $sql = "SELECT n.nid, title, body, GREATEST(n.created, MAX(c.timestamp)) AS date_sort, COUNT(c.nid) AS num_comments FROM node n, term_node t, forum f LEFT JOIN comments c ON c.nid = n.nid WHERE n.nid = t.nid AND n.nid = f.nid AND t.tid = '%d' AND f.shadow = 0 AND n.status = 1 GROUP BY n.nid ORDER BY ". _forum_get_topic_order(isset($user->sortby) ? $user->sortby : variable_get("forum_order",1)); + + $terms = array_keys(taxonomy_node_get_terms_by_vocabulary($node->nid, variable_get("forum_nav_vocabulary", ""))); + $result = db_query($sql, $terms[0]); + + while ($topic = db_fetch_object($result)) { + if ($stop == 1) { + $next->nid = $topic->nid; + $next->title = $topic->title; + $next->body = $topic->body; + break; + } + if ($topic->nid == $node->nid) { + $stop = 1; + } + else { + $prev->nid = $topic->nid; + $prev->title = $topic->title; + $prev->body = $topic->body; + } + } + + if ($prev) { + $links[] = l(t("previous topic"), array("id" => $prev->nid), "node", "", array("title" => $prev->title."\n".substr(strip_tags($prev->body), 0, 100)."...")); + } + + if ($next) { + $links[] = l(t("next topic"), array("id" => $next->nid), "node", "", array("title" => $next->title."\n".substr(strip_tags($next->body), 0, 100)."...")); + } } return $links ? $links : array(); } -function forum_view($node) { +function forum_view($node, $main = 0) { global $theme; - $output .= "

    ". lm(t("Forum"), array("mod" => "forum")) ." / ". l(check_output($node->title), array("id" => $node->nid)) .":

    ". check_output($node->body) ."

    "; + + $term_data = array_shift(taxonomy_node_get_terms($node->nid)); + if (!$term_data) { + // we are previewing + $term_data = taxonomy_get_term($node->taxonomy[0]); + } + $voc = taxonomy_get_vocabulary($term_data->vid); + + $output .= "

    "._forum_get_icon($node)." ".lm(check_output($voc->name), array("mod" => "forum"))." : ".lm(check_output($term_data->name), array("mod" => "forum", "tid" => $term_data->tid)); + + $output .= " / ". check_output($node->title) ."
    ".t("%a by %b", array("%a" => format_date($node->created), "%b" => format_name($node)))."

    ". check_output($node->body, 1) ."

    "; $output .= "

    ". $theme->links(link_node($node, $main)) ."

    "; - $theme->box(t("Discussion forum"), $output); + #$theme->box(t("Discussion forum"), $output); + + $node->title = _forum_get_icon($node)." ".lm(check_output($voc->name), array("mod" => "forum"))." : ".lm(check_output($term_data->name), array("mod" => "forum", "tid" => $term_data->tid)) . " / ". check_output($node->title) .""; + + $theme->node($node, $main); } function forum_form(&$node, &$help, &$error) { - - if (function_exists("taxonomy_node_form")) { - $output = implode("", taxonomy_node_form("forum", $node)); + global $tid; + if ($node->nid) { + if ($node->taxonomy) { + $tid = $node->taxonomy; + } else { + // editing: load category from taxonomy + $tid = implode(",", array_keys(taxonomy_node_get_terms($node->nid))); + } + //$output .= implode("

    ", taxonomy_node_form("forum", $node)); + $output .= "nid)))."\">"; + $output .= form_checkbox(t("Leave shadow?"), "shadow", 1, 1, t("If you move this topic, you can leave a link in the old forum to the new forum.")); + } else { + if ($node->taxonomy) { + $tid = $node->taxonomy; + } } - $output .= form_textarea(t("Body"), "body", $node->body, 60, 10); + $output .= _taxonomy_term_select("Forum", "taxonomy", $tid, variable_get("forum_nav_vocabulary", ""), "", 0, "", variable_get("forum_containers", array())); + + if ($icon_path = variable_get("forum_topic_icon_path", "images/forum/topics/")) { + if ($node->icon) { + // we are editing post + if ($dir = @opendir($icon_path)) { + $icon_num = 0; + while($icon = readdir($dir)) { + if ($icon == '.' || $icon == '..') {continue;} + if ($node->icon == $icon) {$checked = "checked";} else {$checked = "";} + $radio .= " \n"; + $icon_num++; + } + closedir($dir); + } + } + else { + if ($dir = @opendir($icon_path)) { + $icon_num = 0; + while($icon = readdir($dir)) { + if ($icon == '.' || $icon == '..') {continue;} + if ($node->icon_num == $icon_num) {$checked = "checked";} else {$checked = "";} + $radio .= " \n"; + $icon_num++; + } + closedir($dir); + } + } + $output .= form_item(t("Topic icon"), $radio); + } + $output .= form_textarea("Body", "body", $node->body, 60, 10); return $output; } +function forum_insert($node) { + $node->icon = _forum_decode_icon($node); + if (!$node->shadow) { + db_query("INSERT INTO forum (nid, icon) VALUES ('%d', '%s')", $node->nid, $node->icon); + } else { + // we created a shadow, a link to a moved topic in a new forum + db_query("INSERT INTO forum (nid, icon, shadow) VALUES ('%d', '%s', '%d')", $node->nid, $node->icon, $node->shadow); + } -function forum_num_comments($nid) { - $value = db_fetch_object(db_query("SELECT COUNT(cid) AS count FROM comments WHERE nid = '$nid'")); + if (variable_get("forum_cache", 0)) { + cache_clear(); + } +} + +function forum_update($node) { + $node->icon = _forum_decode_icon($node); + db_query("UPDATE forum SET icon = '%s' WHERE nid = '%d'", $node->icon, $node->nid); + if ($node->shadow) { + // insert in old forum a new topic, with link to new forum + $node->nid = ""; + $node->type = "forum"; + $node->status = 1; + $node->taxonomy = array($node->old_container); + node_submit($node); + } +} + +function _forum_decode_icon($node) { + // to prevent malicious users + if ($icon_path = variable_get("forum_topic_icon_path", "images/forum/topics/")) { + if ($dir = @opendir($icon_path)) { + $icon_num = 0; + while($icon = readdir($dir)) { + if ($icon == '.' || $icon == '..') {continue;} + if ($icon_num == $node->icon_num) {$myicon = $icon;} + $icon_num++; + } + closedir($dir); + } + } + return $myicon; +} + +function forum_delete(&$node) { + db_query("DELETE FROM forum WHERE nid = '%d'", $node->nid); +} + +function _forum_num_comments($nid) { + $value = db_fetch_object(db_query("SELECT COUNT(cid) AS count FROM comments WHERE nid = '%d' AND status = 0", $nid)); return ($value) ? $value->count : 0; } -function forum_last_comment($nid) { - $value = db_fetch_object(db_query("SELECT timestamp FROM comments WHERE nid = '$nid' ORDER BY timestamp DESC LIMIT 1")); +function _forum_last_comment($nid) { + $value = db_fetch_object(db_query("SELECT timestamp FROM comments WHERE nid = '%d' AND status = 0 ORDER BY timestamp DESC LIMIT 1", $nid)); return ($value) ? format_date($value->timestamp, "small") : " "; } +function _forum_last_reply($nid) { + $value = db_fetch_object(db_query("SELECT c.timestamp, u.name, u.uid FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.nid = '%d' AND c.status = 0 ORDER BY c.timestamp DESC LIMIT 1", $nid)); + return $value; +} + +function _forum_format($topic) { + if ($topic) { + return format_date($topic->timestamp, "small")."
    ".t("by")." ". format_name($topic); + } + else { + return message_na(); + } +} + +function forum_get_forums($tid = 0) { + global $user; + + if (!$tid) { + $tid = 0; + } + + if (variable_get("forum_cache", 0)) { + $forums = unserialize(cache_get("forum:$tid")); + } + + if (!$forums) { + taxonomy_get_tree(variable_get("forum_nav_vocabulary", ""), $_forums, $tid); + $n = 0; + foreach ($_forums as $forum) { + if (in_array($forum->tid, variable_get("forum_containers", array()))) { + $forum->container = 1; + } + else { + $forum->num_topics = _forum_num_topics($forum->tid); + $forum->num_posts = _forum_num_replies($forum->tid) + $forum->num_topics; + $forum->last_post = _forum_last_post($forum->tid); + } + $forums[$forum->tid] = $forum; + $n++; + } + + if (variable_get("forum_cache", 0)) { + cache_set("forum:$tid", serialize($forums), time()+60*10); + } + } + + if ($user->uid && $forums) { + foreach (_forum_topics_read($user->uid) as $tid => $old) { + if ($forums[$tid]) { + $forums[$tid]->old_topics = $old; + } + } + } + + return $forums; +} + +function forum_get_parents($tid) { + if ($tid) { + $parents[] = taxonomy_get_term($tid); + } + $n = 0; + while ($parent = taxonomy_get_parents($parents[$n]->tid)) { + $parents = array_merge($parents, $parent); + $n++; + } + return $parents; +} + +function _forum_num_topics($term) { + $value = db_fetch_object(db_query("SELECT COUNT(n.nid) AS count FROM node n, term_node r, forum f WHERE r.tid = '%d' AND n.nid = r.nid AND n.nid = f.nid AND n.status = 1 AND n.type = 'forum' AND f.shadow = 0", $term)); + return ($value) ? $value->count : 0; +} + +function _forum_num_replies($term) { + $value = db_fetch_object(db_query("SELECT COUNT(*) AS count FROM comments c, term_node r, node n WHERE r.tid = '%d' AND n.nid = r.nid AND n.nid = c.nid AND n.status = 1 AND c.status = 0 AND n.type = 'forum'", $term)); + return ($value) ? $value->count : 0; +} + +function _forum_topics_read($uid) { + $result = db_query("SELECT tid, count(*) AS c FROM history h, term_node r, node n WHERE r.nid = h.nid AND n.nid = h.nid AND n.type = 'forum' AND n.status = 1 AND h.uid = '%d' GROUP BY tid", $uid); + + while ($obj = db_fetch_object($result)) { + $topics_read[$obj->tid] = $obj->c; + } + + return $topics_read ? $topics_read : array(); +} + +function _forum_last_post($term) { + $topic = db_fetch_object(db_query("SELECT n.nid, n.created AS timestamp, u.name AS name, u.uid AS uid FROM node n, term_node r LEFT JOIN users u ON n.uid = u.uid WHERE r.tid = '%d' AND n.nid = r.nid AND n.type = 'forum' AND n.status = 1 ORDER BY timestamp DESC LIMIT 1", $term)); + + $reply = db_fetch_object(db_query("SELECT n.nid, c.timestamp, u.name AS name, u.uid AS uid FROM term_node r, node n LEFT JOIN comments c ON n.nid=c.nid LEFT JOIN users u ON c.uid = u.uid WHERE r.tid = '%d' AND n.nid = r.nid AND n.type = 'forum' AND n.status = 1 AND c.status = 0 ORDER BY c.timestamp DESC LIMIT 1", $term)); + + $value = ($topic->timestamp > $reply->timestamp) ? $topic : $reply; + + return $value; +} + +function forum_get_topics($tid, $sortby, $forum_per_page, $offset) { + global $user; + + $term = taxonomy_get_term($tid); + $voc = taxonomy_get_vocabulary($term->vid); + + $sql_sortby = _forum_get_topic_order($sortby); + + $sql = " + SELECT n.nid, title, users.name AS name, users.uid AS uid, n.created AS timestamp, GREATEST(n.created, MAX(c.timestamp)) AS date_sort, COUNT(c.nid) AS num_comments, icon, n.comment AS comment_mode, shadow FROM node n, term_node r LEFT JOIN users ON n.uid = users.uid LEFT JOIN comments c ON c.nid = n.nid LEFT JOIN forum f ON n.nid = f.nid WHERE n.nid = r.nid AND r.tid = '%d' AND n.status = 1 AND n.type = 'forum' GROUP BY n.nid ORDER BY $sql_sortby"; + + $result = db_query($sql, $tid); + $topic_num = db_num_rows($result); + + $n = 0; + while ($topic = db_fetch_object($result)) { + if ($n < $offset) { + $n++; + continue; + } + + $history = _forum_user_last_visit($topic->nid); + // folder is new if topic is new or there are new comments since last visit + if ($topic->shadow > 0) { + $topic->new = 0; + } + else { + if (!$history && $user->uid) { + $topic->new_replies = 0; + $topic->new = 1; + } + else { + $comments = db_result(db_query("SELECT COUNT(c.nid) FROM node n LEFT JOIN comments c ON n.nid = c.nid WHERE n.nid = '$topic->nid' AND c.status = 0 AND timestamp > '$history' GROUP BY n.nid")); + + $topic->new_replies = $comments ? $comments : 0; + if ($topic->new_replies) { + $topic->new = 1; + } + else { + $topic->new = 0; + } + } + } + + $topic->last_reply = _forum_last_reply($topic->nid); + $topics[] = $topic; + + $n++; + if ($n == ($forum_per_page + $offset)) { + break; + } + } + + return array($topics, $topic_num); +} + +function _forum_first_new($tid) { + global $user; + $result = db_query("SELECT r.nid FROM node n, history h, term_node r WHERE n.type = 'forum' AND n.status = 1 AND h.nid = n.nid AND r.nid = h.nid AND r.tid = '%d' AND h.uid = '%d' ", $tid, $user->uid); + while ($r = db_fetch_object($result)) { + $read[] = $r->nid; + } + + if ($read) { + $nid = db_result(db_query("SELECT r.nid AS c FROM node n LEFT JOIN term_node r ON r.nid = n.nid WHERE n.type = 'forum' AND n.status = 1 AND r.tid = '%d' AND NOT (r.nid IN (".implode(",", $read).")) ORDER BY created LIMIT 1", $tid)); + } + + return $nid ? $nid : 0; +} + function forum_page() { - global $theme; + global $theme, $tid, $sortby, $forum_per_page, $offset, $op, $user; if (user_access("access content")) { - $result = db_query("SELECT nid FROM node WHERE type = 'forum' ORDER BY title"); - - $output .= ""; - $output .= " "; - while ($node = db_fetch_object($result)) { - $node = node_load(array("nid" => $node->nid)); - $output .= " "; + if ($op == "Update settings") { + $user = user_save($user, array("sortby" => $sortby, "forum_per_page" => $forum_per_page)); } - $output .= "
    ". t("Forum") ."". t("Comments") ."". t("Last comment") ."
    ". l(check_output($node->title), array("id" => $node->nid)) ."
    ". check_output($node->body, 1) ."
    ". forum_num_comments($node->nid) ."". forum_last_comment($node->nid) ."
    "; - $theme->header(); - $theme->box(t("Discussion forum"), $output); - $theme->footer(); + if ($op == "first_new") { + if ($nid = _forum_first_new($tid)) { + drupal_goto(drupal_url(array("id" => $nid), "node")); + } + } + + if (empty($sortby)) { + $sortby = isset($user->sortby) ? $user->sortby : variable_get("forum_order",1); + } + if (empty($forum_per_page)) { + $forum_per_page = isset($user->forum_per_page) ? $user->forum_per_page : variable_get("forum_per_page", 25); + } + if (empty($offset)) { + $offset = 0; + } + + $forums = forum_get_forums($tid); + $parents = forum_get_parents($tid); + if ($tid && !in_array($tid, variable_get("forum_containers", array()))) { + list($topics, $topic_num) = forum_get_topics($tid, $sortby, $forum_per_page, $offset); + } + + _forum_theme_invoke("forum_render", $forums, $topics, $topic_num, $parents, $tid, $sortby, $forum_per_page, $offset); } else { $theme->header(); @@ -105,4 +490,249 @@ function forum_page() { } } -?> \ No newline at end of file +/** +*** render functions +**/ + +function forum_render($forums, $topics, $topic_num, $parents, $tid, $sortby, $forum_per_page, $offset) { + // forum list, topics list, topic browser and "add new topic" link + global $theme; + + $output .= _forum_theme_invoke("forum_forum_list", $forums, $parents, $tid); + if ($tid && !in_array($tid, variable_get("forum_containers", array()))) { + $output .= _forum_theme_invoke("forum_topic_list", $topics, $topic_num, $sortby, $forum_per_page, $offset); + } + + $theme->header(); + $theme->box(t("Discussion forum"), $output); + if ($tid && !in_array($tid, variable_get("forum_containers", array()))) { + $theme->box(t("Control panel"), _forum_theme_invoke("forum_topic_browser", $sortby, $forum_per_page, $offset)); + } + $theme->footer(); +} + +function forum_forum_list($forums, $parents, $tid) { + global $user; + if ($parents) { + foreach($parents as $p) { + if ($tid != $p->tid) { + $t[] = lm($p->name, array("mod" => "forum", "tid" => $p->tid)); + } + else { + $t[] = $p->name; + } + } + } + $t[] = lm(t("forum"), array("mod" => "forum")); + + $output .= "\n"; + $output .= " "; + if ($forums) { + $output .= ""; + + + foreach ($forums as $forum) { + if ($forum->container) { + $output .= " "; + } else { + if ($user->uid) $new_topics = $forum->num_topics - $forum->old_topics; + $icon = _forum_get_folder_icon($new_topics); + $output .= " "; + $output .= ""; + $output .= ""; + } + } + } + $output .= "
    ".implode(" : ", array_reverse($t)) ."". t("topics") ."". t("posts") ."". t("last post") ."
    ".lm(check_output($forum->name), array("mod" => "forum", "tid" => $forum->tid))."
    ". ($forum->description ? check_output($forum->description, 1) : "") ."
     $icon
    depth*20)."\"> ".lm(check_output($forum->name), array("mod" => "forum", "tid" => $forum->tid))."
    ". check_output($forum->description, 1); + + $links = array(); + if ($forum->last_post) { + $links[] = l(t("last topic"), array("nid" => $forum->last_post->nid)); + } + if ($new_topics) { + $links[] = lm(t("first new topic"), array("mod" => "forum", "op" => "first_new", "tid" => $forum->tid)); + } + + if ($links) { + $output .= "
    (".implode(", ", $links).")"; + } + + $output .= "
    ".$forum->num_topics.($new_topics ? "
    (".t("%a new", array("%a" => $new_topics)).")" : "")."
    ".$forum->num_posts.""._forum_format($forum->last_post)."
    \n"; + + return $output; +} + +function forum_topic_browser() { + global $tid, $sortby, $forum_per_page, $offset; + + if (empty($sortby)) { + $sortby = variable_get("forum_order",1); + } + if (empty($forum_per_page)) { + $forum_per_page = variable_get("forum_per_page", 25); + } + + $forum_per_page_options = array(10, 25, 50, 75, 100); + foreach ($forum_per_page_options as $value) { + $options .= " \n"; + } + + $output .= "\n"; + + $options = ""; + $sortby_options = array(1 => t("Date - newest first"), 2 => t("Date - oldest first"), 3 => t("Posts - most active first"), 4=> t("Posts - least active first")); + foreach ($sortby_options as $key => $value) { + $options .= " \n"; + } + + $output .= "\n\n"; + $output .= form_hidden("tid", $tid); + $output .= form_submit(t("Update settings")); + return form(form_item(t("Topic viewing options"), $output, t("Select your preferred way to display the topics and click 'Update settings'."))); +} + +function forum_topic_list($topics, $num_topics, $sortby, $forum_per_page, $offset) { + global $theme, $id, $status, $tid, $user; + + $output .= "

    (".t("%a topics, %b topics per page, page %c of %d", array("%a" => $num_topics, "%b" => $forum_per_page, "%c" => ceil(($offset + 1)/$forum_per_page), "%d" => ceil($num_topics/$forum_per_page))).")
    "; + + if ($topics) { + $output .= "\n"; + $output .= " "; + + foreach ($topics as $topic) { + // folder is new if topic is new or there are new comments since last visit + if ($topic->shadow) { + $output .= " + + + + + + "; + } + else { + $output .= " + + + + + + + + "; + } + } + + $output .= "
      ". t("topic") ."". t("replies") ."". t("posted") ."". t("last reply") ."
    "._forum_get_folder_icon($topic->new, $topic->num_comments, $topic->comment_mode).""._forum_get_icon($topic)."". check_output($topic->title) ."".lm(t("This topic has been moved"), array("mod" => "forum", "tid" => $topic->shadow))."
    "._forum_get_folder_icon($topic->new, $topic->num_comments, $topic->comment_mode).""._forum_get_icon($topic)."".l(check_output($topic->title), array("id" => $topic->nid))."".$topic->num_comments.($topic->new_replies ? " (".t("%a new", array("%a" => $topic->new_replies)).")" : "").""._forum_format($topic).""._forum_format($topic->last_reply)."
    \n"; + } + + $output .= ""; + + if ($offset > 0) { + $output .= ""; + } + else { + $output .= ""; + } + + $output .= ""; + + if (count($topics) >= $forum_per_page) { + $output .= ""; + } + else { + $output .= ""; + } + + $output .= "

    ".lm(t("Previous topics"), array("mod" => "forum", "tid" => $tid, "offset" => ($offset-$forum_per_page)))."

     

    ".lm(t("Start new topic"), array("mod" => "node", "op" => "add", "type" => "forum", "tid" => $tid))."

    ".lm(t("Next topics"), array("mod" => "forum", "tid" => $tid, "offset" => ($offset+$forum_per_page)))."

     
    "; + + return $output; +} + + +function _forum_get_icon($node) { + if (variable_get("forum_topic_icon_path", "images/topics/") && $node->icon) { + return "icon)."\">"; + } + else { + return " "; + } +} + +function _forum_get_folder_icon($new_posts, $num_posts = 0, $comment_mode = 0) { + // "folder" icon because it's generally rendered as a folder + global $theme; + + $base_path = variable_get("forum_folder_icon_path", "images/forum/folder"); + if ($base_path) { + if ($num_posts > variable_get("forum_hot_topic", 15)) { + $icon = $new_posts ? "hot_new" : "hot"; + } + else { + $icon = $new_posts ? "new" : "default"; + } + + if ($comment_mode == 1) { + $icon = "closed"; + } + + if ($theme->bbs_icons) { + $file = $theme->bbs_icons[$icon]; + } + else { + // default + $file = $base_path."/".$icon.".gif"; + } + + return ""; + } + else { + return " "; + } +} + +function _forum_user_last_visit($nid) { + global $user; + static $history; + if (!$history) { + $result = db_query("SELECT nid, timestamp FROM history WHERE uid = '%d'", $user->uid); + while ($t = db_fetch_object($result)) { + $history[$t->nid] = $t->timestamp; + } + } + return $history[$nid] ? $history[$nid] : 0; +} + +// this will go away as soon as theme_invoke is committed +function _forum_theme_invoke() { + global $theme; + $args = func_get_args(); + $function = array_shift($args); + + if (method_exists($theme, $function)) { + return call_user_method_array($function, $theme, $args); + } + else { + return call_user_func_array($function, $args); + } +} + +function _forum_get_topic_order($sortby) { + switch ($sortby) { + case 1: + return "date_sort DESC"; + break; + case 2: + return "date_sort ASC"; + break; + case 3: + return "num_comments DESC"; + break; + case 4: + return "num_comments ASC"; + break; + } +} + +?> diff --git a/modules/forum/forum.module b/modules/forum/forum.module index a7dd3b2902b..3f8f230e3e9 100644 --- a/modules/forum/forum.module +++ b/modules/forum/forum.module @@ -2,7 +2,7 @@ // $Id$ function forum_system($field){ - $system["description"] = t("Allows threaded discussions about general topics."); + $system["description"] = t("Enable threaded discussions about general topics."); return $system[$field]; } @@ -17,16 +17,49 @@ function forum_access($op, $node) { if ($op == "view") { return $node->status; } + if ($op == "create") { + return user_access("post content"); + } +} + +function forum_perm() { + return array("post forum topics without approval"); +} + +function forum_conf_options() { + foreach (taxonomy_get_vocabularies("forum") as $vid => $voc) { + $vocs[$vid] = $voc->name; + } + $output .= ""; + $output .= form_select("Forum vocabulary", "forum_nav_vocabulary", variable_get("forum_nav_vocabulary", ""), $vocs, t("The taxonomy vocabulary that will be uses as the navigation tree.")); + $output .= _taxonomy_term_select("Containers", "forum_containers", variable_get("forum_containers", array()), variable_get("forum_nav_vocabulary", ""), t("You can choose vocabularies which will not have topics, but will be just containers for other forums."), 1, t("")); + + $output .= form_textfield("Topic icons path", "forum_topic_icon_path", variable_get("forum_topic_icon_path", "images/forum/topics/"), 30, 255, "The path to the topic icons. Leave blank to disable icons."); + $output .= form_textfield("Folder icons path", "forum_folder_icon_path", variable_get("forum_folder_icon_path", "images/forum/folder/"), 30, 255, "Path to the default, hot, new and closed folder icons. Leave blank to disable icons."); + $number = array(5 => 5, 10 => 10, 15 => 15, 20 => 20, 25 => 25, 30 => 30, 35 => 35, 40 => 40, 50 => 50, 60 => 60, 80 => 80, 100 => 100, 10000=>10000); + $output .= form_select("Hot topic threshold", "forum_hot_topic", variable_get("forum_hot_topic", 15), $number, "The number of posts a topic must have to be considered hot."); + $number = array(10 => 10, 25 => 25, 50 => 50, 75 => 75, 100 => 100); + $output .= form_select("Topics per page", "forum_per_page", variable_get("forum_per_page", 25), $number, "The default number of topics displayed per page; links to browse older messages are automatically being displayed."); + $forder = array(1 => "Date - newest first", 2 => "Date - oldest first", 3 => "Posts - most active first", 4=> "Posts - least active first"); + $output .= form_select("Default order", "forum_order", variable_get("forum_order", 1), $forder, "The default display order for topics."); + $output .= form_textfield("Number of topics in block", "forum_block_num", variable_get("forum_block_num", "5"), 5, 5, "The number of topics in the active topics block."); + $output .= form_select("Internal caching", "forum_cache", variable_get("forum_cache", 0), array("disabled", "enabled"), "Cache internal datastructures for both anonymous and autheticated users. When enabled, the forum data won't be 100% up to date with regards regardto the number of posts, the number of replies and the last topic being posted. Enable when you have busy forum."); + + return $output; } function forum_save($op, $node) { - if ($op == "approve") { return array("status" => 1); } if ($op == "create") { - return array("body" => filter($node->body), "teaser" => filter($node->teaser)); + if (user_access("post forum topics without approval")) { + $moderation = array("body" => filter($node->body), "moderate" => 0, "status" => 1); + } else { + $moderation = array("body" => filter($node->body), "moderate" => 1, "status" => 0); + } + return array_merge($moderation, array("tid", "icon_num", "shadow")); } if ($op == "decline") { @@ -34,69 +67,421 @@ function forum_save($op, $node) { } if ($op == "update") { - return array("body" => filter($node->body), "teaser" => filter($node->teaser)); + // if she changed the term, we could have to leave a shadow + $old_terms = explode(",", $node->old_container); + foreach ($node->taxonomy as $term) { + if ($term && (!in_array($term, $old_terms)) && $node->shadow) { + $shadow = $term; + break; + } + } + return array("body" => filter($node->body), "tid", "icon_num", "shadow" => $shadow, "old_container"); } } -function forum_link($type) { +function forum_load($node) { + $forum = db_fetch_object(db_query("SELECT * FROM forum WHERE nid = '%d' AND shadow = 0", $node->nid)); + + return $forum; +} + +function forum_block() { + if (user_access("access content")) { + $result = db_query("SELECT n.nid, n.title, n.body, GREATEST(n.created, MAX(c.timestamp)) AS sort FROM node n, forum f LEFT JOIN comments c ON c.nid = n.nid WHERE n.type = 'forum' AND n.nid = f.nid AND f.shadow = 0 AND n.status = 1 GROUP BY n.nid ORDER BY sort DESC LIMIT ". variable_get("forum_block_num", "5")); + while ($node = db_fetch_object($result)) { + $content .= "
  • ".l(check_output($node->title), array("id" => $node->nid), "node", "", array("title" => substr(strip_tags($node->body), 0, 100)."..."))."
  • \n"; + } + + if ($content) { + $content .= "

    ".lm(t("browse forums"), array("mod" => "forum"))."

    "; + } + } + $blocks[0][subject] = t("Forum active topics"); + $blocks[0][content] = $content; + $blocks[0][info] = t("Forum active topics"); + + return $blocks; +} + +function forum_link($type, $node) { if ($type == "page" && user_access("access content")) { - $links[] = lm(t("forum"), array("mod" => "forum"), "", array("title" => t("Read and participate in the discussion forums."))); + $links[] = lm(t("forum"), array("mod" => "forum")); } - if ($type == "menu.create" && user_access("administer nodes")) { - $links[] = lm(t("create forum"), array("mod" => "node", "op" => "add", "type" => "forum"), "", array("title" => t("Add a new discussion forum."))); + if ($type == "menu.create" && user_access("post content")) { + // I had complains that having this link leads people not to choose a forum, and always post in the first forum. + #$links[] = lm(t("create forum topic"), array("mod" => "node", "op" => "add", "type" => "forum"), "", array("title" => t("Start a new forum topic."))); + } + + if ($type == "node" && $node->type == "forum") { + // get previous and next topic + + $sql = "SELECT n.nid, title, body, GREATEST(n.created, MAX(c.timestamp)) AS date_sort, COUNT(c.nid) AS num_comments FROM node n, term_node t, forum f LEFT JOIN comments c ON c.nid = n.nid WHERE n.nid = t.nid AND n.nid = f.nid AND t.tid = '%d' AND f.shadow = 0 AND n.status = 1 GROUP BY n.nid ORDER BY ". _forum_get_topic_order(isset($user->sortby) ? $user->sortby : variable_get("forum_order",1)); + + $terms = array_keys(taxonomy_node_get_terms_by_vocabulary($node->nid, variable_get("forum_nav_vocabulary", ""))); + $result = db_query($sql, $terms[0]); + + while ($topic = db_fetch_object($result)) { + if ($stop == 1) { + $next->nid = $topic->nid; + $next->title = $topic->title; + $next->body = $topic->body; + break; + } + if ($topic->nid == $node->nid) { + $stop = 1; + } + else { + $prev->nid = $topic->nid; + $prev->title = $topic->title; + $prev->body = $topic->body; + } + } + + if ($prev) { + $links[] = l(t("previous topic"), array("id" => $prev->nid), "node", "", array("title" => $prev->title."\n".substr(strip_tags($prev->body), 0, 100)."...")); + } + + if ($next) { + $links[] = l(t("next topic"), array("id" => $next->nid), "node", "", array("title" => $next->title."\n".substr(strip_tags($next->body), 0, 100)."...")); + } } return $links ? $links : array(); } -function forum_view($node) { +function forum_view($node, $main = 0) { global $theme; - $output .= "

    ". lm(t("Forum"), array("mod" => "forum")) ." / ". l(check_output($node->title), array("id" => $node->nid)) .":

    ". check_output($node->body) ."

    "; + + $term_data = array_shift(taxonomy_node_get_terms($node->nid)); + if (!$term_data) { + // we are previewing + $term_data = taxonomy_get_term($node->taxonomy[0]); + } + $voc = taxonomy_get_vocabulary($term_data->vid); + + $output .= "

    "._forum_get_icon($node)." ".lm(check_output($voc->name), array("mod" => "forum"))." : ".lm(check_output($term_data->name), array("mod" => "forum", "tid" => $term_data->tid)); + + $output .= " / ". check_output($node->title) ."
    ".t("%a by %b", array("%a" => format_date($node->created), "%b" => format_name($node)))."

    ". check_output($node->body, 1) ."

    "; $output .= "

    ". $theme->links(link_node($node, $main)) ."

    "; - $theme->box(t("Discussion forum"), $output); + #$theme->box(t("Discussion forum"), $output); + + $node->title = _forum_get_icon($node)." ".lm(check_output($voc->name), array("mod" => "forum"))." : ".lm(check_output($term_data->name), array("mod" => "forum", "tid" => $term_data->tid)) . " / ". check_output($node->title) .""; + + $theme->node($node, $main); } function forum_form(&$node, &$help, &$error) { - - if (function_exists("taxonomy_node_form")) { - $output = implode("", taxonomy_node_form("forum", $node)); + global $tid; + if ($node->nid) { + if ($node->taxonomy) { + $tid = $node->taxonomy; + } else { + // editing: load category from taxonomy + $tid = implode(",", array_keys(taxonomy_node_get_terms($node->nid))); + } + //$output .= implode("

    ", taxonomy_node_form("forum", $node)); + $output .= "nid)))."\">"; + $output .= form_checkbox(t("Leave shadow?"), "shadow", 1, 1, t("If you move this topic, you can leave a link in the old forum to the new forum.")); + } else { + if ($node->taxonomy) { + $tid = $node->taxonomy; + } } - $output .= form_textarea(t("Body"), "body", $node->body, 60, 10); + $output .= _taxonomy_term_select("Forum", "taxonomy", $tid, variable_get("forum_nav_vocabulary", ""), "", 0, "", variable_get("forum_containers", array())); + + if ($icon_path = variable_get("forum_topic_icon_path", "images/forum/topics/")) { + if ($node->icon) { + // we are editing post + if ($dir = @opendir($icon_path)) { + $icon_num = 0; + while($icon = readdir($dir)) { + if ($icon == '.' || $icon == '..') {continue;} + if ($node->icon == $icon) {$checked = "checked";} else {$checked = "";} + $radio .= " \n"; + $icon_num++; + } + closedir($dir); + } + } + else { + if ($dir = @opendir($icon_path)) { + $icon_num = 0; + while($icon = readdir($dir)) { + if ($icon == '.' || $icon == '..') {continue;} + if ($node->icon_num == $icon_num) {$checked = "checked";} else {$checked = "";} + $radio .= " \n"; + $icon_num++; + } + closedir($dir); + } + } + $output .= form_item(t("Topic icon"), $radio); + } + $output .= form_textarea("Body", "body", $node->body, 60, 10); return $output; } +function forum_insert($node) { + $node->icon = _forum_decode_icon($node); + if (!$node->shadow) { + db_query("INSERT INTO forum (nid, icon) VALUES ('%d', '%s')", $node->nid, $node->icon); + } else { + // we created a shadow, a link to a moved topic in a new forum + db_query("INSERT INTO forum (nid, icon, shadow) VALUES ('%d', '%s', '%d')", $node->nid, $node->icon, $node->shadow); + } -function forum_num_comments($nid) { - $value = db_fetch_object(db_query("SELECT COUNT(cid) AS count FROM comments WHERE nid = '$nid'")); + if (variable_get("forum_cache", 0)) { + cache_clear(); + } +} + +function forum_update($node) { + $node->icon = _forum_decode_icon($node); + db_query("UPDATE forum SET icon = '%s' WHERE nid = '%d'", $node->icon, $node->nid); + if ($node->shadow) { + // insert in old forum a new topic, with link to new forum + $node->nid = ""; + $node->type = "forum"; + $node->status = 1; + $node->taxonomy = array($node->old_container); + node_submit($node); + } +} + +function _forum_decode_icon($node) { + // to prevent malicious users + if ($icon_path = variable_get("forum_topic_icon_path", "images/forum/topics/")) { + if ($dir = @opendir($icon_path)) { + $icon_num = 0; + while($icon = readdir($dir)) { + if ($icon == '.' || $icon == '..') {continue;} + if ($icon_num == $node->icon_num) {$myicon = $icon;} + $icon_num++; + } + closedir($dir); + } + } + return $myicon; +} + +function forum_delete(&$node) { + db_query("DELETE FROM forum WHERE nid = '%d'", $node->nid); +} + +function _forum_num_comments($nid) { + $value = db_fetch_object(db_query("SELECT COUNT(cid) AS count FROM comments WHERE nid = '%d' AND status = 0", $nid)); return ($value) ? $value->count : 0; } -function forum_last_comment($nid) { - $value = db_fetch_object(db_query("SELECT timestamp FROM comments WHERE nid = '$nid' ORDER BY timestamp DESC LIMIT 1")); +function _forum_last_comment($nid) { + $value = db_fetch_object(db_query("SELECT timestamp FROM comments WHERE nid = '%d' AND status = 0 ORDER BY timestamp DESC LIMIT 1", $nid)); return ($value) ? format_date($value->timestamp, "small") : " "; } +function _forum_last_reply($nid) { + $value = db_fetch_object(db_query("SELECT c.timestamp, u.name, u.uid FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.nid = '%d' AND c.status = 0 ORDER BY c.timestamp DESC LIMIT 1", $nid)); + return $value; +} + +function _forum_format($topic) { + if ($topic) { + return format_date($topic->timestamp, "small")."
    ".t("by")." ". format_name($topic); + } + else { + return message_na(); + } +} + +function forum_get_forums($tid = 0) { + global $user; + + if (!$tid) { + $tid = 0; + } + + if (variable_get("forum_cache", 0)) { + $forums = unserialize(cache_get("forum:$tid")); + } + + if (!$forums) { + taxonomy_get_tree(variable_get("forum_nav_vocabulary", ""), $_forums, $tid); + $n = 0; + foreach ($_forums as $forum) { + if (in_array($forum->tid, variable_get("forum_containers", array()))) { + $forum->container = 1; + } + else { + $forum->num_topics = _forum_num_topics($forum->tid); + $forum->num_posts = _forum_num_replies($forum->tid) + $forum->num_topics; + $forum->last_post = _forum_last_post($forum->tid); + } + $forums[$forum->tid] = $forum; + $n++; + } + + if (variable_get("forum_cache", 0)) { + cache_set("forum:$tid", serialize($forums), time()+60*10); + } + } + + if ($user->uid && $forums) { + foreach (_forum_topics_read($user->uid) as $tid => $old) { + if ($forums[$tid]) { + $forums[$tid]->old_topics = $old; + } + } + } + + return $forums; +} + +function forum_get_parents($tid) { + if ($tid) { + $parents[] = taxonomy_get_term($tid); + } + $n = 0; + while ($parent = taxonomy_get_parents($parents[$n]->tid)) { + $parents = array_merge($parents, $parent); + $n++; + } + return $parents; +} + +function _forum_num_topics($term) { + $value = db_fetch_object(db_query("SELECT COUNT(n.nid) AS count FROM node n, term_node r, forum f WHERE r.tid = '%d' AND n.nid = r.nid AND n.nid = f.nid AND n.status = 1 AND n.type = 'forum' AND f.shadow = 0", $term)); + return ($value) ? $value->count : 0; +} + +function _forum_num_replies($term) { + $value = db_fetch_object(db_query("SELECT COUNT(*) AS count FROM comments c, term_node r, node n WHERE r.tid = '%d' AND n.nid = r.nid AND n.nid = c.nid AND n.status = 1 AND c.status = 0 AND n.type = 'forum'", $term)); + return ($value) ? $value->count : 0; +} + +function _forum_topics_read($uid) { + $result = db_query("SELECT tid, count(*) AS c FROM history h, term_node r, node n WHERE r.nid = h.nid AND n.nid = h.nid AND n.type = 'forum' AND n.status = 1 AND h.uid = '%d' GROUP BY tid", $uid); + + while ($obj = db_fetch_object($result)) { + $topics_read[$obj->tid] = $obj->c; + } + + return $topics_read ? $topics_read : array(); +} + +function _forum_last_post($term) { + $topic = db_fetch_object(db_query("SELECT n.nid, n.created AS timestamp, u.name AS name, u.uid AS uid FROM node n, term_node r LEFT JOIN users u ON n.uid = u.uid WHERE r.tid = '%d' AND n.nid = r.nid AND n.type = 'forum' AND n.status = 1 ORDER BY timestamp DESC LIMIT 1", $term)); + + $reply = db_fetch_object(db_query("SELECT n.nid, c.timestamp, u.name AS name, u.uid AS uid FROM term_node r, node n LEFT JOIN comments c ON n.nid=c.nid LEFT JOIN users u ON c.uid = u.uid WHERE r.tid = '%d' AND n.nid = r.nid AND n.type = 'forum' AND n.status = 1 AND c.status = 0 ORDER BY c.timestamp DESC LIMIT 1", $term)); + + $value = ($topic->timestamp > $reply->timestamp) ? $topic : $reply; + + return $value; +} + +function forum_get_topics($tid, $sortby, $forum_per_page, $offset) { + global $user; + + $term = taxonomy_get_term($tid); + $voc = taxonomy_get_vocabulary($term->vid); + + $sql_sortby = _forum_get_topic_order($sortby); + + $sql = " + SELECT n.nid, title, users.name AS name, users.uid AS uid, n.created AS timestamp, GREATEST(n.created, MAX(c.timestamp)) AS date_sort, COUNT(c.nid) AS num_comments, icon, n.comment AS comment_mode, shadow FROM node n, term_node r LEFT JOIN users ON n.uid = users.uid LEFT JOIN comments c ON c.nid = n.nid LEFT JOIN forum f ON n.nid = f.nid WHERE n.nid = r.nid AND r.tid = '%d' AND n.status = 1 AND n.type = 'forum' GROUP BY n.nid ORDER BY $sql_sortby"; + + $result = db_query($sql, $tid); + $topic_num = db_num_rows($result); + + $n = 0; + while ($topic = db_fetch_object($result)) { + if ($n < $offset) { + $n++; + continue; + } + + $history = _forum_user_last_visit($topic->nid); + // folder is new if topic is new or there are new comments since last visit + if ($topic->shadow > 0) { + $topic->new = 0; + } + else { + if (!$history && $user->uid) { + $topic->new_replies = 0; + $topic->new = 1; + } + else { + $comments = db_result(db_query("SELECT COUNT(c.nid) FROM node n LEFT JOIN comments c ON n.nid = c.nid WHERE n.nid = '$topic->nid' AND c.status = 0 AND timestamp > '$history' GROUP BY n.nid")); + + $topic->new_replies = $comments ? $comments : 0; + if ($topic->new_replies) { + $topic->new = 1; + } + else { + $topic->new = 0; + } + } + } + + $topic->last_reply = _forum_last_reply($topic->nid); + $topics[] = $topic; + + $n++; + if ($n == ($forum_per_page + $offset)) { + break; + } + } + + return array($topics, $topic_num); +} + +function _forum_first_new($tid) { + global $user; + $result = db_query("SELECT r.nid FROM node n, history h, term_node r WHERE n.type = 'forum' AND n.status = 1 AND h.nid = n.nid AND r.nid = h.nid AND r.tid = '%d' AND h.uid = '%d' ", $tid, $user->uid); + while ($r = db_fetch_object($result)) { + $read[] = $r->nid; + } + + if ($read) { + $nid = db_result(db_query("SELECT r.nid AS c FROM node n LEFT JOIN term_node r ON r.nid = n.nid WHERE n.type = 'forum' AND n.status = 1 AND r.tid = '%d' AND NOT (r.nid IN (".implode(",", $read).")) ORDER BY created LIMIT 1", $tid)); + } + + return $nid ? $nid : 0; +} + function forum_page() { - global $theme; + global $theme, $tid, $sortby, $forum_per_page, $offset, $op, $user; if (user_access("access content")) { - $result = db_query("SELECT nid FROM node WHERE type = 'forum' ORDER BY title"); - - $output .= ""; - $output .= " "; - while ($node = db_fetch_object($result)) { - $node = node_load(array("nid" => $node->nid)); - $output .= " "; + if ($op == "Update settings") { + $user = user_save($user, array("sortby" => $sortby, "forum_per_page" => $forum_per_page)); } - $output .= "
    ". t("Forum") ."". t("Comments") ."". t("Last comment") ."
    ". l(check_output($node->title), array("id" => $node->nid)) ."
    ". check_output($node->body, 1) ."
    ". forum_num_comments($node->nid) ."". forum_last_comment($node->nid) ."
    "; - $theme->header(); - $theme->box(t("Discussion forum"), $output); - $theme->footer(); + if ($op == "first_new") { + if ($nid = _forum_first_new($tid)) { + drupal_goto(drupal_url(array("id" => $nid), "node")); + } + } + + if (empty($sortby)) { + $sortby = isset($user->sortby) ? $user->sortby : variable_get("forum_order",1); + } + if (empty($forum_per_page)) { + $forum_per_page = isset($user->forum_per_page) ? $user->forum_per_page : variable_get("forum_per_page", 25); + } + if (empty($offset)) { + $offset = 0; + } + + $forums = forum_get_forums($tid); + $parents = forum_get_parents($tid); + if ($tid && !in_array($tid, variable_get("forum_containers", array()))) { + list($topics, $topic_num) = forum_get_topics($tid, $sortby, $forum_per_page, $offset); + } + + _forum_theme_invoke("forum_render", $forums, $topics, $topic_num, $parents, $tid, $sortby, $forum_per_page, $offset); } else { $theme->header(); @@ -105,4 +490,249 @@ function forum_page() { } } -?> \ No newline at end of file +/** +*** render functions +**/ + +function forum_render($forums, $topics, $topic_num, $parents, $tid, $sortby, $forum_per_page, $offset) { + // forum list, topics list, topic browser and "add new topic" link + global $theme; + + $output .= _forum_theme_invoke("forum_forum_list", $forums, $parents, $tid); + if ($tid && !in_array($tid, variable_get("forum_containers", array()))) { + $output .= _forum_theme_invoke("forum_topic_list", $topics, $topic_num, $sortby, $forum_per_page, $offset); + } + + $theme->header(); + $theme->box(t("Discussion forum"), $output); + if ($tid && !in_array($tid, variable_get("forum_containers", array()))) { + $theme->box(t("Control panel"), _forum_theme_invoke("forum_topic_browser", $sortby, $forum_per_page, $offset)); + } + $theme->footer(); +} + +function forum_forum_list($forums, $parents, $tid) { + global $user; + if ($parents) { + foreach($parents as $p) { + if ($tid != $p->tid) { + $t[] = lm($p->name, array("mod" => "forum", "tid" => $p->tid)); + } + else { + $t[] = $p->name; + } + } + } + $t[] = lm(t("forum"), array("mod" => "forum")); + + $output .= "\n"; + $output .= " "; + if ($forums) { + $output .= ""; + + + foreach ($forums as $forum) { + if ($forum->container) { + $output .= " "; + } else { + if ($user->uid) $new_topics = $forum->num_topics - $forum->old_topics; + $icon = _forum_get_folder_icon($new_topics); + $output .= " "; + $output .= ""; + $output .= ""; + } + } + } + $output .= "
    ".implode(" : ", array_reverse($t)) ."". t("topics") ."". t("posts") ."". t("last post") ."
    ".lm(check_output($forum->name), array("mod" => "forum", "tid" => $forum->tid))."
    ". ($forum->description ? check_output($forum->description, 1) : "") ."
     $icon
    depth*20)."\"> ".lm(check_output($forum->name), array("mod" => "forum", "tid" => $forum->tid))."
    ". check_output($forum->description, 1); + + $links = array(); + if ($forum->last_post) { + $links[] = l(t("last topic"), array("nid" => $forum->last_post->nid)); + } + if ($new_topics) { + $links[] = lm(t("first new topic"), array("mod" => "forum", "op" => "first_new", "tid" => $forum->tid)); + } + + if ($links) { + $output .= "
    (".implode(", ", $links).")"; + } + + $output .= "
    ".$forum->num_topics.($new_topics ? "
    (".t("%a new", array("%a" => $new_topics)).")" : "")."
    ".$forum->num_posts.""._forum_format($forum->last_post)."
    \n"; + + return $output; +} + +function forum_topic_browser() { + global $tid, $sortby, $forum_per_page, $offset; + + if (empty($sortby)) { + $sortby = variable_get("forum_order",1); + } + if (empty($forum_per_page)) { + $forum_per_page = variable_get("forum_per_page", 25); + } + + $forum_per_page_options = array(10, 25, 50, 75, 100); + foreach ($forum_per_page_options as $value) { + $options .= " \n"; + } + + $output .= "\n"; + + $options = ""; + $sortby_options = array(1 => t("Date - newest first"), 2 => t("Date - oldest first"), 3 => t("Posts - most active first"), 4=> t("Posts - least active first")); + foreach ($sortby_options as $key => $value) { + $options .= " \n"; + } + + $output .= "\n\n"; + $output .= form_hidden("tid", $tid); + $output .= form_submit(t("Update settings")); + return form(form_item(t("Topic viewing options"), $output, t("Select your preferred way to display the topics and click 'Update settings'."))); +} + +function forum_topic_list($topics, $num_topics, $sortby, $forum_per_page, $offset) { + global $theme, $id, $status, $tid, $user; + + $output .= "

    (".t("%a topics, %b topics per page, page %c of %d", array("%a" => $num_topics, "%b" => $forum_per_page, "%c" => ceil(($offset + 1)/$forum_per_page), "%d" => ceil($num_topics/$forum_per_page))).")
    "; + + if ($topics) { + $output .= "\n"; + $output .= " "; + + foreach ($topics as $topic) { + // folder is new if topic is new or there are new comments since last visit + if ($topic->shadow) { + $output .= " + + + + + + "; + } + else { + $output .= " + + + + + + + + "; + } + } + + $output .= "
      ". t("topic") ."". t("replies") ."". t("posted") ."". t("last reply") ."
    "._forum_get_folder_icon($topic->new, $topic->num_comments, $topic->comment_mode).""._forum_get_icon($topic)."". check_output($topic->title) ."".lm(t("This topic has been moved"), array("mod" => "forum", "tid" => $topic->shadow))."
    "._forum_get_folder_icon($topic->new, $topic->num_comments, $topic->comment_mode).""._forum_get_icon($topic)."".l(check_output($topic->title), array("id" => $topic->nid))."".$topic->num_comments.($topic->new_replies ? " (".t("%a new", array("%a" => $topic->new_replies)).")" : "").""._forum_format($topic).""._forum_format($topic->last_reply)."
    \n"; + } + + $output .= ""; + + if ($offset > 0) { + $output .= ""; + } + else { + $output .= ""; + } + + $output .= ""; + + if (count($topics) >= $forum_per_page) { + $output .= ""; + } + else { + $output .= ""; + } + + $output .= "

    ".lm(t("Previous topics"), array("mod" => "forum", "tid" => $tid, "offset" => ($offset-$forum_per_page)))."

     

    ".lm(t("Start new topic"), array("mod" => "node", "op" => "add", "type" => "forum", "tid" => $tid))."

    ".lm(t("Next topics"), array("mod" => "forum", "tid" => $tid, "offset" => ($offset+$forum_per_page)))."

     
    "; + + return $output; +} + + +function _forum_get_icon($node) { + if (variable_get("forum_topic_icon_path", "images/topics/") && $node->icon) { + return "icon)."\">"; + } + else { + return " "; + } +} + +function _forum_get_folder_icon($new_posts, $num_posts = 0, $comment_mode = 0) { + // "folder" icon because it's generally rendered as a folder + global $theme; + + $base_path = variable_get("forum_folder_icon_path", "images/forum/folder"); + if ($base_path) { + if ($num_posts > variable_get("forum_hot_topic", 15)) { + $icon = $new_posts ? "hot_new" : "hot"; + } + else { + $icon = $new_posts ? "new" : "default"; + } + + if ($comment_mode == 1) { + $icon = "closed"; + } + + if ($theme->bbs_icons) { + $file = $theme->bbs_icons[$icon]; + } + else { + // default + $file = $base_path."/".$icon.".gif"; + } + + return ""; + } + else { + return " "; + } +} + +function _forum_user_last_visit($nid) { + global $user; + static $history; + if (!$history) { + $result = db_query("SELECT nid, timestamp FROM history WHERE uid = '%d'", $user->uid); + while ($t = db_fetch_object($result)) { + $history[$t->nid] = $t->timestamp; + } + } + return $history[$nid] ? $history[$nid] : 0; +} + +// this will go away as soon as theme_invoke is committed +function _forum_theme_invoke() { + global $theme; + $args = func_get_args(); + $function = array_shift($args); + + if (method_exists($theme, $function)) { + return call_user_method_array($function, $theme, $args); + } + else { + return call_user_func_array($function, $args); + } +} + +function _forum_get_topic_order($sortby) { + switch ($sortby) { + case 1: + return "date_sort DESC"; + break; + case 2: + return "date_sort ASC"; + break; + case 3: + return "num_comments DESC"; + break; + case 4: + return "num_comments ASC"; + break; + } +} + +?> diff --git a/update.php b/update.php index b97811d5543..bfb9382206e 100644 --- a/update.php +++ b/update.php @@ -50,7 +50,8 @@ $mysql_updates = array( "2002-08-10" => "update_35", "2002-08-16" => "update_36", "2002-08-19" => "update_37", - "2002-08-26" => "update_38" + "2002-08-26" => "update_38", + "2002-09-15" => "update_39" ); // Update functions @@ -537,6 +538,49 @@ function update_38() { update_sql("ALTER TABLE watchdog CHANGE message message text NOT NULL default '';"); } +function update_39() { + update_sql("DROP TABLE moderate"); + + update_sql("ALTER TABLE comments ADD score MEDIUMINT NOT NULL;"); + update_sql("ALTER TABLE comments ADD status TINYINT UNSIGNED NOT NULL;"); + update_sql("ALTER TABLE comments ADD users MEDIUMTEXT;"); + + update_sql("CREATE TABLE moderation_votes ( + mid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + vote VARCHAR(255), + weight TINYINT NOT NULL + );"); + + update_sql("CREATE TABLE moderation_roles ( + rid INT UNSIGNED NOT NULL, + mid INT UNSIGNED NOT NULL, + value TINYINT NOT NULL + );"); + + update_sql("ALTER TABLE moderation_roles ADD INDEX (rid);"); + update_sql("ALTER TABLE moderation_roles ADD INDEX (mid);"); + + update_sql("CREATE TABLE moderation_filters ( + fid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + filter VARCHAR(255) NOT NULL, + minimum SMALLINT NOT NULL + );"); + + update_sql("DELETE FROM moderation_votes;"); + update_sql("INSERT INTO moderation_votes VALUES (1, '+1', 0);"); + update_sql("INSERT INTO moderation_votes VALUES (2, '-1', 1);"); + + update_sql("DELETE FROM moderation_roles;"); + update_sql("INSERT INTO moderation_roles VALUES (2, 1, 1);"); + update_sql("INSERT INTO moderation_roles VALUES (2, 2, -1);"); + + update_sql("CREATE TABLE forum ( + nid int unsigned not null primary key, + icon varchar(255) not null, + shadow int unsigned not null + );"); +} + function update_upgrade3() { update_sql("INSERT INTO system VALUES ('archive.module','archive','module','',1);"); update_sql("INSERT INTO system VALUES ('block.module','block','module','',1);");