Index: modules/tracker/tracker.install
===================================================================
RCS file: modules/tracker/tracker.install
diff -N modules/tracker/tracker.install
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/tracker/tracker.install 3 Jun 2009 13:57:20 -0000
@@ -0,0 +1,193 @@
+ $max_nid)));
+ }
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function tracker_schema() {
+ $schema['tracker_node'] = array(
+ 'description' => 'Stores information about node.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The nid of the node.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'published' => array(
+ 'description' => 'Boolean indicating whether the node is published.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'changed' => array(
+ 'description' => 'The Unix timestamp when the node was most recently saved.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'indexes' => array(
+ 'tracker' => array('published', 'changed'),
+ ),
+ 'primary key' => array('nid'),
+ );
+
+ $schema['tracker_user'] = array(
+ 'description' => 'Stores information about node per user.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The id of the node.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'uid' => array(
+ 'description' => 'The id of the user.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'published' => array(
+ 'description' => 'Boolean indicating whether the node is published.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'changed' => array(
+ 'description' => 'The Unix timestamp when the node was most recently saved.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'indexes' => array(
+ 'tracker' => array('uid', 'published', 'changed'),
+ ),
+ 'primary key' => array('nid', 'uid'),
+ );
+
+ return $schema;
+}
+
+/**
+ * @defgroup updates-6.x-to-7.x System updates from 6.x to 7.x
+ * @{
+ */
+
+/**
+ * Create new tables.
+ */
+function tracker_update_7000() {
+ $schema['tracker_node'] = array(
+ 'description' => 'Stores information about node.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The nid of the node.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'published' => array(
+ 'description' => 'Boolean indicating whether the node is published.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'changed' => array(
+ 'description' => 'The Unix timestamp when the node was most recently saved.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'indexes' => array(
+ 'tracker' => array('published', 'changed'),
+ ),
+ 'primary key' => array('nid'),
+ );
+
+ $schema['tracker_user'] = array(
+ 'description' => 'Stores information about node per user.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The id of the node.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'uid' => array(
+ 'description' => 'The id of the user.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'published' => array(
+ 'description' => 'Boolean indicating whether the node is published.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'changed' => array(
+ 'description' => 'The Unix timestamp when the node was most recently saved.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'indexes' => array(
+ 'tracker' => array('uid', 'published', 'changed'),
+ ),
+ 'primary key' => array('nid', 'uid'),
+ );
+
+ $ret = array();
+ foreach ($schema as $name => $table) {
+ db_create_table($ret, $name, $table);
+ }
+ return $ret;
+}
\ No newline at end of file
Index: modules/tracker/tracker.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/tracker/tracker.module,v
retrieving revision 1.158
diff -u -p -r1.158 tracker.module
--- modules/tracker/tracker.module 27 May 2009 18:34:01 -0000 1.158
+++ modules/tracker/tracker.module 3 Jun 2009 13:57:21 -0000
@@ -31,10 +31,13 @@ function tracker_menu() {
);
$items['tracker/all'] = array(
'title' => 'All recent posts',
+ 'page callback' => 'tracker_page',
+ 'access arguments' => array('access content'),
'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),
@@ -53,10 +56,110 @@ function tracker_menu() {
'title' => 'Track posts',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
+
+ $items['admin/settings/tracker'] = array(
+ 'title' => 'Tracker',
+ 'description' => 'Settings for the tracker module',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('tracker_admin_settings'),
+ 'access arguments' => array('administer tracker')
+ );
+
return $items;
}
/**
+ * Implementation of hook_perm().
+ */
+function tracker_perm() {
+ return array(
+ 'administer tracker' => array(
+ 'title' => t('Administer tracker'),
+ 'description' => t('Manage tracker administration settings.'),
+ ),
+ );
+}
+
+/**
+ * Implementation of 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', array(':max_nid' => $max_nid), 0, $batch_size);
+
+ $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,
+ ))
+ ->execute();
+
+ $uids = db_query('SELECT DISTINCT uid FROM {comment} c WHERE c.nid = :nid AND uid <> :uid AND status = :status', array(
+ ':nid' => $row->nid,
+ ':status' => COMMENT_PUBLISHED,
+ ':uid' => $row->uid,
+ ))->fetchCol();
+
+ // Insert the user-level data for the commenters (except if a commenter is the node's author).
+ $query = db_insert('tracker_user')->fields(array('nid', 'published', 'uid', 'changed'));
+ foreach ($uids as $uid) {
+ $query->values(array(
+ 'nid' => $row->nid,
+ 'published' => $row->status,
+ 'uid' => $uid,
+ 'changed' => $changed,
+ ));
+ }
+ $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) {
@@ -71,3 +174,252 @@ function _tracker_user_access($account)
return user_view_access($account) && user_access('access content');
}
+/**
+ * Menu callback argument. Prints a listing of active nodes on the site.
+ */
+function tracker_admin_settings() {
+ $form = array();
+
+ $max_nid = variable_get('tracker_index_nid', 0);
+
+ if ($max_nid) {
+ $form['max_nid'] = array(
+ '#markup' => t('Max node ID for indexing on the next cron run: @max', array('@max' => $max_nid)),
+ );
+ }
+ else {
+ $form['max_nid'] = array(
+ '#markup' => t('Existing nodes have finished tracker indexing.'),
+ );
+ }
+
+ $form['tracker_batch_size'] = array(
+ '#title' => t('Batch size'),
+ '#description' => t('Number of nodes to index during each cron run.'),
+ '#type' => 'textfield',
+ '#size' => 6,
+ '#maxlength' => 7,
+ '#default_value' => variable_get('tracker_batch_size', 1000),
+ );
+
+ return system_settings_form($form);
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function tracker_form_alter(&$form, $form_state, $form_id) {
+ if ($form_id == 'node_admin_nodes') {
+ $form['#submit']['tracker2_batch_node_alter'] = array();
+ }
+}
+
+function tracker_batch_node_alter($form, &$form_state) {
+ $op = $form_state['values']['operation'];
+ foreach($form_state['values']['nodes'] as $nid => $selected) {
+ if ($selected) {
+ if ($op == 'publish') {
+ db_update('tracker_node')
+ ->fields(array('published' => 1))
+ ->condition('nid', $nid)
+ ->execute();
+ db_update('tracker_user')
+ ->fields(array('published' => 1))
+ ->condition('nid', $nid)
+ ->execute();
+ }
+ else if ($op == 'unpublish') {
+ db_update('tracker_node')
+ ->fields(array('published' => 0))
+ ->condition('nid', $nid)
+ ->execute();
+ db_update('tracker_user')
+ ->fields(array('published' => 0))
+ ->condition('nid', $nid)
+ ->execute();
+ }
+ else if ($op == 'delete') {
+ _tracker_remove($nid);
+ }
+ }
+ }
+}
+
+/**
+ * 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_insert().
+ */
+function tracker_comment_insert($comment) {
+ $comment = (array) $comment;
+ if ($comment['status'] == COMMENT_PUBLISHED) {
+ _tracker_add($comment['nid'], $comment['uid'], $comment['timestamp']);
+ }
+ else {
+ _tracker_remove($comment['nid'], $comment['uid'], $comment['timestamp']);
+ }
+}
+
+/**
+ * Implement hook_comment_update().
+ */
+function tracker_comment_update($comment) {
+ $comment = (array) $comment;
+ if ($comment['status'] == COMMENT_PUBLISHED) {
+ _tracker_add($comment['nid'], $comment['uid'], $comment['timestamp']);
+ }
+ else {
+ _tracker_remove($comment['nid'], $comment['uid'], $comment['timestamp']);
+ }
+}
+
+/**
+ * Implement hook_comment_publish().
+ */
+function tracker_comment_publish($comment) {
+ $comment = (array) $comment;
+ _tracker_add($comment['nid'], $comment['uid'], $comment['timestamp']);
+}
+
+/**
+ * Implement hook_comment_unpublish().
+ */
+function tracker_comment_unpublish($comment) {
+ $comment = (array) $comment;
+ _tracker_remove($comment['nid'], $comment['uid'], $comment['timestamp']);
+}
+
+/**
+ * Implement hook_comment_delete().
+ */
+function tracker_comment_delete($comment) {
+ $comment = (array) $comment;
+ _tracker_delete($comment['nid'], $comment['uid'], $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 easy.
+ $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();
+}
+
+function _tracker_calculate_changed($nid) {
+ $changed = db_query('SELECT changed FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchField();
+ $latest_comment = db_query_range('SELECT cid, timestamp FROM {comment} WHERE nid = :nid AND status = :status ORDER BY timestamp DESC', array(
+ ':nid' => $nid,
+ ':status' => COMMENT_PUBLISHED,
+ ), 0, 1)->fetchObject();
+ if ($latest_comment && $latest_comment->timestamp > $changed) {
+ $changed = $latest_comment->timestamp;
+ }
+ return $changed;
+}
+
+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 {comments} WHERE nid = :nid AND uid = :uid AND status = 0', array(
+ ':nid' => $nid,
+ ':uid' => $uid,
+ ), 0, 1)->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();
+ }
+}
Index: modules/tracker/tracker.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/tracker/tracker.pages.inc,v
retrieving revision 1.20
diff -u -p -r1.20 tracker.pages.inc
--- modules/tracker/tracker.pages.inc 29 May 2009 18:51:46 -0000 1.20
+++ modules/tracker/tracker.pages.inc 3 Jun 2009 13:57:21 -0000
@@ -16,60 +16,71 @@ function tracker_page($account = NULL, $
$header = array(t('Type'), t('Post'), t('Author'), t('Replies'), t('Last updated'));
- // TODO: These queries are very expensive, see http://drupal.org/node/105639
- $query = db_select('node', 'n', array('target' => 'slave'))->extend('PagerDefault');
- $query->join('users', 'u', 'n.uid = u.uid');
- $query->join('node_comment_statistics', 'l', 'n.nid = l.nid');
- $query->addExpression('GREATEST(n.changed, l.last_comment_timestamp)', 'last_updated');
- $query
- ->distinct()
- ->fields('n', array('nid', 'title', 'type', 'changed', 'uid'))
- ->fields('u', array('name'))
- ->fields('l', array('comment_count'))
- ->condition('n.status', 1)
- ->orderBy('last_updated', 'DESC')
- ->addTag('node_access')
- ->limit(25);
-
if ($account) {
+ $query = db_select('tracker_user', 't')->extend('PagerDefault');
+ $query->condition('t.uid', $account->uid);
+
if ($set_title) {
// When viewed from user/%user/track, display the name of the user
// as page title -- the tab title remains Track so this needs to be done
// here and not in the menu definition.
drupal_set_title($account->name);
}
- $query->leftJoin('comment', 'c', 'n.nid = c.nid AND (c.status = :status OR c.status IS NULL)', array(':status' => COMMENT_PUBLISHED));
- $query->condition(db_or()
- ->condition('n.uid', $account->uid)
- ->condition('c.uid', $account->uid)
- );
+ } else {
+ $query = db_select('tracker_node', 't')->extend('PagerDefault');
+
+ $count_query = db_select('node', 'n');
+ $count_query->addExpression('COUNT(n.nid)');
+ $count_query
+ ->condition('n.status', 1)
+ ->addTag('node_access');
+
+ $query->setCountQuery($count_query);
}
- $result = $query->execute();
+ // This array acts as a placeholder for the data selected later
+ // while keeping the correct order.
+ $nodes = $query
+ ->addTag('node_access')
+ ->fields('t', array('nid', 'changed'))
+ ->condition('t.published', 1)
+ ->orderBy('t.changed', 'DESC')
+ ->limit(25)
+ ->execute()
+ ->fetchAllAssoc('nid');
+
+ if (!empty($nodes)) {
+ // Now, get the data and put into the placeholder array
+ $result = db_query("SELECT n.nid, n.title, n.type, n.changed, n.uid, u.name, l.comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {users} u ON n.uid = u.uid WHERE n.nid IN (:nids)", array(':nids' => array_keys($nodes)));
+ foreach ($result as $node) {
+ $node->last_activity = $nodes[$node->nid]->changed;
+ $nodes[$node->nid] = $node;
+ }
- $rows = array();
- foreach ($result as $node) {
- // Determine the number of comments:
- $comments = 0;
- if ($node->comment_count) {
- $comments = $node->comment_count;
-
- if ($new = comment_num_new($node->nid)) {
- $comments .= '
';
- $comments .= l(format_plural($new, '1 new', '@count new'), "node/$node->nid", array('query' => comment_new_page_count($node->comment_count, $new, $node), 'fragment' => 'new'));
+ // Finally display the data
+ $rows = array();
+ foreach ($nodes as $node) {
+ // Determine the number of comments:
+ $comments = 0;
+ if ($node->comment_count) {
+ $comments = $node->comment_count;
+
+ if ($new = comment_num_new($node->nid)) {
+ $comments .= '
';
+ $comments .= l(format_plural($new, '1 new', '@count new'), 'node/'. $node->nid, array('fragment' => 'new'));
+ }
}
- }
- $rows[] = array(
- check_plain(node_get_types('name', $node->type)),
- l($node->title, "node/$node->nid") . ' ' . theme('mark', node_mark($node->nid, $node->changed)),
- theme('username', $node),
- array('class' => 'replies', 'data' => $comments),
- t('!time ago', array('!time' => format_interval(REQUEST_TIME - $node->last_updated)))
- );
+ $rows[] = array(
+ check_plain(node_get_types('name', $node->type)),
+ l($node->title, "node/$node->nid") .' '. theme('mark', node_mark($node->nid, $node->changed)),
+ theme('username', $node),
+ array('class' => 'replies', 'data' => $comments),
+ t('!time ago', array('!time' => format_interval(time() - $node->last_activity)))
+ );
+ }
}
-
- if (!$rows) {
+ else {
$rows[] = array(array('data' => t('No posts available.'), 'colspan' => '5'));
}