Index: modules/block/block.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.api.php,v
retrieving revision 1.13
diff -u -p -r1.13 block.api.php
--- modules/block/block.api.php	13 Aug 2010 12:25:14 -0000	1.13
+++ modules/block/block.api.php	6 Oct 2010 07:21:47 -0000
@@ -57,6 +57,7 @@
  *     - DRUPAL_CACHE_GLOBAL: The block is the same for every user on every
  *       page where it is visible.
  *     - DRUPAL_NO_CACHE: The block should not get cached.
+ *   - 'properties': (optional) @todo: Document me!
  *   - 'weight': (optional) Initial value for the ordering weight of this block.
  *     Most modules do not provide an initial value, and any value provided can
  *     be modified by a user on the block configuration screen.
Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.904
diff -u -p -r1.904 comment.module
--- modules/comment/comment.module	5 Oct 2010 06:17:28 -0000	1.904
+++ modules/comment/comment.module	6 Oct 2010 07:21:48 -0000
@@ -402,6 +402,7 @@ function comment_permission() {
  */
 function comment_block_info() {
   $blocks['recent']['info'] = t('Recent comments');
+  $blocks['recent']['properties']['administrative'] = TRUE;
 
   return $blocks;
 }
Index: modules/dashboard/dashboard.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/dashboard/dashboard.module,v
retrieving revision 1.39
diff -u -p -r1.39 dashboard.module
--- modules/dashboard/dashboard.module	3 Oct 2010 00:14:57 -0000	1.39
+++ modules/dashboard/dashboard.module	6 Oct 2010 07:21:48 -0000
@@ -185,6 +185,33 @@ function dashboard_system_info_alter(&$i
 }
 
 /**
+ * Implements hook_block_info_alter().
+ *
+ * Saves a list of blocks to be available to the dashboard by default, which
+ * are defined with $block['properties']['administrative'] = TRUE.
+ */
+function dashboard_block_info_alter(&$blocks, $theme, $code_blocks) {
+  if (!$theme = variable_get('admin_theme')) {
+    global $theme_key;
+    $theme = $theme_key;
+  }
+  $available_blocks = variable_get('dashboard_available_blocks', array());
+
+  foreach ($blocks as $module => &$module_blocks) {
+    foreach ($module_blocks as $delta => &$block) {
+      if ($block['theme'] != $theme) {
+        // Only process for the admin theme, which displays the dashboard.
+        continue;
+      }
+      if (!empty($code_blocks[$module][$delta]['properties']['administrative']) && !isset($available_blocks[$module][$delta])) {
+        $available_blocks[$module][$delta] = TRUE;
+      }
+    }
+  }
+  variable_set('dashboard_available_blocks', $available_blocks);
+}
+
+/**
  * Implements hook_theme().
  */
 function dashboard_theme() {
@@ -281,14 +308,16 @@ function dashboard_admin_blocks() {
   module_load_include('inc', 'block', 'block.admin');
 
   // Prepare the blocks for the current theme, and remove those that are
-  // currently displayed in non-dashboard regions.
+  // currently displayed in non-dashboard regions, or are not marked as
+  // available through hook_block_info() or the block configure form.
   // @todo This assumes the current page is being displayed using the same
   //   theme that the dashboard is displayed in.
   $blocks = block_admin_display_prepare_blocks($theme_key);
   $dashboard_regions = dashboard_region_descriptions();
   $regions_to_remove = array_diff_key(system_region_list($theme_key, REGIONS_VISIBLE), $dashboard_regions);
+  $available_blocks = variable_get('dashboard_available_blocks', array());
   foreach ($blocks as $id => $block) {
-    if (isset($regions_to_remove[$block['region']])) {
+    if (isset($regions_to_remove[$block['region']]) || (empty($available_blocks[$block['module']][$block['delta']]) && !in_array($block['region'], dashboard_regions()))) {
       unset($blocks[$id]);
     }
   }
@@ -356,6 +385,60 @@ function dashboard_form_block_admin_conf
       $region['#options'] = array_diff_key($region['#options'], $dashboard_regions);
     }
   }
+
+  // Provide a block setting to control dashboard availability.
+  $available_blocks = variable_get('dashboard_available_blocks', array());
+  $form['settings']['dashboard_availability'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Available on dashboard'),
+    '#default_value' => !empty($available_blocks[$form['module']['#value']][$form['delta']['#value']]),
+  );
+
+  $form['#validate'][] = 'dashboard_form_block_admin_configure_validate';
+  $form['#submit'] = array_merge(array('dashboard_form_block_admin_configure_submit'), $form['#submit']);
+}
+
+/**
+ * Form validation handler for block configuration form.
+ */
+function dashboard_form_block_admin_configure_validate($form, &$form_state) {
+  global $theme_key;
+  drupal_theme_initialize();
+
+  // Ensure that the dashboard availability flag is only settable if the block
+  // hasn't been assigned to a non-dashboard region.
+  $selected_region = $form_state['values']['regions'][$theme_key];
+  $changed = ($form['settings']['dashboard_availability']['#default_value'] != $form_state['values']['dashboard_availability']);
+  if ($form_state['values']['dashboard_availability'] && $changed && $selected_region != BLOCK_REGION_NONE && !in_array($selected_region, dashboard_regions())) {
+    form_set_error('regions][' . $theme_key, t('The block must be removed from a region on the @theme theme to enable it on the dashboard.', array('@theme' => $form['regions'][$theme_key]['#title'])));
+  }
+}
+
+/**
+ * Form submission handler for block configuration form.
+ */
+function dashboard_form_block_admin_configure_submit($form, &$form_state) {
+  global $theme_key;
+  drupal_theme_initialize();
+
+  // Cause the block to be added/removed from the dashboard based on the
+  // dashboard availability setting, if the state has changed.
+  if ($form['settings']['dashboard_availability']['#default_value'] != $form_state['values']['dashboard_availability']) {
+    $region = &$form_state['values']['regions'][$theme_key];
+    $available_blocks = variable_get('dashboard_available_blocks', array());
+    $available_blocks[$form_state['values']['module']][$form_state['values']['delta']] = $form_state['values']['dashboard_availability'];
+    variable_set('dashboard_available_blocks', $available_blocks);
+    if ($form_state['values']['dashboard_availability']) {
+      // Add the block to the dashboard if not already assigned to a region.
+      if ($region == BLOCK_REGION_NONE) {
+        $region = 'dashboard_main';
+        drupal_set_message(t('Block added to the dashboard.'));
+      }
+    }
+    elseif (in_array($region, dashboard_regions())) {
+      $region = BLOCK_REGION_NONE;
+    }
+  }
 }
 
 /**
@@ -445,9 +528,11 @@ function dashboard_show_disabled() {
   // Blocks are not necessarily initialized at this point.
   $blocks = _block_rehash();
 
-  // Limit the list to disabled blocks for the current theme.
+  // Limit the list to disabled blocks for the current theme which are marked
+  // as available through hook_block_info() or the block configure form.
+  $available_blocks = variable_get('dashboard_available_blocks', array());
   foreach ($blocks as $key => $block) {
-    if ($block['theme'] != $theme_key || (!empty($block['status']) && !empty($block['region']))) {
+    if ($block['theme'] != $theme_key || (!empty($block['status']) && !empty($block['region'])) || empty($available_blocks[$block['module']][$block['delta']])) {
       unset($blocks[$key]);
     }
   }
Index: modules/dashboard/dashboard.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/dashboard/dashboard.test,v
retrieving revision 1.5
diff -u -p -r1.5 dashboard.test
--- modules/dashboard/dashboard.test	14 Sep 2010 21:51:01 -0000	1.5
+++ modules/dashboard/dashboard.test	6 Oct 2010 07:21:48 -0000
@@ -42,7 +42,7 @@ class DashboardBlocksTestCase extends Dr
     $custom_block['info'] = $this->randomName(8);
     $custom_block['title'] = $this->randomName(8);
     $custom_block['body[value]'] = $this->randomName(32);
-    $custom_block['regions[stark]'] = 'dashboard_main';
+    $custom_block['dashboard_availability'] = TRUE;
     $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
 
     // Ensure admin access.
@@ -79,4 +79,60 @@ class DashboardBlocksTestCase extends Dr
       $this->assertTrue(empty($elements), t('%region is not an available choice on the block configuration page.', array('%region' => $region)));
     }
   }
+
+  /**
+   * Test that defining a block with ['properties']['administrative'] = TRUE
+   * adds it as an available block for the dashboard.
+   */
+  function testBlockAvailability() {
+    $this->drupalGet('admin/structure/dashboard');
+    // "Recent comments" should be available in the dashboard block admin UI.
+    $this->assertText(t('Recent comments'), t('Block defined as "administrative" found in the dashboard block admin UI.'));
+    // "Syndicate" should not show up in the UI since it is not defined
+    // as "administrative".
+    $this->assertNoText(t('Syndicate'), t('Blocks not defined as "administrative" are excluded from dashboard UI by default.'));
+
+    // Now test admin/dashboard/drawer in the same way.
+    $this->drupalGet('admin/dashboard/drawer');
+    $this->assertText(t('Recent comments'), t('Drawer of disabled blocks includes the one defined as "administrative".'));
+    $this->assertNoText(t('Syndicate'), t('Drawer of disabled blocks excludes the one not defined as "administrative".'));
+
+    // @todo: manipulate availability checkbox and reverse it: make "Recent
+    // comments" disappear and make "Syndicate" an available block. Also test
+    // that block isn't automatically added if checkbox value isn't changed.
+  }
+
+  /**
+   * Test that a block can be added and removed from the dashboard via the
+   * "Available on dashboard" checkbox. Also test the validation error if the
+   * block is already assigned to a region.
+   */
+  function testDashboardEnableCheckbox() {
+    // Attempt to put "System help" block on the dashboard. This should fail
+    // since the block is already assigned to a non-dashboard region of the
+    // admin theme.
+    $this->drupalGet('admin/structure/block/manage/system/help/configure');
+    $elements = $this->xpath('//input[@id=:id]', array(':id' => 'edit-dashboard-availability'));
+    $this->assertTrue(!empty($elements[0]) && empty($elements[0]['checked']), t('Dashboard availability checkbox is present and unchecked.'));
+    $values = array();
+    $values['dashboard_availability'] = TRUE;
+    $this->drupalPost('admin/structure/block/manage/system/help/configure', $values, t('Save block'));
+    $this->assertText(t('The block must be removed from a region on the @theme theme to enable it on the dashboard.', array('@theme' => 'Stark')), t('Dashboard availability setting not selectable when the block is already in a non-dashboard region for the admin theme.'));
+
+    // Unassign the region, and now we should see a success message.
+    $values['regions[stark]'] = BLOCK_REGION_NONE;
+    $this->drupalPost('admin/structure/block/manage/system/help/configure', $values, t('Save block'));
+    $this->assertText(t('Block added to the dashboard.'), t('Validation error is fixed by unassigning the region.'));
+
+    // Ensure that the block shows up on the dashboard.
+    $this->drupalGet('admin/dashboard');
+    $this->assertText(t('System help'), t('Block is now on the dashboard.'));
+
+    // Now remove the block from the dashboard via the checkbox.
+    $values = array();
+    $values['dashboard_availability'] = FALSE;
+    $this->drupalPost('admin/structure/block/manage/system/help/configure', $values, t('Save block'));
+    $this->drupalGet('admin/dashboard');
+    $this->assertNoText(t('System help'), t('Block was removed from the dashboard via the dashboard availability setting.'));
+  }
 }
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1304
diff -u -p -r1.1304 node.module
--- modules/node/node.module	3 Oct 2010 01:15:33 -0000	1.1304
+++ modules/node/node.module	6 Oct 2010 07:21:48 -0000
@@ -2045,6 +2045,7 @@ function node_block_info() {
   $blocks['syndicate']['cache'] = DRUPAL_NO_CACHE;
 
   $blocks['recent']['info'] = t('Recent content');
+  $blocks['recent']['properties']['administrative'] = TRUE;
 
   return $blocks;
 }
