Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,9 @@
langcode: en
status: true
dependencies:
module:
- taxonomy
id: taxonomy_term.full
label: 'Taxonomy term page'
targetEntityType: taxonomy_term
cache: true

View file

@ -0,0 +1,3 @@
maintain_index_table: true
override_selector: false
terms_per_page_admin: 100

View file

@ -0,0 +1,311 @@
langcode: en
status: true
dependencies:
config:
- core.entity_view_mode.node.teaser
module:
- node
- taxonomy
- user
id: taxonomy_term
label: 'Taxonomy term'
module: taxonomy
description: 'Content belonging to a certain taxonomy term.'
tag: default
base_table: node_field_data
base_field: nid
core: '8'
display:
default:
id: default
display_title: Master
display_plugin: default
position: 0
display_options:
query:
type: views_query
options:
query_comment: ''
disable_sql_rewrite: false
distinct: false
replica: false
query_tags: { }
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: mini
options:
items_per_page: 10
offset: 0
id: 0
total_pages: 0
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous:
next:
sorts:
sticky:
id: sticky
table: taxonomy_index
field: sticky
order: DESC
plugin_id: standard
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
created:
id: created
table: taxonomy_index
field: created
order: DESC
plugin_id: date
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
granularity: second
arguments:
tid:
id: tid
table: taxonomy_index
field: tid
relationship: none
group_type: group
admin_label: ''
default_action: 'not found'
exception:
value: ''
title_enable: false
title: All
title_enable: true
title: '{{ arguments.tid }}'
default_argument_type: fixed
default_argument_options:
argument: ''
default_argument_skip_url: false
summary_options:
base_path: ''
count: true
items_per_page: 25
override: false
summary:
sort_order: asc
number_of_records: 0
format: default_summary
specify_validation: true
validate:
type: 'entity:taxonomy_term'
fail: 'not found'
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
filters:
langcode:
id: langcode
table: node_field_data
field: langcode
relationship: none
group_type: group
admin_label: ''
operator: in
value:
'***LANGUAGE_language_content***': '***LANGUAGE_language_content***'
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
reduce: false
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: language
entity_type: node
entity_field: langcode
status:
id: status
table: taxonomy_index
field: status
relationship: none
group_type: group
admin_label: ''
operator: '='
value: '1'
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: boolean
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: 'entity:node'
options:
view_mode: teaser
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
target: '{{ raw_arguments.tid }}'
view_mode: full
bypass_access: false
plugin_id: entity
footer: { }
empty: { }
relationships: { }
fields: { }
display_extenders: { }
link_url: ''
link_display: page_1
cache_metadata:
contexts:
- 'languages:language_interface'
- url
- url.query_args
- 'user.node_grants:view'
- user.permissions
max-age: -1
tags: { }
feed_1:
id: feed_1
display_title: Feed
display_plugin: feed
position: 2
display_options:
query:
type: views_query
options: { }
pager:
type: some
options:
items_per_page: 10
offset: 0
path: taxonomy/term/%/feed
displays:
page_1: page_1
default: '0'
style:
type: rss
options:
description: ''
grouping: { }
uses_fields: false
row:
type: node_rss
options:
relationship: none
view_mode: default
display_extenders: { }
cache_metadata:
contexts:
- 'languages:language_interface'
- url
- 'user.node_grants:view'
- user.permissions
max-age: -1
tags: { }
page_1:
id: page_1
display_title: Page
display_plugin: page
position: 1
display_options:
query:
type: views_query
options: { }
path: taxonomy/term/%
display_extenders: { }
cache_metadata:
contexts:
- 'languages:language_interface'
- url
- url.query_args
- 'user.node_grants:view'
- user.permissions
max-age: -1
tags: { }

View file

@ -0,0 +1,39 @@
# Schema for the configuration files of the Taxonomy module.
taxonomy.settings:
type: config_object
label: 'Taxonomy settings'
mapping:
maintain_index_table:
type: boolean
label: 'Maintain index table'
override_selector:
type: boolean
label: 'Override selector'
terms_per_page_admin:
type: integer
label: 'Number of terms per page'
taxonomy.vocabulary.*:
type: config_entity
label: 'Vocabulary'
mapping:
name:
type: label
label: 'Name'
vid:
type: string
label: 'Machine name'
description:
type: label
label: 'Description'
hierarchy:
type: integer
label: 'Hierarchy'
weight:
type: integer
label: 'Weight'
field.formatter.settings.entity_reference_rss_category:
type: mapping
label: 'Taxonomy format settings'

View file

@ -0,0 +1,162 @@
# Schema for the views plugins of the Taxonomy module.
views.argument.taxonomy_index_tid:
type: views.argument.many_to_one
label: 'Taxonomy term ID'
views.argument.taxonomy_index_tid_depth:
type: views_argument
label: 'Taxonomy term ID'
mapping:
depth:
type: integer
label: 'Depth'
break_phrase:
type: boolean
label: 'Allow multiple values'
use_taxonomy_term_path:
type: boolean
label: 'Use taxonomy term path'
views.argument.taxonomy_index_tid_depth_modifier:
type: views_argument
label: 'Taxonomy depth modifier'
views.argument.taxonomy:
type: views_argument
label: 'Taxonomy'
mapping:
break_phrase:
type: boolean
label: 'Allow multiple values'
not:
type: boolean
label: 'Exclude'
views.argument.vocabulary_vid:
type: views_argument
label: 'Vocabulary'
mapping:
break_phrase:
type: boolean
label: 'Allow multiple values'
not:
type: boolean
label: 'Exclude'
views.argument_validator.entity:taxonomy_term:
type: views.argument_validator_entity
label: 'Taxonomy term'
views.argument_validator.taxonomy_term_name:
type: views.argument_validator_entity
label: 'Taxonomy term'
mapping:
vids:
type: sequence
label: 'Vocabularies'
sequence:
type: string
label: 'Vocabulary'
transform:
type: boolean
label: 'Transform dashes in URL to spaces in term name filter values'
views.argument_default.taxonomy_tid:
type: mapping
label: 'Taxonomy term ID from URL'
mapping:
term_page:
type: string
label: 'Load default filter from term page'
node:
type: boolean
label: 'Load default filter from node page, that''s good for related taxonomy blocks'
limit:
type: boolean
label: 'Limit terms by vocabulary'
vids:
type: sequence
label: 'Vocabularies'
sequence:
type: string
label: 'Vocabulary'
anyall:
type: string
label: 'Multiple-value handling'
views.field.term_name:
type: views.field.field
mapping:
convert_spaces:
type: boolean
label: 'Convert spaces in term names to hyphens'
views.field.taxonomy_index_tid:
type: views_field
label: 'Taxonomy language'
mapping:
type:
type: string
label: 'Display type'
separator:
type: string
label: 'Separator'
link_to_taxonomy:
type: boolean
label: 'Link this field to its term page'
limit:
type: boolean
label: 'Limit terms by vocabulary'
vids:
type: sequence
label: 'Vocabularies'
sequence:
type: string
label: 'Vocabulary'
views.filter.taxonomy_index_tid:
type: views.filter.many_to_one
label: 'Taxonomy term ID'
mapping:
vid:
type: string
label: 'Vocabulary'
type:
type: string
label: 'Selection type'
hierarchy:
type: boolean
label: 'Show hierarchy in dropdown'
limit:
type: boolean
label: 'Limit to vocabulary'
error_message:
type: boolean
label: 'Display error message'
value:
type: sequence
label: 'Values'
sequence:
type: integer
label: 'Value'
views.filter.taxonomy_index_tid_depth:
type: views.filter.taxonomy_index_tid
label: 'Taxonomy term ID with depth'
mapping:
depth:
type: integer
label: 'Depth'
views.relationship.node_term_data:
type: views_relationship
label: 'Taxonomy term'
mapping:
vids:
type: sequence
label: 'Vocabularies'
sequence:
type: string
label: 'Vocabulary'

View file

@ -0,0 +1,10 @@
.taxonomy-term-preview {
background-color: #eee;
}
.taxonomy-term-divider-top {
border-bottom: none;
}
.taxonomy-term-divider-bottom {
border-top: 1px dotted #ccc;
}

View file

@ -0,0 +1,36 @@
id: d6_taxonomy_term
label: Taxonomy terms
migration_tags:
- Drupal 6
source:
plugin: d6_taxonomy_term
process:
# If you are using this file to build a custom migration consider removing
# the tid field to allow incremental migrations.
tid: tid
vid:
plugin: migration
migration: d6_taxonomy_vocabulary
source: vid
name: name
description: description
weight: weight
# Only attempt to stub real (non-zero) parents.
parent_id:
-
plugin: skip_on_empty
method: process
source: parent
-
plugin: migration
migration: d6_taxonomy_term
parent:
plugin: default_value
default_value: 0
source: '@parent_id'
changed: timestamp
destination:
plugin: entity:taxonomy_term
migration_dependencies:
required:
- d6_taxonomy_vocabulary

View file

@ -0,0 +1,24 @@
id: d6_taxonomy_vocabulary
label: Taxonomy vocabularies
migration_tags:
- Drupal 6
source:
plugin: d6_taxonomy_vocabulary
process:
vid:
-
plugin: machine_name
source: name
-
plugin: dedupe_entity
entity_type: taxonomy_vocabulary
field: vid
length: 32
migrated: true
label: name
name: name
description: description
hierarchy: hierarchy
weight: weight
destination:
plugin: entity:taxonomy_vocabulary

View file

@ -0,0 +1,25 @@
id: d6_term_node
label: Term/node relationships
migration_tags:
- Drupal 6
deriver: Drupal\taxonomy\Plugin\migrate\D6TermNodeDeriver
source:
plugin: d6_term_node
process:
nid:
-
plugin: migration
migration: d6_node
source: nid
-
plugin: skip_on_empty
method: row
type: type
# The actual field name is dynamic and will be added by the builder.
destination:
plugin: entity:node
migration_dependencies:
required:
- d6_vocabulary_entity_display
- d6_vocabulary_entity_form_display
- d6_node

View file

@ -0,0 +1,24 @@
id: d6_term_node_revision
label: Term/node relationship revisions
migration_tags:
- Drupal 6
deriver: Drupal\taxonomy\Plugin\migrate\D6TermNodeDeriver
source:
plugin: d6_term_node_revision
process:
vid:
-
plugin: migration
migration: d6_node
source: vid
-
plugin: skip_on_empty
method: row
type: type
# The actual field name is dynamic and will be added by the builder.
destination:
plugin: entity_revision:node
migration_dependencies:
required:
- d6_term_node
- d6_node_revision

View file

@ -0,0 +1,27 @@
id: d6_vocabulary_entity_display
label: Vocabulary display configuration
migration_tags:
- Drupal 6
source:
plugin: d6_taxonomy_vocabulary_per_type
constants:
entity_type: node
view_mode: default
options:
label: hidden
type: entity_reference_label
weight: 20
process:
entity_type: 'constants/entity_type'
view_mode: 'constants/view_mode'
options: 'constants/options'
bundle: type
field_name:
plugin: migration
migration: d6_taxonomy_vocabulary
source: vid
destination:
plugin: component_entity_display
migration_dependencies:
required:
- d6_vocabulary_field_instance

View file

@ -0,0 +1,31 @@
id: d6_vocabulary_entity_form_display
label: Vocabulary form display configuration
migration_tags:
- Drupal 6
source:
plugin: d6_taxonomy_vocabulary_per_type
constants:
entity_type: node
form_mode: default
options:
weight: 20
process:
entity_type: 'constants/entity_type'
form_mode: 'constants/form_mode'
options/type:
plugin: static_map
source: tags
map:
0: options_select
1: entity_reference_autocomplete_tags
options/weight: 'constants/options/weight'
bundle: type
field_name:
plugin: migration
migration: d6_taxonomy_vocabulary
source: vid
destination:
plugin: component_entity_form_display
migration_dependencies:
required:
- d6_vocabulary_field_instance

View file

@ -0,0 +1,31 @@
id: d6_vocabulary_field
label: Vocabulary field configuration
migration_tags:
- Drupal 6
source:
plugin: d6_taxonomy_vocabulary
constants:
entity_type: node
type: entity_reference
target_entity_type: taxonomy_term
process:
entity_type: 'constants/entity_type'
type: 'constants/type'
field_name:
-
plugin: migration
migration: d6_taxonomy_vocabulary
source: vid
-
plugin: skip_on_empty
method: row
'settings/target_type': 'constants/target_entity_type'
cardinality: cardinality
destination:
plugin: entity:field_storage_config
dependencies:
module:
- entity_reference
migration_dependencies:
required:
- d6_taxonomy_vocabulary

View file

@ -0,0 +1,32 @@
id: d6_vocabulary_field_instance
label: Vocabulary field instance configuration
migration_tags:
- Drupal 6
source:
plugin: d6_taxonomy_vocabulary_per_type
constants:
entity_type: node
auto_create: true
selection_handler: 'default:taxonomy_term'
process:
entity_type: 'constants/entity_type'
bundle: type
field_name:
-
plugin: migration
migration: d6_taxonomy_vocabulary
source: vid
-
plugin: skip_on_empty
method: row
label: name
'settings/handler': 'constants/selection_handler'
'settings/handler_settings/target_bundles/0': '@field_name'
'settings/handler_settings/auto_create': 'constants/auto_create'
required: required
destination:
plugin: entity:field_config
migration_dependencies:
required:
- d6_node_type
- d6_vocabulary_field

View file

@ -0,0 +1,40 @@
id: d7_taxonomy_term
label: Taxonomy terms
migration_tags:
- Drupal 7
deriver: Drupal\taxonomy\Plugin\migrate\D7TaxonomyTermDeriver
source:
plugin: d7_taxonomy_term
process:
# If you are using this file to build a custom migration consider removing
# the tid field to allow incremental migrations.
tid: tid
vid:
plugin: migration
migration: d7_taxonomy_vocabulary
source: vid
name: name
'description/value': description
'description/format': format
weight: weight
# Only attempt to stub real (non-zero) parents.
parent_id:
-
plugin: skip_on_empty
method: process
source: parent
-
plugin: migration
migration: d7_taxonomy_term
parent:
plugin: default_value
default_value: 0
source: '@parent_id'
changed: timestamp
destination:
plugin: entity:taxonomy_term
migration_dependencies:
required:
- d7_taxonomy_vocabulary
optional:
- d7_field_instance

View file

@ -0,0 +1,15 @@
id: d7_taxonomy_vocabulary
label: Taxonomy vocabularies
migration_tags:
- Drupal 7
source:
plugin: d7_taxonomy_vocabulary
process:
vid: machine_name
label: name
name: name
description: description
hierarchy: hierarchy
weight: weight
destination:
plugin: entity:taxonomy_vocabulary

View file

@ -0,0 +1,16 @@
id: taxonomy_settings
label: Taxonomy configuration
migration_tags:
- Drupal 6
- Drupal 7
source:
plugin: variable
variables:
- taxonomy_override_selector
- taxonomy_terms_per_page_admin
process:
override_selector: taxonomy_override_selector
terms_per_page_admin: taxonomy_terms_per_page_admin
destination:
plugin: config
config_name: taxonomy.settings

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\taxonomy\Controller;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Controller\ControllerBase;
use Drupal\taxonomy\TermInterface;
use Drupal\taxonomy\VocabularyInterface;
/**
* Provides route responses for taxonomy.module.
*/
class TaxonomyController extends ControllerBase {
/**
* Returns a form to add a new term to a vocabulary.
*
* @param \Drupal\taxonomy\VocabularyInterface $taxonomy_vocabulary
* The vocabulary this term will be added to.
*
* @return array
* The taxonomy term add form.
*/
public function addForm(VocabularyInterface $taxonomy_vocabulary) {
$term = $this->entityManager()->getStorage('taxonomy_term')->create(array('vid' => $taxonomy_vocabulary->id()));
return $this->entityFormBuilder()->getForm($term);
}
/**
* Route title callback.
*
* @param \Drupal\taxonomy\VocabularyInterface $taxonomy_vocabulary
* The vocabulary.
*
* @return string
* The vocabulary label as a render array.
*/
public function vocabularyTitle(VocabularyInterface $taxonomy_vocabulary) {
return ['#markup' => $taxonomy_vocabulary->label(), '#allowed_tags' => Xss::getHtmlTagList()];
}
/**
* Route title callback.
*
* @param \Drupal\taxonomy\TermInterface $taxonomy_term
* The taxonomy term.
*
* @return array
* The term label as a render array.
*/
public function termTitle(TermInterface $taxonomy_term) {
return ['#markup' => $taxonomy_term->getName(), '#allowed_tags' => Xss::getHtmlTagList()];
}
}

View file

@ -0,0 +1,237 @@
<?php
namespace Drupal\taxonomy\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\taxonomy\TermInterface;
/**
* Defines the taxonomy term entity.
*
* @ContentEntityType(
* id = "taxonomy_term",
* label = @Translation("Taxonomy term"),
* bundle_label = @Translation("Vocabulary"),
* handlers = {
* "storage" = "Drupal\taxonomy\TermStorage",
* "storage_schema" = "Drupal\taxonomy\TermStorageSchema",
* "view_builder" = "Drupal\taxonomy\TermViewBuilder",
* "access" = "Drupal\taxonomy\TermAccessControlHandler",
* "views_data" = "Drupal\taxonomy\TermViewsData",
* "form" = {
* "default" = "Drupal\taxonomy\TermForm",
* "delete" = "Drupal\taxonomy\Form\TermDeleteForm"
* },
* "translation" = "Drupal\taxonomy\TermTranslationHandler"
* },
* base_table = "taxonomy_term_data",
* data_table = "taxonomy_term_field_data",
* uri_callback = "taxonomy_term_uri",
* translatable = TRUE,
* entity_keys = {
* "id" = "tid",
* "bundle" = "vid",
* "label" = "name",
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* bundle_entity_type = "taxonomy_vocabulary",
* field_ui_base_route = "entity.taxonomy_vocabulary.overview_form",
* common_reference_target = TRUE,
* links = {
* "canonical" = "/taxonomy/term/{taxonomy_term}",
* "delete-form" = "/taxonomy/term/{taxonomy_term}/delete",
* "edit-form" = "/taxonomy/term/{taxonomy_term}/edit",
* },
* permission_granularity = "bundle"
* )
*/
class Term extends ContentEntityBase implements TermInterface {
use EntityChangedTrait;
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
// See if any of the term's children are about to be become orphans.
$orphans = array();
foreach (array_keys($entities) as $tid) {
if ($children = $storage->loadChildren($tid)) {
foreach ($children as $child) {
// If the term has multiple parents, we don't delete it.
$parents = $storage->loadParents($child->id());
if (empty($parents)) {
$orphans[] = $child->id();
}
}
}
}
// Delete term hierarchy information after looking up orphans but before
// deleting them so that their children/parent information is consistent.
$storage->deleteTermHierarchy(array_keys($entities));
if (!empty($orphans)) {
entity_delete_multiple('taxonomy_term', $orphans);
}
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
// Only change the parents if a value is set, keep the existing values if
// not.
if (isset($this->parent->target_id)) {
$storage->deleteTermHierarchy(array($this->id()));
$storage->updateTermHierarchy($this);
}
}
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
$fields = parent::baseFieldDefinitions($entity_type);
$fields['tid']->setLabel(t('Term ID'))
->setDescription(t('The term ID.'));
$fields['uuid']->setDescription(t('The term UUID.'));
$fields['vid']->setLabel(t('Vocabulary'))
->setDescription(t('The vocabulary to which the term is assigned.'));
$fields['langcode']->setDescription(t('The term language code.'));
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setDescription(t('The term name.'))
->setTranslatable(TRUE)
->setRequired(TRUE)
->setSetting('max_length', 255)
->setDisplayOptions('view', array(
'label' => 'hidden',
'type' => 'string',
'weight' => -5,
))
->setDisplayOptions('form', array(
'type' => 'string_textfield',
'weight' => -5,
))
->setDisplayConfigurable('form', TRUE);
$fields['description'] = BaseFieldDefinition::create('text_long')
->setLabel(t('Description'))
->setDescription(t('A description of the term.'))
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'label' => 'hidden',
'type' => 'text_default',
'weight' => 0,
))
->setDisplayConfigurable('view', TRUE)
->setDisplayOptions('form', array(
'type' => 'text_textfield',
'weight' => 0,
))
->setDisplayConfigurable('form', TRUE);
$fields['weight'] = BaseFieldDefinition::create('integer')
->setLabel(t('Weight'))
->setDescription(t('The weight of this term in relation to other terms.'))
->setDefaultValue(0);
$fields['parent'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Term Parents'))
->setDescription(t('The parents of this term.'))
->setSetting('target_type', 'taxonomy_term')
->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED)
->setCustomStorage(TRUE);
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time that the term was last edited.'))
->setTranslatable(TRUE);
return $fields;
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->get('description')->value;
}
/**
* {@inheritdoc}
*/
public function setDescription($description) {
$this->set('description', $description);
return $this;
}
/**
* {@inheritdoc}
*/
public function getFormat() {
return $this->get('description')->format;
}
/**
* {@inheritdoc}
*/
public function setFormat($format) {
$this->get('description')->format = $format;
return $this;
}
/**
* {@inheritdoc}
*/
public function getName() {
return $this->label();
}
/**
* {@inheritdoc}
*/
public function setName($name) {
$this->set('name', $name);
return $this;
}
/**
* {@inheritdoc}
*/
public function getWeight() {
return $this->get('weight')->value;
}
/**
* {@inheritdoc}
*/
public function setWeight($weight) {
$this->set('weight', $weight);
return $this;
}
/**
* {@inheritdoc}
*/
public function getVocabularyId() {
return $this->get('vid')->target_id;
}
}

View file

@ -0,0 +1,175 @@
<?php
namespace Drupal\taxonomy\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\taxonomy\VocabularyInterface;
/**
* Defines the taxonomy vocabulary entity.
*
* @ConfigEntityType(
* id = "taxonomy_vocabulary",
* label = @Translation("Taxonomy vocabulary"),
* handlers = {
* "storage" = "Drupal\taxonomy\VocabularyStorage",
* "list_builder" = "Drupal\taxonomy\VocabularyListBuilder",
* "form" = {
* "default" = "Drupal\taxonomy\VocabularyForm",
* "reset" = "Drupal\taxonomy\Form\VocabularyResetForm",
* "delete" = "Drupal\taxonomy\Form\VocabularyDeleteForm"
* }
* },
* admin_permission = "administer taxonomy",
* config_prefix = "vocabulary",
* bundle_of = "taxonomy_term",
* entity_keys = {
* "id" = "vid",
* "label" = "name",
* "weight" = "weight"
* },
* links = {
* "add-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/add",
* "delete-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/delete",
* "reset-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/reset",
* "overview-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/overview",
* "edit-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}",
* "collection" = "/admin/structure/taxonomy",
* },
* config_export = {
* "name",
* "vid",
* "description",
* "hierarchy",
* "weight",
* }
* )
*/
class Vocabulary extends ConfigEntityBundleBase implements VocabularyInterface {
/**
* The taxonomy vocabulary ID.
*
* @var string
*/
protected $vid;
/**
* Name of the vocabulary.
*
* @var string
*/
protected $name;
/**
* Description of the vocabulary.
*
* @var string
*/
protected $description;
/**
* The type of hierarchy allowed within the vocabulary.
*
* Possible values:
* - VocabularyInterface::HIERARCHY_DISABLED: No parents.
* - VocabularyInterface::HIERARCHY_SINGLE: Single parent.
* - VocabularyInterface::HIERARCHY_MULTIPL: Multiple parents.
*
* @var int
*/
protected $hierarchy = VocabularyInterface::HIERARCHY_DISABLED;
/**
* The weight of this vocabulary in relation to other vocabularies.
*
* @var int
*/
protected $weight = 0;
/**
* {@inheritdoc}
*/
public function getHierarchy() {
return $this->hierarchy;
}
/**
* {@inheritdoc}
*/
public function setHierarchy($hierarchy) {
$this->hierarchy = $hierarchy;
return $this;
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->vid;
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->description;
}
/**
* {@inheritdoc}
*/
public static function preDelete(EntityStorageInterface $storage, array $entities) {
parent::preDelete($storage, $entities);
// Only load terms without a parent, child terms will get deleted too.
entity_delete_multiple('taxonomy_term', $storage->getToplevelTids(array_keys($entities)));
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
// Reset caches.
$storage->resetCache(array_keys($entities));
if (reset($entities)->isSyncing()) {
return;
}
$vocabularies = array();
foreach ($entities as $vocabulary) {
$vocabularies[$vocabulary->id()] = $vocabulary->id();
}
// Load all Taxonomy module fields and delete those which use only this
// vocabulary.
$field_storages = entity_load_multiple_by_properties('field_storage_config', array('module' => 'taxonomy'));
foreach ($field_storages as $field_storage) {
$modified_storage = FALSE;
// Term reference fields may reference terms from more than one
// vocabulary.
foreach ($field_storage->getSetting('allowed_values') as $key => $allowed_value) {
if (isset($vocabularies[$allowed_value['vocabulary']])) {
$allowed_values = $field_storage->getSetting('allowed_values');
unset($allowed_values[$key]);
$field_storage->setSetting('allowed_values', $allowed_values);
$modified_storage = TRUE;
}
}
if ($modified_storage) {
$allowed_values = $field_storage->getSetting('allowed_values');
if (empty($allowed_values)) {
$field_storage->delete();
}
else {
// Update the field definition with the new allowed values.
$field_storage->save();
}
}
}
}
}

View file

