cvs diff: Diffing release
Index: release/project_release.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project/release/project_release.install,v
retrieving revision 1.15
diff -u -p -r1.15 project_release.install
--- release/project_release.install	22 Dec 2007 09:11:44 -0000	1.15
+++ release/project_release.install	8 Jan 2008 10:20:18 -0000
@@ -25,16 +25,18 @@ function project_release_install() {
       db_query("CREATE TABLE IF NOT EXISTS {project_release_projects} (
           nid int unsigned NOT NULL default '0',
           releases tinyint NOT NULL default '1',
-          snapshot_table tinyint unsigned NOT NULL default '1',
           version_format varchar(255) NOT NULL default '',
           PRIMARY KEY (nid),
           KEY project_release_projects_releases (releases)
         ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
-      db_query("CREATE TABLE IF NOT EXISTS {project_release_default_versions} (
+      db_query("CREATE TABLE IF NOT EXISTS {project_release_supported_versions} (
           nid int unsigned NOT NULL default '0',
           tid int unsigned NOT NULL default '0',
           major int unsigned NOT NULL default '0',
-          PRIMARY KEY (nid, tid)
+          supported tinyint unsigned NOT NULL default '1',
+          recommended tinyint unsigned NOT NULL default '0',
+          snapshot tinyint unsigned NOT NULL default '0',
+          PRIMARY KEY (nid, tid, major)
         ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
       db_query("CREATE TABLE IF NOT EXISTS {project_release_package_errors} (
           nid int unsigned NOT NULL default '0',
@@ -75,18 +77,20 @@ function project_release_install() {
         db_query("CREATE TABLE {project_release_projects} (
             nid int NOT NULL default '0',
             releases smallint NOT NULL default '1',
-            snapshot_table smallint NOT NULL default '1',
             version_format varchar(255) NOT NULL default '',
             PRIMARY KEY (nid)
           );");
         db_query("CREATE INDEX {project_release_projects}_releases_idx ON {project_release_projects} (releases)");
       }
-      if (!db_table_exists('project_release_default_versions')) {
-        db_query("CREATE TABLE {project_release_default_versions} (
+      if (!db_table_exists('project_release_supported_versions')) {
+        db_query("CREATE TABLE {project_release_supported_versions} (
             nid int NOT NULL default '0',
             tid int NOT NULL default '0',
             major int NOT NULL default '0',
-            PRIMARY KEY (nid, tid)
+            supported smallint NOT NULL default '1',
+            recommended smallint NOT NULL default '0',
+            snapshot smallint NOT NULL default '0',
+            PRIMARY KEY (nid, tid, major)
           );");
       }
       if (!db_table_exists('project_release_package_errors')) {
@@ -296,3 +300,56 @@ function project_release_update_5003() {
   }
   return $ret;
 }
+
+/**
+ * Add the new {project_release_supported_versions} table, transfer data
+ * from {project_release_default_versions} and {project_release_projects},
+ * and drop the stale table and columns.
+ */
+function project_release_update_5200() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("CREATE TABLE IF NOT EXISTS {project_release_supported_versions} (
+          nid int unsigned NOT NULL default '0',
+          tid int unsigned NOT NULL default '0',
+          major int unsigned NOT NULL default '0',
+          supported tinyint unsigned NOT NULL default '1',
+          recommended tinyint unsigned NOT NULL default '0',
+          snapshot tinyint unsigned NOT NULL default '0',
+          PRIMARY KEY (nid, tid, major)
+        ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
+      break;
+    case 'pgsql':
+      if (!db_table_exists('project_release_supported_versions')) {
+        $ret[] = update_sql("CREATE TABLE {project_release_supported_versions} (
+            nid int NOT NULL default '0',
+            tid int NOT NULL default '0',
+            major int NOT NULL default '0',
+            supported smallint NOT NULL default '1',
+            recommended smallint NOT NULL default '0',
+            snapshot smallint NOT NULL default '0',
+            PRIMARY KEY (nid, tid, major)
+          );");
+      }
+      break;
+  }
+  // Now, populate the table with the existing data. First, populate all the
+  // recommended majors versions from {project_release_default_versions}.
+  $ret[] = update_sql("INSERT INTO {project_release_supported_versions} (nid, tid, major, supported, recommended, snapshot) SELECT prdv.nid, prdv.tid, prdv.major, 1, 1, prp.snapshot_table FROM {project_release_default_versions} prdv INNER JOIN {project_release_projects} prp ON prdv.nid = prp.nid");
+
+  // Now, fill in all the non-recommended versions. For the migration path, we
+  // assume that if it's not recommended, it's also not supported. Project
+  // maintainers will have to manually visit the new UI if they want to
+  // specify multiple supported versions.
+  $vid = _project_release_get_api_vid();
+  $ret[] = update_sql("INSERT INTO {project_release_supported_versions} (nid, tid, major, supported, recommended, snapshot) SELECT DISTINCT prn.pid, tn.tid, prn.version_major, 0, 0, prp.snapshot_table FROM {project_release_nodes} prn INNER JOIN {project_release_projects} prp ON prn.pid = prp.nid INNER JOIN {term_node} tn ON prn.nid = tn.nid INNER JOIN {term_data} td ON tn.tid = td.tid INNER JOIN {node} n ON prn.nid = n.nid LEFT JOIN {project_release_default_versions} prdv ON prdv.nid = prn.pid AND prdv.tid = tn.tid AND prdv.major = prn.version_major WHERE n.status = 1 AND td.vid = $vid AND prn.version_major IS NOT NULL AND prdv.major IS NULL");
+
+  // Finally, drop the stale table and column.
+  // While testing the upgrade path, these are both commented out for now:
+  //$ret[] = update_sql('DROP TABLE {project_release_default_versions}');
+  //$ret[] = update_sql("ALTER TABLE {project_release_projects} DROP snapshot_table");
+
+  return $ret;
+}
Index: release/project_release.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project/release/project_release.module,v
retrieving revision 1.56
diff -u -p -r1.56 project_release.module
--- release/project_release.module	6 Jan 2008 18:29:22 -0000	1.56
+++ release/project_release.module	8 Jan 2008 10:20:19 -0000
@@ -621,12 +621,12 @@ function project_release_db_save($node, 
   db_query($sql, $values);
 
   // Since release nodes can be unpublished, we need to make sure that the
-  // default branch information is still up to date.
+  // recommended branch information is still up to date.
   if (module_exists('taxonomy')) {
     $vid = _project_release_get_api_vid();
     if (isset($node->taxonomy[$vid])) {
       $tid = $node->taxonomy[$vid];
-      project_release_check_default_version($node->pid, $tid);
+      project_release_check_supported_versions($node->pid, $tid);
     }
   }
 
@@ -637,31 +637,37 @@ function project_release_db_save($node, 
 }
 
 /**
- * Verifies the data for default release versions, and updates if necessary.
+ * Verifies the data for supported release versions, and updates if necessary.
  *
  * @param $pid The project ID.
  * @param $tid The API compatibility term ID.
  */
-function project_release_check_default_version($pid, $tid) {
-  // Grab the current default branch for this API version from the default
-  // release table.
-  $default_branch = db_result(db_query("SELECT major FROM {project_release_default_versions} WHERE nid = %d AND tid = %d", $pid, $tid));
-
-  // A default entry exists, so check to see if the current default branch
-  // contains active release nodes.
-  if ($default_branch !== FALSE) {
-    $active_branch_releases = db_result(db_query("SELECT COUNT(DISTINCT(r.nid)) FROM {project_release_nodes} r INNER JOIN {node} n ON n.nid = r.nid INNER JOIN {term_node} tn ON tn.nid = n.nid WHERE r.pid = %d AND tn.tid = %d AND n.status = 1 AND r.file_path <> '' AND r.version_major = %d", $pid, $tid, $default_branch));
+function project_release_check_supported_versions($pid, $tid) {
+  // Grab the recommended branch for this API version from the supported
+  // majors table.
+  $recommended_branch = db_result(db_query("SELECT major FROM {project_release_supported_versions} WHERE nid = %d AND tid = %d AND recommended = 1", $pid, $tid));
+
+  if ($recommended_branch !== FALSE) {
+    // An entry exists, so check to see if the current recommended branch
+    // contains active release nodes.
+    $active_branch_releases = db_result(db_query("SELECT COUNT(DISTINCT(r.nid)) FROM {project_release_nodes} r INNER JOIN {node} n ON n.nid = r.nid INNER JOIN {term_node} tn ON tn.nid = n.nid WHERE r.pid = %d AND tn.tid = %d AND n.status = 1 AND r.file_path <> '' AND r.version_major = %d", $pid, $tid, $recommended_branch));
   }
 
-  if ($default_branch === FALSE || !$active_branch_releases) {
-    // Missing default, so first clear out any bad data.
-    db_query("DELETE FROM {project_release_default_versions} WHERE nid = %d AND tid = %d", $pid, $tid);
-
+  if ($recommended_branch === FALSE || !$active_branch_releases) {
     // For a sensible default, pull the lowest major number that has a
     // published release.
     $lowest_major = db_result(db_query("SELECT MIN(r.version_major) FROM {project_release_nodes} r INNER JOIN {node} n ON n.nid = r.nid INNER JOIN {term_node} tn ON tn.nid = n.nid WHERE r.pid = %d AND tn.tid = %d AND n.status = 1 AND r.file_path <> ''", $pid, $tid));
     if ($lowest_major !== FALSE) {
-      db_query("INSERT INTO {project_release_default_versions} (nid, tid, major) VALUES (%d, %d, %d)", $pid, $tid, $lowest_major);
+      // Bogus recommended branch, so first clear out any bad data.
+      db_query("UPDATE {project_release_supported_versions} SET recommended = 0 WHERE nid = %d AND tid = %d", $pid, $tid);
+
+      // See if we've got a record for the lowest_major yet:
+      if (db_result(db_query("SELECT COUNT(*) FROM {project_release_supported_versions} WHERE nid = %d AND tid = %d AND major = %d", $pid, $tid, $lowest_major))) {
+        db_query("UPDATE {project_release_supported_versions} SET supported = 1, recommended = 1 WHERE nid = %d AND tid = %d AND major = %d", $pid, $tid, $lowest_major);
+      }
+      else {
+        db_query("INSERT INTO {project_release_supported_versions} (nid, tid, major, supported, recommended, snapshot) VALUES (%d, %d, %d, 1, 1, 0)", $pid, $tid, $lowest_major);
+      }
     }
   }
 }
@@ -676,8 +682,8 @@ function project_release_delete($node) {
   }
   db_query("DELETE FROM {project_release_nodes} WHERE nid = %d", $node->nid);
 
-  // Make sure that the default release version information is up to date.
-  project_release_check_default_version($node->pid, $node->version_api_tid);
+  // Make sure that the supported release version information is up to date.
+  project_release_check_supported_versions($node->pid, $node->version_api_tid);
 
   // Finally, clear the cache for the release table whenever a release node is
   // deleted, since what we want to display might have changed, too.
@@ -1024,6 +1030,25 @@ function project_release_project_edit_re
 }
 
 function project_release_project_edit_form($node) {
+  // Get all the data about major versions for this project.
+  $data = _project_release_get_version_major_data($node);
+
+  // Build all the form elements for supported and recommended major versions.
+  $form = _project_release_edit_version_major_form($data);
+
+  // Now, add a header and some help text for those elements.
+  $form['header'] = array(
+    '#type' => 'markup',
+    '#value' => t('Supported versions'),
+  );
+
+  $vocab = taxonomy_get_vocabulary(_project_release_get_api_vid());
+  $form['help'] = array(
+    '#type' => 'markup',
+    '#value' => t('For each term in the %api_vocabulary_name vocabulary, the following tables allow you to define which major versions are supported. If there are releases from more than one major version number, you can select which major version should be recommended for new users to download. You can also control if the development snapshot releases should be displayed on the front page of the project. Finally, for each term in the %api_vocabulary_name vocabulary, the current recommended release is shown. If no official releases have been made, the development snapshot from the corresponding branch will be listed. Otherwise, the most recent official release will be listed.', array('%api_vocabulary_name' => $vocab->name)),
+  );
+
+  // Finally, add everything else (currently, the "Advanced options").
   $form['advanced'] = array(
     '#type' => 'fieldset',
     '#title' => t('Advanced options'),
@@ -1040,121 +1065,152 @@ function project_release_project_edit_fo
     '#default_value' => isset($node->releases) ? $node->releases : 1,
     '#description' => t('Allow releases of this project with specific versions.'),
   );
-  if ($node->releases) {
-    $releases = array();
-    if ($tree = project_release_get_api_taxonomy()) {
-      $query = db_query('SELECT tid, major FROM {project_release_default_versions} WHERE nid = %d', $node->nid);
-      $node->default_versions = array();
-      while ($version = db_fetch_object($query)) {
-        $tid = $version->tid;
-        $major = $version->major;
-        $node->default_versions[$tid] = $major;
-      }
-      $result = db_query(db_rewrite_sql("SELECT n.nid, tn.tid, td.name, r.* FROM {node} n INNER JOIN {project_release_nodes} r ON r.nid = n.nid INNER JOIN {term_node} tn ON tn.nid = r.nid INNER JOIN {term_data} td ON tn.tid = td.tid WHERE (r.pid = %d) AND (n.status = 1) AND (r.file_path <> '') ORDER BY tn.tid, r.rebuild, r.version_major DESC, r.version_minor DESC, r.version_patch DESC, r.file_date DESC"), $node->nid);
-      while ($rel = db_fetch_object($result)) {
-        $tid = $rel->tid;
-        $major = $rel->version_major;
-        if (isset($major)) {
-          $releases[$tid]['majors'][$major] = $major;
-        }
-        $releases[$tid]['name'] = $rel->name;
-        if (!isset($releases[$tid]['current']) && (!isset($node->default_versions[$tid]) || $node->default_versions[$tid] == $major)) {
-          $releases[$tid]['current'] = $rel->version;
-        }
-      }
-      $form['default_versions'] = array('#tree' => TRUE);
-      foreach ($tree as $term) {
-        $tid = $term->tid;
-        if (isset($releases[$tid])) {
-          $form['default_versions'][$tid] = array();
-          if (count($releases[$tid]['majors']) > 1) {
-            $form['default_versions'][$tid]['major'] = array(
-              '#title' => t('Major version'),
-              '#type' => 'select',
-              '#options' => $releases[$tid]['majors'],
-              '#default_value' => isset($node->default_versions[$tid]) ? $node->default_versions[$tid] : 1,
-            );
-          }
-          else {
-            $form['default_versions'][$tid]['major'] = array(
-              '#type' => 'value',
-              '#theme' => 'project_release_form_value',
-              '#value' => is_array($releases[$tid]['majors']) ? current($releases[$tid]['majors']) : '',
-            );
-          }
-          $form['default_versions'][$tid]['current_release'] = array(
-            '#type' => 'value',
-            '#theme' => 'project_release_form_value',
-            '#value' => $releases[$tid]['current'],
-          );
-          $form['default_versions'][$tid]['name'] = array(
-            '#type' => 'value',
-            '#theme' => 'project_release_form_value',
-            '#value' => $releases[$tid]['name'],
-          );
-        }
-      }
-    }
-
-    if (user_access('administer projects')) {
-      $form['advanced']['version_format'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Version format string'),
-        '#default_value' => $node->version_format,
-        '#size' => 50,
-        '#maxlength' => 255,
-        '#description' => t('Customize the format of the version strings for releases of this project.') .' '. PROJECT_RELEASE_VERSION_FORMAT_HELP .' '. t('If blank, this project will use the site-wide default (currently set to: %default)', array('%default' => variable_get('project_release_default_version_format', PROJECT_RELEASE_DEFAULT_VERSION_FORMAT))),
-      );
-    }
-    $vocab = taxonomy_get_vocabulary(_project_release_get_api_vid());
-    $vocab = isset($vocab) ? theme('placeholder', $vocab->name) : t('version');
-    $form['snapshot_table'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Display snapshot releases on project page'),
-      '#return_value' => 1,
-      '#default_value' => isset($node->snapshot_table) ? $node->snapshot_table : 1,
-      '#description' => t('Note: Even if the current default release for a given !vocabulary_name is a development snapshot, it will only be shown if this checkbox is enabled.', array('!vocabulary_name' => $vocab)),
+  
+  if (user_access('administer projects')) {
+    $form['advanced']['version_format'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Version format string'),
+      '#default_value' => $node->version_format,
+      '#size' => 50,
+      '#maxlength' => 255,
+      '#description' => t('Customize the format of the version strings for releases of this project.') .' '. PROJECT_RELEASE_VERSION_FORMAT_HELP .' '. t('If blank, this project will use the site-wide default (currently set to: %default)', array('%default' => variable_get('project_release_default_version_format', PROJECT_RELEASE_DEFAULT_VERSION_FORMAT))),
     );
   }
 
   $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
   $form['submit'] = array(
     '#type' => 'submit',
-    '#value' => t('Submit'),
+    '#value' => t('Update'),
     '#weight' => 45,
   );
 
   return $form;
 }
 
+function _project_release_get_version_major_data($node) {
+  $data = array();
+  $data['node'] = $node;
+  $params = array();
+  $params[] = $node->nid;
+  $placeholders = array();
+  $active_tids = project_release_compatibility_list();
+  foreach ($active_tids as $tid => $api_term) {
+    $placeholders[] = '%d';
+    $params[] = $tid;
+  }
+  $tid_where = 'prsv.tid IN ('. implode(',', $placeholders) .')';
+  $result = db_query("SELECT prsv.*, td.name AS term_name FROM {project_release_supported_versions} prsv INNER JOIN {term_data} td ON prsv.tid = td.tid WHERE prsv.nid = %d AND $tid_where", $params);
+  while ($obj = db_fetch_object($result)) {
+    $tid = $obj->tid;
+    if (empty($data[$tid])) {
+      $data[$tid] = array(
+        'name' => $obj->term_name,
+        'majors' => array(),
+      );
+    }
+    $data[$tid]['majors'][$obj->major] = array(
+      '#snapshot' => $obj->snapshot ? TRUE : FALSE,
+      '#supported' => $obj->supported ? TRUE : FALSE,
+      '#recommended' => $obj->recommended ? TRUE : FALSE,
+    );
+  }
+  return $data;
+}
+
+function _project_release_edit_version_major_form($data) {
+  $form = array();
+  
+  $node = $data['node'];
+  unset($data['node']);
+  
+  $form['api'] = array(
+    '#tree' => TRUE,
+  );
+  foreach ($data as $api_tid => $api_data) {
+    $form['api'][$api_tid] = array(
+      '#api_term_name' => $api_data['name'],
+      'major' => array(),
+    );
+    $recommended_version = -1;
+    $recommended_options = array();
+    foreach ($api_data['majors'] as $major_version => $major_data) {
+      if ($major_data['#recommended'] == TRUE) {
+        $recommended_version = $major_version;
+      }
+      $recommended_options[$major_version] = '';
+      $form['api'][$api_tid]['major'][$major_version] = array(
+        'supported' => array(
+          '#type' => 'checkbox',
+          '#title' => t('Supported'),
+          '#default_value' => $major_data['#supported'],
+        ),
+        'snapshot' => array(
+          '#type' => 'checkbox',
+          '#title' => t('Show snapshot release'),
+          '#default_value' => $major_data['#snapshot'],
+        ),
+      );
+    }
+    $form['api'][$api_tid]['recommended'] = array(
+      '#type' => 'radios',
+      '#title' => t('Recommended'),
+      '#options' => $recommended_options,
+      '#default_value' => $recommended_version,
+    ); 
+    $current = project_release_get_current_recommended($node->nid, $api_tid);
+    $current_version = (!empty($current)) ? $current->version : t('n/a');
+    $form['api'][$api_tid]['currently_recommended'] = array(
+      '#type' => 'markup',
+      '#value' => '<label>'. t('Currently recommended') .':</label> <span>'. $current_version .'</span>',
+    ); 
+  }
+  return $form;
+}
+
 function theme_project_release_form_value($element) {
   return check_plain($element['#value']);
 }
 
 function theme_project_release_project_edit_form($form) {
   $output = '';
-  $rows = array();
-  if ($vocab = taxonomy_get_vocabulary(_project_release_get_api_vid())) {
-    $header = array(
-      check_plain($vocab->name),
-      t('Major version'),
-      t('Current default release'),
-    );
-  }
-  foreach (element_children($form['default_versions']) as $tid) {
+  $output .= '<h3>'. drupal_render($form['header']) ."</h3>\n";
+  $output .= '<p>'. drupal_render($form['help']) ."</p>\n";
+
+  $header = array(
+    t('Major version'),
+    t('Supported'),
+    t('Recommended'),
+    t('Show snapshot release'),
+  );
+
+  foreach (element_children($form['api']) as $tid) {
+    $output .= '<h3>'. $form['api'][$tid]['#api_term_name'] .'</h3>';
+    $rows = array();
+    krsort($form['api'][$tid]['major']);
+    foreach (element_children($form['api'][$tid]['major']) as $major) {
+      $row = array();
+      $row[] = $major;
+      // We want to unset the titles for each element, since we already have
+      // table headers to label each column.
+      unset($form['api'][$tid]['major'][$major]['supported']['#title']);
+      $row[] = drupal_render($form['api'][$tid]['major'][$major]['supported']);
+      unset($form['api'][$tid]['recommended'][$major]['#title']);
+      $row[] = drupal_render($form['api'][$tid]['recommended'][$major]);
+      unset($form['api'][$tid]['major'][$major]['snapshot']['#title']);
+      $row[] = drupal_render($form['api'][$tid]['major'][$major]['snapshot']);
+      $rows[] = $row;
+    }
+    // Finally, add a row for the currently recommended version.
     $row = array();
-    $row[] = drupal_render($form['default_versions'][$tid]['name']);
-    unset($form['default_versions'][$tid]['major']['#title']);
-    $row[] = drupal_render($form['default_versions'][$tid]['major']);
-    $row[] = drupal_render($form['default_versions'][$tid]['current_release']);
+    $row[] = array(
+      'data' => drupal_render($form['api'][$tid]['currently_recommended']),
+      'colspan' => 4,
+    );
     $rows[] = $row;
-  }
-  if (count($rows)) {
-    $output .= '<h3>'. t('Default versions') .'</h3>';
     $output .= theme('table', $header, $rows);
-    $output .= '<div class="form-item"><div class="description">'. t('This table allows you to specify the default version to download for any given %vocabulary_name. If there are releases from more than one major version number, you can select which major version should be used. For each %vocabulary_name, the current default release based on the saved major version is shown. If no official releases have been made, the development snapshot from the corresponding branch will be listed. Otherwise, the most recent official release is used.', array('%vocabulary_name' => $vocab->name)) ."</div></div>\n";
   }
-  $output .= drupal_render($form['snapshot_table']);
+  unset($form['api']);
+  
   $output .= drupal_render($form['advanced']);
   $output .= drupal_render($form);
   return $output;
@@ -1170,6 +1226,34 @@ function project_release_project_edit_fo
   if (!empty($form_values['version_format'])) {
     _project_release_validate_format_string($form_values, 'version_format');
   }
+  if (isset($form_values['api'])) {
+    foreach ($form_values['api'] as $tid => $api_info) {
+      $supported = FALSE;
+      // First, we just iterate through to see if *any* majors are supported.
+      foreach ($api_info['major'] as $major => $flags) {
+        if (!empty($flags['supported'])) {
+          $supported = TRUE;
+          break;
+        }
+      }
+      if ($supported) {
+        // At least 1 major is supported, so validate the settings.
+        foreach ($api_info['major'] as $major => $flags) {
+          if (empty($flags['supported']) && !empty($flags['snapshot'])) {
+            $element = 'api]['. $tid .'][major]['. $major .'][snapshot';
+            form_set_error($element, t('You can not show a snapshot release for a major version that is not supported.'));
+          }
+        }
+        $recommended = $api_info['recommended'];
+        if ($recommended < 0) {
+          form_set_error("api][$tid][recommended", t('You must select a recommend a major version.'));
+        }
+        elseif (empty($api_info['major'][$recommended]['supported'])) {
+          form_set_error("api][$tid][recommended", t('You can not recommend a major version that is not supported.'));
+        }
+      }
+    }
+  }
 }
 
 /**
@@ -1178,17 +1262,29 @@ function project_release_project_edit_fo
  * @see project_release_project_edit_releases
  */
 function project_release_project_edit_form_submit($form_id, $form_values) {
-  db_query("UPDATE {project_release_projects} SET releases = %d, snapshot_table = %d, version_format = '%s' WHERE nid = %d", $form_values['releases'], $form_values['snapshot_table'], $form_values['version_format'], $form_values['nid']);
+  $nid = $form_values['nid'];
+  db_query("UPDATE {project_release_projects} SET releases = %d, version_format = '%s' WHERE nid = %d", $form_values['releases'], $form_values['version_format'], $nid);
 
-  if (!empty($form_values['default_versions'])) {
-    foreach ($form_values['default_versions'] as $tid => $values) {
-      if ($obj = db_fetch_object(db_query("SELECT * FROM {project_release_default_versions} WHERE nid = %d AND tid = %d", $form_values['nid'], $tid))) {
-        if ($obj->major != $values['major']) {
-          db_query("UPDATE {project_release_default_versions} SET major = %d WHERE nid = %d AND tid = %d", $values['major'], $form_values['nid'], $tid);
-        }
+  if (!empty($form_values['api'])) {
+    foreach ($form_values['api'] as $tid => $values) {
+      if (isset($values['recommended'])) {
+        $recommended_major = $values['recommended'];
       }
-      else {
-        db_query("INSERT INTO {project_release_default_versions} (nid, tid, major) VALUES (%d, %d, %d)", $form_values['nid'], $tid, $values['major']);
+      if (!empty($values['major'])) {
+        foreach ($values['major'] as $major => $major_values) {
+          $major_values['recommended'] = ($major_values['supported'] && $recommended_major == $major) ? 1 : 0;
+          $major_values['snapshot'] = ($major_values['supported'] && $major_values['snapshot']) ? 1 : 0;
+          if ($obj = db_fetch_object(db_query("SELECT * FROM {project_release_supported_versions} WHERE nid = %d AND tid = %d AND major = %d", $nid, $tid, $major))) {
+            if ($obj->supported != $major_values['supported']
+                || $obj->recommended != $major_values['recommended']
+                || $obj->snapshot != $major_values['snapshot']) {
+              db_query("UPDATE {project_release_supported_versions} SET supported = %d, recommended = %d, snapshot = %d WHERE nid = %d AND tid = %d AND major = %d", $major_values['supported'], $major_values['recommended'], $major_values['snapshot'], $nid, $tid, $major);
+            }
+          }
+          else {
+            db_query("INSERT INTO {project_release_supported_versions} (nid, tid, major, supported, recommended, snapshot) VALUES (%d, %d, %d, %d, %d, %d)", $nid, $tid, $major, $major_values['supported'], $major_values['recommended'], $major_values['snapshot']);
+          }
+        }
       }
     }
   }
@@ -1315,6 +1411,63 @@ function project_release_project_nodeapi
 }
 
 /**
+ * Finds the currently recommended release for a given project.
+ *
+ * @param $project_nid
+ *  The nid of the project to find the current release for.
+ * @param $api_tid
+ *  The API compatibility term ID you want to search.
+ * @param $recommended_major
+ *  (Optional) a specific major version to search. If not specified, the
+ *  current value from the {project_release_supported_versions} table is used.
+ *
+ * @return
+ *  An object containing all the fields from {project_release_nodes}, along
+ *  with {node}.title, for the appropriate release.
+ */
+function project_release_get_current_recommended($project_nid, $api_tid, $recommended_major = NULL) {
+  static $current = array();
+  $major = isset($recommended_major) ? $recommended_major : 'current';
+  if (isset($current_major[$project_nid][$api_tid][$major])) {
+    return $current[$project_nid][$api_tid][$major];
+  }
+
+  $join = $where = '';
+  $params = $orderby = array();
+
+  $join = ' INNER JOIN {term_node} tn ON n.nid = tn.nid AND tn.tid = %d';
+  $params[] = $api_tid;
+
+  $where = 'WHERE (r.pid = %d) AND (n.status = 1)';
+  $params[] = $project_nid;
+
+  if (isset($recommended_major)) {
+    $where .= ' AND (r.version_major = %d)';
+    $params[] = $recommended_major;
+  }
+  else {
+    $join .= ' INNER JOIN {project_release_supported_versions} prsv ON prsv.nid = r.pid AND prsv.tid = tn.tid AND prsv.major = r.version_major AND prsv.recommended = 1 ';
+  }
+
+  // We always want the dev snapshots to show up last.
+  $orderby[] = 'r.rebuild';
+  $orderby[] = 'r.version_major DESC';
+  $orderby[] = 'r.version_minor DESC';
+  $orderby[] = 'r.version_patch DESC';
+  $orderby[] = 'r.file_date DESC';
+  $order_by = 'ORDER BY '. implode(', ', $orderby);
+
+  $result = db_query(db_rewrite_sql(
+    "SELECT n.nid, n.title, n.created, r.* FROM {node} n ".
+    "INNER JOIN {project_release_nodes} r ON r.nid = n.nid $join ".
+    "$where $order_by LIMIT 1"), $params);
+
+  $release = db_fetch_object($result);
+  $current_major[$project_nid][$api_tid][$major] = $release;
+  return $release;
+}
+
+/**
  * Theme the appropriate release download table for a project node.
  */
 function theme_project_release_project_download_table($node) {
@@ -1322,9 +1475,9 @@ function theme_project_release_project_d
     return;
   }
   $output = '<h3>'. t('Releases') .'</h3>';
-  $output .= project_release_table($node, 'defaults', 'official', t('Official releases'));
+  $output .= project_release_table($node, 'recommended', 'official', t('Official releases'));
   if ($node->snapshot_table) {
-    $output .= project_release_table($node, 'defaults', 'snapshot', t('Development snapshots'));
+    $output .= project_release_table($node, 'recommended', 'snapshot', t('Development snapshots'));
   }
   $links = array();
   $links[] = l(t('View all releases'), 'node/'. $node->nid .'/release');
@@ -1360,7 +1513,7 @@ function theme_project_release_table_ove
  *
  * @param $table_type
  *   Indicates what kind of table should be generated. Possible options:
- *    'defaults': Only show the current default versions.
+ *    'recommended': Only show the current recommended versions.
  *    'all': Include all releases.
  *
  * @param $release_type
@@ -1375,7 +1528,7 @@ function theme_project_release_table_ove
  * @param $print_size
  *   Should the table include the filesize of each release?
  */
-function project_release_table($project, $table_type = 'defaults', $release_type = 'all', $title = NULL, $print_size = TRUE) {
+function project_release_table($project, $table_type = 'recommended', $release_type = 'all', $title = NULL, $print_size = TRUE) {
   if (empty($title)) {
     $title = t('Version');
   }
@@ -1398,9 +1551,9 @@ function project_release_table($project,
     }
   }
 
-  if ($table_type == 'defaults') {
+  if ($table_type == 'recommended') {
     if ($tids) {
-      $join .= ' INNER JOIN {project_release_default_versions} prdv ON prdv.nid = r.pid AND prdv.tid = tn.tid AND prdv.major = r.version_major ';
+      $join .= ' INNER JOIN {project_release_supported_versions} prsv ON prsv.nid = r.pid AND prsv.tid = tn.tid AND prsv.major = r.version_major AND prsv.recommended = 1 ';
     }
     else {
       // TODO
@@ -1418,9 +1571,10 @@ function project_release_table($project,
         // If we're generating the default releases table, we want the
         // dev snapshots to be first in the query results, so that we
         // skip over them and only show official releases (if any).
-      $orderby[] = 'r.rebuild'. (($table_type == 'defaults') ? ' DESC' : '');
+      $orderby[] = 'r.rebuild'. (($table_type == 'recommended') ? ' DESC' : '');
       break;
   }
+
   $orderby[] = 'r.version_major';
   $orderby[] = 'r.version_minor';
   $orderby[] = 'r.version_patch';
@@ -1517,7 +1671,7 @@ function project_release_table($project,
 
   $rows = array();
   foreach ($items as $tid => $item) {
-    if ($table_type == 'defaults') {
+    if ($table_type == 'recommended') {
       // We only want the last entry in the array.
       $rows[] = array_pop($item);
     }
