drupal/modules/tracker/tracker.module

359 lines
11 KiB
Plaintext

<?php
// $Id$
/**
* @file
* Enables tracking of recent posts for users.
*/
/**
* Implement hook_help().
*/
function tracker_help($path, $arg) {
switch ($path) {
case 'admin/help#tracker':
$output = '<p>' . t('The tracker module displays the most recently added or updated content on your site, and provides user-level tracking to follow the contributions of particular authors.') . '</p>';
$output .= '<p>' . t("The <em>Recent posts</em> page is available via a link in the navigation menu block and displays new and recently-updated content (including the content type, the title, the author's name, number of comments, and time of last update) in reverse chronological order. Posts are marked updated when changes occur in the text, or when new comments are added. To use the tracker module to follow a specific user's contributions, select the <em>Track</em> tab from the user's profile page.") . '</p>';
$output .= '<p>' . t('For more information, see the online handbook entry for <a href="@tracker">Tracker module</a>.', array('@tracker' => 'http://drupal.org/handbook/modules/tracker/')) . '</p>';
return $output;
}
}
/**
* Implement hook_menu().
*/
function tracker_menu() {
$items['tracker'] = array(
'title' => 'Recent posts',
'page callback' => 'tracker_page',
'access arguments' => array('access content'),
'weight' => 1,
'file' => 'tracker.pages.inc',
);
$items['tracker/all'] = array(
'title' => 'All recent posts',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['tracker/%user_uid_optional'] = array(
'title' => 'My recent posts',
'page callback' => 'tracker_page',
'access callback' => '_tracker_myrecent_access',
'access arguments' => array(1),
'page arguments' => array(1),
'type' => MENU_LOCAL_TASK,
'file' => 'tracker.pages.inc',
);
$items['user/%user/track'] = array(
'title' => 'Track',
'page callback' => 'tracker_page',
'page arguments' => array(1, TRUE),
'access callback' => '_tracker_user_access',
'access arguments' => array(1),
'type' => MENU_LOCAL_TASK,
'file' => 'tracker.pages.inc',
);
$items['user/%user/track/posts'] = array(
'title' => 'Track posts',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
return $items;
}
/**
* Implement hook_cron().
*/
function tracker_cron() {
$max_nid = variable_get('tracker_index_nid', 0);
$batch_size = variable_get('tracker_batch_size', 1000);
if ($max_nid > 0) {
$last_nid = FALSE;
$result = db_query_range('SELECT nid, uid, status FROM {node} WHERE nid <= :max_nid ORDER BY nid DESC', 0, $batch_size, array(':max_nid' => $max_nid), array('target' => 'slave'));
$count = 0;
foreach ($result as $row) {
// Calculate the changed timestamp for this node.
$changed = _tracker_calculate_changed($row->nid);
// Remove existing data for this node.
db_delete('tracker_node')
->condition('nid', $row->nid)
->execute();
db_delete('tracker_user')
->condition('nid', $row->nid)
->execute();
// Insert the node-level data.
db_insert('tracker_node')
->fields(array(
'nid' => $row->nid,
'published' => $row->status,
'changed' => $changed,
))
->execute();
// Insert the user-level data for the node's author.
db_insert('tracker_user')
->fields(array(
'nid' => $row->nid,
'published' => $row->status,
'changed' => $changed,
'uid' => $row->uid,
))
->execute();
$query = db_select('comment', 'c', array('target' => 'slave'));
// Force PostgreSQL to do an implicit cast by adding 0.
$query->addExpression('0 + :changed', 'changed', array(':changed' => $changed));
$query->addField('c', 'status', 'published');
$query
->distinct()
->fields('c', array('uid', 'nid'))
->condition('c.nid', $row->nid)
->condition('c.uid', $row->uid, '<>')
->condition('c.status', COMMENT_PUBLISHED);
// Insert the user-level data for the commenters (except if a commenter
// is the node's author).
db_insert('tracker_user')
->from($query)
->execute();
// Note that we have indexed at least one node.
$last_nid = $row->nid;
$count++;
}
if ($last_nid !== FALSE) {
// Prepare a starting point for the next run.
variable_set('tracker_index_nid', $last_nid - 1);
watchdog('tracker', t('Indexed %count nodes for tracking.', array('%count' => $count)));
}
else {
// If all nodes have been indexed, set to zero to skip future cron runs.
variable_set('tracker_index_nid', 0);
}
}
}
/**
* Access callback for tracker/%user_uid_optional.
*/
function _tracker_myrecent_access($account) {
// This path is only allowed for authenticated users looking at their own posts.
return $account->uid && ($GLOBALS['user']->uid == $account->uid) && user_access('access content');
}
/**
* Access callback for user/%user/track.
*/
function _tracker_user_access($account) {
return user_view_access($account) && user_access('access content');
}
/**
* Implement hook_nodeapi_insert().
*/
function tracker_node_insert($node, $arg = 0) {
_tracker_add($node->nid, $node->uid, $node->changed);
}
/**
* Implement hook_nodeapi_update().
*/
function tracker_node_update($node, $arg = 0) {
_tracker_add($node->nid, $node->uid, $node->changed);
}
/**
* Implement hook_nodeapi_delete().
*/
function tracker_node_delete($node, $arg = 0) {
_tracker_remove($node->nid, $node->uid, $node->changed);
}
/**
* Implement hook_comment_update().
*
* Comment module doesn't call hook_comment_unpublish() when saving individual
* comments so we need to check for those here.
*/
function tracker_comment_update($comment) {
$comment = (array) $comment;
// comment_save() calls hook_comment_publish() for all published comments
// so we to handle all other values here.
if ($comment['status'] != COMMENT_PUBLISHED) {
_tracker_remove($comment['nid'], $comment['uid'], $comment['timestamp']);
}
}
/**
* Implement hook_comment_publish().
*
* This actually handles the insert and update of published nodes since
* comment_save() calls hook_comment_publish() for all published comments.
*/
function tracker_comment_publish($comment) {
_tracker_add($comment->nid, $comment->uid, $comment->changed);
}
/**
* Implement hook_comment_unpublish().
*/
function tracker_comment_unpublish($comment) {
_tracker_remove($comment->nid, $comment->uid, $comment->changed);
}
/**
* Implement hook_comment_delete().
*/
function tracker_comment_delete($comment) {
_tracker_remove($comment->nid, $comment->uid, $comment->changed);
}
/**
* Update indexing tables when a node is added, updated or commented on.
*
* @param $nid
* A node ID.
* @param $uid
* The node or comment author.
* @param $changed
* The node updated timestamp or comment timestamp.
*/
function _tracker_add($nid, $uid, $changed) {
$node = db_query('SELECT nid, status, uid, changed FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject();
// Adding a comment can only increase the changed timestamp, so our
// calculation here is simple.
$changed = max($node->changed, $changed);
// Update the node-level data.
db_merge('tracker_node')
->key(array('nid' => $nid))
->fields(array(
'changed' => $changed,
'published' => $node->status,
))
->execute();
// Create or update the user-level data.
db_merge('tracker_user')
->key(array(
'nid' => $nid,
'uid' => $uid,
))
->fields(array(
'changed' => $changed,
'published' => $node->status,
))
->execute();
}
/**
* Determine the max timestamp between $node->changed and the last comment.
*
* @param $nid
* A node ID.
*
* @return
* The $node->changed timestamp, or most recent comment timestamp, whichever
* is the greatest.
*/
function _tracker_calculate_changed($nid) {
$changed = db_query('SELECT changed FROM {node} WHERE nid = :nid', array(':nid' => $nid), array('target' => 'slave'))->fetchField();
$latest_comment = db_query_range('SELECT cid, changed FROM {comment} WHERE nid = :nid AND status = :status ORDER BY changed DESC', 0, 1, array(
':nid' => $nid,
':status' => COMMENT_PUBLISHED,
), array('target' => 'slave'))->fetchObject();
if ($latest_comment && $latest_comment->changed > $changed) {
$changed = $latest_comment->changed;
}
return $changed;
}
/**
* Clean up indexed data when nodes or comments are removed.
*
* @param $nid
* The node ID.
* @param $uid
* The author of the node or comment.
* @param $changed
* The last changed timestamp of the node.
*/
function _tracker_remove($nid, $uid = NULL, $changed = NULL) {
$node = db_query('SELECT nid, status, uid, changed FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject();
// The user only keeps his or her subscription if both of the following are true:
// (1) The node exists.
// (2) The user is either the node author or has commented on the node.
$keep_subscription = FALSE;
if ($node) {
// Self-authorship is one reason to keep the user's subscription.
$keep_subscription = ($node->uid == $uid);
// Comments are a second reason to keep the user's subscription.
if (!$keep_subscription) {
// Check if the user has commented at least once on the given nid
$keep_subscription = db_query_range('SELECT COUNT(*) FROM {comment} WHERE nid = :nid AND uid = :uid AND status = 0', 0, 1, array(
':nid' => $nid,
':uid' => $uid,
))->fetchField();
}
// If we haven't found a reason to keep the user's subscription, delete it.
if (!$keep_subscription) {
db_delete('tracker_user')
->condition('nid', $nid)
->condition('uid', $uid)
->execute();
}
// Now we need to update the (possibly) changed timestamps for other users
// and the node itself.
// We only need to do this if the removed item has a timestamp that equals
// or exceeds the listed changed timestamp for the node
$tracker_node = db_query('SELECT nid, changed FROM {tracker_node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject();
if ($tracker_node && $changed >= $tracker_node->changed) {
// If we're here, the item being removed is *possibly* the item that
// established the node's changed timestamp.
// We just have to recalculate things from scratch.
$changed = _tracker_calculate_changed($nid);
// And then we push the out the new changed timestamp to our denormalized
// tables.
db_update('tracker_node')
->fields(array(
'changed' => $changed,
'published' => $node->status,
))
->condition('nid', $nid)
->execute();
db_update('tracker_node')
->fields(array(
'changed' => $changed,
'published' => $node->status,
))
->condition('nid', $nid)
->execute();
}
}
else {
// If the node doesn't exist, remove everything.
db_delete('tracker_node')
->condition('nid', $nid)
->execute();
db_delete('tracker_user')
->condition('nid', $nid)
->execute();
}
}