@ -0,0 +1,468 @@
<?php
namespace Drupal\taxonomy\Form;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\taxonomy\VocabularyInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides terms overview form for a taxonomy vocabulary.
*/
class OverviewTerms extends FormBase {
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The term storage handler.
*
* @var \Drupal\taxonomy\TermStorageInterface
*/
protected $storageController;
/**
* Constructs an OverviewTerms object.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
*/
public function __construct(ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager) {
$this->moduleHandler = $module_handler;
$this->storageController = $entity_manager->getStorage('taxonomy_term');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('module_handler'),
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'taxonomy_overview_terms';
}
/**
* Form constructor.
*
* Display a tree of all the terms in a vocabulary, with options to edit
* each one. The form is made drag and drop by the theme function.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param \Drupal\taxonomy\VocabularyInterface $taxonomy_vocabulary
* The vocabulary to display the overview form for.
*
* @return array
* The form structure.
*/
public function buildForm(array $form, FormStateInterface $form_state, VocabularyInterface $taxonomy_vocabulary = NULL) {
// @todo Remove global variables when https://www.drupal.org/node/2044435 is
// in.
global $pager_page_array, $pager_total, $pager_total_items;
$form_state->set(['taxonomy', 'vocabulary'], $taxonomy_vocabulary);
$parent_fields = FALSE;
$page = $this->getRequest()->query->get('page') ?: 0;
// Number of terms per page.
$page_increment = $this->config('taxonomy.settings')->get('terms_per_page_admin');
// Elements shown on this page.
$page_entries = 0;
// Elements at the root level before this page.
$before_entries = 0;
// Elements at the root level after this page.
$after_entries = 0;
// Elements at the root level on this page.
$root_entries = 0;
// Terms from previous and next pages are shown if the term tree would have
// been cut in the middle. Keep track of how many extra terms we show on
// each page of terms.
$back_step = NULL;
$forward_step = 0;
// An array of the terms to be displayed on this page.
$current_page = array();
$delta = 0;
$term_deltas = array();
$tree = $this->storageController->loadTree($taxonomy_vocabulary->id(), 0, NULL, TRUE);
$tree_index = 0;
do {
// In case this tree is completely empty.
if (empty($tree[$tree_index])) {
break;
}
$delta++;
// Count entries before the current page.
if ($page && ($page * $page_increment) > $before_entries && !isset($back_step)) {
$before_entries++;
continue;
}
// Count entries after the current page.
elseif ($page_entries > $page_increment && isset($complete_tree)) {
$after_entries++;
continue;
}
// Do not let a term start the page that is not at the root.
$term = $tree[$tree_index];
if (isset($term->depth) && ($term->depth > 0) && !isset($back_step)) {
$back_step = 0;
while ($pterm = $tree[--$tree_index]) {
$before_entries--;
$back_step++;
if ($pterm->depth == 0) {
$tree_index--;
// Jump back to the start of the root level parent.
continue 2;
}
}
}
$back_step = isset($back_step) ? $back_step : 0;
// Continue rendering the tree until we reach the a new root item.
if ($page_entries >= $page_increment + $back_step + 1 && $term->depth == 0 && $root_entries > 1) {
$complete_tree = TRUE;
// This new item at the root level is the first item on the next page.
$after_entries++;
continue;
}
if ($page_entries >= $page_increment + $back_step) {
$forward_step++;
}
// Finally, if we've gotten down this far, we're rendering a term on this
// page.
$page_entries++;
$term_deltas[$term->id()] = isset($term_deltas[$term->id()]) ? $term_deltas[$term->id()] + 1 : 0;
$key = 'tid:' . $term->id() . ':' . $term_deltas[$term->id()];
// Keep track of the first term displayed on this page.
if ($page_entries == 1) {
$form['#first_tid'] = $term->id();
}
// Keep a variable to make sure at least 2 root elements are displayed.
if ($term->parents[0] == 0) {
$root_entries++;
}
$current_page[$key] = $term;
} while (isset($tree[++$tree_index]));
// Because we didn't use a pager query, set the necessary pager variables.
$total_entries = $before_entries + $page_entries + $after_entries;
$pager_total_items[0] = $total_entries;
$pager_page_array[0] = $page;
$pager_total[0] = ceil($total_entries / $page_increment);
// If this form was already submitted once, it's probably hit a validation
// error. Ensure the form is rebuilt in the same order as the user
// submitted.
$user_input = $form_state->getUserInput();
if (!empty($user_input)) {
// Get the POST order.
$order = array_flip(array_keys($user_input['terms']));
// Update our form with the new order.
$current_page = array_merge($order, $current_page);
foreach ($current_page as $key => $term) {
// Verify this is a term for the current page and set at the current
// depth.
if (is_array($user_input['terms'][$key]) && is_numeric($user_input['terms'][$key]['term']['tid'])) {
$current_page[$key]->depth = $user_input['terms'][$key]['term']['depth'];
}
else {
unset($current_page[$key]);
}
}
}
$errors = $form_state->getErrors();
$destination = $this->getDestinationArray();
$row_position = 0;
// Build the actual form.
$form['terms'] = array(
'#type' => 'table',
'#header' => array($this->t('Name'), $this->t('Weight'), $this->t('Operations')),
'#empty' => $this->t('No terms available. <a href=":link">Add term</a>.', array(':link' => $this->url('entity.taxonomy_term.add_form', array('taxonomy_vocabulary' => $taxonomy_vocabulary->id())))),
'#attributes' => array(
'id' => 'taxonomy',
),
);
foreach ($current_page as $key => $term) {
/** @var $term \Drupal\Core\Entity\EntityInterface */
$form['terms'][$key]['#term'] = $term;
$indentation = array();
if (isset($term->depth) && $term->depth > 0) {
$indentation = array(
'#theme' => 'indentation',
'#size' => $term->depth,
);
}
$form['terms'][$key]['term'] = array(
'#prefix' => !empty($indentation) ? drupal_render($indentation) : '',
'#type' => 'link',
'#title' => $term->getName(),
'#url' => $term->urlInfo(),
);
if ($taxonomy_vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) {
$parent_fields = TRUE;
$form['terms'][$key]['term']['tid'] = array(
'#type' => 'hidden',
'#value' => $term->id(),
'#attributes' => array(
'class' => array('term-id'),
),
);
$form['terms'][$key]['term']['parent'] = array(
'#type' => 'hidden',
// Yes, default_value on a hidden. It needs to be changeable by the
// javascript.
'#default_value' => $term->parents[0],
'#attributes' => array(
'class' => array('term-parent'),
),
);
$form['terms'][$key]['term']['depth'] = array(
'#type' => 'hidden',
// Same as above, the depth is modified by javascript, so it's a
// default_value.
'#default_value' => $term->depth,
'#attributes' => array(
'class' => array('term-depth'),
),
);
}
$form['terms'][$key]['weight'] = array(
'#type' => 'weight',
'#delta' => $delta,
'#title' => $this->t('Weight for added term'),
'#title_display' => 'invisible',
'#default_value' => $term->getWeight(),
'#attributes' => array(
'class' => array('term-weight'),
),
);
$operations = array(
'edit' => array(
'title' => $this->t('Edit'),
'query' => $destination,
'url' => $term->urlInfo('edit-form'),
),
'delete' => array(
'title' => $this->t('Delete'),
'query' => $destination,
'url' => $term->urlInfo('delete-form'),
),
);
if ($this->moduleHandler->moduleExists('content_translation') && content_translation_translate_access($term)->isAllowed()) {
$operations['translate'] = array(
'title' => $this->t('Translate'),
'query' => $destination,
'url' => $term->urlInfo('drupal:content-translation-overview'),
);
}
$form['terms'][$key]['operations'] = array(
'#type' => 'operations',
'#links' => $operations,
);
$form['terms'][$key]['#attributes']['class'] = array();
if ($parent_fields) {
$form['terms'][$key]['#attributes']['class'][] = 'draggable';
}
// Add classes that mark which terms belong to previous and next pages.
if ($row_position < $back_step || $row_position >= $page_entries - $forward_step) {
$form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-preview';
}
if ($row_position !== 0 && $row_position !== count($tree) - 1) {
if ($row_position == $back_step - 1 || $row_position == $page_entries - $forward_step - 1) {
$form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-divider-top';
}
elseif ($row_position == $back_step || $row_position == $page_entries - $forward_step) {
$form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-divider-bottom';
}
}
// Add an error class if this row contains a form error.
foreach ($errors as $error_key => $error) {
if (strpos($error_key, $key) === 0) {
$form['terms'][$key]['#attributes']['class'][] = 'error';
}
}
$row_position++;
}
if ($parent_fields) {
$form['terms']['#tabledrag'][] = array(
'action' => 'match',
'relationship' => 'parent',
'group' => 'term-parent',
'subgroup' => 'term-parent',
'source' => 'term-id',
'hidden' => FALSE,
);
$form['terms']['#tabledrag'][] = array(
'action' => 'depth',
'relationship' => 'group',
'group' => 'term-depth',
'hidden' => FALSE,
);
$form['terms']['#attached']['library'][] = 'taxonomy/drupal.taxonomy';
$form['terms']['#attached']['drupalSettings']['taxonomy'] = [
'backStep' => $back_step,
'forwardStep' => $forward_step,
];
}
$form['terms']['#tabledrag'][] = array(
'action' => 'order',
'relationship' => 'sibling',
'group' => 'term-weight',
);
if ($taxonomy_vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) {
$form['actions'] = array('#type' => 'actions', '#tree' => FALSE);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save'),
'#button_type' => 'primary',
);
$form['actions']['reset_alphabetical'] = array(
'#type' => 'submit',
'#submit' => array('::submitReset'),
'#value' => $this->t('Reset to alphabetical'),
);
}
$form['pager_pager'] = ['#type' => 'pager'];
return $form;
}
/**
* Form submission handler.
*
* Rather than using a textfield or weight field, this form depends entirely
* upon the order of form elements on the page to determine new weights.
*
* Because there might be hundreds or thousands of taxonomy terms that need to
* be ordered, terms are weighted from 0 to the number of terms in the
* vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted
* lowest to highest, but are not necessarily sequential. Numbers may be
* skipped when a term has children so that reordering is minimal when a child
* is added or removed from a term.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Sort term order based on weight.
uasort($form_state->getValue('terms'), array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
$vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
// Update the current hierarchy type as we go.
$hierarchy = VocabularyInterface::HIERARCHY_DISABLED;
$changed_terms = array();
$tree = $this->storageController->loadTree($vocabulary->id(), 0, NULL, TRUE);
if (empty($tree)) {
return;
}
// Build a list of all terms that need to be updated on previous pages.
$weight = 0;
$term = $tree[0];
while ($term->id() != $form['#first_tid']) {
if ($term->parents[0] == 0 && $term->getWeight() != $weight) {
$term->setWeight($weight);
$changed_terms[$term->id()] = $term;
}
$weight++;
$hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE : $hierarchy;
$term = $tree[$weight];
}
// Renumber the current page weights and assign any new parents.
$level_weights = array();
foreach ($form_state->getValue('terms') as $tid => $values) {
if (isset($form['terms'][$tid]['#term'])) {
$term = $form['terms'][$tid]['#term'];
// Give terms at the root level a weight in sequence with terms on previous pages.
if ($values['term']['parent'] == 0 && $term->getWeight() != $weight) {
$term->setWeight($weight);
$changed_terms[$term->id()] = $term;
}
// Terms not at the root level can safely start from 0 because they're all on this page.
elseif ($values['term']['parent'] > 0) {
$level_weights[$values['term']['parent']] = isset($level_weights[$values['term']['parent']]) ? $level_weights[$values['term']['parent']] + 1 : 0;
if ($level_weights[$values['term']['parent']] != $term->getWeight()) {
$term->setWeight($level_weights[$values['term']['parent']]);
$changed_terms[$term->id()] = $term;
}
}
// Update any changed parents.
if ($values['term']['parent'] != $term->parents[0]) {
$term->parent->target_id = $values['term']['parent'];
$changed_terms[$term->id()] = $term;
}
$hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE : $hierarchy;
$weight++;
}
}
// Build a list of all terms that need to be updated on following pages.
for ($weight; $weight < count($tree); $weight++) {
$term = $tree[$weight];
if ($term->parents[0] == 0 && $term->getWeight() != $weight) {
$term->parent->target_id = $term->parents[0];
$term->setWeight($weight);
$changed_terms[$term->id()] = $term;
}
$hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE : $hierarchy;
}
// Save all updated terms.
foreach ($changed_terms as $term) {
$term->save();
}
// Update the vocabulary hierarchy to flat or single hierarchy.
if ($vocabulary->getHierarchy() != $hierarchy) {
$vocabulary->setHierarchy($hierarchy);
$vocabulary->save();
}
drupal_set_message($this->t('The configuration options have been saved.'));
}
/**
* Redirects to confirmation form for the reset action.
*/
public function submitReset(array &$form, FormStateInterface $form_state) {
/** @var $vocabulary \Drupal\taxonomy\VocabularyInterface */
$vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
$form_state->setRedirectUrl($vocabulary->urlInfo('reset-form'));
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Drupal\taxonomy\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\ContentEntityDeleteForm;
use Drupal\Core\Url;
/**
* Provides a deletion confirmation form for taxonomy term.
*/
class TermDeleteForm extends ContentEntityDeleteForm {
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
// The cancel URL is the vocabulary collection, terms have no global
// list page.
return new Url('entity.taxonomy_vocabulary.collection');
}
/**
* {@inheritdoc}
*/
protected function getRedirectUrl() {
return $this->getCancelUrl();
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('Deleting a term will delete all its children if there are any. This action cannot be undone.');
}
/**
* {@inheritdoc}
*/
protected function getDeletionMessage() {
return $this->t('Deleted term %name.', array('%name' => $this->entity->label()));
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
/** @var \Drupal\Core\Entity\ContentEntityInterface $term */
$term = $this->getEntity();
if ($term->isDefaultTranslation()) {
$storage = $this->entityManager->getStorage('taxonomy_vocabulary');
$vocabulary = $storage->load($this->entity->bundle());
// @todo Move to storage http://drupal.org/node/1988712
taxonomy_check_vocabulary_hierarchy($vocabulary, array('tid' => $term->id()));
}
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\taxonomy\Form;
use Drupal\Core\Entity\EntityDeleteForm;
/**
* Provides a deletion confirmation form for taxonomy vocabulary.
*/
class VocabularyDeleteForm extends EntityDeleteForm {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'taxonomy_vocabulary_confirm_delete';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete the vocabulary %title?', array('%title' => $this->entity->label()));
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.');
}
/**
* {@inheritdoc}
*/
protected function getDeletionMessage() {
return $this->t('Deleted vocabulary %name.', array('%name' => $this->entity->label()));
}
}

View file

@ -0,0 +1,88 @@
<?php
namespace Drupal\taxonomy\Form;
use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\taxonomy\TermStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides confirmation form for resetting a vocabulary to alphabetical order.
*/
class VocabularyResetForm extends EntityConfirmFormBase {
/**
* The term storage.
*
* @var \Drupal\taxonomy\TermStorageInterface
*/
protected $termStorage;
/**
* Constructs a new VocabularyResetForm object.
*
* @param \Drupal\taxonomy\TermStorageInterface $term_storage
* The term storage.
*/
public function __construct(TermStorageInterface $term_storage) {
$this->termStorage = $term_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')->getStorage('taxonomy_term')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'taxonomy_vocabulary_confirm_reset_alphabetical';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to reset the vocabulary %title to alphabetical order?', array('%title' => $this->entity->label()));
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return $this->entity->urlInfo('overview-form');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('Resetting a vocabulary will discard all custom ordering and sort items alphabetically.');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Reset to alphabetical');
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
$this->termStorage->resetWeights($this->entity->id());
drupal_set_message($this->t('Reset vocabulary %name to alphabetical order.', array('%name' => $this->entity->label())));
$this->logger('taxonomy')->notice('Reset vocabulary %name to alphabetical order.', array('%name' => $this->entity->label()));
$form_state->setRedirectUrl($this->getCancelUrl());
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace Drupal\taxonomy\Plugin\EntityReferenceSelection;
use Drupal\Component\Utility\Html;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
use Drupal\Core\Form\FormStateInterface;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Provides specific access control for the taxonomy_term entity type.
*
* @EntityReferenceSelection(
* id = "default:taxonomy_term",
* label = @Translation("Taxonomy Term selection"),
* entity_types = {"taxonomy_term"},
* group = "default",
* weight = 1
* )
*/
class TermSelection extends DefaultSelection {
/**
* {@inheritdoc}
*/
public function entityQueryAlter(SelectInterface $query) {
// @todo: How to set access, as vocabulary is now config?
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['target_bundles']['#title'] = $this->t('Available Vocabularies');
// Sorting is not possible for taxonomy terms because we use
// \Drupal\taxonomy\TermStorageInterface::loadTree() to retrieve matches.
$form['sort']['#access'] = FALSE;
return $form;
}
/**
* {@inheritdoc}
*/
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
if ($match || $limit) {
$this->configuration['handler_settings']['sort'] = ['field' => 'name', 'direction' => 'asc'];
return parent::getReferenceableEntities($match, $match_operator, $limit);
}
$options = array();
$bundles = $this->entityManager->getBundleInfo('taxonomy_term');
$handler_settings = $this->configuration['handler_settings'];
$bundle_names = !empty($handler_settings['target_bundles']) ? $handler_settings['target_bundles'] : array_keys($bundles);
foreach ($bundle_names as $bundle) {
if ($vocabulary = Vocabulary::load($bundle)) {
if ($terms = $this->entityManager->getStorage('taxonomy_term')->loadTree($vocabulary->id(), 0, NULL, TRUE)) {
foreach ($terms as $term) {
$options[$vocabulary->id()][$term->id()] = str_repeat('-', $term->depth) . Html::escape($this->entityManager->getTranslationFromContext($term)->label());
}
}
}
}
return $options;
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Drupal\taxonomy\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase;
/**
* Plugin implementation of the 'entity reference taxonomy term RSS' formatter.
*
* @FieldFormatter(
* id = "entity_reference_rss_category",
* label = @Translation("RSS category"),
* description = @Translation("Display reference to taxonomy term in RSS."),
* field_types = {
* "entity_reference"
* }
* )
*/
class EntityReferenceTaxonomyTermRssFormatter extends EntityReferenceFormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$parent_entity = $items->getEntity();
$elements = array();
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) {
$parent_entity->rss_elements[] = array(
'key' => 'category',
'value' => $entity->label(),
'attributes' => array(
'domain' => $entity->id() ? \Drupal::url('entity.taxonomy_term.canonical', ['taxonomy_term' => $entity->id()], array('absolute' => TRUE)) : '',
),
);
}
return $elements;
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
// This formatter is only available for taxonomy terms.
return $field_definition->getFieldStorageDefinition()->getSetting('target_type') == 'taxonomy_term';
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\migrate\Plugin\MigrationDeriverTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Deriver for Drupal 6 term node migrations based on vocabularies.
*/
class D6TermNodeDeriver extends DeriverBase implements ContainerDeriverInterface {
use MigrationDeriverTrait;
/**
* The base plugin ID this derivative is for.
*
* @var string
*/
protected $basePluginId;
/**
* The migration plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $migrationPluginManager;
/**
* D6TermNodeDeriver constructor.
*
* @param string $base_plugin_id
* The base plugin ID this derivative is for.
* @param \Drupal\Component\Plugin\PluginManagerInterface $migration_plugin_manager
* The migration plugin manager.
*/
public function __construct($base_plugin_id, PluginManagerInterface $migration_plugin_manager) {
$this->basePluginId = $base_plugin_id;
$this->migrationPluginManager = $migration_plugin_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('plugin.manager.migration')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition, $base_plugin_definitions = NULL) {
try {
foreach (static::getSourcePlugin('d6_taxonomy_vocabulary') as $row) {
$source_vid = $row->getSourceProperty('vid');
$definition = $base_plugin_definition;
$definition['source']['vid'] = $source_vid;
// migrate_drupal_migration_plugins_alter() adds to this definition.
$this->derivatives[$source_vid] = $definition;
}
}
catch (\Exception $e) {
// It is possible no D6 tables are loaded so just eat exceptions.
}
return $this->derivatives;
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\Migration;
use Drupal\migrate\Plugin\MigrationDeriverTrait;
use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Deriver for Drupal 7 taxonomy term migrations based on vocabularies.
*/
class D7TaxonomyTermDeriver extends DeriverBase implements ContainerDeriverInterface {
use MigrationDeriverTrait;
/**
* The base plugin ID this derivative is for.
*
* @var string
*/
protected $basePluginId;
/**
* Already-instantiated cckfield plugins, keyed by ID.
*
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldInterface[]
*/
protected $cckPluginCache;
/**
* The CCK plugin manager.
*
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
/**
* D7TaxonomyTermDeriver constructor.
*
* @param string $base_plugin_id
* The base plugin ID for the plugin ID.
* @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_manager
* The CCK plugin manager.
*/
public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager) {
$this->basePluginId = $base_plugin_id;
$this->cckPluginManager = $cck_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('plugin.manager.migrate.cckfield')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$fields = [];
try {
$source_plugin = static::getSourcePlugin('d7_field_instance');
$source_plugin->checkRequirements();
// Read all field instance definitions in the source database.
foreach ($source_plugin as $row) {
if ($row->getSourceProperty('entity_type') == 'taxonomy_term') {
$fields[$row->getSourceProperty('bundle')][$row->getSourceProperty('field_name')] = $row->getSource();
}
}
}
catch (RequirementsException $e) {
// If checkRequirements() failed then the field module did not exist and
// we do not have any fields. Therefore, $fields will be empty and below
// we'll create a migration just for the node properties.
}
try {
foreach (static::getSourcePlugin('d7_taxonomy_vocabulary') as $row) {
$bundle = $row->getSourceProperty('machine_name');
$values = $base_plugin_definition;
$values['label'] = t('@label (@type)', [
'@label' => $values['label'],
'@type' => $row->getSourceProperty('name'),
]);
$values['source']['bundle'] = $bundle;
$values['destination']['default_bundle'] = $bundle;
/** @var Migration $migration */
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($values);
if (isset($fields[$bundle])) {
foreach ($fields[$bundle] as $field_name => $info) {
$field_type = $info['type'];
try {
$plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, ['core' => 7], $migration);
if (!isset($this->cckPluginCache[$field_type])) {
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, ['core' => 7], $migration);
}
$this->cckPluginCache[$field_type]
->processCckFieldValues($migration, $field_name, $info);
}
catch (PluginNotFoundException $ex) {
$migration->setProcessOfProperty($field_name, $field_name);
}
}
}
$this->derivatives[$bundle] = $migration->getPluginDefinition();
}
}
catch (DatabaseExceptionWrapper $e) {
// Once we begin iterating the source plugin it is possible that the
// source tables will not exist. This can happen when the
// MigrationPluginManager gathers up the migration definitions but we do
// not actually have a Drupal 7 source database.
}
return $this->derivatives;
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate\cckfield;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
/**
* @MigrateCckField(
* id = "taxonomy_term_reference",
* type_map = {
* "taxonomy_term_reference" = "entity_reference"
* },
* core = {6,7}
* )
*/
class TaxonomyTermReference extends CckFieldPluginBase {
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return array();
}
/**
* {@inheritdoc}
*/
public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
$process = array(
'plugin' => 'iterator',
'source' => $field_name,
'process' => array(
'target_id' => 'tid',
),
);
$migration->setProcessOfProperty($field_name, $process);
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate\source;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Taxonomy term source from database.
*
* @todo Support term_relation, term_synonym table if possible.
*
* @MigrateSource(
* id = "taxonomy_term",
* source_provider = "taxonomy"
* )
*
* @deprecated in Drupal 8.3.0, intended to be removed in Drupal 9.0.0.
* Use \Drupal\taxonomy\Plugin\migrate\source\d6\Term or
* \Drupal\taxonomy\Plugin\migrate\source\d7\Term.
*/
class Term extends DrupalSqlBase {
/**
* Name of the term data table.
*
* @var string
*/
protected $termDataTable;
/**
* Name of the term hierarchy table.
*
* @var string
*/
protected $termHierarchyTable;
/**
* {@inheritdoc}
*/
public function query() {
if ($this->getModuleSchemaVersion('taxonomy') >= 7000) {
$this->termDataTable = 'taxonomy_term_data';
$this->termHierarchyTable = 'taxonomy_term_hierarchy';
}
else {
$this->termDataTable = 'term_data';
$this->termHierarchyTable = 'term_hierarchy';
}
$query = $this->select($this->termDataTable, 'td')
->fields('td')
->distinct()
->orderBy('td.tid');
if (isset($this->configuration['vocabulary'])) {
$query->condition('td.vid', (array) $this->configuration['vocabulary'], 'IN');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = array(
'tid' => $this->t('The term ID.'),
'vid' => $this->t('Existing term VID'),
'name' => $this->t('The name of the term.'),
'description' => $this->t('The term description.'),
'weight' => $this->t('Weight'),
'parent' => $this->t("The Drupal term IDs of the term's parents."),
);
if ($this->getModuleSchemaVersion('taxonomy') >= 7000) {
$fields['format'] = $this->t('Format of the term description.');
}
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// Find parents for this row.
$parents = $this->select($this->termHierarchyTable, 'th')
->fields('th', array('parent', 'tid'))
->condition('tid', $row->getSourceProperty('tid'))
->execute()
->fetchCol();
$row->setSourceProperty('parent', $parents);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['tid']['type'] = 'integer';
return $ids;
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Taxonomy term source from database.
*
* @todo Support term_relation, term_synonym table if possible.
*
* @MigrateSource(
* id = "d6_taxonomy_term",
* source_provider = "taxonomy"
* )
*/
class Term extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('term_data', 'td')
->fields('td')
->distinct()
->orderBy('td.tid');
if (isset($this->configuration['bundle'])) {
$query->condition('td.vid', (array) $this->configuration['bundle'], 'IN');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'tid' => $this->t('The term ID.'),
'vid' => $this->t('Existing term VID'),
'name' => $this->t('The name of the term.'),
'description' => $this->t('The term description.'),
'weight' => $this->t('Weight'),
'parent' => $this->t("The Drupal term IDs of the term's parents."),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// Find parents for this row.
$parents = $this->select('term_hierarchy', 'th')
->fields('th', ['parent', 'tid'])
->condition('tid', $row->getSourceProperty('tid'))
->execute()
->fetchCol();
$row->setSourceProperty('parent', $parents);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['tid']['type'] = 'integer';
return $ids;
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Source returning tids from the term_node table for the current revision.
*
* @MigrateSource(
* id = "d6_term_node",
* source_provider = "taxonomy"
* )
*/
class TermNode extends DrupalSqlBase {
/**
* The join options between the node and the term node table.
*/
const JOIN = 'tn.vid = n.vid';
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('term_node', 'tn')
->distinct()
->fields('tn', array('nid', 'vid'))
->fields('n', array('type'));
// Because this is an inner join it enforces the current revision.
$query->innerJoin('term_data', 'td', 'td.tid = tn.tid AND td.vid = :vid', array(':vid' => $this->configuration['vid']));
$query->innerJoin('node', 'n', static::JOIN);
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'nid' => $this->t('The node revision ID.'),
'vid' => $this->t('The node revision ID.'),
'tid' => $this->t('The term ID.'),
);
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// Select the terms belonging to the revision selected.
$query = $this->select('term_node', 'tn')
->fields('tn', array('tid'))
->condition('n.nid', $row->getSourceProperty('nid'));
$query->join('node', 'n', static::JOIN);
$query->innerJoin('term_data', 'td', 'td.tid = tn.tid AND td.vid = :vid', array(':vid' => $this->configuration['vid']));
$row->setSourceProperty('tid', $query->execute()->fetchCol());
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['vid']['type'] = 'integer';
$ids['vid']['alias'] = 'tn';
return $ids;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
/**
* Source returning tids from the term_node table for the non-current revision.
*
* @MigrateSource(
* id = "d6_term_node_revision"
* )
*/
class TermNodeRevision extends TermNode {
/**
* {@inheritdoc}
*/
const JOIN = 'tn.nid = n.nid AND tn.vid != n.vid';
}

View file

@ -0,0 +1,83 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Drupal 6 vocabularies source from database.
*
* @MigrateSource(
* id = "d6_taxonomy_vocabulary",
* source_provider = "taxonomy"
* )
*/
class Vocabulary extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('vocabulary', 'v')
->fields('v', array(
'vid',
'name',
'description',
'help',
'relations',
'hierarchy',
'multiple',
'required',
'tags',
'module',
'weight',
));
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'vid' => $this->t('The vocabulary ID.'),
'name' => $this->t('The name of the vocabulary.'),
'description' => $this->t('The description of the vocabulary.'),
'help' => $this->t('Help text to display for the vocabulary.'),
'relations' => $this->t('Whether or not related terms are enabled within the vocabulary. (0 = disabled, 1 = enabled)'),
'hierarchy' => $this->t('The type of hierarchy allowed within the vocabulary. (0 = disabled, 1 = single, 2 = multiple)'),
'multiple' => $this->t('Whether or not multiple terms from this vocabulary may be assigned to a node. (0 = disabled, 1 = enabled)'),
'required' => $this->t('Whether or not terms are required for nodes using this vocabulary. (0 = disabled, 1 = enabled)'),
'tags' => $this->t('Whether or not free tagging is enabled for the vocabulary. (0 = disabled, 1 = enabled)'),
'weight' => $this->t('The weight of the vocabulary in relation to other vocabularies.'),
'parents' => $this->t("The Drupal term IDs of the term's parents."),
'node_types' => $this->t('The names of the node types the vocabulary may be used with.'),
);
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// Find node types for this row.
$node_types = $this->select('vocabulary_node_types', 'nt')
->fields('nt', array('type', 'vid'))
->condition('vid', $row->getSourceProperty('vid'))
->execute()
->fetchCol();
$row->setSourceProperty('node_types', $node_types);
$row->setSourceProperty('cardinality', ($row->getSourceProperty('tags') == 1 || $row->getSourceProperty('multiple') == 1) ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : 1);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['vid']['type'] = 'integer';
return $ids;
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
/**
* Gets all the vocabularies based on the node types that have Taxonomy enabled.
*
* @MigrateSource(
* id = "d6_taxonomy_vocabulary_per_type",
* source_provider = "taxonomy"
* )
*/
class VocabularyPerType extends Vocabulary {
/**
* {@inheritdoc}
*/
public function query() {
$query = parent::query();
$query->join('vocabulary_node_types', 'nt', 'v.vid = nt.vid');
$query->fields('nt', array('type'));
return $query;
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['vid']['type'] = 'integer';
$ids['vid']['alias'] = 'nt';
$ids['type']['type'] = 'string';
return $ids;
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate\source\d7;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
/**
* Taxonomy term source from database.
*
* @todo Support term_relation, term_synonym table if possible.
*
* @MigrateSource(
* id = "d7_taxonomy_term",
* source_provider = "taxonomy"
* )
*/
class Term extends FieldableEntity {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('taxonomy_term_data', 'td')
->fields('td')
->distinct()
->orderBy('tid');
$query->leftJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
$query->addField('tv', 'machine_name');
if (isset($this->configuration['bundle'])) {
$query->condition('tv.machine_name', (array) $this->configuration['bundle'], 'IN');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'tid' => $this->t('The term ID.'),
'vid' => $this->t('Existing term VID'),
'machine_name' => $this->t('Vocabulary machine name'),
'name' => $this->t('The name of the term.'),
'description' => $this->t('The term description.'),
'weight' => $this->t('Weight'),
'parent' => $this->t("The Drupal term IDs of the term's parents."),
'format' => $this->t("Format of the term description."),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// Get Field API field values.
foreach (array_keys($this->getFields('taxonomy_term', $row->getSourceProperty('machine_name'))) as $field) {
$tid = $row->getSourceProperty('tid');
$row->setSourceProperty($field, $this->getFieldValues('taxonomy_term', $field, $tid));
}
// Find parents for this row.
$parents = $this->select('taxonomy_term_hierarchy', 'th')
->fields('th', ['parent', 'tid'])
->condition('tid', $row->getSourceProperty('tid'))
->execute()
->fetchCol();
$row->setSourceProperty('parent', $parents);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['tid']['type'] = 'integer';
return $ids;
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Drupal\taxonomy\Plugin\migrate\source\d7;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 7 vocabularies source from database.
*
* @MigrateSource(
* id = "d7_taxonomy_vocabulary",
* source_provider = "taxonomy"
* )
*/
class Vocabulary extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('taxonomy_vocabulary', 'v')
->fields('v', array(
'vid',
'name',
'description',
'hierarchy',
'module',
'weight',
'machine_name',
));
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'vid' => $this->t('The vocabulary ID.'),
'name' => $this->t('The name of the vocabulary.'),
'description' => $this->t('The description of the vocabulary.'),
'hierarchy' => $this->t('The type of hierarchy allowed within the vocabulary. (0 = disabled, 1 = single, 2 = multiple)'),
'module' => $this->t('Module responsible for the vocabulary.'),
'weight' => $this->t('The weight of the vocabulary in relation to other vocabularies.'),
'machine_name' => $this->t('Unique machine name of the vocabulary.')
);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['vid']['type'] = 'integer';
return $ids;
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Drupal\taxonomy\Plugin\views\argument;
use Drupal\taxonomy\Entity\Term;
use Drupal\views\Plugin\views\argument\ManyToOne;
/**
* Allow taxonomy term ID(s) as argument.
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("taxonomy_index_tid")
*/
class IndexTid extends ManyToOne {
public function titleQuery() {
$titles = array();
$terms = Term::loadMultiple($this->value);
foreach ($terms as $term) {
$titles[] = \Drupal::entityManager()->getTranslationFromContext($term)->label();
}
return $titles;
}
}

View file

@ -0,0 +1,142 @@
<?php
namespace Drupal\taxonomy\Plugin\views\argument;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Argument handler for taxonomy terms with depth.
*
* This handler is actually part of the node table and has some restrictions,
* because it uses a subquery to find nodes with.
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("taxonomy_index_tid_depth")
*/
class IndexTidDepth extends ArgumentPluginBase implements ContainerFactoryPluginInterface {
/**
* @var EntityStorageInterface
*/
protected $termStorage;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $termStorage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->termStorage = $termStorage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity.manager')->getStorage('taxonomy_term'));
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['depth'] = array('default' => 0);
$options['break_phrase'] = array('default' => FALSE);
$options['use_taxonomy_term_path'] = array('default' => FALSE);
return $options;
}
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['depth'] = array(
'#type' => 'weight',
'#title' => $this->t('Depth'),
'#default_value' => $this->options['depth'],
'#description' => $this->t('The depth will match nodes tagged with terms in the hierarchy. For example, if you have the term "fruit" and a child term "apple", with a depth of 1 (or higher) then filtering for the term "fruit" will get nodes that are tagged with "apple" as well as "fruit". If negative, the reverse is true; searching for "apple" will also pick up nodes tagged with "fruit" if depth is -1 (or lower).'),
);
$form['break_phrase'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Allow multiple values'),
'#description' => $this->t('If selected, users can enter multiple values in the form of 1+2+3. Due to the number of JOINs it would require, AND will be treated as OR with this filter.'),
'#default_value' => !empty($this->options['break_phrase']),
);
parent::buildOptionsForm($form, $form_state);
}
/**
* Override defaultActions() to remove summary actions.
*/
protected function defaultActions($which = NULL) {
if ($which) {
if (in_array($which, array('ignore', 'not found', 'empty', 'default'))) {
return parent::defaultActions($which);
}
return;
}
$actions = parent::defaultActions();
unset($actions['summary asc']);
unset($actions['summary desc']);
unset($actions['summary asc by count']);
unset($actions['summary desc by count']);
return $actions;
}
public function query($group_by = FALSE) {
$this->ensureMyTable();
if (!empty($this->options['break_phrase'])) {
$break = static::breakString($this->argument);
if ($break->value === array(-1)) {
return FALSE;
}
$operator = (count($break->value) > 1) ? 'IN' : '=';
$tids = $break->value;
}
else {
$operator = "=";
$tids = $this->argument;
}
// Now build the subqueries.
$subquery = db_select('taxonomy_index', 'tn');
$subquery->addField('tn', 'nid');
$where = db_or()->condition('tn.tid', $tids, $operator);
$last = "tn";
if ($this->options['depth'] > 0) {
$subquery->leftJoin('taxonomy_term_hierarchy', 'th', "th.tid = tn.tid");
$last = "th";
foreach (range(1, abs($this->options['depth'])) as $count) {
$subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.parent = th$count.tid");
$where->condition("th$count.tid", $tids, $operator);
$last = "th$count";
}
}
elseif ($this->options['depth'] < 0) {
foreach (range(1, abs($this->options['depth'])) as $count) {
$subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.tid = th$count.parent");
$where->condition("th$count.tid", $tids, $operator);
$last = "th$count";
}
}
$subquery->condition($where);
$this->query->addWhere(0, "$this->tableAlias.$this->realField", $subquery, 'IN');
}
function title() {
$term = $this->termStorage->load($this->argument);
if (!empty($term)) {
return $term->getName();
}
// TODO review text
return $this->t('No name');
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Drupal\taxonomy\Plugin\views\argument;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
/**
* Argument handler for to modify depth for a previous term.
*
* This handler is actually part of the node table and has some restrictions,
* because it uses a subquery to find nodes with.
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("taxonomy_index_tid_depth_modifier")
*/
class IndexTidDepthModifier extends ArgumentPluginBase {
public function buildOptionsForm(&$form, FormStateInterface $form_state) { }
public function query($group_by = FALSE) { }
public function preQuery() {
// We don't know our argument yet, but it's based upon our position:
$argument = isset($this->view->args[$this->position]) ? $this->view->args[$this->position] : NULL;
if (!is_numeric($argument)) {
return;
}
if ($argument > 10) {
$argument = 10;
}
if ($argument < -10) {
$argument = -10;
}
// figure out which argument preceded us.
$keys = array_reverse(array_keys($this->view->argument));
$skip = TRUE;
foreach ($keys as $key) {
if ($key == $this->options['id']) {
$skip = FALSE;
continue;
}
if ($skip) {
continue;
}
if (empty($this->view->argument[$key])) {
continue;
}
if (isset($handler)) {
unset($handler);
}
$handler = &$this->view->argument[$key];
if (empty($handler->definition['accept depth modifier'])) {
continue;
}
// Finally!
$handler->options['depth'] = $argument;
}
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\taxonomy\Plugin\views\argument;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\views\Plugin\views\argument\NumericArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Argument handler for basic taxonomy tid.
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("taxonomy")
*/
class Taxonomy extends NumericArgument implements ContainerFactoryPluginInterface {
/**
* @var EntityStorageInterface
*/
protected $termStorage;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $term_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->termStorage = $term_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getStorage('taxonomy_term')
);
}
/**
* Override the behavior of title(). Get the title of the node.
*/
function title() {
// There might be no valid argument.
if ($this->argument) {
$term = $this->termStorage->load($this->argument);
if (!empty($term)) {
return $term->getName();
}
}
// TODO review text
return $this->t('No name');
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Drupal\taxonomy\Plugin\views\argument;
use Drupal\views\Plugin\views\argument\NumericArgument;
use Drupal\taxonomy\VocabularyStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Argument handler to accept a vocabulary id.
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("vocabulary_vid")
*/
class VocabularyVid extends NumericArgument {
/**
* The vocabulary storage.
*
* @var \Drupal\taxonomy\VocabularyStorageInterface
*/
protected $vocabularyStorage;
/**
* Constructs the VocabularyVid object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param VocabularyStorageInterface $vocabulary_storage
* The vocabulary storage.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, VocabularyStorageInterface $vocabulary_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->vocabularyStorage = $vocabulary_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getStorage('taxonomy_vocabulary')
);
}
/**
* Override the behavior of title(). Get the name of the vocabulary.
*/
function title() {
$vocabulary = $this->vocabularyStorage->load($this->argument);
if ($vocabulary) {
return $vocabulary->label();
}
return $this->t('No vocabulary');
}
}

View file

@ -0,0 +1,245 @@
<?php
namespace Drupal\taxonomy\Plugin\views\argument_default;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\taxonomy\TermInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\taxonomy\VocabularyStorageInterface;
/**
* Taxonomy tid default argument.
*
* @ViewsArgumentDefault(
* id = "taxonomy_tid",
* title = @Translation("Taxonomy term ID from URL")
* )
*/
class Tid extends ArgumentDefaultPluginBase implements CacheableDependencyInterface {
/**
* The route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* The vocabulary storage.
*
* @var \Drupal\taxonomy\VocabularyStorageInterface.
*/
protected $vocabularyStorage;
/**
* Constructs a new Tid instance.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition. *
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
* The vocabulary storage.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match, VocabularyStorageInterface $vocabulary_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->routeMatch = $route_match;
$this->vocabularyStorage = $vocabulary_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('current_route_match'),
$container->get('entity.manager')->getStorage('taxonomy_vocabulary')
);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
// @todo Remove the legacy code.
// Convert legacy vids option to machine name vocabularies.
if (!empty($this->options['vids'])) {
$vocabularies = taxonomy_vocabulary_get_names();
foreach ($this->options['vids'] as $vid) {
if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) {
$this->options['vocabularies'][$vocabularies[$vid]->machine_name] = $vocabularies[$vid]->machine_name;
}
}
}
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['term_page'] = array('default' => TRUE);
$options['node'] = array('default' => FALSE);
$options['anyall'] = array('default' => ',');
$options['limit'] = array('default' => FALSE);
$options['vids'] = array('default' => array());
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['term_page'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Load default filter from term page'),
'#default_value' => $this->options['term_page'],
);
$form['node'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Load default filter from node page, that\'s good for related taxonomy blocks'),
'#default_value' => $this->options['node'],
);
$form['limit'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Limit terms by vocabulary'),
'#default_value' => $this->options['limit'],
'#states' => array(
'visible' => array(
':input[name="options[argument_default][taxonomy_tid][node]"]' => array('checked' => TRUE),
),
),
);
$options = array();
$vocabularies = $this->vocabularyStorage->loadMultiple();
foreach ($vocabularies as $voc) {
$options[$voc->id()] = $voc->label();
}
$form['vids'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('Vocabularies'),
'#options' => $options,
'#default_value' => $this->options['vids'],
'#states' => array(
'visible' => array(
':input[name="options[argument_default][taxonomy_tid][limit]"]' => array('checked' => TRUE),
':input[name="options[argument_default][taxonomy_tid][node]"]' => array('checked' => TRUE),
),
),
);
$form['anyall'] = array(
'#type' => 'radios',
'#title' => $this->t('Multiple-value handling'),
'#default_value' => $this->options['anyall'],
'#options' => array(
',' => $this->t('Filter to items that share all terms'),
'+' => $this->t('Filter to items that share any term'),
),
'#states' => array(
'visible' => array(
':input[name="options[argument_default][taxonomy_tid][node]"]' => array('checked' => TRUE),
),
),
);
}
/**
* {@inheritdoc}
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state, &$options = array()) {
// Filter unselected items so we don't unnecessarily store giant arrays.
$options['vids'] = array_filter($options['vids']);
}
/**
* {@inheritdoc}
*/
public function getArgument() {
// Load default argument from taxonomy page.
if (!empty($this->options['term_page'])) {
if (($taxonomy_term = $this->routeMatch->getParameter('taxonomy_term')) && $taxonomy_term instanceof TermInterface) {
return $taxonomy_term->id();
}
}
// Load default argument from node.
if (!empty($this->options['node'])) {
// Just check, if a node could be detected.
if (($node = $this->routeMatch->getParameter('node')) && $node instanceof NodeInterface) {
$taxonomy = array();
foreach ($node->getFieldDefinitions() as $field) {
if ($field->getType() == 'entity_reference' && $field->getSetting('target_type') == 'taxonomy_term') {
$taxonomy_terms = $node->{$field->getName()}->referencedEntities();
/** @var \Drupal\taxonomy\TermInterface $taxonomy_term */
foreach ($taxonomy_terms as $taxonomy_term) {
$taxonomy[$taxonomy_term->id()] = $taxonomy_term->getVocabularyId();
}
}
}
if (!empty($this->options['limit'])) {
$tids = array();
// filter by vocabulary
foreach ($taxonomy as $tid => $vocab) {
if (!empty($this->options['vids'][$vocab])) {
$tids[] = $tid;
}
}
return implode($this->options['anyall'], $tids);
}
// Return all tids.
else {
return implode($this->options['anyall'], array_keys($taxonomy));
}
}
}
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return Cache::PERMANENT;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return ['url'];
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
foreach ($this->vocabularyStorage->loadMultiple(array_keys($this->options['vids'])) as $vocabulary) {
$dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
}
return $dependencies;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Drupal\taxonomy\Plugin\views\argument_validator;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\argument_validator\Entity;
/**
* Adds legacy vocabulary handling to standard Entity Argument validation..
*/
class Term extends Entity {
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
// @todo Remove the legacy code.
// Convert legacy vids option to machine name vocabularies.
if (!empty($this->options['vids'])) {
$vocabularies = taxonomy_vocabulary_get_names();
foreach ($this->options['vids'] as $vid) {
if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) {
$this->options['vocabularies'][$vocabularies[$vid]->machine_name] = $vocabularies[$vid]->machine_name;
}
}
}
}
}

View file

@ -0,0 +1,85 @@
<?php
namespace Drupal\taxonomy\Plugin\views\argument_validator;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\views\Plugin\views\argument_validator\Entity;
/**
* Validates whether a term name is a valid term argument.
*
* @ViewsArgumentValidator(
* id = "taxonomy_term_name",
* title = @Translation("Taxonomy term name"),
* entity_type = "taxonomy_term"
* )
*/
class TermName extends Entity {
/**
* The taxonomy term storage.
*
* @var \Drupal\taxonomy\TermStorageInterface
*/
protected $termStorage;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager);
// Not handling exploding term names.
$this->multipleCapable = FALSE;
$this->termStorage = $entity_manager->getStorage('taxonomy_term');
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['transform'] = array('default' => FALSE);
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['transform'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Transform dashes in URL to spaces in term name filter values'),
'#default_value' => $this->options['transform'],
);
}
/**
* {@inheritdoc}
*/
public function validateArgument($argument) {
if ($this->options['transform']) {
$argument = str_replace('-', ' ', $argument);
}
$terms = $this->termStorage->loadByProperties(array('name' => $argument));
if (!$terms) {
// Returned empty array no terms with the name.
return FALSE;
}
// Not knowing which term will be used if more than one is returned check
// each one.
foreach ($terms as $term) {
if (!$this->validateEntity($term)) {
return FALSE;
}
}
return TRUE;
}
}

View file

@ -0,0 +1,177 @@
<?php
namespace Drupal\taxonomy\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\field\PrerenderList;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\taxonomy\VocabularyStorageInterface;
/**
* Field handler to display all taxonomy terms of a node.
*
* @ingroup views_field_handlers
*
* @ViewsField("taxonomy_index_tid")
*/
class TaxonomyIndexTid extends PrerenderList {
/**
* The vocabulary storage.
*
* @var \Drupal\taxonomy\VocabularyStorageInterface.
*/
protected $vocabularyStorage;
/**
* Constructs a TaxonomyIndexTid object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
* The vocabulary storage.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, VocabularyStorageInterface $vocabulary_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->vocabularyStorage = $vocabulary_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getStorage('taxonomy_vocabulary')
);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
// @todo: Wouldn't it be possible to use $this->base_table and no if here?
if ($view->storage->get('base_table') == 'node_field_revision') {
$this->additional_fields['nid'] = array('table' => 'node_field_revision', 'field' => 'nid');
}
else {
$this->additional_fields['nid'] = array('table' => 'node_field_data', 'field' => 'nid');
}
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['link_to_taxonomy'] = array('default' => TRUE);
$options['limit'] = array('default' => FALSE);
$options['vids'] = array('default' => array());
return $options;
}
/**
* Provide "link to term" option.
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['link_to_taxonomy'] = array(
'#title' => $this->t('Link this field to its term page'),
'#type' => 'checkbox',
'#default_value' => !empty($this->options['link_to_taxonomy']),
);
$form['limit'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Limit terms by vocabulary'),
'#default_value' => $this->options['limit'],
);
$options = array();
$vocabularies = $this->vocabularyStorage->loadMultiple();
foreach ($vocabularies as $voc) {
$options[$voc->id()] = $voc->label();
}
$form['vids'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('Vocabularies'),
'#options' => $options,
'#default_value' => $this->options['vids'],
'#states' => array(
'visible' => array(
':input[name="options[limit]"]' => array('checked' => TRUE),
),
),
);
parent::buildOptionsForm($form, $form_state);
}
/**
* Add this term to the query
*/
public function query() {
$this->addAdditionalFields();
}
public function preRender(&$values) {
$vocabularies = $this->vocabularyStorage->loadMultiple();
$this->field_alias = $this->aliases['nid'];
$nids = array();
foreach ($values as $result) {
if (!empty($result->{$this->aliases['nid']})) {
$nids[] = $result->{$this->aliases['nid']};
}
}
if ($nids) {
$vocabs = array_filter($this->options['vids']);
if (empty($this->options['limit'])) {
$vocabs = array();
}
$result = \Drupal::entityManager()->getStorage('taxonomy_term')->getNodeTerms($nids, $vocabs);
foreach ($result as $node_nid => $data) {
foreach ($data as $tid => $term) {
$this->items[$node_nid][$tid]['name'] = \Drupal::entityManager()->getTranslationFromContext($term)->label();
$this->items[$node_nid][$tid]['tid'] = $tid;
$this->items[$node_nid][$tid]['vocabulary_vid'] = $term->getVocabularyId();
$this->items[$node_nid][$tid]['vocabulary'] = $vocabularies[$term->getVocabularyId()]->label();
if (!empty($this->options['link_to_taxonomy'])) {
$this->items[$node_nid][$tid]['make_link'] = TRUE;
$this->items[$node_nid][$tid]['path'] = 'taxonomy/term/' . $tid;
}
}
}
}
}
function render_item($count, $item) {
return $item['name'];
}
protected function documentSelfTokens(&$tokens) {
$tokens['{{ ' . $this->options['id'] . '__tid' . ' }}'] = $this->t('The taxonomy term ID for the term.');
$tokens['{{ ' . $this->options['id'] . '__name' . ' }}'] = $this->t('The taxonomy term name for the term.');
$tokens['{{ ' . $this->options['id'] . '__vocabulary_vid' . ' }}'] = $this->t('The machine name for the vocabulary the term belongs to.');
$tokens['{{ ' . $this->options['id'] . '__vocabulary' . ' }}'] = $this->t('The name for the vocabulary the term belongs to.');
}
protected function addSelfTokens(&$tokens, $item) {
foreach (array('tid', 'name', 'vocabulary_vid', 'vocabulary') as $token) {
$tokens['{{ ' . $this->options['id'] . '__' . $token . ' }}'] = isset($item[$token]) ? $item[$token] : '';
}
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Drupal\taxonomy\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\field\Field;
use Drupal\views\ResultRow;
/**
* Displays taxonomy term names and allows converting spaces to hyphens.
*
* @ingroup views_field_handlers
*
* @ViewsField("term_name")
*/
class TermName extends Field {
/**
* {@inheritdoc}
*/
public function getItems(ResultRow $values) {
$items = parent::getItems($values);
if ($this->options['convert_spaces']) {
foreach ($items as &$item) {
// Replace spaces with hyphens.
$name = $item['raw']->get('value')->getValue();
// @todo Add link support https://www.drupal.org/node/2567745
$item['rendered']['#context']['value'] = str_replace(' ', '-', $name);
}
}
return $items;
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['convert_spaces'] = array('default' => FALSE);
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['convert_spaces'] = array(
'#title' => $this->t('Convert spaces in term names to hyphens'),
'#type' => 'checkbox',
'#default_value' => !empty($this->options['convert_spaces']),
);
parent::buildOptionsForm($form, $form_state);
}
}

View file

@ -0,0 +1,401 @@
<?php
namespace Drupal\taxonomy\Plugin\views\filter;
use Drupal\Core\Entity\Element\EntityAutocomplete;
use Drupal\Core\Form\FormStateInterface;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\TermStorageInterface;
use Drupal\taxonomy\VocabularyStorageInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\filter\ManyToOne;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Filter by term id.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("taxonomy_index_tid")
*/
class TaxonomyIndexTid extends ManyToOne {
// Stores the exposed input for this filter.
public $validated_exposed_input = NULL;
/**
* The vocabulary storage.
*
* @var \Drupal\taxonomy\VocabularyStorageInterface
*/
protected $vocabularyStorage;
/**
* The term storage.
*
* @var \Drupal\taxonomy\TermStorageInterface
*/
protected $termStorage;
/**
* Constructs a TaxonomyIndexTid object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
* The vocabulary storage.
* @param \Drupal\taxonomy\TermStorageInterface $term_storage
* The term storage.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, VocabularyStorageInterface $vocabulary_storage, TermStorageInterface $term_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->vocabularyStorage = $vocabulary_storage;
$this->termStorage = $term_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getStorage('taxonomy_vocabulary'),
$container->get('entity.manager')->getStorage('taxonomy_term')
);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
if (!empty($this->definition['vocabulary'])) {
$this->options['vid'] = $this->definition['vocabulary'];
}
}
public function hasExtraOptions() { return TRUE; }
/**
* {@inheritdoc}
*/
public function getValueOptions() {
return $this->valueOptions;
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['type'] = array('default' => 'textfield');
$options['limit'] = array('default' => TRUE);
$options['vid'] = array('default' => '');
$options['hierarchy'] = array('default' => FALSE);
$options['error_message'] = array('default' => TRUE);
return $options;
}
public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) {
$vocabularies = $this->vocabularyStorage->loadMultiple();
$options = array();
foreach ($vocabularies as $voc) {
$options[$voc->id()] = $voc->label();
}
if ($this->options['limit']) {
// We only do this when the form is displayed.
if (empty($this->options['vid'])) {
$first_vocabulary = reset($vocabularies);
$this->options['vid'] = $first_vocabulary->id();
}
if (empty($this->definition['vocabulary'])) {
$form['vid'] = array(
'#type' => 'radios',
'#title' => $this->t('Vocabulary'),
'#options' => $options,
'#description' => $this->t('Select which vocabulary to show terms for in the regular options.'),
'#default_value' => $this->options['vid'],
);
}
}
$form['type'] = array(
'#type' => 'radios',
'#title' => $this->t('Selection type'),
'#options' => array('select' => $this->t('Dropdown'), 'textfield' => $this->t('Autocomplete')),
'#default_value' => $this->options['type'],
);
$form['hierarchy'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Show hierarchy in dropdown'),
'#default_value' => !empty($this->options['hierarchy']),
'#states' => array(
'visible' => array(
':input[name="options[type]"]' => array('value' => 'select'),
),
),
);
}
protected function valueForm(&$form, FormStateInterface $form_state) {
$vocabulary = $this->vocabularyStorage->load($this->options['vid']);
if (empty($vocabulary) && $this->options['limit']) {
$form['markup'] = array(
'#markup' => '<div class="js-form-item form-item">' . $this->t('An invalid vocabulary is selected. Please change it in the options.') . '</div>',
);
return;
}
if ($this->options['type'] == 'textfield') {
$terms = $this->value ? Term::loadMultiple(($this->value)) : array();
$form['value'] = array(
'#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->label())) : $this->t('Select terms'),
'#type' => 'textfield',
'#default_value' => EntityAutocomplete::getEntityLabels($terms),
);
if ($this->options['limit']) {
$form['value']['#type'] = 'entity_autocomplete';
$form['value']['#target_type'] = 'taxonomy_term';
$form['value']['#selection_settings']['target_bundles'] = array($vocabulary->id());
$form['value']['#tags'] = TRUE;
$form['value']['#process_default_value'] = FALSE;
}
}
else {
if (!empty($this->options['hierarchy']) && $this->options['limit']) {
$tree = $this->termStorage->loadTree($vocabulary->id(), 0, NULL, TRUE);
$options = array();
if ($tree) {
foreach ($tree as $term) {
$choice = new \stdClass();
$choice->option = array($term->id() => str_repeat('-', $term->depth) . \Drupal::entityManager()->getTranslationFromContext($term)->label());
$options[] = $choice;
}
}
}
else {
$options = array();
$query = \Drupal::entityQuery('taxonomy_term')
// @todo Sorting on vocabulary properties -
// https://www.drupal.org/node/1821274.
->sort('weight')
->sort('name')
->addTag('taxonomy_term_access');
if ($this->options['limit']) {
$query->condition('vid', $vocabulary->id());
}
$terms = Term::loadMultiple($query->execute());
foreach ($terms as $term) {
$options[$term->id()] = \Drupal::entityManager()->getTranslationFromContext($term)->label();
}
}
$default_value = (array) $this->value;
if ($exposed = $form_state->get('exposed')) {
$identifier = $this->options['expose']['identifier'];
if (!empty($this->options['expose']['reduce'])) {
$options = $this->reduceValueOptions($options);
if (!empty($this->options['expose']['multiple']) && empty($this->options['expose']['required'])) {
$default_value = array();
}
}
if (empty($this->options['expose']['multiple'])) {
if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce']))) {
$default_value = 'All';
}
elseif (empty($default_value)) {
$keys = array_keys($options);
$default_value = array_shift($keys);
}
// Due to #1464174 there is a chance that array('') was saved in the admin ui.
// Let's choose a safe default value.
elseif ($default_value == array('')) {
$default_value = 'All';
}
else {
$copy = $default_value;
$default_value = array_shift($copy);
}
}
}
$form['value'] = array(
'#type' => 'select',
'#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->label())) : $this->t('Select terms'),
'#multiple' => TRUE,
'#options' => $options,
'#size' => min(9, count($options)),
'#default_value' => $default_value,
);
$user_input = $form_state->getUserInput();
if ($exposed && isset($identifier) && !isset($user_input[$identifier])) {
$user_input[$identifier] = $default_value;
$form_state->setUserInput($user_input);
}
}
if (!$form_state->get('exposed')) {
// Retain the helper option
$this->helper->buildOptionsForm($form, $form_state);
// Show help text if not exposed to end users.
$form['value']['#description'] = t('Leave blank for all. Otherwise, the first selected term will be the default instead of "Any".');
}
}
protected function valueValidate($form, FormStateInterface $form_state) {
// We only validate if they've chosen the text field style.
if ($this->options['type'] != 'textfield') {
return;
}
$tids = array();
if ($values = $form_state->getValue(array('options', 'value'))) {
foreach ($values as $value) {
$tids[] = $value['target_id'];
}
}
$form_state->setValue(array('options', 'value'), $tids);
}
public function acceptExposedInput($input) {
if (empty($this->options['exposed'])) {
return TRUE;
}
// We need to know the operator, which is normally set in
// \Drupal\views\Plugin\views\filter\FilterPluginBase::acceptExposedInput(),
// before we actually call the parent version of ourselves.
if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) {
$this->operator = $input[$this->options['expose']['operator_id']];
}
// If view is an attachment and is inheriting exposed filters, then assume
// exposed input has already been validated
if (!empty($this->view->is_attachment) && $this->view->display_handler->usesExposed()) {
$this->validated_exposed_input = (array) $this->view->exposed_raw_input[$this->options['expose']['identifier']];
}
// If we're checking for EMPTY or NOT, we don't need any input, and we can
// say that our input conditions are met by just having the right operator.
if ($this->operator == 'empty' || $this->operator == 'not empty') {
return TRUE;
}
// If it's non-required and there's no value don't bother filtering.
if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) {
return FALSE;
}
$rc = parent::acceptExposedInput($input);
if ($rc) {
// If we have previously validated input, override.
if (isset($this->validated_exposed_input)) {
$this->value = $this->validated_exposed_input;
}
}
return $rc;
}
public function validateExposed(&$form, FormStateInterface $form_state) {
if (empty($this->options['exposed'])) {
return;
}
$identifier = $this->options['expose']['identifier'];
// We only validate if they've chosen the text field style.
if ($this->options['type'] != 'textfield') {
if ($form_state->getValue($identifier) != 'All') {
$this->validated_exposed_input = (array) $form_state->getValue($identifier);
}
return;
}
if (empty($this->options['expose']['identifier'])) {
return;
}
if ($values = $form_state->getValue($identifier)) {
foreach ($values as $value) {
$this->validated_exposed_input[] = $value['target_id'];
}
}
}
protected function valueSubmit($form, FormStateInterface $form_state) {
// prevent array_filter from messing up our arrays in parent submit.
}
public function buildExposeForm(&$form, FormStateInterface $form_state) {
parent::buildExposeForm($form, $form_state);
if ($this->options['type'] != 'select') {
unset($form['expose']['reduce']);
}
$form['error_message'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Display error message'),
'#default_value' => !empty($this->options['error_message']),
);
}
public function adminSummary() {
// set up $this->valueOptions for the parent summary
$this->valueOptions = array();
if ($this->value) {
$this->value = array_filter($this->value);
$terms = Term::loadMultiple($this->value);
foreach ($terms as $term) {
$this->valueOptions[$term->id()] = \Drupal::entityManager()->getTranslationFromContext($term)->label();
}
}
return parent::adminSummary();
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
$contexts = parent::getCacheContexts();
// The result potentially depends on term access and so is just cacheable
// per user.
// @todo See https://www.drupal.org/node/2352175.
$contexts[] = 'user';
return $contexts;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
$vocabulary = $this->vocabularyStorage->load($this->options['vid']);
$dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
foreach ($this->termStorage->loadMultiple($this->options['value']) as $term) {
$dependencies[$term->getConfigDependencyKey()][] = $term->getConfigDependencyName();
}
return $dependencies;
}
}

View file

@ -0,0 +1,99 @@
<?php
namespace Drupal\taxonomy\Plugin\views\filter;
use Drupal\Core\Form\FormStateInterface;
/**
* Filter handler for taxonomy terms with depth.
*
* This handler is actually part of the node table and has some restrictions,
* because it uses a subquery to find nodes with.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("taxonomy_index_tid_depth")
*/
class TaxonomyIndexTidDepth extends TaxonomyIndexTid {
public function operatorOptions($which = 'title') {
return array(
'or' => $this->t('Is one of'),
);
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['depth'] = array('default' => 0);
return $options;
}
public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildExtraOptionsForm($form, $form_state);
$form['depth'] = array(
'#type' => 'weight',
'#title' => $this->t('Depth'),
'#default_value' => $this->options['depth'],
'#description' => $this->t('The depth will match nodes tagged with terms in the hierarchy. For example, if you have the term "fruit" and a child term "apple", with a depth of 1 (or higher) then filtering for the term "fruit" will get nodes that are tagged with "apple" as well as "fruit". If negative, the reverse is true; searching for "apple" will also pick up nodes tagged with "fruit" if depth is -1 (or lower).'),
);
}
public function query() {
// If no filter values are present, then do nothing.
if (count($this->value) == 0) {
return;
}
elseif (count($this->value) == 1) {
// Sometimes $this->value is an array with a single element so convert it.
if (is_array($this->value)) {
$this->value = current($this->value);
}
$operator = '=';
}
else {
$operator = 'IN';# " IN (" . implode(', ', array_fill(0, sizeof($this->value), '%d')) . ")";
}
// The normal use of ensureMyTable() here breaks Views.
// So instead we trick the filter into using the alias of the base table.
// See https://www.drupal.org/node/271833.
// If a relationship is set, we must use the alias it provides.
if (!empty($this->relationship)) {
$this->tableAlias = $this->relationship;
}
// If no relationship, then use the alias of the base table.
else {
$this->tableAlias = $this->query->ensureTable($this->view->storage->get('base_table'));
}
// Now build the subqueries.
$subquery = db_select('taxonomy_index', 'tn');
$subquery->addField('tn', 'nid');
$where = db_or()->condition('tn.tid', $this->value, $operator);
$last = "tn";
if ($this->options['depth'] > 0) {
$subquery->leftJoin('taxonomy_term_hierarchy', 'th', "th.tid = tn.tid");
$last = "th";
foreach (range(1, abs($this->options['depth'])) as $count) {
$subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.parent = th$count.tid");
$where->condition("th$count.tid", $this->value, $operator);
$last = "th$count";
}
}
elseif ($this->options['depth'] < 0) {
foreach (range(1, abs($this->options['depth'])) as $count) {
$subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.tid = th$count.parent");
$where->condition("th$count.tid", $this->value, $operator);
$last = "th$count";
}
}
$subquery->condition($where);
$this->query->addWhere($this->options['group'], "$this->tableAlias.$this->realField", $subquery, 'IN');
}
}

View file

@ -0,0 +1,164 @@
<?php
namespace Drupal\taxonomy\Plugin\views\relationship;
use Drupal\Core\Form\FormStateInterface;
use Drupal\taxonomy\VocabularyStorageInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\relationship\RelationshipPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Relationship handler to return the taxonomy terms of nodes.
*
* @ingroup views_relationship_handlers
*
* @ViewsRelationship("node_term_data")
*/
class NodeTermData extends RelationshipPluginBase {
/**
* The vocabulary storage.
*
* @var \Drupal\taxonomy\VocabularyStorageInterface
*/
protected $vocabularyStorage;
/**
* Constructs a NodeTermData object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
* The vocabulary storage.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, VocabularyStorageInterface $vocabulary_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->vocabularyStorage = $vocabulary_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getStorage('taxonomy_vocabulary')
);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
// @todo Remove the legacy code.
// Convert legacy vids option to machine name vocabularies.
if (!empty($this->options['vids'])) {
$vocabularies = taxonomy_vocabulary_get_names();
foreach ($this->options['vids'] as $vid) {
if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) {
$this->options['vocabularies'][$vocabularies[$vid]->machine_name] = $vocabularies[$vid]->machine_name;
}
}
}
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['vids'] = array('default' => array());
return $options;
}
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$vocabularies = $this->vocabularyStorage->loadMultiple();
$options = array();
foreach ($vocabularies as $voc) {
$options[$voc->id()] = $voc->label();
}
$form['vids'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('Vocabularies'),
'#options' => $options,
'#default_value' => $this->options['vids'],
'#description' => $this->t('Choose which vocabularies you wish to relate. Remember that every term found will create a new record, so this relationship is best used on just one vocabulary that has only one term per node.'),
);
parent::buildOptionsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state) {
// Transform the #type = checkboxes value to a numerically indexed array,
// because the config schema expects a sequence, not a mapping.
$vids = $form_state->getValue(['options', 'vids']);
$form_state->setValue(['options', 'vids'], array_values(array_filter($vids)));
}
/**
* Called to implement a relationship in a query.
*/
public function query() {
$this->ensureMyTable();
$def = $this->definition;
$def['table'] = 'taxonomy_term_field_data';
if (!array_filter($this->options['vids'])) {
$taxonomy_index = $this->query->addTable('taxonomy_index', $this->relationship);
$def['left_table'] = $taxonomy_index;
$def['left_field'] = 'tid';
$def['field'] = 'tid';
$def['type'] = empty($this->options['required']) ? 'LEFT' : 'INNER';
}
else {
// If vocabularies are supplied join a subselect instead
$def['left_table'] = $this->tableAlias;
$def['left_field'] = 'nid';
$def['field'] = 'nid';
$def['type'] = empty($this->options['required']) ? 'LEFT' : 'INNER';
$def['adjusted'] = TRUE;
$query = db_select('taxonomy_term_field_data', 'td');
$query->addJoin($def['type'], 'taxonomy_index', 'tn', 'tn.tid = td.tid');
$query->condition('td.vid', array_filter($this->options['vids']), 'IN');
$query->addTag('taxonomy_term_access');
$query->fields('td');
$query->fields('tn', array('nid'));
$def['table formula'] = $query;
}
$join = \Drupal::service('plugin.manager.views.join')->createInstance('standard', $def);
// use a short alias for this:
$alias = $def['table'] . '_' . $this->table;
$this->alias = $this->query->addRelationship($alias, $join, 'taxonomy_term_field_data', $this->relationship);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
foreach ($this->options['vids'] as $vocabulary_id) {
if ($vocabulary = $this->vocabularyStorage->load($vocabulary_id)) {
$dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
}
}
return $dependencies;
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\taxonomy\Plugin\views\wizard;
use Drupal\views\Plugin\views\wizard\WizardPluginBase;
/**
* Tests creating taxonomy views with the wizard.
*
* @ViewsWizard(
* id = "taxonomy_term",
* base_table = "taxonomy_term_field_data",
* title = @Translation("Taxonomy terms")
* )
*/
class TaxonomyTerm extends WizardPluginBase {
/**
* {@inheritdoc}
*/
protected function defaultDisplayOptions() {
$display_options = parent::defaultDisplayOptions();
// Add permission-based access control.
$display_options['access']['type'] = 'perm';
$display_options['access']['options']['perm'] = 'access content';
// Remove the default fields, since we are customizing them here.
unset($display_options['fields']);
/* Field: Taxonomy: Term */
$display_options['fields']['name']['id'] = 'name';
$display_options['fields']['name']['table'] = 'taxonomy_term_field_data';
$display_options['fields']['name']['field'] = 'name';
$display_options['fields']['name']['entity_type'] = 'taxonomy_term';
$display_options['fields']['name']['entity_field'] = 'name';
$display_options['fields']['name']['label'] = '';
$display_options['fields']['name']['alter']['alter_text'] = 0;
$display_options['fields']['name']['alter']['make_link'] = 0;
$display_options['fields']['name']['alter']['absolute'] = 0;
$display_options['fields']['name']['alter']['trim'] = 0;
$display_options['fields']['name']['alter']['word_boundary'] = 0;
$display_options['fields']['name']['alter']['ellipsis'] = 0;
$display_options['fields']['name']['alter']['strip_tags'] = 0;
$display_options['fields']['name']['alter']['html'] = 0;
$display_options['fields']['name']['hide_empty'] = 0;
$display_options['fields']['name']['empty_zero'] = 0;
$display_options['fields']['name']['type'] = 'string';
$display_options['fields']['name']['settings']['link_to_entity'] = 1;
$display_options['fields']['name']['plugin_id'] = 'term_name';
return $display_options;
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Drupal\taxonomy;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides dynamic permissions of the taxonomy module.
*
* @see taxonomy.permissions.yml
*/
class TaxonomyPermissions implements ContainerInjectionInterface {
use StringTranslationTrait;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a TaxonomyPermissions instance.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('entity.manager'));
}
/**
* Get taxonomy permissions.
*
* @return array
* Permissions array.
*/
public function permissions() {
$permissions = [];
foreach ($this->entityManager->getStorage('taxonomy_vocabulary')->loadMultiple() as $vocabulary) {
$permissions += [
'edit terms in ' . $vocabulary->id() => [
'title' => $this->t('Edit terms in %vocabulary', ['%vocabulary' => $vocabulary->label()]),
],
];
$permissions += [
'delete terms in ' . $vocabulary->id() => [
'title' => $this->t('Delete terms from %vocabulary', ['%vocabulary' => $vocabulary->label()]),
],
];
}
return $permissions;
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Drupal\taxonomy;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the access control handler for the taxonomy term entity type.
*
* @see \Drupal\taxonomy\Entity\Term
*/
class TermAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
switch ($operation) {
case 'view':
return AccessResult::allowedIfHasPermission($account, 'access content');
case 'update':
return AccessResult::allowedIfHasPermissions($account, ["edit terms in {$entity->bundle()}", 'administer taxonomy'], 'OR');
case 'delete':
return AccessResult::allowedIfHasPermissions($account, ["delete terms in {$entity->bundle()}", 'administer taxonomy'], 'OR');
default:
// No opinion.
return AccessResult::neutral();
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return AccessResult::allowedIfHasPermission($account, 'administer taxonomy');
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace Drupal\taxonomy;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Provides a custom taxonomy breadcrumb builder that uses the term hierarchy.
*/
class TermBreadcrumbBuilder implements BreadcrumbBuilderInterface {
use StringTranslationTrait;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The taxonomy storage.
*
* @var \Drupal\Taxonomy\TermStorageInterface
*/
protected $termStorage;
/**
* Constructs the TermBreadcrumbBuilder.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entityManager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entityManager) {
$this->entityManager = $entityManager;
$this->termStorage = $entityManager->getStorage('taxonomy_term');
}
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $route_match) {
return $route_match->getRouteName() == 'entity.taxonomy_term.canonical'
&& $route_match->getParameter('taxonomy_term') instanceof TermInterface;
}
/**
* {@inheritdoc}
*/
public function build(RouteMatchInterface $route_match) {
$breadcrumb = new Breadcrumb();
$breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '<front>'));
$term = $route_match->getParameter('taxonomy_term');
// Breadcrumb needs to have terms cacheable metadata as a cacheable
// dependency even though it is not shown in the breadcrumb because e.g. its
// parent might have changed.
$breadcrumb->addCacheableDependency($term);
// @todo This overrides any other possible breadcrumb and is a pure
// hard-coded presumption. Make this behavior configurable per
// vocabulary or term.
$parents = $this->termStorage->loadAllParents($term->id());
// Remove current term being accessed.
array_shift($parents);
foreach (array_reverse($parents) as $term) {
$term = $this->entityManager->getTranslationFromContext($term);
$breadcrumb->addCacheableDependency($term);
$breadcrumb->addLink(Link::createFromRoute($term->getName(), 'entity.taxonomy_term.canonical', array('taxonomy_term' => $term->id())));
}
// This breadcrumb builder is based on a route parameter, and hence it
// depends on the 'route' cache context.
$breadcrumb->addCacheContexts(['route']);
return $breadcrumb;
}
}

View file

@ -0,0 +1,164 @@
<?php
namespace Drupal\taxonomy;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
/**
* Base for handler for taxonomy term edit forms.
*/
class TermForm extends ContentEntityForm {
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$term = $this->entity;
$vocab_storage = $this->entityManager->getStorage('taxonomy_vocabulary');
$taxonomy_storage = $this->entityManager->getStorage('taxonomy_term');
$vocabulary = $vocab_storage->load($term->bundle());
$parent = array_keys($taxonomy_storage->loadParents($term->id()));
$form_state->set(['taxonomy', 'parent'], $parent);
$form_state->set(['taxonomy', 'vocabulary'], $vocabulary);
$form['relations'] = array(
'#type' => 'details',
'#title' => $this->t('Relations'),
'#open' => $vocabulary->getHierarchy() == VocabularyInterface::HIERARCHY_MULTIPLE,
'#weight' => 10,
);
// \Drupal\taxonomy\TermStorageInterface::loadTree() and
// \Drupal\taxonomy\TermStorageInterface::loadParents() may contain large
// numbers of items so we check for taxonomy.settings:override_selector
// before loading the full vocabulary. Contrib modules can then intercept
// before hook_form_alter to provide scalable alternatives.
if (!$this->config('taxonomy.settings')->get('override_selector')) {
$parent = array_keys($taxonomy_storage->loadParents($term->id()));
$children = $taxonomy_storage->loadTree($vocabulary->id(), $term->id());
// A term can't be the child of itself, nor of its children.
foreach ($children as $child) {
$exclude[] = $child->tid;
}
$exclude[] = $term->id();
$tree = $taxonomy_storage->loadTree($vocabulary->id());
$options = array('<' . $this->t('root') . '>');
if (empty($parent)) {
$parent = array(0);
}
foreach ($tree as $item) {
if (!in_array($item->tid, $exclude)) {
$options[$item->tid] = str_repeat('-', $item->depth) . $item->name;
}
}
$form['relations']['parent'] = array(
'#type' => 'select',
'#title' => $this->t('Parent terms'),
'#options' => $options,
'#default_value' => $parent,
'#multiple' => TRUE,
);
}
$form['relations']['weight'] = array(
'#type' => 'textfield',
'#title' => $this->t('Weight'),
'#size' => 6,
'#default_value' => $term->getWeight(),
'#description' => $this->t('Terms are displayed in ascending order by weight.'),
'#required' => TRUE,
);
$form['vid'] = array(
'#type' => 'value',
'#value' => $vocabulary->id(),
);
$form['tid'] = array(
'#type' => 'value',
'#value' => $term->id(),
);
return parent::form($form, $form_state, $term);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
// Ensure numeric values.
if ($form_state->hasValue('weight') && !is_numeric($form_state->getValue('weight'))) {
$form_state->setErrorByName('weight', $this->t('Weight value must be numeric.'));
}
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
$term = parent::buildEntity($form, $form_state);
// Prevent leading and trailing spaces in term names.
$term->setName(trim($term->getName()));
// Assign parents with proper delta values starting from 0.
$term->parent = array_keys($form_state->getValue('parent'));
return $term;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$term = $this->entity;
$result = $term->save();
$edit_link = $term->link($this->t('Edit'), 'edit-form');
$view_link = $term->link($term->getName());
switch ($result) {
case SAVED_NEW:
drupal_set_message($this->t('Created new term %term.', array('%term' => $view_link)));
$this->logger('taxonomy')->notice('Created new term %term.', array('%term' => $term->getName(), 'link' => $edit_link));
break;
case SAVED_UPDATED:
drupal_set_message($this->t('Updated term %term.', array('%term' => $view_link)));
$this->logger('taxonomy')->notice('Updated term %term.', array('%term' => $term->getName(), 'link' => $edit_link));
break;
}
$current_parent_count = count($form_state->getValue('parent'));
$previous_parent_count = count($form_state->get(['taxonomy', 'parent']));
// Root doesn't count if it's the only parent.
if ($current_parent_count == 1 && $form_state->hasValue(array('parent', 0))) {
$current_parent_count = 0;
$form_state->setValue('parent', array());
}
// If the number of parents has been reduced to one or none, do a check on the
// parents of every term in the vocabulary value.
$vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
if ($current_parent_count < $previous_parent_count && $current_parent_count < 2) {
taxonomy_check_vocabulary_hierarchy($vocabulary, $form_state->getValues());
}
// If we've increased the number of parents and this is a single or flat
// hierarchy, update the vocabulary immediately.
elseif ($current_parent_count > $previous_parent_count && $vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE) {
$vocabulary->setHierarchy($current_parent_count == 1 ? VocabularyInterface::HIERARCHY_SINGLE : VocabularyInterface::HIERARCHY_MULTIPLE);
$vocabulary->save();
}
$form_state->setValue('tid', $term->id());
$form_state->set('tid', $term->id());
}
}

View file

@ -0,0 +1,93 @@
<?php
namespace Drupal\taxonomy;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangedInterface;
/**
* Provides an interface defining a taxonomy term entity.
*/
interface TermInterface extends ContentEntityInterface, EntityChangedInterface {
/**
* Gets the term's description.
*
* @return string
* The term description.
*/
public function getDescription();
/**
* Sets the term's description.
*
* @param string $description
* The term's description.
*
* @return $this
*/
public function setDescription($description);
/**
* Gets the text format name for the term's description.
*
* @return string
* The text format name.
*/
public function getFormat();
/**
* Sets the text format name for the term's description.
*
* @param string $format
* The term's description text format.
*
* @return $this
*/
public function setFormat($format);
/**
* Gets the name of the term.
*
* @return string
* The name of the term.
*/
public function getName();
/**
* Sets the name of the term.
*
* @param int $name
* The term's name.
*
* @return $this
*/
public function setName($name);
/**
* Gets the weight of this term.
*
* @return int
* The weight of the term.
*/
public function getWeight();
/**
* Gets the weight of this term.
*
* @param int $weight
* The term's weight.
*
* @return $this
*/
public function setWeight($weight);
/**
* Get the taxonomy vocabulary id this term belongs to.
*
* @return int
* The id of the vocabulary.
*/
public function getVocabularyId();
}

View file

@ -0,0 +1,373 @@
<?php
namespace Drupal\taxonomy;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Entity\EntityInterface;
/**
* Defines a Controller class for taxonomy terms.
*/
class TermStorage extends SqlContentEntityStorage implements TermStorageInterface {
/**
* Array of loaded parents keyed by child term ID.
*
* @var array
*/
protected $parents = array();
/**
* Array of all loaded term ancestry keyed by ancestor term ID.
*
* @var array
*/
protected $parentsAll = array();
/**
* Array of child terms keyed by parent term ID.
*
* @var array
*/
protected $children = array();
/**
* Array of term parents keyed by vocabulary ID and child term ID.
*
* @var array
*/
protected $treeParents = array();
/**
* Array of term ancestors keyed by vocabulary ID and parent term ID.
*
* @var array
*/
protected $treeChildren = array();
/**
* Array of terms in a tree keyed by vocabulary ID and term ID.
*
* @var array
*/
protected $treeTerms = array();
/**
* Array of loaded trees keyed by a cache id matching tree arguments.
*
* @var array
*/
protected $trees = array();
/**
* {@inheritdoc}
*
* @param array $values
* An array of values to set, keyed by property name. A value for the
* vocabulary ID ('vid') is required.
*/
public function create(array $values = array()) {
// Save new terms with no parents by default.
if (empty($values['parent'])) {
$values['parent'] = array(0);
}
$entity = parent::create($values);
return $entity;
}
/**
* {@inheritdoc}
*/
public function resetCache(array $ids = NULL) {
drupal_static_reset('taxonomy_term_count_nodes');
$this->parents = array();
$this->parentsAll = array();
$this->children = array();
$this->treeChildren = array();
$this->treeParents = array();
$this->treeTerms = array();
$this->trees = array();
parent::resetCache($ids);
}
/**
* {@inheritdoc}
*/
public function deleteTermHierarchy($tids) {
$this->database->delete('taxonomy_term_hierarchy')
->condition('tid', $tids, 'IN')
->execute();
}
/**
* {@inheritdoc}
*/
public function updateTermHierarchy(EntityInterface $term) {
$query = $this->database->insert('taxonomy_term_hierarchy')
->fields(array('tid', 'parent'));
foreach ($term->parent as $parent) {
$query->values(array(
'tid' => $term->id(),
'parent' => (int) $parent->target_id,
));
}
$query->execute();
}
/**
* {@inheritdoc}
*/
public function loadParents($tid) {
if (!isset($this->parents[$tid])) {
$parents = array();
$query = $this->database->select('taxonomy_term_field_data', 't');
$query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid');
$query->addField('t', 'tid');
$query->condition('h.tid', $tid);
$query->condition('t.default_langcode', 1);
$query->addTag('taxonomy_term_access');
$query->orderBy('t.weight');
$query->orderBy('t.name');
if ($ids = $query->execute()->fetchCol()) {
$parents = $this->loadMultiple($ids);
}
$this->parents[$tid] = $parents;
}
return $this->parents[$tid];
}
/**
* {@inheritdoc}
*/
public function loadAllParents($tid) {
if (!isset($this->parentsAll[$tid])) {
$parents = array();
if ($term = $this->load($tid)) {
$parents[$term->id()] = $term;
$terms_to_search[] = $term->id();
while ($tid = array_shift($terms_to_search)) {
if ($new_parents = $this->loadParents($tid)) {
foreach ($new_parents as $new_parent) {
if (!isset($parents[$new_parent->id()])) {
$parents[$new_parent->id()] = $new_parent;
$terms_to_search[] = $new_parent->id();
}
}
}
}
}
$this->parentsAll[$tid] = $parents;
}
return $this->parentsAll[$tid];
}
/**
* {@inheritdoc}
*/
public function loadChildren($tid, $vid = NULL) {
if (!isset($this->children[$tid])) {
$children = array();
$query = $this->database->select('taxonomy_term_field_data', 't');
$query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
$query->addField('t', 'tid');
$query->condition('h.parent', $tid);
if ($vid) {
$query->condition('t.vid', $vid);
}
$query->condition('t.default_langcode', 1);
$query->addTag('taxonomy_term_access');
$query->orderBy('t.weight');
$query->orderBy('t.name');
if ($ids = $query->execute()->fetchCol()) {
$children = $this->loadMultiple($ids);
}
$this->children[$tid] = $children;
}
return $this->children[$tid];
}
/**
* {@inheritdoc}
*/
public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) {
$cache_key = implode(':', func_get_args());
if (!isset($this->trees[$cache_key])) {
// We cache trees, so it's not CPU-intensive to call on a term and its
// children, too.
if (!isset($this->treeChildren[$vid])) {
$this->treeChildren[$vid] = array();
$this->treeParents[$vid] = array();
$this->treeTerms[$vid] = array();
$query = $this->database->select('taxonomy_term_field_data', 't');
$query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
$result = $query
->addTag('taxonomy_term_access')
->fields('t')
->fields('h', array('parent'))
->condition('t.vid', $vid)
->condition('t.default_langcode', 1)
->orderBy('t.weight')
->orderBy('t.name')
->execute();
foreach ($result as $term) {
$this->treeChildren[$vid][$term->parent][] = $term->tid;
$this->treeParents[$vid][$term->tid][] = $term->parent;
$this->treeTerms[$vid][$term->tid] = $term;
}
}
// Load full entities, if necessary. The entity controller statically
// caches the results.
$term_entities = array();
if ($load_entities) {
$term_entities = $this->loadMultiple(array_keys($this->treeTerms[$vid]));
}
$max_depth = (!isset($max_depth)) ? count($this->treeChildren[$vid]) : $max_depth;
$tree = array();
// Keeps track of the parents we have to process, the last entry is used
// for the next processing step.
$process_parents = array();
$process_parents[] = $parent;
// Loops over the parent terms and adds its children to the tree array.
// Uses a loop instead of a recursion, because it's more efficient.
while (count($process_parents)) {
$parent = array_pop($process_parents);
// The number of parents determines the current depth.
$depth = count($process_parents);
if ($max_depth > $depth && !empty($this->treeChildren[$vid][$parent])) {
$has_children = FALSE;
$child = current($this->treeChildren[$vid][$parent]);
do {
if (empty($child)) {
break;
}
$term = $load_entities ? $term_entities[$child] : $this->treeTerms[$vid][$child];
if (isset($this->treeParents[$vid][$load_entities ? $term->id() : $term->tid])) {
// Clone the term so that the depth attribute remains correct
// in the event of multiple parents.
$term = clone $term;
}
$term->depth = $depth;
unset($term->parent);
$tid = $load_entities ? $term->id() : $term->tid;
$term->parents = $this->treeParents[$vid][$tid];
$tree[] = $term;
if (!empty($this->treeChildren[$vid][$tid])) {
$has_children = TRUE;
// We have to continue with this parent later.
$process_parents[] = $parent;
// Use the current term as parent for the next iteration.
$process_parents[] = $tid;
// Reset pointers for child lists because we step in there more
// often with multi parents.
reset($this->treeChildren[$vid][$tid]);
// Move pointer so that we get the correct term the next time.
next($this->treeChildren[$vid][$parent]);
break;
}
} while ($child = next($this->treeChildren[$vid][$parent]));
if (!$has_children) {
// We processed all terms in this hierarchy-level, reset pointer
// so that this function works the next time it gets called.
reset($this->treeChildren[$vid][$parent]);
}
}
}
$this->trees[$cache_key] = $tree;
}
return $this->trees[$cache_key];
}
/**
* {@inheritdoc}
*/
public function nodeCount($vid) {
$query = $this->database->select('taxonomy_index', 'ti');
$query->addExpression('COUNT(DISTINCT ti.nid)');
$query->leftJoin('taxonomy_term_data', 'td', 'ti.tid = td.tid');
$query->condition('td.vid', $vid);
$query->addTag('vocabulary_node_count');
return $query->execute()->fetchField();
}
/**
* {@inheritdoc}
*/
public function resetWeights($vid) {
$this->database->update('taxonomy_term_field_data')
->fields(array('weight' => 0))
->condition('vid', $vid)
->execute();
}
/**
* {@inheritdoc}
*/
public function getNodeTerms(array $nids, array $vocabs = array(), $langcode = NULL) {
$query = db_select('taxonomy_term_field_data', 'td');
$query->innerJoin('taxonomy_index', 'tn', 'td.tid = tn.tid');
$query->fields('td', array('tid'));
$query->addField('tn', 'nid', 'node_nid');
$query->orderby('td.weight');
$query->orderby('td.name');
$query->condition('tn.nid', $nids, 'IN');
$query->addTag('taxonomy_term_access');
if (!empty($vocabs)) {
$query->condition('td.vid', $vocabs, 'IN');
}
if (!empty($langcode)) {
$query->condition('td.langcode', $langcode);
}
$results = array();
$all_tids = array();
foreach ($query->execute() as $term_record) {
$results[$term_record->node_nid][] = $term_record->tid;
$all_tids[] = $term_record->tid;
}
$all_terms = $this->loadMultiple($all_tids);
$terms = array();
foreach ($results as $nid => $tids) {
foreach ($tids as $tid) {
$terms[$nid][$tid] = $all_terms[$tid];
}
}
return $terms;
}
/**
* {@inheritdoc}
*/
public function __sleep() {
$vars = parent::__sleep();
// Do not serialize static cache.
unset($vars['parents'], $vars['parentsAll'], $vars['children'], $vars['treeChildren'], $vars['treeParents'], $vars['treeTerms'], $vars['trees']);
return $vars;
}
/**
* {@inheritdoc}
*/
public function __wakeup() {
parent::__wakeup();
// Initialize static caches.
$this->parents = array();
$this->parentsAll = array();
$this->children = array();
$this->treeChildren = array();
$this->treeParents = array();
$this->treeTerms = array();
$this->trees = array();
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace Drupal\taxonomy;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\ContentEntityStorageInterface;
/**
* Defines an interface for taxonomy_term entity storage classes.
*/
interface TermStorageInterface extends ContentEntityStorageInterface {
/**
* Removed reference to terms from term_hierarchy.
*
* @param array $tids
* Array of terms that need to be removed from hierarchy.
*/
public function deleteTermHierarchy($tids);
/**
* Updates terms hierarchy information with the hierarchy trail of it.
*
* @param \Drupal\Core\Entity\EntityInterface $term
* Term entity that needs to be added to term hierarchy information.
*/
public function updateTermHierarchy(EntityInterface $term);
/**
* Finds all parents of a given term ID.
*
* @param int $tid
* Term ID to retrieve parents for.
*
* @return \Drupal\taxonomy\TermInterface[]
* An array of term objects which are the parents of the term $tid.
*/
public function loadParents($tid);
/**
* Finds all ancestors of a given term ID.
*
* @param int $tid
* Term ID to retrieve ancestors for.
*
* @return \Drupal\taxonomy\TermInterface[]
* An array of term objects which are the ancestors of the term $tid.
*/
public function loadAllParents($tid);
/**
* Finds all children of a term ID.
*
* @param int $tid
* Term ID to retrieve parents for.
* @param string $vid
* An optional vocabulary ID to restrict the child search.
*
* @return \Drupal\taxonomy\TermInterface[]
* An array of term objects that are the children of the term $tid.
*/
public function loadChildren($tid, $vid = NULL);
/**
* Finds all terms in a given vocabulary ID.
*
* @param string $vid
* Vocabulary ID to retrieve terms for.
* @param int $parent
* The term ID under which to generate the tree. If 0, generate the tree
* for the entire vocabulary.
* @param int $max_depth
* The number of levels of the tree to return. Leave NULL to return all
* levels.
* @param bool $load_entities
* If TRUE, a full entity load will occur on the term objects. Otherwise
* they are partial objects queried directly from the {taxonomy_term_data}
* table to save execution time and memory consumption when listing large
* numbers of terms. Defaults to FALSE.
*
* @return object[]|\Drupal\taxonomy\TermInterface[]
* An array of term objects that are the children of the vocabulary $vid.
*/
public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE);
/**
* Count the number of nodes in a given vocabulary ID.
*
* @param string $vid
* Vocabulary ID to retrieve terms for.
*
* @return int
* A count of the nodes in a given vocabulary ID.
*/
public function nodeCount($vid);
/**
* Reset the weights for a given vocabulary ID.
*
* @param string $vid
* Vocabulary ID to retrieve terms for.
*/
public function resetWeights($vid);
/**
* Returns all terms used to tag some given nodes.
*
* @param array $nids
* Node IDs to retrieve terms for.
* @param array $vocabs
* (optional) A vocabularies array to restrict the term search. Defaults to
* empty array.
* @param string $langcode
* (optional) A language code to restrict the term search. Defaults to NULL.
*
* @return array
* An array of nids and the term entities they were tagged with.
*/
public function getNodeTerms(array $nids, array $vocabs = array(), $langcode = NULL);
}

View file

@ -0,0 +1,139 @@
<?php
namespace Drupal\taxonomy;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Defines the term schema handler.
*/
class TermStorageSchema extends SqlContentEntityStorageSchema {
/**
* {@inheritdoc}
*/
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
$schema = parent::getEntitySchema($entity_type, $reset = FALSE);
$schema['taxonomy_term_field_data']['indexes'] += array(
'taxonomy_term__tree' => array('vid', 'weight', 'name'),
'taxonomy_term__vid_name' => array('vid', 'name'),
);
$schema['taxonomy_term_hierarchy'] = array(
'description' => 'Stores the hierarchical relationship between terms.',
'fields' => array(
'tid' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'Primary Key: The {taxonomy_term_data}.tid of the term.',
),
'parent' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => "Primary Key: The {taxonomy_term_data}.tid of the term's parent. 0 indicates no parent.",
),
),
'indexes' => array(
'parent' => array('parent'),
),
'foreign keys' => array(
'taxonomy_term_data' => array(
'table' => 'taxonomy_term_data',
'columns' => array('tid' => 'tid'),
),
),
'primary key' => array('tid', 'parent'),
);
$schema['taxonomy_index'] = array(
'description' => 'Maintains denormalized information about node/term relationships.',
'fields' => array(
'nid' => array(
'description' => 'The {node}.nid this record tracks.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'tid' => array(
'description' => 'The term ID.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'status' => array(
'description' => 'Boolean indicating whether the node is published (visible to non-administrators).',
'type' => 'int',
'not null' => TRUE,
'default' => 1,
),
'sticky' => array(
'description' => 'Boolean indicating whether the node is sticky.',
'type' => 'int',
'not null' => FALSE,
'default' => 0,
'size' => 'tiny',
),
'created' => array(
'description' => 'The Unix timestamp when the node was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('nid', 'tid'),
'indexes' => array(
'term_node' => array('tid', 'status', 'sticky', 'created'),
),
'foreign keys' => array(
'tracked_node' => array(
'table' => 'node',
'columns' => array('nid' => 'nid'),
),
'term' => array(
'table' => 'taxonomy_term_data',
'columns' => array('tid' => 'tid'),
),
),
);
return $schema;
}
/**
* {@inheritdoc}
*/
protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
$schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
$field_name = $storage_definition->getName();
if ($table_name == 'taxonomy_term_field_data') {
// Remove unneeded indexes.
unset($schema['indexes']['taxonomy_term_field__vid__target_id']);
unset($schema['indexes']['taxonomy_term_field__description__format']);
switch ($field_name) {
case 'weight':
// Improves the performance of the taxonomy_term__tree index defined
// in getEntitySchema().
$schema['fields'][$field_name]['not null'] = TRUE;
break;
case 'name':
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
break;
}
}
return $schema;
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace Drupal\taxonomy;
use Drupal\Core\Entity\EntityInterface;
use Drupal\content_translation\ContentTranslationHandler;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines the translation handler for terms.
*/
class TermTranslationHandler extends ContentTranslationHandler {
/**
* {@inheritdoc}
*/
public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
parent::entityFormAlter($form, $form_state, $entity);
$form['actions']['submit']['#submit'][] = array($this, 'entityFormSave');
}
/**
* Form submission handler for TermTranslationHandler::entityFormAlter().
*
* This handles the save action.
*
* @see \Drupal\Core\Entity\EntityForm::build()
*/
function entityFormSave(array $form, FormStateInterface $form_state) {
if ($this->getSourceLangcode($form_state)) {
$entity = $form_state->getFormObject()->getEntity();
// We need a redirect here, otherwise we would get an access denied page,
// since the current URL would be preserved and we would try to add a
// translation for a language that already has a translation.
$form_state->setRedirectUrl($entity->urlInfo('edit-form'));
}
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Drupal\taxonomy;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityViewBuilder;
/**
* View builder handler for taxonomy terms.
*/
class TermViewBuilder extends EntityViewBuilder {
/**
* {@inheritdoc}
*/
protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
parent::alterBuild($build, $entity, $display, $view_mode);
$build['#contextual_links']['taxonomy_term'] = array(
'route_parameters' => array('taxonomy_term' => $entity->id()),
'metadata' => array('changed' => $entity->getChangedTime()),
);
}
}

View file

@ -0,0 +1,264 @@
<?php
namespace Drupal\taxonomy;
use Drupal\views\EntityViewsData;
/**
* Provides the views data for the taxonomy entity type.
*/
class TermViewsData extends EntityViewsData {
/**
* {@inheritdoc}
*/
public function getViewsData() {
$data = parent::getViewsData();
$data['taxonomy_term_field_data']['table']['base']['help'] = $this->t('Taxonomy terms are attached to nodes.');
$data['taxonomy_term_field_data']['table']['base']['access query tag'] = 'taxonomy_term_access';
$data['taxonomy_term_field_data']['table']['wizard_id'] = 'taxonomy_term';
$data['taxonomy_term_field_data']['table']['join'] = array(
// This is provided for the many_to_one argument.
'taxonomy_index' => array(
'field' => 'tid',
'left_field' => 'tid',
),
);
$data['taxonomy_term_field_data']['tid']['help'] = $this->t('The tid of a taxonomy term.');
$data['taxonomy_term_field_data']['tid']['argument']['id'] = 'taxonomy';
$data['taxonomy_term_field_data']['tid']['argument']['name field'] = 'name';
$data['taxonomy_term_field_data']['tid']['argument']['zero is null'] = TRUE;
$data['taxonomy_term_field_data']['tid']['filter']['id'] = 'taxonomy_index_tid';
$data['taxonomy_term_field_data']['tid']['filter']['title'] = $this->t('Term');
$data['taxonomy_term_field_data']['tid']['filter']['help'] = $this->t('Taxonomy term chosen from autocomplete or select widget.');
$data['taxonomy_term_field_data']['tid']['filter']['hierarchy table'] = 'taxonomy_term_hierarchy';
$data['taxonomy_term_field_data']['tid']['filter']['numeric'] = TRUE;
$data['taxonomy_term_field_data']['tid_raw'] = array(
'title' => $this->t('Term ID'),
'help' => $this->t('The tid of a taxonomy term.'),
'real field' => 'tid',
'filter' => array(
'id' => 'numeric',
'allow empty' => TRUE,
),
);
$data['taxonomy_term_field_data']['tid_representative'] = array(
'relationship' => array(
'title' => $this->t('Representative node'),
'label' => $this->t('Representative node'),
'help' => $this->t('Obtains a single representative node for each term, according to a chosen sort criterion.'),
'id' => 'groupwise_max',
'relationship field' => 'tid',
'outer field' => 'taxonomy_term_field_data.tid',
'argument table' => 'taxonomy_term_field_data',
'argument field' => 'tid',
'base' => 'node_field_data',
'field' => 'nid',
'relationship' => 'node_field_data:term_node_tid'
),
);
$data['taxonomy_term_field_data']['vid']['help'] = $this->t('Filter the results of "Taxonomy: Term" to a particular vocabulary.');
unset($data['taxonomy_term_field_data']['vid']['field']);
unset($data['taxonomy_term_field_data']['vid']['argument']);
unset($data['taxonomy_term_field_data']['vid']['sort']);
$data['taxonomy_term_field_data']['name']['field']['id'] = 'term_name';
$data['taxonomy_term_field_data']['name']['argument']['many to one'] = TRUE;
$data['taxonomy_term_field_data']['name']['argument']['empty field name'] = $this->t('Uncategorized');
$data['taxonomy_term_field_data']['description__value']['field']['click sortable'] = FALSE;
$data['taxonomy_term_field_data']['changed']['title'] = $this->t('Updated date');
$data['taxonomy_term_field_data']['changed']['help'] = $this->t('The date the term was last updated.');
$data['taxonomy_term_field_data']['changed_fulldate'] = array(
'title' => $this->t('Updated date'),
'help' => $this->t('Date in the form of CCYYMMDD.'),
'argument' => array(
'field' => 'changed',
'id' => 'date_fulldate',
),
);
$data['taxonomy_term_field_data']['changed_year_month'] = array(
'title' => $this->t('Updated year + month'),
'help' => $this->t('Date in the form of YYYYMM.'),
'argument' => array(
'field' => 'changed',
'id' => 'date_year_month',
),
);
$data['taxonomy_term_field_data']['changed_year'] = array(
'title' => $this->t('Updated year'),
'help' => $this->t('Date in the form of YYYY.'),
'argument' => array(
'field' => 'changed',
'id' => 'date_year',
),
);
$data['taxonomy_term_field_data']['changed_month'] = array(
'title' => $this->t('Updated month'),
'help' => $this->t('Date in the form of MM (01 - 12).'),
'argument' => array(
'field' => 'changed',
'id' => 'date_month',
),
);
$data['taxonomy_term_field_data']['changed_day'] = array(
'title' => $this->t('Updated day'),
'help' => $this->t('Date in the form of DD (01 - 31).'),
'argument' => array(
'field' => 'changed',
'id' => 'date_day',
),
);
$data['taxonomy_term_field_data']['changed_week'] = array(
'title' => $this->t('Updated week'),
'help' => $this->t('Date in the form of WW (01 - 53).'),
'argument' => array(
'field' => 'changed',
'id' => 'date_week',
),
);
$data['taxonomy_index']['table']['group'] = $this->t('Taxonomy term');
$data['taxonomy_index']['table']['join'] = array(
'taxonomy_term_field_data' => array(
// links directly to taxonomy_term_field_data via tid
'left_field' => 'tid',
'field' => 'tid',
),
'node_field_data' => array(
// links directly to node via nid
'left_field' => 'nid',
'field' => 'nid',
),
'taxonomy_term_hierarchy' => array(
'left_field' => 'tid',
'field' => 'tid',
),
);
$data['taxonomy_index']['nid'] = array(
'title' => $this->t('Content with term'),
'help' => $this->t('Relate all content tagged with a term.'),
'relationship' => array(
'id' => 'standard',
'base' => 'node',
'base field' => 'nid',
'label' => $this->t('node'),
'skip base' => 'node',
),
);
// @todo This stuff needs to move to a node field since really it's all
// about nodes.
$data['taxonomy_index']['tid'] = array(
'group' => $this->t('Content'),
'title' => $this->t('Has taxonomy term ID'),
'help' => $this->t('Display content if it has the selected taxonomy terms.'),
'argument' => array(
'id' => 'taxonomy_index_tid',
'name table' => 'taxonomy_term_field_data',
'name field' => 'name',
'empty field name' => $this->t('Uncategorized'),
'numeric' => TRUE,
'skip base' => 'taxonomy_term_field_data',
),
'filter' => array(
'title' => $this->t('Has taxonomy term'),
'id' => 'taxonomy_index_tid',
'hierarchy table' => 'taxonomy_term_hierarchy',
'numeric' => TRUE,
'skip base' => 'taxonomy_term_field_data',
'allow empty' => TRUE,
),
);
$data['taxonomy_index']['status'] = [
'title' => $this->t('Publish status'),
'help' => $this->t('Whether or not the content related to a term is published.'),
'filter' => [
'id' => 'boolean',
'label' => $this->t('Published status'),
'type' => 'yes-no',
],
];
$data['taxonomy_index']['sticky'] = [
'title' => $this->t('Sticky status'),
'help' => $this->t('Whether or not the content related to a term is sticky.'),
'filter' => [
'id' => 'boolean',
'label' => $this->t('Sticky status'),
'type' => 'yes-no',
],
'sort' => [
'id' => 'standard',
'help' => $this->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' => $this->t('Post date'),
'help' => $this->t('The date the content related to a term was posted.'),
'sort' => [
'id' => 'date'
],
'filter' => [
'id' => 'date',
],
];
$data['taxonomy_term_hierarchy']['table']['group'] = $this->t('Taxonomy term');
$data['taxonomy_term_hierarchy']['table']['provider'] = 'taxonomy';
$data['taxonomy_term_hierarchy']['table']['join'] = array(
'taxonomy_term_hierarchy' => array(
// Link to self through left.parent = right.tid (going down in depth).
'left_field' => 'tid',
'field' => 'parent',
),
'taxonomy_term_field_data' => array(
// Link directly to taxonomy_term_field_data via tid.
'left_field' => 'tid',
'field' => 'tid',
),
);
$data['taxonomy_term_hierarchy']['parent'] = array(
'title' => $this->t('Parent term'),
'help' => $this->t('The parent term of the term. This can produce duplicate entries if you are using a vocabulary that allows multiple parents.'),
'relationship' => array(
'base' => 'taxonomy_term_field_data',
'field' => 'parent',
'label' => $this->t('Parent'),
'id' => 'standard',
),
'filter' => array(
'help' => $this->t('Filter the results of "Taxonomy: Term" by the parent pid.'),
'id' => 'numeric',
),
'argument' => array(
'help' => $this->t('The parent term of the term.'),
'id' => 'taxonomy',
),
);
return $data;
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\taxonomy\Tests;
/**
* Verifies operation of a taxonomy-based Entity Query.
*
* @group taxonomy
*/
class EfqTest extends TaxonomyTestBase {
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy']));
$this->vocabulary = $this->createVocabulary();
}
/**
* Tests that a basic taxonomy entity query works.
*/
function testTaxonomyEfq() {
$terms = array();
for ($i = 0; $i < 5; $i++) {
$term = $this->createTerm($this->vocabulary);
$terms[$term->id()] = $term;
}
$result = \Drupal::entityQuery('taxonomy_term')->execute();
sort($result);
$this->assertEqual(array_keys($terms), $result, 'Taxonomy terms were retrieved by entity query.');
$tid = reset($result);
$ids = (object) array(
'entity_type' => 'taxonomy_term',
'entity_id' => $tid,
'bundle' => $this->vocabulary->id(),
);
$term = _field_create_entity_from_ids($ids);
$this->assertEqual($term->id(), $tid, 'Taxonomy term can be created based on the IDs.');
// Create a second vocabulary and five more terms.
$vocabulary2 = $this->createVocabulary();
$terms2 = array();
for ($i = 0; $i < 5; $i++) {
$term = $this->createTerm($vocabulary2);
$terms2[$term->id()] = $term;
}
$result = \Drupal::entityQuery('taxonomy_term')
->condition('vid', $vocabulary2->id())
->execute();
sort($result);
$this->assertEqual(array_keys($terms2), $result, format_string('Taxonomy terms from the %name vocabulary were retrieved by entity query.', array('%name' => $vocabulary2->label())));
$tid = reset($result);
$ids = (object) array(
'entity_type' => 'taxonomy_term',
'entity_id' => $tid,
'bundle' => $vocabulary2->id(),
);
$term = _field_create_entity_from_ids($ids);
$this->assertEqual($term->id(), $tid, 'Taxonomy term can be created based on the IDs.');
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use \Drupal\taxonomy\Entity\Vocabulary;
/**
* Posts an article with a taxonomy term and a date prior to 1970.
*
* @group taxonomy
*/
class LegacyTest extends TaxonomyTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'datetime');
protected function setUp() {
parent::setUp();
// Create a tags vocabulary for the 'article' content type.
$vocabulary = Vocabulary::create([
'name' => 'Tags',
'vid' => 'tags',
]);
$vocabulary->save();
$field_name = 'field_' . $vocabulary->id();
$handler_settings = array(
'target_bundles' => array(
$vocabulary->id() => $vocabulary->id(),
),
'auto_create' => TRUE,
);
$this->createEntityReferenceField('node', 'article', $field_name, 'Tags', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
entity_get_form_display('node', 'article', 'default')
->setComponent($field_name, array(
'type' => 'entity_reference_autocomplete_tags',
))
->save();
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'administer nodes', 'bypass node access']));
}
/**
* Test taxonomy functionality with nodes prior to 1970.
*/
function testTaxonomyLegacyNode() {
// Posts an article with a taxonomy term and a date prior to 1970.
$date = new DrupalDateTime('1969-01-01 00:00:00');
$edit = array();
$edit['title[0][value]'] = $this->randomMachineName();
$edit['created[0][value][date]'] = $date->format('Y-m-d');
$edit['created[0][value][time]'] = $date->format('H:i:s');
$edit['body[0][value]'] = $this->randomMachineName();
$edit['field_tags[target_id]'] = $this->randomMachineName();
$this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
// Checks that the node has been saved.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertEqual($node->getCreatedTime(), $date->getTimestamp(), 'Legacy node was saved with the right date.');
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\taxonomy\Entity\Term;
/**
* Tests the loading of multiple taxonomy terms at once.
*
* @group taxonomy
*/
class LoadMultipleTest extends TaxonomyTestBase {
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy']));
}
/**
* Create a vocabulary and some taxonomy terms, ensuring they're loaded
* correctly using entity_load_multiple().
*/
function testTaxonomyTermMultipleLoad() {
// Create a vocabulary.
$vocabulary = $this->createVocabulary();
// Create five terms in the vocabulary.
$i = 0;
while ($i < 5) {
$i++;
$this->createTerm($vocabulary);
}
// Load the terms from the vocabulary.
$terms = entity_load_multiple_by_properties('taxonomy_term', array('vid' => $vocabulary->id()));
$count = count($terms);
$this->assertEqual($count, 5, format_string('Correct number of terms were loaded. @count terms.', array('@count' => $count)));
// Load the same terms again by tid.
$terms2 = Term::loadMultiple(array_keys($terms));
$this->assertEqual($count, count($terms2), 'Five terms were loaded by tid.');
$this->assertEqual($terms, $terms2, 'Both arrays contain the same terms.');
// Remove one term from the array, then delete it.
$deleted = array_shift($terms2);
$deleted->delete();
$deleted_term = Term::load($deleted->id());
$this->assertFalse($deleted_term);
// Load terms from the vocabulary by vid.
$terms3 = entity_load_multiple_by_properties('taxonomy_term', array('vid' => $vocabulary->id()));
$this->assertEqual(count($terms3), 4, 'Correct number of terms were loaded.');
$this->assertFalse(isset($terms3[$deleted->id()]));
// Create a single term and load it by name.
$term = $this->createTerm($vocabulary);
$loaded_terms = entity_load_multiple_by_properties('taxonomy_term', array('name' => $term->getName()));
$this->assertEqual(count($loaded_terms), 1, 'One term was loaded.');
$loaded_term = reset($loaded_terms);
$this->assertEqual($term->id(), $loaded_term->id(), 'Term loaded by name successfully.');
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\views\Views;
/**
* Ensure that data added as terms appears in RSS feeds if "RSS Category" format
* is selected.
*
* @group taxonomy
*/
class RssTest extends TaxonomyTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'field_ui', 'views');
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Name of the taxonomy term reference field.
*
* @var string
*/
protected $fieldName;
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'bypass node access', 'administer content types', 'administer node display']));
$this->vocabulary = $this->createVocabulary();
$this->fieldName = 'taxonomy_' . $this->vocabulary->id();
$handler_settings = array(
'target_bundles' => array(
$this->vocabulary->id() => $this->vocabulary->id(),
),
'auto_create' => TRUE,
);
$this->createEntityReferenceField('node', 'article', $this->fieldName, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
entity_get_form_display('node', 'article', 'default')
->setComponent($this->fieldName, array(
'type' => 'options_select',
))
->save();
entity_get_display('node', 'article', 'default')
->setComponent($this->fieldName, array(
'type' => 'entity_reference_label',
))
->save();
}
/**
* Tests that terms added to nodes are displayed in core RSS feed.
*
* Create a node and assert that taxonomy terms appear in rss.xml.
*/
function testTaxonomyRss() {
// Create two taxonomy terms.
$term1 = $this->createTerm($this->vocabulary);
// RSS display must be added manually.
$this->drupalGet("admin/structure/types/manage/article/display");
$edit = array(
"display_modes_custom[rss]" => '1',
);
$this->drupalPostForm(NULL, $edit, t('Save'));
// Change the format to 'RSS category'.
$this->drupalGet("admin/structure/types/manage/article/display/rss");
$edit = array(
"fields[taxonomy_" . $this->vocabulary->id() . "][type]" => 'entity_reference_rss_category',
);
$this->drupalPostForm(NULL, $edit, t('Save'));
// Post an article.
$edit = array();
$edit['title[0][value]'] = $this->randomMachineName();
$edit[$this->fieldName . '[]'] = $term1->id();
$this->drupalPostForm('node/add/article', $edit, t('Save'));
// Check that the term is displayed when the RSS feed is viewed.
$this->drupalGet('rss.xml');
$test_element = sprintf(
'<category %s>%s</category>',
'domain="' . $term1->url('canonical', array('absolute' => TRUE)) . '"',
$term1->getName()
);
$this->assertRaw($test_element, 'Term is displayed when viewing the rss feed.');
// Test that the feed icon exists for the term.
$this->drupalGet("taxonomy/term/{$term1->id()}");
$this->assertLinkByHref("taxonomy/term/{$term1->id()}/feed");
// Test that the feed page exists for the term.
$this->drupalGet("taxonomy/term/{$term1->id()}/feed");
$this->assertTrue(!empty($this->cssSelect('rss[version="2.0"]')), "Feed page is RSS.");
// Check that the "Exception value" is disabled by default.
$this->drupalGet('taxonomy/term/all/feed');
$this->assertResponse(404);
// Set the exception value to 'all'.
$view = Views::getView('taxonomy_term');
$arguments = $view->getDisplay()->getOption('arguments');
$arguments['tid']['exception']['value'] = 'all';
$view->getDisplay()->overrideOption('arguments', $arguments);
$view->storage->save();
// Check the article is shown in the feed.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$raw_xml = '<title>' . $node->label() . '</title>';
$this->drupalGet('taxonomy/term/all/feed');
$this->assertRaw($raw_xml, "Raw text '$raw_xml' is found.");
// Unpublish the article and check that it is not shown in the feed.
$node->setPublished(FALSE)->save();
$this->drupalGet('taxonomy/term/all/feed');
$this->assertNoRaw($raw_xml);
}
}

View file

@ -0,0 +1,97 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\field\Entity\FieldConfig;
use Drupal\user\RoleInterface;
use Drupal\file\Entity\File;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests access checks of private image fields.
*
* @group taxonomy
*/
class TaxonomyImageTest extends TaxonomyTestBase {
/**
* Used taxonomy vocabulary.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('image');
protected function setUp() {
parent::setUp();
// Remove access content permission from registered users.
user_role_revoke_permissions(RoleInterface::AUTHENTICATED_ID, array('access content'));
$this->vocabulary = $this->createVocabulary();
// Add a field to the vocabulary.
$entity_type = 'taxonomy_term';
$name = 'field_test';
FieldStorageConfig::create(array(
'field_name' => $name,
'entity_type' => $entity_type,
'type' => 'image',
'settings' => array(
'uri_scheme' => 'private',
),
))->save();
FieldConfig::create([
'field_name' => $name,
'entity_type' => $entity_type,
'bundle' => $this->vocabulary->id(),
'settings' => array(),
])->save();
entity_get_display($entity_type, $this->vocabulary->id(), 'default')
->setComponent($name, array(
'type' => 'image',
'settings' => array(),
))
->save();
entity_get_form_display($entity_type, $this->vocabulary->id(), 'default')
->setComponent($name, array(
'type' => 'image_image',
'settings' => array(),
))
->save();
}
public function testTaxonomyImageAccess() {
$user = $this->drupalCreateUser(array('administer site configuration', 'administer taxonomy', 'access user profiles'));
$this->drupalLogin($user);
// Create a term and upload the image.
$files = $this->drupalGetTestFiles('image');
$image = array_pop($files);
$edit['name[0][value]'] = $this->randomMachineName();
$edit['files[field_test_0]'] = drupal_realpath($image->uri);
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add', $edit, t('Save'));
$this->drupalPostForm(NULL, ['field_test[0][alt]' => $this->randomMachineName()], t('Save'));
$terms = entity_load_multiple_by_properties('taxonomy_term', array('name' => $edit['name[0][value]']));
$term = reset($terms);
$this->assertText(t('Created new term @name.', array('@name' => $term->getName())));
// Create a user that should have access to the file and one that doesn't.
$access_user = $this->drupalCreateUser(array('access content'));
$no_access_user = $this->drupalCreateUser();
$image = File::load($term->field_test->target_id);
$this->drupalLogin($access_user);
$this->drupalGet(file_create_url($image->getFileUri()));
$this->assertResponse(200, 'Private image on term is accessible with right permission');
$this->drupalLogin($no_access_user);
$this->drupalGet(file_create_url($image->getFileUri()));
$this->assertResponse(403, 'Private image on term not accessible without right permission');
}
}

View file

@ -0,0 +1,119 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests that appropriate query tags are added.
*
* @group taxonomy
*/
class TaxonomyQueryAlterTest extends WebTestBase {
use TaxonomyTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['taxonomy', 'taxonomy_test'];
/**
* Tests that appropriate tags are added when querying the database.
*/
public function testTaxonomyQueryAlter() {
// Create a new vocabulary and add a few terms to it.
$vocabulary = $this->createVocabulary();
$terms = array();
for ($i = 0; $i < 5; $i++) {
$terms[$i] = $this->createTerm($vocabulary);
}
// Set up hierarchy. Term 2 is a child of 1.
$terms[2]->parent = $terms[1]->id();
$terms[2]->save();
$term_storage = \Drupal::entityManager()->getStorage('taxonomy_term');
$this->setupQueryTagTestHooks();
$loaded_term = $term_storage->load($terms[0]->id());
$this->assertEqual($loaded_term->id(), $terms[0]->id(), 'First term was loaded');
$this->assertQueryTagTestResult(1, 0, 'TermStorage::load()');
$this->setupQueryTagTestHooks();
$loaded_terms = $term_storage->loadTree($vocabulary->id());
$this->assertEqual(count($loaded_terms), count($terms), 'All terms were loaded');
$this->assertQueryTagTestResult(1, 1, 'TermStorage::loadTree()');
$this->setupQueryTagTestHooks();
$loaded_terms = $term_storage->loadParents($terms[2]->id());
$this->assertEqual(count($loaded_terms), 1, 'All parent terms were loaded');
$this->assertQueryTagTestResult(2, 1, 'TermStorage::loadParents()');
$this->setupQueryTagTestHooks();
$loaded_terms = $term_storage->loadChildren($terms[1]->id());
$this->assertEqual(count($loaded_terms), 1, 'All child terms were loaded');
$this->assertQueryTagTestResult(2, 1, 'TermStorage::loadChildren()');
$this->setupQueryTagTestHooks();
$query = db_select('taxonomy_term_data', 't');
$query->addField('t', 'tid');
$query->addTag('taxonomy_term_access');
$tids = $query->execute()->fetchCol();
$this->assertEqual(count($tids), count($terms), 'All term IDs were retrieved');
$this->assertQueryTagTestResult(1, 1, 'custom db_select() with taxonomy_term_access tag (preferred)');
$this->setupQueryTagTestHooks();
$query = db_select('taxonomy_term_data', 't');
$query->addField('t', 'tid');
$query->addTag('term_access');
$tids = $query->execute()->fetchCol();
$this->assertEqual(count($tids), count($terms), 'All term IDs were retrieved');
$this->assertQueryTagTestResult(1, 1, 'custom db_select() with term_access tag (deprecated)');
$this->setupQueryTagTestHooks();
$query = \Drupal::entityQuery('taxonomy_term');
$query->addTag('taxonomy_term_access');
$result = $query->execute();
$this->assertEqual(count($result), count($terms), 'All term IDs were retrieved');
$this->assertQueryTagTestResult(1, 1, 'custom EntityFieldQuery with taxonomy_term_access tag (preferred)');
$this->setupQueryTagTestHooks();
$query = \Drupal::entityQuery('taxonomy_term');
$query->addTag('term_access');
$result = $query->execute();
$this->assertEqual(count($result), count($terms), 'All term IDs were retrieved');
$this->assertQueryTagTestResult(1, 1, 'custom EntityFieldQuery with term_access tag (deprecated)');
}
/**
* Sets up the hooks in the test module.
*/
protected function setupQueryTagTestHooks() {
taxonomy_terms_static_reset();
\Drupal::state()->set('taxonomy_test_query_alter', 0);
\Drupal::state()->set('taxonomy_test_query_term_access_alter', 0);
\Drupal::state()->set('taxonomy_test_query_taxonomy_term_access_alter', 0);
}
/**
* Verifies invocation of the hooks in the test module.
*
* @param int $expected_generic_invocations
* The number of times the generic query_alter hook is expected to have
* been invoked.
* @param int $expected_specific_invocations
* The number of times the tag-specific query_alter hooks are expected to
* have been invoked.
* @param string $method
* A string describing the invoked function which generated the query.
*/
protected function assertQueryTagTestResult($expected_generic_invocations, $expected_specific_invocations, $method) {
$this->assertIdentical($expected_generic_invocations, \Drupal::state()->get('taxonomy_test_query_alter'), 'hook_query_alter() invoked when executing ' . $method);
$this->assertIdentical($expected_specific_invocations, \Drupal::state()->get('taxonomy_test_query_term_access_alter'), 'Deprecated hook_query_term_access_alter() invoked when executing ' . $method);
$this->assertIdentical($expected_specific_invocations, \Drupal::state()->get('taxonomy_test_query_taxonomy_term_access_alter'), 'Preferred hook_query_taxonomy_term_access_alter() invoked when executing ' . $method);
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace Drupal\taxonomy\Tests;
/**
* Ensure that the term indentation works properly.
*
* @group taxonomy
*/
class TaxonomyTermIndentationTest extends TaxonomyTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('taxonomy');
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'bypass node access']));
$this->vocabulary = $this->createVocabulary();
}
/**
* Tests term indentation.
*/
function testTermIndentation() {
// Create three taxonomy terms.
$term1 = $this->createTerm($this->vocabulary);
$term2 = $this->createTerm($this->vocabulary);
$term3 = $this->createTerm($this->vocabulary);
// Get the taxonomy storage.
$taxonomy_storage = $this->container->get('entity.manager')->getStorage('taxonomy_term');
// Indent the second term under the first one.
$edit = array(
'terms[tid:' . $term2->id() . ':0][term][tid]' => 2,
'terms[tid:' . $term2->id() . ':0][term][parent]' => 1,
'terms[tid:' . $term2->id() . ':0][term][depth]' => 1,
'terms[tid:' . $term2->id() . ':0][weight]' => 1,
);
// Submit the edited form and check for HTML indentation element presence.
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->get('vid') . '/overview', $edit, t('Save'));
$this->assertPattern('|<div class="js-indentation indentation">&nbsp;</div>|');
// Check explicitly that term 2's parent is term 1.
$parents = $taxonomy_storage->loadParents($term2->id());
$this->assertEqual(key($parents), 1, 'Term 1 is the term 2\'s parent');
// Move the second term back out to the root level.
$edit = array(
'terms[tid:' . $term2->id() . ':0][term][tid]' => 2,
'terms[tid:' . $term2->id() . ':0][term][parent]' => 0,
'terms[tid:' . $term2->id() . ':0][term][depth]' => 0,
'terms[tid:' . $term2->id() . ':0][weight]' => 1,
);
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->get('vid' ) . '/overview', $edit, t('Save'));
// All terms back at the root level, no indentation should be present.
$this->assertNoPattern('|<div class="js-indentation indentation">&nbsp;</div>|');
// Check explicitly that term 2 has no parents.
\Drupal::entityManager()->getStorage('taxonomy_term')->resetCache();
$parents = $taxonomy_storage->loadParents($term2->id());
$this->assertTrue(empty($parents), 'Term 2 has no parents now');
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Drupal\taxonomy\Tests;
/**
* Ensures that the term pager works properly.
*
* @group taxonomy
*/
class TaxonomyTermPagerTest extends TaxonomyTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['taxonomy'];
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'bypass node access']));
$this->vocabulary = $this->createVocabulary();
}
/**
* Tests that the pager is displayed properly on the term overview page.
*/
public function testTaxonomyTermOverviewPager() {
// Set limit to 3 terms per page.
$this->config('taxonomy.settings')
->set('terms_per_page_admin', '3')
->save();
// Create 3 terms.
for ($x = 1; $x <= 3; $x++) {
$this->createTerm($this->vocabulary);
}
// Get Page 1.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertNoPattern('|<nav class="pager" [^>]*>|', 'Pager is not visible on page 1');
// Create 3 more terms to show pager.
for ($x = 1; $x <= 3; $x++) {
$this->createTerm($this->vocabulary);
}
// Get Page 1.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertPattern('|<nav class="pager" [^>]*>|', 'Pager is visible on page 1');
// Get Page 2.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview', ['query' => ['page' => 1]]);
$this->assertPattern('|<nav class="pager" [^>]*>|', 'Pager is visible on page 2');
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\simpletest\WebTestBase;
/**
* Provides common helper methods for Taxonomy module tests.
*/
abstract class TaxonomyTestBase extends WebTestBase {
use TaxonomyTestTrait;
use EntityReferenceTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('taxonomy', 'block');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
// Create Basic page and Article node types.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
}
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Language\LanguageInterface;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\Entity\Term;
/**
* Provides common helper methods for Taxonomy module tests.
*/
trait TaxonomyTestTrait {
/**
* Returns a new vocabulary with random properties.
*/
function createVocabulary() {
// Create a vocabulary.
$vocabulary = Vocabulary::create([
'name' => $this->randomMachineName(),
'description' => $this->randomMachineName(),
'vid' => Unicode::strtolower($this->randomMachineName()),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'weight' => mt_rand(0, 10),
]);
$vocabulary->save();
return $vocabulary;
}
/**
* Returns a new term with random properties in vocabulary $vid.
*
* @param \Drupal\taxonomy\Entity\Vocabulary $vocabulary
* The vocabulary object.
* @param array $values
* (optional) An array of values to set, keyed by property name. If the
* entity type has bundles, the bundle key has to be specified.
*
* @return \Drupal\taxonomy\Entity\Term
* The new taxonomy term object.
*/
function createTerm(Vocabulary $vocabulary, $values = array()) {
$filter_formats = filter_formats();
$format = array_pop($filter_formats);
$term = Term::create($values + [
'name' => $this->randomMachineName(),
'description' => [
'value' => $this->randomMachineName(),
// Use the first available text format.
'format' => $format->id(),
],
'vid' => $vocabulary->id(),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
$term->save();
return $term;
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Provides common testing base for translated taxonomy terms.
*/
trait TaxonomyTranslationTestTrait {
use EntityReferenceTestTrait;
/**
* The vocabulary.
*
* @var \Drupal\taxonomy\Entity\Vocabulary;
*/
protected $vocabulary;
/**
* The field name for our taxonomy term field.
*
* @var string
*/
protected $termFieldName = 'field_tag';
/**
* The langcode of the source language.
*
* @var string
*/
protected $baseLangcode = 'en';
/**
* Target langcode for translation.
*
* @var string
*/
protected $translateToLangcode = 'hu';
/**
* The node to check the translated value on.
*
* @var \Drupal\node\Entity\Node
*/
protected $node;
/**
* Adds additional languages.
*/
protected function setupLanguages() {
ConfigurableLanguage::createFromLangcode($this->translateToLangcode)->save();
$this->rebuildContainer();
}
/**
* Enables translations where it needed.
*/
protected function enableTranslation() {
// Enable translation for the current entity type and ensure the change is
// picked up.
\Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE);
\Drupal::service('content_translation.manager')->setEnabled('taxonomy_term', $this->vocabulary->id(), TRUE);
drupal_static_reset();
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder')->rebuild();
\Drupal::service('entity.definition_update_manager')->applyUpdates();
}
/**
* Adds term reference field for the article content type.
*
* @param bool $translatable
* (optional) If TRUE, create a translatable term reference field. Defaults
* to FALSE.
*/
protected function setUpTermReferenceField() {
$handler_settings = array(
'target_bundles' => array(
$this->vocabulary->id() => $this->vocabulary->id(),
),
'auto_create' => TRUE,
);
$this->createEntityReferenceField('node', 'article', $this->termFieldName, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$field_storage = FieldStorageConfig::loadByName('node', $this->termFieldName);
$field_storage->setTranslatable(FALSE);
$field_storage->save();
entity_get_form_display('node', 'article', 'default')
->setComponent($this->termFieldName, array(
'type' => 'entity_reference_autocomplete_tags',
))
->save();
entity_get_display('node', 'article', 'default')
->setComponent($this->termFieldName, array(
'type' => 'entity_reference_label',
))
->save();
}
}

View file

@ -0,0 +1,204 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests the autocomplete implementation of the taxonomy class.
*
* @group taxonomy
*/
class TermAutocompleteTest extends TaxonomyTestBase {
/**
* The vocabulary.
*
* @var \Drupal\taxonomy\Entity\Vocabulary
*/
protected $vocabulary;
/**
* The field to add to the content type for the taxonomy terms.
*
* @var string
*/
protected $fieldName;
/**
* The admin user.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* The autocomplete URL to call.
*
* @var string
*/
protected $autocompleteUrl;
/**
* The term IDs indexed by term names.
*
* @var array
*/
protected $termIds;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a vocabulary.
$this->vocabulary = $this->createVocabulary();
// Create 11 terms, which have some sub-string in common, in a
// non-alphabetical order, so that we will have more than 10 matches later
// when we test the correct number of results is returned, and we can test
// the order of the results. The location of the sub-string to match varies
// also, since it should not be necessary to start with the sub-string to
// match it. Save term IDs to reuse later.
$termNames = [
'aaa 20 bbb',
'aaa 70 bbb',
'aaa 10 bbb',
'aaa 12 bbb',
'aaa 40 bbb',
'aaa 11 bbb',
'aaa 30 bbb',
'aaa 50 bbb',
'aaa 80',
'aaa 90',
'bbb 60 aaa',
];
foreach ($termNames as $termName) {
$term = $this->createTerm($this->vocabulary, ['name' => $termName]);
$this->termIds[$termName] = $term->id();
}
// Create a taxonomy_term_reference field on the article Content Type that
// uses a taxonomy_autocomplete widget.
$this->fieldName = Unicode::strtolower($this->randomMachineName());
FieldStorageConfig::create([
'field_name' => $this->fieldName,
'entity_type' => 'node',
'type' => 'entity_reference',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'settings' => [
'target_type' => 'taxonomy_term',
],
])->save();
FieldConfig::create([
'field_name' => $this->fieldName,
'bundle' => 'article',
'entity_type' => 'node',
'settings' => [
'handler' => 'default',
'handler_settings' => [
// Restrict selection of terms to a single vocabulary.
'target_bundles' => [
$this->vocabulary->id() => $this->vocabulary->id(),
],
],
],
])->save();
EntityFormDisplay::load('node.article.default')
->setComponent($this->fieldName, [
'type' => 'entity_reference_autocomplete',
])
->save();
EntityViewDisplay::load('node.article.default')
->setComponent($this->fieldName, [
'type' => 'entity_reference_label',
])
->save();
// Create a user and then login.
$this->adminUser = $this->drupalCreateUser(['create article content']);
$this->drupalLogin($this->adminUser);
// Retrieve the autocomplete url.
$this->drupalGet('node/add/article');
$result = $this->xpath('//input[@name="' . $this->fieldName . '[0][target_id]"]');
$this->autocompleteUrl = $this->getAbsoluteUrl($result[0]['data-autocomplete-path']);
}
/**
* Tests that the autocomplete method returns the good number of results.
*
* @see \Drupal\taxonomy\Controller\TermAutocompleteController::autocomplete()
*/
public function testAutocompleteCountResults() {
// Test that no matching term found.
$data = $this->drupalGetJSON(
$this->autocompleteUrl,
['query' => ['q' => 'zzz']]
);
$this->assertTrue(empty($data), 'Autocomplete returned no results');
// Test that only one matching term found, when only one matches.
$data = $this->drupalGetJSON(
$this->autocompleteUrl,
['query' => ['q' => 'aaa 10']]
);
$this->assertEqual(1, count($data), 'Autocomplete returned 1 result');
// Test the correct number of matches when multiple are partial matches.
$data = $this->drupalGetJSON(
$this->autocompleteUrl,
['query' => ['q' => 'aaa 1']]
);
$this->assertEqual(3, count($data), 'Autocomplete returned 3 results');
// Tests that only 10 results are returned, even if there are more than 10
// matches.
$data = $this->drupalGetJSON(
$this->autocompleteUrl,
['query' => ['q' => 'aaa']]
);
$this->assertEqual(10, count($data), 'Autocomplete returned only 10 results (for over 10 matches)');
}
/**
* Tests that the autocomplete method returns properly ordered results.
*
* @see \Drupal\taxonomy\Controller\TermAutocompleteController::autocomplete()
*/
public function testAutocompleteOrderedResults() {
$expectedResults = [
'aaa 10 bbb',
'aaa 11 bbb',
'aaa 12 bbb',
'aaa 20 bbb',
'aaa 30 bbb',
'aaa 40 bbb',
'aaa 50 bbb',
'aaa 70 bbb',
'bbb 60 aaa',
];
// Build $expected to match the autocomplete results.
$expected = [];
foreach ($expectedResults as $termName) {
$expected[] = [
'value' => $termName . ' (' . $this->termIds[$termName] . ')',
'label' => $termName
];
}
$data = $this->drupalGetJSON(
$this->autocompleteUrl,
['query' => ['q' => 'bbb']]
);
$this->assertIdentical($expected, $data);
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\Entity\Term;
/**
* Tests the Taxonomy term entity's cache tags.
*
* @group taxonomy
*/
class TermCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('taxonomy');
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" vocabulary.
$vocabulary = Vocabulary::create([
'name' => 'Camelids',
'vid' => 'camelids',
]);
$vocabulary->save();
// Create a "Llama" taxonomy term.
$term = Term::create([
'name' => 'Llama',
'vid' => $vocabulary->id(),
]);
$term->save();
return $term;
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
/**
* Tests the settings of restricting term selection to a single vocabulary.
*
* @group taxonomy
*/
class TermEntityReferenceTest extends TaxonomyTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['entity_reference_test', 'entity_test'];
/**
* Tests an entity reference field restricted to a single vocabulary.
*
* Creates two vocabularies with a term, then set up the entity reference
* field to limit the target vocabulary to one of them, ensuring that
* the restriction applies.
*/
function testSelectionTestVocabularyRestriction() {
// Create two vocabularies.
$vocabulary = $this->createVocabulary();
$vocabulary2 = $this->createVocabulary();
$term = $this->createTerm($vocabulary);
$term2 = $this->createTerm($vocabulary2);
// Create an entity reference field.
$field_name = 'taxonomy_' . $vocabulary->id();
$field_storage = FieldStorageConfig::create(array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'translatable' => FALSE,
'settings' => array(
'target_type' => 'taxonomy_term',
),
'type' => 'entity_reference',
'cardinality' => 1,
));
$field_storage->save();
$field = FieldConfig::create(array(
'field_storage' => $field_storage,
'entity_type' => 'entity_test',
'bundle' => 'test_bundle',
'settings' => array(
'handler' => 'default',
'handler_settings' => array(
// Restrict selection of terms to a single vocabulary.
'target_bundles' => array(
$vocabulary->id() => $vocabulary->id(),
),
),
),
));
$field->save();
$handler = $this->container->get('plugin.manager.entity_reference_selection')->getSelectionHandler($field);
$result = $handler->getReferenceableEntities();
$expected_result = array(
$vocabulary->id() => array(
$term->id() => $term->getName(),
),
);
$this->assertIdentical($result, $expected_result, 'Terms selection restricted to a single vocabulary.');
}
}

View file

@ -0,0 +1,214 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Tests the hook implementations that maintain the taxonomy index.
*
* @group taxonomy
*/
class TermIndexTest extends TaxonomyTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('views');
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Name of the taxonomy term reference field.
*
* @var string
*/
protected $fieldName1;
/**
* Name of the taxonomy term reference field.
*
* @var string
*/
protected $fieldName2;
protected function setUp() {
parent::setUp();
// Create an administrative user.
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'bypass node access']));
// Create a vocabulary and add two term reference fields to article nodes.
$this->vocabulary = $this->createVocabulary();
$this->fieldName1 = Unicode::strtolower($this->randomMachineName());
$handler_settings = array(
'target_bundles' => array(
$this->vocabulary->id() => $this->vocabulary->id(),
),
'auto_create' => TRUE,
);
$this->createEntityReferenceField('node', 'article', $this->fieldName1, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
entity_get_form_display('node', 'article', 'default')
->setComponent($this->fieldName1, array(
'type' => 'options_select',
))
->save();
entity_get_display('node', 'article', 'default')
->setComponent($this->fieldName1, array(
'type' => 'entity_reference_label',
))
->save();
$this->fieldName2 = Unicode::strtolower($this->randomMachineName());
$this->createEntityReferenceField('node', 'article', $this->fieldName2, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
entity_get_form_display('node', 'article', 'default')
->setComponent($this->fieldName2, array(
'type' => 'options_select',
))
->save();
entity_get_display('node', 'article', 'default')
->setComponent($this->fieldName2, array(
'type' => 'entity_reference_label',
))
->save();
}
/**
* Tests that the taxonomy index is maintained properly.
*/
function testTaxonomyIndex() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
// Create terms in the vocabulary.
$term_1 = $this->createTerm($this->vocabulary);
$term_2 = $this->createTerm($this->vocabulary);
// Post an article.
$edit = array();
$edit['title[0][value]'] = $this->randomMachineName();
$edit['body[0][value]'] = $this->randomMachineName();
$edit["{$this->fieldName1}[]"] = $term_1->id();
$edit["{$this->fieldName2}[]"] = $term_1->id();
$this->drupalPostForm('node/add/article', $edit, t('Save'));
// Check that the term is indexed, and only once.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
':nid' => $node->id(),
':tid' => $term_1->id(),
))->fetchField();
$this->assertEqual(1, $index_count, 'Term 1 is indexed once.');
// Update the article to change one term.
$edit["{$this->fieldName1}[]"] = $term_2->id();
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
// Check that both terms are indexed.
$index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
':nid' => $node->id(),
':tid' => $term_1->id(),
))->fetchField();
$this->assertEqual(1, $index_count, 'Term 1 is indexed.');
$index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
':nid' => $node->id(),
':tid' => $term_2->id(),
))->fetchField();
$this->assertEqual(1, $index_count, 'Term 2 is indexed.');
// Update the article to change another term.
$edit["{$this->fieldName2}[]"] = $term_2->id();
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
// Check that only one term is indexed.
$index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
':nid' => $node->id(),
':tid' => $term_1->id(),
))->fetchField();
$this->assertEqual(0, $index_count, 'Term 1 is not indexed.');
$index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
':nid' => $node->id(),
':tid' => $term_2->id(),
))->fetchField();
$this->assertEqual(1, $index_count, 'Term 2 is indexed once.');
// Redo the above tests without interface.
$node_storage->resetCache(array($node->id()));
$node = $node_storage->load($node->id());
$node->title = $this->randomMachineName();
// Update the article with no term changed.
$node->save();
// Check that the index was not changed.
$index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
':nid' => $node->id(),
':tid' => $term_1->id(),
))->fetchField();
$this->assertEqual(0, $index_count, 'Term 1 is not indexed.');
$index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
':nid' => $node->id(),
':tid' => $term_2->id(),
))->fetchField();
$this->assertEqual(1, $index_count, 'Term 2 is indexed once.');
// Update the article to change one term.
$node->{$this->fieldName1} = array(array('target_id' => $term_1->id()));
$node->save();
// Check that both terms are indexed.
$index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
':nid' => $node->id(),
':tid' => $term_1->id(),
))->fetchField();
$this->assertEqual(1, $index_count, 'Term 1 is indexed.');
$index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
':nid' => $node->id(),
':tid' => $term_2->id(),
))->fetchField();
$this->assertEqual(1, $index_count, 'Term 2 is indexed.');
// Update the article to change another term.
$node->{$this->fieldName2} = array(array('target_id' => $term_1->id()));
$node->save();
// Check that only one term is indexed.
$index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
':nid' => $node->id(),
':tid' => $term_1->id(),
))->fetchField();
$this->assertEqual(1, $index_count, 'Term 1 is indexed once.');
$index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
':nid' => $node->id(),
':tid' => $term_2->id(),
))->fetchField();
$this->assertEqual(0, $index_count, 'Term 2 is not indexed.');
}
/**
* Tests that there is a link to the parent term on the child term page.
*/
function testTaxonomyTermHierarchyBreadcrumbs() {
// Create two taxonomy terms and set term2 as the parent of term1.
$term1 = $this->createTerm($this->vocabulary);
$term2 = $this->createTerm($this->vocabulary);
$term1->parent = array($term2->id());
$term1->save();
// Verify that the page breadcrumbs include a link to the parent term.
$this->drupalGet('taxonomy/term/' . $term1->id());
// Breadcrumbs are not rendered with a language, prevent the term
// language from being added to the options.
$this->assertRaw(\Drupal::l($term2->getName(), $term2->urlInfo('canonical', ['language' => NULL])), 'Parent term link is displayed when viewing the node.');
}
}

