diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index be60e48..30198a3 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -388,9 +388,12 @@ function _node_extract_type($node) {
 }
 
 /**
- * Returns a list of all the available node types.
- *
- * This list can include types that are queued for addition or deletion.
+ * Returns a list of a site's known node types.
+
+ * Any node type added by hook_node_info() or node_type_save() that has not been
+ * explicitly removed by node_type_delete or direct database interaction will
+ * appear in this list. The list can include types that are queued for addition
+ * or deletion.
  * See _node_types_build() for details.
  *
  * @return
@@ -473,10 +476,8 @@ function node_type_get_name($node) {
 /**
  * Updates the database cache of node types.
  *
- * All new module-defined node types are saved to the database via a call to
- * node_type_save(), and obsolete ones are deleted via a call to
- * node_type_delete(). See _node_types_build() for an explanation of the new
- * and obsolete types.
+ * All new node types defined by hook_node_info() are saved to the database via
+ * a call to node_type_save().
  */
 function node_types_rebuild() {
   _node_types_build(TRUE);
@@ -497,7 +498,8 @@ function node_type_load($name) {
 }
 
 /**
- * Saves a node type to the database.
+ * Saves a node type to the database. Modules should not implement node types
+ * with this function, but should use hook_node_info() instead.
  *
  * @param $info
  *   The node type to save, as an object.
@@ -509,6 +511,18 @@ function node_type_save($info) {
   $existing_type = !empty($info->old_type) ? $info->old_type : $info->type;
   $is_existing = (bool) db_query_range('SELECT 1 FROM {node_type} WHERE type = :type', 0, 1, array(':type' => $existing_type))->fetchField();
   $type = node_type_set_defaults($info);
+  $map = array('%type' => $type->name);
+  if (!$type->custom && $type->base == 'node_content') {
+    // A modular node type with base==node_content does not behave like something
+    // owned by its module.
+    drupal_set_message(t('The content type %type will not be disabled with its parent module.', $map), 'warning');
+  } elseif (!$type->custom && $type->module == '') {
+    // content_types.inc uses node_hook() to build the `Content types' list,
+    // but when the module is disabled, the [base]_form vanishes if it was
+    // implemented by the same module. Under this condition, one can add content
+    // of the disabled module, but cannot manage the content type.
+    drupal_set_message(t('The modular content type %type must either use base==node_content or be implemented by hook_node_info().', $map), 'error');
+  }
 
   $fields = array(
     'type' => (string) $type->type,
@@ -669,30 +683,39 @@ function node_type_update_nodes($old_type, $type) {
     ->execute();
 }
 
-/**
- * Builds and returns the list of available node types.
- *
- * The list of types is built by invoking hook_node_info() on all modules and
- * comparing this information with the node types in the {node_type} table.
- * 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.
- *   - types: Associative array of node type objects, keyed by the type.
- *   Both of these arrays will include new types that have been defined by
- *   hook_node_info() implementations but not yet saved in the {node_type}
- *   table. These are indicated in the type object by $type->is_new being set
- *   to the value 1. These arrays will also include obsolete types: types that
- *   were previously defined by modules that have now been disabled, or for
- *   whatever reason are no longer being defined in hook_node_info()
- *   implementations, but are still in the database. These are indicated in the
- *   type object by $type->disabled being set to TRUE.
- */
+ /**
+  * Get the list of all node types that have not been deleted by a call to
+  * node_type_delete().
+  *
+  * This function provides the `node_type' table state as it would appear after a
+  * rebuild, actually rebuilding the table for $rebuild==TRUE. The returned node
+  * type's `disabled' property indicates if the node type's implementing module
+  * has been disabled, however, any node type with a `base' value of
+  * `node_content' always remains non-disabled (making Node Content a
+  * poor name choice for a module).
+  *
+  * @param $rebuild
+  *  TRUE to rebuild `node_types' table of the database.
+  * @return
+  *   An object with two properties:
+  *   - names: Associative array of the names corresponding to active or disabled
+  *     node types, keyed by node type. This property does not necessarily reflect
+  *     the state of the `node_type' table unless $rebuild==TRUE. The property is
+  *     a thin version of the `types' property. See below for exactly what
+  *     membership in this array means.
+  *   - types: Associative array of node type objects, keyed by node type.
+  *     Like the `names' array, this data structure does not necessarily reflect
+  *     the state of the `node_type' table. The node type objects contain fresher
+  *     data.
+  *     - disabled: If `base==node_content', then this property does not vary from
+  *       0. Otherwise, the $type->disabled flag takes a value of 0 for any type
+  *       in the `node_type' table whose module is enabled, or it takes a value of
+  *       1 if its module is disabled (or possibly uninstalled).
+  *     - is_new: The $type->is_new flag does not appear in the `node_type' table.
+  *       The property gets set to 1 for node types implemented by
+  *       hook_node_info() that did not appear in the `node_type' table at
+  *       _node_types_build() entry. The property is left unset otherwise.
+  */
 function _node_types_build($rebuild = FALSE) {
   $cid = 'node_types:' . $GLOBALS['language_interface']->langcode;
 
@@ -707,65 +730,109 @@ function _node_types_build($rebuild = FALSE) {
     }
   }
 
-  $_node_types = (object) array('types' => array(), 'names' => array());
+  // Gather node types from the hook_node_info() implementations of active
+  // modules.
+  $active_types = (object) array('types' => array(), 'names' => array());
 
   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'];
+    foreach (module_invoke($module, 'node_info') as $type => $node_info) {
+      $node_info['type'] = $type;
+      $active_types->types[$type] = node_type_set_defaults($node_info);
+      $active_types->types[$type]->module = $module;
+      // active_type not in node_type table => active_type is new, so clobber
+      // this default value if the type is found in the database.
+      $active_types->types[$type]->is_new = 1;
+      // active_type is new => active_type is not disabled, so be consistent
+      // with other defaults for now
+      $active_types->types[$type]->disabled = 0;
+      // attach flag to indicate whether the type needs to be (re)written to the
+      // database
+      if ($rebuild) {
+         $active_types->types[$type]->new_state = 1;
+      }
+
+      // Copy subset of the types property's data to the name property
+      $active_types->names[$type] = $active_types->types[$type]->name;
     }
   }
