diff --git a/core/includes/entity.inc b/core/includes/entity.inc index bf6c1ce..dee5e1d 100644 --- a/core/includes/entity.inc +++ b/core/includes/entity.inc @@ -404,7 +404,13 @@ function entity_form_state_defaults(EntityInterface $entity, $operation = 'defau $form_state = array(); $controller = entity_form_controller($entity->entityType(), $operation); $form_state['build_info']['callback'] = array($controller, 'build'); - $form_state['build_info']['base_form_id'] = $entity->entityType() . '_form'; + // Assign ENTITYTYPE_form as base form ID to invoke corresponding + // hook_form_alter(), #validate, #submit, and #theme callbacks, but only if it + // is different from the actual form_id, since callbacks would be invoked + // twice otherwise. + if ($entity->entityType() . '_form' != entity_form_id($entity, $operation)) { + $form_state['build_info']['base_form_id'] = $entity->entityType() . '_form'; + } $form_state['build_info']['args'] = array($entity); $form_state['langcode'] = $langcode; return $form_state; diff --git a/core/lib/Drupal/Core/Entity/EntityListController.php b/core/lib/Drupal/Core/Entity/EntityListController.php index 1fdb4ba..a1ea211 100644 --- a/core/lib/Drupal/Core/Entity/EntityListController.php +++ b/core/lib/Drupal/Core/Entity/EntityListController.php @@ -154,7 +154,9 @@ public function render() { '#empty' => t('There is no @label yet.', array('@label' => $this->entityInfo['label'])), ); foreach ($this->load() as $entity) { - $build['#rows'][$entity->id()] = $this->buildRow($entity); + if ($row = $this->buildRow($entity)) { + $build['#rows'][$entity->id()] = $row; + } } return $build; } diff --git a/core/modules/book/book.install b/core/modules/book/book.install index 797902a..8104dea 100644 --- a/core/modules/book/book.install +++ b/core/modules/book/book.install @@ -6,14 +6,6 @@ */ /** - * Implements hook_install(). - */ -function book_install() { - // Add the node type. - _book_install_type_create(); -} - -/** * Implements hook_uninstall(). */ function book_uninstall() { @@ -25,28 +17,6 @@ function book_uninstall() { } /** - * Creates the book content type. - */ -function _book_install_type_create() { - // Create an additional node type. - $book_node_type = array( - 'type' => 'book', - 'name' => t('Book page'), - 'base' => 'node_content', - 'description' => t('Books have a built-in hierarchical navigation. Use for handbooks or tutorials.'), - 'custom' => 1, - 'modified' => 1, - 'locked' => 0, - ); - - $book_node_type = node_type_set_defaults($book_node_type); - node_type_save($book_node_type); - node_add_body_field($book_node_type); - // Default to not promoted. - variable_set('node_options_book', array('status')); -} - -/** * Implements hook_schema(). */ function book_schema() { diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 99edfc5..75c8052 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -6,6 +6,7 @@ */ use Drupal\node\Plugin\Core\Entity\Node; +use Drupal\node\Plugin\Core\Entity\NodeType; use Drupal\entity\Plugin\Core\Entity\EntityDisplay; use Drupal\Core\Template\Attribute; @@ -1228,22 +1229,22 @@ function book_type_is_allowed($type) { * Updates the Book module's persistent variables if the machine-readable name * of a node type is changed. */ -function book_node_type_update($type) { - if (!empty($type->old_type) && $type->old_type != $type->type) { +function book_node_type_update(NodeType $type) { + if ($type->original->id() != $type->id()) { $config = config('book.settings'); // Update the list of node types that are allowed to be added to books. $allowed_types = $config->get('allowed_types'); - $key = array_search($type->old_type, $allowed_types); + $key = array_search($type->original->id(), $allowed_types); if ($key !== FALSE) { - $allowed_types[$type->type] = $allowed_types[$key] ? $type->type : 0; + $allowed_types[$type->id()] = $allowed_types[$key] ? $type->id() : 0; unset($allowed_types[$key]); $config->set('allowed_types', $allowed_types); } // Update the setting for the "Add child page" link. - if ($config->get('child_type') == $type->old_type) { - $config->set('child_type', $type->type); + if ($config->get('child_type') == $type->original->id()) { + $config->set('child_type', $type->id()); } $config->save(); } diff --git a/core/modules/book/config/node.type.book.yml b/core/modules/book/config/node.type.book.yml new file mode 100644 index 0000000..fe4b939 --- /dev/null +++ b/core/modules/book/config/node.type.book.yml @@ -0,0 +1,9 @@ +type: book +name: 'Book page' +base: node_content +description: 'Books have a built-in hierarchical navigation. Use for handbooks or tutorials.' +settings: + node: + # Not promoted to front page. + options: + - status diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 7ebef3e..9963f52 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -10,6 +10,7 @@ */ use Drupal\node\Plugin\Core\Entity\Node; +use Drupal\node\Plugin\Core\Entity\NodeType; use Drupal\entity\Plugin\Core\Entity\EntityDisplay; use Drupal\file\Plugin\Core\Entity\File; use Drupal\Core\Entity\EntityInterface; @@ -102,22 +103,22 @@ function comment_help($path, $arg) { * Implements hook_entity_info(). */ function comment_entity_info(&$info) { - foreach (node_type_get_names() as $type => $name) { - $info['comment']['bundles']['comment_node_' . $type] = array( - 'label' => t('@node_type comment', array('@node_type' => $name)), + foreach (node_type_get_names() as $id => $label) { + $info['comment']['bundles']['comment_node_' . $id] = array( + 'label' => t('@node_type comment', array('@node_type' => $label)), // Provide the node type/bundle name for other modules, so it does not // have to be extracted manually from the bundle name. - 'node bundle' => $type, + 'node bundle' => $id, 'admin' => array( // Place the Field UI paths for comments one level below the // corresponding paths for nodes, so that they appear in the same set // 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', + 'real path' => 'admin/structure/types/manage/' . $id . '/comment', 'access arguments' => array('administer content types'), ), ); @@ -137,7 +138,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 +292,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; } /** @@ -323,9 +324,9 @@ function comment_node_type_insert($info) { /** * Implements hook_node_type_update(). */ -function comment_node_type_update($info) { - if (!empty($info->old_type) && $info->type != $info->old_type) { - field_attach_rename_bundle('comment', 'comment_node_' . $info->old_type, 'comment_node_' . $info->type); +function comment_node_type_update(NodeType $type) { + if ($type->original->id() != $type->id()) { + field_attach_rename_bundle('comment', 'comment_node_' . $type->original->id(), 'comment_node_' . $type->id()); } } @@ -1022,7 +1023,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['#node_type']->type, COMMENT_NODE_OPEN), + '#default_value' => variable_get('comment_' . $form_state['entity']->id(), COMMENT_NODE_OPEN), '#options' => array( COMMENT_NODE_OPEN => t('Open'), COMMENT_NODE_CLOSED => t('Closed'), @@ -1032,19 +1033,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['#node_type']->type, COMMENT_MODE_THREADED), + '#default_value' => variable_get('comment_default_mode_' . $form_state['entity']->id(), 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['#node_type']->type, 50), + '#default_value' => variable_get('comment_default_per_page_' . $form_state['entity']->id(), 50), '#options' => _comment_per_page(), ); $form['comment']['comment_anonymous'] = array( '#type' => 'select', '#title' => t('Anonymous commenting'), - '#default_value' => variable_get('comment_anonymous_' . $form['#node_type']->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT), + '#default_value' => variable_get('comment_anonymous_' . $form_state['entity']->id(), 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 +1056,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['#node_type']->type, 1), + '#default_value' => variable_get('comment_subject_field_' . $form_state['entity']->id(), 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['#node_type']->type, COMMENT_FORM_BELOW), + '#default_value' => variable_get('comment_form_location_' . $form_state['entity']->id(), COMMENT_FORM_BELOW), ); $form['comment']['comment_preview'] = array( '#type' => 'radios', '#title' => t('Preview comment'), - '#default_value' => variable_get('comment_preview_' . $form['#node_type']->type, DRUPAL_OPTIONAL), + '#default_value' => variable_get('comment_preview_' . $form_state['entity']->id(), DRUPAL_OPTIONAL), '#options' => array( DRUPAL_DISABLED => t('Disabled'), DRUPAL_OPTIONAL => t('Optional'), @@ -1076,7 +1077,7 @@ function comment_form_node_type_form_alter(&$form, $form_state) { if (module_exists('translation_entity')) { $comment_form = $form; $comment_form_state['translation_entity']['key'] = 'language_configuration'; - $form['comment'] += translation_entity_enable_widget('comment', 'comment_node_' . $form['#node_type']->type, $comment_form, $comment_form_state); + $form['comment'] += translation_entity_enable_widget('comment', 'comment_node_' . $form_state['entity']->id(), $comment_form, $comment_form_state); array_unshift($form['#submit'], 'comment_translation_configuration_element_submit'); } } @@ -1098,7 +1099,7 @@ function comment_translation_configuration_element_submit($form, &$form_state) { $key = 'language_configuration'; $comment_form_state = array( 'translation_entity' => array('key' => $key), - 'language' => array($key => array('entity_type' => 'comment', 'bundle' => 'comment_node_' . $form['#node_type']->type)), + 'language' => array($key => array('entity_type' => 'comment', 'bundle' => 'comment_node_' . $form_state['entity']->id())), 'values' => array($key => array('translation_entity' => $form_state['values']['translation_entity'])), ); translation_entity_language_configuration_element_submit($form, $comment_form_state); diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc index 32a8c37..84e53fd 100644 --- a/core/modules/field/field.attach.inc +++ b/core/modules/field/field.attach.inc @@ -1523,8 +1523,10 @@ function field_attach_prepare_translation(EntityInterface $entity, $langcode, En * The name of the newly created bundle. */ function field_attach_create_bundle($entity_type, $bundle) { - // Clear the cache. + // Clear the field cache. field_cache_clear(); + // Clear the entity info cache (contains bundles). + entity_info_cache_clear(); // Let other modules act on creating the bundle. module_invoke_all('field_attach_create_bundle', $entity_type, $bundle); @@ -1547,8 +1549,9 @@ function field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { ->condition('bundle', $bundle_old) ->execute(); - // Clear the cache. + // Clear the field cache. field_cache_clear(); + // Clear the entity info cache (contains bundles). entity_info_cache_clear(); // Update bundle settings. @@ -1584,8 +1587,10 @@ function field_attach_delete_bundle($entity_type, $bundle) { field_delete_instance($instance); } - // Clear the cache. + // Clear the field cache. field_cache_clear(); + // Clear the entity info cache (contains bundles). + entity_info_cache_clear(); // Clear bundle display settings. variable_del('field_bundle_settings_' . $entity_type . '__' . $bundle); diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module index dd2a1cc..e9e4cd0 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['#node_type']->type)) { + if ($form_state['entity']->isNew()) { $form['actions']['save_continue'] = array( '#type' => 'submit', '#value' => t('Save and manage fields'), diff --git a/core/modules/forum/config/node.type.forum.yml b/core/modules/forum/config/node.type.forum.yml new file mode 100644 index 0000000..605c7c2 --- /dev/null +++ b/core/modules/forum/config/node.type.forum.yml @@ -0,0 +1,15 @@ +type: forum +name: 'Forum topic' +base: forum +module: forum +locked: '1' +description: 'A forum topic starts a new discussion thread within a forum.' +help: '' +has_title: '1' +title_label: Subject +settings: + node: + # Not promoted to front page. + options: + - status +langcode: und diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install index b1e63de..224503b 100644 --- a/core/modules/forum/forum.install +++ b/core/modules/forum/forum.install @@ -11,11 +11,6 @@ function forum_install() { // Set the weight of the forum.module to 1 so it is loaded after the taxonomy.module. module_set_weight('forum', 1); - // Forum topics are published by default, but do not have any other default - // 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')); } /** @@ -107,11 +102,6 @@ function forum_enable() { )) ->save(); } - - // Ensure the forum node type is available. - node_types_rebuild(); - $types = node_type_get_types(); - node_add_body_field($types['forum']); } /** @@ -121,8 +111,6 @@ function forum_uninstall() { // Load the dependent Taxonomy module, in case it has been disabled. drupal_load('module', 'taxonomy'); - variable_del('node_options_forum'); - field_delete_field('taxonomy_forums'); // Purge field data now to allow taxonomy module to be uninstalled // if this is the only field remaining. diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php index 7b0bef7..d8b964b 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('settings[node][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/language/language.module b/core/modules/language/language.module index c2356be..375d143 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -5,6 +5,8 @@ * Add language handling functionality to Drupal. */ +use Drupal\node\Plugin\Core\Entity\NodeType; + /** * Implements hook_help(). */ @@ -335,6 +337,15 @@ function language_configuration_element_process($element, &$form_state, &$form) ), ); + // Determine where to attach the language_configuration element submit handler. + // @todo Form API: Allow form widgets/sections to declare #submit handlers. + if (isset($form['actions']['submit'])) { + $form['actions']['submit']['#submit'][] = 'language_configuration_element_submit'; + } + else { + $form['#submit'][] = 'language_configuration_element_submit'; + } + return $element; } @@ -344,7 +355,10 @@ function language_configuration_element_process($element, &$form_state, &$form) function language_configuration_element_submit(&$form, &$form_state) { // Iterate through all the language_configuration elements and save their // values. - if (isset($form_state['language'])) { + if (isset($form_state['language']) && !isset($form_state['language_configuration_saved'])) { + // Prevent this from being executed twice. + // @todo language_configuration_element_process() + $form_state['language_configuration_saved'] = TRUE; foreach ($form_state['language'] as $element_name => $values) { language_save_default_configuration($values['entity_type'], $values['bundle'], $form_state['values'][$element_name]); } @@ -425,10 +439,10 @@ function language_get_default_configuration_settings_key($entity_type, $bundle) /** * Implements hook_node_type_update(). */ -function language_node_type_update($info) { - if (!empty($info->old_type) && $info->old_type != $info->type) { - language_save_default_configuration('node', $info->type, language_get_default_configuration('node', $info->old_type)); - language_clear_default_configuration('node', $info->old_type); +function language_node_type_update(NodeType $type) { + if ($type->original->id() != $type->id()) { + language_save_default_configuration('node', $type->id(), language_get_default_configuration('node', $type->original->id())); + language_clear_default_configuration('node', $type->original->id()); } } diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageConfigurationElementTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageConfigurationElementTest.php index 6f8d9c8..15dcf63 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageConfigurationElementTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageConfigurationElementTest.php @@ -127,7 +127,8 @@ public function testNodeTypeUpdate() { $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type')); // Check the language default configuration for the articles. $configuration = language_get_default_configuration('node', 'article'); - $this->assertEqual($configuration, array('langcode' => 'current_interface', 'language_hidden' => 0), 'The default language configuration has been saved on the Article content type.'); + $this->assertEqual($configuration, array('langcode' => 'current_interface', 'language_hidden' => 0)); + // Rename the article content type. $edit = array( 'type' => 'article_2' @@ -135,6 +136,6 @@ public function testNodeTypeUpdate() { $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type')); // Check that we still have the settings for the new node type. $configuration = language_get_default_configuration('node', 'article_2'); - $this->assertEqual($configuration, array('langcode' => 'current_interface', 'language_hidden' => 0), 'The default language configuration has been kept on the new Article content type.'); + $this->assertEqual($configuration, array('langcode' => 'current_interface', 'language_hidden' => 0)); } } diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module index 3d4a360..a482485 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['#node_type']; + $type = $form_state['entity']; $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 538b19f..06311f3 100644 --- a/core/modules/node/content_types.inc +++ b/core/modules/node/content_types.inc @@ -5,6 +5,8 @@ * Content type editing user interface. */ +use Drupal\node\Plugin\Core\Entity\NodeType; + /** * Page callback: Displays the content type admin overview page. * @@ -14,112 +16,62 @@ * @see node_menu() */ function node_overview_types() { - $types = node_type_get_types(); - $names = node_type_get_names(); - $field_ui = module_exists('field_ui'); - $header = array(t('Name'), t('Operations')); - $rows = array(); - - foreach ($names as $key => $name) { - $type = $types[$key]; - if (node_hook($type->type, 'form')) { - $row = array(theme('node_admin_overview', array('name' => $name, 'type' => $type))); - $links['edit'] = array( - 'title' => t('edit'), - 'href' => 'admin/structure/types/manage/' . $type->type, - 'weight' => 0, - ); - - if ($field_ui) { - $links['fields'] = array( - 'title' => t('manage fields'), - 'href' => 'admin/structure/types/manage/' . $type->type . '/fields', - 'weight' => 5, - ); - $links['display'] = array( - 'title' => t('manage display'), - 'href' => 'admin/structure/types/manage/' . $type->type . '/display', - 'weight' => 10, - ); - } - - if ($type->custom) { - $links['delete'] = array( - 'title' => t('delete'), - 'href' => 'admin/structure/types/manage/' . $type->type . '/delete', - 'weight' => 15, - ); - } - - $row[] = array( - 'data' => array( - '#type' => 'operations', - '#links' => $links, - ), - ); - - $rows[] = $row; - } - } - - $build['node_table'] = array( - '#theme' => 'table', - '#header' => $header, - '#rows' => $rows, - '#empty' => t('No content types available. Add content type.', array('@link' => url('admin/structure/types/add'))), - ); + return entity_list_controller('node_type')->render(); +} - return $build; +/** + * Page callback: Presents the node type creation form. + * + * @return array + * A form array as expected by drupal_render(). + * + * @see node_menu() + */ +function node_type_add() { + drupal_set_title(t('Add content type')); + $type = entity_create('node_type', array()); + return entity_get_form($type); } /** - * Returns HTML for a node type description for the content type admin page. + * Page callback: Presents the node type edit form. * - * @param $variables - * An associative array containing: - * - name: The human-readable name of the content type. - * - type: An object containing the 'type' (machine name) and 'description' of - * the content type. + * @param \Drupal\node\Plugin\Core\Entity\NodeType $type + * The node type to edit. * - * @return string - * An HTML-formatted string of the description for this node type. + * @return array + * A form array as expected by drupal_render(). * - * @ingroup themeable + * @see node_menu() */ -function theme_node_admin_overview($variables) { - $name = $variables['name']; - $type = $variables['type']; - - $output = check_plain($name); - $output .= ' ' . t('(Machine name: @type)', array('@type' => $type->type)) . ''; - $output .= '
' . filter_xss_admin($type->description) . '
'; - return $output; +function node_type_edit(NodeType $type) { + drupal_set_title(t('Edit %label content type', array('%label' => $type->label())), PASS_THROUGH); + return entity_get_form($type); } /** * Form constructor for the node type editing form. * - * @param $type - * (optional) An object representing the node type, when editing an existing - * node type. + * @param \Drupal\node\Plugin\Core\Entity\NodeType $type + * A node type entity. * * @see node_type_form_validate() * @see node_type_form_submit() * @ingroup forms */ -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)); - } - - // Make the type object available to implementations of hook_form_alter. - $form['#node_type'] = $type; +function node_type_form($form, &$form_state, NodeType $type) { + $node_settings = $type->getNodeSettings('node'); + // Ensure default settings. + $node_settings += array( + 'options' => array('status', 'promote'), + 'preview' => DRUPAL_OPTIONAL, + 'submitted' => TRUE, + ); $form['name'] = array( '#title' => t('Name'), '#type' => 'textfield', - '#default_value' => $type->name, + '#default_value' => $type->label(), '#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, @@ -127,7 +79,7 @@ function node_type_form($form, &$form_state, $type = NULL) { $form['type'] = array( '#type' => 'machine_name', - '#default_value' => $type->type, + '#default_value' => $type->id(), '#maxlength' => 32, '#disabled' => $type->locked, '#machine_name' => array( @@ -172,10 +124,11 @@ function node_type_form($form, &$form_state, $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), + '#parents' => array('settings', 'node', 'preview'), + '#default_value' => $node_settings['preview'], '#options' => array( DRUPAL_DISABLED => t('Disabled'), DRUPAL_OPTIONAL => t('Optional'), @@ -195,9 +148,10 @@ function node_type_form($form, &$form_state, $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->type, array('status', 'promote')), + '#parents' => array('settings', 'node', 'options'), + '#default_value' => $node_settings['options'], '#options' => array( 'status' => t('Published'), 'promote' => t('Promoted to front page'), @@ -215,17 +169,15 @@ function node_type_form($form, &$form_state, $type = NULL) { '#group' => 'additional_settings', ); - $language_configuration = language_get_default_configuration('node', $type->type); + $language_configuration = language_get_default_configuration('node', $type->id()); $form['language']['language_configuration'] = array( '#type' => 'language_configuration', '#entity_information' => array( 'entity_type' => 'node', - 'bundle' => $type->type, + 'bundle' => $type->id(), ), '#default_value' => $language_configuration, ); - - $form['#submit'][] = 'language_configuration_element_submit'; } $form['display'] = array( '#type' => 'details', @@ -234,20 +186,13 @@ function node_type_form($form, &$form_state, $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->type, TRUE), + '#parents' => array('settings', 'node', 'submitted'), + '#default_value' => $node_settings['submitted'], '#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, @@ -265,24 +210,6 @@ function node_type_form($form, &$form_state, $type = NULL) { '#value' => $type->locked, ); - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Save content type'), - '#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; } @@ -305,27 +232,18 @@ function _node_characters($length) { * @see node_type_form_submit() */ function node_type_form_validate($form, &$form_state) { - $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))); - } + $type = $form_state['entity']; + $id = $form_state['values']['type']; + $label = $form_state['values']['name']; + // 'theme' conflicts with theme_node_form(). + // '0' is invalid, since elsewhere we check it using empty(). + if (in_array(trim($id), array('0', 'theme'))) { + form_set_error('type', t("Invalid machine-readable name. Enter a name other than %invalid.", array('%invalid' => $id))); } - $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))); + $types = node_type_get_names(); + if ($type->isNew() && in_array($label, $types)) { + form_set_error('name', t('The human-readable name %name is already taken.', array('%name' => $label))); } } @@ -335,121 +253,52 @@ function node_type_form_validate($form, &$form_state) { * @see node_type_form_validate() */ function node_type_form_submit($form, &$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; - } - - if ($op == t('Delete content type')) { - $form_state['redirect'] = 'admin/structure/types/manage/' . $type->old_type . '/delete'; - return; - } - + $type = $form_state['entity']; $variables = $form_state['values']; - // Remove everything that's been saved already - whatever's left is assumed - // to be a persistent variable. - 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']); + // @todo Fix vertical_tabs. + unset($variables['additional_settings__active_tab']); + // @todo Remove the entire following code after converting node settings of + // Comment and Menu module. + // Remove all node type entity properties. + foreach (get_class_vars(get_class($type)) as $key => $value) { + unset($variables[$key]); + } // Save or reset persistent variable values. foreach ($variables as $key => $value) { - $variable_new = $key . '_' . $type->type; - $variable_old = $key . '_' . $type->old_type; - + $variable_new = $key . '_' . $type->id(); + $variable_old = $key . '_' . $type->getOriginalID(); if (is_array($value)) { $value = array_keys(array_filter($value)); } variable_set($variable_new, $value); - if ($variable_new != $variable_old) { variable_del($variable_old); } } - // Saving the content type after saving the variables allows modules to act // on those variables via hook_node_type_insert(). - $status = node_type_save($type); + $status = $type->save(); - node_types_rebuild(); - menu_router_rebuild(); - $t_args = array('%name' => $type->name); + $t_args = array('%name' => $type->label()); if ($status == SAVED_UPDATED) { drupal_set_message(t('The content type %name has been updated.', $t_args)); } 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'; - return; -} - -/** - * Implements hook_node_type_update(). - */ -function node_node_type_update($info) { - if (!empty($info->old_type) && $info->old_type != $info->type) { - $update_count = node_type_update_nodes($info->old_type, $info->type); - - if ($update_count) { - drupal_set_message(format_plural($update_count, 'Changed the content type of 1 post from %old-type to %type.', 'Changed the content type of @count posts from %old-type to %type.', array('%old-type' => $info->old_type, '%type' => $info->type))); - } - } -} - -/** - * Resets relevant fields of a module-defined node type to their default values. - * - * @param $type - * The node type to reset. The node type is passed back by reference with its - * resetted values. If there is no module-defined info for this node type, - * then nothing happens. - */ -function node_type_reset($type) { - $info_array = module_invoke_all('node_info'); - if (isset($info_array[$type->orig_type])) { - $info_array[$type->orig_type]['type'] = $type->orig_type; - $info = node_type_set_defaults($info_array[$type->orig_type]); - - foreach ($info as $field => $value) { - $type->$field = $value; - } - } } /** * Page callback: Form constructor for the content type delete form. * - * @param $type - * Content type object. + * @param \Drupal\node\Plugin\Core\Entity\NodeType $type + * A node type entity. * * @return * Form array for delete confirmation form. @@ -457,16 +306,16 @@ function node_type_reset($type) { * @see node_type_delete_confirm_submit() * @ingroup forms */ -function node_type_delete_confirm($form, &$form_state, $type) { - $form['type'] = array('#type' => 'value', '#value' => $type->type); - $form['name'] = array('#type' => 'value', '#value' => $type->name); +function node_type_delete_confirm($form, &$form_state, NodeType $type) { + $form_state['entity'] = $type; + $form['type'] = array('#type' => 'value', '#value' => $type->id()); - $message = t('Are you sure you want to delete the content type %type?', array('%type' => $type->name)); + $message = t('Are you sure you want to delete the content type %type?', array('%type' => $type->label())); - $num_nodes = db_query("SELECT COUNT(*) FROM {node} WHERE type = :type", array(':type' => $type->type))->fetchField(); + $num_nodes = db_query("SELECT COUNT(*) FROM {node} WHERE type = :type", array(':type' => $type->id()))->fetchField(); if ($num_nodes) { drupal_set_title($message, PASS_THROUGH); - $caption = '

' . format_plural($num_nodes, '%type is used by 1 piece of content on your site. You can not remove this content type until you have removed all of the %type content.', '%type is used by @count pieces of content on your site. You may not remove %type until you have removed all of the %type content.', array('%type' => $type->name)) . '

'; + $caption = '

' . format_plural($num_nodes, '%type is used by 1 piece of content on your site. You can not remove this content type until you have removed all of the %type content.', '%type is used by @count pieces of content on your site. You may not remove %type until you have removed all of the %type content.', array('%type' => $type->id())) . '

'; $form['description'] = array('#markup' => $caption); return $form; } @@ -480,16 +329,12 @@ function node_type_delete_confirm($form, &$form_state, $type) { * Form submission handler for node_type_delete_confirm(). */ function node_type_delete_confirm_submit($form, &$form_state) { - node_type_delete($form_state['values']['type']); + $type = $form_state['entity']; + $type->delete(); - variable_del('node_preview_' . $form_state['values']['type']); - $t_args = array('%name' => $form_state['values']['name']); + $t_args = array('%name' => $type->label()); drupal_set_message(t('The content type %name has been deleted.', $t_args)); watchdog('node', 'Deleted content type %name.', $t_args, WATCHDOG_NOTICE); - node_types_rebuild(); - menu_router_rebuild(); - $form_state['redirect'] = 'admin/structure/types'; - return; } diff --git a/core/modules/node/content_types.js b/core/modules/node/content_types.js index 40d6422..f6a545b 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^='settings[node][options']:checked").parent().each(function() { vals.push(Drupal.checkPlain($(this).text())); }); - if (!$(context).find('#edit-node-options-status').is(':checked')) { + if (!$(context).find('#edit-settings-node-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-settings-node-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..3b24fd5 100644 --- a/core/modules/node/lib/Drupal/node/NodeFormController.php +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -17,6 +17,8 @@ */ class NodeFormController extends EntityFormController { + protected $settings; + /** * Prepares the node object. * @@ -27,13 +29,20 @@ 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')); + $type = entity_load('node_type', $node->bundle()); + $this->settings = $type->getNodeSettings('node'); + $this->settings += array( + 'options' => array('status', 'promote'), + 'preview' => DRUPAL_OPTIONAL, + 'submitted' => TRUE, + ); + // 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) { // Multistep node forms might have filled in something already. if (!isset($node->$key)) { - $node->$key = (int) in_array($key, $node_options); + $node->$key = (int) in_array($key, $this->settings['options']); } } global $user; @@ -46,7 +55,7 @@ protected function prepareEntity(EntityInterface $node) { $node->log = NULL; } // Always use the default revision setting. - $node->setNewRevision(in_array('revision', $node_options)); + $node->setNewRevision(in_array('revision', $this->settings['options'])); node_invoke($node, 'prepare'); module_invoke_all('node_prepare', $node); @@ -251,7 +260,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 = $this->settings['preview']; $element['preview'] = array( '#access' => $preview_mode != DRUPAL_DISABLED, diff --git a/core/modules/node/lib/Drupal/node/NodeTypeFormController.php b/core/modules/node/lib/Drupal/node/NodeTypeFormController.php new file mode 100644 index 0000000..f90c01b --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeTypeFormController.php @@ -0,0 +1,72 @@ +getEntity($form_state); + $actions = parent::actions($form, $form_state); + $actions['submit']['#value'] = t('Save content type'); + $actions['delete']['#value'] = t('Delete content type'); + $actions['delete']['#access'] = $type->custom && $type->id(); + return $actions; + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::validate(). + */ + public function validate(array $form, array &$form_state) { + parent::validate($form, $form_state); + // @todo Convert node_type_form(). + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::save(). + */ + public function save(array $form, array &$form_state) { + $type = $this->getEntity($form_state); + $type->type = trim($type->id()); + $type->name = trim($type->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 != ''); + + // @todo Convert node_type_form(). + node_type_form_submit($form, $form_state); + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::delete(). + */ + public function delete(array $form, array &$form_state) { + $type = $this->getEntity($form_state); + $form_state['redirect'] = 'admin/structure/types/manage/' . $type->id() . '/delete'; + } + +} diff --git a/core/modules/node/lib/Drupal/node/NodeTypeListController.php b/core/modules/node/lib/Drupal/node/NodeTypeListController.php new file mode 100644 index 0000000..d7338b8 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeTypeListController.php @@ -0,0 +1,83 @@ + t('Description'), + 'class' => array(RESPONSIVE_PRIORITY_MEDIUM), + ); + $row['operations'] = t('Operations'); + return $row; + } + + /** + * Overrides \Drupal\Core\Entity\EntityListController::buildRow(). + */ + public function buildRow(EntityInterface $entity) { + if (!node_hook($entity->id(), 'form')) { + return array(); + } + $row['title'] = array( + 'data' => check_plain($entity->label()), + 'class' => array('menu-label'), + ); + $row['description'] = filter_xss_admin($entity->description); + $row['operations']['data'] = $this->buildOperations($entity); + return $row; + } + + /** + * Overrides \Drupal\Core\Entity\EntityListController::getOperations(); + */ + public function getOperations(EntityInterface $entity) { + $operations = parent::getOperations($entity); + if (module_exists('field_ui')) { + $uri = $entity->uri(); + $operations['manage-fields'] = array( + 'title' => t('Manage fields'), + 'href' => $uri['path'] . '/fields', + 'options' => $uri['options'], + 'weight' => 11, + ); + $operations['manage-display'] = array( + 'title' => t('Manage display'), + 'href' => $uri['path'] . '/display', + 'options' => $uri['options'], + 'weight' => 12, + ); + } + if (!$entity->custom) { + unset($operations['delete']); + } + return $operations; + } + + /** + * Overrides \Drupal\Core\Entity\EntityListController::render(); + */ + public function render() { + $build = parent::render(); + $build['#empty'] = t('No content types available. Add content type.', array( + '@link' => url('admin/structure/types/add'), + )); + return $build; + } + +} diff --git a/core/modules/node/lib/Drupal/node/NodeTypeStorageController.php b/core/modules/node/lib/Drupal/node/NodeTypeStorageController.php new file mode 100644 index 0000000..6f69a58 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeTypeStorageController.php @@ -0,0 +1,83 @@ + $entity) { + $entity->disabled = !module_exists($entity->module); + } + parent::attachLoad($queried_entities, $revision_id); + } + + /** + * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postSave(). + */ + protected function postSave(EntityInterface $entity, $update) { + parent::postSave($entity, $update); + + if (!$update) { + // Clear the node type cache, so the new type appears. + node_type_cache_reset(); + + field_attach_create_bundle('node_type', $entity->id()); + + // Unless disabled, automatically create a Body field for new node types. + if ($entity->get('create_body')) { + $label = $entity->get('create_body_label'); + node_add_body_field($entity, $label); + } + } + elseif ($entity->original->id() != $entity->id()) { + // Clear the node type cache to reflect the rename. + node_type_cache_reset(); + + $update_count = node_type_update_nodes($entity->original->id(), $entity->id()); + if ($update_count) { + drupal_set_message(format_plural($update_count, + 'Changed the content type of 1 post from %old-type to %type.', + 'Changed the content type of @count posts from %old-type to %type.', + array( + '%old-type' => $entity->original->id(), + '%type' => $entity->id(), + ))); + } + field_attach_rename_bundle('node_type', $entity->original->id(), $entity->id()); + } + else { + // Invalidate the cache tag of the updated node type only. + cache()->invalidateTags(array('node_type' => $entity->id())); + } + } + + /** + * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postDelete(). + */ + protected function postDelete($entities) { + parent::postDelete($entities); + + // Clear the node type cache to reflect the removal. + node_type_cache_reset(); + + foreach ($entities as $entity) { + field_attach_delete_bundle('node_type', $entity->id()); + } + } + +} 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 new file mode 100644 index 0000000..12a1b5f --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/NodeType.php @@ -0,0 +1,199 @@ +type; + } + + /** + * Returns the configured Node settings of a given module, if any. + * + * @param string $module + * The name of the module whose settings to return. + * + * @return array + * An associative array containing the module's Node settings. Note that + * this can be empty, and default values do not necessarily exist. + */ + public function getNodeSettings($module) { + if (isset($this->settings[$module]) && is_array($this->settings[$module])) { + return $this->settings[$module]; + } + return array(); + } + +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php index 36baad2..3dc4838 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.type.page')->set('settings.node.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..e97927c 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['settings[node][submitted]'] = TRUE; $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); // Create a node. @@ -48,16 +48,11 @@ function testPagePostInfo() { $node = $this->drupalGetNodeByTitle($edit["title"]); $elements = $this->xpath('//*[contains(@class,:class)]', array(':class' => 'submitted')); $this->assertEqual(count($elements), 1, 'Post information is displayed.'); - } - - /** - * Confirms absence of post information on a new node. - */ - function testPageNotPostInfo() { + $node->delete(); // Set "Basic page" content type to display post information. $edit = array(); - $edit['node_submitted'] = FALSE; + $edit['settings[node][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 c51b5a0..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 = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); - $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 = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); - $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 = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); - $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 = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); - $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 944af15..f9229f9 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeTypeTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTypeTest.php @@ -43,7 +43,6 @@ function testNodeTypeGetFunctions() { $this->assertEqual($node_types['article'], node_type_load('article'), 'Correct node type has been returned.'); $this->assertEqual($node_types['article']->name, node_type_get_label('article'), 'Correct node type name has been returned.'); - $this->assertEqual($node_types['page']->base, node_type_get_base('page'), 'Correct node type base has been returned.'); } /** @@ -53,7 +52,7 @@ function testNodeTypeCreation() { // Create a content type programmaticaly. $type = $this->drupalCreateContentType(); - $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $type->type))->fetchField(); + $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 +71,7 @@ function testNodeTypeCreation() { 'type' => 'foo', ); $this->drupalPost('admin/structure/types/add', $edit, t('Save content type')); - $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => 'foo'))->fetchField(); + $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..1cf2db9 100644 --- a/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php @@ -52,13 +52,15 @@ function testPagePreview() { /** * Checks the node preview functionality, when using revisions. + * + * @todo This test passes even with revisioning disabled. */ function testPagePreviewWithRevisions() { $langcode = LANGUAGE_NOT_SPECIFIED; $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.type.page')->set('settings.node.options', array('status', 'revision'))->save(); // Fill in node creation form and preview node. $edit = array(); diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index 0959e76..3c76a39 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -1017,44 +1017,35 @@ function hook_ranking() { /** * Respond to node type creation. * - * This hook is invoked from node_type_save() after the node type is added to - * the database. - * - * @param $info - * The node type object that is being created. + * @param \Drupal\node\Plugin\Core\Entity\NodeType $type + * The node type entity that was created. */ -function hook_node_type_insert($info) { - drupal_set_message(t('You have just created a content type with a machine name %type.', array('%type' => $info->type))); +function hook_node_type_insert(\Drupal\node\Plugin\Core\Entity\NodeType $type) { + drupal_set_message(t('You have just created a content type with a machine name %type.', array('%type' => $type->id()))); } /** * Respond to node type updates. * - * This hook is invoked from node_type_save() after the node type is updated in - * the database. - * - * @param $info - * The node type object that is being updated. + * @param \Drupal\node\Plugin\Core\Entity\NodeType $type + * The node type entity that was updated. */ -function hook_node_type_update($info) { - if (!empty($info->old_type) && $info->old_type != $info->type) { - $setting = variable_get('comment_' . $info->old_type, COMMENT_NODE_OPEN); - variable_del('comment_' . $info->old_type); - variable_set('comment_' . $info->type, $setting); +function hook_node_type_update(\Drupal\node\Plugin\Core\Entity\NodeType $type) { + if ($type->original->id() != $type->id()) { + $setting = variable_get('comment_' . $type->original->id(), COMMENT_NODE_OPEN); + variable_del('comment_' . $type->original->id()); + variable_set('comment_' . $type->id(), $setting); } } /** * Respond to node type deletion. * - * This hook is invoked from node_type_delete() after the node type is removed - * from the database. - * - * @param $info - * The node type object that is being deleted. + * @param \Drupal\node\Plugin\Core\Entity\NodeType $type + * The node type entity that was deleted. */ -function hook_node_type_delete($info) { - variable_del('comment_' . $info->type); +function hook_node_type_delete(\Drupal\node\Plugin\Core\Entity\NodeType $type) { + variable_del('comment_' . $type->id()); } /** diff --git a/core/modules/node/node.install b/core/modules/node/node.install index 697d3a7..7a36a9a 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -5,6 +5,8 @@ * Install, update and uninstall functions for the node module. */ +use Drupal\Component\Uuid\Uuid; + /** * Implements hook_schema(). */ @@ -34,7 +36,7 @@ function node_schema() { 'default' => NULL, ), 'type' => array( - 'description' => 'The {node_type}.type of this node.', + 'description' => 'The type of this node.', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, @@ -281,103 +283,6 @@ function node_schema() { ), ); - $schema['node_type'] = array( - 'description' => 'Stores information about all defined {node} types.', - 'fields' => array( - 'type' => array( - 'description' => 'The machine-readable name of this type.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - ), - 'name' => array( - 'description' => 'The human-readable name of this type.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'translatable' => TRUE, - ), - 'base' => array( - 'description' => 'The base string used to construct callbacks corresponding to this node type.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - ), - 'module' => array( - 'description' => 'The module defining this node type.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - ), - 'description' => array( - 'description' => 'A brief description of this type.', - 'type' => 'text', - 'not null' => TRUE, - 'size' => 'medium', - 'translatable' => TRUE, - ), - 'help' => array( - 'description' => 'Help information shown to the user when creating a {node} of this type.', - 'type' => 'text', - 'not null' => TRUE, - 'size' => 'medium', - 'translatable' => TRUE, - ), - 'has_title' => array( - 'description' => 'Boolean indicating whether this type uses the {node}.title field.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'size' => 'tiny', - ), - 'title_label' => array( - 'description' => 'The label displayed for the title field on the edit form.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'translatable' => TRUE, - ), - 'custom' => array( - 'description' => 'A boolean indicating whether this type is defined by a module (FALSE) or by a user via Add content type (TRUE).', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'size' => 'tiny', - ), - 'modified' => array( - 'description' => 'A boolean indicating whether this type has been modified by an administrator; currently not used in any way.', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'size' => 'tiny', - ), - 'locked' => array( - 'description' => 'A boolean indicating whether the administrator can change the machine name of this type.', - 'type' => 'int', - 'not null' => TRUE, - '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', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - ), - ), - 'primary key' => array('type'), - ); - $schema['block_node_type'] = array( 'description' => 'Sets up display criteria for blocks based on content types', 'fields' => array( @@ -397,7 +302,7 @@ function node_schema() { 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, - 'description' => "The machine-readable name of this type from {node_type}.type.", + 'description' => "The machine-readable name of this type.", ), ), 'primary key' => array('module', 'delta', 'type'), @@ -434,17 +339,9 @@ function node_install() { */ function node_uninstall() { // Delete node type variables. - $types = db_query('SELECT type FROM {node_type}')->fetchCol(); - foreach ($types as $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(); + $types = config_get_storage_names_with_prefix('node.type.'); + foreach ($types as $config_name) { + $type = config($config_name)->get('type'); config('language.settings')->clear('node. ' . $type . '.language.default_configuration')->save(); } @@ -734,6 +631,51 @@ function node_update_8013() { } /** + * Convert node types into configuration. + * + * @ingroup config_upgrade + */ +function node_update_8014() { + $uuid = new Uuid(); + // Note: {node_type}.name was the label, .type the machine name. + $result = db_query('SELECT * FROM {node_type}') + ->execute() + ->fetchAllAssoc('type', PDO::FETCH_ASSOC); + foreach ($result as $id => $node_type) { + $config = config('node.type.' . $id); + // Node type. + $config->setData($node_type); + $config->set('uuid', $uuid->generate()); + $config->set('langcode', LANGUAGE_NOT_SPECIFIED); + + // Node type settings. + $variables = db_query('SELECT name, value FROM {variable} WHERE name IN (:names)', array( + ':names' => array( + 'node_submitted_' . $id, + 'node_preview_' . $id, + 'node_options_' . $id, + ), + ))->execute()->fetchAllKeyed(); + // There are not necessarily values for all settings, so pollute defaults. + $variables += array( + 'node_submitted_' . $id => TRUE, + 'node_preview_' . $id => 1, // DRUPAL_OPTIONAL + 'node_options_' . $id => array('status', 'promote'), + ); + foreach ($variables as $name => $value) { + // Turn e.g. 'node_submitted_ID' into 'submitted'. + $name = str_replace(array('node_', '_' . $id), '', $name); + $value = unserialize($value); + $config->set('settings.node.' . $name, $value); + + update_variable_del($name); + } + $config->save(); + update_config_manifest_add('node.type', array($id)); + } +} + +/** * @} End of "addtogroup updates-7.x-to-8.x" * The next series of updates should start at 9000. */ diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 9e8fbd3..5ea4490 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -17,6 +17,7 @@ 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; @@ -196,15 +197,12 @@ function node_entity_info(&$info) { ); } - // 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, + foreach (node_type_get_names() as $id => $label) { + $info['node']['bundles'][$id] = array( + 'label' => $label, 'admin' => array( 'path' => 'admin/structure/types/manage/%node_type', - 'real path' => 'admin/structure/types/manage/' . $type, + 'real path' => 'admin/structure/types/manage/' . $id, 'bundle argument' => 4, 'access arguments' => array('administer content types'), ), @@ -230,6 +228,21 @@ function node_entity_display_alter(EntityDisplay $display, $context) { /** * Entity URI callback. * + * @param \Drupal\node\Plugin\Core\Entity\NodeType $type + * A node type entity. + * + * @return array + * An array with 'path' as the key and the path to the entity as its value. + */ +function node_type_uri(NodeType $type) { + return array( + 'path' => 'admin/structure/types/manage/' . $type->id(), + ); +} + +/** + * Entity URI callback. + * * @param Drupal\node\Node $node * A node entity. * @@ -323,51 +336,44 @@ 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. + * @return array + * An array of node type entities, keyed by ID. * - * @see _node_types_build() * @see node_type_load() */ function node_type_get_types() { - return _node_types_build()->types; -} - -/** - * Returns the node type base of the passed node or node type string. - * - * The base indicates which module implements this node type and is used to - * execute node-type-specific hooks. For types defined in the user interface - * and managed by node.module, the base is 'node_content'. - * - * @param string $type - * A string that indicates the node type to return. - * - * @return string|false - * The node type base or FALSE if the node type is not found. - * - * @see node_invoke() - */ -function node_type_get_base($type) { - $types = _node_types_build()->types; - return isset($types[$type]) && isset($types[$type]->base) ? $types[$type]->base : FALSE; + return entity_load_multiple('node_type'); } /** * 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 + * @return array * An array of node type labels, keyed by the node type name. - * - * @see _node_types_build() */ function node_type_get_names() { - return _node_types_build()->names; + $cid = 'node_type:names:' . language(LANGUAGE_TYPE_INTERFACE)->langcode; + if ($cache = cache()->get($cid)) { + return $cache->data; + } + // Not using node_type_get_types() or entity_load_multiple() here, to allow + // this function being used in hook_entity_info() implementations. + // @todo Consider to convert this into a generic config entity helper. + $config_names = config_get_storage_names_with_prefix('node.type.'); + $names = array(); + foreach ($config_names as $config_name) { + $config = config($config_name); + $names[$config->get('type')] = $config->get('name'); + } + cache()->set($cid, $names, CacheBackendInterface::CACHE_PERMANENT, array( + 'node_type' => array_keys($names), + // @see node_type_cache_reset() + 'node_types' => TRUE, + )); + return $names; } /** @@ -378,9 +384,11 @@ function node_type_get_names() { * * @return string|false * The node type label or FALSE if the node type is not found. + * + * @todo D8: Remove this. */ function node_type_get_label($name) { - $types = _node_types_build()->names; + $types = node_type_get_names(); return isset($types[$name]) ? $types[$name] : FALSE; } @@ -392,50 +400,35 @@ function node_type_get_label($name) { * * @return string|false * The node type label or FALSE if the node type is not found. + * + * @todo D8: Add this as generic helper method for config entities representing + * entity bundles. */ function node_get_type_label($node) { - $types = _node_types_build()->names; + $types = node_type_get_names(); return isset($types[$node->type]) ? $types[$node->type] : FALSE; } /** - * Title callback: Returns the sanitized node type name. - * - * @param $node_type - * The node type object. - * - * @return - * The node type name that is safe for printing. - */ -function node_type_get_clean_name($node_type) { - return check_plain($node_type->name); -} - -/** * Description callback: Returns the node type description. * - * @param $node_type + * @param \Drupal\node\Plugin\Core\Entity\NodeType $node_type * The node type object. * - * @return + * @return string * The node type description. */ -function node_type_get_description($node_type) { +function node_type_get_description(NodeType $node_type) { return $node_type->description; } /** - * Updates the database cache of node types. + * Updates the 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() + * @todo D8: Replace all instances with node_type_cache_reset(). */ function node_types_rebuild() { - _node_types_build(TRUE); + node_type_cache_reset(); } /** @@ -444,94 +437,11 @@ function node_types_rebuild() { * @param $name * The machine name of a node type to load. * - * @return + * @return \Drupal\node\Plugin\Core\Entity\NodeType * A node type object or FALSE if $name does not exist. */ function node_type_load($name) { - $types = _node_types_build()->types; - return isset($types[$name]) ? $types[$name] : FALSE; -} - -/** - * Saves a node type to the database. - * - * @param object $info - * The node type to save; an object with the following properties: - * - type: A string giving the machine name of the node type. - * - name: A string giving the human-readable name of the node type. - * - base: A string that indicates the base string for hook functions. For - * example, 'node_content' is the value used by the UI when creating a new - * node type. - * - description: A string that describes the node type. - * - help: A string giving the help information shown to the user when - * creating a node of this type. - * - custom: TRUE or FALSE indicating whether this type is defined by a module - * (FALSE) or by a user (TRUE) via Add Content Type. - * - modified: TRUE or FALSE indicating whether this type has been modified by - * an administrator. Currently not used in any way. - * - locked: TRUE or FALSE indicating whether the administrator can change the - * machine name of this type. - * - disabled: TRUE or FALSE indicating whether this type has been disabled. - * - has_title: TRUE or FALSE indicating whether this type uses the node title - * field. - * - title_label: A string containing the label for the title. - * - module: A string giving the module defining this type of node. - * - orig_type: A string giving the original machine-readable name of this - * node type. This may be different from the current type name if the locked - * field is 0. - * - * @return int - * A status flag indicating the outcome of the operation, either SAVED_NEW or - * SAVED_UPDATED. - */ -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); - - $fields = 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) { - db_update('node_type') - ->fields($fields) - ->condition('type', $existing_type) - ->execute(); - - 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 { - $fields['orig_type'] = (string) $type->orig_type; - db_insert('node_type') - ->fields($fields) - ->execute(); - - 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(); - - return $status; + return entity_load('node_type', $name); } /** @@ -545,10 +455,10 @@ function node_type_save($info) { * @return * Body field instance. */ -function node_add_body_field($type, $label = 'Body') { +function node_add_body_field(NodeType $type, $label = 'Body') { // Add or remove the body field, as needed. $field = field_info_field('body'); - $instance = field_info_instance('node', 'body', $type->type); + $instance = field_info_instance('node', 'body', $type->id()); if (empty($field)) { $field = array( 'field_name' => 'body', @@ -561,7 +471,7 @@ function node_add_body_field($type, $label = 'Body') { $instance = array( 'field_name' => 'body', 'entity_type' => 'node', - 'bundle' => $type->type, + 'bundle' => $type->id(), 'label' => $label, 'widget' => array('type' => 'text_textarea_with_summary'), 'settings' => array('display_summary' => TRUE), @@ -629,196 +539,32 @@ function node_field_extra_fields() { } /** - * Deletes a node type from the database. - * - * @param $name - * The machine name of the node type to delete. - */ -function node_type_delete($name) { - $type = node_type_load($name); - db_delete('node_type') - ->condition('type', $name) - ->execute(); - field_attach_delete_bundle('node', $name); - module_invoke_all('node_type_delete', $type); - - // Clear the node type cache. - node_type_cache_reset(); -} - -/** * Updates all nodes of one type to be of another type. * - * @param $old_type + * @param $old_id * The current node type of the nodes. - * @param $type + * @param $new_id * The new node type of the nodes. * - * @return + * @return int * The number of nodes whose node type field was modified. */ -function node_type_update_nodes($old_type, $type) { +function node_type_update_nodes($old_id, $new_id) { return db_update('node') - ->fields(array('type' => $type)) - ->condition('type', $old_type) + ->fields(array('type' => $new_id)) + ->condition('type', $old_id) ->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 - * (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 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. - */ -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']; - } - } - $query = 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; - } - 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() { + // @see node_type_get_names() + // @see NodeType::postSave() cache()->deleteTags(array('node_types' => TRUE)); - drupal_static_reset('_node_types_build'); -} - -/** - * Sets the default values for a node type. - * - * The defaults are appropriate for a type defined through hook_node_info(), - * since 'custom' is TRUE for types defined in the user interface, and FALSE - * for types defined by modules. (The 'custom' flag prevents types from being - * deleted through the user interface.) Also, the default for 'locked' is TRUE, - * which prevents users from changing the machine name of the type. - * - * @param $info - * (optional) An object or array containing values to override the defaults. - * See hook_node_info() for details on what the array elements mean. Defaults - * to an empty array. - * - * @return - * A node type object, with missing values in $info set to their defaults. - * - * @see hook_node_ifo() - */ -function node_type_set_defaults($info = array()) { - $info = (array) $info; - $new_type = $info + array( - 'type' => '', - 'name' => '', - 'base' => '', - 'description' => '', - 'help' => '', - 'custom' => 0, - 'modified' => 0, - 'locked' => 1, - 'disabled' => 0, - 'is_new' => 1, - 'has_title' => 1, - 'title_label' => 'Title', - ); - $new_type = (object) $new_type; - - // If the type has no title, set an empty label. - if (!$new_type->has_title) { - $new_type->title_label = ''; - } - if (empty($new_type->module)) { - $new_type->module = $new_type->base == 'node_content' ? 'node' : ''; - } - $new_type->orig_type = isset($info['type']) ? $info['type'] : ''; - - return $new_type; + // @todo Possibly needless: + entity_get_controller('node_type')->resetCache(); } /** @@ -871,16 +617,19 @@ function node_rdf_mapping() { /** * Determines whether a node hook exists. * - * @param string $type - * A string containing the node type. + * @param string $id + * A string containing the node type ID. * @param $hook * A string containing the name of the hook. * * @return string|false * A string containing the function name or FALSE if it isn't found. */ -function node_hook($type, $hook) { - $base = node_type_get_base($type); +function node_hook($id, $hook) { + if (!$type = entity_load('node_type', $id)) { + return FALSE; + } + $base = $type->get('base'); return module_hook($base, $hook) ? $base . '_' . $hook : FALSE; } @@ -1156,7 +905,9 @@ 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)) { + // Avoid loading the entire node type config entity here. + $submitted = config('node.type.' . $node->type)->get('settings.node.submitted'); + if (!isset($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')) { @@ -1668,18 +1419,17 @@ function node_menu() { ); $items['admin/structure/types/add'] = array( 'title' => 'Add content type', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('node_type_form'), + 'page callback' => 'node_type_add', 'access arguments' => array('administer content types'), 'type' => MENU_LOCAL_ACTION, 'file' => 'content_types.inc', ); $items['admin/structure/types/manage/%node_type'] = array( 'title' => 'Edit content type', - 'title callback' => 'node_type_page_title', + 'title callback' => 'entity_page_label', 'title arguments' => array(4), - 'page callback' => 'drupal_get_form', - 'page arguments' => array('node_type_form', 4), + 'page callback' => 'node_type_edit', + 'page arguments' => array(4), 'access arguments' => array('administer content types'), 'file' => 'content_types.inc', ); @@ -1689,6 +1439,7 @@ function node_menu() { ); $items['admin/structure/types/manage/%node_type/delete'] = array( 'title' => 'Delete', + 'page callback' => 'drupal_get_form', 'page arguments' => array('node_type_delete_confirm', 4), 'access arguments' => array('administer content types'), 'file' => 'content_types.inc', @@ -1716,7 +1467,7 @@ function node_menu() { 'type' => MENU_CALLBACK, ); $items['node/add/%node_type'] = array( - 'title callback' => 'node_type_get_clean_name', + 'title callback' => 'entity_page_label', 'title arguments' => array(2), 'page callback' => 'node_add', 'page arguments' => array(2), @@ -1816,21 +1567,6 @@ function node_menu_local_tasks_alter(&$data, $router_item, $root_path) { } /** - * Title callback: Provides the title for a node type edit form. - * - * @param $type - * The node type object. - * - * @return string - * An unsanitized string that is the title of the node type edit form. - * - * @see node_menu() - */ -function node_type_page_title($type) { - return $type->name; -} - -/** * Title callback: Displays the node's title. * * @param Drupal\node\Node $node @@ -2017,8 +1753,26 @@ function node_form_block_admin_configure_alter(&$form, &$form_state) { * Cleans up the {block_node_type} table from modules' blocks. */ function node_modules_uninstalled($modules) { + // Remove module-specific settings from all node types. + $config_names = config_get_storage_names_with_prefix('node.type.'); + foreach ($config_names as $config_name) { + $config = config($config_name); + $changed = FALSE; + foreach ($modules as $module) { + if ($config->get('settings.' . $module)) { + $config->clear('settings.' . $module); + $changed = TRUE; + } + } + if ($changed) { + $config->save(); + } + } + // Remove the block visibility settings for all node types. - $block_configs = config_get_storage_names_with_prefix('plugin.core.block'); + // @todo Why does this remove node type visibility settings from all block + // instances whenever ANY module is uninstalled? + $block_configs = config_get_storage_names_with_prefix('plugin.core.block.'); foreach ($block_configs as $config_id) { $config = config($config_id); $node_types = $config->get('visibility.node_type.types'); @@ -2775,7 +2529,8 @@ 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)) { + $node_settings = $type->getNodeSettings('node'); + if (!isset($node_settings['permissions']) || !empty($node_settings['permissions'])) { $configured_types[$name] = $type; } } diff --git a/core/modules/poll/config/node.type.poll.yml b/core/modules/poll/config/node.type.poll.yml new file mode 100644 index 0000000..6947662 --- /dev/null +++ b/core/modules/poll/config/node.type.poll.yml @@ -0,0 +1,16 @@ +type: poll +name: Poll +base: poll +module: poll +locked: '1' +description: 'A poll is a question with a set of possible responses. A poll, once created, automatically provides a simple running count of the number of votes received for each response.' +help: '' +has_title: '1' +title_label: Question +create_body: FALSE +settings: + node: + # Not promoted to front page. + options: + - status +langcode: und 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/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 85aba85..7e44b13 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -290,48 +290,31 @@ protected function drupalCreateNode(array $settings = array()) { /** * Creates a custom content type based on default settings. * - * @param $settings + * @param array $values * An array of settings to change from the defaults. * Example: 'type' => 'foo'. - * @return + * @return \Drupal\node\Plugin\Core\Entity\NodeType * Created content type. */ - protected function drupalCreateContentType($settings = array()) { + protected function drupalCreateContentType($values = array()) { // Find a non-existent random type name. - do { - $name = strtolower($this->randomName(8)); - } while (node_type_load($name)); - - // Populate defaults array. - $defaults = array( - 'type' => $name, - 'name' => $name, - 'base' => 'node_content', - 'description' => '', - 'help' => '', - 'title_label' => 'Title', - 'body_label' => 'Body', - 'has_title' => 1, - 'has_body' => 1, - ); - // Imposed values for a custom type. - $forced = array( - 'orig_type' => '', - 'old_type' => '', - 'module' => 'node', - 'custom' => 1, - 'modified' => 1, - 'locked' => 0, + if (!isset($values['type'])) { + do { + $id = strtolower($this->randomName(8)); + } while (node_type_load($id)); + } + else { + $id = $values['type']; + } + $values += array( + 'type' => $id, + 'name' => $id, ); - $type = $forced + $settings + $defaults; - $type = (object) $type; - - $saved_type = node_type_save($type); - node_types_rebuild(); + $type = entity_create('node_type', $values); + $status = $type->save(); menu_router_rebuild(); - node_add_body_field($type); - $this->assertEqual($saved_type, SAVED_NEW, t('Created content type %type.', array('%type' => $type->type))); + $this->assertEqual($status, SAVED_NEW, t('Created content type %type.', array('%type' => $type->id()))); // Reset permissions so that permissions for this content type are available. $this->checkPermissions(array(), TRUE); diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 7e95a03..5be4f93 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -2610,7 +2610,7 @@ function hook_schema() { 'default' => 0, ), 'type' => array( - 'description' => 'The {node_type} of this node.', + 'description' => 'The type of this node.', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyTestBase.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyTestBase.php index 67adc3a..cf005e8 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyTestBase.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyTestBase.php @@ -66,14 +66,9 @@ function setUp() { * @see http://drupal.org/node/1708692 */ protected function mockStandardInstall() { - $type = array( + $type = $this->drupalCreateContentType(array( 'type' => 'article', - ); - - $type = node_type_set_defaults($type); - node_type_save($type); - node_add_body_field($type); - + )); // Create the vocabulary for the tag field. $this->vocabulary = entity_create('taxonomy_vocabulary', array( 'name' => 'Views testing tags', diff --git a/core/modules/translation/translation.module b/core/modules/translation/translation.module index a15cd3e..3d68d70 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['#node_type']->type, FALSE), + '#default_value' => variable_get('node_type_language_translation_enabled_' . $form_state['entity']->id(), FALSE), '#element_validate' => array('translation_node_type_language_translation_enabled_validate'), '#prefix' => "", ); diff --git a/core/profiles/standard/config/node.type.article.yml b/core/profiles/standard/config/node.type.article.yml new file mode 100644 index 0000000..36b80db --- /dev/null +++ b/core/profiles/standard/config/node.type.article.yml @@ -0,0 +1,8 @@ +type: article +name: Article +description: 'Use articles for time-sensitive content like news, press releases or blog posts.' +help: '' +has_title: '1' +title_label: Title +settings: { } +langcode: und diff --git a/core/profiles/standard/config/node.type.page.yml b/core/profiles/standard/config/node.type.page.yml new file mode 100644 index 0000000..0a30efc --- /dev/null +++ b/core/profiles/standard/config/node.type.page.yml @@ -0,0 +1,14 @@ +type: page +name: 'Basic page' +description: 'Use basic pages for your static content, such as an ''About us'' page.' +help: '' +has_title: '1' +title_label: Title +settings: + node: + # Not promoted to front page. + options: + - status + # Hide author/submission info. + submitted: FALSE +langcode: und diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install index 5cb8fb3..8ddf331 100644 --- a/core/profiles/standard/standard.install +++ b/core/profiles/standard/standard.install @@ -78,36 +78,6 @@ function standard_install() { // Set front page to "node". config('system.site')->set('page.front', 'node')->save(); - // Insert default pre-defined node types into the database. For a complete - // list of available node type attributes, refer to the node type API - // documentation at: http://api.drupal.org/api/HEAD/function/hook_node_info. - $types = array( - array( - 'type' => 'page', - 'name' => st('Basic page'), - 'base' => 'node_content', - 'description' => st("Use basic pages for your static content, such as an 'About us' page."), - 'custom' => 1, - 'modified' => 1, - 'locked' => 0, - ), - array( - 'type' => 'article', - 'name' => st('Article'), - 'base' => 'node_content', - 'description' => st('Use articles for time-sensitive content like news, press releases or blog posts.'), - 'custom' => 1, - 'modified' => 1, - 'locked' => 0, - ), - ); - - foreach ($types as $type) { - $type = node_type_set_defaults($type); - node_type_save($type); - node_add_body_field($type); - } - // Insert default pre-defined RDF mapping into the database. $rdf_mappings = array( array( @@ -137,12 +107,8 @@ function standard_install() { } // Default "Basic page" to not be promoted and have comments disabled. - variable_set('node_options_page', array('status')); variable_set('comment_page', COMMENT_NODE_HIDDEN); - // Don't display date and author information for "Basic page" nodes by default. - variable_set('node_submitted_page', FALSE); - // Allow visitor account creation with administrative approval. $user_settings = config('user.settings'); $user_settings->set('register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)->save();