Last beta blocker. From: <> --- includes/common.inc | 4 +- modules/forum/forum.install | 42 ++++++++++--------- modules/node/content_types.inc | 3 + modules/node/node.install | 39 +++++++++++++++++- modules/node/node.module | 88 +++++++++++++++++++++++++--------------- modules/node/node.test | 73 +++++++++++++++++++++++++++++++++ 6 files changed, 194 insertions(+), 55 deletions(-) diff --git includes/common.inc includes/common.inc index 803b53c..ea463d5 100644 --- includes/common.inc +++ includes/common.inc @@ -6595,8 +6595,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 modules/forum/forum.install modules/forum/forum.install index f8c7d04..4e94959 100644 --- modules/forum/forum.install +++ 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 modules/node/content_types.inc modules/node/content_types.inc index ce74427..42b207b 100644 --- modules/node/content_types.inc +++ 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']->defining_module)) { + $type->defining_module = $form['#node_type']->defining_module; + } if ($op == t('Delete content type')) { $form_state['redirect'] = 'admin/structure/types/manage/' . str_replace('_', '-', $type->old_type) . '/delete'; diff --git modules/node/node.install modules/node/node.install index 23e959a..7db7e00 100644 --- modules/node/node.install +++ modules/node/node.install @@ -294,6 +294,12 @@ function node_schema() { 'length' => 255, 'not null' => TRUE, ), + 'defining_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,9 +451,33 @@ 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() { + db_add_field('node_type', 'defining_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') + ->expression('defining_module', 'module') + ->condition('module', $modules, 'IN') + ->execute(); + db_update('node_type') ->fields(array('module' => 'node_content')) ->condition('module', 'node') diff --git modules/node/node.module modules/node/node.module index a04ab10..3916a57 100644 --- modules/node/node.module +++ 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, + 'defining_module' => $type->defining_module, ); if ($is_existing) { @@ -620,14 +613,15 @@ function node_field_extra_fields() { * Deletes a node type from the database. * * @param $type - * The machine-readable name of the node type to be deleted. + * The machine-readable name of the node type or the node type object itself + * to be deleted. */ function node_type_delete($type) { - $info = node_type_get_type($type); + $info = is_string($type) ? node_type_get_type($type) : $type; db_delete('node_type') - ->condition('type', $type) + ->condition('type', $info->type) ->execute(); - field_attach_delete_bundle('node', $type); + field_attach_delete_bundle('node', $info->type); module_invoke_all('node_type_delete', $info); // Clear the node type cache. @@ -660,6 +654,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 +669,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]->defining_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 +762,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 +778,9 @@ function node_type_set_defaults($info = array()) { if (!$new_type->has_title) { $new_type->title_label = ''; } + if (empty($new_type->defining_module)) { + $new_type->defining_module = $new_type->base == 'node_content' ? 'node' : ''; + } $new_type->orig_type = isset($info['type']) ? $info['type'] : ''; return $new_type; diff --git modules/node/node.test modules/node/node.test index 32de8fe..2fc9f60 100644 --- modules/node/node.test +++ modules/node/node.test @@ -1149,6 +1149,79 @@ 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', + ); + } + + 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. */ class NodeAccessRebuildTestCase extends DrupalWebTestCase {