-  $query = db_select('node_type', 'nt')
+
+  // Gather node types from the node_type table (and I have no clue what the
+  // translatable tag does)
+  $db_types = db_select('node_type', 'nt')
     ->addTag('translatable')
     ->addTag('node_type_access')
     ->fields('nt')
-    ->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($_node_types->types[$type_db])) {
-      $type_object->disabled = TRUE;
-    }
-    if (isset($_node_types->types[$type_db])) {
-      $type_object->disabled = FALSE;
+    ->orderBy('nt.type', 'ASC')
+    ->execute();
+  // Could condition on base!=node_content and/or custom=0, but then a second
+  // query and loop would be necessary to add these results verbatim to the
+  // data structure
+
+  // Reconcile node_type table against the enabled modules list. Any node type
+  // with base!=node_content inherits its disabled state from that of its 
+  // module.
+  $enabled_modules = module_list();
+  foreach ($db_types as $db_type) {
+    // Clobber any type information from hook_node_info() with that from the
+    // database to preserve any modifications
+    $is_hook_implemented = isset($active_types->types[$db_type->type]);
+    $active_types->types[$db_type->type] = $db_type;
+    $type = $active_types->types[$db_type->type];
+    if ($is_hook_implemented) {
+      $new_state = ($type->disabled != 0);
+      $type->disabled = 0;
+    } else {
+      // A hook_node_info() was unavailable for the current iterant from the
+      // database, so enable or disable the iterant depending on its module's
+      // state
+      if ($type->module=='' || (isset($type->base) && $type->base=='node_content')) {
+        // Special case: always enabled
+        $new_state = 0;
+        $type->disabled = 0;
+      } elseif (isset($enabled_modules[$db_type->module])) {
+        // The module is enabled
+        $new_state = ($type->disabled != 0);
+        $type->disabled = 0;
+      } else {
+        // The module is disabled, uninstalled, or missing entirely
+        $new_state = ($type->disabled != 1);
+        $type->disabled = 1;
+      }
     }
-    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_db != $type_object->orig_type) {
-        unset($_node_types->types[$type_object->orig_type]);
-        unset($_node_types->names[$type_object->orig_type]);
-      }
+    // Don't litter the return data structure with internal data
+    if ($rebuild) {
+      $active_types->types[$db_type->type]->new_state = $new_state;
     }
-    $_node_types->types[$type_db]->disabled = $type_object->disabled;
-    $_node_types->types[$type_db]->disabled_changed = $disabled != $type_object->disabled;
+
+    // Copy data to names array.
+    // This was inside the hook_node_info()-unavailable-case above, since the
+    // name data from the hook_node_info() call should be fine to return, but
+    // changes in hook_node_info() may not have propagated into the database yet
+    // (and a rebuild won't fix a changed name, so the spec comes into play:
+    // return what the node type table would look like after a rebuild).
+    $active_types->names[$db_type->type] =
+      $active_types->types[$db_type->type]->name;
   }
 
+  // Save new node types if a rebuild was requested. Needs a fresh loop, since
+  // the loop above doesn't touch new hook_node_info() implementors.
   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);
+    foreach ($active_types->types as $type) {
+      if (isset($type->is_new) || isset($type->new_state)) {
+        node_type_save($type);
+        // explicitly remove the new_state flag or someone will start depending
+        // on unspec'ed behavior
+        unset($type->new_state);
       }
     }
   }
 
-  asort($_node_types->names);
+  asort($active_types->names);
 
-  cache()->set($cid, $_node_types);
+  cache()->set($cid, $active_types);
 
-  return $_node_types;
+  return $active_types;
 }
 
 /**
@@ -2086,10 +2153,9 @@ function node_menu() {
     'type' => MENU_CALLBACK,
   );
   // @todo Remove this loop when we have a 'description callback' property.
-  // Resets the internal static cache of _node_types_build() and forces a
-  // rebuild of the node type information.
-  node_type_cache_reset();
-  foreach (node_type_get_types() as $type) {
+  $enabled_types = array_filter(node_type_get_types(),
+                                create_function('$t','return !$t->disabled;'));
+  foreach ($enabled_types as $type) {
     $type_url_str = str_replace('_', '-', $type->type);
     $items['node/add/' . $type_url_str] = array(
       'title' => $type->name,
@@ -3723,7 +3789,8 @@ function _node_access_rebuild_batch_finished($success, $results, $operations) {
  * Implements hook_form().
  */
 function node_content_form($node, $form_state) {
-  // It is impossible to define a content type without implementing hook_form()
+  // It is impossible to define a content type with base!=node_content without
+  // also implementing hook_form()
   // @todo: remove this requirement.
   $form = array();
   $type = node_type_get_type($node);
