diff --git a/core/modules/node/node.module b/core/modules/node/node.module index d7eeeed69abb98ceb34721590fa754ae865342fd..5dc8d92ea3c247fada246fd109b59a1015f090ba 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -893,100 +893,6 @@ function node_get_recent($number = 10) { return $nodes ? $nodes : array(); } -/** - * Page callback: Generates and prints an RSS feed. - * - * Generates an RSS feed from an array of node IDs, and prints it with an HTTP - * header, with Content Type set to RSS/XML. - * - * @param $nids - * (optional) An array of node IDs (nid). Defaults to FALSE so empty feeds can - * be generated with passing an empty array, if no items are to be added - * to the feed. - * @param $channel - * (optional) An associative array containing 'title', 'link', 'description', - * and other keys, to be parsed by format_rss_channel() and - * format_xml_elements(). A list of channel elements can be found at the - * @link http://cyber.law.harvard.edu/rss/rss.html RSS 2.0 Specification. @endlink - * The link should be an absolute URL. - * - * @todo Convert taxonomy_term_feed() to a view, so this method is not needed - * anymore. - * - * @return Symfony\Component\HttpFoundation\Response - * A response object. - * - * @see node_menu() - */ -function node_feed($nids = FALSE, $channel = array()) { - global $base_url; - $language_content = \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT); - $rss_config = \Drupal::config('system.rss'); - - if ($nids === FALSE) { - $nids = \Drupal::entityQuery('node') - ->condition('status', 1) - ->condition('promote', 1) - ->sort('created', 'DESC') - ->range(0, $rss_config->get('items.limit')) - ->addTag('node_access') - ->execute(); - } - - $view_mode = $rss_config->get('items.view_mode'); - $namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/'); - - // Load all nodes to be rendered. - /** @var \Drupal\node\NodeInterface[] $nodes */ - $nodes = node_load_multiple($nids); - $items = ''; - foreach ($nodes as $node) { - $item_text = ''; - - $node->link = url('node/' . $node->id(), array('absolute' => TRUE)); - $node->rss_namespaces = array(); - $node->rss_elements = array( - array('key' => 'pubDate', 'value' => gmdate('r', $node->getCreatedTime())), - array('key' => 'dc:creator', 'value' => $node->getOwner()->label()), - array('key' => 'guid', 'value' => $node->id() . ' at ' . $base_url, 'attributes' => array('isPermaLink' => 'false')) - ); - - // The node gets built and modules add to or modify $node->rss_elements - // and $node->rss_namespaces. - $build = node_view($node, 'rss'); - unset($build['#theme']); - - if (!empty($node->rss_namespaces)) { - $namespaces = array_merge($namespaces, $node->rss_namespaces); - } - - if ($view_mode != 'title') { - // We render node contents and force links to be last. - $build['links']['#weight'] = 1000; - $item_text .= drupal_render($build); - } - - $items .= format_rss_item($node->label(), $node->link, $item_text, $node->rss_elements); - } - - $channel_defaults = array( - 'version' => '2.0', - 'title' => \Drupal::config('system.site')->get('name'), - 'link' => $base_url, - 'description' => $rss_config->get('channel.description'), - 'language' => $language_content->id - ); - $channel_extras = array_diff_key($channel, $channel_defaults); - $channel = array_merge($channel_defaults, $channel); - - $output = "\n"; - $output .= "\n"; - $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language'], $channel_extras); - $output .= "\n"; - - return new Response($output, 200, array('Content-Type' => 'application/rss+xml; charset=utf-8')); -} - /** * Generates an array for rendering the given node. * @@ -1049,7 +955,6 @@ function node_page_build(&$page) { * default setting for number of posts to show on node listing pages. * * @see node_page_default() - * @see taxonomy_term_page() * @see node_form_system_site_information_settings_form_submit() */ function node_form_system_site_information_settings_form_alter(&$form, FormStateInterface $form_state, $form_id) { diff --git a/core/modules/node/src/Plugin/views/row/Rss.php b/core/modules/node/src/Plugin/views/row/Rss.php index 820f9fbfe469f3bb6185f41f48c41d3ec49a26f2..04aff57910ff551ebac1f9185a157a181be56aea 100644 --- a/core/modules/node/src/Plugin/views/row/Rss.php +++ b/core/modules/node/src/Plugin/views/row/Rss.php @@ -85,7 +85,6 @@ public function preRender($values) { } public function render($row) { - // For the most part, this code is taken from node_feed() in node.module global $base_url; $nid = $row->{$this->field_alias}; diff --git a/core/modules/node/src/Tests/NodeAccessBaseTableTest.php b/core/modules/node/src/Tests/NodeAccessBaseTableTest.php index 0bd170789b37eb5c118aa508a47f3a939ce172a2..d5e4dd4318cf1939acdcc2ec75b87f5792df8ecd 100644 --- a/core/modules/node/src/Tests/NodeAccessBaseTableTest.php +++ b/core/modules/node/src/Tests/NodeAccessBaseTableTest.php @@ -19,7 +19,7 @@ class NodeAccessBaseTableTest extends NodeTestBase { * * @var array */ - public static $modules = array('node_access_test'); + public static $modules = array('node_access_test', 'views'); /** * The installation profile to use with this test. diff --git a/core/modules/rdf/src/Tests/TaxonomyAttributesTest.php b/core/modules/rdf/src/Tests/TaxonomyAttributesTest.php index 76c97cd4d9eebed1112977d03e3578700d0023a4..1900485cd915b0a03ee8cada68159d7a7ff467da 100644 --- a/core/modules/rdf/src/Tests/TaxonomyAttributesTest.php +++ b/core/modules/rdf/src/Tests/TaxonomyAttributesTest.php @@ -21,7 +21,7 @@ class TaxonomyAttributesTest extends TaxonomyTestBase { * * @var array */ - public static $modules = array('rdf'); + public static $modules = array('rdf', 'views'); protected function setUp() { parent::setUp(); diff --git a/core/modules/taxonomy/config/install/core.entity_view_mode.taxonomy_term.full.yml b/core/modules/taxonomy/config/install/core.entity_view_mode.taxonomy_term.full.yml index 50b1854e974e5d083d91333cd00d0e21a74dd361..bb8c47e80905f733f65d56d230bceaca7a47c7e6 100644 --- a/core/modules/taxonomy/config/install/core.entity_view_mode.taxonomy_term.full.yml +++ b/core/modules/taxonomy/config/install/core.entity_view_mode.taxonomy_term.full.yml @@ -1,6 +1,6 @@ id: taxonomy_term.full label: 'Taxonomy term page' -status: false +status: true cache: true targetEntityType: taxonomy_term dependencies: diff --git a/core/modules/taxonomy/config/install/views.view.taxonomy_term.yml b/core/modules/taxonomy/config/install/views.view.taxonomy_term.yml index 08ce70703f06d1f7623eab90e2484ab3b302199a..8b7bf6abac857b9b0c46582b930315db0b555dda 100644 --- a/core/modules/taxonomy/config/install/views.view.taxonomy_term.yml +++ b/core/modules/taxonomy/config/install/views.view.taxonomy_term.yml @@ -1,5 +1,5 @@ langcode: en -status: false +status: true dependencies: module: - node @@ -69,7 +69,7 @@ display: sorts: sticky: id: sticky - table: node_field_data + table: taxonomy_index field: sticky order: DESC plugin_id: standard @@ -82,7 +82,7 @@ display: provider: views created: id: created - table: node_field_data + table: taxonomy_index field: created order: DESC plugin_id: date @@ -95,35 +95,24 @@ display: granularity: second provider: views arguments: - term_node_tid_depth: - id: term_node_tid_depth - table: node - field: term_node_tid_depth - default_action: 'not found' + tid: + id: tid + table: taxonomy_index + field: tid + relationship: none + group_type: group + admin_label: '' + dependencies: + module: + - taxonomy + default_action: ignore exception: value: all - title_enable: true + title_enable: false title: All title_enable: true title: '%1' default_argument_type: fixed - summary: - format: default_summary - specify_validation: true - validate: - type: 'entity:taxonomy_term' - fail: 'not found' - validate_options: - access: true - operation: view - multiple: 1 - bundles: { } - depth: 0 - break_phrase: true - plugin_id: taxonomy_index_tid_depth - relationship: none - group_type: group - admin_label: '' default_argument_options: argument: '' default_argument_skip_url: false @@ -132,42 +121,26 @@ display: count: true items_per_page: 25 override: false - provider: taxonomy - term_node_tid_depth_modifier: - id: term_node_tid_depth_modifier - table: node - field: term_node_tid_depth_modifier - exception: - title_enable: true - default_argument_type: fixed summary: + sort_order: asc + number_of_records: 0 format: default_summary specify_validation: true - plugin_id: taxonomy_index_tid_depth_modifier - relationship: none - group_type: group - admin_label: '' - default_action: ignore - title_enable: false - title: '' - default_argument_options: { } - default_argument_skip_url: false - summary_options: { } validate: - type: none + type: 'entity:taxonomy_term' fail: 'not found' - validate_options: { } + validate_options: + access: true + operation: view + multiple: 0 + bundles: { } + break_phrase: false + add_table: false + require_value: false + reduce_duplicates: false + plugin_id: taxonomy_index_tid provider: taxonomy filters: - status_extra: - id: status_extra - table: node_field_data - field: status_extra - group: 0 - expose: - operator: '0' - plugin_id: node_status - provider: node langcode: id: langcode table: node_field_data @@ -224,7 +197,21 @@ display: view_mode: teaser comments: false provider: views - header: { } + header: + entity_taxonomy_term: + id: entity_taxonomy_term + table: views + field: entity_taxonomy_term + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: true + entity_id: '!1' + view_mode: full + bypass_access: false + plugin_id: entity + provider: views footer: { } empty: { } relationships: { } @@ -276,7 +263,7 @@ display: last: 'last ยป' quantity: 9 provider: views - path: taxonomy/term/%/%/feed + path: taxonomy/term/%/feed displays: page: page default: '0' diff --git a/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml b/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml index 1de5e1e8a3a733b15599aea846419742b1e55609..3fce880a310ef66211a7e1263b10b4b9604c627e 100644 --- a/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml +++ b/core/modules/taxonomy/config/schema/taxonomy.views.schema.yml @@ -1,18 +1,8 @@ # Schema for the views plugins of the Taxonomy module. views.argument.taxonomy_index_tid: - type: views_argument + type: views.argument.many_to_one label: 'Taxonomy term ID' - mapping: - break_phrase: - type: boolean - label: 'Allow multiple values' - add_table: - type: boolean - label: 'Allow multiple filter values to work together' - require_value: - type: boolean - label: 'Do not display items with no value in summary' views.argument.taxonomy_index_tid_depth: type: views_argument diff --git a/core/modules/taxonomy/src/Controller/TaxonomyController.php b/core/modules/taxonomy/src/Controller/TaxonomyController.php index 0ddbce478a339e31cd189370b239cc84ea261ac9..e24eb6f94f8ccc50dba983e810899e3de4d0fc9e 100644 --- a/core/modules/taxonomy/src/Controller/TaxonomyController.php +++ b/core/modules/taxonomy/src/Controller/TaxonomyController.php @@ -17,6 +17,19 @@ */ class TaxonomyController extends ControllerBase { + /** + * Title callback for term pages. + * + * @param \Drupal\taxonomy\TermInterface $term + * A taxonomy term entity. + * + * @return + * The term name to be used as the page title. + */ + public function getTitle(TermInterface $term) { + return $term->label(); + } + /** * Returns a rendered edit form to create a new term associated to the given vocabulary. * @@ -31,15 +44,6 @@ public function addForm(VocabularyInterface $taxonomy_vocabulary) { return $this->entityFormBuilder()->getForm($term); } - /** - * @todo Remove taxonomy_term_page(). - */ - public function termPage(TermInterface $taxonomy_term) { - module_load_include('pages.inc', 'taxonomy'); - return taxonomy_term_page($taxonomy_term); - - } - /** * Route title callback. * @@ -66,12 +70,4 @@ public function termTitle(TermInterface $taxonomy_term) { return Xss::filter($taxonomy_term->getName()); } - /** - * @todo Remove taxonomy_term_feed(). - */ - public function termFeed(TermInterface $taxonomy_term) { - module_load_include('pages.inc', 'taxonomy'); - return taxonomy_term_feed($taxonomy_term); - } - } diff --git a/core/modules/taxonomy/src/TermViewsData.php b/core/modules/taxonomy/src/TermViewsData.php index 03eca1928d982eac71949d7b19c92a01b41043bd..8c9f37a5a8b8b150a8c1a334329b6846a00e2649 100644 --- a/core/modules/taxonomy/src/TermViewsData.php +++ b/core/modules/taxonomy/src/TermViewsData.php @@ -313,6 +313,32 @@ public function getViewsData() { ), ); + + $data['taxonomy_index']['sticky'] = [ + 'title' => t('Sticky status'), + 'help' => t('Whether or not the content related to a term is sticky.'), + 'filter' => [ + 'id' => 'boolean', + 'label' => t('Sticky status'), + 'type' => 'yes-no', + ], + 'sort' => [ + 'id' => 'standard', + 'help' => t('Whether or not the content related to a term is sticky. To list sticky content first, set this to descending.'), + ], + ]; + + $data['taxonomy_index']['created'] = [ + 'title' => t('Post date'), + 'help' => t('The date the content related to a term was posted.'), + 'sort' => [ + 'id' => 'date' + ], + 'filter' => [ + 'id' => 'date', + ], + ]; + $data['taxonomy_term_hierarchy']['table']['group'] = t('Taxonomy term'); $data['taxonomy_term_hierarchy']['table']['join'] = array( diff --git a/core/modules/taxonomy/src/Tests/TermIndexTest.php b/core/modules/taxonomy/src/Tests/TermIndexTest.php index d1ec1b42ee5212c595dbae7fcf18977300968e96..877b8b72ecf45868a4e3fb527df3c698dc950d6f 100644 --- a/core/modules/taxonomy/src/Tests/TermIndexTest.php +++ b/core/modules/taxonomy/src/Tests/TermIndexTest.php @@ -17,6 +17,13 @@ */ class TermIndexTest extends TaxonomyTestBase { + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('views'); + protected function setUp() { parent::setUp(); diff --git a/core/modules/taxonomy/src/Tests/TermTest.php b/core/modules/taxonomy/src/Tests/TermTest.php index b2e9757a2c6bfecd1fd7c9d7c15db1c91b3854f1..8b660018b9cdec827faa9e346218f655fd6be133 100644 --- a/core/modules/taxonomy/src/Tests/TermTest.php +++ b/core/modules/taxonomy/src/Tests/TermTest.php @@ -347,6 +347,7 @@ function testTermAutocompletion() { * Save, edit and delete a term using the user interface. */ function testTermInterface() { + \Drupal::moduleHandler()->install(array('views')); $edit = array( 'name[0][value]' => $this->randomMachineName(12), 'description[0][value]' => $this->randomMachineName(100), diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e1aa1dedbc16c1d5ffe6c34df28da7c1605dea35 --- /dev/null +++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php @@ -0,0 +1,122 @@ +admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access')); + $this->drupalLogin($this->admin_user); + + // Create a vocabulary and add two term reference fields to article nodes. + + $this->field_name_1 = drupal_strtolower($this->randomMachineName()); + entity_create('field_storage_config', array( + 'name' => $this->field_name_1, + 'entity_type' => 'node', + 'type' => 'taxonomy_term_reference', + 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => $this->vocabulary->id(), + 'parent' => 0, + ), + ), + ), + ))->save(); + entity_create('field_instance_config', array( + 'field_name' => $this->field_name_1, + 'bundle' => 'article', + 'entity_type' => 'node', + ))->save(); + entity_get_form_display('node', 'article', 'default') + ->setComponent($this->field_name_1, array( + 'type' => 'options_select', + )) + ->save(); + entity_get_display('node', 'article', 'default') + ->setComponent($this->field_name_1, array( + 'type' => 'taxonomy_term_reference_link', + )) + ->save(); + } + + /** + * Tests that the taxonomy term view is working properly. + */ + public function testTaxonomyTermView() { + // Create terms in the vocabulary. + $term = $this->createTerm($this->vocabulary); + + // Post an article. + $edit = array(); + $edit['title[0][value]'] = $original_title = $this->randomMachineName(); + $edit['body[0][value]'] = $this->randomMachineName(); + $edit["{$this->field_name_1}[]"] = $term->id(); + $this->drupalPostForm('node/add/article', $edit, t('Save')); + $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); + + $this->drupalGet('taxonomy/term/' . $term->id()); + $this->assertText($term->label()); + $this->assertText($node->label()); + + \Drupal::moduleHandler()->install(array('language', 'content_translation')); + $language = ConfigurableLanguage::createFromLangcode('ur'); + $language->save(); + // Enable translation for the article content type and ensure the change is + // picked up. + content_translation_set_config('node', 'article', 'enabled', TRUE); + $roles = $this->admin_user->getRoles(TRUE); + Role::load(reset($roles)) + ->grantPermission('create content translations') + ->grantPermission('translate any entity') + ->save(); + drupal_static_reset(); + \Drupal::entityManager()->clearCachedDefinitions(); + \Drupal::service('router.builder')->rebuild(); + + $edit['title[0][value]'] = $translated_title = $this->randomMachineName(); + + $this->drupalPostForm('node/' . $node->id() . '/translations/add/en/ur', $edit, t('Save (this translation)')); + + $this->drupalGet('taxonomy/term/' . $term->id()); + $this->assertText($term->label()); + $this->assertText($original_title); + $this->assertNoText($translated_title); + + $this->drupalGet('ur/taxonomy/term/' . $term->id()); + $this->assertText($term->label()); + $this->assertNoText($original_title); + $this->assertText($translated_title); + } + +} diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index 9668dbedd74be5ec1a9584a4f113130ec7655e93..7b26e7b895b889aeb00f80bc388fd6af3b69e029 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -16,6 +16,7 @@ use Drupal\node\Entity\Node; use Drupal\taxonomy\Entity\Term; use Drupal\taxonomy\Entity\Vocabulary; +use Drupal\taxonomy\TermInterface; use Drupal\taxonomy\VocabularyInterface; use Drupal\Component\Utility\String; @@ -68,8 +69,8 @@ function taxonomy_help($route_name, RouteMatchInterface $route_match) { $output .= '
' . t('Before you can use a new vocabulary to classify your content, a new Taxonomy term field must be added to a content type on its manage fields page. When adding a taxonomy field, you choose a widget to use to enter the taxonomy information on the content editing page: a select list, checkboxes, radio buttons, or an auto-complete field (to build a free-tagging vocabulary). After choosing the field type and widget, on the subsequent field settings page you can choose the desired vocabulary, whether one or multiple terms can be chosen from the vocabulary, and other settings. The same vocabulary can be added to multiple content types, by using the "Re-use existing field" section on the manage fields page.', array('@ctedit' => url('admin/structure/types'))) . '
'; $output .= '
' . t('Classifying content') . '
'; $output .= '
' . t('After the vocabulary is assigned to the content type, you can start classifying content. The field with terms will appear on the content editing screen when you edit or add new content.', array('@addnode' => url('node/add'))) . '
'; - $output .= '
' . t('Viewing listings and RSS feeds by term') . '
'; - $output .= '
' . t("Each taxonomy term automatically provides a page listing content that has its classification, and a corresponding RSS feed. For example, if the taxonomy term country rock has the ID 123 (you can see this by looking at the URL when hovering on the linked term, which you can click to navigate to the listing page), then you will find this list at the path taxonomy/term/123. The RSS feed will use the path taxonomy/term/123/feed (the RSS icon for this term's listing will automatically display in your browser's address bar when viewing the listing page).") . '
'; + $output .= '
' . t('Viewing listings') . '
'; + $output .= '
' . t("Each taxonomy term automatically provides a page listing content that has its classification. For example, if the taxonomy term country rock has the ID 123 (you can see this by looking at the URL when hovering on the linked term, which you can click to navigate to the listing page), then you will find this list at the path taxonomy/term/123.") . '
'; $output .= '
' . t('Extending Taxonomy module') . '
'; $output .= '
' . t('There are many contributed modules that extend the behavior of the Taxonomy module for both display and organization of terms.', array('@taxcontrib' => 'http://drupal.org/project/modules?filters=tid:71&solrsort=sis_project_release_usage%20desc')); $output .= ''; @@ -125,6 +126,37 @@ function taxonomy_term_uri($term) { )); } +/** + * Implements hook_page_build(). + */ +function taxonomy_page_build(&$page) { + $route_match = \Drupal::routeMatch(); + if ($route_match->getRouteName() == 'entity.taxonomy_term.canonical' && ($term = $route_match->getParameter('taxonomy_term')) && $term instanceof TermInterface) { + foreach ($term->uriRelationships() as $rel) { + // Set the URI relationships, like canonical. + $page['#attached']['drupal_add_html_head_link'][] = array( + array( + 'rel' => $rel, + 'href' => $term->url($rel), + ), + TRUE, + ); + + // Set the term path as the canonical URL to prevent duplicate content. + if ($rel == 'canonical') { + // Set the non-aliased canonical path as a default shortlink. + $page['#attached']['drupal_add_html_head_link'][] = array( + array( + 'rel' => 'shortlink', + 'href' => $term->url($rel, array('alias' => TRUE)), + ), + TRUE, + ); + } + } + } +} + /** * Return nodes attached to a term across all field instances. * @@ -328,8 +360,8 @@ function template_preprocess_taxonomy_term(&$variables) { * A taxonomy term entity. */ function taxonomy_term_is_page(Term $term) { - if ($page_term = \Drupal::routeMatch()->getParameter('taxonomy_term')) { - return $page_term->id() == $term->id(); + if (\Drupal::routeMatch()->getRouteName() == 'entity.taxonomy_term.canonical' && $page_term_id = \Drupal::routeMatch()->getRawParameter('taxonomy_term')) { + return $page_term_id == $term->id(); } return FALSE; } diff --git a/core/modules/taxonomy/taxonomy.pages.inc b/core/modules/taxonomy/taxonomy.pages.inc deleted file mode 100644 index 294dabba7d13db5281d40905f0dcd5728d68841e..0000000000000000000000000000000000000000 --- a/core/modules/taxonomy/taxonomy.pages.inc +++ /dev/null @@ -1,81 +0,0 @@ -id() . '/feed', 'RSS - ' . $term->getName()); - - foreach ($term->uriRelationships() as $rel) { - // Set the term path as the canonical URL to prevent duplicate content. - $build['#attached']['drupal_add_html_head_link'][] = array( - array( - 'rel' => $rel, - 'href' => $term->url($rel), - ), - TRUE, - ); - - if ($rel == 'canonical') { - // Set the non-aliased canonical path as a default shortlink. - $build['#attached']['drupal_add_html_head_link'][] = array( - array( - 'rel' => 'shortlink', - 'href' => $term->url($rel, array('alias' => TRUE)), - ), - TRUE, - ); - } - } - - $build['taxonomy_terms'] = taxonomy_term_view_multiple(array($term->id() => $term)); - if ($nids = taxonomy_select_nodes($term->id(), TRUE, \Drupal::config('node.settings')->get('items_per_page'))) { - $nodes = node_load_multiple($nids); - $build['nodes'] = node_view_multiple($nodes); - $build['pager'] = array( - '#theme' => 'pager', - '#weight' => 5, - ); - } - else { - $build['no_content'] = array( - '#prefix' => '

