--- modules/update/update.manager.inc	Mon Jan 03 03:53:07 2011
+++ modules/update/update.manager.inc	Wed Mar 02 14:25:37 2011
@@ -93,63 +93,87 @@
     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.
+    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>';
+      }
+    }
+    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>';
+      }
     }
 
-    // Create an entry for this project.
-    $entry = array(
-      'title' => $project_name,
-      'installed_version' => $project['existing_version'],
-      'recommended_version' => $recommended_version,
-    );
+    // Create an entry/entries for this project.
+    if ($recommended_release !== NULL) {
+      $recommended_entry = array(
+        // The project name to display can vary based on the info we have.
+      	'title' => _update_manager_get_project_name($project, $name),
+      	'installed_version' => $project['existing_version'],
+        'available_version' => $recommended_version,
+      );
+    }
+    if ($dev_release !== NULL) {
+      $dev_entry = array(
+        // The project name to display can vary based on the info we have.
+      	'title' => _update_manager_get_project_name($project, $name),
+      	'installed_version' => $project['existing_version'],
+        'available_version' => $dev_version,
+      );
+    }
 
     switch ($project['status']) {
       case UPDATE_NOT_SECURE:
       case UPDATE_REVOKED:
-        $entry['title'] .= ' ' . t('(Security update)');
-        $entry['#weight'] = -2;
-        $type = 'security';
+        $recommended_entry['title'] .= ' ' . t('(Security update)');
+        $recommended_entry['#weight'] = -2;
+        $recommended_entry['#attributes'] = array('class' => array('update-security'));
         break;
 
       case UPDATE_NOT_SUPPORTED:
-        $type = 'unsupported';
-        $entry['title'] .= ' ' . t('(Unsupported)');
-        $entry['#weight'] = -1;
+        $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:
-        $type = 'recommended';
+        $recommended_entry['#attributes'] = array('class' => array('update-recommended'));
         break;
 
       default:
@@ -157,18 +181,7 @@
         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 {
+    if ($project['project_type'] !== 'core') {
       $form['project_downloads'][$name] = array(
         '#type' => 'value',
         '#value' => $recommended_release['download_link'],
@@ -180,19 +193,42 @@
     switch ($project['project_type']) {
       case 'core':
         // Core needs manual updates at this time.
-        $projects['manual'][$name] = $entry;
+        // 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] = $entry;
+        $projects['enabled'][$name] = $recommended_entry;
         break;
 
       case 'module-disabled':
       case 'theme-disabled':
-        $projects['disabled'][$name] = $entry;
+        $projects['disabled'][$name] = $recommended_entry;
         break;
     }
+
+    if ($dev_entry !== NULL) {
+      $dev_entry['#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;
+      }
+    }
   }
 
   if (empty($projects)) {
@@ -208,7 +244,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 +268,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 +328,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;
 }
 
 /**
