diff --git a/includes/common.inc b/includes/common.inc
index 7195e985ed374015493ad69329c2ab5965a09716..1123b1c9aed253597cefa1d83e0450bc5fcd79fb 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -6727,8 +6727,10 @@ function drupal_flush_all_caches() {
   system_rebuild_theme_data();
   drupal_theme_rebuild();
 
-  menu_rebuild();
   node_types_rebuild();
+  // node_menu() defines menu items based on node types so it needs to come
+  // after node types are rebuilt.
+  menu_rebuild();
 
   // Synchronize to catch any actions that were added or removed.
   actions_synchronize();
diff --git a/modules/forum/forum.install b/modules/forum/forum.install
index bc2a9b1b6b9a1e60ca767cc775dbe01051a8aff4..a5dbc3e1030becb32ba87cee83ddfa99771e9f7c 100644
--- a/modules/forum/forum.install
+++ b/modules/forum/forum.install
@@ -71,30 +71,30 @@ function forum_enable() {
     );
     $term = (object) $edit;
     taxonomy_term_save($term);
-  }
 
-  // Create the instance on the bundle.
-  $instance = array(
-    'field_name' => 'taxonomy_' . $vocabulary->machine_name,
-    'entity_type' => 'node',
-    'label' => $vocabulary->name,
-    'bundle' => 'forum',
-    'required' => TRUE,
-    'widget' => array(
-      'type' => 'options_select',
-    ),
-    'display' => array(
-      'default' => array(
-        'type' => 'taxonomy_term_reference_link',
-        'weight' => 10,
+    // Create the instance on the bundle.
+    $instance = array(
+      'field_name' => 'taxonomy_' . $vocabulary->machine_name,
+      'entity_type' => 'node',
+      'label' => $vocabulary->name,
+      'bundle' => 'forum',
+      'required' => TRUE,
+      'widget' => array(
+        'type' => 'options_select',
       ),
-      'teaser' => array(
-        'type' => 'taxonomy_term_reference_link',
-        'weight' => 10,
+      'display' => array(
+        'default' => array(
+          'type' => 'taxonomy_term_reference_link',
+         'weight' => 10,
+        ),
+        'teaser' => array(
+          'type' => 'taxonomy_term_reference_link',
+         'weight' => 10,
+        ),
       ),
-    ),
-  );
-  field_create_instance($instance);
+    );
+    field_create_instance($instance);
+  }
 
   // Ensure the forum node type is available.
   node_types_rebuild();
diff --git a/modules/node/content_types.inc b/modules/node/content_types.inc
index a9d4680cf183c739c6cdb81842d8532f0ea8f946..23565ce24d82335a2be67626d86dfa59b1418775 100644
--- a/modules/node/content_types.inc
+++ b/modules/node/content_types.inc
@@ -320,6 +320,9 @@ function node_type_form_submit($form, &$form_state) {
   $type->custom = $form_state['values']['custom'];
   $type->modified = TRUE;
   $type->locked = $form_state['values']['locked'];
+  if (isset($form['#node_type']->module)) {
+    $type->module = $form['#node_type']->module;
+  }
 
   if ($op == t('Delete content type')) {
     $form_state['redirect'] = 'admin/structure/types/manage/' . str_replace('_', '-', $type->old_type) . '/delete';
diff --git a/modules/node/node.install b/modules/node/node.install
index 5f93db2e8f5153952978e96e68ac5d370e8ec899..e50571ab2844593f856a3d0e85dab0b4f2aadc6b 100644
--- a/modules/node/node.install
+++ b/modules/node/node.install
@@ -294,6 +294,12 @@ function node_schema() {
         'length' => 255,
         'not null' => TRUE,
       ),
+      'module' => array(
+        'description' => 'The module defining this node type.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+      ),
       'description' => array(
         'description' => 'A brief description of this type.',
         'type' => 'text',
@@ -344,6 +350,13 @@ function node_schema() {
         'default' => 0,
         'size' => 'tiny',
       ),
+      'disabled' => array(
+        'description' => 'A boolean indicating whether the node type is disabled.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny'
+      ),
       'orig_type' => array(
         'description' => 'The original machine-readable name of this node type. This may be different from the current type name if the locked field is 0.',
         'type' => 'varchar',
@@ -438,16 +451,40 @@ function _update_7000_node_get_types() {
  */
 
 /**
- * Fix node type 'module' attribute to avoid name-space conflicts.
+ * Upgrade the node type table and fix node type 'module' attribute to avoid name-space conflicts.
  */
 function node_update_7000() {
+  // Rename the module column to base.
+  db_change_field('node_type', 'module', 'base', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE));
+
+  db_add_field('node_type', 'module', array(
+    'description' => 'The module defining this node type.',
+    'type' => 'varchar',
+    'default' => '',
+    'length' => 255,
+    'not null' => TRUE,
+  ));
+
+  db_add_field('node_type', 'disabled', array(
+    'description' => 'A boolean indicating whether the node type is disabled.',
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0,
+    'size' => 'tiny'
+  ));
+
+  $modules = db_select('system', 's')
+    ->fields('s', array('name'))
+    ->condition('type', 'module');
   db_update('node_type')
-    ->fields(array('module' => 'node_content'))
-    ->condition('module', 'node')
+    ->expression('module', 'base')
+    ->condition('base', $modules, 'IN')
     ->execute();
 
-  // Rename the module column to base.
-  db_change_field('node_type', 'module', 'base', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE));
+  db_update('node_type')
+    ->fields(array('base' => 'node_content'))
+    ->condition('base', 'node')
+    ->execute();
 }
 
 /**
diff --git a/modules/node/node.module b/modules/node/node.module
index 990a93dd2d7dbedea8df17af933dfb4cd93df86a..6435fa2e57f56f06ced63644f611c0e157a66e5c 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -461,16 +461,7 @@ function node_type_get_name($node) {
  * and obsolete types.
  */
 function node_types_rebuild() {
-  // Reset and load updated node types.
-  drupal_static_reset('_node_types_build');
-  foreach (node_type_get_types() as $type => $info) {
-    if (!empty($info->is_new)) {
-      node_type_save($info);
-    }
-    if (!empty($info->disabled)) {
-      node_type_delete($info->type);
-    }
-  }
+  _node_types_build(TRUE);
 }
 
 /**
@@ -513,6 +504,8 @@ function node_type_save($info) {
     'custom' => (int) $type->custom,
     'modified' => (int) $type->modified,
     'locked' => (int) $type->locked,
+    'disabled' => (int) $type->disabled,
+    'module' => $type->module,
   );
 
   if ($is_existing) {
@@ -660,6 +653,8 @@ function node_type_update_nodes($old_type, $type) {
  * These two information sources are not synchronized during module installation
  * until node_types_rebuild() is called.
  *
+ * @param $rebuild
+ *  TRUE to rebuild node types. Equivalent to calling node_types_rebuild().
  * @return
  *   Associative array with two components:
  *   - names: Associative array of the names of node types, keyed by the type.
@@ -673,42 +668,66 @@ function node_type_update_nodes($old_type, $type) {
  *   implementations, but are still in the database. These are indicated in the
  *   type object by $type->disabled being set to TRUE.
  */
-function _node_types_build() {
-  $_node_types = &drupal_static(__FUNCTION__);
-  if (is_object($_node_types)) {
-    return $_node_types;
+function _node_types_build($rebuild = FALSE) {
+  if (!$rebuild) {
+    $_node_types = &drupal_static(__FUNCTION__);
+    if (is_object($_node_types)) {
+      return $_node_types;
+    }
   }
+
   $_node_types = (object)array('types' => array(), 'names' => array());
 
-  $info_array = module_invoke_all('node_info');
-  foreach ($info_array as $type => $info) {
-    $info['type'] = $type;
-    $_node_types->types[$type] = node_type_set_defaults($info);
-    $_node_types->names[$type] = $info['name'];
+  foreach (module_implements('node_info') as $module) {
+    $info_array = module_invoke($module, 'node_info');
+    foreach ($info_array as $type => $info) {
+      $info['type'] = $type;
+      $_node_types->types[$type] = node_type_set_defaults($info);
+      $_node_types->types[$type]->module = $module;
+      $_node_types->names[$type] = $info['name'];
+    }
   }
-  $type_result = db_select('node_type', 'nt')
+  $query = db_select('node_type', 'nt')
     ->addTag('translatable')
     ->addTag('node_type_access')
     ->fields('nt')
-    ->orderBy('nt.type', 'ASC')
-    ->execute();
-  foreach ($type_result as $type_object) {
+    ->orderBy('nt.type', 'ASC');
+  if (!$rebuild) {
+    $query->condition('disabled', 0);
+  }
+  foreach ($query->execute() as $type_object) {
+    $type_db = $type_object->type;
+    // Original disabled value.
+    $disabled = $type_object->disabled;
     // Check for node types from disabled modules and mark their types for removal.
     // Types defined by the node module in the database (rather than by a separate
     // module using hook_node_info) have a base value of 'node_content'. The isset()
     // check prevents errors on old (pre-Drupal 7) databases.
-    if (isset($type_object->base) && $type_object->base != 'node_content' && empty($info_array[$type_object->type])) {
+    if (isset($type_object->base) && $type_object->base != 'node_content' && empty($info_array[$type_db])) {
       $type_object->disabled = TRUE;
     }
-    if (!isset($_node_types->types[$type_object->type]) || $type_object->modified) {
-      $_node_types->types[$type_object->type] = $type_object;
-      $_node_types->names[$type_object->type] = $type_object->name;
+    if (isset($info_array[$type_db])) {
+      $type_object->disabled = FALSE;
+    }
+    if (!isset($_node_types->types[$type_db]) || $type_object->modified) {
+      $_node_types->types[$type_db] = $type_object;
+      $_node_types->names[$type_db] = $type_object->name;
 
-      if ($type_object->type != $type_object->orig_type) {
+      if ($type_db != $type_object->orig_type) {
         unset($_node_types->types[$type_object->orig_type]);
         unset($_node_types->names[$type_object->orig_type]);
       }
     }
+    $_node_types->types[$type_db]->disabled = $type_object->disabled;
+    $_node_types->types[$type_db]->disabled_changed = $disabled != $type_object->disabled;
+  }
+
+  if ($rebuild) {
+    foreach ($_node_types->types as $type => $type_object) {
+      if (!empty($type_object->is_new) || !empty($type_object->disabled_changed)) {
+        node_type_save($type_object);
+      }
+    }
   }
 
   asort($_node_types->names);
@@ -742,6 +761,7 @@ function node_type_set_defaults($info = array()) {
     $type->custom = 0;
     $type->modified = 0;
     $type->locked = 1;
+    $type->disabled = 0;
     $type->is_new = 1;
 
     $type->has_title = 1;
@@ -757,6 +777,9 @@ function node_type_set_defaults($info = array()) {
   if (!$new_type->has_title) {
     $new_type->title_label = '';
   }
+  if (empty($new_type->module)) {
+    $new_type->module = $new_type->base == 'node_content' ? 'node' : '';
+  }
   $new_type->orig_type = isset($info['type']) ? $info['type'] : '';
 
   return $new_type;
diff --git a/modules/node/node.test b/modules/node/node.test
index 109b6735a454e7456a7d7eec0b01848c98f89a13..59b12f0086fa77f4d8e8ae7b3863afefe0bb9a1b 100644
--- a/modules/node/node.test
+++ b/modules/node/node.test
@@ -1148,6 +1148,82 @@ class NodeTypeTestCase extends DrupalWebTestCase {
   }
 }
 
+/**
+ * Test node type customizations persistence.
+ */
+class NodeTypePersistenceTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Node type persist',
+      'description' => 'Ensures that node type customization survives module enabling and disabling.',
+      'group' => 'Node',
+    );
+  }
+
+  /**
+   * Test node type customizations persist through disable and uninstall.
+   */
+  function testNodeTypeCustomizationPersistence() {
+    $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer modules'));
+    $this->drupalLogin($web_user);
+    $poll_key = 'modules[Core][poll][enable]';
+    $poll_enable = array($poll_key => "1");
+    $poll_disable = array($poll_key => FALSE);
+
+    // Enable poll and verify that the node type is in the DB and is not
+    // disabled.
+    $this->drupalPost('admin/modules', $poll_enable, t('Save configuration'));
+    $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField();
+    $this->assertNotIdentical($disabled, FALSE, t('Poll node type found in the database'));
+    $this->assertEqual($disabled, 0, t('Poll node type is not disabled'));
+
+    // Check that poll node type (uncustomized) shows up.
+    $this->drupalGet('node/add');
+    $this->assertText('poll', t('poll type is found on node/add'));
+
+    // Customize poll description.
+    $description = $this->randomName();
+    $edit = array('description' => $description);
+    $this->drupalPost('admin/structure/types/manage/poll', $edit, t('Save content type'));
+
+    // Check that poll node type customization shows up.
+    $this->drupalGet('node/add');
+    $this->assertText($description, t('Customized description found'));
+
+    // Disable poll and check that the node type gets disabled.
+    $this->drupalPost('admin/modules', $poll_disable, t('Save configuration'));
+    $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField();
+    $this->assertEqual($disabled, 1, t('Poll node type is disabled'));
+    $this->drupalGet('node/add');
+    $this->assertNoText('poll', t('poll type is not found on node/add'));
+
+    // Reenable poll and check that the customization survived the module
+    // disable.
+    $this->drupalPost('admin/modules', $poll_enable, t('Save configuration'));
+    $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField();
+    $this->assertNotIdentical($disabled, FALSE, t('Poll node type found in the database'));
+    $this->assertEqual($disabled, 0, t('Poll node type is not disabled'));
+    $this->drupalGet('node/add');
+    $this->assertText($description, t('Customized description found'));
+
+    // Disable and uninstall poll.
+    $this->drupalPost('admin/modules', $poll_disable, t('Save configuration'));
+    $edit = array('uninstall[poll]' => 'poll');
+    $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall'));
+    $this->drupalPost(NULL, array(), t('Uninstall'));
+    $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField();
+    $this->assertTrue($disabled, t('Poll node type is in the database and is disabled'));
+    $this->drupalGet('node/add');
+    $this->assertNoText('poll', t('poll type is no longer found on node/add'));
+
+    // Reenable poll and check that the customization survived the module
+    // uninstall.
+    $this->drupalPost('admin/modules', $poll_enable, t('Save configuration'));
+    $this->drupalGet('node/add');
+    $this->assertText($description, t('Customized description is found even after uninstall and reenable.'));
+  }
+}
+
 /**
  * Rebuild the node_access table.
  */