View file

@ -0,0 +1,110 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the language functionality for the taxonomy terms.
*
* @group taxonomy
*/
class TermLanguageTest extends TaxonomyTestBase {
public static $modules = array('language');
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
protected function setUp() {
parent::setUp();
// Create an administrative user.
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy']));
// Create a vocabulary to which the terms will be assigned.
$this->vocabulary = $this->createVocabulary();
// Add some custom languages.
foreach (array('aa', 'bb', 'cc') as $language_code) {
ConfigurableLanguage::create(array(
'id' => $language_code,
'label' => $this->randomMachineName(),
))->save();
}
}
function testTermLanguage() {
// Configure the vocabulary to not hide the language selector.
$edit = array(
'default_language[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id(), $edit, t('Save'));
// Add a term.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
// Check that we have the language selector.
$this->assertField('edit-langcode-0-value', t('The language selector field was found on the page.'));
// Submit the term.
$edit = array(
'name[0][value]' => $this->randomMachineName(),
'langcode[0][value]' => 'aa',
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$terms = taxonomy_term_load_multiple_by_name($edit['name[0][value]']);
$term = reset($terms);
$this->assertEqual($term->language()->getId(), $edit['langcode[0][value]'], 'The term contains the correct langcode.');
// Check if on the edit page the language is correct.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->assertOptionSelected('edit-langcode-0-value', $edit['langcode[0][value]'], 'The term language was correctly selected.');
// Change the language of the term.
$edit['langcode[0][value]'] = 'bb';
$this->drupalPostForm('taxonomy/term/' . $term->id() . '/edit', $edit, t('Save'));
// Check again that on the edit page the language is correct.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->assertOptionSelected('edit-langcode-0-value', $edit['langcode[0][value]'], 'The term language was correctly selected.');
}
function testDefaultTermLanguage() {
// Configure the vocabulary to not hide the language selector, and make the
// default language of the terms fixed.
$edit = array(
'default_language[langcode]' => 'bb',
'default_language[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id(), $edit, t('Save'));
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->assertOptionSelected('edit-langcode-0-value', 'bb', 'The expected langcode was selected.');
// Make the default language of the terms to be the current interface.
$edit = array(
'default_language[langcode]' => 'current_interface',
'default_language[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id(), $edit, t('Save'));
$this->drupalGet('aa/admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->assertOptionSelected('edit-langcode-0-value', 'aa', "The expected langcode, 'aa', was selected.");
$this->drupalGet('bb/admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->assertOptionSelected('edit-langcode-0-value', 'bb', "The expected langcode, 'bb', was selected.");
// Change the default language of the site and check if the default terms
// language is still correctly selected.
$this->config('system.site')->set('default_langcode', 'cc')->save();
$edit = array(
'default_language[langcode]' => LanguageInterface::LANGCODE_SITE_DEFAULT,
'default_language[language_alterable]' => TRUE,
);
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id(), $edit, t('Save'));
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->assertOptionSelected('edit-langcode-0-value', 'cc', "The expected langcode, 'cc', was selected.");
}
}

View file

@ -0,0 +1,588 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\Component\Utility\Tags;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests load, save and delete for taxonomy terms.
*
* @group taxonomy
*/
class TermTest extends TaxonomyTestBase {
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Taxonomy term reference field for testing.
*
* @var \Drupal\field\FieldConfigInterface
*/
protected $field;
/**
* Modules to enable.
*
* @var string[]
*/
public static $modules = ['block'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'bypass node access']));
$this->vocabulary = $this->createVocabulary();
$field_name = 'taxonomy_' . $this->vocabulary->id();
$handler_settings = array(
'target_bundles' => array(
$this->vocabulary->id() => $this->vocabulary->id(),
),
'auto_create' => TRUE,
);
$this->createEntityReferenceField('node', 'article', $field_name, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$this->field = FieldConfig::loadByName('node', 'article', $field_name);
entity_get_form_display('node', 'article', 'default')
->setComponent($field_name, array(
'type' => 'options_select',
))
->save();
entity_get_display('node', 'article', 'default')
->setComponent($field_name, array(
'type' => 'entity_reference_label',
))
->save();
}
/**
* Test terms in a single and multiple hierarchy.
*/
function testTaxonomyTermHierarchy() {
// Create two taxonomy terms.
$term1 = $this->createTerm($this->vocabulary);
$term2 = $this->createTerm($this->vocabulary);
// Get the taxonomy storage.
$taxonomy_storage = $this->container->get('entity.manager')->getStorage('taxonomy_term');
// Check that hierarchy is flat.
$vocabulary = Vocabulary::load($this->vocabulary->id());
$this->assertEqual(0, $vocabulary->getHierarchy(), 'Vocabulary is flat.');
// Edit $term2, setting $term1 as parent.
$edit = array();
$edit['parent[]'] = array($term1->id());
$this->drupalPostForm('taxonomy/term/' . $term2->id() . '/edit', $edit, t('Save'));
// Check the hierarchy.
$children = $taxonomy_storage->loadChildren($term1->id());
$parents = $taxonomy_storage->loadParents($term2->id());
$this->assertTrue(isset($children[$term2->id()]), 'Child found correctly.');
$this->assertTrue(isset($parents[$term1->id()]), 'Parent found correctly.');
// Load and save a term, confirming that parents are still set.
$term = Term::load($term2->id());
$term->save();
$parents = $taxonomy_storage->loadParents($term2->id());
$this->assertTrue(isset($parents[$term1->id()]), 'Parent found correctly.');
// Create a third term and save this as a parent of term2.
$term3 = $this->createTerm($this->vocabulary);
$term2->parent = array($term1->id(), $term3->id());
$term2->save();
$parents = $taxonomy_storage->loadParents($term2->id());
$this->assertTrue(isset($parents[$term1->id()]) && isset($parents[$term3->id()]), 'Both parents found successfully.');
}
/**
* Tests that many terms with parents show on each page
*/
function testTaxonomyTermChildTerms() {
// Set limit to 10 terms per page. Set variable to 9 so 10 terms appear.
$this->config('taxonomy.settings')->set('terms_per_page_admin', '9')->save();
$term1 = $this->createTerm($this->vocabulary);
$terms_array = [];
$taxonomy_storage = $this->container->get('entity.manager')->getStorage('taxonomy_term');
// Create 40 terms. Terms 1-12 get parent of $term1. All others are
// individual terms.
for ($x = 1; $x <= 40; $x++) {
$edit = array();
// Set terms in order so we know which terms will be on which pages.
$edit['weight'] = $x;
// Set terms 1-20 to be children of first term created.
if ($x <= 12) {
$edit['parent'] = $term1->id();
}
$term = $this->createTerm($this->vocabulary, $edit);
$children = $taxonomy_storage->loadChildren($term1->id());
$parents = $taxonomy_storage->loadParents($term->id());
$terms_array[$x] = Term::load($term->id());
}
// Get Page 1.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertText($term1->getName(), 'Parent Term is displayed on Page 1');
for ($x = 1; $x <= 13; $x++) {
$this->assertText($terms_array[$x]->getName(), $terms_array[$x]->getName() . ' found on Page 1');
}
// Get Page 2.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview', array('query' => array('page' => 1)));
$this->assertText($term1->getName(), 'Parent Term is displayed on Page 2');
for ($x = 1; $x <= 18; $x++) {
$this->assertText($terms_array[$x]->getName(), $terms_array[$x]->getName() . ' found on Page 2');
}
// Get Page 3.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview', array('query' => array('page' => 2)));
$this->assertNoText($term1->getName(), 'Parent Term is not displayed on Page 3');
for ($x = 1; $x <= 17; $x++) {
$this->assertNoText($terms_array[$x]->getName(), $terms_array[$x]->getName() . ' not found on Page 3');
}
for ($x = 18; $x <= 25; $x++) {
$this->assertText($terms_array[$x]->getName(), $terms_array[$x]->getName() . ' found on Page 3');
}
}
/**
* Test that hook_node_$op implementations work correctly.
*
* Save & edit a node and assert that taxonomy terms are saved/loaded properly.
*/
function testTaxonomyNode() {
// Create two taxonomy terms.
$term1 = $this->createTerm($this->vocabulary);
$term2 = $this->createTerm($this->vocabulary);
// Post an article.
$edit = array();
$edit['title[0][value]'] = $this->randomMachineName();
$edit['body[0][value]'] = $this->randomMachineName();
$edit[$this->field->getName() . '[]'] = $term1->id();
$this->drupalPostForm('node/add/article', $edit, t('Save'));
// Check that the term is displayed when the node is viewed.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->drupalGet('node/' . $node->id());
$this->assertText($term1->getName(), 'Term is displayed when viewing the node.');
$this->clickLink(t('Edit'));
$this->assertText($term1->getName(), 'Term is displayed when editing the node.');
$this->drupalPostForm(NULL, array(), t('Save'));
$this->assertText($term1->getName(), 'Term is displayed after saving the node with no changes.');
// Edit the node with a different term.
$edit[$this->field->getName() . '[]'] = $term2->id();
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
$this->drupalGet('node/' . $node->id());
$this->assertText($term2->getName(), 'Term is displayed when viewing the node.');
// Preview the node.
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Preview'));
$this->assertUniqueText($term2->getName(), 'Term is displayed when previewing the node.');
$this->drupalPostForm('node/' . $node->id() . '/edit', NULL, t('Preview'));
$this->assertUniqueText($term2->getName(), 'Term is displayed when previewing the node again.');
}
/**
* Test term creation with a free-tagging vocabulary from the node form.
*/
function testNodeTermCreationAndDeletion() {
// Enable tags in the vocabulary.
$field = $this->field;
entity_get_form_display($field->getTargetEntityTypeId(), $field->getTargetBundle(), 'default')
->setComponent($field->getName(), array(
'type' => 'entity_reference_autocomplete_tags',
'settings' => array(
'placeholder' => 'Start typing here.',
),
))
->save();
// Prefix the terms with a letter to ensure there is no clash in the first
// three letters.
// @see https://www.drupal.org/node/2397691
$terms = array(
'term1' => 'a' . $this->randomMachineName(),
'term2' => 'b' . $this->randomMachineName(),
'term3' => 'c' . $this->randomMachineName() . ', ' . $this->randomMachineName(),
'term4' => 'd' . $this->randomMachineName(),
);
$edit = array();
$edit['title[0][value]'] = $this->randomMachineName();
$edit['body[0][value]'] = $this->randomMachineName();
// Insert the terms in a comma separated list. Vocabulary 1 is a
// free-tagging field created by the default profile.
$edit[$field->getName() . '[target_id]'] = Tags::implode($terms);
// Verify the placeholder is there.
$this->drupalGet('node/add/article');
$this->assertRaw('placeholder="Start typing here."', 'Placeholder is present.');
// Preview and verify the terms appear but are not created.
$this->drupalPostForm(NULL, $edit, t('Preview'));
foreach ($terms as $term) {
$this->assertText($term, 'The term appears on the node preview.');
}
$tree = $this->container->get('entity.manager')->getStorage('taxonomy_term')->loadTree($this->vocabulary->id());
$this->assertTrue(empty($tree), 'The terms are not created on preview.');
// taxonomy.module does not maintain its static caches.
taxonomy_terms_static_reset();
// Save, creating the terms.
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$this->assertText(t('@type @title has been created.', array('@type' => t('Article'), '@title' => $edit['title[0][value]'])), 'The node was created successfully.');
// Verify that the creation message contains a link to a node.
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', array(':href' => 'node/'));
$this->assert(isset($view_link), 'The message area contains a link to a node');
foreach ($terms as $term) {
$this->assertText($term, 'The term was saved and appears on the node page.');
}
// Get the created terms.
$term_objects = array();
foreach ($terms as $key => $term) {
$term_objects[$key] = taxonomy_term_load_multiple_by_name($term);
$term_objects[$key] = reset($term_objects[$key]);
}
// Get the node.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
// Test editing the node.
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
foreach ($terms as $term) {
$this->assertText($term, 'The term was retained after edit and still appears on the node page.');
}
// Delete term 1 from the term edit page.
$this->drupalGet('taxonomy/term/' . $term_objects['term1']->id() . '/edit');
$this->clickLink(t('Delete'));
$this->drupalPostForm(NULL, NULL, t('Delete'));
// Delete term 2 from the term delete page.
$this->drupalGet('taxonomy/term/' . $term_objects['term2']->id() . '/delete');
$this->drupalPostForm(NULL, array(), t('Delete'));
$term_names = array($term_objects['term3']->getName(), $term_objects['term4']->getName());
$this->drupalGet('node/' . $node->id());
foreach ($term_names as $term_name) {
$this->assertText($term_name, format_string('The term %name appears on the node page after two terms, %deleted1 and %deleted2, were deleted.', array('%name' => $term_name, '%deleted1' => $term_objects['term1']->getName(), '%deleted2' => $term_objects['term2']->getName())));
}
$this->assertNoText($term_objects['term1']->getName(), format_string('The deleted term %name does not appear on the node page.', array('%name' => $term_objects['term1']->getName())));
$this->assertNoText($term_objects['term2']->getName(), format_string('The deleted term %name does not appear on the node page.', array('%name' => $term_objects['term2']->getName())));
}
/**
* Save, edit and delete a term using the user interface.
*/
function testTermInterface() {
\Drupal::service('module_installer')->install(array('views'));
$edit = array(
'name[0][value]' => $this->randomMachineName(12),
'description[0][value]' => $this->randomMachineName(100),
);
// Explicitly set the parents field to 'root', to ensure that
// TermForm::save() handles the invalid term ID correctly.
$edit['parent[]'] = array(0);
// Create the term to edit.
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add', $edit, t('Save'));
$terms = taxonomy_term_load_multiple_by_name($edit['name[0][value]']);
$term = reset($terms);
$this->assertNotNull($term, 'Term found in database.');
// Submitting a term takes us to the add page; we need the List page.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->clickLink(t('Edit'));
$this->assertRaw($edit['name[0][value]'], 'The randomly generated term name is present.');
$this->assertText($edit['description[0][value]'], 'The randomly generated term description is present.');
$edit = array(
'name[0][value]' => $this->randomMachineName(14),
'description[0][value]' => $this->randomMachineName(102),
);
// Edit the term.
$this->drupalPostForm('taxonomy/term/' . $term->id() . '/edit', $edit, t('Save'));
// Check that the term is still present at admin UI after edit.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertText($edit['name[0][value]'], 'The randomly generated term name is present.');
$this->assertLink(t('Edit'));
// Check the term link can be clicked through to the term page.
$this->clickLink($edit['name[0][value]']);
$this->assertResponse(200, 'Term page can be accessed via the listing link.');
// View the term and check that it is correct.
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertText($edit['name[0][value]'], 'The randomly generated term name is present.');
$this->assertText($edit['description[0][value]'], 'The randomly generated term description is present.');
// Did this page request display a 'term-listing-heading'?
$this->assertTrue($this->xpath('//div[contains(@class, "field--name-description")]'), 'Term page displayed the term description element.');
// Check that it does NOT show a description when description is blank.
$term->setDescription(NULL);
$term->save();
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertFalse($this->xpath('//div[contains(@class, "field--entity-taxonomy-term--description")]'), 'Term page did not display the term description when description was blank.');
// Check that the description value is processed.
$value = $this->randomMachineName();
$term->setDescription($value);
$term->save();
$this->assertEqual($term->description->processed, "<p>$value</p>\n");
// Check that the term feed page is working.
$this->drupalGet('taxonomy/term/' . $term->id() . '/feed');
// Delete the term.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->clickLink(t('Delete'));
$this->drupalPostForm(NULL, NULL, t('Delete'));
// Assert that the term no longer exists.
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertResponse(404, 'The taxonomy term page was not found.');
}
/**
* Save, edit and delete a term using the user interface.
*/
function testTermReorder() {
$this->createTerm($this->vocabulary);
$this->createTerm($this->vocabulary);
$this->createTerm($this->vocabulary);
$taxonomy_storage = $this->container->get('entity.manager')->getStorage('taxonomy_term');
// Fetch the created terms in the default alphabetical order, i.e. term1
// precedes term2 alphabetically, and term2 precedes term3.
$taxonomy_storage->resetCache();
list($term1, $term2, $term3) = $taxonomy_storage->loadTree($this->vocabulary->id(), 0, NULL, TRUE);
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
// Each term has four hidden fields, "tid:1:0[tid]", "tid:1:0[parent]",
// "tid:1:0[depth]", and "tid:1:0[weight]". Change the order to term2,
// term3, term1 by setting weight property, make term3 a child of term2 by
// setting the parent and depth properties, and update all hidden fields.
$edit = array(
'terms[tid:' . $term2->id() . ':0][term][tid]' => $term2->id(),
'terms[tid:' . $term2->id() . ':0][term][parent]' => 0,
'terms[tid:' . $term2->id() . ':0][term][depth]' => 0,
'terms[tid:' . $term2->id() . ':0][weight]' => 0,
'terms[tid:' . $term3->id() . ':0][term][tid]' => $term3->id(),
'terms[tid:' . $term3->id() . ':0][term][parent]' => $term2->id(),
'terms[tid:' . $term3->id() . ':0][term][depth]' => 1,
'terms[tid:' . $term3->id() . ':0][weight]' => 1,
'terms[tid:' . $term1->id() . ':0][term][tid]' => $term1->id(),
'terms[tid:' . $term1->id() . ':0][term][parent]' => 0,
'terms[tid:' . $term1->id() . ':0][term][depth]' => 0,
'terms[tid:' . $term1->id() . ':0][weight]' => 2,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$taxonomy_storage->resetCache();
$terms = $taxonomy_storage->loadTree($this->vocabulary->id());
$this->assertEqual($terms[0]->tid, $term2->id(), 'Term 2 was moved above term 1.');
$this->assertEqual($terms[1]->parents, array($term2->id()), 'Term 3 was made a child of term 2.');
$this->assertEqual($terms[2]->tid, $term1->id(), 'Term 1 was moved below term 2.');
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview', array(), t('Reset to alphabetical'));
// Submit confirmation form.
$this->drupalPostForm(NULL, array(), t('Reset to alphabetical'));
// Ensure form redirected back to overview.
$this->assertUrl('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$taxonomy_storage->resetCache();
$terms = $taxonomy_storage->loadTree($this->vocabulary->id(), 0, NULL, TRUE);
$this->assertEqual($terms[0]->id(), $term1->id(), 'Term 1 was moved to back above term 2.');
$this->assertEqual($terms[1]->id(), $term2->id(), 'Term 2 was moved to back below term 1.');
$this->assertEqual($terms[2]->id(), $term3->id(), 'Term 3 is still below term 2.');
$this->assertEqual($terms[2]->parents, array($term2->id()), 'Term 3 is still a child of term 2.');
}
/**
* Test saving a term with multiple parents through the UI.
*/
function testTermMultipleParentsInterface() {
// Add a new term to the vocabulary so that we can have multiple parents.
$parent = $this->createTerm($this->vocabulary);
// Add a new term with multiple parents.
$edit = array(
'name[0][value]' => $this->randomMachineName(12),
'description[0][value]' => $this->randomMachineName(100),
'parent[]' => array(0, $parent->id()),
);
// Save the new term.
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add', $edit, t('Save'));
// Check that the term was successfully created.
$terms = taxonomy_term_load_multiple_by_name($edit['name[0][value]']);
$term = reset($terms);
$this->assertNotNull($term, 'Term found in database.');
$this->assertEqual($edit['name[0][value]'], $term->getName(), 'Term name was successfully saved.');
$this->assertEqual($edit['description[0][value]'], $term->getDescription(), 'Term description was successfully saved.');
// Check that the parent tid is still there. The other parent (<root>) is
// not added by \Drupal\taxonomy\TermStorageInterface::loadParents().
$parents = $this->container->get('entity.manager')->getStorage('taxonomy_term')->loadParents($term->id());
$parent = reset($parents);
$this->assertEqual($edit['parent[]'][1], $parent->id(), 'Term parents were successfully saved.');
}
/**
* Test taxonomy_term_load_multiple_by_name().
*/
function testTaxonomyGetTermByName() {
$term = $this->createTerm($this->vocabulary);
// Load the term with the exact name.
$terms = taxonomy_term_load_multiple_by_name($term->getName());
$this->assertTrue(isset($terms[$term->id()]), 'Term loaded using exact name.');
// Load the term with space concatenated.
$terms = taxonomy_term_load_multiple_by_name(' ' . $term->getName() . ' ');
$this->assertTrue(isset($terms[$term->id()]), 'Term loaded with extra whitespace.');
// Load the term with name uppercased.
$terms = taxonomy_term_load_multiple_by_name(strtoupper($term->getName()));
$this->assertTrue(isset($terms[$term->id()]), 'Term loaded with uppercased name.');
// Load the term with name lowercased.
$terms = taxonomy_term_load_multiple_by_name(strtolower($term->getName()));
$this->assertTrue(isset($terms[$term->id()]), 'Term loaded with lowercased name.');
// Try to load an invalid term name.
$terms = taxonomy_term_load_multiple_by_name('Banana');
$this->assertFalse($terms, 'No term loaded with an invalid name.');
// Try to load the term using a substring of the name.
$terms = taxonomy_term_load_multiple_by_name(Unicode::substr($term->getName(), 2), 'No term loaded with a substring of the name.');
$this->assertFalse($terms);
// Create a new term in a different vocabulary with the same name.
$new_vocabulary = $this->createVocabulary();
$new_term = Term::create([
'name' => $term->getName(),
'vid' => $new_vocabulary->id(),
]);
$new_term->save();
// Load multiple terms with the same name.
$terms = taxonomy_term_load_multiple_by_name($term->getName());
$this->assertEqual(count($terms), 2, 'Two terms loaded with the same name.');
// Load single term when restricted to one vocabulary.
$terms = taxonomy_term_load_multiple_by_name($term->getName(), $this->vocabulary->id());
$this->assertEqual(count($terms), 1, 'One term loaded when restricted by vocabulary.');
$this->assertTrue(isset($terms[$term->id()]), 'Term loaded using exact name and vocabulary machine name.');
// Create a new term with another name.
$term2 = $this->createTerm($this->vocabulary);
// Try to load a term by name that doesn't exist in this vocabulary but
// exists in another vocabulary.
$terms = taxonomy_term_load_multiple_by_name($term2->getName(), $new_vocabulary->id());
$this->assertFalse($terms, 'Invalid term name restricted by vocabulary machine name not loaded.');
// Try to load terms filtering by a non-existing vocabulary.
$terms = taxonomy_term_load_multiple_by_name($term2->getName(), 'non_existing_vocabulary');
$this->assertEqual(count($terms), 0, 'No terms loaded when restricted by a non-existing vocabulary.');
}
/**
* Tests that editing and saving a node with no changes works correctly.
*/
function testReSavingTags() {
// Enable tags in the vocabulary.
$field = $this->field;
entity_get_form_display($field->getTargetEntityTypeId(), $field->getTargetBundle(), 'default')
->setComponent($field->getName(), array(
'type' => 'entity_reference_autocomplete_tags',
))
->save();
// Create a term and a node using it.
$term = $this->createTerm($this->vocabulary);
$edit = array();
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = $this->randomMachineName(16);
$edit[$this->field->getName() . '[target_id]'] = $term->getName();
$this->drupalPostForm('node/add/article', $edit, t('Save'));
// Check that the term is displayed when editing and saving the node with no
// changes.
$this->clickLink(t('Edit'));
$this->assertRaw($term->getName(), 'Term is displayed when editing the node.');
$this->drupalPostForm(NULL, array(), t('Save'));
$this->assertRaw($term->getName(), 'Term is displayed after saving the node with no changes.');
}
/**
* Check the breadcrumb on edit and delete a term page.
*/
public function testTermBreadcrumbs() {
$edit = [
'name[0][value]' => $this->randomMachineName(14),
'description[0][value]' => $this->randomMachineName(100),
'parent[]' => [0],
];
// Create the term.
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add', $edit, t('Save'));
$terms = taxonomy_term_load_multiple_by_name($edit['name[0][value]']);
$term = reset($terms);
$this->assertNotNull($term, 'Term found in database.');
// Check the breadcrumb on the term edit page.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$breadcrumbs = $this->cssSelect('nav.breadcrumb ol li a');
$this->assertIdentical(count($breadcrumbs), 2, 'The breadcrumbs are present on the page.');
$this->assertIdentical((string) $breadcrumbs[0], 'Home', 'First breadcrumb text is Home');
$this->assertIdentical((string) $breadcrumbs[1], $term->label(), 'Second breadcrumb text is term name on term edit page.');
$this->assertEscaped((string) $breadcrumbs[1], 'breadcrumbs displayed and escaped.');
// Check the breadcrumb on the term delete page.
$this->drupalGet('taxonomy/term/' . $term->id() . '/delete');
$breadcrumbs = $this->cssSelect('nav.breadcrumb ol li a');
$this->assertIdentical(count($breadcrumbs), 2, 'The breadcrumbs are present on the page.');
$this->assertIdentical((string) $breadcrumbs[0], 'Home', 'First breadcrumb text is Home');
$this->assertIdentical((string) $breadcrumbs[1], $term->label(), 'Second breadcrumb text is term name on term delete page.');
$this->assertEscaped((string) $breadcrumbs[1], 'breadcrumbs displayed and escaped.');
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\node\Entity\Node;
/**
* Tests the translation of taxonomy terms field on nodes.
*
* @group taxonomy
*/
class TermTranslationFieldViewTest extends TaxonomyTestBase {
use TaxonomyTranslationTestTrait;
/**
* The term that should be translated.
*
* @var \Drupal\taxonomy\Entity\Term
*/
protected $term;
/**
* The tag in the source language.
*
* @var string
*/
protected $baseTagName = 'OriginalTagName';
/**
* The translated value for the tag.
*
* @var string
*/
protected $translatedTagName = 'TranslatedTagName';
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'content_translation', 'taxonomy');
protected function setUp() {
parent::setUp();
$this->setupLanguages();
$this->vocabulary = $this->createVocabulary();
$this->enableTranslation();
$this->setUpTerm();
$this->setUpTermReferenceField();
$this->setUpNode();
}
/**
* Tests if the translated taxonomy term is displayed.
*/
public function testTranslatedTaxonomyTermReferenceDisplay() {
$path = 'node/' . $this->node->id();
$translation_path = $this->translateToLangcode . '/' . $path;
$this->drupalGet($path);
$this->assertNoText($this->translatedTagName);
$this->assertText($this->baseTagName);
$this->drupalGet($translation_path);
$this->assertText($this->translatedTagName);
$this->assertNoText($this->baseTagName);
}
/**
* Creates a test subject node, with translation.
*/
protected function setUpNode() {
/** @var \Drupal\node\Entity\Node $node */
$node = Node::create([
'title' => $this->randomMachineName(),
'type' => 'article',
'description' => [[
'value' => $this->randomMachineName(),
'format' => 'basic_html'
]],
$this->termFieldName => array(array('target_id' => $this->term->id())),
'langcode' => $this->baseLangcode,
]);
$node->save();
$node->addTranslation($this->translateToLangcode, $node->toArray());
$node->save();
$this->node = $node;
}
/**
* Creates a test subject term, with translation.
*/
protected function setUpTerm() {
$this->term = $this->createTerm($this->vocabulary, array(
'name' => $this->baseTagName,
'langcode' => $this->baseLangcode,
));
$this->term->addTranslation($this->translateToLangcode, array(
'name' => $this->translatedTagName,
));
$this->term->save();
}
}

View file

@ -0,0 +1,149 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\Core\Url;
use Drupal\system\Tests\Menu\AssertBreadcrumbTrait;
/**
* Tests for proper breadcrumb translation.
*
* @group taxonomy
*/
class TermTranslationTest extends TaxonomyTestBase {
use AssertBreadcrumbTrait;
use TaxonomyTranslationTestTrait;
/**
* Term to translated term mapping.
*
* @var array
*/
protected $termTranslationMap = array(
'one' => 'translatedOne',
'two' => 'translatedTwo',
'three' => 'translatedThree',
);
/**
* Created terms.
*
* @var \Drupal\taxonomy\Entity\Term[]
*/
protected $terms = array();
/**
* {@inheritdoc}
*/
public static $modules = array('taxonomy', 'language', 'content_translation');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->setupLanguages();
$this->vocabulary = $this->createVocabulary();
$this->enableTranslation();
$this->setUpTerms();
$this->setUpTermReferenceField();
}
/**
* Test translated breadcrumbs.
*/
public function testTranslatedBreadcrumbs() {
// Ensure non-translated breadcrumb is correct.
$breadcrumb = array(Url::fromRoute('<front>')->toString() => 'Home');
foreach ($this->terms as $term) {
$breadcrumb[$term->url()] = $term->label();
}
// The last item will not be in the breadcrumb.
array_pop($breadcrumb);
// Check the breadcrumb on the leaf term page.
$term = $this->getLeafTerm();
$this->assertBreadcrumb($term->urlInfo(), $breadcrumb, $term->label());
$languages = \Drupal::languageManager()->getLanguages();
// Construct the expected translated breadcrumb.
$breadcrumb = array(Url::fromRoute('<front>', [], ['language' => $languages[$this->translateToLangcode]])->toString() => 'Home');
foreach ($this->terms as $term) {
$translated = $term->getTranslation($this->translateToLangcode);
$url = $translated->url('canonical', ['language' => $languages[$this->translateToLangcode]]);
$breadcrumb[$url] = $translated->label();
}
array_pop($breadcrumb);
// Check for the translated breadcrumb on the translated leaf term page.
$term = $this->getLeafTerm();
$translated = $term->getTranslation($this->translateToLangcode);
$this->assertBreadcrumb($translated->urlInfo('canonical', ['language' => $languages[$this->translateToLangcode]]), $breadcrumb, $translated->label());
}
/**
* Test translation of terms are showed in the node.
*/
protected function testTermsTranslation() {
// Set the display of the term reference field on the article content type
// to "Check boxes/radio buttons".
entity_get_form_display('node', 'article', 'default')
->setComponent($this->termFieldName, array(
'type' => 'options_buttons',
))
->save();
$this->drupalLogin($this->drupalCreateUser(['create article content']));
// Test terms are listed.
$this->drupalget('node/add/article');
$this->assertText('one');
$this->assertText('two');
$this->assertText('three');
// Test terms translated are listed.
$this->drupalget('hu/node/add/article');
$this->assertText('translatedOne');
$this->assertText('translatedTwo');
$this->assertText('translatedThree');
}
/**
* Setup translated terms in a hierarchy.
*/
protected function setUpTerms() {
$parent_vid = 0;
foreach ($this->termTranslationMap as $name => $translation) {
$term = $this->createTerm($this->vocabulary, array(
'name' => $name,
'langcode' => $this->baseLangcode,
'parent' => $parent_vid,
));
$term->addTranslation($this->translateToLangcode, array(
'name' => $translation,
));
$term->save();
// Each term is nested under the last.
$parent_vid = $term->id();
$this->terms[] = $term;
}
}
/**
* Get the final (leaf) term in the hierarchy.
*
* @return \Drupal\taxonomy\Entity\Term
* The final term in the hierarchy.
*/
protected function getLeafTerm() {
return $this->terms[count($this->termTranslationMap) - 1];
}
}

View file

@ -0,0 +1,164 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\content_translation\Tests\ContentTranslationUITestBase;
use Drupal\Core\Language\LanguageInterface;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests the Term Translation UI.
*
* @group taxonomy
*/
class TermTranslationUITest extends ContentTranslationUITestBase {
/**
* The vocabulary used for creating terms.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'content_translation', 'taxonomy');
protected function setUp() {
$this->entityTypeId = 'taxonomy_term';
$this->bundle = 'tags';
parent::setUp();
}
/**
* {@inheritdoc}
*/
protected function setupBundle() {
parent::setupBundle();
// Create a vocabulary.
$this->vocabulary = Vocabulary::create([
'name' => $this->bundle,
'description' => $this->randomMachineName(),
'vid' => $this->bundle,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'weight' => mt_rand(0, 10),
]);
$this->vocabulary->save();
}
/**
* {@inheritdoc}
*/
protected function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), array('administer taxonomy'));
}
/**
* {@inheritdoc}
*/
protected function getNewEntityValues($langcode) {
return array('name' => $this->randomMachineName()) + parent::getNewEntityValues($langcode);
}
/**
* Returns an edit array containing the values to be posted.
*/
protected function getEditValues($values, $langcode, $new = FALSE) {
$edit = parent::getEditValues($values, $langcode, $new);
// To be able to post values for the configurable base fields (name,
// description) have to be suffixed with [0][value].
foreach ($edit as $property => $value) {
foreach (array('name', 'description') as $key) {
if ($property == $key) {
$edit[$key . '[0][value]'] = $value;
unset($edit[$property]);
}
}
}
return $edit;
}
/**
* {@inheritdoc}
*/
public function testTranslationUI() {
parent::testTranslationUI();
// Make sure that no row was inserted for taxonomy vocabularies which do
// not have translations enabled.
$rows = db_query('SELECT tid, count(tid) AS count FROM {taxonomy_term_field_data} WHERE vid <> :vid GROUP BY tid', array(':vid' => $this->bundle))->fetchAll();
foreach ($rows as $row) {
$this->assertTrue($row->count < 2, 'Term does not have translations.');
}
}
/**
* Tests translate link on vocabulary term list.
*/
function testTranslateLinkVocabularyAdminPage() {
$this->drupalLogin($this->drupalCreateUser(array_merge(parent::getTranslatorPermissions(), ['access administration pages', 'administer taxonomy'])));
$values = array(
'name' => $this->randomMachineName(),
);
$translatable_tid = $this->createEntity($values, $this->langcodes[0], $this->vocabulary->id());
// Create an untranslatable vocabulary.
$untranslatable_vocabulary = Vocabulary::create([
'name' => 'untranslatable_voc',
'description' => $this->randomMachineName(),
'vid' => 'untranslatable_voc',
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'weight' => mt_rand(0, 10),
]);
$untranslatable_vocabulary->save();
$values = array(
'name' => $this->randomMachineName(),
);
$untranslatable_tid = $this->createEntity($values, $this->langcodes[0], $untranslatable_vocabulary->id());
// Verify translation links.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertResponse(200, 'The translatable vocabulary page was found.');
$this->assertLinkByHref('term/' . $translatable_tid . '/translations', 0, 'The translations link exists for a translatable vocabulary.');
$this->assertLinkByHref('term/' . $translatable_tid . '/edit', 0, 'The edit link exists for a translatable vocabulary.');
$this->drupalGet('admin/structure/taxonomy/manage/' . $untranslatable_vocabulary->id() . '/overview');
$this->assertResponse(200);
$this->assertLinkByHref('term/' . $untranslatable_tid . '/edit');
$this->assertNoLinkByHref('term/' . $untranslatable_tid . '/translations');
}
/**
* {@inheritdoc}
*/
protected function doTestTranslationEdit() {
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
foreach ($this->langcodes as $langcode) {
// We only want to test the title for non-english translations.
if ($langcode != 'en') {
$options = array('language' => $languages[$langcode]);
$url = $entity->urlInfo('edit-form', $options);
$this->drupalGet($url);
$title = t('@title [%language translation]', array(
'@title' => $entity->getTranslation($langcode)->label(),
'%language' => $languages[$langcode]->getName(),
));
$this->assertRaw($title);
}
}
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Drupal\taxonomy\Tests;
/**
* Verifies that various taxonomy pages use the expected theme.
*
* @group taxonomy
*/
class ThemeTest extends TaxonomyTestBase {
protected function setUp() {
parent::setUp();
// Make sure we are using distinct default and administrative themes for
// the duration of these tests.
\Drupal::service('theme_handler')->install(array('bartik', 'seven'));
$this->config('system.theme')
->set('default', 'bartik')
->set('admin', 'seven')
->save();
// Create and log in as a user who has permission to add and edit taxonomy
// terms and view the administrative theme.
$admin_user = $this->drupalCreateUser(array('administer taxonomy', 'view the administration theme'));
$this->drupalLogin($admin_user);
}
/**
* Test the theme used when adding, viewing and editing taxonomy terms.
*/
function testTaxonomyTermThemes() {
// Adding a term to a vocabulary is considered an administrative action and
// should use the administrative theme.
$vocabulary = $this->createVocabulary();
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add');
$this->assertRaw('seven/css/base/elements.css', t("The administrative theme's CSS appears on the page for adding a taxonomy term."));
// Viewing a taxonomy term should use the default theme.
$term = $this->createTerm($vocabulary);
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertRaw('bartik/css/base/elements.css', t("The default theme's CSS appears on the page for viewing a taxonomy term."));
// Editing a taxonomy term should use the same theme as adding one.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->assertRaw('seven/css/base/elements.css', t("The administrative theme's CSS appears on the page for editing a taxonomy term."));
}
}

View file

@ -0,0 +1,147 @@
<?php
namespace Drupal\taxonomy\Tests;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Render\BubbleableMetadata;
/**
* Generates text using placeholders for dummy content to check taxonomy token
* replacement.
*
* @group taxonomy
*/
class TokenReplaceTest extends TaxonomyTestBase {
/**
* The vocabulary used for creating terms.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Name of the taxonomy term reference field.
*
* @var string
*/
protected $fieldName;
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'bypass node access']));
$this->vocabulary = $this->createVocabulary();
$this->fieldName = 'taxonomy_' . $this->vocabulary->id();
$handler_settings = array(
'target_bundles' => array(
$this->vocabulary->id() => $this->vocabulary->id(),
),
'auto_create' => TRUE,
);
$this->createEntityReferenceField('node', 'article', $this->fieldName, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
entity_get_form_display('node', 'article', 'default')
->setComponent($this->fieldName, array(
'type' => 'options_select',
))
->save();
entity_get_display('node', 'article', 'default')
->setComponent($this->fieldName, array(
'type' => 'entity_reference_label',
))
->save();
}
/**
* Creates some terms and a node, then tests the tokens generated from them.
*/
function testTaxonomyTokenReplacement() {
$token_service = \Drupal::token();
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
// Create two taxonomy terms.
$term1 = $this->createTerm($this->vocabulary);
$term2 = $this->createTerm($this->vocabulary);
// Edit $term2, setting $term1 as parent.
$edit = array();
$edit['name[0][value]'] = '<blink>Blinking Text</blink>';
$edit['parent[]'] = array($term1->id());
$this->drupalPostForm('taxonomy/term/' . $term2->id() . '/edit', $edit, t('Save'));
// Create node with term2.
$edit = array();
$node = $this->drupalCreateNode(array('type' => 'article'));
$edit[$this->fieldName . '[]'] = $term2->id();
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
// Generate and test sanitized tokens for term1.
$tests = array();
$tests['[term:tid]'] = $term1->id();
$tests['[term:name]'] = $term1->getName();
$tests['[term:description]'] = $term1->description->processed;
$tests['[term:url]'] = $term1->url('canonical', array('absolute' => TRUE));
$tests['[term:node-count]'] = 0;
$tests['[term:parent:name]'] = '[term:parent:name]';
$tests['[term:vocabulary:name]'] = $this->vocabulary->label();
$tests['[term:vocabulary]'] = $this->vocabulary->label();
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($term1);
$metadata_tests = array();
$metadata_tests['[term:tid]'] = $base_bubbleable_metadata;
$metadata_tests['[term:name]'] = $base_bubbleable_metadata;
$metadata_tests['[term:description]'] = $base_bubbleable_metadata;
$metadata_tests['[term:url]'] = $base_bubbleable_metadata;
$metadata_tests['[term:node-count]'] = $base_bubbleable_metadata;
$metadata_tests['[term:parent:name]'] = $base_bubbleable_metadata;
$bubbleable_metadata = clone $base_bubbleable_metadata;
$metadata_tests['[term:vocabulary:name]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
$metadata_tests['[term:vocabulary]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
foreach ($tests as $input => $expected) {
$bubbleable_metadata = new BubbleableMetadata();
$output = $token_service->replace($input, array('term' => $term1), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
$this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
}
// Generate and test sanitized tokens for term2.
$tests = array();
$tests['[term:tid]'] = $term2->id();
$tests['[term:name]'] = $term2->getName();
$tests['[term:description]'] = $term2->description->processed;
$tests['[term:url]'] = $term2->url('canonical', array('absolute' => TRUE));
$tests['[term:node-count]'] = 1;
$tests['[term:parent:name]'] = $term1->getName();
$tests['[term:parent:url]'] = $term1->url('canonical', array('absolute' => TRUE));
$tests['[term:parent:parent:name]'] = '[term:parent:parent:name]';
$tests['[term:vocabulary:name]'] = $this->vocabulary->label();
// Test to make sure that we generated something for each token.
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
foreach ($tests as $input => $expected) {
$output = $token_service->replace($input, array('term' => $term2), array('langcode' => $language_interface->getId()));
$this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
}
// Generate and test sanitized tokens.
$tests = array();
$tests['[vocabulary:vid]'] = $this->vocabulary->id();
$tests['[vocabulary:name]'] = $this->vocabulary->label();
$tests['[vocabulary:description]'] = $this->vocabulary->getDescription();
$tests['[vocabulary:node-count]'] = 1;
$tests['[vocabulary:term-count]'] = 2;
// Test to make sure that we generated something for each token.
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
foreach ($tests as $input => $expected) {
$output = $token_service->replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->getId()));
$this->assertEqual($output, $expected, format_string('Sanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
}
}
}

View file

@ -0,0 +1,122 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\views\Views;
/**
* Tests the plugin of the taxonomy: term argument validator.
*
* @group taxonomy
* @see Views\taxonomy\Plugin\views\argument_validator\Term
*/
class ArgumentValidatorTermTest extends TaxonomyTestBase {
/**
* Stores the taxonomy term used by this test.
*
* @var array
*/
protected $terms = [];
/**
* Stores the taxonomy names used by this test.
*
* @var array
*/
protected $names = [];
/**
* Stores the taxonomy IDs used by this test.
*
* @var array
*/
protected $ids = [];
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['taxonomy', 'taxonomy_test_views', 'views_test_config'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_argument_validator_term'];
protected function setUp() {
parent::setUp();
// Add three terms to the 'tags' vocabulary.
for ($i = 0; $i < 3; $i++) {
$this->terms[] = $term = $this->createTerm();
$this->names[] = $term->label();
$this->ids[] = $term->id();
}
}
/**
* Tests the term argument validator plugin.
*/
public function testArgumentValidatorTerm() {
$view = Views::getView('test_argument_validator_term');
$view->initHandlers();
// Test the single validator for term IDs.
$view->argument['tid']->validator->options['type'] = 'tid';
// Pass in a single valid term.
foreach ($this->terms as $term) {
$this->assertTrue($view->argument['tid']->setArgument($term->id()));
$this->assertEqual($view->argument['tid']->getTitle(), $term->label());
$view->argument['tid']->validated_title = NULL;
$view->argument['tid']->argument_validated = NULL;
}
// Pass in a invalid term.
$this->assertFalse($view->argument['tid']->setArgument(rand(1000, 10000)));
$this->assertEqual('', $view->argument['tid']->getTitle());
$view->argument['tid']->validated_title = NULL;
$view->argument['tid']->argument_validated = NULL;
// Test the multiple validator for term IDs.
$view->argument['tid']->validator->options['type'] = 'tids';
$view->argument['tid']->options['break_phrase'] = TRUE;
// Pass in a single term.
$this->assertTrue($view->argument['tid']->setArgument($this->terms[0]->id()));
$this->assertEqual($view->argument['tid']->getTitle(), $this->terms[0]->label());
$view->argument['tid']->validated_title = NULL;
$view->argument['tid']->argument_validated = NULL;
// Check for multiple valid terms separated by commas.
$this->assertTrue($view->argument['tid']->setArgument(implode(',', $this->ids)));
$this->assertEqual($view->argument['tid']->getTitle(), implode(', ', $this->names));
$view->argument['tid']->validated_title = NULL;
$view->argument['tid']->argument_validated = NULL;
// Check for multiple valid terms separated by plus signs.
$this->assertTrue($view->argument['tid']->setArgument(implode('+', $this->ids)));
$this->assertEqual($view->argument['tid']->getTitle(), implode(' + ', $this->names));
$view->argument['tid']->validated_title = NULL;
$view->argument['tid']->argument_validated = NULL;
// Check for a single invalid term.
$this->assertFalse($view->argument['tid']->setArgument(rand(1000, 10000)));
$this->assertEqual('', $view->argument['tid']->getTitle());
$view->argument['tid']->validated_title = NULL;
$view->argument['tid']->argument_validated = NULL;
// Check for multiple invalid terms.
$this->assertFalse($view->argument['tid']->setArgument(implode(',', [rand(1000, 10000), rand(1000, 10000)])));
$this->assertEqual('', $view->argument['tid']->getTitle());
$view->argument['tid']->validated_title = NULL;
$view->argument['tid']->argument_validated = NULL;
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\views\Views;
/**
* Tests the taxonomy term on node relationship handler.
*
* @group taxonomy
*/
class RelationshipNodeTermDataTest extends TaxonomyTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_taxonomy_node_term_data');
function testViewsHandlerRelationshipNodeTermData() {
$view = Views::getView('test_taxonomy_node_term_data');
// Tests \Drupal\taxonomy\Plugin\views\relationship\NodeTermData::calculateDependencies().
$expected = [
'config' => ['core.entity_view_mode.node.teaser'],
'module' => [
'node',
'taxonomy',
'user',
],
];
$this->assertIdentical($expected, $view->getDependencies());
$this->executeView($view, array($this->term1->id(), $this->term2->id()));
$expected_result = array(
array(
'nid' => $this->nodes[1]->id(),
),
array(
'nid' => $this->nodes[0]->id(),
),
);
$column_map = array('nid' => 'nid');
$this->assertIdenticalResultset($view, $expected_result, $column_map);
// Change the view to test relation limited by vocabulary.
$this->config('views.view.test_taxonomy_node_term_data')
->set('display.default.display_options.relationships.term_node_tid.vids', ['views_testing_tags'])
->save();
$view = Views::getView('test_taxonomy_node_term_data');
// Tests \Drupal\taxonomy\Plugin\views\relationship\NodeTermData::calculateDependencies().
$expected['config'][] = 'taxonomy.vocabulary.views_testing_tags';
$this->assertIdentical($expected, $view->getDependencies());
$this->executeView($view, array($this->term1->id(), $this->term2->id()));
$this->assertIdenticalResultset($view, $expected_result, $column_map);
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\views\Views;
/**
* Tests the representative node relationship for terms.
*
* @group taxonomy
*/
class RelationshipRepresentativeNodeTest extends TaxonomyTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_groupwise_term');
/**
* Tests the relationship.
*/
public function testRelationship() {
$view = Views::getView('test_groupwise_term');
$this->executeView($view);
$map = array('node_field_data_taxonomy_term_field_data_nid' => 'nid', 'tid' => 'tid');
$expected_result = array(
array(
'nid' => $this->nodes[1]->id(),
'tid' => $this->term2->id(),
),
array(
'nid' => $this->nodes[1]->id(),
'tid' => $this->term1->id(),
),
);
$this->assertIdenticalResultset($view, $expected_result, $map);
}
}

View file

@ -0,0 +1,100 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\field\Entity\FieldConfig;
use Drupal\views\Views;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Tests the representative node relationship for terms.
*
* @group taxonomy
*/
class TaxonomyDefaultArgumentTest extends TaxonomyTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('taxonomy_default_argument_test');
/**
* Tests the relationship.
*/
public function testNodePath() {
$view = Views::getView('taxonomy_default_argument_test');
$request = Request::create($this->nodes[0]->url());
$request->server->set('SCRIPT_NAME', $GLOBALS['base_path'] . 'index.php');
$request->server->set('SCRIPT_FILENAME', 'index.php');
$response = $this->container->get('http_kernel')
->handle($request, HttpKernelInterface::SUB_REQUEST);
$view->setRequest($request);
$view->setResponse($response);
$view->initHandlers();
$expected = implode(',', array($this->term1->id(), $this->term2->id()));
$this->assertEqual($expected, $view->argument['tid']->getDefaultArgument());
$view->destroy();
}
public function testNodePathWithViewSelection() {
// Change the term entity reference field to use a view as selection plugin.
\Drupal::service('module_installer')->install(['entity_reference_test']);
$field_name = 'field_' . $this->vocabulary->id();
$field = FieldConfig::loadByName('node', 'article', $field_name);
$field->setSetting('handler', 'views');
$field->setSetting('handler_settings', [
'view' => [
'view_name' => 'test_entity_reference',
'display_name' => 'entity_reference_1',
],
]);
$field->save();
$view = Views::getView('taxonomy_default_argument_test');
$request = Request::create($this->nodes[0]->url());
$request->server->set('SCRIPT_NAME', $GLOBALS['base_path'] . 'index.php');
$request->server->set('SCRIPT_FILENAME', 'index.php');
$response = $this->container->get('http_kernel')->handle($request, HttpKernelInterface::SUB_REQUEST);
$view->setRequest($request);
$view->setResponse($response);
$view->initHandlers();
$expected = implode(',', array($this->term1->id(), $this->term2->id()));
$this->assertEqual($expected, $view->argument['tid']->getDefaultArgument());
}
public function testTermPath() {
$view = Views::getView('taxonomy_default_argument_test');
$request = Request::create($this->term1->url());
$request->server->set('SCRIPT_NAME', $GLOBALS['base_path'] . 'index.php');
$request->server->set('SCRIPT_FILENAME', 'index.php');
$response = $this->container->get('http_kernel')->handle($request, HttpKernelInterface::SUB_REQUEST);
$view->setRequest($request);
$view->setResponse($response);
$view->initHandlers();
$expected = $this->term1->id();
$this->assertEqual($expected, $view->argument['tid']->getDefaultArgument());
}
/**
* Tests escaping of page title when the taxonomy plugin provides it.
*/
public function testTermTitleEscaping() {
$this->term1->setName('<em>Markup</em>')->save();
$this->drupalGet('taxonomy_default_argument_test/' . $this->term1->id());
$this->assertEscaped($this->term1->label());
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\views\Views;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests the "All terms" taxonomy term field handler.
*
* @group taxonomy
*/
class TaxonomyFieldAllTermsTest extends TaxonomyTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('taxonomy_all_terms_test');
/**
* Tests the "all terms" field handler.
*/
public function testViewsHandlerAllTermsField() {
$this->term1->setName('<em>Markup</em>')->save();
$view = Views::getView('taxonomy_all_terms_test');
$this->executeView($view);
$this->drupalGet('taxonomy_all_terms_test');
$actual = $this->xpath('//a[@href="' . $this->term1->url() . '"]');
$this->assertEqual(count($actual), 2, 'Correct number of taxonomy term1 links');
$this->assertEqual($actual[0]->__toString(), $this->term1->label());
$this->assertEqual($actual[1]->__toString(), $this->term1->label());
$this->assertEscaped($this->term1->label());
$actual = $this->xpath('//a[@href="' . $this->term2->url() . '"]');
$this->assertEqual(count($actual), 2, 'Correct number of taxonomy term2 links');
$this->assertEqual($actual[0]->__toString(), $this->term2->label());
$this->assertEqual($actual[1]->__toString(), $this->term2->label());
}
/**
* Tests token replacement in the "all terms" field handler.
*/
public function testViewsHandlerAllTermsWithTokens() {
$view = Views::getView('taxonomy_all_terms_test');
$this->drupalGet('taxonomy_all_terms_token_test');
// Term itself: {{ term_node_tid }}
$this->assertText('Term: ' . $this->term1->getName());
// The taxonomy term ID for the term: {{ term_node_tid__tid }}
$this->assertText('The taxonomy term ID for the term: ' . $this->term1->id());
// The taxonomy term name for the term: {{ term_node_tid__name }}
$this->assertText('The taxonomy term name for the term: ' . $this->term1->getName());
// The machine name for the vocabulary the term belongs to: {{ term_node_tid__vocabulary_vid }}
$this->assertText('The machine name for the vocabulary the term belongs to: ' . $this->term1->getVocabularyId());
// The name for the vocabulary the term belongs to: {{ term_node_tid__vocabulary }}
$vocabulary = Vocabulary::load($this->term1->bundle());
$this->assertText('The name for the vocabulary the term belongs to: ' . $vocabulary->label());
}
}

View file

@ -0,0 +1,185 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Views;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\Entity\Term;
/**
* Tests taxonomy field filters with translations.
*
* @group taxonomy
*/
class TaxonomyFieldFilterTest extends ViewTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('language', 'taxonomy', 'taxonomy_test_views', 'text', 'views', 'node');
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_field_filters');
/**
* The vocabulary used for creating terms.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* List of taxonomy term names by language.
*
* @var array
*/
public $termNames = [];
function setUp() {
parent::setUp();
// Add two new languages.
ConfigurableLanguage::createFromLangcode('fr')->save();
ConfigurableLanguage::createFromLangcode('es')->save();
// Set up term names.
$this->termNames = array(
'en' => 'Food in Paris',
'es' => 'Comida en Paris',
'fr' => 'Nouriture en Paris',
);
// Create a vocabulary.
$this->vocabulary = Vocabulary::create([
'name' => 'Views testing tags',
'vid' => 'views_testing_tags',
]);
$this->vocabulary->save();
// Add a translatable field to the vocabulary.
$field = FieldStorageConfig::create(array(
'field_name' => 'field_foo',
'entity_type' => 'taxonomy_term',
'type' => 'text',
));
$field->save();
FieldConfig::create([
'field_name' => 'field_foo',
'entity_type' => 'taxonomy_term',
'label' => 'Foo',
'bundle' => 'views_testing_tags',
])->save();
// Create term with translations.
$taxonomy = $this->createTermWithProperties(array('name' => $this->termNames['en'], 'langcode' => 'en', 'description' => $this->termNames['en'], 'field_foo' => $this->termNames['en']));
foreach (array('es', 'fr') as $langcode) {
$translation = $taxonomy->addTranslation($langcode, array('name' => $this->termNames[$langcode]));
$translation->description->value = $this->termNames[$langcode];
$translation->field_foo->value = $this->termNames[$langcode];
}
$taxonomy->save();
Views::viewsData()->clear();
ViewTestData::createTestViews(get_class($this), array('taxonomy_test_views'));
$this->container->get('router.builder')->rebuild();
}
/**
* Tests description and term name filters.
*/
public function testFilters() {
// Test the name filter page, which filters for name contains 'Comida'.
// Should show just the Spanish translation, once.
$this->assertPageCounts('test-name-filter', array('es' => 1, 'fr' => 0, 'en' => 0), 'Comida name filter');
// Test the description filter page, which filters for description contains
// 'Comida'. Should show just the Spanish translation, once.
$this->assertPageCounts('test-desc-filter', array('es' => 1, 'fr' => 0, 'en' => 0), 'Comida description filter');
// Test the field filter page, which filters for field_foo contains
// 'Comida'. Should show just the Spanish translation, once.
$this->assertPageCounts('test-field-filter', array('es' => 1, 'fr' => 0, 'en' => 0), 'Comida field filter');
// Test the name Paris filter page, which filters for name contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-name-paris', array('es' => 1, 'fr' => 1, 'en' => 1), 'Paris name filter');
// Test the description Paris page, which filters for description contains
// 'Paris'. Should show each translation, once.
$this->assertPageCounts('test-desc-paris', array('es' => 1, 'fr' => 1, 'en' => 1), 'Paris description filter');
// Test the field Paris filter page, which filters for field_foo contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-field-paris', array('es' => 1, 'fr' => 1, 'en' => 1), 'Paris field filter');
}
/**
* Asserts that the given taxonomy translation counts are correct.
*
* @param string $path
* Path of the page to test.
* @param array $counts
* Array whose keys are languages, and values are the number of times
* that translation should be shown on the given page.
* @param string $message
* Message suffix to display.
*/
protected function assertPageCounts($path, $counts, $message) {
// Get the text of the page.
$this->drupalGet($path);
$text = $this->getTextContent();
// Check the counts. Note that the title and body are both shown on the
// page, and they are the same. So the title/body string should appear on
// the page twice as many times as the input count.
foreach ($counts as $langcode => $count) {
$this->assertEqual(substr_count($text, $this->termNames[$langcode]), 2 * $count, 'Translation ' . $langcode . ' has count ' . $count . ' with ' . $message);
}
}
/**
* Creates a taxonomy term with specified name and other properties.
*
* @param array $properties
* Array of properties and field values to set.
*
* @return \Drupal\taxonomy\TermInterface
* The created taxonomy term.
*/
protected function createTermWithProperties($properties) {
// Use the first available text format.
$filter_formats = filter_formats();
$format = array_pop($filter_formats);
$properties += array(
'name' => $this->randomMachineName(),
'description' => $this->randomMachineName(),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'field_foo' => $this->randomMachineName(),
);
$term = Term::create([
'name' => $properties['name'],
'description' => $properties['description'],
'format' => $format->id(),
'vid' => $this->vocabulary->id(),
'langcode' => $properties['langcode'],
]);
$term->field_foo->value = $properties['field_foo'];
$term->save();
return $term;
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\Core\Render\RenderContext;
use Drupal\views\Views;
/**
* Tests the taxonomy term TID field handler.
*
* @group taxonomy
*/
class TaxonomyFieldTidTest extends TaxonomyTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_taxonomy_tid_field');
function testViewsHandlerTidField() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$view = Views::getView('test_taxonomy_tid_field');
$this->executeView($view);
$actual = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$expected = \Drupal::l($this->term1->label(), $this->term1->urlInfo());
$this->assertEqual($expected, $actual);
}
}

View file

@ -0,0 +1,156 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\views\Entity\View;
use Drupal\views\Tests\ViewTestData;
/**
* Test the taxonomy term index filter.
*
* @see \Drupal\taxonomy\Plugin\views\filter\TaxonomyIndexTid
*
* @group taxonomy
*/
class TaxonomyIndexTidFilterTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['taxonomy', 'taxonomy_test_views', 'views', 'node'];
/**
* {@inheritdoc}
*/
public static $testViews = ['test_filter_taxonomy_index_tid__non_existing_dependency'];
/**
* @var \Drupal\taxonomy\TermInterface[]
*/
protected $terms = [];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp(FALSE);
// Setup vocabulary and terms so the initial import is valid.
Vocabulary::create([
'vid' => 'tags',
'name' => 'Tags',
])->save();
// This will get a term ID of 3.
$term = Term::create([
'vid' => 'tags',
'name' => 'muh',
]);
$term->save();
// This will get a term ID of 4.
$this->terms[$term->id()] = $term;
$term = Term::create([
'vid' => 'tags',
'name' => 'muh',
]);
$term->save();
$this->terms[$term->id()] = $term;
ViewTestData::createTestViews(get_class($this), array('taxonomy_test_views'));
}
/**
* Tests dependencies are not added for terms that do not exist.
*/
public function testConfigDependency() {
/** @var \Drupal\views\Entity\View $view */
$view = View::load('test_filter_taxonomy_index_tid__non_existing_dependency');
// Dependencies are sorted.
$content_dependencies = [
$this->terms[3]->getConfigDependencyName(),
$this->terms[4]->getConfigDependencyName(),
];
sort($content_dependencies);
$this->assertEqual([
'config' => [
'taxonomy.vocabulary.tags',
],
'content' => $content_dependencies,
'module' => [
'node',
'taxonomy',
'user',
],
], $view->calculateDependencies()->getDependencies());
$this->terms[3]->delete();
$this->assertEqual([
'config' => [
'taxonomy.vocabulary.tags',
],
'content' => [
$this->terms[4]->getConfigDependencyName(),
],
'module' => [
'node',
'taxonomy',
'user',
],
], $view->calculateDependencies()->getDependencies());
}
/**
* Tests post update function fixes dependencies.
*
* @see views_post_update_taxonomy_index_tid()
*/
public function testPostUpdateFunction() {
/** @var \Drupal\views\Entity\View $view */
$view = View::load('test_filter_taxonomy_index_tid__non_existing_dependency');
// Dependencies are sorted.
$content_dependencies = [
$this->terms[3]->getConfigDependencyName(),
$this->terms[4]->getConfigDependencyName(),
];
sort($content_dependencies);
$this->assertEqual([
'config' => [
'taxonomy.vocabulary.tags',
],
'content' => $content_dependencies,
'module' => [
'node',
'taxonomy',
'user',
],
], $view->calculateDependencies()->getDependencies());
$this->terms[3]->delete();
\Drupal::moduleHandler()->loadInclude('views', 'post_update.php');
views_post_update_taxonomy_index_tid();
$view = View::load('test_filter_taxonomy_index_tid__non_existing_dependency');
$this->assertEqual([
'config' => [
'taxonomy.vocabulary.tags',
],
'content' => [
$this->terms[4]->getConfigDependencyName(),
],
'module' => [
'node',
'taxonomy',
'user',
],
], $view->getDependencies());
}
}

View file

@ -0,0 +1,221 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\views\Tests\ViewTestData;
use Drupal\views_ui\Tests\UITestBase;
use Drupal\views\Entity\View;
/**
* Tests the taxonomy index filter handler UI.
*
* @group taxonomy
* @see \Drupal\taxonomy\Plugin\views\field\TaxonomyIndexTid
*/
class TaxonomyIndexTidUiTest extends UITestBase {
use EntityReferenceTestTrait;
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_filter_taxonomy_index_tid', 'test_taxonomy_term_name');
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'taxonomy', 'views', 'views_ui', 'taxonomy_test_views'];
/**
* A nested array of \Drupal\taxonomy\TermInterface objects.
*
* @var \Drupal\taxonomy\TermInterface[][]
*/
protected $terms = [];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(['administer taxonomy', 'administer views']);
$this->drupalLogin($this->adminUser);
Vocabulary::create([
'vid' => 'tags',
'name' => 'Tags',
])->save();
// Setup a hierarchy which looks like this:
// term 0.0
// term 1.0
// - term 1.1
// term 2.0
// - term 2.1
// - term 2.2
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j <= $i; $j++) {
$this->terms[$i][$j] = $term = Term::create([
'vid' => 'tags',
'name' => "Term $i.$j",
'parent' => isset($terms[$i][0]) ? $terms[$i][0]->id() : 0,
]);
$term->save();
}
}
ViewTestData::createTestViews(get_class($this), array('taxonomy_test_views'));
Vocabulary::create([
'vid' => 'empty_vocabulary',
'name' => 'Empty Vocabulary',
])->save();
}
/**
* Tests the filter UI.
*/
public function testFilterUI() {
$this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');
$result = $this->xpath('//select[@id="edit-options-value"]/option');
// Ensure that the expected hierarchy is available in the UI.
$counter = 0;
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j <= $i; $j++) {
$option = $result[$counter++];
$prefix = $this->terms[$i][$j]->parent->target_id ? '-' : '';
$attributes = $option->attributes();
$tid = (string) $attributes->value;
$this->assertEqual($prefix . $this->terms[$i][$j]->getName(), (string) $option);
$this->assertEqual($this->terms[$i][$j]->id(), $tid);
}
}
// Ensure the autocomplete input element appears when using the 'textfield'
// type.
$view = View::load('test_filter_taxonomy_index_tid');
$display =& $view->getDisplay('default');
$display['display_options']['filters']['tid']['type'] = 'textfield';
$view->save();
$this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');
$this->assertFieldByXPath('//input[@id="edit-options-value"]');
// Tests \Drupal\taxonomy\Plugin\views\filter\TaxonomyIndexTid::calculateDependencies().
$expected = [
'config' => [
'taxonomy.vocabulary.tags',
],
'content' => [
'taxonomy_term:tags:' . Term::load(2)->uuid(),
],
'module' => [
'node',
'taxonomy',
'user',
],
];
$this->assertIdentical($expected, $view->calculateDependencies()->getDependencies());
}
/**
* Tests exposed taxonomy filters.
*/
public function testExposedFilter() {
$node_type = $this->drupalCreateContentType(['type' => 'page']);
// Create the tag field itself.
$field_name = 'taxonomy_tags';
$this->createEntityReferenceField('node', $node_type->id(), $field_name, NULL, 'taxonomy_term');
// Create 4 nodes: 1 without a term, 2 with the same term, and 1 with a
// different term.
$node1 = $this->drupalCreateNode();
$node2 = $this->drupalCreateNode([
$field_name => [['target_id' => $this->terms[1][0]->id()]],
]);
$node3 = $this->drupalCreateNode([
$field_name => [['target_id' => $this->terms[1][0]->id()]],
]);
$node4 = $this->drupalCreateNode([
$field_name => [['target_id' => $this->terms[2][0]->id()]],
]);
// Only the nodes with the selected term should be shown.
$this->drupalGet('test-filter-taxonomy-index-tid');
$xpath = $this->xpath('//div[@class="view-content"]//a');
$this->assertIdentical(2, count($xpath));
$xpath = $this->xpath('//div[@class="view-content"]//a[@href=:href]', [':href' => $node2->url()]);
$this->assertIdentical(1, count($xpath));
$xpath = $this->xpath('//div[@class="view-content"]//a[@href=:href]', [':href' => $node3->url()]);
$this->assertIdentical(1, count($xpath));
// Expose the filter.
$this->drupalPostForm('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid', [], 'Expose filter');
// Set the operator to 'empty' and remove the default term ID.
$this->drupalPostForm(NULL, [
'options[operator]' => 'empty',
'options[value][]' => [],
], 'Apply');
// Save the view.
$this->drupalPostForm(NULL, [], 'Save');
// After switching to 'empty' operator, the node without a term should be
// shown.
$this->drupalGet('test-filter-taxonomy-index-tid');
$xpath = $this->xpath('//div[@class="view-content"]//a');
$this->assertIdentical(1, count($xpath));
$xpath = $this->xpath('//div[@class="view-content"]//a[@href=:href]', [':href' => $node1->url()]);
$this->assertIdentical(1, count($xpath));
// Set the operator to 'not empty'.
$this->drupalPostForm('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid', ['options[operator]' => 'not empty'], 'Apply');
// Save the view.
$this->drupalPostForm(NULL, [], 'Save');
// After switching to 'not empty' operator, all nodes with terms should be
// shown.
$this->drupalGet('test-filter-taxonomy-index-tid');
$xpath = $this->xpath('//div[@class="view-content"]//a');
$this->assertIdentical(3, count($xpath));
$xpath = $this->xpath('//div[@class="view-content"]//a[@href=:href]', [':href' => $node2->url()]);
$this->assertIdentical(1, count($xpath));
$xpath = $this->xpath('//div[@class="view-content"]//a[@href=:href]', [':href' => $node3->url()]);
$this->assertIdentical(1, count($xpath));
$xpath = $this->xpath('//div[@class="view-content"]//a[@href=:href]', [':href' => $node4->url()]);
$this->assertIdentical(1, count($xpath));
// Select 'Term ID' as the field to be displayed.
$edit = ['name[taxonomy_term_field_data.tid]' => TRUE];
$this->drupalPostForm('admin/structure/views/nojs/add-handler/test_taxonomy_term_name/default/field', $edit, 'Add and configure fields');
// Select 'Term' and 'Vocabulary' as filters.
$edit = [
'name[taxonomy_term_field_data.tid]' => TRUE,
'name[taxonomy_term_field_data.vid]' => TRUE
];
$this->drupalPostForm('admin/structure/views/nojs/add-handler/test_taxonomy_term_name/default/filter', $edit, 'Add and configure filter criteria');
// Select 'Empty Vocabulary' and 'Autocomplete' from the list of options.
$this->drupalPostForm('admin/structure/views/nojs/handler-extra/test_taxonomy_term_name/default/filter/tid', [], 'Apply and continue');
// Expose the filter.
$edit = ['options[expose_button][checkbox][checkbox]' => TRUE];
$this->drupalPostForm('admin/structure/views/nojs/handler/test_taxonomy_term_name/default/filter/tid', $edit, 'Expose filter');
$this->drupalPostForm('admin/structure/views/nojs/handler/test_taxonomy_term_name/default/filter/tid', $edit, 'Apply');
// Filter 'Taxonomy terms' belonging to 'Empty Vocabulary'.
$edit = ['options[value][empty_vocabulary]' => TRUE];
$this->drupalPostForm('admin/structure/views/nojs/handler/test_taxonomy_term_name/default/filter/vid', $edit, 'Apply');
$this->drupalPostForm('admin/structure/views/view/test_taxonomy_term_name/edit/default', [], 'Save');
$this->drupalPostForm(NULL, [], t('Update preview'));
$preview = $this->xpath("//div[@class='view-content']");
$this->assertTrue(empty($preview), 'No results.');
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\views\Tests\ViewTestData;
use Drupal\views_ui\Tests\UITestBase;
/**
* Tests views taxonomy parent plugin UI.
*
* @group taxonomy
* @see Drupal\taxonomy\Plugin\views\access\Role
*/
class TaxonomyParentUITest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_taxonomy_parent');
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('taxonomy', 'taxonomy_test_views');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
ViewTestData::createTestViews(get_class($this), array('taxonomy_test_views'));
}
/**
* Tests the taxonomy parent plugin UI.
*/
public function testTaxonomyParentUI() {
$this->drupalGet('admin/structure/views/nojs/handler/test_taxonomy_parent/default/relationship/parent');
$this->assertNoText('The handler for this item is broken or missing.');
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\node\NodeInterface;
use Drupal\taxonomy\TermInterface;
use Drupal\views\Views;
/**
* Tests taxonomy relationships with parent term and node.
*
* @group taxonomy
*/
class TaxonomyRelationshipTest extends TaxonomyTestBase {
/**
* Stores the terms used in the tests.
*
* @var \Drupal\taxonomy\TermInterface[]
*/
protected $terms = array();
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_taxonomy_term_relationship');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Make term2 parent of term1.
$this->term1->set('parent', $this->term2->id());
$this->term1->save();
// Store terms in an array for testing.
$this->terms[] = $this->term1;
$this->terms[] = $this->term2;
// Only set term1 on node1 and term2 on node2 for testing.
unset($this->nodes[0]->field_views_testing_tags[1]);
$this->nodes[0]->save();
unset($this->nodes[1]->field_views_testing_tags[0]);
$this->nodes[1]->save();
Views::viewsData()->clear();
}
/**
* Tests the taxonomy parent plugin UI.
*/
public function testTaxonomyRelationships() {
// Check the generated views data of taxonomy_index.
$views_data = Views::viewsData()->get('taxonomy_index');
// Check the table join data.
$this->assertEqual($views_data['table']['join']['taxonomy_term_field_data']['left_field'], 'tid');
$this->assertEqual($views_data['table']['join']['taxonomy_term_field_data']['field'], 'tid');
$this->assertEqual($views_data['table']['join']['node_field_data']['left_field'], 'nid');
$this->assertEqual($views_data['table']['join']['node_field_data']['field'], 'nid');
$this->assertEqual($views_data['table']['join']['taxonomy_term_hierarchy']['left_field'], 'tid');
$this->assertEqual($views_data['table']['join']['taxonomy_term_hierarchy']['field'], 'tid');
// Check the generated views data of taxonomy_term_hierarchy.
$views_data = Views::viewsData()->get('taxonomy_term_hierarchy');
// Check the table join data.
$this->assertEqual($views_data['table']['join']['taxonomy_term_hierarchy']['left_field'], 'tid');
$this->assertEqual($views_data['table']['join']['taxonomy_term_hierarchy']['field'], 'parent');
$this->assertEqual($views_data['table']['join']['taxonomy_term_field_data']['left_field'], 'tid');
$this->assertEqual($views_data['table']['join']['taxonomy_term_field_data']['field'], 'tid');
// Check the parent relationship data.
$this->assertEqual($views_data['parent']['relationship']['base'], 'taxonomy_term_field_data');
$this->assertEqual($views_data['parent']['relationship']['field'], 'parent');
$this->assertEqual($views_data['parent']['relationship']['label'], t('Parent'));
$this->assertEqual($views_data['parent']['relationship']['id'], 'standard');
// Check the parent filter and argument data.
$this->assertEqual($views_data['parent']['filter']['id'], 'numeric');
$this->assertEqual($views_data['parent']['argument']['id'], 'taxonomy');
// Check an actual test view.
$view = Views::getView('test_taxonomy_term_relationship');
$this->executeView($view);
/** @var \Drupal\views\ResultRow $row */
foreach ($view->result as $index => $row) {
// Check that the actual ID of the entity is the expected one.
$this->assertEqual($row->tid, $this->terms[$index]->id());
// Also check that we have the correct result entity.
$this->assertEqual($row->_entity->id(), $this->terms[$index]->id());
$this->assertTrue($row->_entity instanceof TermInterface);
if (!$index) {
$this->assertTrue($row->_relationship_entities['parent'] instanceof TermInterface);
$this->assertEqual($row->_relationship_entities['parent']->id(), $this->term2->id());
$this->assertEqual($row->taxonomy_term_field_data_taxonomy_term_hierarchy_tid, $this->term2->id());
}
$this->assertTrue($row->_relationship_entities['nid'] instanceof NodeInterface);
$this->assertEqual($row->_relationship_entities['nid']->id(), $this->nodes[$index]->id());
}
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
/**
* Tests the taxonomy term with depth argument.
*
* @group taxonomy
*/
class TaxonomyTermArgumentDepthTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['taxonomy', 'taxonomy_test_views', 'views', 'node'];
/**
* {@inheritdoc}
*/
public static $testViews = ['test_argument_taxonomy_index_tid_depth'];
/**
* @var \Drupal\taxonomy\TermInterface[]
*/
protected $terms = [];
/**
* @var \Drupal\views\ViewExecutable
*/
protected $view;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a term with markup in the label.
$first = $this->createTerm(['name' => '<em>First</em>']);
// Create a node w/o any terms.
$settings = ['type' => 'article'];
// Create a node with linked to the term.
$settings['field_views_testing_tags'][0]['target_id'] = $first->id();
$this->nodes[] = $this->drupalCreateNode($settings);
$this->terms[0] = $first;
}
/**
* Tests title escaping.
*/
public function testTermWithDepthArgumentTitleEscaping() {
$this->drupalGet('test_argument_taxonomy_index_tid_depth/' . $this->terms[0]->id());
$this->assertEscaped($this->terms[0]->label());
}
}

