diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleContentTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleContentTest.php index bc6ae1a..c55927f 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleContentTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleContentTest.php @@ -212,6 +212,7 @@ function testContentTypeDirLang() { * Test filtering Node content by language. */ function testNodeAdminLanguageFilter() { + module_enable(array('views')); // User to add and remove language. $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'access content overview', 'administer nodes', 'bypass node access')); @@ -227,14 +228,8 @@ function testNodeAdminLanguageFilter() { $node_en = $this->drupalCreateNode(array('langcode' => 'en')); $node_zh_hant = $this->drupalCreateNode(array('langcode' => 'zh-hant')); - $this->drupalGet('admin/content'); - // Verify filtering by language. - $edit = array( - 'langcode' => 'zh-hant', - ); - $this->drupalPost(NULL, $edit, t('Filter')); - + $this->drupalGet('admin/content', array('query' => array('langcode' => 'zh-hant'))); $this->assertLinkByHref('node/' . $node_zh_hant->nid . '/edit'); $this->assertNoLinkByHref('node/' . $node_en->nid . '/edit'); } diff --git a/core/modules/node/config/views.view.content.yml b/core/modules/node/config/views.view.content.yml new file mode 100644 index 0000000..a30f1cd --- /dev/null +++ b/core/modules/node/config/views.view.content.yml @@ -0,0 +1,359 @@ +base_field: nid +base_table: node +core: 8.x +description: 'Find and manage content.' +status: '1' +display: + default: + display_options: + access: + type: perm + options: + perm: 'access content overview' + cache: + type: none + query: + type: views_query + exposed_form: + type: basic + options: + submit_button: Filter + reset_button: '0' + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: '1' + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: '50' + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: '1' + row_class_special: '1' + override: '1' + sticky: '1' + summary: '' + columns: + node_bulk_form: node_bulk_form + title: title + type: type + name: name + status: status + changed: changed + edit_node: edit_node + delete_node: delete_node + translation_link: translation_link + dropbutton: dropbutton + info: + node_bulk_form: + sortable: '0' + default_sort_order: asc + responsive: '' + title: + sortable: '1' + default_sort_order: asc + type: + sortable: '1' + default_sort_order: asc + name: + sortable: '0' + default_sort_order: asc + responsive: priority-low + status: + sortable: '1' + default_sort_order: asc + responsive: '' + changed: + sortable: '1' + default_sort_order: desc + responsive: priority-low + edit_node: + sortable: '0' + default_sort_order: asc + responsive: '' + delete_node: + sortable: '0' + default_sort_order: asc + responsive: '' + translation_link: + sortable: '0' + default_sort_order: asc + responsive: '' + dropbutton: + sortable: '0' + default_sort_order: asc + responsive: '' + default: changed + empty_table: '1' + row: + type: fields + fields: + node_bulk_form: + id: node_bulk_form + table: node + field: node_bulk_form + label: '' + exclude: '0' + alter: + alter_text: '0' + element_class: '' + element_default_classes: '1' + empty: '' + hide_empty: '0' + empty_zero: '0' + hide_alter_empty: '1' + plugin_id: node_bulk_form + title: + id: title + table: node_field_data + field: title + label: Title + exclude: '0' + alter: + alter_text: '0' + element_class: '' + element_default_classes: '1' + empty: '' + hide_empty: '0' + empty_zero: '0' + hide_alter_empty: '1' + link_to_node: '1' + plugin_id: node + type: + id: type + table: node_field_data + field: type + label: 'Content Type' + exclude: '0' + alter: + alter_text: '0' + element_class: '' + element_default_classes: '1' + empty: '' + hide_empty: '0' + empty_zero: '0' + hide_alter_empty: '1' + link_to_node: '0' + machine_name: '0' + plugin_id: node_type + name: + id: name + table: users + field: name + relationship: uid + label: Author + exclude: '0' + alter: + alter_text: '0' + element_class: '' + element_default_classes: '1' + empty: '' + hide_empty: '0' + empty_zero: '0' + hide_alter_empty: '1' + link_to_user: '1' + overwrite_anonymous: '0' + anonymous_text: '' + format_username: '1' + plugin_id: user_name + status: + id: status + table: node_field_data + field: status + label: Status + exclude: '0' + alter: + alter_text: '0' + element_class: '' + element_default_classes: '1' + empty: '' + hide_empty: '0' + empty_zero: '0' + hide_alter_empty: '1' + type: published-notpublished + type_custom_true: '' + type_custom_false: '' + not: '0' + plugin_id: boolean + changed: + id: changed + table: node_field_data + field: changed + label: Updated + exclude: '0' + alter: + alter_text: '0' + element_class: '' + element_default_classes: '1' + empty: '' + hide_empty: '0' + empty_zero: '0' + hide_alter_empty: '1' + date_format: short + custom_date_format: '' + timezone: '' + plugin_id: date + edit_node: + id: edit_node + table: views_entity_node + field: edit_node + label: '' + exclude: '1' + text: Edit + plugin_id: node_link_edit + delete_node: + id: delete_node + table: views_entity_node + field: delete_node + label: '' + exclude: '1' + text: Delete + plugin_id: node_link_delete + translation_link: + id: translation_link + table: node + field: translation_link + label: '' + exclude: '1' + alter: + alter_text: '0' + element_class: '' + element_default_classes: '1' + hide_alter_empty: '1' + hide_empty: '0' + empty_zero: '0' + empty: '' + text: Translate + optional: '1' + plugin_id: translation_entity_link + dropbutton: + id: dropbutton + table: views + field: dropbutton + label: Operations + fields: + edit_node: edit_node + delete_node: delete_node + translation_link: translation_link + destination: '1' + plugin_id: dropbutton + filters: + status_extra: + id: status_extra + table: node_field_data + field: status_extra + operator: '=' + value: '' + plugin_id: node_status + status: + id: status + table: node_field_data + field: status + operator: '=' + value: All + exposed: '1' + expose: + operator_id: '' + label: Status + description: '' + use_operator: '0' + operator: status_op + identifier: status + required: '0' + remember: '0' + multiple: '0' + remember_roles: + authenticated: authenticated + plugin_id: boolean + type: + id: type + table: node_field_data + field: type + operator: in + value: { } + exposed: '1' + expose: + operator_id: type_op + label: Type + description: '' + use_operator: '0' + operator: type_op + identifier: type + required: '0' + remember: '0' + multiple: '0' + remember_roles: + authenticated: authenticated + reduce: '0' + plugin_id: bundle + langcode: + id: langcode + table: node + field: langcode + operator: in + value: { } + group: '1' + exposed: '1' + expose: + operator_id: langcode_op + label: Language + operator: langcode_op + identifier: langcode + remember_roles: + authenticated: authenticated + optional: '1' + plugin_id: language + sorts: { } + title: Content + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + empty: '1' + content: 'No content available.' + plugin_id: text_custom + arguments: { } + relationships: + uid: + id: uid + table: node_field_data + field: uid + admin_label: author + required: '1' + plugin_id: standard + show_admin_links: '0' + display_plugin: default + display_title: Master + id: default + position: '0' + page_1: + display_options: + path: admin/content/node + menu: + type: 'default tab' + title: Content + description: '' + name: admin + weight: '-10' + context: '0' + tab_options: + type: normal + title: Content + description: 'Find and manage content' + name: admin + weight: '-10' + display_plugin: page + display_title: Page + id: page_1 + position: '1' +label: Content +module: node +id: content +tag: default +langcode: en diff --git a/core/modules/node/lib/Drupal/node/NodeBCDecorator.php b/core/modules/node/lib/Drupal/node/NodeBCDecorator.php new file mode 100644 index 0000000..9fa6bbf --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeBCDecorator.php @@ -0,0 +1,16 @@ +get('vid')->value; } + /** + * {@inheritdoc} + */ + public function getBCEntity() { + if (!isset($this->bcEntity)) { + $this->getPropertyDefinitions(); + $this->bcEntity = new NodeBCDecorator($this, $this->fieldDefinitions); + } + return $this->bcEntity; + } + } diff --git a/core/modules/node/lib/Drupal/node/Plugin/views/field/NodeBulkForm.php b/core/modules/node/lib/Drupal/node/Plugin/views/field/NodeBulkForm.php new file mode 100644 index 0000000..da633d7 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Plugin/views/field/NodeBulkForm.php @@ -0,0 +1,63 @@ +actions = array_filter($this->actions, function ($action) { + return $action->getType() == 'node'; + }); + } + + /** + * {@inheritdoc} + */ + protected function getBulkOptions() { + return array_map(function ($action) { + return $action->label(); + }, $this->actions); + } + + /** + * {@inheritdoc} + */ + public function views_form_validate(&$form, &$form_state) { + $selected = array_filter($form_state['values'][$this->options['id']]); + if (empty($selected)) { + form_set_error('', t('No items selected.')); + } + } + + /** + * {@inheritdoc} + */ + public function views_form_submit(&$form, &$form_state) { + parent::views_form_submit($form, $form_state); + if ($form_state['step'] == 'views_form_views_form') { + Cache::invalidateTags(array('content' => TRUE)); + } + } + +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php index e34f61b..c96c68c 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php @@ -12,6 +12,13 @@ */ class NodeAdminTest extends NodeTestBase { + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('views'); + public static function getInfo() { return array( 'name' => 'Node administration', @@ -39,38 +46,42 @@ function setUp() { */ function testContentAdminSort() { $this->drupalLogin($this->admin_user); + + // Create nodes that have different node.changed values. + $this->container->get('state')->set('node_test.storage_controller', TRUE); + module_enable(array('node_test')); + $changed = REQUEST_TIME; foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) { - $this->drupalCreateNode(array('title' => $prefix . $this->randomName(6))); + $changed += 1000; + $this->drupalCreateNode(array('title' => $prefix . $this->randomName(6), 'changed' => $changed)); } // Test that the default sort by node.changed DESC actually fires properly. $nodes_query = db_select('node_field_data', 'n') - ->fields('n', array('nid')) + ->fields('n', array('title')) ->orderBy('changed', 'DESC') ->execute() ->fetchCol(); - $nodes_form = array(); $this->drupalGet('admin/content'); - foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) { - $nodes_form[] = $input; + foreach ($nodes_query as $delta => $string) { + $elements = $this->xpath('//table[contains(@class, :class)]//tr[' . ($delta + 1) . ']/td[2]/a[normalize-space(text())=:label]', array(':class' => 'views-table', ':label' => $string)); + $this->assertTrue(!empty($elements), 'The node was found in the correct order.'); } - $this->assertEqual($nodes_query, $nodes_form, 'Nodes are sorted in the form according to the default query.'); // Compare the rendered HTML node list to a query for the nodes ordered by // title to account for possible database-dependent sort order. $nodes_query = db_select('node_field_data', 'n') - ->fields('n', array('nid')) + ->fields('n', array('title')) ->orderBy('title') ->execute() ->fetchCol(); - $nodes_form = array(); - $this->drupalGet('admin/content', array('query' => array('sort' => 'asc', 'order' => 'Title'))); - foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) { - $nodes_form[] = $input; + $this->drupalGet('admin/content', array('query' => array('sort' => 'asc', 'order' => 'title'))); + foreach ($nodes_query as $delta => $string) { + $elements = $this->xpath('//table[contains(@class, :class)]//tr[' . ($delta + 1) . ']/td[2]/a[normalize-space(text())=:label]', array(':class' => 'views-table', ':label' => $string)); + $this->assertTrue(!empty($elements), 'The node was found in the correct order.'); } - $this->assertEqual($nodes_query, $nodes_form, 'Nodes are sorted in the form the same as they are in the query.'); } /** @@ -95,30 +106,17 @@ function testContentAdminPages() { $this->assertLinkByHref('node/' . $node->nid); $this->assertLinkByHref('node/' . $node->nid . '/edit'); $this->assertLinkByHref('node/' . $node->nid . '/delete'); - // Verify tableselect. - $this->assertFieldByName('nodes[' . $node->nid . ']', '', 'Tableselect found.'); } // Verify filtering by publishing status. - $edit = array( - 'status' => 'status-1', - ); - $this->drupalPost(NULL, $edit, t('Filter')); - - $this->assertRaw(t('where %property is %value', array('%property' => t('status'), '%value' => 'published')), 'Content list is filtered by status.'); + $this->drupalGet('admin/content', array('query' => array('status' => TRUE))); $this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit'); $this->assertLinkByHref('node/' . $nodes['published_article']->nid . '/edit'); $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit'); // Verify filtering by status and content type. - $edit = array( - 'type' => 'page', - ); - $this->drupalPost(NULL, $edit, t('Refine')); - - $this->assertRaw(t('where %property is %value', array('%property' => t('status'), '%value' => 'published')), 'Content list is filtered by status.'); - $this->assertRaw(t('and where %property is %value', array('%property' => t('type'), '%value' => 'Basic page')), 'Content list is filtered by content type.'); + $this->drupalGet('admin/content', array('query' => array('status' => TRUE, 'type' => 'page'))); $this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit'); $this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/edit'); diff --git a/core/modules/node/lib/Drupal/node/Tests/Views/BulkFormTest.php b/core/modules/node/lib/Drupal/node/Tests/Views/BulkFormTest.php new file mode 100644 index 0000000..838c553 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/Views/BulkFormTest.php @@ -0,0 +1,55 @@ + 'Node: Bulk form', + 'description' => 'Tests a node bulk form.', + 'group' => 'Views Modules', + ); + } + + /** + * Tests the node bulk form. + */ + public function testBulkForm() { + $this->drupalLogin($this->drupalCreateUser(array('administer nodes'))); + $node = $this->drupalCreateNode(); + + $this->drupalGet('test-node-bulk-form'); + $elements = $this->xpath('//select[@id="edit-action"]//option'); + $this->assertIdentical(count($elements), 8, 'All node operations are found.'); + + // Block a node using the bulk form. + $this->assertTrue($node->status); + $edit = array( + 'node_bulk_form[0]' => TRUE, + 'action' => 'node_unpublish_action', + ); + $this->drupalPost(NULL, $edit, t('Apply')); + // Re-load the node and check their status. + $node = entity_load('node', $node->id()); + $this->assertFalse($node->status); + } + +} diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc index 8835861..687b721 100644 --- a/core/modules/node/node.admin.inc +++ b/core/modules/node/node.admin.inc @@ -5,8 +5,8 @@ * Content administration and module settings user interface. */ -use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Language\Language; +use Drupal\node\NodeInterface; /** * Page callback: Form constructor for the permission rebuild confirmation form. @@ -32,197 +32,6 @@ function node_configure_rebuild_confirm_submit($form, &$form_state) { } /** - * Lists node administration filters that can be applied. - * - * @return - * An associative array of filters. - */ -function node_filters() { - // Regular filters - $filters['status'] = array( - 'title' => t('status'), - 'options' => array( - '[any]' => t('any'), - 'status-1' => t('published'), - 'status-0' => t('not published'), - 'promote-1' => t('promoted'), - 'promote-0' => t('not promoted'), - 'sticky-1' => t('sticky'), - 'sticky-0' => t('not sticky'), - ), - ); - // Include translation states if we have this module enabled - if (module_exists('translation')) { - $filters['status']['options'] += array( - 'translate-0' => t('Up to date translation'), - 'translate-1' => t('Outdated translation'), - ); - } - - $filters['type'] = array( - 'title' => t('type'), - 'options' => array( - '[any]' => t('any'), - ) + node_type_get_names(), - ); - - // Language filter if language support is present. - if (language_multilingual()) { - $languages = language_list(Language::STATE_ALL); - foreach ($languages as $langcode => $language) { - // Make locked languages appear special in the list. - $language_options[$langcode] = $language->locked ? t('- @name -', array('@name' => $language->name)) : $language->name; - } - $filters['langcode'] = array( - 'title' => t('language'), - 'options' => array( - '[any]' => t('- Any -'), - ) + $language_options, - ); - } - return $filters; -} - -/** - * Applies filters for the node administration overview based on session. - * - * @param Drupal\Core\Database\Query\SelectInterface $query - * A SelectQuery to which the filters should be applied. - */ -function node_build_filter_query(SelectInterface $query) { - // Build query - $filter_data = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array(); - foreach ($filter_data as $index => $filter) { - list($key, $value) = $filter; - switch ($key) { - case 'status': - // Note: no exploitable hole as $key/$value have already been checked when submitted - list($key, $value) = explode('-', $value, 2); - case 'type': - case 'langcode': - $query->condition('n.' . $key, $value); - break; - } - } -} - -/** - * Returns the node administration filters form array to node_admin_content(). - * - * @see node_admin_nodes() - * @see node_admin_nodes_submit() - * @see node_admin_nodes_validate() - * @see node_filter_form_submit() - * @see node_multiple_delete_confirm() - * @see node_multiple_delete_confirm_submit() - * - * @ingroup forms - */ -function node_filter_form() { - $session = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array(); - $filters = node_filters(); - - $i = 0; - $form['filters'] = array( - '#type' => 'details', - '#title' => t('Show only items where'), - '#theme' => 'exposed_filters__node', - ); - foreach ($session as $filter) { - list($type, $value) = $filter; - if ($type == 'term') { - // Load term name from DB rather than search and parse options array. - $value = module_invoke('taxonomy', 'term_load', $value); - $value = $value->name; - } - elseif ($type == 'langcode') { - $value = language_name($value); - } - else { - $value = $filters[$type]['options'][$value]; - } - $t_args = array('%property' => $filters[$type]['title'], '%value' => $value); - if ($i++) { - $form['filters']['current'][] = array('#markup' => t('and where %property is %value', $t_args)); - } - else { - $form['filters']['current'][] = array('#markup' => t('where %property is %value', $t_args)); - } - if (in_array($type, array('type', 'langcode'))) { - // Remove the option if it is already being filtered on. - unset($filters[$type]); - } - } - - $form['filters']['status'] = array( - '#type' => 'container', - '#attributes' => array('class' => array('clearfix')), - '#prefix' => ($i ? '