Index: project.inc
===================================================================
--- project.inc	(revision 581)
+++ project.inc	(working copy)
@@ -337,18 +337,6 @@
     // Build a nested array of sections of links to display on project_project node pages.
     $all_links = array();
 
-    // Resources section
-    $all_links['resources'] = array(
-      'name' => t('Resources'),
-      'weight' => 4,
-    );
-    foreach (array('homepage' => t('Home page'), 'documentation' => t('Read documentation'), 'license' => t('Read license'), 'changelog' => t('Read complete log of changes'), 'demo' => t('Try out a demonstration'), 'screenshots' => t('Look at screenshots')) as $uri => $name) {
-      if (!empty($node->project[$uri])) {
-        $all_links['resources']['links'][$uri] = l($name, $node->project[$uri]);
-      }
-    }
-
-
     // Flags that indicate what kind of access to project issues to allow.
     $has_issues = module_exists('project_issue') && !empty($node->project_issue['issues']);
     $view_issues = $has_issues && (user_access('access project issues') || user_access('access own project issues') || user_access('administer projects'));
@@ -378,29 +366,6 @@
     }
     $all_links['support']['links'] = $links;
 
-
-    // Developer section
-    $all_links['development'] = array(
-      'name' => t('Development'),
-      'weight' => 8,
-    );
-    $links = array();
-    if ($view_issues) {
-      $links['pending_patches'] = l(t('View pending patches'), 'project/issues/'. $node->project['uri'], array('query' => 'states=8,13', 'absolute' => TRUE, 'html' => TRUE));
-      $links['available_tasks'] = l(t('View available tasks'), 'project/issues/'. $node->project['uri'], array('query' => 'categories=task'));
-      $links['pending_issues'] = l(t('View all pending issues'), 'project/issues/'. $node->project['uri']);
-    }
-
-    if ($node->project['cvs']) {
-      $links['browse_repository'] = l(t('Browse the CVS repository'), $node->project['cvs']);
-    }
-
-    if (project_use_cvs($node)) {
-      $links['view_cvs_messages'] = l(t('View CVS messages'), 'project/cvs/'. $node->nid);
-      $links['developers'] = l(t('Developers'), 'project/developers/'. $node->nid);
-    }
-    $all_links['development']['links'] = $links;
-
     // Allow other modules to add sections of links and/or links to the sections defined above.
     project_page_link_alter($node, $all_links);
 
Index: project.module
===================================================================
--- project.module	(revision 581)
+++ project.module	(working copy)
@@ -71,6 +71,18 @@
       'info' => t('Project navigation (text field)'),
       'cache' => BLOCK_CACHE_PER_ROLE,
     );
+    $blocks[3] = array(
+      'info' => t('Project links'),
+      'cache' => BLOCK_CACHE_PER_ROLE,
+    );
+    $blocks[4] = array(
+      'info' => t('Get involved'),
+      'cache' => BLOCK_CACHE_PER_ROLE,
+    );
+    $blocks[5] = array(
+      'info' => t('Project developers'),
+      'cache' => BLOCK_CACHE_PER_ROLE,
+    );
     return $blocks;
   }
   else if ($op == 'view') {
@@ -96,6 +108,24 @@
           'content' => drupal_get_form('project_quick_navigate_title_form'),
         );
         break;
+        
+      case 3:
+        $block = array(
+          'subject' => t('Links'),
+          'content' => project_links_block_output(),
+        );
+        break;
+        
+      case 4:
+        $block = array(
+          'subject' => t('Get involved'),
+          'content' => project_get_involved_block_output(),
+        );
+        break;
+        
+      case 5:
+        $block = project_developers_block();
+        break;
     }
     return $block;
   }
@@ -114,6 +144,89 @@
   }
 }
 
