New module 'Nodecomment'

This commit is contained in:
Manuel Cillero 2017-07-26 11:36:38 +02:00
parent 09b74574f1
commit 3c2bb788b4
26 changed files with 3513 additions and 0 deletions

View file

@ -0,0 +1,666 @@
<?php
/**
* @file
* Helper functions of the Node Comments module.
*/
/**
* Accepts a submission of new or changed comment content.
*
* @param $node
* The node that is serving as a comment to another node.
*
* @return
* If the comment is successfully saved the node ID of the comment is returned. If the comment
* is not saved, FALSE is returned.
*/
function nodecomment_save($node) {
global $user;
if (!isset($node->thread)) {
$node->thread = nodecomment_get_thread($node);
}
// MySQL-only upsert. Sorry Postgres guys.
// When updating, do not change the original submitted IP Address.
db_query(
"INSERT INTO {node_comments}
(cid, nid, pid, hostname, thread, name, uid, mail, homepage)
VALUES (%d, %d, %d, '%s', '%s', '%s', %d, '%s', '%s')
ON DUPLICATE KEY UPDATE
nid=VALUES(nid), pid=VALUES(pid), thread=VALUES(thread), name=VALUES(name),
uid=VALUES(uid), mail=VALUES(mail), homepage=VALUES(homepage)",
$node->nid, $node->comment_target_nid, $node->comment_target_cid,
ip_address(), $node->thread, $node->name, $node->uid, $node->mail,
$node->homepage
);
_nodecomment_update_node_statistics($node->comment_target_nid);
// Explain the approval queue if necessary, and then
// redirect the user to the node he's commenting on.
if ($node->moderate == 1) {
drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.'));
}
return $node->nid;
}
/**
* Set the breadcrumb trail to include another node.
*
* This is used when viewing or adding a comment so that the parent node's
* breadcrumb trail is used instead of the normal breadcrumb paths.
*
* @param $node
* The node to use.
*/
function nodecomment_set_breadcrumb($node) {
// If the node had any breadcrumb changes, they will be made via nodeapi('view')
// as a general rule, so this will make them happen.
node_invoke_nodeapi($node, 'view', FALSE, TRUE);
// Then add the parent node to the trail.
$breadcrumb = drupal_get_breadcrumb();
$breadcrumb[] = l($node->title, "node/$node->nid");
drupal_set_breadcrumb($breadcrumb);
}
/**
* Get number of new comments for current user and specified node
*
* @param $nid node-id to count comments for
* @param $timestamp time to count from (defaults to time of last user access
* to node)
*/
function nodecomment_num_new($nid, $timestamp = 0) {
global $user;
if ($user->uid) {
// Retrieve the timestamp at which the current user last viewed the
// specified node.
if (!$timestamp) {
$timestamp = node_last_viewed($nid);
}
$timestamp = ($timestamp > NODE_NEW_LIMIT ? $timestamp : NODE_NEW_LIMIT);
// Use the timestamp to retrieve the number of new comments.
$result = db_result(db_query(
"SELECT COUNT(cn.nid) FROM {node} n
INNER JOIN {node_comments} c ON n.nid = c.nid
INNER JOIN {node} cn ON c.cid = cn.nid
WHERE n.nid = %d AND (cn.created > %d OR cn.changed > %d) AND cn.status = %d",
$nid, $timestamp, $timestamp, 1
));
return $result;
}
else {
return 0;
}
}
/**
* Calculate page number for any given comment.
*
* @param $comment
* The comment.
* @return
* The page number.
*/
function nodecomment_page_count($comment, $node = NULL) {
if (!$node) {
if (empty($comment->comment_target_nid)) {
return '';
}
$node = node_load($comment->comment_target_nid);
if (!nodecomment_get_comment_type($node->type)) {
return '';
}
}
$comments_per_page = _comment_get_display_setting('comments_per_page', $node);
$mode = _comment_get_display_setting('mode', $node);
$order = _comment_get_display_setting('sort', $node);
$flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED, COMMENT_MODE_FLAT_EXPANDED));
if ($flat) {
$field = 'n.nid';
$value = '%d';
$arg = $comment->nid;
}
else {
$field = 'nc.thread';
$value = "'%s'";
$arg = $comment->thread;
}
if ($order == COMMENT_ORDER_NEWEST_FIRST) {
$op = ' >= ';
}
else {
$op = ' <= ';
}
$query = "SELECT COUNT(*) FROM {node_comments} nc
INNER JOIN {node} n ON n.nid = nc.cid
WHERE $field $op $value AND n.status <> 0 AND n.nid != %d AND nc.nid = %d";
$count = db_result(db_query($query, $arg, $comment->nid, $node->nid));
$pageno = intval($count / $comments_per_page);
return $pageno;
}
/**
* Calculate page number for first new comment.
*
* This works for both comments and nodecomments.
*
* @param $num_comments
* Number of comments.
* @param $new_replies
* Number of new replies.
* @param $node
* The first new comment node.
* @return
* "page=X" if the page number is greater than zero; empty string otherwise.
*/
function nodecomment_new_page_count($num_comments, $new_replies, $node) {
// Default to normal comments so this function works either way.
if (!nodecomment_get_comment_type($node->type)) {
return comment_new_page_count($num_comments, $new_replies, $node);
}
$comments_per_page = _comment_get_display_setting('comments_per_page', $node);
$mode = _comment_get_display_setting('mode', $node);
$order = _comment_get_display_setting('sort', $node);
$pagenum = NULL;
$flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED, COMMENT_MODE_FLAT_EXPANDED));
if ($num_comments <= $comments_per_page || ($flat && $order == COMMENT_ORDER_NEWEST_FIRST)) {
// Only one page of comments or flat forum and newest first.
// First new comment will always be on first page.
$pageno = 0;
}
else {
if ($flat) {
// Flat comments and oldest first.
$count = $num_comments - $new_replies;
}
else {
// Threaded comments. See the documentation for comment_render().
if ($order == COMMENT_ORDER_NEWEST_FIRST) {
// Newest first: find the last thread with new comment
$thread = db_result(db_query(
"(SELECT thread FROM {node_comments} nc
INNER JOIN {node} n ON n.nid = nc.cid
WHERE nc.nid = %d AND n.status <> 0
ORDER BY n.created DESC LIMIT %d)
ORDER BY thread DESC LIMIT 1",
$node->nid, $new_replies
));
$result_count = db_query(
"SELECT COUNT(*) FROM {node_comments} nc
INNER JOIN {node} n ON n.nid = nc.cid
WHERE nc.nid = %d AND n.status <> 0 AND nc.thread > '%s'",
$node->nid, $thread
);
}
else {
// Oldest first: find the first thread with new comment
$result = db_query(
'(SELECT thread FROM {node_comments} nc
INNER JOIN {node} n ON n.nid = nc.cid
WHERE nc.nid = %d AND n.status <> 0
ORDER BY n.created DESC LIMIT %d)
ORDER BY SUBSTRING(thread, 1, (LENGTH(thread) - 1)) LIMIT 1',
$node->nid, $new_replies
);
$thread = substr(db_result($result), 0, -1);
$result_count = db_query(
"SELECT COUNT(*) FROM {comments} nc
INNER JOIN {node} n ON n.nid = nc.cid
WHERE nc.nid = %d AND n.status <> 0 AND SUBSTRING(nc.thread, 1, (LENGTH(nc.thread) - 1)) < '%s'",
$node->nid, $thread
);
}
$count = db_result($result_count);
}
$pageno = $count / $comments_per_page;
}
if ($pageno >= 1) {
$pagenum = "page=". intval($pageno);
}
return $pagenum;
}
function nodecomment_get_thread($node) {
// Here we are building the thread field. See the documentation for
// comment_render().
if (empty($node->comment_target_cid)) {
// This is a comment with no parent comment (depth 0): we start
// by retrieving the maximum thread level.
$max = db_result(db_query(
"SELECT MAX(thread) FROM {node_comments} WHERE nid = %d",
$node->comment_target_nid
));
// Strip the "/" from the end of the thread.
$max = rtrim($max, '/');
// Finally, build the thread field for this new comment.
$thread = int2vancode(vancode2int($max) + 1) .'/';
}
else {
// This is comment with a parent comment: we increase
// the part of the thread value at the proper depth.
// Get the parent comment:
$parent = node_load($node->comment_target_cid);
// Strip the "/" from the end of the parent thread.
$parent->thread = (string) rtrim((string) $parent->thread, '/');
// Get the max value in _this_ thread.
$max = db_result(db_query(
"SELECT MAX(thread) FROM {node_comments}
WHERE thread LIKE '%s.%%' AND nid = %d",
$parent->thread, $node->comment_target_nid
));
if ($max == '') {
// First child of this parent.
$thread = $parent->thread .'.'. int2vancode(0) .'/';
}
else {
// Strip the "/" at the end of the thread.
$max = rtrim($max, '/');
// We need to get the value at the correct depth.
$parts = explode('.', $max);
$parent_depth = count(explode('.', $parent->thread));
$last = $parts[$parent_depth];
// Finally, build the thread field for this new comment.
$thread = $parent->thread .'.'. int2vancode(vancode2int($last) + 1) .'/';
}
}
return $thread;
}
/**
* Delete children comments from the same thread as comment.
*/
function _nodecomment_thread_delete_children($cid, $nid) {
// Delete the comment's replies.
// We have to be careful here to not delete comments from a separate thread
// started by this node, if it has own comments.
// To be sure about this, also check node id.
$result = db_query("SELECT cid FROM {node_comments} WHERE pid = %d AND nid = %d", $cid, $nid);
$delete = array();
while ($row = db_fetch_object($result)) {
$delete[] = $row->cid;
}
foreach ($delete as $cid) {
_nodecomment_delete_comment($cid);
}
}
/**
* Delete node comments of a node.
*/
function _nodecomment_delete_comments($nid) {
$result = db_query('SELECT cid FROM {node_comments} WHERE nid = %d', $nid);
$delete = array();
while ($row = db_fetch_object($result)) {
$delete[] = $row->cid;
}
foreach ($delete as $cid) {
_nodecomment_delete_comment($cid);
}
}
/**
* Delete a nodecomment.
*
* Based on code from node_delete() function but has different logics.
*
* Previously, the module used node_delete() which checks permissions.
* If the user didn't have the permission to delete the comment, the comment
* was left in the database as orphan.
*
* Having own function is better:
* 1) We don't check permissions, so we don't create orphans. Usually only
* moderators have the permission to delete other user's comments, so this
* is essential for any community site where users delete own comments.
* This also follows core comment behavior.
* 2) We warn other modules about the delete event, if nodeapi delete
* event is too late for them to react. They can also veto the deletion,
* provided they allow alternate action.
* 3) We don't print messages on screen.
*/
function _nodecomment_delete_comment($cid) {
// Clear the cache before the load, so if multiple nodes are deleted, the
// memory will not fill up with nodes (possibly) already removed.
// This also allows to find out easily if the node is already deleted.
$node = node_load($cid, NULL, TRUE);
// The node might not exist already for a number of reasons. This is
// generally ok.
if (!$node) {
return;
}
// Warn other modules about deletion and let them veto it.
$delete = TRUE;
$votes = nodecomment_invoke($node, 'delete_vote');
foreach ($votes as $vote) {
if ($vote === FALSE) {
$delete = FALSE;
break;
}
}
if ($delete) {
// Let the modules prepare for deleting.
nodecomment_invoke($node, 'delete');
db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
db_query('DELETE FROM {node_access} WHERE nid = %d', $node->nid);
// Call the node-specific callback (if any):
node_invoke($node, 'delete');
node_invoke_nodeapi($node, 'delete');
// Clear the page and block caches.
cache_clear_all();
// Remove this node from the search index if needed.
if (function_exists('search_wipe')) {
search_wipe($node->nid, 'node');
}
// Log the event, but don't print messages on screen.
watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title));
}
}
/**
* Updates the comment statistics for a given node. This should be called any
* time a comment is added, deleted, or updated.
*
* The following fields are contained in the node_comment_statistics table:
*
* - last_comment_timestamp: the timestamp of the last comment for this node
* or the node create stamp if no comments exist for the node.
*
* - last_comment_name: the name of the anonymous poster for the last comment.
*
* - last_comment_uid: the uid of the poster for the last comment for this node
* or the node authors uid if no comments exists for the node.
*
* - comment_count: the total number of approved/published comments on this node.
*/
function _nodecomment_update_node_statistics($nid) {
$count = db_result(db_query(
'SELECT COUNT(*) FROM {node_comments} nc
INNER JOIN {node} n ON n.nid = nc.cid
WHERE nc.nid = %d AND n.status = 1',
$nid
));
// Comments exist.
if ($count > 0) {
$last_reply = db_fetch_object(db_query_range(
'SELECT nc.cid, nc.name, n.created, n.changed, n.uid
FROM {node} n LEFT JOIN {node_comments} nc ON n.nid = nc.cid
WHERE nc.nid = %d AND n.status = 1
ORDER BY cid DESC',
$nid, 0, 1
));
$timestamp = max($last_reply->created, $last_reply->changed);
$name = $last_reply->uid ? '' : $last_reply->name;
db_query(
"UPDATE {node_comment_statistics}
SET comment_count = %d, last_comment_timestamp = %d, last_comment_name = '%s', last_comment_uid = %d
WHERE nid = %d",
$count, $timestamp, $name, $last_reply->uid, $nid
);
}
// No comments.
else {
// The node might not exist if called from hook_nodeapi($op = 'delete').
if ($node = db_fetch_object(db_query("SELECT uid, created FROM {node} WHERE nid = %d", $nid))) {
db_query(
"UPDATE {node_comment_statistics}
SET comment_count = 0, last_comment_timestamp = %d, last_comment_name = '', last_comment_uid = %d
WHERE nid = %d",
$node->created, $node->uid, $nid
);
}
else {
db_query("DELETE FROM {node_comment_statistics} WHERE nid = %d", $nid);
}
}
}
function nodecomment_form($node) {
$comment_type = nodecomment_get_comment_type($node->type);
if ($comment_type) {
global $user;
$new_node = array(
'uid' => $user->uid,
'name' => $user->name,
'type' => $comment_type,
'comment_target_nid' => $node->nid,
'comment_target_cid' => 0,
);
module_load_include('inc', 'node', 'node.pages');
return drupal_get_form($comment_type .'_node_form', $new_node);
}
}
/**
* Menu callback; view a single node.
*/
function nodecomment_node_view($node, $cid = NULL) {
drupal_set_title(check_plain($node->title));
$output = node_view($node, FALSE, TRUE);
if (nodecomment_get_commentable($node)) {
if ($node->comment_type) {
$output .= nodecomment_render($node, $cid);
}
else {
$output .= comment_render($node, $cid);
}
}
// Update the history table, stating that this user viewed this node.
node_tag_new($node->nid);
return $output;
}
/**
* Node comment's version of comment_render, to render all comments on a node.
*/
function nodecomment_render($node, $cid = 0) {
global $user;
$output = '';
if (user_access('access comments')) {
// Pre-process variables.
$nid = $node->nid;
if (empty($nid)) {
$nid = 0;
}
// Render nothing if there are no comments to render.
if (!empty($node->comment_count)) {
if ($cid && is_numeric($cid)) {
// Single comment view.
if ($comment = node_load($cid)) {
$output = theme('node', $comment, TRUE, TRUE);
}
}
else {
$view_name = variable_get('node_comment_view_'. $node->type, 'nodecomments');
if ($view_name) {
$output = views_embed_view($view_name, 'nodecomment_comments_1', $nid);
}
}
}
// If enabled, show new comment form.
$comment_type = nodecomment_get_comment_type($node->type);
if (user_access("create $comment_type content") && nodecomment_is_readwrite($node) && (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_BELOW)) {
// There is likely a cleaner way to do this, but for now it will have to do. -- JE
$friendly_name = node_get_types('name', $comment_type);
$output .= nodecomment_form_box($node, t('Post new !type', array('!type' => $friendly_name)));
}
if ($output) {
$output = theme('comment_wrapper', $output, $node);
}
}
return $output;
}
function nodecomment_form_box($node, $title = NULL) {
return theme('box', $title, nodecomment_form($node));
}
/**
* Public API function to retrieve the comment type for a node type.
*
* @param $node_type
* The name of the node type for which the comment type will be retreived.
* @return
* Returns a string containing the node type which will be used for comments.
* If node comments are not used for the passed in type, returns empty string.
*/
function nodecomment_get_comment_type($node_type) {
return variable_get('node_comment_type_'. $node_type, '');
}
/**
* Return array of content types which serve as nodecomments.
*/
function nodecomment_get_comment_types() {
$comment_types = array();
foreach (node_get_types('names') as $type => $blank) {
$comment_type = nodecomment_get_comment_type($type);
if ($comment_type) {
$comment_types[$comment_type] = $comment_type;
}
}
return $comment_types;
}
/**
* Redirect to target node page containing a comment. Supports comment threads
* spanning multiple pages.
*
* @param $node
* node comment object
*/
function _nodecomment_target_node_redirect($node) {
$pagenum = nodecomment_page_count($node);
$query = NULL;
if ($pagenum) {
$query = array('page' => $pagenum);
}
$fragment = 'comment-' . $node->nid;
drupal_goto("node/" . $node->comment_target_nid, $query, $fragment);
}
/**
* Check if the content type should be treated as full featured content type,
* even if it works as comment too.
*/
function nodecomment_is_content($type) {
// Types which are not comments are always treated as content.
if (!in_array($type, nodecomment_get_comment_types())) {
return TRUE;
}
return variable_get('node_comment_is_content_' . $type, FALSE);
}
/**
* Check if the node is commentable (read/write).
*/
function nodecomment_is_readwrite(&$node) {
$commentable = nodecomment_get_commentable($node);
return ($commentable == COMMENT_NODE_READ_WRITE);
}
/**
* Get "commentable" setting.
*/
function nodecomment_get_commentable(&$node) {
static $settings = array();
if (!isset($settings[$node->nid])) {
$commentable = isset($node->node_comment) ? $node->node_comment : $node->comment;
// Let modules override commentability dynamically.
drupal_alter('nodecomment_commentable', $commentable, $node);
$settings[$node->nid] = $commentable;
}
return $settings[$node->nid];
}
/**
* Return list of Node Comments module variables.
*
* @param $type
* content type
*/
function _nodecomment_vars() {
$vars = _nodecomment_type_vars();
$vars[] = 'node_comment_node_redirect';
return $vars;
}
/**
* Return list of Node Comments module variables, associated with a content type.
*
* @param $type
* Content type. If not provided, return variables for all content types.
*/
function _nodecomment_type_vars($type) {
if ($type) {
$types = array($type);
}
else {
$types = array_keys(node_get_types('names'));
}
$vars = array();
foreach ($types as $type) {
$vars[] = 'node_comment_type_' . $type;
$vars[] = 'node_comment_view_' . $type;
$vars[] = 'node_comment_plural_' . $type;
$vars[] = 'node_comment_is_content_' . $type;
}
return $vars;
}
function nodecomment_invoke($node, $op) {
$results = array();
$hook = 'nodecomment_' . $op;
foreach (module_implements($hook) as $module) {
$function = $module . '_' . $hook;
$results[] = $function($node);
}
return $results;
}
function nodecomment_include($inc) {
module_load_include('inc', 'nodecomment', "includes/nodecomment.$inc");
}