diff --git a/core/modules/node/config/views.view.content.yml b/core/modules/node/config/views.view.content.yml index 64f1acf8991..e18480ab4f8 100644 --- a/core/modules/node/config/views.view.content.yml +++ b/core/modules/node/config/views.view.content.yml @@ -18,7 +18,7 @@ display: type: basic options: submit_button: Filter - reset_button: true + reset_button: false reset_button_label: Reset exposed_sorts_label: 'Sort by' expose_sort_order: true diff --git a/core/modules/node/config/views.view.content_recent.yml b/core/modules/node/config/views.view.content_recent.yml deleted file mode 100644 index fec1cfdcd98..00000000000 --- a/core/modules/node/config/views.view.content_recent.yml +++ /dev/null @@ -1,461 +0,0 @@ -base_field: nid -base_table: node -core: 8.x -description: 'Recent content.' -status: '0' -display: - block_1: - display_plugin: block - id: block_1 - display_title: Block - position: '1' - block_category: 'Lists (Views)' - display_options: - link_url: admin/content - default: - display_plugin: default - id: default - display_title: Master - position: '1' - display_options: - access: - type: perm - options: - perm: 'access content' - cache: - type: none - options: { } - query: - type: views_query - options: - disable_sql_rewrite: '0' - distinct: '0' - slave: '0' - query_comment: '' - query_tags: { } - exposed_form: - type: basic - options: - submit_button: Apply - 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: some - options: - items_per_page: '10' - offset: '0' - style: - type: table - options: - grouping: { } - row_class: '' - default_row_class: '1' - row_class_special: '1' - override: '1' - sticky: '0' - caption: '' - summary: '' - description: '' - columns: - title: title - timestamp: title - name: title - edit_node: edit_node - delete_node: delete_node - info: - title: - sortable: '0' - default_sort_order: asc - align: '' - separator: '' - empty_column: '0' - responsive: '' - timestamp: - sortable: '0' - default_sort_order: asc - align: '' - separator: '' - empty_column: '0' - responsive: '' - name: - sortable: '0' - default_sort_order: asc - align: '' - separator: '' - empty_column: '0' - responsive: '' - edit_node: - sortable: '0' - default_sort_order: asc - align: '' - separator: '' - empty_column: '1' - responsive: '' - delete_node: - sortable: '0' - default_sort_order: asc - align: '' - separator: '' - empty_column: '1' - responsive: '' - default: '-1' - empty_table: '0' - row: - type: fields - fields: - title: - id: title - table: node_field_data - field: title - relationship: none - group_type: group - admin_label: '' - label: '' - exclude: '0' - alter: - alter_text: '0' - text: '' - make_link: '0' - path: '' - absolute: '0' - external: '0' - replace_spaces: '0' - path_case: none - trim_whitespace: '0' - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: '0' - max_length: '' - word_boundary: '0' - ellipsis: '0' - more_link: '0' - more_link_text: '' - more_link_path: '' - strip_tags: '0' - trim: '0' - preserve_tags: '' - html: '0' - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: '0' - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: '1' - empty: '' - hide_empty: '0' - empty_zero: '0' - hide_alter_empty: '1' - link_to_node: '1' - provider: node - timestamp: - id: timestamp - table: history - field: timestamp - relationship: none - group_type: group - admin_label: '' - label: '' - exclude: '0' - alter: - alter_text: '0' - text: '' - make_link: '0' - path: '' - absolute: '0' - external: '0' - replace_spaces: '0' - path_case: none - trim_whitespace: '0' - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: '0' - max_length: '' - word_boundary: '1' - ellipsis: '1' - more_link: '0' - more_link_text: '' - more_link_path: '' - strip_tags: '0' - trim: '0' - preserve_tags: '' - html: '0' - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: '0' - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: '1' - empty: '' - hide_empty: '0' - empty_zero: '0' - hide_alter_empty: '1' - link_to_node: '0' - comments: '0' - plugin_id: history_user_timestamp - provider: history - name: - id: name - table: users - field: name - relationship: uid - group_type: group - admin_label: '' - label: '' - exclude: '0' - alter: - alter_text: '0' - text: '' - make_link: '0' - path: '' - absolute: '0' - external: '0' - replace_spaces: '0' - path_case: none - trim_whitespace: '0' - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: '0' - max_length: '' - word_boundary: '1' - ellipsis: '1' - more_link: '0' - more_link_text: '' - more_link_path: '' - strip_tags: '0' - trim: '0' - preserve_tags: '' - html: '0' - element_type: div - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: '0' - element_wrapper_type: '' - element_wrapper_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 - provider: user - edit_node: - id: edit_node - table: node - field: edit_node - relationship: none - group_type: group - admin_label: '' - label: '' - exclude: '0' - alter: - alter_text: '0' - text: '' - make_link: '0' - path: '' - absolute: '0' - external: '0' - replace_spaces: '0' - path_case: none - trim_whitespace: '0' - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: '0' - max_length: '' - word_boundary: '1' - ellipsis: '1' - more_link: '0' - more_link_text: '' - more_link_path: '' - strip_tags: '0' - trim: '0' - preserve_tags: '' - html: '0' - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: '0' - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: '1' - empty: '' - hide_empty: '0' - empty_zero: '0' - hide_alter_empty: '1' - text: edit - plugin_id: node_link_edit - provider: node - delete_node: - id: delete_node - table: node - field: delete_node - relationship: none - group_type: group - admin_label: '' - label: '' - exclude: '0' - alter: - alter_text: '0' - text: '' - make_link: '0' - path: '' - absolute: '0' - external: '0' - replace_spaces: '0' - path_case: none - trim_whitespace: '0' - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: '0' - max_length: '' - word_boundary: '1' - ellipsis: '1' - more_link: '0' - more_link_text: '' - more_link_path: '' - strip_tags: '0' - trim: '0' - preserve_tags: '' - html: '0' - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: '0' - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: '1' - empty: '' - hide_empty: '0' - empty_zero: '0' - hide_alter_empty: '1' - text: delete - plugin_id: node_link_delete - provider: node - filters: - status_extra: - id: status_extra - table: node_field_data - field: status_extra - relationship: none - group_type: group - admin_label: '' - operator: '=' - value: '' - group: '1' - exposed: '0' - expose: - operator_id: '0' - label: '' - description: '' - use_operator: '0' - operator: '' - identifier: '' - required: '0' - remember: '0' - multiple: '0' - remember_roles: - authenticated: authenticated - is_grouped: '0' - group_info: - label: '' - description: '' - identifier: '' - optional: '1' - widget: select - multiple: '0' - remember: '0' - default_group: All - default_group_multiple: { } - group_items: { } - plugin_id: node_status - provider: node - sorts: - changed: - id: changed - table: node_field_data - field: changed - relationship: none - group_type: group - admin_label: '' - order: DESC - exposed: '0' - expose: - label: '' - granularity: second - plugin_id: date - provider: views - title: 'Recent content' - header: { } - footer: { } - empty: - area_text_custom: - id: area_text_custom - table: views - field: area_text_custom - relationship: none - group_type: group - admin_label: '' - empty: '1' - tokenize: '0' - content: 'No content available.' - plugin_id: text_custom - provider: views - relationships: - uid: - id: uid - table: node_field_data - field: uid - relationship: none - group_type: group - admin_label: author - required: '1' - plugin_id: standard - provider: views - arguments: { } - filter_groups: - operator: AND - groups: { } - use_more: '1' - use_more_always: '0' - use_more_text: More - link_display: custom_url -label: 'Recent content' -module: views -id: content_recent -tag: default -uuid: 22208367-bde8-4977-ae04-4f1a34383ae3 -langcode: en diff --git a/core/modules/node/lib/Drupal/node/Plugin/Block/RecentContentBlock.php b/core/modules/node/lib/Drupal/node/Plugin/Block/RecentContentBlock.php new file mode 100644 index 00000000000..d5db3651040 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Plugin/Block/RecentContentBlock.php @@ -0,0 +1,79 @@ + 10, + ); + } + + /** + * {@inheritdoc} + */ + public function access(AccountInterface $account) { + return $account->hasPermission('access content'); + } + + /** + * Overrides \Drupal\block\BlockBase::blockForm(). + */ + public function blockForm($form, &$form_state) { + $form['block_count'] = array( + '#type' => 'select', + '#title' => t('Number of recent content items to display'), + '#default_value' => $this->configuration['block_count'], + '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)), + ); + return $form; + } + + /** + * Overrides \Drupal\block\BlockBase::blockSubmit(). + */ + public function blockSubmit($form, &$form_state) { + $this->configuration['block_count'] = $form_state['values']['block_count']; + } + + /** + * {@inheritdoc} + */ + public function build() { + if ($nodes = node_get_recent($this->configuration['block_count'])) { + return array( + '#theme' => 'node_recent_block', + '#nodes' => $nodes, + ); + } + else { + return array( + '#children' => t('No content available.'), + ); + } + } + +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php new file mode 100644 index 00000000000..70598eec607 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php @@ -0,0 +1,153 @@ + 'Node blocks', + 'description' => 'Test node block functionality.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(); + + // Create users and test node. + $this->adminUser = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer blocks')); + $this->webUser = $this->drupalCreateUser(array('access content', 'create article content')); + } + + /** + * Tests the recent comments block. + */ + public function testRecentNodeBlock() { + $this->drupalLogin($this->adminUser); + + // Disallow anonymous users to view content. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access content' => FALSE, + )); + + // Enable the recent content block with two items. + $block = $this->drupalPlaceBlock('node_recent_block', array('id' => 'test_block', 'block_count' => 2)); + + // Test that block is not visible without nodes. + $this->drupalGet(''); + $this->assertText(t('No content available.'), 'Block with "No content available." found.'); + + // Add some test nodes. + $default_settings = array('uid' => $this->webUser->id(), 'type' => 'article'); + $node1 = $this->drupalCreateNode($default_settings); + $node2 = $this->drupalCreateNode($default_settings); + $node3 = $this->drupalCreateNode($default_settings); + + // Change the changed time for node so that we can test ordering. + db_update('node_field_data') + ->fields(array( + 'changed' => $node1->getChangedTime() + 100, + )) + ->condition('nid', $node2->id()) + ->execute(); + db_update('node_field_data') + ->fields(array( + 'changed' => $node1->getChangedTime() + 200, + )) + ->condition('nid', $node3->id()) + ->execute(); + + // Test that a user without the 'access content' permission cannot + // see the block. + $this->drupalLogout(); + $this->drupalGet(''); + $this->assertNoText($block->label(), 'Block was not found.'); + + // Test that only the 2 latest nodes are shown. + $this->drupalLogin($this->webUser); + $this->assertNoText($node1->label(), 'Node not found in block.'); + $this->assertText($node2->label(), 'Node found in block.'); + $this->assertText($node3->label(), 'Node found in block.'); + + // Check to make sure nodes are in the right order. + $this->assertTrue($this->xpath('//div[@id="block-test-block"]/div/table/tbody/tr[position() = 1]/td/div/a[text() = "' . $node3->label() . '"]'), 'Nodes were ordered correctly in block.'); + + $this->drupalLogout(); + $this->drupalLogin($this->adminUser); + + // Set the number of recent nodes to show to 10. + $block->getPlugin()->setConfigurationValue('block_count', 10); + $block->save(); + + // Post an additional node. + $node4 = $this->drupalCreateNode($default_settings); + // drupalCreateNode() does not automatically flush content caches unlike + // posting a node from a node form. + cache_invalidate_tags(array('content' => TRUE)); + + // Test that all four nodes are shown. + $this->drupalGet(''); + $this->assertText($node1->label(), 'Node found in block.'); + $this->assertText($node2->label(), 'Node found in block.'); + $this->assertText($node3->label(), 'Node found in block.'); + $this->assertText($node4->label(), 'Node found in block.'); + + // Enable the "Powered by Drupal" block only on article nodes. + $block = $this->drupalPlaceBlock('system_powered_by_block', array( + 'visibility' => array( + 'node_type' => array( + 'types' => array( + 'article' => 'article', + ), + ), + ), + )); + $visibility = $block->get('visibility'); + $this->assertTrue(isset($visibility['node_type']['types']['article']), 'Visibility settings were saved to configuration'); + + // Create a page node. + $node5 = $this->drupalCreateNode(array('uid' => $this->adminUser->id(), 'type' => 'page')); + + // Verify visibility rules. + $this->drupalGet(''); + $label = $block->label(); + $this->assertNoText($label, 'Block was not displayed on the front page.'); + $this->drupalGet('node/add/article'); + $this->assertText($label, 'Block was displayed on the node/add/article page.'); + $this->drupalGet('node/' . $node1->id()); + $this->assertText($label, 'Block was displayed on the node/N when node is of type article.'); + $this->drupalGet('node/' . $node5->id()); + $this->assertNoText($label, 'Block was not displayed on nodes of type page.'); + } + +} diff --git a/core/modules/node/node.module b/core/modules/node/node.module index bd7d8cf9a9e..1702ee9782b 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -162,6 +162,12 @@ function node_theme() { 'variables' => array('node' => NULL), 'file' => 'node.pages.inc', ), + 'node_recent_block' => array( + 'variables' => array('nodes' => NULL), + ), + 'node_recent_content' => array( + 'variables' => array('node' => NULL), + ), 'node_edit_form' => array( 'render element' => 'form', 'template' => 'node-edit-form', @@ -622,6 +628,9 @@ function node_preprocess_block(&$variables) { case 'node_syndicate_block': $variables['attributes']['role'] = 'complementary'; break; + case 'node_recent_block': + $variables['attributes']['role'] = 'navigation'; + break; } } } @@ -1128,6 +1137,94 @@ function node_get_recent($number = 10) { return $nodes ? $nodes : array(); } +/** + * Returns HTML for a list of recent content. + * + * @param $variables + * An associative array containing: + * - nodes: An array of recent node entities. + * + * @ingroup themeable + */ +function theme_node_recent_block($variables) { + $rows = array(); + $output = ''; + + $l_options = array('query' => drupal_get_destination()); + foreach ($variables['nodes'] as $node) { + $row = array(); + $node_recent_content = array( + '#theme' => 'node_recent_content', + '#node' => $node, + ); + $row[] = array( + 'data' => drupal_render($node_recent_content), + 'class' => 'title-author', + ); + if ($node->access('update')) { + $row[] = array( + 'data' => l(t('edit'), 'node/' . $node->id() . '/edit', $l_options), + 'class' => 'edit', + ); + } + if ($node->access('delete')) { + $row[] = array( + 'data' => l(t('delete'), 'node/' . $node->id() . '/delete', $l_options), + 'class' => 'delete', + ); + } + $rows[] = $row; + } + + if ($rows) { + $table = array( + '#theme' => 'table', + '#rows' => $rows, + ); + $output = drupal_render($table); + if (user_access('access content overview')) { + $more_link = array( + '#theme' => 'more_link', + '#url' => 'admin/content', + '#title' => t('Show more content'), + ); + $output .= drupal_render($more_link); + } + } + + return $output; +} + +/** + * Returns HTML for a recent node to be displayed in the recent content block. + * + * @param $variable + * An associative array containing: + * - node: A node entity. + * + * @ingroup themeable + */ +function theme_node_recent_content($variables) { + $node = $variables['node']; + + $output = '
'; + $output .= l($node->label(), 'node/' . $node->id()); + $mark = array( + '#theme' => 'mark', + '#status' => node_mark($node->id(), $node->getChangedTime()), + ); + $output .= drupal_render($mark); + $output .= '
'; + $username = array( + '#theme' => 'username', + '#account' => $node->getAuthor(), + ); + $output .= drupal_render($username); + $output .= '
'; + + return $output; +} + /** * Implements hook_form_FORM_ID_alter() for block_form(). *