--- modules/update/update.manager.inc	Mon Jan 03 03:53:07 2011
+++ modules/update/update.manager.inc	Wed Mar 02 14:51:33 2011
@@ -93,105 +93,138 @@
     if ($project['status'] == UPDATE_CURRENT) {
       continue;
     }
-    // The project name to display can vary based on the info we have.
-    if (!empty($project['title'])) {
-      if (!empty($project['link'])) {
-        $project_name = l($project['title'], $project['link']);
-      }
-      else {
-        $project_name = check_plain($project['title']);
-      }
-    }
-    elseif (!empty($project['info']['name'])) {
-      $project_name = check_plain($project['info']['name']);
+
+    // What versions should we offer: dev and/or recommended?
+    // normal => recommended: yes
+    // normal => dev: no (@todo: what if we want to install a fix? for now: manual)
+    // dev => recommended: yes, if newer, because we should *not* downgrade
+    // dev => dev: yes, if newer
+    // @todo: alpha/beta => recommended (for now: handled as normal, so no dev option considered)
+    // @todo: alpha/beta => dev (for now: handled as normal, thus manual)
+    if ($project['install_type'] !== 'dev') {
+      $dev_release = NULL;
+      $recommended_release = !empty($project['recommended']) ? $project['releases'][$project['recommended']] : NULL;
     }
     else {
-      $project_name = check_plain($name);
-    }
-    if ($project['project_type'] == 'theme' || $project['project_type'] == 'theme-disabled') {
-      $project_name .= ' ' . t('(Theme)');
+      $dev_release = !empty($project['dev_version']) && $project['datestamp'] < $project['releases'][$project['dev_version']]['date']
+        ? $project['releases'][$project['dev_version']]
+        : NULL;
+      $recommended_release = !empty($project['recommended']) && $project['datestamp'] < $project['releases'][$project['recommended']]['date']
+        ? $project['releases'][$project['recommended']]
+        : NULL;
     }
 
-    if (empty($project['recommended'])) {
-      // If we don't know what to recommend they upgrade to, we should skip
-      // the project entirely.
+    if ($dev_release === NULL && $recommended_release === NULL) {
+      // Without available updates, be it reocmmended or dev, skip the project entirely.
       continue;
     }
 
-    $recommended_release = $project['releases'][$project['recommended']];
-    $recommended_version = $recommended_release['version'] . ' ' . l(t('(Release notes)'), $recommended_release['release_link'], array('attributes' => array('title' => t('Release notes for @project_title', array('@project_title' => $project['title'])))));
-    if ($recommended_release['version_major'] != $project['existing_major']) {
-      $recommended_version .= '<div title="Major upgrade warning" class="update-major-version-warning">' . t('This update is a major version update which means that it may not be backwards compatible with your currently running version.  It is recommended that you read the release notes and proceed at your own risk.') . '</div>';
-    }
+    // Create the help/link information.
+    $title = _update_manager_get_project_name($project, $name);
+    $installed_version = $project['existing_version'];
+    $installed_version .= ' <span class="version-date">(' . format_date($project['datestamp'], 'custom', 'Y-M-d') . ')</span>';
+
+    // Create an entry/entries for this project.
+    if ($recommended_release !== NULL) {
+      $recommended_version = $recommended_release['version'];
+      $recommended_version .= ' <span class="version-date">(' . format_date($recommended_release['date'], 'custom', 'Y-M-d') . ')</span>';
+      $recommended_version .= ' ' . l(t('(Release notes)'), $recommended_release['release_link'], array('attributes' => array('title' => t('Release notes for @project_title', array('@project_title' => $project['title'])))));
+      if ($recommended_release['version_major'] != $project['existing_major']) {
+        $recommended_version .= '<div title="Major upgrade warning" class="update-major-version-warning">' . t('This update is a major version update which means that it may not be backwards compatible with your currently running version.  It is recommended that you read the release notes and proceed at your own risk.') . '</div>';
+      }
+      $recommended_entry = array(
+        // The project name to display can vary based on the info we have.
+      	'title' => $title,
+      	'installed_version' => $installed_version,
+        'available_version' => $recommended_version,
+      );
 
-    // Create an entry for this project.
-    $entry = array(
-      'title' => $project_name,
-      'installed_version' => $project['existing_version'],
-      'recommended_version' => $recommended_version,
-    );
+      switch ($project['status']) {
+        case UPDATE_NOT_SECURE:
+        case UPDATE_REVOKED:
+          $recommended_entry['title'] .= ' ' . t('(Security update)');
+          $recommended_entry['#weight'] = -2;
+          $recommended_entry['#attributes'] = array('class' => array('update-security'));
+          break;
+
+        case UPDATE_NOT_SUPPORTED:
+          $recommended_entry['title'] .= ' ' . t('(Unsupported)');
+          $recommended_entry['#weight'] = -1;
+          $recommended_entry['#attributes'] = array('class' => array('update-unsupported'));
+          break;
+
+        case UPDATE_UNKNOWN:
+        case UPDATE_NOT_FETCHED:
+        case UPDATE_NOT_CHECKED:
+        case UPDATE_NOT_CURRENT:
+          $recommended_entry['#attributes'] = array('class' => array('update-recommended'));
+          break;
+
+        default:
+          // Jump out of the switch and onto the next project in foreach.
+          continue 2;
+      }
 
-    switch ($project['status']) {
-      case UPDATE_NOT_SECURE:
-      case UPDATE_REVOKED:
-        $entry['title'] .= ' ' . t('(Security update)');
-        $entry['#weight'] = -2;
-        $type = 'security';
-        break;
-
-      case UPDATE_NOT_SUPPORTED:
-        $type = 'unsupported';
-        $entry['title'] .= ' ' . t('(Unsupported)');
-        $entry['#weight'] = -1;
-        break;
-
-      case UPDATE_UNKNOWN:
-      case UPDATE_NOT_FETCHED:
-      case UPDATE_NOT_CHECKED:
-      case UPDATE_NOT_CURRENT:
-        $type = 'recommended';
-        break;
-
-      default:
-        // Jump out of the switch and onto the next project in foreach.
-        continue 2;
-    }
-
-    $entry['#attributes'] = array('class' => array('update-' . $type));
-
-    // Drupal core needs to be upgraded manually.
-    $needs_manual = $project['project_type'] == 'core';
-
-    if ($needs_manual) {
-      // Since it won't be tableselect, #weight will add an extra column to the
-      // table if it's defined, so just unset it. The order doesn't matter that
-      // much in the manual updates table, anyway.
-      unset($entry['#weight']);
-    }
-    else {
-      $form['project_downloads'][$name] = array(
-        '#type' => 'value',
-        '#value' => $recommended_release['download_link'],
-      );
+      if ($project['project_type'] !== 'core') {
+        $form['project_downloads'][$name] = array(
+          '#type' => 'value',
+          '#value' => $recommended_release['download_link'],
+        );
+      }
+
+      // Based on what kind of project this is, save the entry into the
+      // appropriate subarray.
+      switch ($project['project_type']) {
+        case 'core':
+          // Core needs manual updates at this time.
+          // Since it won't be tableselect, #weight will add an extra column to the
+          // table if it's defined, so just unset it. The order doesn't matter that
+          // much in the manual updates table, anyway.
+          unset($recommended_entry['#weight']);
+          $projects['manual'][$name] = $recommended_entry;
+          break;
+
+        case 'module':
+        case 'theme':
+          $projects['enabled'][$name] = $recommended_entry;
+          break;
+
+        case 'module-disabled':
+        case 'theme-disabled':
+          $projects['disabled'][$name] = $recommended_entry;
+          break;
+      }
     }
 
-    // Based on what kind of project this is, save the entry into the
-    // appropriate subarray.
-    switch ($project['project_type']) {
-      case 'core':
-        // Core needs manual updates at this time.
-        $projects['manual'][$name] = $entry;
-        break;
-
-      case 'module':
-      case 'theme':
-        $projects['enabled'][$name] = $entry;
-        break;
-
-      case 'module-disabled':
-      case 'theme-disabled':
-        $projects['disabled'][$name] = $entry;
-        break;
+    if ($dev_release !== NULL) {
+      $dev_version = $dev_release['version'];
+      $dev_version .= ' <span class="version-date">(' . format_date($dev_release['date'], 'custom', 'Y-M-d') . ')</span>';
+      $dev_version .= ' ' . l(t('(Release notes)'), $dev_release['release_link'], array('attributes' => array('title' => t('Release notes for @project_title', array('@project_title' => $project['title'])))));
+      if ($dev_release['version_major'] != $project['existing_major']) {
+        $dev_version .= '<div title="Major upgrade warning" class="update-major-version-warning">' . t('This update is a major version update which means that it may not be backwards compatible with your currently running version.  It is recommended that you read the release notes and proceed at your own risk.') . '</div>';
+      }
+      $dev_entry = array(
+        // The project name to display can vary based on the info we have.
+      	'title' => $title,
+      	'installed_version' => $installed_version,
+        'available_version' => $dev_version,
+        '#attributes' => array('class' => array('update-dev')),
+      );
+      switch ($project['project_type']) {
+        case 'core':
+          // We don't present dev core updates, not even in the manual section
+          break;
+
+        case 'module':
+        case 'theme':
+          $projects['dev_enabled'][$name] = $dev_entry;
+          break;
+
+        case 'module-disabled':
+        case 'theme-disabled':
+          $projects['dev_disabled'][$name] = $dev_entry;
+          break;
+      }
     }
   }
 
@@ -208,7 +241,7 @@
       'class' => array('update-project-name'),
     ),
     'installed_version' => t('Installed version'),
-    'recommended_version' => t('Recommended version'),
+    'available_version' => t('Recommended version'),
   );
 
   if (!empty($projects['enabled'])) {
@@ -232,9 +265,46 @@
     );
   }
 
