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.23
diff -u -p -r1.23 project_release.install
--- release/project_release.install	3 Aug 2009 18:25:15 -0000	1.23
+++ release/project_release.install	4 Aug 2009 23:51:30 -0000
@@ -219,6 +219,20 @@ function project_release_schema() {
         'not null' => TRUE,
         'default' => 0,
       ),
+      'recommended_release' => array(
+        'description' => 'The {project_release_nodes}.nid of the recommended release node for this tid and major version (the latest release without any "extra" version info such as "alpha1").',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'latest_release' => array(
+        'description' => 'The {project_release_nodes}.nid of the latest release node for this tid and major version (even if it has "extra" version info such as "alpha1").',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
     ),
     'primary key' => array('nid', 'tid', 'major'),
   );
@@ -369,3 +383,39 @@ function project_release_update_6003() {
   return $ret;
 }
 
+/**
+ * Add new columns to {project_release_supported_versions}.
+ */
+function project_release_update_6004() {
+  $ret = array('#finished' => 0);
+  if (!isset($_SESSION['project_release_update_6004'])) {
+    $spec = array('type' => 'int', 'unsigned' => TRUE, 'default' => NULL, 'not null' => FALSE);
+    db_add_field($ret, 'project_release_supported_versions', 'recommended_release', $spec);
+    db_add_field($ret, 'project_release_supported_versions', 'latest_release', $spec);
+    $_SESSION['project_release_update_6004'] = 0;
+    $_SESSION['project_release_update_6004_max'] = db_result(db_query("SELECT COUNT(*) FROM {project_release_supported_versions}"));
+  }
+
+  // Number of rows to convert per batch.
+  $limit = 20;
+  while ($limit-- && $item = db_fetch_array(db_query_range("SELECT * FROM {project_release_supported_versions} WHERE latest_release IS NULL", 0, 1))) {
+    if (!project_release_check_supported_versions($item['nid'], $item['tid'], $item['major'], FALSE)) {
+      db_query("UPDATE {project_release_supported_versions} SET recommended_release = %d, latest_release = %d WHERE nid = %d AND tid = %d AND major = %d", 0, 0, $item['nid'], $item['tid'], $item['major']);
+    }
+    $_SESSION['project_release_update_6004']++;
+  }
+
+  if ($_SESSION['project_release_update_6004'] >= $_SESSION['project_release_update_6004_max']) {
+    // We're done.  Set our new columns to default to 0 from here on out.
+    $ret[] = update_sql("ALTER TABLE {project_release_supported_versions} ALTER COLUMN recommended_release SET DEFAULT 0");
+    $ret[] = update_sql("ALTER TABLE {project_release_supported_versions} ALTER COLUMN latest_release SET DEFAULT 0");
+    unset($_SESSION['project_release_update_6004']);
+    unset($_SESSION['project_release_update_6004_max']);
+    $ret['#finished'] = 1;
+  }
+  else {
+    $ret['#finished'] = $_SESSION['project_release_update_6004'] / $_SESSION['project_release_update_6004_max'];
+  }
+  return $ret;
+}
+
Index: release/project_release.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project/release/project_release.module,v
retrieving revision 1.119
diff -u -p -r1.119 project_release.module
--- release/project_release.module	3 Aug 2009 23:36:24 -0000	1.119
+++ release/project_release.module	4 Aug 2009 23:51:31 -0000
@@ -215,17 +215,36 @@ function project_release_update($node) {
  *   The project ID.
  * @param $tid
  *   The API compatibility term ID.
- * $param $major
+ * @param $major
  *   The major version of the new/modified/deleted release.
- * $param $delete
+ * @param $delete
  *   Boolean to indicate if we're deleting a release of this major or not.
+ *
+ * @return
+ *   TRUE if we updated a record in {project_release_supported_versions},
+ *   otherwise FALSE (e.g. if there were no published releases on the
+ *   requested branch).
  */
 function project_release_check_supported_versions($pid, $tid, $major, $delete) {
+  // Remember if we updated {project_release_supported_versions} so we can
+  // return the value to our caller.
+  $did_update = FALSE;
+
+  // Regardless of if we're deleting, adding, or editing, we need to know the
+  // latest and recommended releases (if any) from the given branch. If
+  // there's no published release, these values will be 0.
+  list($latest_release, $recommended_release) = project_release_find_latest_releases($pid, $tid, $major);
+
   if ($delete) {
     // Make sure this isn't the last release node for the given major.
-    $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 = %d AND r.version_major = %d", $pid, $tid, 1, $major));
-    if (empty($active_branch_releases)) {
-      // Remove the bogus record.
+    if (!empty($latest_release)) {
+      // Since the node we just deleted might have been the latest or
+      // recommended on the branch, update our record with the real values.
+      db_query("UPDATE {project_release_supported_versions} SET recommended_release = %d, latest_release = %d WHERE nid = %d AND tid = %d AND major = %d", $recommended_release, $latest_release, $pid, $tid, $major);
+      $did_update = TRUE;
+    }
+    else {
+      // No latest release -- remove the bogus record for this branch.
       db_query("DELETE FROM {project_release_supported_versions} WHERE nid = %d AND tid = %d AND major = %d", $pid, $tid, $major);
 
       $num_recommended = db_result(db_query("SELECT COUNT(*) FROM {project_release_supported_versions} WHERE nid = %d AND tid = %d AND supported = %d AND recommended = %d", $pid, $tid, 1, 1));
@@ -248,9 +267,7 @@ function project_release_check_supported
   }
   else {
     // Adding or editing a release.
-    // First make sure this branch has at least 1 published release node
-    $published_releases = db_result(db_query("SELECT COUNT(*) 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 r.version_major = %d AND tn.tid = %d AND n.status = %d", $pid, $major, $tid, 1));
-    if ($published_releases) {
+    if (!empty($latest_release)) {
       // We have at least 1 published release, so make sure we have an entry
       // for this major version in {project_release_supported_versions}.
       $current_branches = db_query("SELECT major FROM {project_release_supported_versions} WHERE nid = %d AND tid = %d", $pid, $tid);
@@ -265,16 +282,24 @@ function project_release_check_supported
       }
       if ($num_branches == 0) {
         // First entry for this API tid. Add it as supported and recommended.
-        $recommended = 1;
+        $recommended_branch = 1;
       }
       elseif (!$have_current_branch) {
         // We've already got some branches (num_branches > 0), but not the
         // current branch, so add it as supported but not recommended.
-        $recommended = 0;
+        $recommended_branch = 0;
       }
-      if (isset($recommended)) {
-        db_query("INSERT INTO {project_release_supported_versions} (nid, tid, major, supported, recommended, snapshot) VALUES (%d, %d, %d, %d, %d, %d)", $pid, $tid, $major, 1, $recommended, 0);
+      if (isset($recommended_branch)) {
+        // This is a branch new branch, so add a new record to the table.
+        db_query("INSERT INTO {project_release_supported_versions} (nid, tid, major, supported, recommended, snapshot, recommended_release, latest_release) VALUES (%d, %d, %d, %d, %d, %d, %d, %d)", $pid, $tid, $major, 1, $recommended_branch, 0, $recommended_release, $latest_release);
       }
+      else {
+        // We already have this branch in the table, but the latest_release
+        // and recommended_release fields might be stale based on whatever
+        // node was just added or edited.
+        db_query("UPDATE {project_release_supported_versions} SET recommended_release = %d, latest_release = %d WHERE nid = %d AND tid = %d AND major = %d", $recommended_release, $latest_release, $pid, $tid, $major);
+      }
+      $did_update = TRUE;
     }
   }
 
@@ -282,6 +307,8 @@ function project_release_check_supported
   // display might have changed, too.
   $cid = 'table:'. $pid .':';
   cache_clear_all($cid, 'cache_project_release', TRUE);
+
+  return $did_update;
 }
 
 /**
@@ -807,67 +834,133 @@ function project_release_release_nodeapi
   }
 }
 
-
 /**
- * Finds the currently recommended release for a given project.
+ * Fetch information about the current releases for a given project.
+ *
+ * This just queries the {project_release_supported_versions} table for either
+ * the latest release or the recommended release, and retrieves data about
+ * that release from the {node} and {project_release_nodes} tables. To
+ * actually recompute the latest and recommended releases for a given branch,
+ * you must use project_release_find_latest_releases().
  *
  * @param $project_nid
- *  The nid of the project to find the current release for.
+ *   The nid of the project to find the current release for.
  * @param $api_tid
- *  The API compatibility term ID you want to search.
+ *   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.
+ *   An optional major version to search. If not specified, the current
+ *   recommended branch from {project_release_supported_versions} is used.
+ * @param $type
+ *   String for what kind of release to get ('recommended' or 'latest').
  *
  * @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];
+ *   An object containing all the fields from {project_release_nodes}, along
+ *   with {node}.title and {node}.created, for the appropriate release; or
+ *   FALSE if no published releases exists that the caller can access on the
+ *   requested branch of the desired project.
+ */
+function project_release_get_current_recommended($project_nid, $api_tid, $recommended_major = NULL, $type = 'recommended') {
+  // Compute the appropriate JOIN ON clauses based on the arguments.
+  $prsv_joins[] = 'n.nid = ' . $type == 'recommended' ? 'prsv.recommended_release' : 'prsv.latest_release';
+  $prsv_joins[] = 'prsv.nid = %d';
+  $join_params[] = $project_nid;
+  $prsv_joins[] = 'prsv.tid = %d';
+  $join_params[] = $api_tid;
+  if (!isset($recommended_major)) {
+    $prsv_joins[] = 'prsv.recommended = %d';
+    $join_params[] = 1;
+  }
+  else {
+    $prsv_joins[] = 'prsv.major = %d';
+    $join_params[] = $recommended_major;
   }
+  // Build the actual JOIN ON string by AND'ing all the clauses together.
+  $prsv_join = implode(' AND ', $prsv_joins);
+  $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 ".
+    "INNER JOIN {project_release_supported_versions} prsv ON $prsv_join "),
+    $join_params);
+  return db_fetch_object($result);
+}
 
-  $join = $where = '';
+/**
+ * Finds the latest and recommended releases for a given project and branch.
+ *
+ * The "latest" release just means the published release node with the highest
+ * version string. The "recommended" release is the published release node
+ * with the highest version string that doesn't have a "version_extra" field
+ * (e.g. "beta1"). If all releases on the given branch have "extra", then the
+ * recommended release will be the same as the latest release.
+ * 
+ * @param $project_nid
+ *   The node ID of the project to find the latest and recommended releases of.
+ * @param $api_tid
+ *   The API compatibility term ID to search.
+ * @param $major
+ *   The {project_release_nodes}.version_major field of the branch to search.
+ * @param $access
+ *   Optional boolean to indicate if node access checks should be enforced.
+ *   Defaults to FALSE since the caller might not actually have access to all
+ *   the releases or projects. However, this function usually has to compute
+ *   the accurate values regardless of access, and consumers of this data are
+ *   responsible for ensuring access.
+ *
+ * @return
+ *  An array containing the node ID (nid) of both the latest and recommended
+ *  releases from the given branch.
+ */
+function project_release_find_latest_releases($project_nid, $api_tid, $major, $access = FALSE) {
+  $latest_release = $recommended_release = 0;
   $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 = %d)';
+  $where = 'WHERE (r.pid = %d) AND (r.version_major = %d) AND (n.status = %d)';
   $params[] = $project_nid;
+  $params[] = $major;
   $params[] = 1;
 
-  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 = %d ';
-    $params[] = 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';
+  // Since we can't really be sure of the relative ordering of versions with
+  // extra, and since there's no good way to do something like
+  // version_compare() directly in the SQL query, for all releases at the same
+  // patch level, we'll order things by the latest file timestamp instead of
+  // trying to do a string comparison based on version_extra.
   $orderby[] = 'f.timestamp DESC';
   $order_by = 'ORDER BY '. implode(', ', $orderby);
 
-  $params[] = 1;
-  $result = db_query(db_rewrite_sql(
-    "SELECT n.nid, n.title, n.created, r.* FROM {node} n ".
+  $sql = "SELECT n.nid, n.title, n.created, r.* FROM {node} n ".
     "INNER JOIN {project_release_nodes} r ON r.nid = n.nid ".
     "INNER JOIN {project_release_file} prf on n.nid = prf.nid ".
     "INNER JOIN {files} f ON prf.fid = f.fid $join ".
-    "$where $order_by LIMIT %d"), $params);
+    "$where $order_by";
+
+  // Only enforce node access via db_rewrite_sql() if the caller specifically
+  // requested that behavior.
+  if ($access) {
+    $sql = db_rewrite_sql($sql);
+  }
 
-  $release = db_fetch_object($result);
-  $current_major[$project_nid][$api_tid][$major] = $release;
-  return $release;
+  $result = db_query($sql, $params);
+  while ($release = db_fetch_object($result)) {
+    if (empty($latest_release)) {
+      $latest_release = $release->nid;
+    }
+    if (empty($release->version_extra)) {
+      $recommended_release = $release->nid;
+      break;
+    }
+  }
+  if (empty($recommended_release)) {
+    $recommended_release = $latest_release;
+  }
+  return array($latest_release, $recommended_release);
 }
 
 /**
