Newer
Older
Dries Buytaert
committed
* Tracks recent content posted by a user or users.
Dries Buytaert
committed
* Implements hook_help().
Gábor Hojtsy
committed
function tracker_help($path, $arg) {
switch ($path) {
Angie Byron
committed
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Tracker module displays the most recently added and updated content on your site, and allows you to follow new content created by each user. This module has no configuration options. For more information, see the online handbook entry for <a href="@tracker">Tracker module</a>.', array('@tracker' => 'http://drupal.org/documentation/modules/tracker')) . '</p>';
Angie Byron
committed
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Navigation') . '</dt>';
$output .= '<dd>' . t('The Tracker module adds a new menu item to the Navigation menu, called <em>Recent content</em>. You can configure menu items via the <a href="@menus">Menus administration page</a>.', array('@menus' => url('admin/structure/menu'))) . '</dd>';
$output .= '<dt>' . t('Tracking new and updated site content') . '</dt>';
$output .= '<dd>' . t("The <a href='@recent'>Recent content</a> page shows new and updated content in reverse chronological order, listing the content type, title, author's name, number of comments, and time of last update. Content is considered updated when changes occur in the text, or when new comments are added. The <em>My recent content</em> tab limits the list to the currently logged-in user.", array('@recent' => url('tracker'))) . '</dd>';
$output .= '<dt>' . t('Tracking user-specific content') . '</dt>';
$output .= '<dd>' . t("To follow a specific user's new and updated content, select the <em>Track</em> tab from the user's profile page.") . '</dd>';
$output .= '</dl>';
Dries Buytaert
committed
return $output;
Dries Buytaert
committed
* Implements hook_menu().
Dries Buytaert
committed
function tracker_menu() {
$items['tracker'] = array(
'title' => 'Recent content',
Dries Buytaert
committed
'page callback' => 'tracker_page',
'access arguments' => array('access content'),
'weight' => 1,
'file' => 'tracker.pages.inc',
Dries Buytaert
committed
);
$items['tracker/all'] = array(
'title' => 'All recent content',
Dries Buytaert
committed
'type' => MENU_DEFAULT_LOCAL_TASK,
);
Dries Buytaert
committed
$items['tracker/%user_uid_optional'] = array(
'title' => 'My recent content',
Dries Buytaert
committed
'page callback' => 'tracker_page',
Dries Buytaert
committed
'access callback' => '_tracker_myrecent_access',
'access arguments' => array(1),
Gábor Hojtsy
committed
'page arguments' => array(1),
'type' => MENU_LOCAL_TASK,
Dries Buytaert
committed
'file' => 'tracker.pages.inc',
Dries Buytaert
committed
);
$items['user/%user/track'] = array(
'title' => 'Track',
Gábor Hojtsy
committed
'page callback' => 'tracker_page',
'page arguments' => array(1, TRUE),
Dries Buytaert
committed
'access callback' => '_tracker_user_access',
'access arguments' => array(1),
Dries Buytaert
committed
'type' => MENU_LOCAL_TASK,
'file' => 'tracker.pages.inc',
Dries Buytaert
committed
);
$items['user/%user/track/content'] = array(
'title' => 'Track content',
Dries Buytaert
committed
'type' => MENU_DEFAULT_LOCAL_TASK,
);
Dries Buytaert
committed
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_cron().
Dries Buytaert
committed
*
* Updates tracking information for any items still to be tracked. The variable
* 'tracker_index_nid' is set to ((the last node ID that was indexed) - 1) and
* used to select the nodes to be processed. If there are no remaining nodes to
* process, 'tracker_index_nid' will be 0.
Dries Buytaert
committed
*/
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'));
Dries Buytaert
committed
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
$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'));
Dries Buytaert
committed
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// 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);
Dries Buytaert
committed
watchdog('tracker', 'Indexed %count content items for tracking.', array('%count' => $count));
Dries Buytaert
committed
}
else {
// If all nodes have been indexed, set to zero to skip future cron runs.
variable_set('tracker_index_nid', 0);
}
}
}
/**
Dries Buytaert
committed
* Access callback: Determines access permission for a user's own account.
*
* @param int $account
* The account ID to check.
*
* @return boolean
* TRUE if a user is accessing tracking info for their own account and
* has permission to access the content.
*
* @see tracker_menu()
Dries Buytaert
committed
*/
function _tracker_myrecent_access($account) {
// This path is only allowed for authenticated users looking at their own content.
Dries Buytaert
committed
return $account->uid && ($GLOBALS['user']->uid == $account->uid) && user_access('access content');
}
/**
Dries Buytaert
committed
* Access callback: Determines access permission for an account.
*
* @param int $account
* The user account ID to track.
*
* @return boolean
* TRUE if a user has permission to access the account for $account and
* has permission to access the content.
*
* @see tracker_menu()
Dries Buytaert
committed
*/
function _tracker_user_access($account) {
return user_view_access($account) && user_access('access content');
}
Dries Buytaert
committed
/**
* Implements hook_node_insert().
Dries Buytaert
committed
*
* Adds new tracking information for this node since it's new.
Dries Buytaert
committed
*/
function tracker_node_insert($node, $arg = 0) {
Dries Buytaert
committed
_tracker_add($node->nid, $node->uid, $node->changed);
}
/**
* Implements hook_node_update().
Dries Buytaert
committed
*
* Adds tracking information for this node since it's been updated.
Dries Buytaert
committed
*/
function tracker_node_update($node, $arg = 0) {
Dries Buytaert
committed
_tracker_add($node->nid, $node->uid, $node->changed);
}
/**
catch
committed
* Implements hook_node_predelete().
Dries Buytaert
committed
*
* Deletes tracking information for a node.
Dries Buytaert
committed
*/
catch
committed
function tracker_node_predelete($node, $arg = 0) {
Angie Byron
committed
db_delete('tracker_node')
->condition('nid', $node->nid)
->execute();
db_delete('tracker_user')
->condition('nid', $node->nid)
->execute();
Dries Buytaert
committed
}
/**
Dries Buytaert
committed
* Implements hook_comment_update().
Dries Buytaert
committed
*
* 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_save() calls hook_comment_publish() for all published comments
Dries Buytaert
committed
// so we need to handle all other values here.
if ($comment->status != COMMENT_PUBLISHED) {
_tracker_remove($comment->nid, $comment->uid, $comment->changed);
Dries Buytaert
committed
}
}
/**
Dries Buytaert
committed
* Implements hook_comment_publish().
Dries Buytaert
committed
*
* 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) {
Dries Buytaert
committed
_tracker_add($comment->nid, $comment->uid, $comment->changed);
Dries Buytaert
committed
}
/**
Dries Buytaert
committed
* Implements hook_comment_unpublish().
Dries Buytaert
committed
*/
function tracker_comment_unpublish($comment) {
Dries Buytaert
committed
_tracker_remove($comment->nid, $comment->uid, $comment->changed);
Dries Buytaert
committed
}
/**
Dries Buytaert
committed
* Implements hook_comment_delete().
Dries Buytaert
committed
*/
function tracker_comment_delete($comment) {
Dries Buytaert
committed
_tracker_remove($comment->nid, $comment->uid, $comment->changed);
Dries Buytaert
committed
}
/**
Dries Buytaert
committed
* Updates indexing tables when a node is added, updated, or commented on.
Dries Buytaert
committed
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
*
* @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();
}
/**
Dries Buytaert
committed
* Determines the max timestamp between $node->changed and the last comment.
Dries Buytaert
committed
*
* @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();
Dries Buytaert
committed
$latest_comment = db_query_range('SELECT cid, changed FROM {comment} WHERE nid = :nid AND status = :status ORDER BY changed DESC', 0, 1, array(
Dries Buytaert
committed
':nid' => $nid,
':status' => COMMENT_PUBLISHED,
), array('target' => 'slave'))->fetchObject();
Dries Buytaert
committed
if ($latest_comment && $latest_comment->changed > $changed) {
$changed = $latest_comment->changed;
Dries Buytaert
committed
}
return $changed;
}
/**
Dries Buytaert
committed
* Cleans up indexed data when nodes or comments are removed.
Dries Buytaert
committed
*
* @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:
Dries Buytaert
committed
// (1) The node exists.
// (2) The user is either the node author or has commented on the node.
Dries Buytaert
committed
$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) {
Dries Buytaert
committed
// 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 = :status', 0, 1, array(
Dries Buytaert
committed
':nid' => $nid,
':uid' => $uid,
':status' => COMMENT_PUBLISHED,
Angie Byron
committed
))->fetchField();
Dries Buytaert
committed
}
// 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
Dries Buytaert
committed
// or exceeds the listed changed timestamp for the node.
Dries Buytaert
committed
$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();
Dries Buytaert
committed
}
Dries Buytaert
committed
}
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();
}
}