View file

@ -0,0 +1,136 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\views\Views;
/**
* Test the taxonomy term with depth filter.
*
* @group taxonomy
*/
class TaxonomyTermFilterDepthTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['taxonomy', 'taxonomy_test_views', 'views', 'node'];
/**
* {@inheritdoc}
*/
public static $testViews = ['test_filter_taxonomy_index_tid_depth'];
/**
* @var \Drupal\taxonomy\TermInterface[]
*/
protected $terms = [];
/**
* @var \Drupal\views\ViewExecutable
*/
protected $view;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a hierarchy 3 deep. Note the parent setup function creates two
// top-level terms w/o children.
$first = $this->createTerm(['name' => 'First']);
$second = $this->createTerm(['name' => 'Second', 'parent' => $first->id()]);
$third = $this->createTerm(['name' => 'Third', 'parent' => $second->id()]);
// Create a node w/o any terms.
$settings = ['type' => 'article'];
$this->nodes[] = $this->drupalCreateNode($settings);
// Create a node with only the top level term.
$settings['field_views_testing_tags'][0]['target_id'] = $first->id();
$this->nodes[] = $this->drupalCreateNode($settings);
// Create a node with only the third level term.
$settings['field_views_testing_tags'][0]['target_id'] = $third->id();
$this->nodes[] = $this->drupalCreateNode($settings);
$this->terms[0] = $first;
$this->terms[1] = $second;
$this->terms[2] = $third;
$this->view = Views::getView('test_filter_taxonomy_index_tid_depth');
}
/**
* Tests the terms with depth filter.
*/
public function testTermWithDepthFilter() {
$column_map = ['nid' => 'nid'];
$assert_method = 'assertIdentical';
// Default view has an empty value for this filter, so all nodes should be
// returned.
$expected = [
['nid' => 1],
['nid' => 2],
['nid' => 3],
['nid' => 4],
['nid' => 5],
];
$this->executeView($this->view);
$this->assertIdenticalResultsetHelper($this->view, $expected, $column_map, $assert_method);
// Set filter to search on top-level term, with depth 0.
$expected = [['nid' => 4]];
$this->assertTermWithDepthResult($this->terms[0]->id(), 0, $expected);
// Top-level term, depth 1.
$expected = [['nid' => 4]];
$this->assertTermWithDepthResult($this->terms[0]->id(), 0, $expected);
// Top-level term, depth 2.
$expected = [['nid' => 4], ['nid' => 5]];
$this->assertTermWithDepthResult($this->terms[0]->id(), 2, $expected);
// Second-level term, depth 1.
$expected = [['nid' => 5]];
$this->assertTermWithDepthResult($this->terms[1]->id(), 1, $expected);
// Third-level term, depth 0.
$expected = [['nid' => 5]];
$this->assertTermWithDepthResult($this->terms[2]->id(), 0, $expected);
// Third-level term, depth 1.
$expected = [['nid' => 5]];
$this->assertTermWithDepthResult($this->terms[2]->id(), 1, $expected);
// Third-level term, depth -2.
$expected = [['nid' => 4], ['nid' => 5]];
$this->assertTermWithDepthResult($this->terms[2]->id(), -2, $expected);
}
/**
* Changes the tid filter to given term and depth.
*
* @param int $tid
* The term ID to filter on.
* @param int $depth
* The depth to search.
* @param array $expected
* The expected views result.
*/
protected function assertTermWithDepthResult($tid, $depth, array $expected) {
$this->view->destroy();
$this->view->initDisplay();
$filters = $this->view->displayHandlers->get('default')
->getOption('filters');
$filters['tid_depth']['depth'] = $depth;
$filters['tid_depth']['value'] = [$tid];
$this->view->displayHandlers->get('default')
->setOption('filters', $filters);
$this->executeView($this->view);
$this->assertIdenticalResultsetHelper($this->view, $expected, ['nid' => 'nid'], 'assertIdentical');
}
}

