diff --git a/core/modules/book/book.install b/core/modules/book/book.install index 797902a..79315f4 100644 --- a/core/modules/book/book.install +++ b/core/modules/book/book.install @@ -43,7 +43,7 @@ function _book_install_type_create() { node_type_save($book_node_type); node_add_body_field($book_node_type); // Default to not promoted. - variable_set('node_options_book', array('status')); + config('node.settings.book')->set('options', array('status')); } /** diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 9ed9a96..63da4c9 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -114,8 +114,8 @@ function comment_entity_info(&$info) { // of local tasks. Note that the paths use a different placeholder name // and thus a different menu loader callback, so that Field UI page // callbacks get a comment bundle name from the node type in the URL. - // See comment_node_type_load() and comment_menu_alter(). - 'path' => 'admin/structure/types/manage/%comment_node_type/comment', + // See comment_menu_node_type_load() and comment_menu_alter(). + 'path' => 'admin/structure/types/manage/%comment_menu_node_type/comment', 'bundle argument' => 4, 'real path' => 'admin/structure/types/manage/' . $type . '/comment', 'access arguments' => array('administer content types'), @@ -137,7 +137,7 @@ function comment_entity_info(&$info) { * * @see comment_menu_alter() */ -function comment_node_type_load($name) { +function comment_menu_node_type_load($name) { if ($type = node_type_load($name)) { return 'comment_node_' . $type->type; } @@ -291,10 +291,10 @@ function comment_menu_alter(&$items) { // Adjust the Field UI tabs on admin/structure/types/manage/[node-type]. // See comment_entity_info(). - $items['admin/structure/types/manage/%comment_node_type/comment/fields']['title'] = 'Comment fields'; - $items['admin/structure/types/manage/%comment_node_type/comment/fields']['weight'] = 3; - $items['admin/structure/types/manage/%comment_node_type/comment/display']['title'] = 'Comment display'; - $items['admin/structure/types/manage/%comment_node_type/comment/display']['weight'] = 4; + $items['admin/structure/types/manage/%comment_menu_node_type/comment/fields']['title'] = 'Comment fields'; + $items['admin/structure/types/manage/%comment_menu_node_type/comment/fields']['weight'] = 3; + $items['admin/structure/types/manage/%comment_menu_node_type/comment/display']['title'] = 'Comment display'; + $items['admin/structure/types/manage/%comment_menu_node_type/comment/display']['weight'] = 4; } /** @@ -1022,7 +1022,7 @@ function comment_form_node_type_form_alter(&$form, $form_state) { $form['comment']['comment'] = array( '#type' => 'select', '#title' => t('Default comment setting for new content'), - '#default_value' => variable_get('comment_' . $form_state['node_type']->type, COMMENT_NODE_OPEN), + '#default_value' => variable_get('comment_' . $form['#node_type']->type, COMMENT_NODE_OPEN), '#options' => array( COMMENT_NODE_OPEN => t('Open'), COMMENT_NODE_CLOSED => t('Closed'), @@ -1032,19 +1032,19 @@ function comment_form_node_type_form_alter(&$form, $form_state) { $form['comment']['comment_default_mode'] = array( '#type' => 'checkbox', '#title' => t('Threading'), - '#default_value' => variable_get('comment_default_mode_' . $form_state['node_type']->type, COMMENT_MODE_THREADED), + '#default_value' => variable_get('comment_default_mode_' . $form['#node_type']->type, COMMENT_MODE_THREADED), '#description' => t('Show comment replies in a threaded list.'), ); $form['comment']['comment_default_per_page'] = array( '#type' => 'select', '#title' => t('Comments per page'), - '#default_value' => variable_get('comment_default_per_page_' . $form_state['node_type']->type, 50), + '#default_value' => variable_get('comment_default_per_page_' . $form['#node_type']->type, 50), '#options' => _comment_per_page(), ); $form['comment']['comment_anonymous'] = array( '#type' => 'select', '#title' => t('Anonymous commenting'), - '#default_value' => variable_get('comment_anonymous_' . $form_state['node_type']->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT), + '#default_value' => variable_get('comment_anonymous_' . $form['#node_type']->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT), '#options' => array( COMMENT_ANONYMOUS_MAYNOT_CONTACT => t('Anonymous posters may not enter their contact information'), COMMENT_ANONYMOUS_MAY_CONTACT => t('Anonymous posters may leave their contact information'), @@ -1055,17 +1055,17 @@ function comment_form_node_type_form_alter(&$form, $form_state) { $form['comment']['comment_subject_field'] = array( '#type' => 'checkbox', '#title' => t('Allow comment title'), - '#default_value' => variable_get('comment_subject_field_' . $form_state['node_type']->type, 1), + '#default_value' => variable_get('comment_subject_field_' . $form['#node_type']->type, 1), ); $form['comment']['comment_form_location'] = array( '#type' => 'checkbox', '#title' => t('Show reply form on the same page as comments'), - '#default_value' => variable_get('comment_form_location_' . $form_state['node_type']->type, COMMENT_FORM_BELOW), + '#default_value' => variable_get('comment_form_location_' . $form['#node_type']->type, COMMENT_FORM_BELOW), ); $form['comment']['comment_preview'] = array( '#type' => 'radios', '#title' => t('Preview comment'), - '#default_value' => variable_get('comment_preview_' . $form_state['node_type']->type, DRUPAL_OPTIONAL), + '#default_value' => variable_get('comment_preview_' . $form['#node_type']->type, DRUPAL_OPTIONAL), '#options' => array( DRUPAL_DISABLED => t('Disabled'), DRUPAL_OPTIONAL => t('Optional'), diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php index ab03d41..caa3661 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php @@ -107,6 +107,7 @@ function testCommentEnable() { * Tests that comment module works correctly with plain text format. */ function testCommentFormat() { + menu_router_rebuild(); // Disable text processing for comments. $this->drupalLogin($this->admin_user); $edit = array('instance[settings][text_processing]' => 0); diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module index ab0e841..26fa641 100644 --- a/core/modules/field_ui/field_ui.module +++ b/core/modules/field_ui/field_ui.module @@ -76,7 +76,7 @@ function field_ui_menu() { // Extract path information from the bundle. $path = $bundle_info['admin']['path']; // Different bundles can appear on the same path (e.g. %node_type and - // %comment_node_type). To allow field_ui_instance_load() to extract + // %comment_menu_node_type). To allow field_ui_instance_load() to extract // the actual bundle object from the translated menu router path // arguments, we need to identify the argument position of the bundle // name string ('bundle argument') and pass that position to the menu @@ -216,7 +216,7 @@ function field_ui_instance_load($field_name, $entity_type, $bundle_name, $bundle // The menu router path to manage fields of an entity can be shared among // multiple bundles. For example: // - admin/structure/types/manage/%node_type/fields/%field_ui_instance - // - admin/structure/types/manage/%comment_node_type/fields/%field_ui_instance + // - admin/structure/types/manage/%comment_menu_node_type/fields/%field_ui_instance // The menu system will automatically load the correct bundle depending on the // actual path arguments, but this menu loader function only receives the node // type string as $bundle_name, which is not the bundle name for comments. @@ -368,7 +368,7 @@ function field_ui_inactive_instances($entity_type, $bundle_name = NULL) { */ function field_ui_form_node_type_form_alter(&$form, $form_state) { // We want to display the button only on add page. - if (empty($form_state['node_type']->type)) { + if (empty($form['#node_type']->type)) { $form['actions']['save_continue'] = array( '#type' => 'submit', '#value' => t('Save and manage fields'), diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install index b1e63de..96cc914 100644 --- a/core/modules/forum/forum.install +++ b/core/modules/forum/forum.install @@ -15,7 +15,7 @@ function forum_install() { // options set (for example, they are not promoted to the front page). // @todo Convert to default module configuration, once Node module's content // types are converted. - variable_set('node_options_forum', array('status')); + config('node.settings.forum')->set('options', array('status')); } /** @@ -121,7 +121,7 @@ function forum_uninstall() { // Load the dependent Taxonomy module, in case it has been disabled. drupal_load('module', 'taxonomy'); - variable_del('node_options_forum'); + config('node.settings.forum')->delete(); field_delete_field('taxonomy_forums'); // Purge field data now to allow taxonomy module to be uninstalled diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php index 7b0bef7..b3d58bf 100644 --- a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php +++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php @@ -195,7 +195,7 @@ function testForum() { // Test loading multiple forum nodes on the front page. $this->drupalLogin($this->drupalCreateUser(array('administer content types', 'create forum content', 'post comments'))); - $this->drupalPost('admin/structure/types/manage/forum', array('node_options[promote]' => 'promote'), t('Save content type')); + $this->drupalPost('admin/structure/types/manage/forum', array('options[promote]' => 'promote'), t('Save content type')); $this->createForumTopic($this->forum, FALSE); $this->createForumTopic($this->forum, FALSE); $this->drupalGet('node'); diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module index 463796d..3d4a360 100644 --- a/core/modules/menu/menu.module +++ b/core/modules/menu/menu.module @@ -679,7 +679,7 @@ function menu_node_submit(Node $node, $form, $form_state) { */ function menu_form_node_type_form_alter(&$form, $form_state) { $menu_options = menu_get_menus(); - $type = $form_state['node_type']; + $type = $form['#node_type']; $form['menu'] = array( '#type' => 'details', '#title' => t('Menu settings'), diff --git a/core/modules/node/content_types.inc b/core/modules/node/content_types.inc index 61e3b79..fc3844e 100644 --- a/core/modules/node/content_types.inc +++ b/core/modules/node/content_types.inc @@ -5,8 +5,6 @@ * Content type editing user interface. */ -use Drupal\node\Plugin\Core\Entity\NodeType; - /** * Page callback: Displays the content type admin overview page. * @@ -109,24 +107,21 @@ function theme_node_admin_overview($variables) { * @see node_type_form_submit() * @ingroup forms */ -function node_type_form($form, &$form_state, NodeType $type = NULL) { - // Standard procedure for handling the entity argument in entity forms, taking - // potential form caching and rebuilds properly into account. - // @see http://drupal.org/node/1499596 - if (!isset($form_state['node_type'])) { - if (!isset($type)) { - $type = entity_create('node_type', array('custom' => 1, 'locked' => 0)); - } - $form_state['node_type'] = $type; - } - else { - $type = $form_state['node_type']; +function node_type_form($form, &$form_state, $type = NULL) { + if (!isset($type->type)) { + // This is a new type. Node module managed types are custom and unlocked. + $type = node_type_set_defaults(array('custom' => 1, 'locked' => 0)); } + $config = config('node.settings.' . $type->type); + + // Make the type object available to implementations of hook_form_alter. + $form['#node_type'] = $type; + $form['name'] = array( '#title' => t('Name'), '#type' => 'textfield', - '#default_value' => $type->label(), + '#default_value' => $type->name, '#description' => t('The human-readable name of this content type. This text will be displayed as part of the list on the Add new content page. It is recommended that this name begin with a capital letter and contain only letters, numbers, and spaces. This name must be unique.'), '#required' => TRUE, '#size' => 30, @@ -134,7 +129,7 @@ function node_type_form($form, &$form_state, NodeType $type = NULL) { $form['type'] = array( '#type' => 'machine_name', - '#default_value' => $type->id(), + '#default_value' => $type->type, '#maxlength' => 32, '#disabled' => $type->locked, '#machine_name' => array( @@ -179,10 +174,10 @@ function node_type_form($form, &$form_state, NodeType $type = NULL) { $form['submission']['title_label']['#description'] = t('This content type does not have a title field.'); $form['submission']['title_label']['#required'] = FALSE; } - $form['submission']['node_preview'] = array( + $form['submission']['preview'] = array( '#type' => 'radios', '#title' => t('Preview before submitting'), - '#default_value' => variable_get('node_preview_' . $type->type, DRUPAL_OPTIONAL), + '#default_value' => $config->get('preview') ?: DRUPAL_OPTIONAL, '#options' => array( DRUPAL_DISABLED => t('Disabled'), DRUPAL_OPTIONAL => t('Optional'), @@ -202,9 +197,9 @@ function node_type_form($form, &$form_state, NodeType $type = NULL) { '#collapsed' => TRUE, '#group' => 'additional_settings', ); - $form['workflow']['node_options'] = array('#type' => 'checkboxes', + $form['workflow']['options'] = array('#type' => 'checkboxes', '#title' => t('Default options'), - '#default_value' => variable_get('node_options_' . $type->id(), array('status', 'promote')), + '#default_value' => $config->get('options') ?: array('status', 'promote'), '#options' => array( 'status' => t('Published'), 'promote' => t('Promoted to front page'), @@ -222,12 +217,12 @@ function node_type_form($form, &$form_state, NodeType $type = NULL) { '#group' => 'additional_settings', ); - $language_configuration = language_get_default_configuration('node', $type->id()); + $language_configuration = language_get_default_configuration('node', $type->type); $form['language']['language_configuration'] = array( '#type' => 'language_configuration', '#entity_information' => array( 'entity_type' => 'node', - 'bundle' => $type->id(), + 'bundle' => $type->type, ), '#default_value' => $language_configuration, ); @@ -241,12 +236,20 @@ function node_type_form($form, &$form_state, NodeType $type = NULL) { '#collapsed' => TRUE, '#group' => 'additional_settings', ); - $form['display']['node_submitted'] = array( + $form['display']['submitted'] = array( '#type' => 'checkbox', '#title' => t('Display author and date information.'), - '#default_value' => variable_get('node_submitted_' . $type->id(), TRUE), + '#default_value' => !is_null($config->get('submitted')) ? $config->get('submitted') : TRUE, '#description' => t('Author username and publish date will be displayed.'), ); + $form['old_type'] = array( + '#type' => 'value', + '#value' => $type->type, + ); + $form['orig_type'] = array( + '#type' => 'value', + '#value' => isset($type->orig_type) ? $type->orig_type : '', + ); $form['base'] = array( '#type' => 'value', '#value' => $type->base, @@ -268,14 +271,18 @@ function node_type_form($form, &$form_state, NodeType $type = NULL) { $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Save content type'), - ); - $form['actions']['delete'] = array( - '#type' => 'submit', - '#value' => t('Delete content type'), - '#access' => $type->custom && $type->id(), - '#submit' => array('node_type_form_delete_submit'), + '#weight' => 40, ); + if ($type->custom) { + if (!empty($type->type)) { + $form['actions']['delete'] = array( + '#type' => 'submit', + '#value' => t('Delete content type'), + '#weight' => 45, + ); + } + } $form['#submit'][] = 'node_type_form_submit'; return $form; @@ -300,10 +307,27 @@ function _node_characters($length) { * @see node_type_form_submit() */ function node_type_form_validate($form, &$form_state) { - // 'theme' conflicts with theme_node_form(). - // '0' is invalid, since elsewhere we check it using empty(). - if (in_array(trim($form_state['values']['type']), array('0', 'theme'))) { - form_set_error('type', t("Invalid machine-readable name. Enter a name other than %invalid.", array('%invalid' => $form_state['values']['type']))); + $type = new stdClass(); + $type->type = trim($form_state['values']['type']); + $type->name = trim($form_state['values']['name']); + + // Work out what the type was before the user submitted this form + $old_type = trim($form_state['values']['old_type']); + + $types = node_type_get_names(); + + if (!$form_state['values']['locked']) { + // 'theme' conflicts with theme_node_form(). + // '0' is invalid, since elsewhere we check it using empty(). + if (in_array($type->type, array('0', 'theme'))) { + form_set_error('type', t("Invalid machine-readable name. Enter a name other than %invalid.", array('%invalid' => $type->type))); + } + } + + $names = array_flip($types); + + if (isset($names[$type->name]) && $names[$type->name] != $old_type) { + form_set_error('name', t('The human-readable name %name is already taken.', array('%name' => $type->name))); } } @@ -313,28 +337,49 @@ function node_type_form_validate($form, &$form_state) { * @see node_type_form_validate() */ function node_type_form_submit($form, &$form_state) { - form_state_values_clean($form_state); + $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : ''; + + $type = node_type_set_defaults(); + + $type->type = trim($form_state['values']['type']); + $type->name = trim($form_state['values']['name']); + $type->orig_type = trim($form_state['values']['orig_type']); + $type->old_type = isset($form_state['values']['old_type']) ? $form_state['values']['old_type'] : $type->type; + + $type->description = $form_state['values']['description']; + $type->help = $form_state['values']['help']; + $type->title_label = $form_state['values']['title_label']; + // title_label is required in core; has_title will always be true, unless a + // module alters the title field. + $type->has_title = ($type->title_label != ''); + + $type->base = !empty($form_state['values']['base']) ? $form_state['values']['base'] : 'node_content'; + $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; + } - $type = $form_state['node_type']; - // @todo Retain the trim()s or not? -// $type = entity_create('node_type', array( -// 'type' => trim($form_state['values']['type']), -// 'name' => trim($form_state['values']['name']), -// )); - entity_form_submit_build_entity('node_type', $type, $form, $form_state); + if ($op == t('Delete content type')) { + $form_state['redirect'] = 'admin/structure/types/manage/' . $type->old_type . '/delete'; + return; + } $variables = $form_state['values']; // Remove everything that's been saved already - whatever's left is assumed // to be a persistent variable. - // @todo foreach ($variables as $key => $value) { if (isset($type->$key)) { unset($variables[$key]); } } + unset($variables['form_token'], $variables['op'], $variables['submit'], $variables['delete'], $variables['reset'], $variables['form_id'], $variables['form_build_id']); + // Save or reset persistent variable values. + $node_type_settings = config('node.settings.' . $type->type); foreach ($variables as $key => $value) { $variable_new = $key . '_' . $type->type; $variable_old = $key . '_' . $type->old_type; @@ -342,39 +387,39 @@ function node_type_form_submit($form, &$form_state) { if (is_array($value)) { $value = array_keys(array_filter($value)); } + $node_type_settings->set($key, $value); + // @todo remove when comment and menu variables are converted. variable_set($variable_new, $value); - if ($variable_new != $variable_old) { variable_del($variable_old); } } + $node_type_settings->save(); + + // Remove old settings if needed. + if ($type->type != $type->old_type) { + config('node.settings.' . $type->old_type)->delete(); + } // Saving the content type after saving the variables allows modules to act // on those variables via hook_node_type_insert(). - $status = $type->save(); + $status = node_type_save($type); node_types_rebuild(); menu_router_rebuild(); - $t_args = array('%name' => $type->label()); + $t_args = array('%name' => $type->name); if ($status == SAVED_UPDATED) { drupal_set_message(t('The content type %name has been updated.', $t_args)); } - else { + elseif ($status == SAVED_NEW) { node_add_body_field($type); drupal_set_message(t('The content type %name has been added.', $t_args)); watchdog('node', 'Added content type %name.', $t_args, WATCHDOG_NOTICE, l(t('view'), 'admin/structure/types')); } $form_state['redirect'] = 'admin/structure/types'; -} - -/** - * Form submission handler for node_type_form() delete action. - */ -function node_type_form_delete_submit($form, &$form_state) { - $type = $form_state['node_type']; - $form_state['redirect'] = 'admin/structure/types/manage/' . $type->id() . '/delete'; + return; } /** diff --git a/core/modules/node/content_types.js b/core/modules/node/content_types.js index 40d6422..75409a5 100644 --- a/core/modules/node/content_types.js +++ b/core/modules/node/content_types.js @@ -18,10 +18,10 @@ Drupal.behaviors.contentTypes = { }); $context.find('#edit-workflow').drupalSetSummary(function(context) { var vals = []; - $(context).find("input[name^='node_options']:checked").parent().each(function() { + $(context).find("input[name^='options']:checked").parent().each(function() { vals.push(Drupal.checkPlain($(this).text())); }); - if (!$(context).find('#edit-node-options-status').is(':checked')) { + if (!$(context).find('#edit-options-status').is(':checked')) { vals.unshift(Drupal.t('Not published')); } return vals.join(', '); @@ -43,7 +43,7 @@ Drupal.behaviors.contentTypes = { $context.find('input:checked').next('label').each(function() { vals.push(Drupal.checkPlain($(this).text())); }); - if (!$context.find('#edit-node-submitted').is(':checked')) { + if (!$context.find('#edit-submitted').is(':checked')) { vals.unshift(Drupal.t("Don't display post information")); } return vals.join(', '); diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php index 9bada6d..1eef45d 100644 --- a/core/modules/node/lib/Drupal/node/NodeFormController.php +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -27,7 +27,10 @@ class NodeFormController extends EntityFormController { */ protected function prepareEntity(EntityInterface $node) { // Set up default values, if required. - $node_options = variable_get('node_options_' . $node->type, array('status', 'promote')); + $node_options = config('node.settings.' . $node->type)->get('options'); + if (is_null($node_options)) { + $node_options = array('status', 'promote'); + } // If this is a new node, fill in the default values. if (!isset($node->nid) || isset($node->is_new)) { foreach (array('status', 'promote', 'sticky') as $key) { @@ -251,7 +254,7 @@ public function form(array $form, array &$form_state, EntityInterface $node) { protected function actions(array $form, array &$form_state) { $element = parent::actions($form, $form_state); $node = $this->getEntity($form_state); - $preview_mode = variable_get('node_preview_' . $node->type, DRUPAL_OPTIONAL); + $preview_mode = config('node.settings.' . $node->type)->get('preview') ?: DRUPAL_OPTIONAL; $element['preview'] = array( '#access' => $preview_mode != DRUPAL_DISABLED, diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/NodeType.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/NodeType.php index 17be06e..067e888 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/NodeType.php +++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/NodeType.php @@ -2,7 +2,7 @@ /** * @file - * Contains Drupal\node\Plugin\Core\Entity\NodeType. + * Definition of Drupal\node\Plugin\Core\Entity\NodeType. */ namespace Drupal\node\Plugin\Core\Entity; @@ -12,7 +12,7 @@ use Drupal\Core\Annotation\Translation; /** - * Defines the node type configuration entity. + * Defines the Node type configuration entity. * * @Plugin( * id = "node_type", @@ -30,106 +30,107 @@ class NodeType extends ConfigEntityBase { /** - * The node type machine name. + * The content type UUID. * * @var string - * - * @todo Rename into $id. */ - public $type; + public $uuid; /** - * The UUID of the node type. + * The content type name (machine name) as specified by theme or module. * * @var string */ - public $uuid; + public $type; /** - * The human-readable name of the node type. + * The content type label. * * @var string - * - * @todo Rename into $label. */ public $name; /** - * The base string used to construct callbacks corresponding to this node type. + * The base string used to construct callbacks corresponding + * to this node type. * * @var string */ - public $base = 'node_content'; + public $base; /** - * The module that owns this node type. + * The module defining this node type. * * @var string */ public $module; /** - * The node type description. + * A brief description of this type. * * @var string */ public $description; /** - * The user-facing help text for this node type. + * Help information shown to the user when creating a node of this type. * * @var string */ public $help; /** - * Indicates whether this node type has been created by a user. - * - * A value of FALSE means that the node type cannot be deleted from the user - * interface. + * Boolean indicating whether this type uses the node.title field. * - * @var bool + * @var string */ - public $custom; + public $has_title; /** - * Indicates whether the node type has been modified. + * The label displayed for the title field on the edit form. * - * @var bool - * - * @todo D8: Remove this property. + * @var string */ - public $modified = TRUE; + public $title_label; /** - * Indicates whether the machine name cannot be changed. + * A boolean indicating whether this type is defined by a module + * (FALSE) or by a user via Add content type (TRUE). * - * @var bool + * @var string */ - public $locked; + public $custom; /** - * Indicates whether owning module is disabled. + * A boolean indicating whether this type has been modified by an + * administrator; currently not used in any way. * - * @var bool + * @var string */ - public $disabled; + public $modified; /** - * Indicates whether the node type has a label. + * A boolean indicating whether the administrator can change the + * machine name of this type. * - * @var bool + * @var string + */ + public $locked; + + /** + * A boolean indicating whether the node type is disabled. * - * @todo Remove this flag; all configurables require a label. + * @var string */ - public $has_title = TRUE; + public $disabled; /** - * The form label to use for a node's title in the user interface. + * The original machine-readable name of this node type. This may be + * different from the current type name if the locked field is 0. * * @var string */ - public $title_label; + public $orig_type; /** * Overrides Drupal\Core\Entity\Entity::id(). @@ -137,21 +138,4 @@ class NodeType extends ConfigEntityBase { public function id() { return $this->type; } - - /** - * Overrides Drupal\Core\Config\Entity\ConfigEntityBase::label(). - */ - public function label() { - return $this->name; - } - - /** - * Overrides Drupal\entity\Entity::save(). - */ - public function save() { - // title_label is required in core; has_title will always be true, unless a - // module alters the title field. - $this->has_title = ($this->title_label !== ''); - return parent::save(); - } } diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php index 36baad2..2499aef 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php @@ -107,7 +107,7 @@ function testUnpublishedNodeCreation() { config('system.site')->set('page.front', 'test-page')->save(); // Set "Basic page" content type to be unpublished by default. - variable_set('node_options_page', array()); + config('node.settings.page')->set('options', array())->save(); // Create a node. $edit = array(); diff --git a/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php index ce48614..2a8821e 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php @@ -34,7 +34,7 @@ function testPagePostInfo() { // Set "Basic page" content type to display post information. $edit = array(); - $edit['node_submitted'] = TRUE; + $edit['submitted'] = TRUE; $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); // Create a node. @@ -57,7 +57,7 @@ function testPageNotPostInfo() { // Set "Basic page" content type to display post information. $edit = array(); - $edit['node_submitted'] = FALSE; + $edit['submitted'] = FALSE; $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); // Create a node. diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTypePersistenceTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTypePersistenceTest.php index 19c1b6f..7e7d2a5 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeTypePersistenceTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTypePersistenceTest.php @@ -32,9 +32,9 @@ function testNodeTypeCustomizationPersistence() { // 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 = entity_load('node_type', 'poll')->disabled; - $this->assertNotIdentical($disabled, FALSE, 'Poll node type found in the database'); - $this->assertEqual($disabled, 0, 'Poll node type is not disabled'); + $poll = entity_load('node_type', 'poll'); + $this->assertNotIdentical($poll->disabled, FALSE, 'Poll node type found in the database'); + $this->assertEqual($poll->disabled, 0, 'Poll node type is not disabled'); // Check that poll node type (uncustomized) shows up. $this->drupalGet('node/add'); @@ -51,17 +51,17 @@ function testNodeTypeCustomizationPersistence() { // Disable poll and check that the node type gets disabled. $this->drupalPost('admin/modules', $poll_disable, t('Save configuration')); - $disabled = entity_load('node_type', 'poll')->disabled; - $this->assertEqual($disabled, 1, 'Poll node type is disabled'); + $poll = entity_load('node_type', 'poll'); + $this->assertEqual($poll->disabled, 1, 'Poll node type is disabled'); $this->drupalGet('node/add'); $this->assertNoText('poll', '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 = entity_load('node_type', 'poll')->disabled; - $this->assertNotIdentical($disabled, FALSE, 'Poll node type found in the database'); - $this->assertEqual($disabled, 0, 'Poll node type is not disabled'); + $poll = entity_load('node_type', 'poll'); + $this->assertNotIdentical($poll->disabled, FALSE, 'Poll node type found in the database'); + $this->assertEqual($poll->disabled, 0, 'Poll node type is not disabled'); $this->drupalGet('node/add'); $this->assertText($description, 'Customized description found'); @@ -70,8 +70,8 @@ function testNodeTypeCustomizationPersistence() { $edit = array('uninstall[poll]' => 'poll'); $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall')); $this->drupalPost(NULL, array(), t('Uninstall')); - $disabled = entity_load('node_type', 'poll')->disabled; - $this->assertTrue($disabled, 'Poll node type is in the database and is disabled'); + $poll = entity_load('node_type', 'poll'); + $this->assertTrue($poll->disabled, 'Poll node type is in the database and is disabled'); $this->drupalGet('node/add'); $this->assertNoText('poll', 'poll type is no longer found on node/add'); diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTypeTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTypeTest.php index 4524978..7df2368 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeTypeTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTypeTest.php @@ -53,7 +53,7 @@ function testNodeTypeCreation() { // Create a content type programmaticaly. $type = $this->drupalCreateContentType(); - $type_exists = entity_load('node_type', $type->type); + $type_exists = (bool) entity_load('node_type', $type->type); $this->assertTrue($type_exists, 'The new content type has been created in the database.'); // Login a test user. @@ -72,7 +72,7 @@ function testNodeTypeCreation() { 'type' => 'foo', ); $this->drupalPost('admin/structure/types/add', $edit, t('Save content type')); - $type_exists = entity_load('node_type', 'foo'); + $type_exists = (bool) entity_load('node_type', 'foo'); $this->assertTrue($type_exists, 'The new content type has been created in the database.'); } diff --git a/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php b/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php index b429e07..6f0eafd 100644 --- a/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php @@ -58,7 +58,7 @@ function testPagePreviewWithRevisions() { $title_key = "title"; $body_key = "body[$langcode][0][value]"; // Force revision on "Basic page" content. - variable_set('node_options_page', array('status', 'revision')); + config('node.settings.page')->set('options', array('status', 'revision')); // Fill in node creation form and preview node. $edit = array(); diff --git a/core/modules/node/node.install b/core/modules/node/node.install index 055ba25..ba8dd62 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -337,18 +337,10 @@ function node_install() { */ function node_uninstall() { // Delete node type variables. - $types = config_get_storage_names_with_prefix('node.type.'); - foreach ($types as $config_name) { - $type = config($config_name)->get('type'); - db_delete('variable') - ->condition(db_or() - ->condition('name', 'node_preview_' . $type) - ->condition('name', 'node_options_' . $type) - ->condition('name', 'node_submitted_' . $type) - ->condition('name', 'node_permissions_' . $type) - ->condition('name', 'node_type_language_translation_enabled_' . $type) - ) - ->execute(); + $content_types = config_get_storage_names_with_prefix('node.type'); + foreach ($content_types as $content_type) { + $type = config($content_type)->get('type'); + config('node.settings.' . $type)->delete(); config('language.settings')->clear('node. ' . $type . '.language.default_configuration')->save(); } @@ -638,25 +630,35 @@ function node_update_8013() { } /** - * Convert node types into configuration. + * Convert existing node types to the new config system. + * + * @ingroup config_upgrade */ -function node_update_8012() { +function node_update_8014() { $result = db_select('node_type', 'nt') ->fields('nt') ->execute() ->fetchAllAssoc('name', PDO::FETCH_ASSOC); foreach ($result as $name => $node_type) { + + // Node type. $config = config('node.type.' . $name); $config->setData($node_type); $config->save(); - } -} + update_config_manifest_add('node.type', array($name)); -/** - * Remove the {node_type} table. - */ -function node_update_8013() { - db_drop_table('node_type'); + // Node type settings. + $variables = db_select('variable', 'v') + ->fields('v') + ->condition('name', array('node_submitted_' . $name, 'node_preview_' . $name, 'node_options_' . $name)) + ->execute(); + $node_type_settings = config('node.settings.' . $name); + foreach ($variables as $variable) { + $node_type_settings->set(str_replace(array('node_', '_' . $name), '', $variable->name), unserialize($variable->value)); + update_variable_del($variable->name); + } + $node_type_settings->save(); + } } /** diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 5de1402..d10bf32 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -17,7 +17,6 @@ use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Template\Attribute; use Drupal\node\Plugin\Core\Entity\Node; -use Drupal\node\Plugin\Core\Entity\NodeType; use Drupal\file\Plugin\Core\Entity\File; use Drupal\Core\Entity\EntityInterface; use Drupal\entity\Plugin\Core\Entity\EntityDisplay; @@ -197,14 +196,15 @@ function node_entity_info(&$info) { ); } - $config_names = config_get_storage_names_with_prefix('node.type.'); - foreach ($config_names as $config_name) { - $config = config($config_name); - $info['node']['bundles'][$config->get('type')] = array( - 'label' => $config->get('name'), + // Bundles must provide a human readable name so we can create help and error + // messages, and the path to attach Field admin pages to. + node_type_cache_reset(); + foreach (node_type_get_names() as $type => $name) { + $info['node']['bundles'][$type] = array( + 'label' => $name, 'admin' => array( 'path' => 'admin/structure/types/manage/%node_type', - 'real path' => 'admin/structure/types/manage/' . $config->get('type'), + 'real path' => 'admin/structure/types/manage/' . $type, 'bundle argument' => 4, 'access arguments' => array('administer content types'), ), @@ -323,6 +323,7 @@ function node_mark($nid, $timestamp) { * 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. * * @return * An array of node types, as objects, keyed by the type. @@ -331,7 +332,7 @@ function node_mark($nid, $timestamp) { * @see node_type_load() */ function node_type_get_types() { - return entity_load_multiple('node_type'); + return _node_types_build()->types; } /** @@ -350,14 +351,15 @@ function node_type_get_types() { * @see node_invoke() */ function node_type_get_base($type) { - $types = node_type_get_types(); - return isset($types[$type]->base) ? $types[$type]->base : FALSE; + $types = _node_types_build()->types; + return isset($types[$type]) && isset($types[$type]->base) ? $types[$type]->base : FALSE; } /** * Returns a list of available node type names. * * This list can include types that are queued for addition or deletion. + * See _node_types_build() for details. * * @return * An array of node type labels, keyed by the node type name. @@ -365,18 +367,7 @@ function node_type_get_base($type) { * @see _node_types_build() */ function node_type_get_names() { - $cid = 'node_type:names:' . language(LANGUAGE_TYPE_INTERFACE)->langcode; - if ($cache = cache()->get($cid)) { - return $cache->data; - } - $types = node_type_get_types(); - $names = array(); - foreach ($types as $id => $type) { - $names[$id] = $type->label(); - } - #cache()->set($cid, $names, array('node_type' => array_keys($types))); - cache()->set($cid, $names); - return $names; + return _node_types_build()->names; } /** @@ -389,8 +380,7 @@ function node_type_get_names() { * The node type label or FALSE if the node type is not found. */ function node_type_get_label($name) { - - $types = node_type_get_names(); + $types = _node_types_build()->names; return isset($types[$name]) ? $types[$name] : FALSE; } @@ -411,36 +401,41 @@ function node_get_type_label($node) { /** * Title callback: Returns the sanitized node type name. * - * @param Drupal\node\NodeType $node_type + * @param $node_type * The node type object. * * @return * The node type name that is safe for printing. */ -function node_type_get_clean_name(NodeType $node_type) { - return check_plain($node_type->label()); +function node_type_get_clean_name($node_type) { + return check_plain($node_type->name); } /** * Description callback: Returns the node type description. * - * @param Drupal\node\NodeType $node_type + * @param $node_type * The node type object. * * @return * The node type description. */ -function node_type_get_description(NodeType $node_type) { +function node_type_get_description($node_type) { return $node_type->description; } /** - * Updates the cache of node types. + * 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. * * @see _node_types_build() */ function node_types_rebuild() { - entity_info_cache_clear(); + _node_types_build(TRUE); } /** @@ -453,7 +448,8 @@ function node_types_rebuild() { * A node type object or FALSE if $name does not exist. */ function node_type_load($name) { - return entity_load('node_type', $name); + $types = _node_types_build()->types; + return isset($types[$name]) ? $types[$name] : FALSE; } /** @@ -488,17 +484,46 @@ function node_type_load($name) { * A status flag indicating the outcome of the operation, either SAVED_NEW or * SAVED_UPDATED. */ -function node_type_save(NodeType $type) { - $status = $type->save(); +function node_type_save($info) { + $existing_type = !empty($info->old_type) ? $info->old_type : $info->type; + $is_existing = (bool) entity_load('node_type', $existing_type); + $type = node_type_set_defaults($info); + + $content_type = entity_create('node_type', array( + 'type' => (string) $type->type, + 'name' => (string) $type->name, + 'base' => (string) $type->base, + 'has_title' => (int) $type->has_title, + 'title_label' => (string) $type->title_label, + 'description' => (string) $type->description, + 'help' => (string) $type->help, + 'custom' => (int) $type->custom, + 'modified' => (int) $type->modified, + 'locked' => (int) $type->locked, + 'disabled' => (int) $type->disabled, + 'module' => $type->module, + )); + + if ($is_existing) { + + $content_type->save(); - if ($status == SAVED_UPDATED) { - if ($type->getOriginalID() !== $type->id()) { - field_attach_rename_bundle('node', $type->getOriginalID(), $type->id()); + if (!empty($type->old_type) && $type->old_type != $type->type) { + field_attach_rename_bundle('node', $type->old_type, $type->type); } + module_invoke_all('node_type_update', $type); + $status = SAVED_UPDATED; } else { - field_attach_create_bundle('node', $type->id()); + $content_type->orig_type = (string) $type->orig_type; + $content_type->save(); + + field_attach_create_bundle('node', $type->type); + + module_invoke_all('node_type_insert', $type); + $status = SAVED_NEW; } + // Clear the node type cache. node_type_cache_reset(); @@ -516,10 +541,10 @@ function node_type_save(NodeType $type) { * @return * Body field instance. */ -function node_add_body_field(NodeType $type, $label = 'Body') { +function node_add_body_field($type, $label = 'Body') { // Add or remove the body field, as needed. $field = field_info_field('body'); - $instance = field_info_instance('node', 'body', $type->id()); + $instance = field_info_instance('node', 'body', $type->type); if (empty($field)) { $field = array( 'field_name' => 'body', @@ -532,7 +557,7 @@ function node_add_body_field(NodeType $type, $label = 'Body') { $instance = array( 'field_name' => 'body', 'entity_type' => 'node', - 'bundle' => $type->id(), + 'bundle' => $type->type, 'label' => $label, 'widget' => array('type' => 'text_textarea_with_summary'), 'settings' => array('display_summary' => TRUE), @@ -607,8 +632,9 @@ function node_field_extra_fields() { */ function node_type_delete($name) { $type = node_type_load($name); - $type->delete(); + entity_delete_multiple('node_type', array($name)); field_attach_delete_bundle('node', $name); + module_invoke_all('node_type_delete', $type); // Clear the node type cache. node_type_cache_reset(); @@ -633,10 +659,110 @@ function node_type_update_nodes($old_type, $type) { } /** + * 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 stored in configuration. + * These two information sources are not synchronized during module installation + * until node_types_rebuild() is called. + * + * @param $rebuild + * (optional) TRUE to rebuild node types. Equivalent to calling + * node_types_rebuild(). Defaults to FALSE. + * + * @return + * An object with two properties: + * - 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 as configuration. + * 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. + */ +function _node_types_build($rebuild = FALSE) { + $cid = 'node_types:' . language(LANGUAGE_TYPE_INTERFACE)->langcode; + + if (!$rebuild) { + $_node_types = &drupal_static(__FUNCTION__); + if (isset($_node_types)) { + return $_node_types; + } + if ($cache = cache()->get($cid)) { + $_node_types = $cache->data; + return $_node_types; + } + } + + $_node_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']; + } + } + + // @todo Fix when http://drupal.org/node/1782460 is in. + $content_types = config_get_storage_names_with_prefix('node.type.'); + foreach ($content_types as $config) { + $type_object = (object) config($config)->get(); + if (!$rebuild && $type_object->disabled) { + continue; + } + $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; + } + 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]); + } + } + $_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); + + cache()->set($cid, $_node_types, CacheBackendInterface::CACHE_PERMANENT, array('node_types' => TRUE)); + + return $_node_types; +} + +/** * Clears the node type cache. */ function node_type_cache_reset() { cache()->deleteTags(array('node_types' => TRUE)); + drupal_static_reset('_node_types_build'); } /** @@ -1023,7 +1149,8 @@ function template_preprocess_node(&$variables) { field_attach_preprocess($node, $variables['content'], $variables); // Display post information only on certain node types. - if (variable_get('node_submitted_' . $node->type, TRUE)) { + $submitted = config('node.settings.' . $node->type)->get('submitted'); + if (is_null($submitted) || $submitted) { $variables['display_submitted'] = TRUE; $variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date'])); if (theme_get_setting('toggle_node_user_picture')) { @@ -2642,7 +2769,7 @@ function node_list_permissions($type) { function node_permissions_get_configured_types() { $configured_types = array(); foreach (node_type_get_types() as $name => $type) { - if (variable_get('node_permissions_' . $name, 1)) { + if (config('node.settings.' . $name)->get('permissions') ?: TRUE) { $configured_types[$name] = $type; } } diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php index 3036977..2979f3c 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php @@ -145,6 +145,11 @@ protected function entityValues($entity_type) { ); case 'node': return array('title' => $this->randomString()); + case 'node_type': + return array( + 'type' => 'article', + 'name' => $this->randomName(), + ); case 'user': return array('name' => $this->randomName()); default: diff --git a/core/modules/translation/translation.module b/core/modules/translation/translation.module index 4a0defc..a15cd3e 100644 --- a/core/modules/translation/translation.module +++ b/core/modules/translation/translation.module @@ -162,7 +162,7 @@ function translation_form_node_type_form_alter(&$form, &$form_state) { $form['language']['node_type_language_translation_enabled'] = array( '#type' => 'checkbox', '#title' => t('Enable translation'), - '#default_value' => variable_get('node_type_language_translation_enabled_' . $form_state['node_type']->type, FALSE), + '#default_value' => variable_get('node_type_language_translation_enabled_' . $form['#node_type']->type, FALSE), '#element_validate' => array('translation_node_type_language_translation_enabled_validate'), '#prefix' => "", );