diff --git a/core/modules/node/node.module b/core/modules/node/node.module index b5ed7e8..8a96347 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -388,14 +388,17 @@ 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. - * See _node_types_build() for details. + * 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. * * @return * An array of node types, as objects, keyed by the type. * + * @see _node_types_build() * @see node_type_get_type() */ function node_type_get_types() { @@ -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); @@ -499,6 +500,9 @@ function node_type_load($name) { /** * 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 +513,19 @@ 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 module provided node type whose 'base' key is 'node_content' will not + // be automatically disabled on disabling of it's parent module. + drupal_set_message(t('The content type %type will not be disabled with its parent module.', $map), 'warning'); + } + elseif (!$type->custom && $type->module == '') { + // The content types list is built by content_types.inc using node_hook(). + // 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 content type %type must either have 'base' key set to 'node_content' or be defined via hook_node_info()."', $map), 'error'); + } $fields = array( 'type' => (string) $type->type, @@ -669,30 +686,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. - */ + /** + * Gets a list of all node types that were not deleted by node_type_delete(). + * + * This function provides the {node_type} table state as it would appear after + * a rebuild, actually rebuilding the table when $rebuild is 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 + * bad name for a module. + * + * @param $rebuild + * TRUE to rebuild {node_type} 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 is equal to 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 {node_type}.base is equal to 'node_content', then this + * property does not vary from FALSE. 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 TRUE 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 TRUE 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 +733,99 @@ 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; + + // Overwrite the defaults of is_new and disabled for the new active type. + $active_types->types[$type]->is_new = TRUE; + $active_types->types[$type]->disabled = FALSE; + // When rebuilding, mark this type as new, to ensure it overwrites any + // existing values in the database. + if ($rebuild) { + $active_types->types[$type]->new_state = TRUE; + } + + // Copy data to the names array. + $active_types->names[$type] = $active_types->types[$type]->name; } } - $query = db_select('node_type', 'nt') + + // Gather node types from the {node_type} table. + $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; + ->orderBy('nt.type', 'ASC') + ->execute(); + // Sync the {node_type} table against the enabled modules list. Any node type + // with a base other than 'node_content' inherits its disabled state from that + // of its module. + $enabled_modules = module_list(); + foreach ($db_types as $db_type) { + // Remove any type information from hook_node_info() 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; + $type->disabled = FALSE; } - if (isset($_node_types->types[$type_db])) { - $type_object->disabled = FALSE; + else { + // A hook_node_info() was unavailable for the current occurence from the + // database, so enable or disable the occurence depending on its module's + // state. + if ($type->module == '' || (isset($type->base) && $type->base == 'node_content')) { + // Special case: always enabled. + $new_state = FALSE; + $type->disabled = FALSE; + } + elseif (isset($enabled_modules[$db_type->module])) { + // The module is enabled. + $new_state = $type->disabled; + $type->disabled = FALSE; + } + else { + // The module is disabled, uninstalled, or missing entirely. + $new_state = !$type->disabled; + $type->disabled = TRUE; + } } - 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. + $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 unspecified 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; } /** @@ -1256,14 +1316,14 @@ function node_delete_multiple($nids) { // Remove this node from the search index if needed. // This code is implemented in node module rather than in search module, - // because node module is implementing search module's API, not the other - // way around. + // because node module is implementing search module's API, not the + // other way around. if (module_exists('search')) { search_reindex($nid, 'node'); } } - // Delete after calling hooks so that they can query node tables as needed. + // Delete after calling hooks so they can query node tables as needed. db_delete('node') ->condition('nid', $nids, 'IN') ->execute(); @@ -2086,10 +2146,10 @@ 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) { + if ($type->disabled) { + continue; + } $type_url_str = str_replace('_', '-', $type->type); $items['node/add/' . $type_url_str] = array( 'title' => $type->name, @@ -3719,7 +3779,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 a base that is not + // node_content without also implementing hook_form(). // @todo: remove this requirement. $form = array(); $type = node_type_get_type($node); diff --git a/core/modules/node/node.test b/core/modules/node/node.test index 1dbcaf3..e1141ae 100644 --- a/core/modules/node/node.test +++ b/core/modules/node/node.test @@ -521,7 +521,7 @@ class NodeCreationTestCase extends NodeWebTestCase { } /** - * Create a page node and verify that a transaction rolls back the failed creation + * Create page node and verify that transactions roll back failed creations. */ function testFailedPageCreation() { // Create a node. @@ -1447,6 +1447,32 @@ class NodeTypeTestCase extends NodeWebTestCase { $this->assertTrue(isset($types[$type]->disabled) && empty($types[$type]->disabled), t('%type type is enabled.', array('%type' => $type))); } } + + /** + * Tests handling of node type types that are undefined. + */ + public function testNodeTypeDisabled() { + $test_type = new stdClass(); + $test_type->base = 'not_node_content'; + $test_type->name = t('Test Type'); + $test_type->type = 'test'; + + node_type_save($test_type); + $updated = db_update('node_type') + ->fields(array('disabled' => TRUE)) + ->condition('type', $test_type->type) + ->execute(); + $this->assertTrue(($updated == TRUE), 'Successfully disabled custom content type.'); + $info = node_type_get_type($test_type->type); + $this->assertTrue($info !== FALSE, 'Return data from node_type_get_type is valid for a disabled content type with a base that is not node_content.'); + + $target_type = 'book'; + module_enable(array($target_type), FALSE); + module_disable(array($target_type)); + $info = node_type_get_type($target_type); + $this->assertTrue($info !== FALSE, 'Return data from node_type_get_type is valid for a disabled content type with base of node_content.'); + + } } /**