Index: modules/search/search.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.module,v
retrieving revision 1.363
diff -u -p -r1.363 search.module
--- modules/search/search.module	1 Oct 2010 15:24:18 -0000	1.363
+++ modules/search/search.module	6 Oct 2010 07:21:48 -0000
@@ -143,6 +143,8 @@ function search_block_info() {
   $blocks['form']['info'] = t('Search form');
   // Not worth caching.
   $blocks['form']['cache'] = DRUPAL_NO_CACHE;
+  $blocks['form']['properties']['administrative'] = TRUE;
+
   return $blocks;
 }
 
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1206
diff -u -p -r1.1206 user.module
--- modules/user/user.module	4 Oct 2010 14:54:10 -0000	1.1206
+++ modules/user/user.module	6 Oct 2010 07:21:49 -0000
@@ -1273,10 +1273,13 @@ function user_block_info() {
   $blocks['login']['cache'] = DRUPAL_NO_CACHE;
 
   $blocks['new']['info'] = t('Who\'s new');
+  $blocks['new']['properties']['administrative'] = TRUE;
 
   // Too dynamic to cache.
   $blocks['online']['info'] = t('Who\'s online');
   $blocks['online']['cache'] = DRUPAL_NO_CACHE;
+  $blocks['online']['properties']['administrative'] = TRUE;
+
   return $blocks;
 }
 