+  if (!empty($projects['dev_enabled']) || !empty($projects['dev_disabled'])) {
+    // Place dev versions in a collapsed fieldset
+    $form['dev_projects'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Available development versions'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#description' => t('Alternatively you may select these available development versions.
+Development versions are not guaranteed to work, nor may upgrading, or downgrading and uninstalling be supported.
+It is recommended that you read the release notes and proceed at your own risk.'),
+     );
+
+    $headers['available_version'] = t('Development version');
+
+    if (!empty($projects['dev_enabled'])) {
+      $form['dev_projects']['dev_enabled'] = array(
+        '#type' => 'tableselect',
+        '#header' => $headers,
+        '#options' => $projects['dev_enabled'],
+        '#weight' => 1,
+      );
+      if (!empty($projects['dev_disabled'])) {
+        $form['dev_projects']['dev_enabled']['#prefix'] = '<h2>' . t('Enabled') . '</h2>';
+      }
+    }
+
+    if (!empty($projects['dev_disabled'])) {
+      $form['dev_projects']['dev_disabled'] = array(
+        '#type' => 'tableselect',
+        '#header' => $headers,
+        '#options' => $projects['dev_disabled'],
+        '#weight' => 2,
+        '#prefix' => '<h2>' . t('Disabled') . '</h2>',
+      );
+    }
+  }
+
   // If either table has been printed yet, we need a submit button and to
   // validate the checkboxes.
-  if (!empty($projects['enabled']) || !empty($projects['disabled'])) {
+  if (!empty($projects['enabled']) || !empty($projects['disabled']) || isset($form['dev_projects'])) {
     $form['actions'] = array('#type' => 'actions');
     $form['actions']['submit'] = array(
       '#type' => 'submit',
@@ -255,6 +325,34 @@
   }
 
   return $form;
+}
+
+/**
+ * Helper method to extract a project name to be displayed to the user
+ *
+ * @param array $project project information
+ * @param string $name internal project name (machine readable name)
+ * @return string
+ */
+function _update_manager_get_project_name($project, $name) {
+  if (!empty($project['title'])) {
+    if (!empty($project['link'])) {
+      $project_name = l($project['title'], $project['link']);
+    }
+    else {
+      $project_name = check_plain($project['title']);
+    }
+  }
+  elseif (!empty($project['info']['name'])) {
+    $project_name = check_plain($project['info']['name']);
+  }
+  else {
+    $project_name = check_plain($name);
+  }
+  if ($project['project_type'] == 'theme' || $project['project_type'] == 'theme-disabled') {
+    $project_name .= ' ' . t('(Theme)');
+  }
+  return $project_name;
 }
 
 /**
