diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php index 7c5f7ed..69211d0 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormController.php +++ b/core/lib/Drupal/Core/Entity/EntityFormController.php @@ -60,7 +60,15 @@ public function setOperation($operation) { * {@inheritdoc} */ public function getBaseFormID() { - return $this->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. + $base_form_id = $this->entity->entityType() . '_form'; + if ($base_form_id == $this->getFormID()) { + $base_form_id = ''; + } + return $base_form_id; } /** diff --git a/core/lib/Drupal/Core/Entity/EntityListController.php b/core/lib/Drupal/Core/Entity/EntityListController.php index b5a5f9c..818ba2b 100644 --- a/core/lib/Drupal/Core/Entity/EntityListController.php +++ b/core/lib/Drupal/Core/Entity/EntityListController.php @@ -182,7 +182,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 7d70a02..610808b 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 1fe04c8..a8dfd59 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -6,6 +6,7 @@ */ use Drupal\Core\Entity\EntityInterface; +use Drupal\node\NodeTypeInterface; use Drupal\Core\Language\Language; use Drupal\entity\Plugin\Core\Entity\EntityDisplay; use Drupal\Core\Template\Attribute; @@ -1230,16 +1231,16 @@ function book_type_is_allowed($type) { * Updates book.settings configuration object 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(NodeTypeInterface $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'); - $old_key = array_search($type->old_type, $allowed_types); + $old_key = array_search($type->original->id(), $allowed_types); + if ($old_key !== FALSE) { - // Replace the old machine-readable name with the new machine-readable - // name. - $allowed_types[$old_key] = $type->type; + $allowed_types[$type->id()] = $allowed_types[$old_key] ? $type->id() : 0; + unset($allowed_types[$old_key]); // Ensure that the allowed_types array is sorted consistently. // @see book_admin_settings_submit() sort($allowed_types); @@ -1247,8 +1248,8 @@ function book_node_type_update($type) { } // 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..7ec1e60 --- /dev/null +++ b/core/modules/book/config/node.type.book.yml @@ -0,0 +1,18 @@ +type: book +name: 'Book page' +description: 'Books have a built-in hierarchical navigation. Use for handbooks or tutorials.' +help: '' +has_title: '1' +title_label: Title +settings: + node: + preview: '1' + options: + status: status + # Not promoted to front page. + promote: '0' + sticky: '0' + revision: '0' + submitted: '1' +status: '1' +langcode: und diff --git a/core/modules/comment/comment.install b/core/modules/comment/comment.install index 71b2cf0..7f95162 100644 --- a/core/modules/comment/comment.install +++ b/core/modules/comment/comment.install @@ -70,7 +70,7 @@ function comment_modules_enabled($modules) { // module is enabled. if (in_array('comment', $modules)) { // Ensure that the list of node types reflects newly enabled modules. - node_types_rebuild(); + node_type_cache_reset(); // Create comment body fields for each node type, if needed. foreach (node_type_get_types() as $type => $info) { diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index b0f71f1..9346a96 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -9,6 +9,7 @@ * book page, etc. */ +use Drupal\node\NodeTypeInterface; use Drupal\entity\Plugin\Core\Entity\EntityDisplay; use Drupal\file\Plugin\Core\Entity\File; use Drupal\Core\Entity\EntityInterface; @@ -114,25 +115,6 @@ function comment_entity_bundle_info() { } /** - * Loads the comment bundle name corresponding a given content type. - * - * This function is used as a menu loader callback in comment_menu(). - * - * @param $name - * The machine name of the node type whose comment fields are to be edited. - * - * @return - * The comment bundle name corresponding to the node type. - * - * @see comment_menu_alter() - */ -function comment_node_type_load($name) { - if ($type = node_type_load($name)) { - return 'comment_node_' . $type->type; - } -} - -/** * Entity URI callback. */ function comment_uri(Comment $comment) { @@ -309,9 +291,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) { - entity_invoke_bundle_hook('rename', 'comment', 'comment_node_' . $info->old_type, 'comment_node_' . $info->type); +function comment_node_type_update(NodeTypeInterface $type) { + if ($type->original->id() != $type->id()) { + entity_invoke_bundle_hook('rename', 'comment', 'comment_node_' . $type->original->id(), 'comment_node_' . $type->id()); } } @@ -990,6 +972,7 @@ function comment_view_multiple($comments, $view_mode = 'full', $langcode = NULL) */ function comment_form_node_type_form_alter(&$form, $form_state) { if (isset($form['type'])) { + $node_type = $form_state['controller']->getEntity(); $form['comment'] = array( '#type' => 'details', '#title' => t('Comment settings'), @@ -1008,7 +991,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_' . $node_type->id(), COMMENT_NODE_OPEN), '#options' => array( COMMENT_NODE_OPEN => t('Open'), COMMENT_NODE_CLOSED => t('Closed'), @@ -1018,19 +1001,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_' . $node_type->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_' . $node_type->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_' . $node_type->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'), @@ -1041,17 +1024,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_' . $node_type->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_' . $node_type->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_' . $node_type->id(), DRUPAL_OPTIONAL), '#options' => array( DRUPAL_DISABLED => t('Disabled'), DRUPAL_OPTIONAL => t('Optional'), @@ -1062,7 +1045,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_' . $node_type->id(), $comment_form, $comment_form_state); array_unshift($form['#submit'], 'comment_translation_configuration_element_submit'); } } @@ -1084,7 +1067,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['controller']->getEntity()->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/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php index 24cb092..fb0ca87 100644 --- a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php +++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php @@ -202,19 +202,19 @@ public function testFieldComponent() { * Tests renaming and deleting a bundle. */ public function testRenameDeleteBundle() { - $this->enableModules(array('field_sql_storage', 'field_test', 'node', 'system')); - $this->installSchema('node', array('node_type')); + $this->enableModules(array('field_sql_storage', 'field_test', 'node', 'system', 'text')); $this->installSchema('system', array('variable')); + $this->installSchema('node', array('node')); // Create a node bundle and display object. - node_type_save((object) array('type' => 'article')); + entity_create('node_type', array('type' => 'article'))->save(); entity_get_display('node', 'article', 'default')->save(); // Rename the article bundle and assert the entity display is renamed. $info = node_type_load('article'); $info->old_type = 'article'; $info->type = 'article_rename'; - node_type_save($info); + $info->save(); $old_display = entity_load('entity_display', 'node.article.default'); $this->assertFalse($old_display); $new_display = entity_load('entity_display', 'node.article_rename.default'); @@ -222,7 +222,7 @@ public function testRenameDeleteBundle() { $this->assertEqual('node.article_rename.default', $new_display->id); // Delete the bundle. - node_type_delete('article_rename'); + entity_delete_multiple('node_type', array('article_rename')); $display = entity_load('entity_display', 'node.article_rename.default'); $this->assertFalse($display); } diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc index 7965679..a367e01 100644 --- a/core/modules/field/field.attach.inc +++ b/core/modules/field/field.attach.inc @@ -1562,7 +1562,7 @@ function field_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) { } } - // Clear the cache. + // Clear the field cache. field_cache_clear(); // Update bundle settings. @@ -1590,8 +1590,10 @@ function field_entity_bundle_delete($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/lib/Drupal/field/Tests/TranslationTest.php b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php index ae52f2e..79f0bb6 100644 --- a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php @@ -37,7 +37,6 @@ public static function getInfo() { function setUp() { parent::setUp(); $this->installSchema('language', array('language')); - $this->installSchema('node', array('node_type')); $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module index fefd9a1..08d9922 100644 --- a/core/modules/field_ui/field_ui.module +++ b/core/modules/field_ui/field_ui.module @@ -278,13 +278,11 @@ function field_ui_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) { */ 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)) { - $form['actions']['save_continue'] = array( - '#type' => 'submit', - '#value' => t('Save and manage fields'), - '#weight' => 45, - ); - $form['#submit'][] = 'field_ui_form_node_type_form_submit'; + if ($form_state['controller']->getEntity()->isNew()) { + $form['actions']['save_continue'] = $form['actions']['submit']; + $form['actions']['save_continue']['#value'] = t('Save and manage fields'); + $form['actions']['save_continue']['#weight'] = $form['actions']['save_continue']['#weight'] + 5; + $form['actions']['save_continue']['#submit'][] = 'field_ui_form_node_type_form_submit'; } } 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..1349dda --- /dev/null +++ b/core/modules/forum/config/node.type.forum.yml @@ -0,0 +1,18 @@ +type: forum +name: 'Forum topic' +description: 'A forum topic starts a new discussion thread within a forum.' +help: '' +has_title: '1' +title_label: Subject +settings: + node: + preview: '1' + options: + status: status + # Not promoted to front page. + promote: '0' + sticky: '0' + revision: '0' + submitted: '1' +status: '1' +langcode: und diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install index 9f16099..e0219ff 100644 --- a/core/modules/forum/forum.install +++ b/core/modules/forum/forum.install @@ -11,11 +11,10 @@ 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')); + // Do not allow to delete the forum's node type machine name. + $locked = Drupal::state()->get('node.type.locked'); + $locked['forum'] = 'forum'; + Drupal::state()->set('node.type.locked', $locked); } /** @@ -106,11 +105,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']); } /** @@ -120,8 +114,6 @@ function forum_uninstall() { // Load the dependent Taxonomy module, in case it has been disabled. drupal_load('module', 'taxonomy'); - variable_del('node_options_forum'); - if ($field = field_info_field('taxonomy_forums')) { $field->delete(); } @@ -129,6 +121,10 @@ function forum_uninstall() { // Purge field data now to allow taxonomy module to be uninstalled // if this is the only field remaining. field_purge_batch(10); + // Allow to delete a forum's node type. + $locked = Drupal::state()->get('node.type.locked'); + unset($locked['forum']); + Drupal::state()->set('node.type.locked', $locked); } /** diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index c06edde..f41a5dd 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -427,20 +427,6 @@ function forum_node_load($nodes) { } /** - * Implements hook_node_info(). - */ -function forum_node_info() { - return array( - 'forum' => array( - 'name' => t('Forum topic'), - 'base' => 'forum', - 'description' => t('A forum topic starts a new discussion thread within a forum.'), - 'title_label' => t('Subject'), - ) - ); -} - -/** * Implements hook_permission(). */ function forum_permission() { diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php index c28b1dc..9cd6ae9 100644 --- a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php +++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php @@ -204,7 +204,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 6b45fa1..f18d494 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -5,6 +5,7 @@ * Add language handling functionality to Drupal. */ +use Drupal\node\NodeTypeInterface; use Drupal\Core\Language\Language; /** @@ -322,6 +323,18 @@ function language_configuration_element_process($element, &$form_state, &$form) ), ); + // Do not add the submit callback for the language content settings page, + // which is handled separately. + if (array_search('language_content_settings_form_submit', $form['#submit']) === FALSE) { + // 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']['#submit']) && array_search('language_configuration_element_submit', $form['actions']['submit']['#submit']) === FALSE) { + $form['actions']['submit']['#submit'][] = 'language_configuration_element_submit'; + } + elseif (array_search('language_configuration_element_submit', $form['#submit']) === FALSE) { + $form['#submit'][] = 'language_configuration_element_submit'; + } + } return $element; } @@ -412,10 +425,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(NodeTypeInterface $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/menu/menu.module b/core/modules/menu/menu.module index 68c3125..265372c 100644 --- a/core/modules/menu/menu.module +++ b/core/modules/menu/menu.module @@ -670,7 +670,7 @@ function menu_node_submit(EntityInterface $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['controller']->getEntity(); $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 deleted file mode 100644 index 156576b..0000000 --- a/core/modules/node/content_types.inc +++ /dev/null @@ -1,491 +0,0 @@ - $name) { - $type = $types[$key]; - $row = array(theme('node_admin_overview', array('name' => $name, 'type' => $type))); - if ($field_ui && user_access('administer node fields')) { - $links['fields'] = array( - 'title' => t('Manage fields'), - 'href' => 'admin/structure/types/manage/' . $type->type . '/fields', - 'weight' => 0, - ); - } - if ($field_ui && user_access('administer node display')) { - $links['display'] = array( - 'title' => t('Manage display'), - 'href' => 'admin/structure/types/manage/' . $type->type . '/display', - 'weight' => 5, - ); - } - - $links['edit'] = array( - 'title' => t('Edit'), - 'href' => 'admin/structure/types/manage/' . $type->type, - '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 $build; -} - -/** - * Returns HTML for a node type description for the content type admin page. - * - * @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. - * - * @return string - * An HTML-formatted string of the description for this node type. - * - * @ingroup themeable - */ -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; -} - -/** - * Form constructor for the node type editing form. - * - * @param $type - * (optional) An object representing the node type, when editing an existing - * node type. - * - * @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; - - $form['name'] = array( - '#title' => t('Name'), - '#type' => 'textfield', - '#default_value' => $type->name, - '#description' => t('The human-readable name of this content type. This text will be displayed as part of the list on the Add new content page. It is recommended that this name begin with a capital letter and contain only letters, numbers, and spaces. This name must be unique.'), - '#required' => TRUE, - '#size' => 30, - ); - - $form['type'] = array( - '#type' => 'machine_name', - '#default_value' => $type->type, - '#maxlength' => 32, - '#disabled' => $type->locked, - '#machine_name' => array( - 'exists' => 'node_type_load', - 'source' => array('name'), - ), - '#description' => t('A unique machine-readable name for this content type. It must only contain lowercase letters, numbers, and underscores. This name will be used for constructing the URL of the %node-add page, in which underscores will be converted into hyphens.', array( - '%node-add' => t('Add new content'), - )), - ); - - $form['description'] = array( - '#title' => t('Description'), - '#type' => 'textarea', - '#default_value' => $type->description, - '#description' => t('Describe this content type. The text will be displayed on the Add new content page.'), - ); - - $form['additional_settings'] = array( - '#type' => 'vertical_tabs', - '#attached' => array( - 'library' => array(array('node', 'drupal.content_types')), - ), - ); - - $form['submission'] = array( - '#type' => 'details', - '#title' => t('Submission form settings'), - '#group' => 'additional_settings', - ); - $form['submission']['title_label'] = array( - '#title' => t('Title field label'), - '#type' => 'textfield', - '#default_value' => $type->title_label, - '#required' => TRUE, - ); - if (!$type->has_title) { - // Avoid overwriting a content type that intentionally does not have a - // title field. - $form['submission']['title_label']['#attributes'] = array('disabled' => 'disabled'); - $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( - '#type' => 'radios', - '#title' => t('Preview before submitting'), - '#default_value' => variable_get('node_preview_' . $type->type, DRUPAL_OPTIONAL), - '#options' => array( - DRUPAL_DISABLED => t('Disabled'), - DRUPAL_OPTIONAL => t('Optional'), - DRUPAL_REQUIRED => t('Required'), - ), - ); - $form['submission']['help'] = array( - '#type' => 'textarea', - '#title' => t('Explanation or submission guidelines'), - '#default_value' => $type->help, - '#description' => t('This text will be displayed at the top of the page when creating or editing content of this type.'), - ); - $form['workflow'] = array( - '#type' => 'details', - '#title' => t('Publishing options'), - '#collapsed' => TRUE, - '#group' => 'additional_settings', - ); - $form['workflow']['node_options'] = array('#type' => 'checkboxes', - '#title' => t('Default options'), - '#default_value' => variable_get('node_options_' . $type->type, array('status', 'promote')), - '#options' => array( - 'status' => t('Published'), - 'promote' => t('Promoted to front page'), - 'sticky' => t('Sticky at top of lists'), - 'revision' => t('Create new revision'), - ), - '#description' => t('Users with the Administer content permission will be able to override these options.'), - ); - if (module_exists('language')) { - $form['language'] = array( - '#type' => 'details', - '#title' => t('Language settings'), - '#collapsed' => TRUE, - '#group' => 'additional_settings', - ); - - $language_configuration = language_get_default_configuration('node', $type->type); - $form['language']['language_configuration'] = array( - '#type' => 'language_configuration', - '#entity_information' => array( - 'entity_type' => 'node', - 'bundle' => $type->type, - ), - '#default_value' => $language_configuration, - ); - - $form['#submit'][] = 'language_configuration_element_submit'; - } - $form['display'] = array( - '#type' => 'details', - '#title' => t('Display settings'), - '#collapsed' => TRUE, - '#group' => 'additional_settings', - ); - $form['display']['node_submitted'] = array( - '#type' => 'checkbox', - '#title' => t('Display author and date information.'), - '#default_value' => variable_get('node_submitted_' . $type->type, TRUE), - '#description' => t('Author username and publish date will be displayed.'), - ); - $form['old_type'] = array( - '#type' => 'value', - '#value' => $type->type, - ); - $form['orig_type'] = array( - '#type' => 'value', - '#value' => isset($type->orig_type) ? $type->orig_type : '', - ); - $form['base'] = array( - '#type' => 'value', - '#value' => $type->base, - ); - $form['custom'] = array( - '#type' => 'value', - '#value' => $type->custom, - ); - $form['modified'] = array( - '#type' => 'value', - '#value' => $type->modified, - ); - $form['locked'] = array( - '#type' => 'value', - '#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; -} - -/** - * Helper function for teaser length choices. - * - * @param int $length - * An integer representing the desired length of the teaser string. - * - * @return string - * A string that reprents the teaser length options. - */ -function _node_characters($length) { - return ($length == 0) ? t('Unlimited') : format_plural($length, '1 character', '@count characters'); -} - -/** - * Form validation handler for node_type_form(). - * - * @see node_type_form_submit() - */ -function node_type_form_validate($form, &$form_state) { - $type = new stdClass(); - $type->type = $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 = $form_state['values']['old_type']; - - $types = node_type_get_names(); - - if (!$form_state['values']['locked']) { - // 'theme' conflicts with theme_node_form(). - // '0' is invalid, since elsewhere we check it using empty(). - if (in_array($type->type, array('0', 'theme'))) { - form_set_error('type', t("Invalid machine-readable name. Enter a name other than %invalid.", array('%invalid' => $type->type))); - } - } - - $names = array_flip($types); - - if (isset($names[$type->name]) && $names[$type->name] != $old_type) { - form_set_error('name', t('The human-readable name %name is already taken.', array('%name' => $type->name))); - } -} - -/** - * Form submission handler for node_type_form(). - * - * @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 = $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; - } - - $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']); - - // Save or reset persistent variable values. - foreach ($variables as $key => $value) { - $variable_new = $key . '_' . $type->type; - $variable_old = $key . '_' . $type->old_type; - - 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); - - node_types_rebuild(); - menu_router_rebuild(); - $t_args = array('%name' => $type->name); - - 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. - * - * @return - * Form array for delete confirmation form. - * - * @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); - - $message = t('Are you sure you want to delete the content type %type?', array('%type' => $type->name)); - - $num_nodes = db_query("SELECT COUNT(*) FROM {node} WHERE type = :type", array(':type' => $type->type))->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)) . '

'; - $form['description'] = array('#markup' => $caption); - return $form; - } - - $caption = '

' . t('This action cannot be undone.') . '

'; - - return confirm_form($form, $message, 'admin/structure/types', $caption, t('Delete')); -} - -/** - * Form submission handler for node_type_delete_confirm(). - */ -function node_type_delete_confirm_submit($form, &$form_state) { - node_type_delete($form_state['values']['type']); - - variable_del('node_preview_' . $form_state['values']['type']); - $t_args = array('%name' => $form_state['values']['name']); - 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/Form/NodeTypeDeleteConfirm.php b/core/modules/node/lib/Drupal/node/Form/NodeTypeDeleteConfirm.php new file mode 100644 index 0000000..23973c9 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Form/NodeTypeDeleteConfirm.php @@ -0,0 +1,95 @@ +database = $database; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) { + return new static( + $container->get('database') + ); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return t('Are you sure you want to delete the content type %type?', array('%type' => $this->entity->label())); + } + + /** + * {@inheritdoc} + */ + public function getCancelPath() { + return 'admin/structure/types'; + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, array &$form_state, Request $request = NULL) { + $num_nodes = $this->database->query("SELECT COUNT(*) FROM {node} WHERE type = :type", array(':type' => $this->entity->id()))->fetchField(); + if ($num_nodes) { + drupal_set_title($this->getQuestion(), 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' => $this->entity->label())) . '

'; + $form['description'] = array('#markup' => $caption); + return $form; + } + + return parent::buildForm($form, $form_state, $request); + } + + /** + * {@inheritdoc} + */ + public function submit(array $form, array &$form_state) { + $this->entity->delete(); + $t_args = array('%name' => $this->entity->label()); + drupal_set_message(t('The content type %name has been deleted.', $t_args)); + watchdog('node', 'Deleted content type %name.', $t_args, WATCHDOG_NOTICE); + + $form_state['redirect'] = $this->getCancelPath(); + } + +} diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php index e64db93..5865920 100644 --- a/core/modules/node/lib/Drupal/node/NodeFormController.php +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -18,6 +18,13 @@ class NodeFormController extends EntityFormController { /** + * The settings array. + * + * @var array + */ + protected $settings; + + /** * Prepares the node object. * * Fills in a few default values, and then invokes hook_node_prepare() on all @@ -28,13 +35,20 @@ class NodeFormController extends EntityFormController { protected function prepareEntity() { $node = $this->entity; // 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; @@ -47,7 +61,7 @@ protected function prepareEntity() { $node->log = NULL; } // Always use the default revision setting. - $node->setNewRevision(in_array('revision', $node_options)); + $node->setNewRevision(in_array('revision', $this->settings['options'])); module_invoke_all('node_prepare', $node); } @@ -240,7 +254,7 @@ public function form(array $form, array &$form_state) { protected function actions(array $form, array &$form_state) { $element = parent::actions($form, $form_state); $node = $this->entity; - $preview_mode = variable_get('node_preview_' . $node->type, DRUPAL_OPTIONAL); + $preview_mode = $this->settings['preview']; $element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])); diff --git a/core/modules/node/lib/Drupal/node/NodeTypeAccessController.php b/core/modules/node/lib/Drupal/node/NodeTypeAccessController.php new file mode 100644 index 0000000..40327e3 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeTypeAccessController.php @@ -0,0 +1,31 @@ +isLocked()) { + return FALSE; + } + return user_access('administer content types', $account); + } + +} 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..feab5fc --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeTypeFormController.php @@ -0,0 +1,255 @@ +entity; + if ($this->operation == 'add') { + drupal_set_title(t('Add content type')); + } + elseif ($this->operation == 'edit') { + drupal_set_title(t('Edit %label content type', array('%label' => $type->label())), PASS_THROUGH); + } + + $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->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, + ); + + $form['type'] = array( + '#type' => 'machine_name', + '#default_value' => $type->id(), + '#maxlength' => 32, + '#disabled' => $type->isLocked(), + '#machine_name' => array( + 'exists' => 'node_type_load', + 'source' => array('name'), + ), + '#description' => t('A unique machine-readable name for this content type. It must only contain lowercase letters, numbers, and underscores. This name will be used for constructing the URL of the %node-add page, in which underscores will be converted into hyphens.', array( + '%node-add' => t('Add new content'), + )), + ); + + $form['description'] = array( + '#title' => t('Description'), + '#type' => 'textarea', + '#default_value' => $type->description, + '#description' => t('Describe this content type. The text will be displayed on the Add new content page.'), + ); + + $form['additional_settings'] = array( + '#type' => 'vertical_tabs', + '#attached' => array( + 'library' => array(array('node', 'drupal.content_types')), + ), + ); + + $form['submission'] = array( + '#type' => 'details', + '#title' => t('Submission form settings'), + '#group' => 'additional_settings', + ); + $form['submission']['title_label'] = array( + '#title' => t('Title field label'), + '#type' => 'textfield', + '#default_value' => $type->title_label, + '#required' => TRUE, + ); + if (!$type->has_title) { + // Avoid overwriting a content type that intentionally does not have a + // title field. + $form['submission']['title_label']['#attributes'] = array('disabled' => 'disabled'); + $form['submission']['title_label']['#description'] = t('This content type does not have a title field.'); + $form['submission']['title_label']['#required'] = FALSE; + } + $form['submission']['preview'] = array( + '#type' => 'radios', + '#title' => t('Preview before submitting'), + '#parents' => array('settings', 'node', 'preview'), + '#default_value' => $node_settings['preview'], + '#options' => array( + DRUPAL_DISABLED => t('Disabled'), + DRUPAL_OPTIONAL => t('Optional'), + DRUPAL_REQUIRED => t('Required'), + ), + ); + $form['submission']['help'] = array( + '#type' => 'textarea', + '#title' => t('Explanation or submission guidelines'), + '#default_value' => $type->help, + '#description' => t('This text will be displayed at the top of the page when creating or editing content of this type.'), + ); + $form['workflow'] = array( + '#type' => 'details', + '#title' => t('Publishing options'), + '#collapsed' => TRUE, + '#group' => 'additional_settings', + ); + $form['workflow']['options'] = array('#type' => 'checkboxes', + '#title' => t('Default options'), + '#parents' => array('settings', 'node', 'options'), + '#default_value' => $node_settings['options'], + '#options' => array( + 'status' => t('Published'), + 'promote' => t('Promoted to front page'), + 'sticky' => t('Sticky at top of lists'), + 'revision' => t('Create new revision'), + ), + '#description' => t('Users with the Administer content permission will be able to override these options.'), + ); + if (\Drupal::moduleHandler()->moduleExists('language')) { + $form['language'] = array( + '#type' => 'details', + '#title' => t('Language settings'), + '#collapsed' => TRUE, + '#group' => 'additional_settings', + ); + + $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->id(), + ), + '#default_value' => $language_configuration, + ); + } + $form['display'] = array( + '#type' => 'details', + '#title' => t('Display settings'), + '#collapsed' => TRUE, + '#group' => 'additional_settings', + ); + $form['display']['submitted'] = array( + '#type' => 'checkbox', + '#title' => t('Display author and date information.'), + '#parents' => array('settings', 'node', 'submitted'), + '#default_value' => $node_settings['submitted'], + '#description' => t('Author username and publish date will be displayed.'), + ); + return $form; + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, array &$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'] = !$this->entity->isLocked() && !$this->entity->isNew(); + return $actions; + } + + /** + * {@inheritdoc} + */ + public function validate(array $form, array &$form_state) { + parent::validate($form, $form_state); + + $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))); + } + + $types = node_type_get_names(); + if ($this->entity->isNew() && in_array($label, $types)) { + form_set_error('name', t('The human-readable name %name is already taken.', array('%name' => $label))); + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, array &$form_state) { + $type = $this->entity; + $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 != ''); + + $variables = $form_state['values']; + + // Do not save settings from vertical tabs. + // @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->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 = $type->save(); + + $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) { + 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'; + } + + /** + * {@inheritdoc} + */ + public function delete(array $form, array &$form_state) { + $form_state['redirect'] = 'admin/structure/types/manage/' . $this->entity->id() . '/delete'; + } + +} diff --git a/core/modules/node/lib/Drupal/node/NodeTypeInterface.php b/core/modules/node/lib/Drupal/node/NodeTypeInterface.php new file mode 100644 index 0000000..4ec2ec7 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeTypeInterface.php @@ -0,0 +1,37 @@ + t('Description'), + 'class' => array(RESPONSIVE_PRIORITY_MEDIUM), + ); + $row['operations'] = t('Operations'); + return $row; + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + $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; + } + + /** + * {@inheritdoc} + */ + public function getOperations(EntityInterface $entity) { + $operations = parent::getOperations($entity); + if (\Drupal::moduleHandler()->moduleExists('field_ui') && user_access('administer node fields')) { + $uri = $entity->uri(); + $operations['manage-fields'] = array( + 'title' => t('Manage fields'), + 'href' => $uri['path'] . '/fields', + 'options' => $uri['options'], + 'weight' => 0, + ); + $operations['manage-display'] = array( + 'title' => t('Manage display'), + 'href' => $uri['path'] . '/display', + 'options' => $uri['options'], + 'weight' => 5, + ); + } + if ($entity->isLocked()) { + unset($operations['delete']); + } + return $operations; + } + + /** + * {@inheritdoc} + */ + 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..1933714 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeTypeStorageController.php @@ -0,0 +1,27 @@ +deleteTags(array('node_types' => TRUE)); + } + +} 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..f5a892d --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/NodeType.php @@ -0,0 +1,220 @@ +type; + } + + /** + * {@inheritdoc} + */ + public function uri() { + return array( + 'path' => 'admin/structure/types/manage/' . $this->id(), + 'options' => array( + 'entity_type' => $this->entityType, + 'entity' => $this, + ), + ); + } + + /** + * {@inheritdoc} + */ + public function getNodeSettings($module) { + if (isset($this->settings[$module]) && is_array($this->settings[$module])) { + return $this->settings[$module]; + } + return array(); + } + + /** + * {@inheritdoc} + */ + public function isLocked() { + $locked = \Drupal::state()->get('node.type.locked'); + return isset($locked[$this->id()]) ? $locked[$this->id()] : FALSE; + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + if (!$update) { + // Clear the node type cache, so the new type appears. + $storage_controller->resetCache(array($this->id())); + + entity_invoke_bundle_hook('create', 'node', $this->id()); + + // Unless disabled, automatically create a Body field for new node types. + if ($this->get('create_body')) { + debug('create'); + $label = $this->get('create_body_label'); + node_add_body_field($this, $label); + } + else debug('do not create'); + } + elseif ($this->getOriginalID() != $this->id()) { + // Clear the node type cache to reflect the rename. + $storage_controller->resetCache(array($this->id())); + + $update_count = node_type_update_nodes($this->getOriginalID(), $this->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' => $this->getOriginalID(), + '%type' => $this->id(), + ))); + } + entity_invoke_bundle_hook('rename', 'node', $this->getOriginalID(), $this->id()); + } + else { + // Invalidate the cache tag of the updated node type only. + cache()->invalidateTags(array('node_type' => $this->id())); + } + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + // Clear the node type cache to reflect the removal. + $storage_controller->resetCache(array_keys($entities)); + foreach ($entities as $entity) { + entity_invoke_bundle_hook('delete', 'node', $entity->id()); + } + } + +} diff --git a/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php b/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php index c5856e5..31d515f 100644 --- a/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php @@ -26,7 +26,6 @@ public static function getInfo() { protected function setUp() { parent::setUp(); - $this->installSchema('node', 'node_type'); $this->installSchema('node', 'node'); $this->installSchema('node', 'node_field_data'); $this->installSchema('node', 'node_field_revision'); diff --git a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportChangeTest.php b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportChangeTest.php new file mode 100644 index 0000000..dadbb52 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportChangeTest.php @@ -0,0 +1,70 @@ +installSchema('system', array('config_snapshot')); + + // Set default storage backend. + $this->installConfig(array('field', 'node_test_config')); + } + + public static function getInfo() { + return array( + 'name' => 'Node config change tests', + 'description' => 'Change content types during config create method invocation.', + 'group' => 'Node', + ); + } + + /** + * Tests importing an updated content type. + */ + function testImportChange() { + $node_type_id = 'default'; + $node_type_config_name = "node.type.$node_type_id"; + + // Simulate config data to import: + // - a modified version (modified label) of the node type config. + $active = $this->container->get('config.storage'); + $node_type = $active->read($node_type_config_name); + $new_label = 'Test update import field'; + $node_type['name'] = $new_label; + + // Save as files in the the staging directory. + $staging = $this->container->get('config.storage.staging'); + $staging->write($node_type_config_name, $node_type); + + // Import the content of the staging directory. + $this->configImporter()->import(); + + // Check that the updated config was correctly imported. + $node_type = entity_load('node_type', $node_type_id); + $this->assertEqual($node_type->label(), $new_label, 'Node type name has been updated.'); + } + +} diff --git a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php new file mode 100644 index 0000000..d2b344e --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php @@ -0,0 +1,89 @@ +installSchema('system', array('config_snapshot')); + + // Set default storage backend. + $this->installConfig(array('field')); + } + + public static function getInfo() { + return array( + 'name' => 'Node config create tests', + 'description' => 'Create content types during config create method invocation.', + 'group' => 'Node', + ); + } + + /** + * Tests creating a content type during default config import. + */ + function testImportCreateDefault() { + $node_type_id = 'default'; + + // Check that the content type does not exist yet. + $this->assertFalse(entity_load('node_type', $node_type_id)); + + // Enable node_test_config module and check that the content type + // shipped in the module's default config is created. + module_enable(array('node_test_config')); + $node_type = entity_load('node_type', $node_type_id); + $this->assertTrue($node_type, 'The default content type was created.'); + } + + /** + * Tests creating a content type during config import. + */ + function testImportCreate() { + $node_type_id = 'import'; + $node_type_config_name = "node.type.$node_type_id"; + $node_type_manifest_name = 'manifest.node.type'; + + // Simulate config data to import: + $src_dir = drupal_get_path('module', 'node_test_config') . '/staging'; + $this->assertTrue(file_unmanaged_copy("$src_dir/$node_type_config_name.yml", "public://config_staging/$node_type_config_name.yml")); + + // Add the coresponding entries to the current manifest data. + $active = $this->container->get('config.storage'); + $node_type_manifest = $active->read($node_type_manifest_name); + $node_type_manifest[$node_type_id] = array('name' => $node_type_config_name); + + // Save the manifests as files in the the staging directory. + $staging = $this->container->get('config.storage.staging'); + $staging->write($node_type_manifest_name, $node_type_manifest); + + // Import the content of the staging directory. + $this->configImporter()->import(); + + // Check that the content type was created. + $node_type = entity_load('node_type', $node_type_id); + $this->assertTrue($node_type, 'Import node type from staging was created.'); + } + +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php index f531520..55719bb 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/NodeFormButtonsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeFormButtonsTest.php index a2bd56c..a592c88 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeFormButtonsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeFormButtonsTest.php @@ -109,7 +109,7 @@ function testNodeFormButtons() { // the initial order of buttons and/or status of the node when creating // a node. variable_set('node_options_article', array('promote')); - $this->refreshVariables(); + config('node.type.article')->set('settings.node.options.status', 0)->save(); // Verify the buttons on a node add form for an administrator. $this->drupalLogin($this->admin_user); diff --git a/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php index c72dc25..a1f3a75 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php @@ -36,7 +36,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. @@ -50,16 +50,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 d8fa419..5428105 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeTypePersistenceTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTypePersistenceTest.php @@ -22,6 +22,20 @@ public static function getInfo() { } /** + * Resets module_list() without rebuilding it. + * + * Required since this test enables Forum module via the internal browser, but + * subsequently loads the forum node type in the test runner process to verify + * the $disabled flag of the node type. + * + * @todo Use HTTP response headers during testing to communicate required + * cache resets. + */ + protected function resetModuleListCache() { + $this->rebuildContainer(); + } + + /** * Tests that node type customizations persist through disable and uninstall. */ function testNodeTypeCustomizationPersistence() { @@ -31,12 +45,12 @@ function testNodeTypeCustomizationPersistence() { $forum_enable = array($forum_key => "1"); $forum_disable = array($forum_key => FALSE); - // Enable forum and verify that the node type is in the DB and is not - // disabled. + // Enable forum and verify that the node type exists and is not disabled. $this->drupalPost('admin/modules', $forum_enable, t('Save configuration')); - $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'forum'))->fetchField(); - $this->assertNotIdentical($disabled, FALSE, 'Forum node type found in the database'); - $this->assertEqual($disabled, 0, 'Forum node type is not disabled'); + $this->resetModuleListCache(); + $forum = entity_load('node_type', 'forum'); + $this->assertTrue($forum->id(), 'Forum node type found.'); + $this->assertTrue($forum->isLocked(), 'Forum node type is locked'); // Check that forum node type (uncustomized) shows up. $this->drupalGet('node/add'); @@ -53,17 +67,19 @@ function testNodeTypeCustomizationPersistence() { // Disable forum and check that the node type gets disabled. $this->drupalPost('admin/modules', $forum_disable, t('Save configuration')); - $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'forum'))->fetchField(); - $this->assertEqual($disabled, 1, 'Forum node type is disabled'); + $this->resetModuleListCache(); + $forum = entity_load('node_type', 'forum'); + $this->assertTrue($forum->isLocked(), 'Forum node type is node locked'); $this->drupalGet('node/add'); $this->assertNoText('forum', 'forum type is not found on node/add'); // Reenable forum and check that the customization survived the module // disable. $this->drupalPost('admin/modules', $forum_enable, t('Save configuration')); - $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'forum'))->fetchField(); - $this->assertNotIdentical($disabled, FALSE, 'Forum node type found in the database'); - $this->assertEqual($disabled, 0, 'Forum node type is not disabled'); + $this->resetModuleListCache(); + $forum = entity_load('node_type', 'forum'); + $this->assertTrue($forum->id(), 'Forum node type found.'); + $this->assertTrue($forum->isLocked(), 'Forum node type is locked'); $this->drupalGet('node/add'); $this->assertText($description, 'Customized description found'); @@ -72,8 +88,9 @@ function testNodeTypeCustomizationPersistence() { $edit = array('uninstall[forum]' => 'forum'); $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' => 'forum'))->fetchField(); - $this->assertTrue($disabled, 'Forum node type is in the database and is disabled'); + $this->resetModuleListCache(); + $forum = entity_load('node_type', 'forum'); + $this->assertFalse($forum->isLocked(), 'Forum node type is in the database and is not locked'); $this->drupalGet('node/add'); $this->assertNoText('forum', 'forum 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 6a5b5f6..f275db8 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.'); } @@ -130,34 +129,34 @@ function testNodeTypeEditing() { } /** - * Tests that node_types_rebuild() correctly handles the 'disabled' flag. + * Tests that node_type_cache_reset() correctly handles the locking. */ function testNodeTypeStatus() { // Enable all core node modules, and all types should be active. module_enable(array('book'), FALSE); - node_types_rebuild(); + node_type_cache_reset(); $types = node_type_get_types(); foreach (array('book', 'article', 'page') as $type) { $this->assertTrue(isset($types[$type]), format_string('%type is found in node types.', array('%type' => $type))); - $this->assertTrue(isset($types[$type]->disabled) && empty($types[$type]->disabled), format_string('%type type is enabled.', array('%type' => $type))); + $this->assertFalse($types[$type]->isLocked(), format_string('%type type is not locked.', array('%type' => $type))); } // Disable book module and the respective type should still be active, since - // it is not provided by hook_node_info(). + // it is not provided by shipped configuration entity. module_disable(array('book'), FALSE); - node_types_rebuild(); + node_type_cache_reset(); $types = node_type_get_types(); - $this->assertTrue(isset($types['book']) && empty($types['book']->disabled), "Book module's node type still active."); - $this->assertTrue(isset($types['article']) && empty($types['article']->disabled), 'Article node type still active.'); - $this->assertTrue(isset($types['page']) && empty($types['page']->disabled), 'Basic page node type still active.'); + $this->assertFalse($types['book']->isLocked(), "Book module's node type still active."); + $this->assertFalse($types['book']->isLocked(), 'Article node type still active.'); + $this->assertFalse($types['book']->isLocked(), 'Basic page node type still active.'); // Re-enable the modules and verify that the types are active again. module_enable(array('book'), FALSE); - node_types_rebuild(); + node_type_cache_reset(); $types = node_type_get_types(); foreach (array('book', 'article', 'page') as $type) { $this->assertTrue(isset($types[$type]), format_string('%type is found in node types.', array('%type' => $type))); - $this->assertTrue(isset($types[$type]->disabled) && empty($types[$type]->disabled), format_string('%type type is enabled.', array('%type' => $type))); + $this->assertFalse($types[$type]->isLocked(), format_string('%type type is not locked.', array('%type' => $type))); } } @@ -194,6 +193,14 @@ function testNodeTypeDeletion() { 'The content type is available for deletion.' ); $this->assertText(t('This action cannot be undone.'), 'The node type deletion confirmation form is available.'); + // Test that forum node type could not be deleted while forum active. + $this->container->get('module_handler')->enable(array('forum')); + $this->drupalGet('admin/structure/types/manage/forum/delete'); + $this->assertResponse(403); + $this->container->get('module_handler')->disable(array('forum')); + $this->container->get('module_handler')->uninstall(array('forum')); + $this->drupalGet('admin/structure/types/manage/forum/delete'); + $this->assertResponse(200); } } diff --git a/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php b/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php index ae7529b..191d45c 100644 --- a/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php @@ -179,6 +179,8 @@ function testPagePreview() { /** * Checks the node preview functionality, when using revisions. + * + * @todo This test passes even with revisioning disabled. */ function testPagePreviewWithRevisions() { $langcode = Language::LANGCODE_NOT_SPECIFIED; @@ -186,7 +188,7 @@ function testPagePreviewWithRevisions() { $body_key = "body[$langcode][0][value]"; $term_key = "{$this->field_name}[$langcode]"; // 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 e15a08e..bfab52d 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -14,7 +14,7 @@ * * Each content type is maintained by a primary module, which is either * node.module (for content types created in the user interface) or the module - * that implements hook_node_info() to define the content type. + * that defines the content type by providing configuration file. * * During node operations (create, insert, update, view, delete, etc.), there * are several sets of hooks that get invoked to allow modules to modify the @@ -845,62 +845,6 @@ function hook_node_view_alter(&$build, \Drupal\Core\Entity\EntityInterface $node } /** - * Define module-provided node types. - * - * This hook allows a module to define one or more of its own node types. For - * example, the forum module uses it to define a forum node-type named "Forum - * topic." The name and attributes of each desired node type are specified in an - * array returned by the hook. - * - * Only module-provided node types should be defined through this hook. User- - * provided (or 'custom') node types should be defined only in the 'node_type' - * database table, and should be maintained by using the node_type_save() and - * node_type_delete() functions. - * - * @return - * An array of information defining the module's node types. The array - * contains a sub-array for each node type, with the the machine name of a - * content type as the key. Each sub-array has up to 10 attributes. - * Possible attributes: - * - name: (required) The human-readable name of the node type. - * - base: (required) The base string used to construct callbacks - * corresponding to this node type (for example, if base is defined as - * example_foo, then example_foo_insert will be called when inserting a node - * of that type). This string is usually the name of the module, but not - * always. - * - description: (required) A brief description of the node type. - * - help: (optional) Help information shown to the user when creating a node - * of this type. - * - has_title: (optional) A Boolean indicating whether or not this node type - * has a title field. - * - title_label: (optional) The label for the title field of this content - * type. - * - locked: (optional) A Boolean indicating whether the administrator can - * change the machine name of this type. FALSE = changeable (not locked), - * TRUE = unchangeable (locked). - * - * The machine name of a node type should contain only letters, numbers, and - * underscores. Underscores will be converted into hyphens for the purpose of - * constructing URLs. - * - * All attributes of a node type that are defined through this hook (except for - * 'locked') can be edited by a site administrator. This includes the - * machine-readable name of a node type, if 'locked' is set to FALSE. - * - * @ingroup node_api_hooks - */ -function hook_node_info() { - return array( - 'forum' => array( - 'name' => t('Forum topic'), - 'base' => 'forum', - 'description' => t('A forum topic starts a new discussion thread within a forum.'), - 'title_label' => t('Subject'), - ) - ); -} - -/** * Provide additional methods of scoring for core search results for nodes. * * A node's search score is used to rank it among other nodes matched by the @@ -965,44 +909,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\NodeTypeInterface $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\NodeTypeInterface $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\NodeTypeInterface $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\NodeTypeInterface $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\NodeTypeInterface $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\NodeTypeInterface $type) { + variable_del('comment_' . $type->id()); } /** diff --git a/core/modules/node/node.install b/core/modules/node/node.install index a0a71ff..ed5c8bb 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -5,6 +5,7 @@ * Install, update and uninstall functions for the node module. */ +use Drupal\Component\Uuid\Uuid; use Drupal\Core\Language\Language; /** @@ -36,7 +37,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, @@ -387,100 +388,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' => '', - ), - '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' => DRUPAL_EXTENSION_NAME_MAX_LENGTH, - 'not null' => TRUE, - ), - 'description' => array( - 'description' => 'A brief description of this type.', - 'type' => 'text', - 'not null' => TRUE, - 'size' => 'medium', - ), - 'help' => array( - 'description' => 'Help information shown to the user when creating a {node} of this type.', - 'type' => 'text', - 'not null' => TRUE, - 'size' => 'medium', - ), - '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' => '', - ), - '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'), - ); - - return $schema; } @@ -509,17 +416,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(); } @@ -1160,6 +1059,56 @@ function node_update_8019() { } /** + * Convert node types into configuration. + * + * @ingroup config_upgrade +*/ +function node_update_8020() { + $uuid = new Uuid(); + // Properties to drop: custom, disabled. + $locked = array(); + // Note: {node_type}.name was the label, .type the machine name. + $result = db_query('SELECT * FROM {node_type}') + ->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::LANGCODE_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, + ), + ))->fetchAllKeyed(); + $variables = array_map('unserialize', $variables); + // 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); + $config->set('settings.node.' . $name, $value); + + update_variable_del($name); + } + $config->save(); + // Convert 'base' property to state. + if ($node_type['base'] !== 'node_content' && Drupal::moduleHandler()->moduleExists($node_type['base'])) { + $locked[$id] = $node_type['base']; + } + } + Drupal::state()->set('node.type.locked', $locked); +} + +/** * @} 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 fbdf07d..9ab66e2 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -16,6 +16,7 @@ use Drupal\Core\Database\Query\SelectExtender; use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Datetime\DrupalDateTime; +use Drupal\node\NodeTypeInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Template\Attribute; use Drupal\entity\Plugin\Core\Entity\EntityDisplay; @@ -80,7 +81,7 @@ * Implements hook_rebuild(). */ function node_rebuild() { - node_types_rebuild(); + node_type_cache_reset(); } /** @@ -163,10 +164,6 @@ function node_theme() { 'variables' => array('node' => NULL), 'file' => 'node.pages.inc', ), - 'node_admin_overview' => array( - 'variables' => array('name' => NULL, 'type' => NULL), - 'file' => 'content_types.inc', - ), 'node_recent_block' => array( 'variables' => array('nodes' => NULL), ), @@ -188,8 +185,8 @@ function node_entity_bundle_info() { // Bundles must provide a human readable name so we can create help and error // messages. node_type_cache_reset(); - foreach (node_type_get_names() as $type => $name) { - $bundles['node'][$type]['label'] = $name; + foreach (node_type_get_names() as $id => $label) { + $bundles['node'][$id]['label'] = $label; } return $bundles; } @@ -305,49 +302,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. - */ -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; } /** @@ -358,9 +350,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; } @@ -372,152 +366,45 @@ function node_type_get_label($name) { * * @return string|false * The node type label or FALSE if the node type is not found. - */ -function node_get_type_label(EntityInterface $node) { - $types = _node_types_build()->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. + * @todo D8: Add this as generic helper method for config entities representing + * entity bundles. */ -function node_type_get_clean_name($node_type) { - return check_plain($node_type->name); +function node_get_type_label(Entityinterface $node) { + $types = node_type_get_names(); + return isset($types[$node->type]) ? $types[$node->type] : FALSE; } /** * Description callback: Returns the node type description. * - * @param $node_type + * @param \Drupal\node\NodeTypeInterface $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(NodeTypeInterface $node_type) { return $node_type->description; } /** - * Updates the database cache of node types. - * - * All new module-defined node types are saved to the database via a call to - * node_type_save(), and obsolete ones are deleted via a call to - * node_type_delete(). See _node_types_build() for an explanation of the new - * and obsolete types. - * - * @see _node_types_build() - */ -function node_types_rebuild() { - _node_types_build(TRUE); -} - -/** * Menu argument loader: Loads a node type by string. * * @param $name * The machine name of a node type to load. * - * @return + * @return \Drupal\node\NodeTypeInterface * 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) { - entity_invoke_bundle_hook('rename', '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(); - - entity_invoke_bundle_hook('create', '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); } /** * Adds the default body field to a node type. * - * @param $type + * @param \Drupal\node\NodeTypeInterface $type * A node type object. * @param $label * (optional) The label for the body instance. @@ -525,10 +412,10 @@ function node_type_save($info) { * @return * Body field instance. */ -function node_add_body_field($type, $label = 'Body') { +function node_add_body_field(NodeTypeInterface $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 = entity_create('field_entity', array( 'field_name' => 'body', @@ -541,7 +428,7 @@ function node_add_body_field($type, $label = 'Body') { $instance = entity_create('field_instance', array( 'field_name' => 'body', 'entity_type' => 'node', - 'bundle' => $type->type, + 'bundle' => $type->id(), 'label' => $label, 'settings' => array('display_summary' => TRUE), )); @@ -615,195 +502,28 @@ 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(); - entity_invoke_bundle_hook('delete', '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 string $old_id * The current node type of the nodes. - * @param $type + * @param string $new_id * The new node type of the nodes. * * @return * 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('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() { - 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_info() - */ -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; + Drupal::entityManager()->getStorageController('node_type')->resetCache(); } /** @@ -1066,7 +786,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 = Drupal::config('node.type.' . $node->type)->get('settings.node.submitted') ?: TRUE; + if ($submitted) { $variables['display_submitted'] = TRUE; $variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date'])); if (theme_get_setting('features.node_user_picture')) { @@ -1581,9 +1303,7 @@ function node_menu() { $items['admin/structure/types'] = array( 'title' => 'Content types', 'description' => 'Manage content types, including default status, front page promotion, comment settings, etc.', - 'page callback' => 'node_overview_types', - 'access arguments' => array('administer content types'), - 'file' => 'content_types.inc', + 'route_name' => 'node_overview_types', ); $items['admin/structure/types/list'] = array( 'title' => 'List', @@ -1591,20 +1311,14 @@ function node_menu() { ); $items['admin/structure/types/add'] = array( 'title' => 'Add content type', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('node_type_form'), - 'access arguments' => array('administer content types'), + 'route_name' => 'node_type_add', '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 arguments' => array(4), - 'page callback' => 'drupal_get_form', - 'page arguments' => array('node_type_form', 4), - 'access arguments' => array('administer content types'), - 'file' => 'content_types.inc', + 'route_name' => 'node_type_edit', ); $items['admin/structure/types/manage/%node_type/edit'] = array( 'title' => 'Edit', @@ -1612,9 +1326,7 @@ function node_menu() { ); $items['admin/structure/types/manage/%node_type/delete'] = array( 'title' => 'Delete', - 'page arguments' => array('node_type_delete_confirm', 4), - 'access arguments' => array('administer content types'), - 'file' => 'content_types.inc', + 'route_name' => 'node_type_delete_confirm', ); $items['node/add'] = array( 'title' => 'Add content', @@ -1623,7 +1335,7 @@ function node_menu() { 'file' => 'node.pages.inc', ); $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), @@ -1719,16 +1431,15 @@ function node_menu_local_tasks(&$data, $router_item, $root_path) { /** * Title callback: Provides the title for a node type edit form. * - * @param $type - * The node type object. + * @param string $type + * The machine name of the node type. * * @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; + $entity = entity_load('node_type', $type); + return $entity->label(); } /** @@ -1938,6 +1649,27 @@ function node_form_block_form_alter(&$form, &$form_state) { } /** + * Implements hook_modules_uninstalled(). + */ +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(); + } + } +} + +/** * Implements hook_block_access(). * * Checks the content type specific visibility settings and removes the block @@ -2463,8 +2195,10 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) { */ function node_access($op, $node, $account = NULL, $langcode = NULL) { if (!$node instanceof EntityInterface) { - $type = is_object($node) ? $node->type : $node; - $node = entity_create('node', array('type' => $type)); + $node = entity_create('node', array('type' => $node)); + } + elseif ($node->entityType() == 'node_type') { + $node = entity_create('node', array('type' => $node->id())); } // If no language code was provided, default to the node's langcode. @@ -2580,7 +2314,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/node/node.routing.yml b/core/modules/node/node.routing.yml index ce897f5..9204a2b 100644 --- a/core/modules/node/node.routing.yml +++ b/core/modules/node/node.routing.yml @@ -4,9 +4,46 @@ node_multiple_delete_confirm: _form: '\Drupal\node\Form\DeleteMultiple' requirements: _permission: 'administer nodes' + node_page_edit: pattern: '/node/{node}/edit' defaults: _entity_form: 'node.edit' requirements: _entity_access: 'node.update' + +node_overview_types: + pattern: '/admin/structure/types' + defaults: + _content: '\Drupal\Core\Entity\Controller\EntityListController::listing' + entity_type: 'node_type' + requirements: + _permission: 'administer content types' + +node_type_add: + pattern: '/admin/structure/types/add' + defaults: + _entity_form: 'node_type.add' + requirements: + _permission: 'administer content types' + +node_type_edit: + pattern: '/admin/structure/types/manage/{node_type}' + defaults: + _entity_form: 'node_type.edit' + requirements: + _permission: 'administer content types' + +node_type_edit_default: + pattern: '/admin/structure/types/manage/{node_type}/edit' + defaults: + _entity_form: 'node_type.edit' + requirements: + _permission: 'administer content types' + +node_type_delete_confirm: + pattern: '/admin/structure/types/manage/{node_type}/delete' + defaults: + _entity_form: 'node_type.delete' + requirements: + _entity_access: 'node_type.delete' diff --git a/core/modules/node/tests/modules/node_test_config/config/node.type.default.yml b/core/modules/node/tests/modules/node_test_config/config/node.type.default.yml new file mode 100644 index 0000000..d960aaf --- /dev/null +++ b/core/modules/node/tests/modules/node_test_config/config/node.type.default.yml @@ -0,0 +1,17 @@ +type: default +name: Default +description: 'Default description.' +help: '' +has_title: '1' +title_label: Title +settings: + node: + preview: '1' + options: + status: status + promote: promote + sticky: '0' + revision: '0' + submitted: '1' +status: '1' +langcode: und diff --git a/core/modules/node/tests/modules/node_test_config/node_test_config.info.yml b/core/modules/node/tests/modules/node_test_config/node_test_config.info.yml new file mode 100644 index 0000000..948b4a5 --- /dev/null +++ b/core/modules/node/tests/modules/node_test_config/node_test_config.info.yml @@ -0,0 +1,7 @@ +name: 'Node configuration tests' +type: module +description: 'Support module for node configuration tests.' +core: 8.x +package: Testing +version: VERSION +hidden: TRUE diff --git a/core/modules/node/tests/modules/node_test_config/node_test_config.module b/core/modules/node/tests/modules/node_test_config/node_test_config.module new file mode 100644 index 0000000..00522b5 --- /dev/null +++ b/core/modules/node/tests/modules/node_test_config/node_test_config.module @@ -0,0 +1,6 @@ +drupalCreateContentType(array('type' => 'article')); + } + /** * Tests several valid and invalid delete requests on all entity types. */ diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php index 4bad46b..808fe2a 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php @@ -166,6 +166,11 @@ protected function entityValues($entity_type) { ); case 'node': return array('title' => $this->randomString(), 'type' => $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/Tests/DrupalUnitTestBaseTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php index 4615b39..a7be5be 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php @@ -110,7 +110,7 @@ function testEnableModulesInstallContainer() { // Install Node module. $this->enableModules(array('field_sql_storage', 'field', 'node')); - $this->installSchema('node', array('node_type', 'node', 'node_field_data')); + $this->installSchema('node', array('node', 'node_field_data')); // Perform an entity query against node. $query = \Drupal::entityQuery('node'); // Disable node access checks, since User module is not enabled. diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 8c46872..ce309ef 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -292,48 +292,32 @@ 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(array $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/lib/Drupal/system/Tests/Condition/ConditionFormTest.php b/core/modules/system/lib/Drupal/system/Tests/Condition/ConditionFormTest.php index e191efe..26ee375 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Condition/ConditionFormTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Condition/ConditionFormTest.php @@ -32,6 +32,8 @@ public static function getInfo() { * Submit the condition_node_type_test_form to test condition forms. */ function testConfigForm() { + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Page')); + $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); $article = entity_create('node', array('type' => 'article', 'title' => $this->randomName())); $article->save(); $this->drupalGet('condition_test'); diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityBCDecoratorTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityBCDecoratorTest.php index 74c37be..0a71a67 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityBCDecoratorTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityBCDecoratorTest.php @@ -32,7 +32,7 @@ public static function getInfo() { public function setUp() { parent::setUp(); $this->installSchema('user', array('users_roles', 'users_data', 'role_permission')); - $this->installSchema('node', array('node', 'node_field_data', 'node_field_revision', 'node_type', 'node_access')); + $this->installSchema('node', array('node', 'node_field_data', 'node_field_revision', 'node_access')); $this->installSchema('comment', array('comment', 'node_comment_statistics')); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php index 3649529..055b014 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php @@ -44,7 +44,7 @@ public static function getInfo() { public function setUp() { parent::setUp(); $this->installSchema('user', array('users_roles', 'users_data')); - $this->installSchema('node', array('node', 'node_field_data', 'node_field_revision', 'node_type', 'node_access')); + $this->installSchema('node', array('node', 'node_field_data', 'node_field_revision', 'node_access')); $this->installSchema('comment', array('comment', 'node_comment_statistics')); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php index 4d24a51..24572b8 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php @@ -36,7 +36,7 @@ public static function getInfo() { public function setUp() { parent::setUp(); $this->installSchema('user', array('users_roles', 'users_data')); - $this->installSchema('node', array('node', 'node_field_data', 'node_field_revision', 'node_type', 'node_access')); + $this->installSchema('node', array('node', 'node_field_data', 'node_field_revision', 'node_access')); $this->installSchema('entity_test', array( 'entity_test_mul', 'entity_test_mul_property_data', diff --git a/core/modules/system/lib/Drupal/system/Tests/Plugin/ContextPluginTest.php b/core/modules/system/lib/Drupal/system/Tests/Plugin/ContextPluginTest.php index 5d5fb63..b04e2d8 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Plugin/ContextPluginTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/ContextPluginTest.php @@ -27,11 +27,6 @@ public static function getInfo() { ); } - protected function setUp() { - parent::setUp(); - $this->installSchema('node', 'node_type'); - } - /** * Tests basic context definition and value getters and setters. */ diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php index 33f7903..9fedb35 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php @@ -95,9 +95,10 @@ public function testFilledStandardUpgrade() { $this->assertFalse($result, 'No {menu_links} entry exists for user/autocomplete'); // Verify that the blog node type has been assigned to node module. - $blog_type = node_type_load('blog'); - $this->assertEqual($blog_type->module, 'node', "Content type 'blog' has been reassigned from the blog module to the node module."); - $this->assertEqual($blog_type->base, 'node_content', "The base string used to construct callbacks corresponding to content type 'Blog' has been reassigned to 'node_content'."); + $node_type = entity_load('node_type', 'blog'); + $this->assertFalse($node_type->isLocked(), "Content type 'blog' has been reassigned from the blog module to the node module."); + $node_type = entity_load('node_type', 'forum'); + $this->assertTrue($node_type->isLocked(), "The base string used to construct callbacks corresponding to content type 'Forum' has been reassigned to forum module."); // Each entity type has a 'full' view mode, ensure it was migrated. $all_view_modes = entity_get_view_modes(); diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index e9e69c5..36e9f77 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -2416,7 +2416,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/system/tests/modules/condition_test/condition_test.module b/core/modules/system/tests/modules/condition_test/condition_test.module index 1310b21..b3d9bbc 100644 --- a/core/modules/system/tests/modules/condition_test/condition_test.module +++ b/core/modules/system/tests/modules/condition_test/condition_test.module @@ -1,21 +1 @@ array( - 'name' => t('Article'), - 'base' => 'article', - 'description' => t('An article content type'), - 'title_label' => t('Subject'), - ), - 'page' => array( - 'name' => t('Page'), - 'base' => 'page', - 'description' => t('A page content type'), - 'title_label' => t('Subject'), - ), - ); -} 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 4e23cd8..e7c7031 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyTestBase.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/Views/TaxonomyTestBase.php @@ -67,14 +67,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 115be1a..e97768f 100644 --- a/core/modules/translation/translation.module +++ b/core/modules/translation/translation.module @@ -166,7 +166,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['controller']->getEntity()->id(), FALSE), '#element_validate' => array('translation_node_type_language_translation_enabled_validate'), '#prefix' => "", ); diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php b/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php index b46840f..50c536b 100644 --- a/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php @@ -81,7 +81,7 @@ public static function getInfo() { protected function setUpFixtures() { $this->installSchema('user', array('users', 'role_permission')); - $this->installSchema('node', array('node_type', 'node', 'node_field_data')); + $this->installSchema('node', array('node', 'node_field_data')); $this->installSchema('comment', array('comment', 'node_comment_statistics')); parent::setUpFixtures(); 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..4918cfb --- /dev/null +++ b/core/profiles/standard/config/node.type.article.yml @@ -0,0 +1,17 @@ +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: + node: + preview: '1' + options: + status: status + promote: promote + sticky: '0' + revision: '0' + submitted: '1' +status: '1' +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..e0d4f79 --- /dev/null +++ b/core/profiles/standard/config/node.type.page.yml @@ -0,0 +1,17 @@ +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: + preview: '1' + options: + status: status + promote: '0' + sticky: '0' + revision: '0' + submitted: '0' +status: '1' +langcode: und diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install index 0075ebd..3b3601d 100644 --- a/core/profiles/standard/standard.install +++ b/core/profiles/standard/standard.install @@ -24,36 +24,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' => t('Basic page'), - 'base' => 'node_content', - 'description' => t("Use basic pages for your static content, such as an 'About us' page."), - 'custom' => 1, - 'modified' => 1, - 'locked' => 0, - ), - array( - 'type' => 'article', - 'name' => t('Article'), - 'base' => 'node_content', - 'description' => t('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( @@ -83,12 +53,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();