+/**
+ * Function that outputs the content of a project "Links" block.
+ */ 
+function project_links_block_output() {
+  if (arg(0) != 'node' && !is_numeric(arg(1))) {
+    return;
+  }
+  $node = node_load(arg(1));
+  $links = array();
+  foreach (array('homepage' => t('Home page'), 'documentation' => t('Read documentation'), 'license' => t('Read license'), 'changelog' => t('Read complete log of changes'), 'demo' => t('Try out a demonstration'), 'screenshots' => t('Look at screenshots')) as $uri => $name) {
+    if (!empty($node->project[$uri])) {
+      $links[$uri] = l($name, $node->project[$uri]);
+    }
+  }
+  if (count($links)) {
+    $output = theme('item_list', $links); 
+  }
+  return $output;
+}
+
+/**
+ * Function that outputs the content of a project "Get involved" block.
+ */ 
+function project_get_involved_block_output() {
+  if (arg(0) != 'node' && !is_numeric(arg(1))) {
+    return;
+  }
+  $node = node_load(arg(1));
+  
+  // Flags that indicate what kind of access to project issues to allow.
+  $has_issues = module_exists('project_issue') && !empty($node->project_issue['issues']);
+  $view_issues = $has_issues && (user_access('access project issues') || user_access('access own project issues') || user_access('administer projects'));
+  $make_issues = $has_issues && node_access('create', 'project_issue');
+  
+  $links = array();
+  if ($view_issues) {
+    $links['pending_patches'] = l(t('View pending patches'), 'project/issues/'. $node->project['uri'], array('query' => 'states=8,13', 'absolute' => TRUE, 'html' => TRUE));
+    $links['available_tasks'] = l(t('View available tasks'), 'project/issues/'. $node->project['uri'], array('query' => 'categories=task'));
+    $links['pending_issues'] = l(t('View all pending issues'), 'project/issues/'. $node->project['uri']);
+  }
+
+  if ($node->project['cvs']) {
+    $links['browse_repository'] = l(t('Browse the CVS repository'), $node->project['cvs']);
+  }
+
+  if (project_use_cvs($node)) {
+    $links['view_cvs_messages'] = l(t('View CVS messages'), 'project/cvs/'. $node->nid);
+    $links['developers'] = l(t('Developers'), 'project/developers/'. $node->nid);
+  }
+
+  if (count($links)) {
+    $output = '<p>'. t('Put your Drupal expertise to good use and get involved now:') .'</p>';
+    $output .= theme('item_list', $links);
+  }
+  return $output;
+}
+
+/**
+ * Function that generates the whole project "Developers" block (title + content).
+ *
+ */
+function project_developers_block() {
+  if (arg(0) != 'node' && !is_numeric(arg(1))) {
+    return;
+  }
+  $node = node_load(arg(1));
+
+  $contributors = module_invoke('cvs', 'get_project_contributors_list', $node->nid, ' ORDER BY last_commit DESC');
+
+  $items = array();
+  foreach ($contributors as $contributor) {
+    $items[] = theme('project_developer_item', $contributor); 
+  }
+  $content = theme('item_list', $items);
+
+  $block = array(
+    'subject' => t('Developers for @project_name', array('@project_name' => $node->title)),
+    'content' => $content,
+  );
+  
+  return $block;
+}
+
 function project_search_block_form($form_state, $node_type) {
   $form = search_box($form_state, 'project_search_block_form');
   $form['node_type'] = array(
@@ -857,7 +970,12 @@
       'arguments' => array(
         'term' => NULL,
       ),
-    ),
+    ),
+    'project_developer_item' => array(
+      'arguments' => array(
+        'user' => NULL,
+      )
+    )
   );
 }
 
@@ -1066,9 +1184,23 @@
     $output .= '<p>' . filter_xss($term->description) . '</p>';
   }
   return $output;
+}
+
+/**
+ * Theme a developer item to display in "Developers for project" block.
+ *
+ * @param $contributor
+ *   The contributor object, as returned by the block's SQL query.
+ * 
+ * @return
+ *   The HTML output.
+ */
+function theme_project_developer_item($contributor) {
+  $output = '<h4>'. theme('username', $contributor) .' - '. format_plural($contributor->commits, '1 commit', '@count commits') .'</h4>';
+  $output .= '<p class="date">'. t('last: !last_time ago, first: !first_time ago', array('!last_time' => format_interval(time() - $contributor->last_commit, 1), '!first_time' => format_interval(time() - $contributor->first_commit, 1))) .'</p>';
+  return $output;
 }
 