View file

@ -0,0 +1,156 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
use Drupal\views\Views;
/**
* Tests the taxonomy term view page and its translation.
*
* @group taxonomy
*/
class TaxonomyTermViewTest extends TaxonomyTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('taxonomy', 'views');
/**
* An user with permissions to administer taxonomy.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Name of the taxonomy term reference field.
*
* @var string
*/
protected $fieldName1;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create an administrative user.
$this->adminUser = $this->drupalCreateUser(['administer taxonomy', 'bypass node access']);
$this->drupalLogin($this->adminUser);
// Create a vocabulary and add two term reference fields to article nodes.
$this->fieldName1 = Unicode::strtolower($this->randomMachineName());
$handler_settings = array(
'target_bundles' => array(
$this->vocabulary->id() => $this->vocabulary->id(),
),
'auto_create' => TRUE,
);
$this->createEntityReferenceField('node', 'article', $this->fieldName1, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
entity_get_form_display('node', 'article', 'default')
->setComponent($this->fieldName1, array(
'type' => 'options_select',
))
->save();
entity_get_display('node', 'article', 'default')
->setComponent($this->fieldName1, array(
'type' => 'entity_reference_label',
))
->save();
}
/**
* Tests that the taxonomy term view is working properly.
*/
public function testTaxonomyTermView() {
// Create terms in the vocabulary.
$term = $this->createTerm();
// Post an article.
$edit = array();
$edit['title[0][value]'] = $original_title = $this->randomMachineName();
$edit['body[0][value]'] = $this->randomMachineName();
$edit["{$this->fieldName1}[]"] = $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::service('module_installer')->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.
\Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE);
$roles = $this->adminUser->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();
\Drupal::service('entity.definition_update_manager')->applyUpdates();
$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);
// Uninstall language module and ensure that the language is not part of the
// query anymore.
// @see \Drupal\views\Plugin\views\filter\LanguageFilter::query()
$node->delete();
\Drupal::service('module_installer')->uninstall(['content_translation', 'language']);
$view = Views::getView('taxonomy_term');
$view->initDisplay();
$view->setArguments([$term->id()]);
$view->build();
/** @var \Drupal\Core\Database\Query\Select $query */
$query = $view->build_info['query'];
$tables = $query->getTables();
// Ensure that the join to node_field_data is not added by default.
$this->assertEqual(['node_field_data', 'taxonomy_index'], array_keys($tables));
// Ensure that the filter to the language column is not there by default.
$condition = $query->conditions();
// We only want to check the no. of conditions in the query.
unset($condition['#conjunction']);
$this->assertEqual(1, count($condition));
// Clear permissions for anonymous users to check access for default views.
Role::load(RoleInterface::ANONYMOUS_ID)->revokePermission('access content')->save();
// Test the default views disclose no data by default.
$this->drupalLogout();
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertResponse(403);
$this->drupalGet('taxonomy/term/' . $term->id() . '/feed');
$this->assertResponse(403);
}
}