', - '#markup' => t('There is currently no content classified with this term.'), - '#suffix' => '

', - ); - } - return $build; -} - -/** - * Generate the content feed for a taxonomy term. - * - * @param \Drupal\taxonomy\Entity\Term $term - * The taxonomy term entity. - * - * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. - * Use \Drupal\taxonomy\Controller\TaxonomyController::termFeed(). - */ -function taxonomy_term_feed(Term $term) { - $channel['link'] = url('taxonomy/term/' . $term->id(), array('absolute' => TRUE)); - $channel['title'] = \Drupal::config('system.site')->get('name') . ' - ' . $term->getName(); - // Only display the description if we have a single term, to avoid clutter and confusion. - // HTML will be removed from feed description. - $channel['description'] = $term->description->processed; - $nids = taxonomy_select_nodes($term->id(), FALSE, \Drupal::config('system.rss')->get('items.limit')); - - return node_feed($nids, $channel); -} diff --git a/core/modules/taxonomy/taxonomy.routing.yml b/core/modules/taxonomy/taxonomy.routing.yml index 5b065c230ffd867a7fc87855397ba6d2cc4973f3..9f6e70b773b76d3a21fd439262af361dfdc5adbd 100644 --- a/core/modules/taxonomy/taxonomy.routing.yml +++ b/core/modules/taxonomy/taxonomy.routing.yml @@ -91,16 +91,7 @@ entity.taxonomy_vocabulary.overview_form: entity.taxonomy_term.canonical: path: '/taxonomy/term/{taxonomy_term}' defaults: - _content: '\Drupal\taxonomy\Controller\TaxonomyController::termPage' - _title: 'Taxonomy term' - _title_callback: '\Drupal\taxonomy\Controller\TaxonomyController::termTitle' - requirements: - _entity_access: 'taxonomy_term.view' - -taxonomy.term_feed: - path: '/taxonomy/term/{taxonomy_term}/feed' - defaults: - _content: '\Drupal\taxonomy\Controller\TaxonomyController::termFeed' + _entity_view: 'taxonomy_term.full' _title: 'Taxonomy term' _title_callback: '\Drupal\taxonomy\Controller\TaxonomyController::termTitle' requirements: diff --git a/core/modules/views/config/schema/views.argument.schema.yml b/core/modules/views/config/schema/views.argument.schema.yml index 8d9a3b47d7fe964e5769735130d65df42f54b5a1..2f43282809b7d2332adc14a7c586c19ba0874846 100644 --- a/core/modules/views/config/schema/views.argument.schema.yml +++ b/core/modules/views/config/schema/views.argument.schema.yml @@ -17,6 +17,9 @@ views.argument.many_to_one: require_value: type: boolean label: 'Do not display items with no value in summary' + reduce_duplicates: + type: boolean + label: 'Reduce duplicates' views.argument.null: type: views_argument diff --git a/core/modules/views/config/schema/views.data_types.schema.yml b/core/modules/views/config/schema/views.data_types.schema.yml index 21da5ddfefa6975543dbb64d6dfc8bb86cf7899e..85ab981d452eaf9bd0a9d43f189c2c0ddf51671c 100644 --- a/core/modules/views/config/schema/views.data_types.schema.yml +++ b/core/modules/views/config/schema/views.data_types.schema.yml @@ -331,15 +331,8 @@ views_handler: type: string label: 'Provider' dependencies: - type: mapping + type: config_dependencies label: 'Dependencies' - mapping: - module: - type: sequence - label: 'Modules' - sequence: - - type: string - label: 'Dependency' views_argument: type: views_handler diff --git a/core/modules/views/src/EventSubscriber/RouteSubscriber.php b/core/modules/views/src/EventSubscriber/RouteSubscriber.php index 9fec99b9b503699b4487e65c23b1041406050e3a..f1f02b387971bdb6bdcf19156df01b986806eafa 100644 --- a/core/modules/views/src/EventSubscriber/RouteSubscriber.php +++ b/core/modules/views/src/EventSubscriber/RouteSubscriber.php @@ -87,6 +87,10 @@ public static function getSubscribedEvents() { $events = parent::getSubscribedEvents(); $events[KernelEvents::VIEW][] = array('onHtmlPage', 75); $events[RoutingEvents::FINISHED] = array('routeRebuildFinished'); + // Ensure to run after the entity resolver subscriber + // @see \Drupal\Core\EventSubscriber\EntityRouteAlterSubscriber + $events[RoutingEvents::ALTER] = ['onAlterRoutes', -175]; + return $events; } @@ -168,7 +172,9 @@ protected function alterRoutes(RouteCollection $collection) { $view_route_names = $display->alterRoutes($collection); $this->viewRouteNames = $view_route_names + $this->viewRouteNames; foreach ($view_route_names as $id_display => $route_name) { + $view_route_name = $this->viewsDisplayPairs[$id_display]; unset($this->viewsDisplayPairs[$id_display]); + $collection->remove("views.$view_route_name"); } } } diff --git a/core/modules/views/src/Plugin/views/display/PathPluginBase.php b/core/modules/views/src/Plugin/views/display/PathPluginBase.php index 4f6e2207713f032382b334026da5174b9e05bbe8..2400ac0767ec1aeb668df5318a6d8358d55edd63 100644 --- a/core/modules/views/src/Plugin/views/display/PathPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/PathPluginBase.php @@ -239,6 +239,7 @@ public function alterRoutes(RouteCollection $collection) { // requirements). // Replace the existing route with a new one based on views. + $original_route = $collection->get($name); $collection->remove($name); $view_id = $this->view->storage->id(); @@ -251,9 +252,13 @@ public function alterRoutes(RouteCollection $collection) { // We assume that the numeric ids of the parameters match the one from // the view argument handlers. foreach ($parameters as $position => $parameter_name) { - $path = str_replace('arg_' . $position, $parameter_name, $path); + $path = str_replace('{arg_' . $position . '}', '{' . $parameter_name . '}', $path); $argument_map['arg_' . $position] = $parameter_name; } + // Copy the original options from the route, so for example we ensure + // that parameter conversion options is carried over. + $route->setOptions($route->getOptions() + $original_route->getOptions()); + // Set the corrected path and the mapping to the route object. $route->setOption('_view_argument_map', $argument_map); $route->setPath($path); diff --git a/core/modules/views/tests/src/Unit/EventSubscriber/RouteSubscriberTest.php b/core/modules/views/tests/src/Unit/EventSubscriber/RouteSubscriberTest.php index e48c3110f0dcdc50127f48d22e306b46d4e3878c..16788d9281ab9425cf53f27ed3c04600067788f2 100644 --- a/core/modules/views/tests/src/Unit/EventSubscriber/RouteSubscriberTest.php +++ b/core/modules/views/tests/src/Unit/EventSubscriber/RouteSubscriberTest.php @@ -88,6 +88,7 @@ public function testRouteRebuildFinished() { */ public function testOnAlterRoutes() { $collection = new RouteCollection(); + // The first route will be overridden later. $collection->add('test_route', new Route('test_route', array('_controller' => 'Drupal\Tests\Core\Controller\TestController'))); $route_2 = new Route('test_route/example', array('_controller' => 'Drupal\Tests\Core\Controller\TestController')); $collection->add('test_route_2', $route_2); @@ -98,29 +99,40 @@ public function testOnAlterRoutes() { // The page_1 display overrides an existing route, so the dynamicRoutes // should only call the second display. + $display_1->expects($this->once()) + ->method('collectRoutes') + ->willReturnCallback(function() use ($collection) { + $collection->add('views.test_id.page_1', new Route('test_route', ['_content' => 'Drupal\views\Routing\ViewPageController'])); + return ['test_id.page_1' => 'views.test_id.page_1']; + }); $display_1->expects($this->once()) ->method('alterRoutes') - ->will($this->returnValue(array('test_id.page_1' => 'test_route'))); - $display_1->expects($this->never()) - ->method('collectRoutes'); + ->willReturn(['test_id.page_1' => 'test_route']); - $display_2->expects($this->once()) - ->method('alterRoutes') - ->will($this->returnValue(array())); $display_2->expects($this->once()) ->method('collectRoutes') - ->will($this->returnValue(array('test_id.page_2' => 'views.test_id.page_2'))); - - $this->assertNull($this->routeSubscriber->onAlterRoutes($route_event)); + ->willReturnCallback(function() use ($collection) { + $collection->add('views.test_id.page_2', new Route('test_route', ['_content' => 'Drupal\views\Routing\ViewPageController'])); + return ['test_id.page_2' => 'views.test_id.page_2']; + }); + $display_2->expects($this->once()) + ->method('alterRoutes') + ->willReturn([]); - // Ensure that after the alterRoutes the collectRoutes method is just called - // once (not for page_1 anymore). + // Ensure that even both the collectRoutes() and alterRoutes() methods + // are called on the displays, we ensure that the route first defined by + // views is dropped. $this->routeSubscriber->routes(); + $this->assertNull($this->routeSubscriber->onAlterRoutes($route_event)); $this->state->expects($this->once()) ->method('set') ->with('views.view_route_names', array('test_id.page_1' => 'test_route', 'test_id.page_2' => 'views.test_id.page_2')); + + $collection = $route_event->getRouteCollection(); + $this->assertEquals(['test_route', 'test_route_2', 'views.test_id.page_2'], array_keys($collection->all())); + $this->routeSubscriber->routeRebuildFinished(); } diff --git a/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php index a6d3faba7a31b954d4b4afd233500de8cdab5727..56ec973e47fdeb7923c042eaf9cffafcf9b08eba 100644 --- a/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php @@ -205,7 +205,7 @@ public function testCollectRoutesWithNamedParameters() { } /** - * Tests alter routes with parameters in the overriding route. + * Tests altering routes with parameters in the overridden route. */ public function testAlterRoutesWithParameters() { $collection = new RouteCollection(); @@ -213,7 +213,7 @@ public function testAlterRoutesWithParameters() { list($view) = $this->setupViewExecutableAccessPlugin(); - // Manually setup an argument handler. + // Manually set up an argument handler. $argument = $this->getMockBuilder('Drupal\views\Plugin\views\argument\ArgumentPluginBase') ->disableOriginalConstructor() ->getMock(); @@ -240,6 +240,77 @@ public function testAlterRoutesWithParameters() { $this->assertEquals(array('arg_0' => 'parameter'), $route->getOption('_view_argument_map')); } + /** + * Tests altering routes with parameters and upcasting information + */ + public function testAlterRoutesWithParametersAndUpcasting() { + $collection = new RouteCollection(); + $collection->add('test_route', new Route('test_route/{parameter}', ['_controller' => 'Drupal\Tests\Core\Controller\TestController::content'], [], ['parameters' => ['taxonomy_term' => 'entity:entity_test']])); + + list($view) = $this->setupViewExecutableAccessPlugin(); + + // Manually set up an argument handler. + $argument = $this->getMockBuilder('Drupal\views\Plugin\views\argument\ArgumentPluginBase') + ->disableOriginalConstructor() + ->getMock(); + $view->argument['test_id'] = $argument; + + $display = array(); + $display['display_plugin'] = 'page'; + $display['id'] = 'page_1'; + $display['display_options'] = [ + 'path' => 'test_route/%', + ]; + $this->pathPlugin->initDisplay($view, $display); + + $view_route_names = $this->pathPlugin->alterRoutes($collection); + $this->assertEquals(['test_id.page_1' => 'test_route'], $view_route_names); + + // Ensure that the test_route is overridden. + $route = $collection->get('test_route'); + $this->assertInstanceOf('\Symfony\Component\Routing\Route', $route); + $this->assertEquals('test_id', $route->getDefault('view_id')); + $this->assertEquals('page_1', $route->getDefault('display_id')); + $this->assertEquals(['taxonomy_term' => 'entity:entity_test'], $route->getOption('parameters')); + // Ensure that the path did not changed and placeholders are respected kk. + $this->assertEquals('/test_route/{parameter}', $route->getPath()); + $this->assertEquals(['arg_0' => 'parameter'], $route->getOption('_view_argument_map')); + } + + /** + * Tests altering routes with optional parameters in the overridden route. + */ + public function testAlterRoutesWithOptionalParameters() { + $collection = new RouteCollection(); + $collection->add('test_route', new Route('test_route/{parameter}', array('_controller' => 'Drupal\Tests\Core\Controller\TestController::content'))); + + list($view) = $this->setupViewExecutableAccessPlugin(); + + $display = array(); + $display['display_plugin'] = 'page'; + $display['id'] = 'page_1'; + $display['display_options'] = array( + 'path' => 'test_route/%', + ); + $display['display_options']['arguments'] = array( + 'test_id' => array(), + 'test_id2' => array(), + ); + $this->pathPlugin->initDisplay($view, $display); + + $view_route_names = $this->pathPlugin->alterRoutes($collection); + $this->assertEquals(array('test_id.page_1' => 'test_route'), $view_route_names); + + // Ensure that the test_route is overridden. + $route = $collection->get('test_route'); + $this->assertInstanceOf('\Symfony\Component\Routing\Route', $route); + $this->assertEquals('test_id', $route->getDefault('view_id')); + $this->assertEquals('page_1', $route->getDefault('display_id')); + // Ensure that the path did not changed and placeholders are respected. + $this->assertEquals('/test_route/{parameter}/{arg_1}', $route->getPath()); + $this->assertEquals(array('arg_0' => 'parameter'), $route->getOption('_view_argument_map')); + } + /** * Returns some mocked view entity, view executable, and access plugin. */