? .cache ? .project ? .settings ? tracker2.patch ? sites/sub.drupal7 ? sites/all/modules ? sites/default/files ? sites/default/settings.php 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 18 Nov 2008 17:03:03 -0000 @@ -0,0 +1,106 @@ + $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; +} Index: modules/tracker/tracker.module =================================================================== RCS file: /cvs/drupal/drupal/modules/tracker/tracker.module,v retrieving revision 1.157 diff -u -p -r1.157 tracker.module --- modules/tracker/tracker.module 6 May 2008 12:18:51 -0000 1.157 +++ modules/tracker/tracker.module 18 Nov 2008 17:03:03 -0000 @@ -43,7 +43,7 @@ function tracker_menu() { $items['user/%user/track'] = array( 'title' => 'Track', - 'page callback' => 'tracker_page', + 'page callback' => 'tracker_track_user', 'page arguments' => array(1, TRUE), 'access callback' => '_tracker_user_access', 'access arguments' => array(1), @@ -53,10 +53,79 @@ 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; + $res = db_query_range('SELECT nid, uid, status FROM {node} WHERE nid <= %d ORDER BY nid DESC', $max_nid, 0, $batch_size); + + $count = 0; + + while ($row = db_fetch_object($res)) { + // Calculate the changed timestamp for this node. + $changed = _tracker_calculate_changed($row->nid); + + // Remove existing data for this node. + db_query('DELETE FROM {tracker_node} WHERE nid = %d', $row->nid); + db_query('DELETE FROM {tracker_user} WHERE nid = %d', $row->nid); + + // Insert the node-level data. + db_query('INSERT INTO {tracker_node} (nid, published, changed) VALUES (%d, %d, %d)', $row->nid, $row->status, $changed); + + // Insert the user-level data for the node's author. + db_query('INSERT INTO {tracker_user} (nid, published, uid, changed) VALUES (%d, %d, %d, %d)', $row->nid, $row->status, $row->uid, $changed); + + // Insert the user-level data for the commenters (except if a commenter is the node's author). + db_query('INSERT INTO {tracker_user} (nid, published, uid, changed) SELECT DISTINCT %d AS nid, %d AS published, uid, %d AS changed FROM {comments} WHERE nid = %d AND uid <> %d AND status = %d', $row->nid, $row->status, $changed, $row->nid, $row->uid, COMMENT_PUBLISHED); + + // 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 +140,178 @@ 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_query('UPDATE {tracker_node} SET published = 1 WHERE nid = %d', $nid); + db_query('UPDATE {tracker_user} SET published = 1 WHERE nid = %d', $nid); + } + else if ($op == 'unpublish') { + db_query('UPDATE {tracker_node} SET published = 0 WHERE nid = %d', $nid); + db_query('UPDATE {tracker_user} SET published = 0 WHERE nid = %d', $nid); + } + else if ($op == 'delete') { + _tracker_remove($nid); + } + } + } +} + +/** + * Implementation of hook_nodeapi_insert(). + */ +function tracker_nodeapi_insert(&$node, $arg = 0) { + _tracker_add($node->nid, $node->uid, $node->changed); +} + +/** + * Implementation of hook_nodeapi_update(). + */ +function tracker_nodeapi_update(&$node, $arg = 0) { + _tracker_add($node->nid, $node->uid, $node->changed); +} + +/** + * Implementation of hook_nodeapi_delete(). + */ +function tracker_nodeapi_delete(&$node, $arg = 0) { + _tracker_remove($node->nid, $node->uid, $node->changed); +} + +/** + * Implementation of hook_comment(). + */ +function tracker_comment(&$a1, $op) { + $comment = (array) $a1; + if ($op == 'insert' || $op == 'update' || $op == 'publish') { + if ($comment['status'] == COMMENT_PUBLISHED) { + _tracker_add($comment['nid'], $comment['uid'], $comment['timestamp']); + } + else { + _tracker_remove($comment['nid'], $comment['uid'], $comment['timestamp']); + } + } + else if ($op == 'delete' || $op == 'unpublish') { + _tracker_remove($comment['nid'], $comment['uid'], $comment['timestamp']); + } +} + +function _tracker_add($nid, $uid, $changed) { + $node = db_fetch_object(db_query('SELECT nid, status, uid, changed FROM {node} WHERE nid = %d', $nid)); + + // 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 + $exists = db_result(db_query('SELECT COUNT(*) FROM {tracker_node} WHERE nid = %d', $nid)); + if ($exists) { + db_query('UPDATE {tracker_node} SET changed = %d, published = %d WHERE nid = %d', $changed, $node->status, $nid); + } + else { + db_query('INSERT INTO {tracker_node} (changed, published, nid) VALUES (%d, %d, %d)', $changed, $node->status, $nid); + } + + // Create or update the user-level data + db_query('UPDATE {tracker_user} SET changed = %d, published = %d WHERE nid = %d', $changed, $node->status, $nid); + $exists = db_result(db_query('SELECT COUNT(*) FROM {tracker_user} WHERE nid = %d AND uid = %d', $nid, $uid)); + if (!$exists) { + db_query('INSERT INTO {tracker_user} (changed, published, nid, uid) VALUES (%d, %d, %d, %d)', $changed, $node->status, $nid, $uid); + } +} + +function _tracker_calculate_changed($nid) { + $changed = db_result(db_query('SELECT changed FROM {node} WHERE nid = %d', $nid)); + $latest_comment = db_fetch_object(db_query_range('SELECT cid, timestamp FROM {comments} WHERE nid = %d AND status = %d ORDER BY timestamp DESC', $nid, COMMENT_PUBLISHED, 0, 1)); + if ($latest_comment && $latest_comment->timestamp > $changed) { + $changed = $latest_comment->timestamp; + } + return $changed; +} + +function _tracker_remove($nid, $uid = NULL, $changed = NULL) { + $node = db_fetch_object(db_query('SELECT nid, status, uid, changed FROM {node} WHERE nid = %d', $nid)); + + // 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_result(db_query_range('SELECT COUNT(*) FROM {comments} WHERE nid = %d AND uid = %d AND status = 0', $nid, $uid, 0, 1)); + } + + // If we haven't found a reason to keep the user's subscription, delete it. + if (!$keep_subscription) { + db_query('DELETE FROM {tracker_user} WHERE nid = %d AND uid = %d', $nid, $uid); + } + + // 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_fetch_object(db_query('SELECT nid, changed FROM {tracker_node} WHERE nid = %d', $nid)); + 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_query('UPDATE {tracker_node} SET changed = %d, published = %d WHERE nid = %d', $changed, $node->status, $nid); + db_query('UPDATE {tracker_user} SET changed = %d, published = %d WHERE nid = %d', $changed, $node->status, $nid); + } + } + else { + // If the node doesn't exist, remove everything + db_query('DELETE FROM {tracker_node} WHERE nid = %d', $nid); + db_query('DELETE FROM {tracker_user} WHERE nid = %d', $nid); + } +} Index: modules/tracker/tracker.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/tracker/tracker.pages.inc,v retrieving revision 1.12 diff -u -p -r1.12 tracker.pages.inc --- modules/tracker/tracker.pages.inc 26 Oct 2008 18:06:39 -0000 1.12 +++ modules/tracker/tracker.pages.inc 18 Nov 2008 17:03:03 -0000 @@ -3,62 +3,70 @@ /** * @file - * User page callbacks for the tracker module. + * Page callbacks for the tracker module. */ - /** * Menu callback. Prints a listing of active nodes on the site. */ -function tracker_page($account = NULL, $set_title = FALSE) { - // Add CSS - drupal_add_css(drupal_get_path('module', 'tracker') . '/tracker.css', array('preprocess' => FALSE)); - - if ($account) { - 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 definiton. - drupal_set_title($account->name); - } - // TODO: These queries are very expensive, see http://drupal.org/node/105639 - $sql = 'SELECT DISTINCT(n.nid), n.title, n.type, n.changed, n.uid, u.name, GREATEST(n.changed, l.last_comment_timestamp) AS last_updated, 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 LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = %d OR c.status IS NULL) WHERE n.status = 1 AND (n.uid = %d OR c.uid = %d) ORDER BY last_updated DESC'; - $sql = db_rewrite_sql($sql); - $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = %d OR c.status IS NULL) WHERE n.status = 1 AND (n.uid = %d OR c.uid = %d)'; - $sql_count = db_rewrite_sql($sql_count); - $result = pager_query($sql, 25, 0, $sql_count, COMMENT_PUBLISHED, $account->uid, $account->uid); +function tracker_page($uid = 0) { + drupal_add_css(drupal_get_path('module', 'tracker') .'/tracker.css', array('type' => 'module', 'preprocess' => FALSE)); + + if ($uid) { + $sql = 'SELECT tu.nid, tu.changed AS last_activity FROM {tracker_user} tu WHERE tu.published = 1 AND tu.uid = %d ORDER BY tu.changed DESC'; + $sql = db_rewrite_sql($sql, 'tu'); + $sql_count = 'SELECT COUNT(tu.nid) FROM {tracker_user} tu WHERE tu.published = 1 AND tu.uid = %d'; + $sql_count = db_rewrite_sql($sql_count, 'tu'); + $result = pager_query($sql, 25, 0, $sql_count, $uid); } else { - $sql = 'SELECT DISTINCT(n.nid), n.title, n.type, n.changed, n.uid, u.name, GREATEST(n.changed, l.last_comment_timestamp) AS last_updated, l.comment_count FROM {node} n INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {node_comment_statistics} l ON n.nid = l.nid WHERE n.status = 1 ORDER BY last_updated DESC'; - $sql = db_rewrite_sql($sql); + $sql = 'SELECT tn.nid, tn.changed AS last_activity FROM {tracker_node} tn WHERE tn.published = 1 ORDER BY tn.changed DESC'; + $sql = db_rewrite_sql($sql, 'tn'); $sql_count = 'SELECT COUNT(n.nid) FROM {node} n WHERE n.status = 1'; $sql_count = db_rewrite_sql($sql_count); $result = pager_query($sql, 25, 0, $sql_count); } - $rows = array(); + // This array acts as a placeholder for the data selected later + // while keeping the correct order. + $nodes = array(); while ($node = db_fetch_object($result)) { - // 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')); + $nodes[$node->nid] = $node; + } + + if (!empty($nodes)) { + // Now, get the data and put into the placeholder array + $placeholders = implode(',', array_fill(0, count($nodes), '%d')); + $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 ($placeholders)", array_keys($nodes)); + while ($node = db_fetch_object($result)) { + $node->last_activity = $nodes[$node->nid]->last_activity; + $nodes[$node->nid] = $node; + } + + // 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, NULL, NULL, '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(time() - $node->last_activity))) + ); } - - $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))) - ); } - - if (!$rows) { + else { $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '5')); } @@ -71,3 +79,22 @@ function tracker_page($account = NULL, $ return $output; } + + +/** + * Menu callback. Prints a listing of active nodes on the site. + */ +function tracker_track_user() { + if ($account = user_load(array('uid' => arg(1)))) { + if ($account->status || user_access('administer users')) { + drupal_set_title(check_plain($account->name)); + return tracker_page($account->uid); + } + else { + drupal_access_denied(); + } + } + else { + drupal_not_found(); + } +}