-
 /**
  * Find a project node ID (nid) based on project short name (uri).
  *
@@ -1103,3 +1235,4 @@
   return $uris[$nid];
 }
 
+
Index: project.inc
===================================================================
--- project.inc	(revision 581)
+++ project.inc	(working copy)
@@ -337,18 +337,6 @@
     // Build a nested array of sections of links to display on project_project node pages.
     $all_links = array();
 
-    // Resources section
-    $all_links['resources'] = array(
-      'name' => t('Resources'),
-      'weight' => 4,
-    );
-    foreach (array('homepage' => t('Home page'), 'documentation' => t('Read documentation'), 'license' => t('Read license'), 'changelog' => t('Read complete log of changes'), 'demo' => t('Try out a demonstration'), 'screenshots' => t('Look at screenshots')) as $uri => $name) {
-      if (!empty($node->project[$uri])) {
-        $all_links['resources']['links'][$uri] = l($name, $node->project[$uri]);
-      }
-    }
-
-
     // Flags that indicate what kind of access to project issues to allow.
     $has_issues = module_exists('project_issue') && !empty($node->project_issue['issues']);
     $view_issues = $has_issues && (user_access('access project issues') || user_access('access own project issues') || user_access('administer projects'));
@@ -378,29 +366,6 @@
     }
     $all_links['support']['links'] = $links;
 
-
-    // Developer section
-    $all_links['development'] = array(
-      'name' => t('Development'),
-      'weight' => 8,
-    );
-    $links = array();
-    if ($view_issues) {
-      $links['pending_patches'] = l(t('View pending patches'), 'project/issues/'. $node->project['uri'], array('query' => 'states=8,13', 'absolute' => TRUE, 'html' => TRUE));
-      $links['available_tasks'] = l(t('View available tasks'), 'project/issues/'. $node->project['uri'], array('query' => 'categories=task'));
-      $links['pending_issues'] = l(t('View all pending issues'), 'project/issues/'. $node->project['uri']);
-    }
-
-    if ($node->project['cvs']) {
-      $links['browse_repository'] = l(t('Browse the CVS repository'), $node->project['cvs']);
-    }
-
-    if (project_use_cvs($node)) {
-      $links['view_cvs_messages'] = l(t('View CVS messages'), 'project/cvs/'. $node->nid);
-      $links['developers'] = l(t('Developers'), 'project/developers/'. $node->nid);
-    }
-    $all_links['development']['links'] = $links;
-
     // Allow other modules to add sections of links and/or links to the sections defined above.
     project_page_link_alter($node, $all_links);
 
Index: project.module
===================================================================
--- project.module	(revision 581)
+++ project.module	(working copy)
@@ -71,6 +71,18 @@
       'info' => t('Project navigation (text field)'),
       'cache' => BLOCK_CACHE_PER_ROLE,
     );
+    $blocks[3] = array(
+      'info' => t('Project links'),
+      'cache' => BLOCK_CACHE_PER_ROLE,
+    );
+    $blocks[4] = array(
+      'info' => t('Get involved'),
+      'cache' => BLOCK_CACHE_PER_ROLE,
+    );
+    $blocks[5] = array(
+      'info' => t('Project developers'),
+      'cache' => BLOCK_CACHE_PER_ROLE,
+    );
     return $blocks;
   }
   else if ($op == 'view') {
@@ -96,24 +108,163 @@
           'content' => drupal_get_form('project_quick_navigate_title_form'),
         );
         break;
+        
+      case 3:
+        $block = array(
+          'subject' => t('Links'),
+          'content' => project_links_block_output(),
+        );
+        break;
+        
+      case 4:
+        $block = array(
+          'subject' => t('Get involved'),
+          'content' => project_get_involved_block_output(),
+        );
+        break;
+        
+      case 5:
+        $block = project_developers_block();
+        break;
     }
     return $block;
   }
-  elseif ($op == 'configure' && $delta == 1) {
-    $form = array();
-    $form['help_text'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Help text'),
-      '#description' => t('Enter optional help text to display in the block.'),
-      '#default_value' => variable_get('project_search_block_help_text', ''),
-    );
+  elseif ($op == 'configure') {
+    $form = array();
+    switch ($delta) {
+      case 1:
+        $form['help_text'] = array(
+          '#type' => 'textfield',
+          '#title' => t('Help text'),
+          '#description' => t('Enter optional help text to display in the block.'),
+          '#default_value' => variable_get('project_search_block_help_text', ''),
+        );
+        break;
+      case 5:
+        $form['developers_count'] = array(
+          '#type' => 'textfield',
+          '#title' => t('Number to display'),
+          '#description' => t('How many commiters to display in the block. (0 for no limit.)'),
+          '#default_value' => variable_get('project_developers_block_count', '0'),
+        );
+        $form['more_link'] = array(
+          '#type' => 'checkbox',
+          '#title' => t('Add a "More developers" link'),
+          '#default_value' => variable_get('project_developers_block_more_link', FALSE),
+        );
+        break;
+    }
     return $form;
   }
-  elseif ($op == 'save' && $delta == 1) {
-    variable_set('project_search_block_help_text', $edit['help_text']);
+  elseif ($op == 'save') {
+    switch ($delta) {
+      case 1:
+        variable_set('project_search_block_help_text', $edit['help_text']);
+        break;
+      case 5:
+        variable_set('project_developers_block_count', $edit['developers_count']);
+        variable_set('project_developers_block_more_link', $edit['more_link']);
+        break;
+    }
   }
 }
 
+/**
+ * Function that outputs the content of a project "Links" block.
+ */ 
+function project_links_block_output() {
+  if (arg(0) != 'node' && !is_numeric(arg(1))) {
+    return;
+  }
+  $node = node_load(arg(1));
+  $links = array();
+  foreach (array('homepage' => t('Home page'), 'documentation' => t('Read documentation'), 'license' => t('Read license'), 'changelog' => t('Read complete log of changes'), 'demo' => t('Try out a demonstration'), 'screenshots' => t('Look at screenshots')) as $uri => $name) {
+    if (!empty($node->project[$uri])) {
+      $links[$uri] = l($name, $node->project[$uri]);
+    }
+  }
+  if (count($links)) {
+    $output = theme('item_list', $links); 
+  }
+  return $output;
+}
+
+/**
+ * Function that outputs the content of a project "Get involved" block.
+ */ 
+function project_get_involved_block_output() {
+  if (arg(0) != 'node' && !is_numeric(arg(1))) {
+    return;
+  }
+  $node = node_load(arg(1));
+  
+  // Flags that indicate what kind of access to project issues to allow.
+  $has_issues = module_exists('project_issue') && !empty($node->project_issue['issues']);
+  $view_issues = $has_issues && (user_access('access project issues') || user_access('access own project issues') || user_access('administer projects'));
+  $make_issues = $has_issues && node_access('create', 'project_issue');
+  
+  $links = array();
+  if ($view_issues) {
+    $links['pending_patches'] = l(t('View pending patches'), 'project/issues/'. $node->project['uri'], array('query' => 'states=8,13', 'absolute' => TRUE, 'html' => TRUE));
+    $links['available_tasks'] = l(t('View available tasks'), 'project/issues/'. $node->project['uri'], array('query' => 'categories=task'));
+    $links['pending_issues'] = l(t('View all pending issues'), 'project/issues/'. $node->project['uri']);
+  }
+
+  if ($node->project['cvs']) {
+    $links['browse_repository'] = l(t('Browse the CVS repository'), $node->project['cvs']);
+  }
+
+  if (project_use_cvs($node)) {
+    $links['view_cvs_messages'] = l(t('View CVS messages'), 'project/cvs/'. $node->nid);
+    $links['developers'] = l(t('Developers'), 'project/developers/'. $node->nid);
+  }
+
+  if (count($links)) {
+    $output = '<p>'. t('Put your Drupal expertise to good use and get involved now:') .'</p>';
+    $output .= theme('item_list', $links);
+  }
+  return $output;
+}
+
+/**
+ * Function that generates the whole project "Developers" block (title + content).
+ *
+ */
+function project_developers_block() {
+  if (arg(0) != 'node' && !is_numeric(arg(1))) {
+    return;
+  }
+  $node = node_load(arg(1));
+
+  $contributors = module_invoke('cvs', 'get_project_contributors_list', $node->nid, ' ORDER BY last_commit DESC');
+
+  $items = array();
+  foreach ($contributors as $contributor) {
+    $items[] = theme('project_developer_item', $contributor); 
+  }
+  /*
+        variable_set('project_developers_block_count', $edit['developers_count']);
+        variable_set('project_developers_block_more_link', $edit['more_link']);
+   */
+  $max_length = variable_get('project_developers_block_count', 0);
+  if ($max_length) {
+    $items = array_slice($items, 0, $max_length);
+  }
+  
+  $content = theme('item_list', $items);
+  
+  if (variable_get('project_developers_block_more_link', 0)) {
+    $content .= l(t('More developers'), 'project/developers/'. $node->nid);
+  }
+
+  $block = array(
+    'subject' => t('Developers for @project_name', array('@project_name' => $node->title)),
+    'content' => $content,
+  );
+  
+  return $block;
+}
+
 function project_search_block_form($form_state, $node_type) {
   $form = search_box($form_state, 'project_search_block_form');
   $form['node_type'] = array(
@@ -857,7 +1008,12 @@
       'arguments' => array(
         'term' => NULL,
       ),
-    ),
+    ),
+    'project_developer_item' => array(
+      'arguments' => array(
+        'user' => NULL,
+      )
+    )
   );
 }
 
@@ -1066,9 +1222,23 @@
     $output .= '<p>' . filter_xss($term->description) . '</p>';
   }
   return $output;
+}
+
+/**
+ * Theme a developer item to display in "Developers for project" block.
+ *
+ * @param $contributor
+ *   The contributor object, as returned by the block's SQL query.
+ * 
+ * @return
+ *   The HTML output.
+ */
+function theme_project_developer_item($contributor) {
+  $output = '<h4>'. theme('username', $contributor) .' - '. format_plural($contributor->commits, '1 commit', '@count commits') .'</h4>';
+  $output .= '<p class="date">'. t('last: !last_time ago, first: !first_time ago', array('!last_time' => format_interval(time() - $contributor->last_commit, 1), '!first_time' => format_interval(time() - $contributor->first_commit, 1))) .'</p>';
+  return $output;
 }
 
-
 /**
  * Find a project node ID (nid) based on project short name (uri).
  *
@@ -1103,3 +1273,4 @@
   return $uris[$nid];
 }
 
+