View file

@ -0,0 +1,155 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\Entity\Term;
/**
* Base class for all taxonomy tests.
*/
abstract class TaxonomyTestBase extends ViewTestBase {
use EntityReferenceTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('taxonomy', 'taxonomy_test_views');
/**
* Stores the nodes used for the different tests.
*
* @var \Drupal\node\NodeInterface[]
*/
protected $nodes = array();
/**
* The vocabulary used for creating terms.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Stores the first term used in the different tests.
*
* @var \Drupal\taxonomy\TermInterface
*/
protected $term1;
/**
* Stores the second term used in the different tests.
*
* @var \Drupal\taxonomy\TermInterface
*/
protected $term2;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->mockStandardInstall();
if ($import_test_views) {
ViewTestData::createTestViews(get_class($this), array('taxonomy_test_views'));
}
$this->term1 = $this->createTerm();
$this->term2 = $this->createTerm();
$node = array();
$node['type'] = 'article';
$node['field_views_testing_tags'][]['target_id'] = $this->term1->id();
$node['field_views_testing_tags'][]['target_id'] = $this->term2->id();
$this->nodes[] = $this->drupalCreateNode($node);
$this->nodes[] = $this->drupalCreateNode($node);
}
/**
* Provides a workaround for the inability to use the standard profile.
*
* @see https://www.drupal.org/node/1708692
*/
protected function mockStandardInstall() {
$this->drupalCreateContentType(array(
'type' => 'article',
));
// Create the vocabulary for the tag field.
$this->vocabulary = Vocabulary::create([
'name' => 'Views testing tags',
'vid' => 'views_testing_tags',
]);
$this->vocabulary->save();
$field_name = 'field_' . $this->vocabulary->id();
$handler_settings = array(
'target_bundles' => array(
$this->vocabulary->id() => $this->vocabulary->id(),
),
'auto_create' => TRUE,
);
$this->createEntityReferenceField('node', 'article', $field_name, 'Tags', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
entity_get_form_display('node', 'article', 'default')
->setComponent($field_name, array(
'type' => 'entity_reference_autocomplete_tags',
'weight' => -4,
))
->save();
entity_get_display('node', 'article', 'default')
->setComponent($field_name, array(
'type' => 'entity_reference_label',
'weight' => 10,
))
->save();
entity_get_display('node', 'article', 'teaser')
->setComponent($field_name, array(
'type' => 'entity_reference_label',
'weight' => 10,
))
->save();
}
/**
* Creates and returns a taxonomy term.
*
* @param array $settings
* (optional) An array of values to override the following default
* properties of the term:
* - name: A random string.
* - description: A random string.
* - format: First available text format.
* - vid: Vocabulary ID of self::$vocabulary object.
* - langcode: LANGCODE_NOT_SPECIFIED.
* Defaults to an empty array.
*
* @return \Drupal\taxonomy\Entity\Term
* The created taxonomy term.
*/
protected function createTerm(array $settings = []) {
$filter_formats = filter_formats();
$format = array_pop($filter_formats);
$settings += [
'name' => $this->randomMachineName(),
'description' => $this->randomMachineName(),
// Use the first available text format.
'format' => $format->id(),
'vid' => $this->vocabulary->id(),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
];
$term = Term::create($settings);
$term->save();
return $term;
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace Drupal\taxonomy\Tests\Views;
use Drupal\views\Views;
/**
* Tests the term_name field handler.
*
* @group taxonomy
*
* @see \Drupal\taxonomy\Plugin\views\field\TermName
*/
class TermNameFieldTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
public static $testViews = array('test_taxonomy_term_name');
/**
* Tests term name field plugin functionality.
*/
public function testTermNameField() {
$this->term1->name->value = $this->randomMachineName() . ' ' . $this->randomMachineName();
$this->term1->save();
$user = $this->drupalCreateUser(['access content']);
$this->drupalLogin($user);
$view = Views::getView('test_taxonomy_term_name');
$view->initDisplay();
$this->executeView($view);
$this->assertEqual($this->term1->getName(), $view->getStyle()->getField(0, 'name'));
$this->assertEqual($this->term2->getName(), $view->getStyle()->getField(1, 'name'));
$view = Views::getView('test_taxonomy_term_name');
$display =& $view->storage->getDisplay('default');
$display['display_options']['fields']['name']['convert_spaces'] = TRUE;
$view->storage->invalidateCaches();
$this->executeView($view);
$this->assertEqual(str_replace(' ', '-', $this->term1->getName()), $view->getStyle()->getField(0, 'name'));
$this->assertEqual($this->term2->getName(), $view->getStyle()->getField(1, 'name'));
}
}

Some files were not shown because too many files have changed in this diff Show more