Move all files to 2017/
This commit is contained in:
parent
ac7370f67f
commit
2875863330
15717 changed files with 0 additions and 0 deletions
|
@ -0,0 +1,9 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- taxonomy
|
||||
id: taxonomy_term.full
|
||||
label: 'Taxonomy term page'
|
||||
targetEntityType: taxonomy_term
|
||||
cache: true
|
|
@ -0,0 +1,3 @@
|
|||
maintain_index_table: true
|
||||
override_selector: false
|
||||
terms_per_page_admin: 100
|
|
@ -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: { }
|
|
@ -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'
|
|
@ -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'
|
10
2017/web/core/modules/taxonomy/css/taxonomy.theme.css
Normal file
10
2017/web/core/modules/taxonomy/css/taxonomy.theme.css
Normal 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;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
id: d6_taxonomy_term
|
||||
label: Taxonomy terms
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
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_lookup
|
||||
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_lookup
|
||||
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
|
|
@ -0,0 +1,31 @@
|
|||
id: d6_taxonomy_vocabulary
|
||||
label: Taxonomy vocabularies
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_taxonomy_vocabulary
|
||||
process:
|
||||
vid:
|
||||
-
|
||||
plugin: machine_name
|
||||
source: name
|
||||
-
|
||||
plugin: make_unique_entity_field
|
||||
entity_type: taxonomy_vocabulary
|
||||
field: vid
|
||||
length: 32
|
||||
migrated: true
|
||||
-
|
||||
# This plugin checks if the vocabulary being migrated is the one used by
|
||||
# Forum. If so, we use the machine name that Forum expects. Otherwise, we
|
||||
# leave it unchanged.
|
||||
plugin: forum_vocabulary
|
||||
machine_name: forums
|
||||
label: name
|
||||
name: name
|
||||
description: description
|
||||
hierarchy: hierarchy
|
||||
weight: weight
|
||||
destination:
|
||||
plugin: entity:taxonomy_vocabulary
|
26
2017/web/core/modules/taxonomy/migrations/d6_term_node.yml
Normal file
26
2017/web/core/modules/taxonomy/migrations/d6_term_node.yml
Normal file
|
@ -0,0 +1,26 @@
|
|||
id: d6_term_node
|
||||
label: Term/node relationships
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
deriver: Drupal\taxonomy\Plugin\migrate\D6TermNodeDeriver
|
||||
source:
|
||||
plugin: d6_term_node
|
||||
process:
|
||||
nid:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
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
|
|
@ -0,0 +1,26 @@
|
|||
id: d6_term_node_revision
|
||||
label: Term/node relationship revisions
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
deriver: Drupal\taxonomy\Plugin\migrate\D6TermNodeDeriver
|
||||
source:
|
||||
plugin: d6_term_node_revision
|
||||
process:
|
||||
vid:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d6_node_revision
|
||||
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
|
|
@ -0,0 +1,58 @@
|
|||
id: d6_vocabulary_entity_display
|
||||
label: Vocabulary display configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_taxonomy_vocabulary_per_type
|
||||
constants:
|
||||
entity_type: node
|
||||
view_mode: default
|
||||
options:
|
||||
label: hidden
|
||||
type: entity_reference_label
|
||||
weight: 20
|
||||
field_prefix: field_
|
||||
process:
|
||||
entity_type: 'constants/entity_type'
|
||||
view_mode: 'constants/view_mode'
|
||||
options: 'constants/options'
|
||||
bundle:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d6_node_type
|
||||
source: type
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
# This value is only used in the 'field_name' process pipeline below.
|
||||
raw_field_name:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d6_taxonomy_vocabulary
|
||||
source: vid
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
field_name:
|
||||
# Prepend field_ to avoid conflicts with base fields, and make sure the
|
||||
# result is no longer than 32 characters.
|
||||
-
|
||||
plugin: concat
|
||||
source:
|
||||
- constants/field_prefix
|
||||
- '@raw_field_name'
|
||||
-
|
||||
plugin: substr
|
||||
length: 32
|
||||
-
|
||||
# This plugin checks if the vocabulary being migrated is the one used by
|
||||
# Forum. If so, we use the machine name that Forum expects. Otherwise, we
|
||||
# leave it unchanged.
|
||||
plugin: forum_vocabulary
|
||||
machine_name: taxonomy_forums
|
||||
destination:
|
||||
plugin: component_entity_display
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_vocabulary_field_instance
|
|
@ -0,0 +1,62 @@
|
|||
id: d6_vocabulary_entity_form_display
|
||||
label: Vocabulary form display configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_taxonomy_vocabulary_per_type
|
||||
constants:
|
||||
entity_type: node
|
||||
form_mode: default
|
||||
options:
|
||||
weight: 20
|
||||
field_prefix: field_
|
||||
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:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d6_node_type
|
||||
source: type
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
# This value is only used in the 'field_name' process pipeline below.
|
||||
raw_field_name:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d6_taxonomy_vocabulary
|
||||
source: vid
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
field_name:
|
||||
# Prepend field_ to avoid conflicts with base fields, and make sure the
|
||||
# result is no longer than 32 characters.
|
||||
-
|
||||
plugin: concat
|
||||
source:
|
||||
- constants/field_prefix
|
||||
- '@raw_field_name'
|
||||
-
|
||||
plugin: substr
|
||||
length: 32
|
||||
-
|
||||
# This plugin checks if the vocabulary being migrated is the one used by
|
||||
# Forum. If so, we use the machine name that Forum expects. Otherwise, we
|
||||
# leave it unchanged.
|
||||
plugin: forum_vocabulary
|
||||
machine_name: taxonomy_forums
|
||||
destination:
|
||||
plugin: component_entity_form_display
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_vocabulary_field_instance
|
|
@ -0,0 +1,51 @@
|
|||
id: d6_vocabulary_field
|
||||
label: Vocabulary field configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_taxonomy_vocabulary
|
||||
constants:
|
||||
entity_type: node
|
||||
type: entity_reference
|
||||
target_entity_type: taxonomy_term
|
||||
field_prefix: field_
|
||||
process:
|
||||
entity_type: 'constants/entity_type'
|
||||
type: 'constants/type'
|
||||
# This value is only used in the 'field_name' process pipeline below.
|
||||
raw_field_name:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d6_taxonomy_vocabulary
|
||||
source: vid
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
field_name:
|
||||
# Prepend field_ to avoid conflicts with base fields, and make sure the
|
||||
# result is no longer than 32 characters.
|
||||
-
|
||||
plugin: concat
|
||||
source:
|
||||
- constants/field_prefix
|
||||
- '@raw_field_name'
|
||||
-
|
||||
plugin: substr
|
||||
length: 32
|
||||
-
|
||||
# This plugin checks if the vocabulary being migrated is the one used by
|
||||
# Forum. If so, we use the machine name that Forum expects. Otherwise, we
|
||||
# leave it unchanged.
|
||||
plugin: forum_vocabulary
|
||||
machine_name: taxonomy_forums
|
||||
'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
|
|
@ -0,0 +1,74 @@
|
|||
id: d6_vocabulary_field_instance
|
||||
label: Vocabulary field instance configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_taxonomy_vocabulary_per_type
|
||||
constants:
|
||||
entity_type: node
|
||||
auto_create: true
|
||||
selection_handler: 'default:taxonomy_term'
|
||||
field_prefix: field_
|
||||
process:
|
||||
entity_type: 'constants/entity_type'
|
||||
bundle:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d6_node_type
|
||||
source: type
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
# This value is only used in the 'field_name' process pipeline below.
|
||||
raw_field_name:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d6_taxonomy_vocabulary
|
||||
source: vid
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
field_name:
|
||||
# Prepend field_ to avoid conflicts with base fields, and make sure the
|
||||
# result is no longer than 32 characters.
|
||||
-
|
||||
plugin: concat
|
||||
source:
|
||||
- constants/field_prefix
|
||||
- '@raw_field_name'
|
||||
-
|
||||
plugin: substr
|
||||
length: 32
|
||||
-
|
||||
# This plugin checks if the vocabulary being migrated is the one used by
|
||||
# Forum. If so, we use the machine name that Forum expects. Otherwise, we
|
||||
# leave it unchanged.
|
||||
plugin: forum_vocabulary
|
||||
machine_name: taxonomy_forums
|
||||
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
|
||||
# Get the i18n taxonomy translation setting for this vocabulary.
|
||||
# 0 - No multilingual options
|
||||
# 1 - Localizable terms. Run through the localization system.
|
||||
# 2 - Predefined language for a vocabulary and its terms.
|
||||
# 3 - Per-language terms, translatable (referencing terms with different
|
||||
# languages) but not localizable.
|
||||
translatable:
|
||||
plugin: static_map
|
||||
source: i18ntaxonomy_vocabulary
|
||||
default_value: 0
|
||||
map:
|
||||
0: false
|
||||
1: false
|
||||
2: false
|
||||
3: true
|
||||
destination:
|
||||
plugin: entity:field_config
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_node_type
|
||||
- d6_vocabulary_field
|
|
@ -0,0 +1,44 @@
|
|||
id: d7_taxonomy_term
|
||||
label: Taxonomy terms
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Content
|
||||
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_lookup
|
||||
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_lookup
|
||||
migration: d7_taxonomy_term
|
||||
parent:
|
||||
plugin: default_value
|
||||
default_value: 0
|
||||
source: '@parent_id'
|
||||
forum_container: is_container
|
||||
changed: timestamp
|
||||
langcode: language
|
||||
destination:
|
||||
plugin: entity:taxonomy_term
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_taxonomy_vocabulary
|
||||
optional:
|
||||
- d7_field_instance
|
|
@ -0,0 +1,29 @@
|
|||
id: d7_taxonomy_vocabulary
|
||||
label: Taxonomy vocabularies
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d7_taxonomy_vocabulary
|
||||
process:
|
||||
vid:
|
||||
-
|
||||
plugin: make_unique_entity_field
|
||||
source: machine_name
|
||||
entity_type: taxonomy_vocabulary
|
||||
field: vid
|
||||
length: 32
|
||||
migrated: true
|
||||
-
|
||||
# This plugin checks if the vocabulary being migrated is the one used by
|
||||
# Forum. If so, we use the machine name that Forum expects. Otherwise, we
|
||||
# leave it unchanged.
|
||||
plugin: forum_vocabulary
|
||||
machine_name: forums
|
||||
label: name
|
||||
name: name
|
||||
description: description
|
||||
hierarchy: hierarchy
|
||||
weight: weight
|
||||
destination:
|
||||
plugin: entity:taxonomy_vocabulary
|
|
@ -0,0 +1,18 @@
|
|||
id: taxonomy_settings
|
||||
label: Taxonomy configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- taxonomy_override_selector
|
||||
- taxonomy_terms_per_page_admin
|
||||
source_module: taxonomy
|
||||
process:
|
||||
override_selector: taxonomy_override_selector
|
||||
terms_per_page_admin: taxonomy_terms_per_page_admin
|
||||
destination:
|
||||
plugin: config
|
||||
config_name: taxonomy.settings
|
|
@ -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(['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()];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Entity\Routing;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
class VocabularyRouteProvider extends AdminHtmlRouteProvider {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutes(EntityTypeInterface $entity_type) {
|
||||
|
||||
$collection = parent::getRoutes($entity_type);
|
||||
|
||||
if ($reset_page_route = $this->getResetPageRoute($entity_type)) {
|
||||
$collection->add("entity.taxonomy_vocabulary.reset_form", $reset_page_route);
|
||||
}
|
||||
|
||||
if ($overview_page_route = $this->getOverviewPageRoute($entity_type)) {
|
||||
$collection->add("entity.taxonomy_vocabulary.overview_form", $overview_page_route);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getCollectionRoute(EntityTypeInterface $entity_type) {
|
||||
if ($route = parent::getCollectionRoute($entity_type)) {
|
||||
$route->setRequirement('_permission', 'access taxonomy overview+administer taxonomy');
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the reset page route.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route|null
|
||||
* The generated route, if available.
|
||||
*/
|
||||
protected function getResetPageRoute(EntityTypeInterface $entity_type) {
|
||||
$route = new Route('/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/reset');
|
||||
$route->setDefault('_entity_form', 'taxonomy_vocabulary.reset');
|
||||
$route->setDefault('_title', 'Reset');
|
||||
$route->setRequirement('_permission', $entity_type->getAdminPermission());
|
||||
$route->setOption('_admin_route', TRUE);
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the overview page route.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route|null
|
||||
* The generated route, if available.
|
||||
*/
|
||||
protected function getOverviewPageRoute(EntityTypeInterface $entity_type) {
|
||||
$route = new Route('/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/overview');
|
||||
$route->setDefault('_title_callback', 'Drupal\taxonomy\Controller\TaxonomyController::vocabularyTitle');
|
||||
$route->setDefault('_form', 'Drupal\taxonomy\Form\OverviewTerms');
|
||||
$route->setRequirement('_entity_access', 'taxonomy_vocabulary.access taxonomy overview');
|
||||
$route->setOption('_admin_route', TRUE);
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
}
|
267
2017/web/core/modules/taxonomy/src/Entity/Term.php
Normal file
267
2017/web/core/modules/taxonomy/src/Entity/Term.php
Normal file
|
@ -0,0 +1,267 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Entity;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityChangedTrait;
|
||||
use Drupal\Core\Entity\EntityPublishedTrait;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\taxonomy\TermInterface;
|
||||
use Drupal\user\StatusItem;
|
||||
|
||||
/**
|
||||
* Defines the taxonomy term entity.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "taxonomy_term",
|
||||
* label = @Translation("Taxonomy term"),
|
||||
* label_collection = @Translation("Taxonomy terms"),
|
||||
* label_singular = @Translation("taxonomy term"),
|
||||
* label_plural = @Translation("taxonomy terms"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count taxonomy term",
|
||||
* plural = "@count taxonomy terms",
|
||||
* ),
|
||||
* bundle_label = @Translation("Vocabulary"),
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\taxonomy\TermStorage",
|
||||
* "storage_schema" = "Drupal\taxonomy\TermStorageSchema",
|
||||
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
|
||||
* "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
|
||||
* "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",
|
||||
* "published" = "status",
|
||||
* },
|
||||
* 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",
|
||||
* "create" = "/taxonomy/term",
|
||||
* },
|
||||
* permission_granularity = "bundle"
|
||||
* )
|
||||
*/
|
||||
class Term extends ContentEntityBase implements TermInterface {
|
||||
|
||||
use EntityChangedTrait;
|
||||
use EntityPublishedTrait;
|
||||
|
||||
/**
|
||||
* {@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 = [];
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
foreach ($entities as $tid => $term) {
|
||||
if ($children = $storage->getChildren($term)) {
|
||||
/** @var \Drupal\taxonomy\TermInterface $child */
|
||||
foreach ($children as $child) {
|
||||
$parent = $child->get('parent');
|
||||
// Update child parents item list.
|
||||
$parent->filter(function ($item) use ($tid) {
|
||||
return $item->target_id != $tid;
|
||||
});
|
||||
|
||||
// If the term has multiple parents, we don't delete it.
|
||||
if ($parent->count()) {
|
||||
$child->save();
|
||||
}
|
||||
else {
|
||||
$orphans[] = $child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($orphans)) {
|
||||
$storage->delete($orphans);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage) {
|
||||
parent::preSave($storage);
|
||||
// Terms with no parents are mandatory children of <root>.
|
||||
if (!$this->get('parent')->count()) {
|
||||
$this->parent->target_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
// Add the published field.
|
||||
$fields += static::publishedBaseFieldDefinitions($entity_type);
|
||||
// @todo Remove the usage of StatusItem in
|
||||
// https://www.drupal.org/project/drupal/issues/2936864.
|
||||
$fields['status']->getItemDefinition()->setClass(StatusItem::class);
|
||||
|
||||
$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'))
|
||||
->setTranslatable(TRUE)
|
||||
->setRequired(TRUE)
|
||||
->setSetting('max_length', 255)
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'string',
|
||||
'weight' => -5,
|
||||
])
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'string_textfield',
|
||||
'weight' => -5,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['description'] = BaseFieldDefinition::create('text_long')
|
||||
->setLabel(t('Description'))
|
||||
->setTranslatable(TRUE)
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'text_default',
|
||||
'weight' => 0,
|
||||
])
|
||||
->setDisplayConfigurable('view', TRUE)
|
||||
->setDisplayOptions('form', [
|
||||
'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);
|
||||
|
||||
$fields['changed'] = BaseFieldDefinition::create('changed')
|
||||
->setLabel(t('Changed'))
|
||||
->setDescription(t('The time that the term was last edited.'))
|
||||
->setTranslatable(TRUE);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
|
||||
// Only terms in the same bundle can be a parent.
|
||||
$fields['parent'] = clone $base_field_definitions['parent'];
|
||||
$fields['parent']->setSetting('handler_settings', ['target_bundles' => [$bundle => $bundle]]);
|
||||
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() {
|
||||
@trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.4.0 and will be removed before 9.0.0. Use ' . __CLASS__ . '::bundle() instead to get the vocabulary ID.', E_USER_DEPRECATED);
|
||||
return $this->bundle();
|
||||
}
|
||||
|
||||
}
|
187
2017/web/core/modules/taxonomy/src/Entity/Vocabulary.php
Normal file
187
2017/web/core/modules/taxonomy/src/Entity/Vocabulary.php
Normal file
|
@ -0,0 +1,187 @@
|
|||
<?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"),
|
||||
* label_singular = @Translation("vocabulary"),
|
||||
* label_plural = @Translation("vocabularies"),
|
||||
* label_collection = @Translation("Taxonomy"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count vocabulary",
|
||||
* plural = "@count vocabularies"
|
||||
* ),
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\taxonomy\VocabularyStorage",
|
||||
* "list_builder" = "Drupal\taxonomy\VocabularyListBuilder",
|
||||
* "access" = "Drupal\taxonomy\VocabularyAccessControlHandler",
|
||||
* "form" = {
|
||||
* "default" = "Drupal\taxonomy\VocabularyForm",
|
||||
* "reset" = "Drupal\taxonomy\Form\VocabularyResetForm",
|
||||
* "delete" = "Drupal\taxonomy\Form\VocabularyDeleteForm",
|
||||
* "overview" = "Drupal\taxonomy\Form\OverviewTerms"
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "html" = "Drupal\taxonomy\Entity\Routing\VocabularyRouteProvider",
|
||||
* }
|
||||
* },
|
||||
* 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/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_MULTIPLE: 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 = [];
|
||||
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', ['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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
512
2017/web/core/modules/taxonomy/src/Form/OverviewTerms.php
Normal file
512
2017/web/core/modules/taxonomy/src/Form/OverviewTerms.php
Normal file
|
@ -0,0 +1,512 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Form;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\taxonomy\VocabularyInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides terms overview form for a taxonomy vocabulary.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class OverviewTerms extends FormBase {
|
||||
|
||||
/**
|
||||
* The module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The term storage handler.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermStorageInterface
|
||||
*/
|
||||
protected $storageController;
|
||||
|
||||
/**
|
||||
* The term list builder.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityListBuilderInterface
|
||||
*/
|
||||
protected $termListBuilder;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
*/
|
||||
public function __construct(ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager, RendererInterface $renderer = NULL) {
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->storageController = $entity_manager->getStorage('taxonomy_term');
|
||||
$this->termListBuilder = $entity_manager->getListBuilder('taxonomy_term');
|
||||
$this->renderer = $renderer ?: \Drupal::service('renderer');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('module_handler'),
|
||||
$container->get('entity.manager'),
|
||||
$container->get('renderer')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 = [];
|
||||
|
||||
$delta = 0;
|
||||
$term_deltas = [];
|
||||
$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();
|
||||
$row_position = 0;
|
||||
// Build the actual form.
|
||||
$access_control_handler = $this->entityManager->getAccessControlHandler('taxonomy_term');
|
||||
$create_access = $access_control_handler->createAccess($taxonomy_vocabulary->id(), NULL, [], TRUE);
|
||||
if ($create_access->isAllowed()) {
|
||||
$empty = $this->t('No terms available. <a href=":link">Add term</a>.', [':link' => Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $taxonomy_vocabulary->id()])->toString()]);
|
||||
}
|
||||
else {
|
||||
$empty = $this->t('No terms available.');
|
||||
}
|
||||
$form['terms'] = [
|
||||
'#type' => 'table',
|
||||
'#empty' => $empty,
|
||||
'#header' => [
|
||||
'term' => $this->t('Name'),
|
||||
'operations' => $this->t('Operations'),
|
||||
'weight' => $this->t('Weight'),
|
||||
],
|
||||
'#attributes' => [
|
||||
'id' => 'taxonomy',
|
||||
],
|
||||
];
|
||||
$this->renderer->addCacheableDependency($form['terms'], $create_access);
|
||||
|
||||
// Only allow access to changing weights if the user has update access for
|
||||
// all terms.
|
||||
$change_weight_access = AccessResult::allowed();
|
||||
foreach ($current_page as $key => $term) {
|
||||
$form['terms'][$key] = [
|
||||
'term' => [],
|
||||
'operations' => [],
|
||||
'weight' => [],
|
||||
];
|
||||
/** @var $term \Drupal\Core\Entity\EntityInterface */
|
||||
$term = $this->entityManager->getTranslationFromContext($term);
|
||||
$form['terms'][$key]['#term'] = $term;
|
||||
$indentation = [];
|
||||
if (isset($term->depth) && $term->depth > 0) {
|
||||
$indentation = [
|
||||
'#theme' => 'indentation',
|
||||
'#size' => $term->depth,
|
||||
];
|
||||
}
|
||||
$form['terms'][$key]['term'] = [
|
||||
'#prefix' => !empty($indentation) ? \Drupal::service('renderer')->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'] = [
|
||||
'#type' => 'hidden',
|
||||
'#value' => $term->id(),
|
||||
'#attributes' => [
|
||||
'class' => ['term-id'],
|
||||
],
|
||||
];
|
||||
$form['terms'][$key]['term']['parent'] = [
|
||||
'#type' => 'hidden',
|
||||
// Yes, default_value on a hidden. It needs to be changeable by the
|
||||
// javascript.
|
||||
'#default_value' => $term->parents[0],
|
||||
'#attributes' => [
|
||||
'class' => ['term-parent'],
|
||||
],
|
||||
];
|
||||
$form['terms'][$key]['term']['depth'] = [
|
||||
'#type' => 'hidden',
|
||||
// Same as above, the depth is modified by javascript, so it's a
|
||||
// default_value.
|
||||
'#default_value' => $term->depth,
|
||||
'#attributes' => [
|
||||
'class' => ['term-depth'],
|
||||
],
|
||||
];
|
||||
}
|
||||
$update_access = $term->access('update', NULL, TRUE);
|
||||
$change_weight_access = $change_weight_access->andIf($update_access);
|
||||
|
||||
if ($update_access->isAllowed()) {
|
||||
$form['terms'][$key]['weight'] = [
|
||||
'#type' => 'weight',
|
||||
'#delta' => $delta,
|
||||
'#title' => $this->t('Weight for added term'),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $term->getWeight(),
|
||||
'#attributes' => ['class' => ['term-weight']],
|
||||
];
|
||||
}
|
||||
|
||||
if ($operations = $this->termListBuilder->getOperations($term)) {
|
||||
$form['terms'][$key]['operations'] = [
|
||||
'#type' => 'operations',
|
||||
'#links' => $operations,
|
||||
];
|
||||
}
|
||||
|
||||
$form['terms'][$key]['#attributes']['class'] = [];
|
||||
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++;
|
||||
}
|
||||
|
||||
$this->renderer->addCacheableDependency($form['terms'], $change_weight_access);
|
||||
if ($change_weight_access->isAllowed()) {
|
||||
if ($parent_fields) {
|
||||
$form['terms']['#tabledrag'][] = [
|
||||
'action' => 'match',
|
||||
'relationship' => 'parent',
|
||||
'group' => 'term-parent',
|
||||
'subgroup' => 'term-parent',
|
||||
'source' => 'term-id',
|
||||
'hidden' => FALSE,
|
||||
];
|
||||
$form['terms']['#tabledrag'][] = [
|
||||
'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'][] = [
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'term-weight',
|
||||
];
|
||||
}
|
||||
|
||||
if (($taxonomy_vocabulary->getHierarchy() !== VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) && $change_weight_access->isAllowed()) {
|
||||
$form['actions'] = ['#type' => 'actions', '#tree' => FALSE];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save'),
|
||||
'#button_type' => 'primary',
|
||||
];
|
||||
$form['actions']['reset_alphabetical'] = [
|
||||
'#type' => 'submit',
|
||||
'#submit' => ['::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'), ['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 = [];
|
||||
$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 = [];
|
||||
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();
|
||||
}
|
||||
$this->messenger()->addStatus($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'));
|
||||
}
|
||||
|
||||
}
|
63
2017/web/core/modules/taxonomy/src/Form/TermDeleteForm.php
Normal file
63
2017/web/core/modules/taxonomy/src/Form/TermDeleteForm.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?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.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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.', ['%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, ['tid' => $term->id()]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityDeleteForm;
|
||||
|
||||
/**
|
||||
* Provides a deletion confirmation form for taxonomy vocabulary.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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?', ['%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.', ['%name' => $this->entity->label()]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?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.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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?', ['%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());
|
||||
|
||||
$this->messenger()->addStatus($this->t('Reset vocabulary %name to alphabetical order.', ['%name' => $this->entity->label()]));
|
||||
$this->logger('taxonomy')->notice('Reset vocabulary %name to alphabetical order.', ['%name' => $this->entity->label()]);
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\EntityReferenceSelection;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
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 defaultConfiguration() {
|
||||
return [
|
||||
'sort' => [
|
||||
'field' => 'name',
|
||||
'direction' => 'asc',
|
||||
],
|
||||
] + parent::defaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildConfigurationForm($form, $form_state);
|
||||
|
||||
// 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) {
|
||||
return parent::getReferenceableEntities($match, $match_operator, $limit);
|
||||
}
|
||||
|
||||
$options = [];
|
||||
|
||||
$bundles = $this->entityManager->getBundleInfo('taxonomy_term');
|
||||
$bundle_names = $this->getConfiguration()['target_bundles'] ?: array_keys($bundles);
|
||||
|
||||
$has_admin_access = $this->currentUser->hasPermission('administer taxonomy');
|
||||
$unpublished_terms = [];
|
||||
foreach ($bundle_names as $bundle) {
|
||||
if ($vocabulary = Vocabulary::load($bundle)) {
|
||||
/** @var \Drupal\taxonomy\TermInterface[] $terms */
|
||||
if ($terms = $this->entityManager->getStorage('taxonomy_term')->loadTree($vocabulary->id(), 0, NULL, TRUE)) {
|
||||
foreach ($terms as $term) {
|
||||
if (!$has_admin_access && (!$term->isPublished() || in_array($term->parent->target_id, $unpublished_terms))) {
|
||||
$unpublished_terms[] = $term->id();
|
||||
continue;
|
||||
}
|
||||
$options[$vocabulary->id()][$term->id()] = str_repeat('-', $term->depth) . Html::escape($this->entityManager->getTranslationFromContext($term)->label());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
|
||||
if ($match) {
|
||||
return parent::countReferenceableEntities($match, $match_operator);
|
||||
}
|
||||
|
||||
$total = 0;
|
||||
$referenceable_entities = $this->getReferenceableEntities($match, $match_operator, 0);
|
||||
foreach ($referenceable_entities as $bundle => $entities) {
|
||||
$total += count($entities);
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
|
||||
$query = parent::buildEntityQuery($match, $match_operator);
|
||||
|
||||
// Adding the 'taxonomy_term_access' tag is sadly insufficient for terms:
|
||||
// core requires us to also know about the concept of 'published' and
|
||||
// 'unpublished'.
|
||||
if (!$this->currentUser->hasPermission('administer taxonomy')) {
|
||||
$query->condition('status', 1);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
|
||||
$term = parent::createNewEntity($entity_type_id, $bundle, $label, $uid);
|
||||
|
||||
// In order to create a referenceable term, it needs to published.
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
$term->setPublished();
|
||||
|
||||
return $term;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateReferenceableNewEntities(array $entities) {
|
||||
$entities = parent::validateReferenceableNewEntities($entities);
|
||||
// Mirror the conditions checked in buildEntityQuery().
|
||||
if (!$this->currentUser->hasPermission('administer taxonomy')) {
|
||||
$entities = array_filter($entities, function ($term) {
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
return $term->isPublished();
|
||||
});
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 = [];
|
||||
|
||||
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) {
|
||||
$parent_entity->rss_elements[] = [
|
||||
'key' => 'category',
|
||||
'value' => $entity->label(),
|
||||
'attributes' => [
|
||||
'domain' => $entity->id() ? \Drupal::url('entity.taxonomy_term.canonical', ['taxonomy_term' => $entity->id()], ['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';
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
<?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\MigrationDeriverTrait;
|
||||
use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
|
||||
use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Already-instantiated field plugins, keyed by ID.
|
||||
*
|
||||
* @var \Drupal\migrate_drupal\Plugin\MigrateFieldInterface[]
|
||||
*/
|
||||
protected $fieldPluginCache;
|
||||
|
||||
/**
|
||||
* The field plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface
|
||||
*/
|
||||
protected $fieldPluginManager;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_manager
|
||||
* The field plugin manager.
|
||||
*/
|
||||
public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager, MigrateFieldPluginManagerInterface $field_manager) {
|
||||
$this->basePluginId = $base_plugin_id;
|
||||
$this->cckPluginManager = $cck_manager;
|
||||
$this->fieldPluginManager = $field_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$base_plugin_id,
|
||||
$container->get('plugin.manager.migrate.cckfield'),
|
||||
$container->get('plugin.manager.migrate.field')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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.
|
||||
}
|
||||
|
||||
$vocabulary_source_plugin = static::getSourcePlugin('d7_taxonomy_vocabulary');
|
||||
try {
|
||||
$vocabulary_source_plugin->checkRequirements();
|
||||
}
|
||||
catch (RequirementsException $e) {
|
||||
// If the d7_taxonomy_vocabulary requirements failed, that means we do not
|
||||
// have a Drupal source database configured - there is nothing to
|
||||
// generate.
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($vocabulary_source_plugin 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->fieldPluginManager->getPluginIdFromFieldType($field_type, ['core' => 7], $migration);
|
||||
if (!isset($this->fieldPluginCache[$field_type])) {
|
||||
$this->fieldPluginCache[$field_type] = $this->fieldPluginManager->createInstance($plugin_id, ['core' => 7], $migration);
|
||||
}
|
||||
$this->fieldPluginCache[$field_type]
|
||||
->defineValueProcessPipeline($migration, $field_name, $info);
|
||||
}
|
||||
catch (PluginNotFoundException $ex) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\cckfield;
|
||||
|
||||
@trigger_error('TaxonomyTermReference is deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.x. Use \Drupal\taxonomy\Plugin\migrate\field\TaxonomyTermReference instead.', E_USER_DEPRECATED);
|
||||
|
||||
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},
|
||||
* source_module = "taxonomy",
|
||||
* destination_module = "core",
|
||||
* )
|
||||
*
|
||||
* @deprecated in Drupal 8.4.x, to be removed before Drupal 9.0.x. Use
|
||||
* \Drupal\taxonomy\Plugin\migrate\field\TaxonomyTermReference instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2751897
|
||||
*/
|
||||
class TaxonomyTermReference extends CckFieldPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
|
||||
$process = [
|
||||
'plugin' => 'sub_process',
|
||||
'source' => $field_name,
|
||||
'process' => [
|
||||
'target_id' => 'tid',
|
||||
],
|
||||
];
|
||||
$migration->setProcessOfProperty($field_name, $process);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\field;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
|
||||
|
||||
/**
|
||||
* @MigrateField(
|
||||
* id = "taxonomy_term_reference",
|
||||
* type_map = {
|
||||
* "taxonomy_term_reference" = "entity_reference"
|
||||
* },
|
||||
* core = {6,7},
|
||||
* source_module = "taxonomy",
|
||||
* destination_module = "core",
|
||||
* )
|
||||
*/
|
||||
class TaxonomyTermReference extends FieldPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldFormatterMap() {
|
||||
return [
|
||||
'taxonomy_term_reference_link' => 'entity_reference_label',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
|
||||
$process = [
|
||||
'plugin' => 'sub_process',
|
||||
'source' => $field_name,
|
||||
'process' => [
|
||||
'target_id' => 'tid',
|
||||
],
|
||||
];
|
||||
$migration->setProcessOfProperty($field_name, $process);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Checks if the vocabulary being migrated is the one used for forums.
|
||||
*
|
||||
* Drupal 8 Forum is expecting specific machine names for its field and
|
||||
* vocabulary names. This process plugin forces a given machine name to the
|
||||
* field or vocabulary that is being migrated.
|
||||
*
|
||||
* The 'forum_vocabulary' source property is evaluated in the
|
||||
* d6_taxonomy_vocabulary or d7_taxonomy_vocabulary source plugins and is set to
|
||||
* true if the vocabulary vid being migrated is the same as the one in the
|
||||
* 'forum_nav_vocabulary' variable on the source site.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* field_name:
|
||||
* plugin: forum_vocabulary
|
||||
* machine_name: taxonomy_forums
|
||||
* @endcode
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "forum_vocabulary"
|
||||
* )
|
||||
*/
|
||||
class ForumVocabulary extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if ($row->getSourceProperty('forum_vocabulary') && !empty($this->configuration['machine_name'])) {
|
||||
$value = $this->configuration['machine_name'];
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
<?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_module = "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.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2879193
|
||||
*/
|
||||
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 = [
|
||||
'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', ['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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<?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_module = "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."),
|
||||
];
|
||||
if (isset($this->configuration['translations'])) {
|
||||
$fields['language'] = $this->t('The term language.');
|
||||
$fields['trid'] = $this->t('Translation ID.');
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Gets i18n taxonomy terms from source database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_term_localized_translation",
|
||||
* source_module = "i18ntaxonomy"
|
||||
* )
|
||||
*/
|
||||
class TermLocalizedTranslation extends Term {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
// Ideally, the query would return rows for each language for each taxonomy
|
||||
// term with the translations for both the name and description or just the
|
||||
// name translation or just the description translation. That query quickly
|
||||
// became complex and would be difficult to maintain.
|
||||
// Therefore, build a query based on i18nstrings table where each row has
|
||||
// the translation for only one property, either name or description. The
|
||||
// method prepareRow() is then used to obtain the translation for the other
|
||||
// property.
|
||||
$query = parent::query();
|
||||
$query->addField('td', 'language', 'td.language');
|
||||
|
||||
// Add in the property, which is either name or description.
|
||||
// Cast td.tid as char for PostgreSQL compatibility.
|
||||
$query->leftJoin('i18n_strings', 'i18n', 'CAST(td.tid AS CHAR(255)) = i18n.objectid');
|
||||
$query->isNotNull('i18n.lid');
|
||||
$query->addField('i18n', 'lid');
|
||||
$query->addField('i18n', 'property');
|
||||
|
||||
// Add in the translation for the property.
|
||||
$query->innerJoin('locales_target', 'lt', 'i18n.lid = lt.lid');
|
||||
$query->addField('lt', 'language', 'lt.language');
|
||||
$query->addField('lt', 'translation');
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$language = $row->getSourceProperty('ltlanguage');
|
||||
$row->setSourceProperty('language', $language);
|
||||
$tid = $row->getSourceProperty('tid');
|
||||
|
||||
// If this row has been migrated it is a duplicate then skip it.
|
||||
if ($this->idMap->lookupDestinationIds(['tid' => $tid, 'language' => $language])) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Save the translation for this property.
|
||||
$property = $row->getSourceProperty('property');
|
||||
$row->setSourceProperty($property . '_translated', $row->getSourceProperty('translation'));
|
||||
|
||||
// Get the translation, if one exists, for the property not already in the
|
||||
// row.
|
||||
$other_property = ($property == 'name') ? 'description' : 'name';
|
||||
$query = $this->select('i18n_strings', 'i18n')
|
||||
->fields('i18n', ['lid'])
|
||||
->condition('i18n.property', $other_property)
|
||||
->condition('i18n.objectid', $tid);
|
||||
$query->leftJoin('locales_target', 'lt', 'i18n.lid = lt.lid');
|
||||
$query->condition('lt.language', $language);
|
||||
$query->addField('lt', 'translation');
|
||||
$results = $query->execute()->fetchAssoc();
|
||||
$row->setSourceProperty($other_property . '_translated', $results['translation']);
|
||||
|
||||
parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'language' => $this->t('Language for this term.'),
|
||||
'name_translated' => $this->t('Term name translation.'),
|
||||
'description_translated' => $this->t('Term description translation.'),
|
||||
];
|
||||
return parent::fields() + $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['language']['type'] = 'string';
|
||||
$ids['language']['alias'] = 'lt';
|
||||
return parent::getIds() + $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -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_module = "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', ['nid', 'vid'])
|
||||
->fields('n', ['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', [':vid' => $this->configuration['vid']]);
|
||||
$query->innerJoin('node', 'n', static::JOIN);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'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', ['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', [':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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?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",
|
||||
* source_module = "taxonomy"
|
||||
* )
|
||||
*/
|
||||
class TermNodeRevision extends TermNode {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
const JOIN = 'tn.nid = n.nid AND tn.vid != n.vid';
|
||||
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?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_module = "taxonomy"
|
||||
* )
|
||||
*/
|
||||
class Vocabulary extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('vocabulary', 'v')
|
||||
->fields('v', [
|
||||
'vid',
|
||||
'name',
|
||||
'description',
|
||||
'help',
|
||||
'relations',
|
||||
'hierarchy',
|
||||
'multiple',
|
||||
'required',
|
||||
'tags',
|
||||
'module',
|
||||
'weight',
|
||||
]);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'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', ['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);
|
||||
|
||||
// If the vocabulary being migrated is the one defined in the
|
||||
// 'forum_nav_vocabulary' variable, set the 'forum_vocabulary' source
|
||||
// property to true so we know this is the vocabulary used by Forum.
|
||||
if ($this->variableGet('forum_nav_vocabulary', 0) == $row->getSourceProperty('vid')) {
|
||||
$row->setSourceProperty('forum_vocabulary', TRUE);
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['vid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Gets all the vocabularies based on the node types that have Taxonomy enabled.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_taxonomy_vocabulary_per_type",
|
||||
* source_module = "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', ['type']);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
// Get the i18n taxonomy translation setting for this vocabulary.
|
||||
// 0 - No multilingual options
|
||||
// 1 - Localizable terms. Run through the localization system.
|
||||
// 2 - Predefined language for a vocabulary and its terms.
|
||||
// 3 - Per-language terms, translatable (referencing terms with different
|
||||
// languages) but not localizable.
|
||||
$i18ntaxonomy_vocab = $this->variableGet('i18ntaxonomy_vocabulary', NULL);
|
||||
$vid = $row->getSourceProperty('vid');
|
||||
$i18ntaxonomy_vocabulary = FALSE;
|
||||
if (array_key_exists($vid, $i18ntaxonomy_vocab)) {
|
||||
$i18ntaxonomy_vocabulary = $i18ntaxonomy_vocab[$vid];
|
||||
}
|
||||
$row->setSourceProperty('i18ntaxonomy_vocabulary', $i18ntaxonomy_vocabulary);
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['vid']['type'] = 'integer';
|
||||
$ids['vid']['alias'] = 'nt';
|
||||
$ids['type']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 6 vocabulary translations from source database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_taxonomy_vocabulary_translation",
|
||||
* source_module = "i18ntaxonomy"
|
||||
* )
|
||||
*/
|
||||
class VocabularyTranslation extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('vocabulary', 'v')
|
||||
->fields('v', ['vid', 'name', 'description'])
|
||||
->fields('i18n', ['lid', 'type', 'property', 'objectid'])
|
||||
->fields('lt', ['lid', 'translation'])
|
||||
->condition('i18n.type', 'vocabulary');
|
||||
$query->addField('lt', 'language', 'language');
|
||||
// The i18n_strings table has two columns containing the object ID, objectid
|
||||
// and objectindex. The objectid column is a text field. Therefore, for the
|
||||
// join to work in PostgreSQL, use the objectindex field as this is numeric
|
||||
// like the vid field.
|
||||
$query->join('i18n_strings', 'i18n', 'v.vid = i18n.objectindex');
|
||||
$query->leftJoin('locales_target', 'lt', 'lt.lid = i18n.lid');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'vid' => $this->t('The vocabulary ID.'),
|
||||
'language' => $this->t('Language for this field.'),
|
||||
'property' => $this->t('Name of property being translated.'),
|
||||
'translation' => $this->t('Translation of either the title or explanation.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['vid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?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_module = "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) {
|
||||
$tid = $row->getSourceProperty('tid');
|
||||
$vocabulary = $row->getSourceProperty('machine_name');
|
||||
$default_language = (array) $this->variableGet('language_default', ['language' => 'en']);
|
||||
|
||||
// If this entity was translated using Entity Translation, we need to get
|
||||
// its source language to get the field values in the right language.
|
||||
// The translations will be migrated by the d7_node_entity_translation
|
||||
// migration.
|
||||
$translatable_vocabularies = array_keys(array_filter($this->variableGet('entity_translation_taxonomy', [])));
|
||||
$entity_translatable = $this->isEntityTranslatable('taxonomy_term') && in_array($vocabulary, $translatable_vocabularies, TRUE);
|
||||
$source_language = $this->getEntityTranslationSourceLanguage('taxonomy_term', $tid);
|
||||
$language = $entity_translatable && $source_language ? $source_language : $default_language['language'];
|
||||
$row->setSourceProperty('language', $language);
|
||||
|
||||
// Get Field API field values.
|
||||
foreach ($this->getFields('taxonomy_term', $vocabulary) as $field_name => $field) {
|
||||
// Ensure we're using the right language if the entity and the field are
|
||||
// translatable.
|
||||
$field_language = $entity_translatable && $field['translatable'] ? $language : NULL;
|
||||
$row->setSourceProperty($field_name, $this->getFieldValues('taxonomy_term', $field_name, $tid, NULL, $field_language));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Determine if this is a forum container.
|
||||
$forum_container_tids = $this->variableGet('forum_containers', []);
|
||||
$current_tid = $row->getSourceProperty('tid');
|
||||
$row->setSourceProperty('is_container', in_array($current_tid, $forum_container_tids));
|
||||
|
||||
// If the term name or term description were replaced by real fields using
|
||||
// the Drupal 7 Title module, use the fields value instead of the term name
|
||||
// or term description.
|
||||
if ($this->moduleExists('title')) {
|
||||
$name_field = $row->getSourceProperty('name_field');
|
||||
if (isset($name_field[0]['value'])) {
|
||||
$row->setSourceProperty('name', $name_field[0]['value']);
|
||||
}
|
||||
$description_field = $row->getSourceProperty('description_field');
|
||||
if (isset($description_field[0]['value'])) {
|
||||
$row->setSourceProperty('description', $description_field[0]['value']);
|
||||
}
|
||||
if (isset($description_field[0]['format'])) {
|
||||
$row->setSourceProperty('format', $description_field[0]['format']);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['tid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
|
||||
|
||||
/**
|
||||
* Provides Drupal 7 taxonomy term entity translation source plugin.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_taxonomy_term_entity_translation",
|
||||
* source_module = "entity_translation"
|
||||
* )
|
||||
*/
|
||||
class TermEntityTranslation extends FieldableEntity {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('entity_translation', 'et')
|
||||
->fields('et')
|
||||
->fields('td', [
|
||||
'name',
|
||||
'description',
|
||||
'format',
|
||||
])
|
||||
->fields('tv', [
|
||||
'machine_name',
|
||||
])
|
||||
->condition('et.entity_type', 'taxonomy_term')
|
||||
->condition('et.source', '', '<>');
|
||||
|
||||
$query->innerJoin('taxonomy_term_data', 'td', 'td.tid = et.entity_id');
|
||||
$query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
|
||||
|
||||
if (isset($this->configuration['bundle'])) {
|
||||
$query->condition('tv.machine_name', (array) $this->configuration['bundle'], 'IN');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$tid = $row->getSourceProperty('entity_id');
|
||||
$vocabulary = $row->getSourceProperty('machine_name');
|
||||
$language = $row->getSourceProperty('language');
|
||||
|
||||
// Get Field API field values.
|
||||
foreach ($this->getFields('taxonomy_term', $vocabulary) as $field_name => $field) {
|
||||
// Ensure we're using the right language if the entity is translatable.
|
||||
$field_language = $field['translatable'] ? $language : NULL;
|
||||
$row->setSourceProperty($field_name, $this->getFieldValues('taxonomy_term', $field_name, $tid, NULL, $field_language));
|
||||
}
|
||||
|
||||
// If the term name or term description were replaced by real fields using
|
||||
// the Drupal 7 Title module, use the fields value instead of the term name
|
||||
// or term description.
|
||||
if ($this->moduleExists('title')) {
|
||||
$name_field = $row->getSourceProperty('name_field');
|
||||
if (isset($name_field[0]['value'])) {
|
||||
$row->setSourceProperty('name', $name_field[0]['value']);
|
||||
}
|
||||
$description_field = $row->getSourceProperty('description_field');
|
||||
if (isset($description_field[0]['value'])) {
|
||||
$row->setSourceProperty('description', $description_field[0]['value']);
|
||||
}
|
||||
if (isset($description_field[0]['format'])) {
|
||||
$row->setSourceProperty('format', $description_field[0]['format']);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if this is a forum container.
|
||||
$forum_container_tids = $this->variableGet('forum_containers', []);
|
||||
$row->setSourceProperty('is_container', in_array($tid, $forum_container_tids));
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'entity_type' => $this->t('The entity type this translation relates to'),
|
||||
'entity_id' => $this->t('The entity ID this translation relates to'),
|
||||
'revision_id' => $this->t('The entity revision ID this translation relates to'),
|
||||
'language' => $this->t('The target language for this translation.'),
|
||||
'source' => $this->t('The source language from which this translation was created.'),
|
||||
'uid' => $this->t('The author of this translation.'),
|
||||
'status' => $this->t('Boolean indicating whether the translation is published (visible to non-administrators).'),
|
||||
'translate' => $this->t('A boolean indicating whether this translation needs to be updated.'),
|
||||
'created' => $this->t('The Unix timestamp when the translation was created.'),
|
||||
'changed' => $this->t('The Unix timestamp when the translation was most recently saved.'),
|
||||
'name' => $this->t('The name of the term.'),
|
||||
'description' => $this->t('The term description.'),
|
||||
'format' => $this->t('Format of the term description.'),
|
||||
'machine_name' => $this->t('Vocabulary machine name'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return [
|
||||
'entity_id' => [
|
||||
'type' => 'integer',
|
||||
'alias' => 'et',
|
||||
],
|
||||
'language' => [
|
||||
'type' => 'string',
|
||||
'alias' => 'et',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 7 vocabularies source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_taxonomy_vocabulary",
|
||||
* source_module = "taxonomy"
|
||||
* )
|
||||
*/
|
||||
class Vocabulary extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('taxonomy_vocabulary', 'v')
|
||||
->fields('v', [
|
||||
'vid',
|
||||
'name',
|
||||
'description',
|
||||
'hierarchy',
|
||||
'module',
|
||||
'weight',
|
||||
'machine_name',
|
||||
]);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'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 prepareRow(Row $row) {
|
||||
// If the vocabulary being migrated is the one defined in the
|
||||
// 'forum_nav_vocabulary' variable, set the 'forum_vocabulary' source
|
||||
// property to true so we know this is the vocabulary used by Forum.
|
||||
if ($this->variableGet('forum_nav_vocabulary', 0) == $row->getSourceProperty('vid')) {
|
||||
$row->setSourceProperty('forum_vocabulary', TRUE);
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['vid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 = [];
|
||||
$terms = Term::loadMultiple($this->value);
|
||||
foreach ($terms as $term) {
|
||||
$titles[] = \Drupal::entityManager()->getTranslationFromContext($term)->label();
|
||||
}
|
||||
return $titles;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\argument;
|
||||
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
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 \Drupal\Core\Entity\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'] = ['default' => 0];
|
||||
$options['break_phrase'] = ['default' => FALSE];
|
||||
$options['use_taxonomy_term_path'] = ['default' => FALSE];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$form['depth'] = [
|
||||
'#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'] = [
|
||||
'#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, ['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 === [-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 = (new Condition('OR'))->condition('tn.tid', $tids, $operator);
|
||||
$last = "tn";
|
||||
|
||||
if ($this->options['depth'] > 0) {
|
||||
$subquery->leftJoin('taxonomy_term__parent', 'th', "th.entity_id = tn.tid");
|
||||
$last = "th";
|
||||
foreach (range(1, abs($this->options['depth'])) as $count) {
|
||||
$subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.parent_target_id = th$count.entity_id");
|
||||
$where->condition("th$count.entity_id", $tids, $operator);
|
||||
$last = "th$count";
|
||||
}
|
||||
}
|
||||
elseif ($this->options['depth'] < 0) {
|
||||
foreach (range(1, abs($this->options['depth'])) as $count) {
|
||||
$field = $count == 1 ? 'tid' : 'entity_id';
|
||||
$subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.$field = th$count.parent_target_id");
|
||||
$where->condition("th$count.entity_id", $tids, $operator);
|
||||
$last = "th$count";
|
||||
}
|
||||
}
|
||||
|
||||
$subquery->condition($where);
|
||||
$this->query->addWhere(0, "$this->tableAlias.$this->realField", $subquery, 'IN');
|
||||
}
|
||||
|
||||
public function title() {
|
||||
$term = $this->termStorage->load($this->argument);
|
||||
if (!empty($term)) {
|
||||
return $term->getName();
|
||||
}
|
||||
// TODO review text
|
||||
return $this->t('No name');
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 \Drupal\Core\Entity\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.
|
||||
*/
|
||||
public 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');
|
||||
}
|
||||
|
||||
}
|
|
@ -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 \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')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the behavior of title(). Get the name of the vocabulary.
|
||||
*/
|
||||
public function title() {
|
||||
$vocabulary = $this->vocabularyStorage->load($this->argument);
|
||||
if ($vocabulary) {
|
||||
return $vocabulary->label();
|
||||
}
|
||||
|
||||
return $this->t('No vocabulary');
|
||||
}
|
||||
|
||||
}
|
|
@ -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'] = ['default' => TRUE];
|
||||
$options['node'] = ['default' => FALSE];
|
||||
$options['anyall'] = ['default' => ','];
|
||||
$options['limit'] = ['default' => FALSE];
|
||||
$options['vids'] = ['default' => []];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$form['term_page'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Load default filter from term page'),
|
||||
'#default_value' => $this->options['term_page'],
|
||||
];
|
||||
$form['node'] = [
|
||||
'#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'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Limit terms by vocabulary'),
|
||||
'#default_value' => $this->options['limit'],
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[argument_default][taxonomy_tid][node]"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$options = [];
|
||||
$vocabularies = $this->vocabularyStorage->loadMultiple();
|
||||
foreach ($vocabularies as $voc) {
|
||||
$options[$voc->id()] = $voc->label();
|
||||
}
|
||||
|
||||
$form['vids'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Vocabularies'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->options['vids'],
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[argument_default][taxonomy_tid][limit]"]' => ['checked' => TRUE],
|
||||
':input[name="options[argument_default][taxonomy_tid][node]"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$form['anyall'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Multiple-value handling'),
|
||||
'#default_value' => $this->options['anyall'],
|
||||
'#options' => [
|
||||
',' => $this->t('Filter to items that share all terms'),
|
||||
'+' => $this->t('Filter to items that share any term'),
|
||||
],
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[argument_default][taxonomy_tid][node]"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitOptionsForm(&$form, FormStateInterface $form_state, &$options = []) {
|
||||
// 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 = [];
|
||||
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->bundle();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($this->options['limit'])) {
|
||||
$tids = [];
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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'] = ['default' => FALSE];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
|
||||
$form['transform'] = [
|
||||
'#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(['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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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'] = ['table' => 'node_field_revision', 'field' => 'nid'];
|
||||
}
|
||||
else {
|
||||
$this->additional_fields['nid'] = ['table' => 'node_field_data', 'field' => 'nid'];
|
||||
}
|
||||
}
|
||||
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
|
||||
$options['link_to_taxonomy'] = ['default' => TRUE];
|
||||
$options['limit'] = ['default' => FALSE];
|
||||
$options['vids'] = ['default' => []];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide "link to term" option.
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$form['link_to_taxonomy'] = [
|
||||
'#title' => $this->t('Link this field to its term page'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => !empty($this->options['link_to_taxonomy']),
|
||||
];
|
||||
|
||||
$form['limit'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Limit terms by vocabulary'),
|
||||
'#default_value' => $this->options['limit'],
|
||||
];
|
||||
|
||||
$options = [];
|
||||
$vocabularies = $this->vocabularyStorage->loadMultiple();
|
||||
foreach ($vocabularies as $voc) {
|
||||
$options[$voc->id()] = $voc->label();
|
||||
}
|
||||
|
||||
$form['vids'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Vocabularies'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->options['vids'],
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[limit]"]' => ['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 = [];
|
||||
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 = [];
|
||||
}
|
||||
$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->bundle();
|
||||
$this->items[$node_nid][$tid]['vocabulary'] = $vocabularies[$term->bundle()]->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public 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 (['tid', 'name', 'vocabulary_vid', 'vocabulary'] as $token) {
|
||||
$tokens['{{ ' . $this->options['id'] . '__' . $token . ' }}'] = isset($item[$token]) ? $item[$token] : '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\field;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\views\Plugin\views\field\EntityField;
|
||||
use Drupal\views\ResultRow;
|
||||
|
||||
/**
|
||||
* Displays taxonomy term names and allows converting spaces to hyphens.
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*
|
||||
* @ViewsField("term_name")
|
||||
*/
|
||||
class TermName extends EntityField {
|
||||
|
||||
/**
|
||||
* {@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'] = ['default' => FALSE];
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$form['convert_spaces'] = [
|
||||
'#title' => $this->t('Convert spaces in term names to hyphens'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => !empty($this->options['convert_spaces']),
|
||||
];
|
||||
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,403 @@
|
|||
<?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'] = ['default' => 'textfield'];
|
||||
$options['limit'] = ['default' => TRUE];
|
||||
$options['vid'] = ['default' => ''];
|
||||
$options['hierarchy'] = ['default' => FALSE];
|
||||
$options['error_message'] = ['default' => TRUE];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$vocabularies = $this->vocabularyStorage->loadMultiple();
|
||||
$options = [];
|
||||
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'] = [
|
||||
'#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'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Selection type'),
|
||||
'#options' => ['select' => $this->t('Dropdown'), 'textfield' => $this->t('Autocomplete')],
|
||||
'#default_value' => $this->options['type'],
|
||||
];
|
||||
|
||||
$form['hierarchy'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Show hierarchy in dropdown'),
|
||||
'#default_value' => !empty($this->options['hierarchy']),
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[type]"]' => ['value' => 'select'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function valueForm(&$form, FormStateInterface $form_state) {
|
||||
$vocabulary = $this->vocabularyStorage->load($this->options['vid']);
|
||||
if (empty($vocabulary) && $this->options['limit']) {
|
||||
$form['markup'] = [
|
||||
'#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)) : [];
|
||||
$form['value'] = [
|
||||
'#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', ['@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'] = [$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 = [];
|
||||
|
||||
if ($tree) {
|
||||
foreach ($tree as $term) {
|
||||
$choice = new \stdClass();
|
||||
$choice->option = [$term->id() => str_repeat('-', $term->depth) . \Drupal::entityManager()->getTranslationFromContext($term)->label()];
|
||||
$options[] = $choice;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$options = [];
|
||||
$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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
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 == ['']) {
|
||||
$default_value = 'All';
|
||||
}
|
||||
else {
|
||||
$copy = $default_value;
|
||||
$default_value = array_shift($copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
$form['value'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', ['@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 = [];
|
||||
if ($values = $form_state->getValue(['options', 'value'])) {
|
||||
foreach ($values as $value) {
|
||||
$tids[] = $value['target_id'];
|
||||
}
|
||||
}
|
||||
$form_state->setValue(['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'] = [
|
||||
'#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 = [];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\filter;
|
||||
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
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 [
|
||||
'or' => $this->t('Is one of'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
|
||||
$options['depth'] = ['default' => 0];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildExtraOptionsForm($form, $form_state);
|
||||
|
||||
$form['depth'] = [
|
||||
'#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';
|
||||
}
|
||||
|
||||
// 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 = (new Condition('OR'))->condition('tn.tid', $this->value, $operator);
|
||||
$last = "tn";
|
||||
|
||||
if ($this->options['depth'] > 0) {
|
||||
$subquery->leftJoin('taxonomy_term__parent', 'th', "th.entity_id = tn.tid");
|
||||
$last = "th";
|
||||
foreach (range(1, abs($this->options['depth'])) as $count) {
|
||||
$subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.parent_target_id = th$count.entity_id");
|
||||
$where->condition("th$count.entity_id", $this->value, $operator);
|
||||
$last = "th$count";
|
||||
}
|
||||
}
|
||||
elseif ($this->options['depth'] < 0) {
|
||||
foreach (range(1, abs($this->options['depth'])) as $count) {
|
||||
$field = $count == 1 ? 'tid' : 'entity_id';
|
||||
$subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.$field = th$count.parent_target_id");
|
||||
$where->condition("th$count.entity_id", $this->value, $operator);
|
||||
$last = "th$count";
|
||||
}
|
||||
}
|
||||
|
||||
$subquery->condition($where);
|
||||
$this->query->addWhere($this->options['group'], "$this->tableAlias.$this->realField", $subquery, 'IN');
|
||||
}
|
||||
|
||||
}
|
|
@ -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'] = ['default' => []];
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$vocabularies = $this->vocabularyStorage->loadMultiple();
|
||||
$options = [];
|
||||
foreach ($vocabularies as $voc) {
|
||||
$options[$voc->id()] = $voc->label();
|
||||
}
|
||||
|
||||
$form['vids'] = [
|
||||
'#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', ['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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
78
2017/web/core/modules/taxonomy/src/TaxonomyPermissions.php
Normal file
78
2017/web/core/modules/taxonomy/src/TaxonomyPermissions.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
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 (Vocabulary::loadMultiple() as $vocabulary) {
|
||||
$permissions += $this->buildPermissions($vocabulary);
|
||||
}
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a standard list of taxonomy term permissions for a given vocabulary.
|
||||
*
|
||||
* @param \Drupal\taxonomy\VocabularyInterface $vocabulary
|
||||
* The vocabulary.
|
||||
*
|
||||
* @return array
|
||||
* An array of permission names and descriptions.
|
||||
*/
|
||||
protected function buildPermissions(VocabularyInterface $vocabulary) {
|
||||
$id = $vocabulary->id();
|
||||
$args = ['%vocabulary' => $vocabulary->label()];
|
||||
|
||||
return [
|
||||
"create terms in $id" => ['title' => $this->t('%vocabulary: Create terms', $args)],
|
||||
"delete terms in $id" => ['title' => $this->t('%vocabulary: Delete terms', $args)],
|
||||
"edit terms in $id" => ['title' => $this->t('%vocabulary: Edit terms', $args)],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?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) {
|
||||
if ($account->hasPermission('administer taxonomy')) {
|
||||
return AccessResult::allowed()->cachePerPermissions();
|
||||
}
|
||||
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
$access_result = AccessResult::allowedIf($account->hasPermission('access content') && $entity->isPublished())
|
||||
->cachePerPermissions()
|
||||
->addCacheableDependency($entity);
|
||||
if (!$access_result->isAllowed()) {
|
||||
$access_result->setReason("The 'access content' permission is required and the taxonomy term must be published.");
|
||||
}
|
||||
return $access_result;
|
||||
|
||||
case 'update':
|
||||
if ($account->hasPermission("edit terms in {$entity->bundle()}")) {
|
||||
return AccessResult::allowed()->cachePerPermissions();
|
||||
}
|
||||
|
||||
return AccessResult::neutral()->setReason("The following permissions are required: 'edit terms in {$entity->bundle()}' OR 'administer taxonomy'.");
|
||||
|
||||
case 'delete':
|
||||
if ($account->hasPermission("delete terms in {$entity->bundle()}")) {
|
||||
return AccessResult::allowed()->cachePerPermissions();
|
||||
}
|
||||
|
||||
return AccessResult::neutral()->setReason("The following permissions are required: 'delete terms in {$entity->bundle()}' OR 'administer taxonomy'.");
|
||||
|
||||
default:
|
||||
// No opinion.
|
||||
return AccessResult::neutral()->cachePerPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
|
||||
return AccessResult::allowedIfHasPermissions($account, ["create terms in $entity_bundle", 'administer taxonomy'], 'OR');
|
||||
}
|
||||
|
||||
}
|
81
2017/web/core/modules/taxonomy/src/TermBreadcrumbBuilder.php
Normal file
81
2017/web/core/modules/taxonomy/src/TermBreadcrumbBuilder.php
Normal 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', ['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;
|
||||
}
|
||||
|
||||
}
|
169
2017/web/core/modules/taxonomy/src/TermForm.php
Normal file
169
2017/web/core/modules/taxonomy/src/TermForm.php
Normal file
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Base for handler for taxonomy term edit forms.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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'] = [
|
||||
'#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')) {
|
||||
$exclude = [];
|
||||
if (!$term->isNew()) {
|
||||
$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 = ['<' . $this->t('root') . '>'];
|
||||
if (empty($parent)) {
|
||||
$parent = [0];
|
||||
}
|
||||
|
||||
foreach ($tree as $item) {
|
||||
if (!in_array($item->tid, $exclude)) {
|
||||
$options[$item->tid] = str_repeat('-', $item->depth) . $item->name;
|
||||
}
|
||||
}
|
||||
|
||||
$form['relations']['parent'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Parent terms'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $parent,
|
||||
'#multiple' => TRUE,
|
||||
];
|
||||
}
|
||||
|
||||
$form['relations']['weight'] = [
|
||||
'#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'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $vocabulary->id(),
|
||||
];
|
||||
|
||||
$form['tid'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $term->id(),
|
||||
];
|
||||
|
||||
return parent::form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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:
|
||||
$this->messenger()->addStatus($this->t('Created new term %term.', ['%term' => $view_link]));
|
||||
$this->logger('taxonomy')->notice('Created new term %term.', ['%term' => $term->getName(), 'link' => $edit_link]);
|
||||
break;
|
||||
case SAVED_UPDATED:
|
||||
$this->messenger()->addStatus($this->t('Updated term %term.', ['%term' => $view_link]));
|
||||
$this->logger('taxonomy')->notice('Updated term %term.', ['%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(['parent', 0])) {
|
||||
$current_parent_count = 0;
|
||||
$form_state->setValue('parent', []);
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
}
|
97
2017/web/core/modules/taxonomy/src/TermInterface.php
Normal file
97
2017/web/core/modules/taxonomy/src/TermInterface.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a taxonomy term entity.
|
||||
*/
|
||||
interface TermInterface extends ContentEntityInterface, EntityChangedInterface, EntityPublishedInterface {
|
||||
|
||||
/**
|
||||
* 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 string $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 string
|
||||
* The id of the vocabulary.
|
||||
*
|
||||
* @deprecated Scheduled for removal before Drupal 9.0.0. Use
|
||||
* TermInterface::bundle() instead.
|
||||
*/
|
||||
public function getVocabularyId();
|
||||
|
||||
}
|
383
2017/web/core/modules/taxonomy/src/TermStorage.php
Normal file
383
2017/web/core/modules/taxonomy/src/TermStorage.php
Normal file
|
@ -0,0 +1,383 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
|
||||
/**
|
||||
* Defines a Controller class for taxonomy terms.
|
||||
*/
|
||||
class TermStorage extends SqlContentEntityStorage implements TermStorageInterface {
|
||||
|
||||
/**
|
||||
* Array of term parents keyed by vocabulary ID and child term ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $treeParents = [];
|
||||
|
||||
/**
|
||||
* Array of term ancestors keyed by vocabulary ID and parent term ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $treeChildren = [];
|
||||
|
||||
/**
|
||||
* Array of terms in a tree keyed by vocabulary ID and term ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $treeTerms = [];
|
||||
|
||||
/**
|
||||
* Array of loaded trees keyed by a cache id matching tree arguments.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $trees = [];
|
||||
|
||||
/**
|
||||
* Array of all loaded term ancestry keyed by ancestor term ID, keyed by term
|
||||
* ID.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermInterface[][]
|
||||
*/
|
||||
protected $ancestors;
|
||||
|
||||
/**
|
||||
* {@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 = []) {
|
||||
// Save new terms with no parents by default.
|
||||
if (empty($values['parent'])) {
|
||||
$values['parent'] = [0];
|
||||
}
|
||||
$entity = parent::create($values);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetCache(array $ids = NULL) {
|
||||
drupal_static_reset('taxonomy_term_count_nodes');
|
||||
$this->ancestors = [];
|
||||
$this->treeChildren = [];
|
||||
$this->treeParents = [];
|
||||
$this->treeTerms = [];
|
||||
$this->trees = [];
|
||||
parent::resetCache($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteTermHierarchy($tids) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateTermHierarchy(EntityInterface $term) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadParents($tid) {
|
||||
$terms = [];
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
if ($tid && $term = $this->load($tid)) {
|
||||
foreach ($this->getParents($term) as $id => $parent) {
|
||||
// This method currently doesn't return the <root> parent.
|
||||
// @see https://www.drupal.org/node/2019905
|
||||
if (!empty($id)) {
|
||||
$terms[$id] = $parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $terms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of parents of this term.
|
||||
*
|
||||
* @return \Drupal\taxonomy\TermInterface[]
|
||||
* The parent taxonomy term entities keyed by term ID. If this term has a
|
||||
* <root> parent, that item is keyed with 0 and will have NULL as value.
|
||||
*
|
||||
* @internal
|
||||
* @todo Refactor away when TreeInterface is introduced.
|
||||
*/
|
||||
protected function getParents(TermInterface $term) {
|
||||
$parents = $ids = [];
|
||||
// Cannot use $this->get('parent')->referencedEntities() here because that
|
||||
// strips out the '0' reference.
|
||||
foreach ($term->get('parent') as $item) {
|
||||
if ($item->target_id == 0) {
|
||||
// The <root> parent.
|
||||
$parents[0] = NULL;
|
||||
continue;
|
||||
}
|
||||
$ids[] = $item->target_id;
|
||||
}
|
||||
|
||||
// @todo Better way to do this? AND handle the NULL/0 parent?
|
||||
// Querying the terms again so that the same access checks are run when
|
||||
// getParents() is called as in Drupal version prior to 8.3.
|
||||
$loaded_parents = [];
|
||||
|
||||
if ($ids) {
|
||||
$query = \Drupal::entityQuery('taxonomy_term')
|
||||
->condition('tid', $ids, 'IN');
|
||||
|
||||
$loaded_parents = static::loadMultiple($query->execute());
|
||||
}
|
||||
|
||||
return $parents + $loaded_parents;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadAllParents($tid) {
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
return (!empty($tid) && $term = $this->load($tid)) ? $this->getAncestors($term) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all ancestors of this term.
|
||||
*
|
||||
* @return \Drupal\taxonomy\TermInterface[]
|
||||
* A list of ancestor taxonomy term entities keyed by term ID.
|
||||
*
|
||||
* @internal
|
||||
* @todo Refactor away when TreeInterface is introduced.
|
||||
*/
|
||||
protected function getAncestors(TermInterface $term) {
|
||||
if (!isset($this->ancestors[$term->id()])) {
|
||||
$this->ancestors[$term->id()] = [$term->id() => $term];
|
||||
$search[] = $term->id();
|
||||
|
||||
while ($tid = array_shift($search)) {
|
||||
foreach ($this->getParents(static::load($tid)) as $id => $parent) {
|
||||
if ($parent && !isset($this->ancestors[$term->id()][$id])) {
|
||||
$this->ancestors[$term->id()][$id] = $parent;
|
||||
$search[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->ancestors[$term->id()];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadChildren($tid, $vid = NULL) {
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
return (!empty($tid) && $term = $this->load($tid)) ? $this->getChildren($term) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all children terms of this term.
|
||||
*
|
||||
* @return \Drupal\taxonomy\TermInterface[]
|
||||
* A list of children taxonomy term entities keyed by term ID.
|
||||
*
|
||||
* @internal
|
||||
* @todo Refactor away when TreeInterface is introduced.
|
||||
*/
|
||||
public function getChildren(TermInterface $term) {
|
||||
$query = \Drupal::entityQuery('taxonomy_term')
|
||||
->condition('parent', $term->id());
|
||||
return static::loadMultiple($query->execute());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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] = [];
|
||||
$this->treeParents[$vid] = [];
|
||||
$this->treeTerms[$vid] = [];
|
||||
$query = $this->database->select($this->getDataTable(), 't');
|
||||
$query->join('taxonomy_term__parent', 'p', 't.tid = p.entity_id');
|
||||
$query->addExpression('parent_target_id', 'parent');
|
||||
$result = $query
|
||||
->addTag('taxonomy_term_access')
|
||||
->fields('t')
|
||||
->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 = [];
|
||||
if ($load_entities) {
|
||||
$term_entities = $this->loadMultiple(array_keys($this->treeTerms[$vid]));
|
||||
}
|
||||
|
||||
$max_depth = (!isset($max_depth)) ? count($this->treeChildren[$vid]) : $max_depth;
|
||||
$tree = [];
|
||||
|
||||
// Keeps track of the parents we have to process, the last entry is used
|
||||
// for the next processing step.
|
||||
$process_parents = [];
|
||||
$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;
|
||||
if (!$load_entities) {
|
||||
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($this->getBaseTable(), '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($this->getDataTable())
|
||||
->fields(['weight' => 0])
|
||||
->condition('vid', $vid)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNodeTerms(array $nids, array $vocabs = [], $langcode = NULL) {
|
||||
$query = db_select($this->getDataTable(), 'td');
|
||||
$query->innerJoin('taxonomy_index', 'tn', 'td.tid = tn.tid');
|
||||
$query->fields('td', ['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 = [];
|
||||
$all_tids = [];
|
||||
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 = [];
|
||||
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['ancestors'], $vars['treeChildren'], $vars['treeParents'], $vars['treeTerms'], $vars['trees']);
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __wakeup() {
|
||||
parent::__wakeup();
|
||||
// Initialize static caches.
|
||||
$this->ancestors = [];
|
||||
$this->treeChildren = [];
|
||||
$this->treeParents = [];
|
||||
$this->treeTerms = [];
|
||||
$this->trees = [];
|
||||
}
|
||||
|
||||
}
|
129
2017/web/core/modules/taxonomy/src/TermStorageInterface.php
Normal file
129
2017/web/core/modules/taxonomy/src/TermStorageInterface.php
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?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.
|
||||
*
|
||||
* @todo Remove this method in Drupal 9.0.x. Now the parent references are
|
||||
* automatically cleared when deleting a taxonomy term.
|
||||
* https://www.drupal.org/node/2785693
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @todo remove this method Drupal 9.0.x. Now the parent references are
|
||||
* automatically updates when when a taxonomy term is added/updated.
|
||||
* https://www.drupal.org/node/2785693
|
||||
*/
|
||||
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 children 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 = [], $langcode = NULL);
|
||||
|
||||
}
|
111
2017/web/core/modules/taxonomy/src/TermStorageSchema.php
Normal file
111
2017/web/core/modules/taxonomy/src/TermStorageSchema.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?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);
|
||||
|
||||
if ($data_table = $this->storage->getDataTable()) {
|
||||
$schema[$data_table]['indexes'] += [
|
||||
'taxonomy_term__tree' => ['vid', 'weight', 'name'],
|
||||
'taxonomy_term__vid_name' => ['vid', 'name'],
|
||||
];
|
||||
}
|
||||
|
||||
$schema['taxonomy_index'] = [
|
||||
'description' => 'Maintains denormalized information about node/term relationships.',
|
||||
'fields' => [
|
||||
'nid' => [
|
||||
'description' => 'The {node}.nid this record tracks.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
],
|
||||
'tid' => [
|
||||
'description' => 'The term ID.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
],
|
||||
'status' => [
|
||||
'description' => 'Boolean indicating whether the node is published (visible to non-administrators).',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 1,
|
||||
],
|
||||
'sticky' => [
|
||||
'description' => 'Boolean indicating whether the node is sticky.',
|
||||
'type' => 'int',
|
||||
'not null' => FALSE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
],
|
||||
'created' => [
|
||||
'description' => 'The Unix timestamp when the node was created.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
],
|
||||
],
|
||||
'primary key' => ['nid', 'tid'],
|
||||
'indexes' => [
|
||||
'term_node' => ['tid', 'status', 'sticky', 'created'],
|
||||
],
|
||||
'foreign keys' => [
|
||||
'tracked_node' => [
|
||||
'table' => 'node',
|
||||
'columns' => ['nid' => 'nid'],
|
||||
],
|
||||
'term' => [
|
||||
'table' => 'taxonomy_term_data',
|
||||
'columns' => ['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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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'][] = [$this, 'entityFormSave'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for TermTranslationHandler::entityFormAlter().
|
||||
*
|
||||
* This handles the save action.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityForm::build()
|
||||
*/
|
||||
public 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'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
18
2017/web/core/modules/taxonomy/src/TermViewBuilder.php
Normal file
18
2017/web/core/modules/taxonomy/src/TermViewBuilder.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
@trigger_error(__NAMESPACE__ . '\TermViewBuilder is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. Use \Drupal\Core\Entity\EntityViewBuilder instead. See https://www.drupal.org/node/2924233.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Core\Entity\EntityViewBuilder;
|
||||
|
||||
/**
|
||||
* View builder handler for taxonomy terms.
|
||||
*
|
||||
* @deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0.
|
||||
* Use \Drupal\Core\Entity\EntityViewBuilder instead.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityViewBuilder
|
||||
* @see https://www.drupal.org/node/2924233
|
||||
*/
|
||||
class TermViewBuilder extends EntityViewBuilder {}
|
239
2017/web/core/modules/taxonomy/src/TermViewsData.php
Normal file
239
2017/web/core/modules/taxonomy/src/TermViewsData.php
Normal file
|
@ -0,0 +1,239 @@
|
|||
<?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'] = [
|
||||
// This is provided for the many_to_one argument.
|
||||
'taxonomy_index' => [
|
||||
'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__parent';
|
||||
$data['taxonomy_term_field_data']['tid']['filter']['numeric'] = TRUE;
|
||||
|
||||
$data['taxonomy_term_field_data']['tid_raw'] = [
|
||||
'title' => $this->t('Term ID'),
|
||||
'help' => $this->t('The tid of a taxonomy term.'),
|
||||
'real field' => 'tid',
|
||||
'filter' => [
|
||||
'id' => 'numeric',
|
||||
'allow empty' => TRUE,
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['tid_representative'] = [
|
||||
'relationship' => [
|
||||
'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.');
|
||||
$data['taxonomy_term_field_data']['vid']['field']['help'] = t('The vocabulary name.');
|
||||
$data['taxonomy_term_field_data']['vid']['argument']['id'] = 'vocabulary_vid';
|
||||
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'] = [
|
||||
'title' => $this->t('Updated date'),
|
||||
'help' => $this->t('Date in the form of CCYYMMDD.'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_fulldate',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['changed_year_month'] = [
|
||||
'title' => $this->t('Updated year + month'),
|
||||
'help' => $this->t('Date in the form of YYYYMM.'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_year_month',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['changed_year'] = [
|
||||
'title' => $this->t('Updated year'),
|
||||
'help' => $this->t('Date in the form of YYYY.'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_year',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['changed_month'] = [
|
||||
'title' => $this->t('Updated month'),
|
||||
'help' => $this->t('Date in the form of MM (01 - 12).'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_month',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['changed_day'] = [
|
||||
'title' => $this->t('Updated day'),
|
||||
'help' => $this->t('Date in the form of DD (01 - 31).'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_day',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['changed_week'] = [
|
||||
'title' => $this->t('Updated week'),
|
||||
'help' => $this->t('Date in the form of WW (01 - 53).'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_week',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_index']['table']['group'] = $this->t('Taxonomy term');
|
||||
|
||||
$data['taxonomy_index']['table']['join'] = [
|
||||
'taxonomy_term_field_data' => [
|
||||
// links directly to taxonomy_term_field_data via tid
|
||||
'left_field' => 'tid',
|
||||
'field' => 'tid',
|
||||
],
|
||||
'node_field_data' => [
|
||||
// links directly to node via nid
|
||||
'left_field' => 'nid',
|
||||
'field' => 'nid',
|
||||
],
|
||||
'taxonomy_term__parent' => [
|
||||
'left_field' => 'entity_id',
|
||||
'field' => 'tid',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_index']['nid'] = [
|
||||
'title' => $this->t('Content with term'),
|
||||
'help' => $this->t('Relate all content tagged with a term.'),
|
||||
'relationship' => [
|
||||
'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'] = [
|
||||
'group' => $this->t('Content'),
|
||||
'title' => $this->t('Has taxonomy term ID'),
|
||||
'help' => $this->t('Display content if it has the selected taxonomy terms.'),
|
||||
'argument' => [
|
||||
'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' => [
|
||||
'title' => $this->t('Has taxonomy term'),
|
||||
'id' => 'taxonomy_index_tid',
|
||||
'hierarchy table' => 'taxonomy_term__parent',
|
||||
'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',
|
||||
],
|
||||
];
|
||||
|
||||
// Link to self through left.parent = right.tid (going down in depth).
|
||||
$data['taxonomy_term__parent']['table']['join']['taxonomy_term__parent'] = [
|
||||
'left_field' => 'entity_id',
|
||||
'field' => 'parent_target_id',
|
||||
];
|
||||
|
||||
$data['taxonomy_term__parent']['parent_target_id']['help'] = $this->t('The parent term of the term. This can produce duplicate entries if you are using a vocabulary that allows multiple parents.');
|
||||
$data['taxonomy_term__parent']['parent_target_id']['relationship']['label'] = $this->t('Parent');
|
||||
$data['taxonomy_term__parent']['parent_target_id']['argument']['id'] = 'taxonomy';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Tests;
|
||||
|
||||
@trigger_error(__NAMESPACE__ . '\TaxonomyTestBase is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\taxonomy\Functional\TaxonomyTestBase', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
|
||||
use Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait;
|
||||
|
||||
/**
|
||||
* Provides common helper methods for Taxonomy module tests.
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0.
|
||||
* Use \Drupal\Tests\taxonomy\Functional\TaxonomyTestBase instead.
|
||||
*/
|
||||
abstract class TaxonomyTestBase extends WebTestBase {
|
||||
|
||||
use TaxonomyTestTrait;
|
||||
use EntityReferenceTestTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['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(['type' => 'article', 'name' => 'Article']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Tests;
|
||||
|
||||
@trigger_error(__NAMESPACE__ . '\TaxonomyTestTrait is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
|
||||
/**
|
||||
* Provides common helper methods for Taxonomy module tests.
|
||||
*
|
||||
* @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0.
|
||||
* Use \Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait
|
||||
*/
|
||||
trait TaxonomyTestTrait {
|
||||
|
||||
/**
|
||||
* Returns a new vocabulary with random properties.
|
||||
*/
|
||||
public function createVocabulary() {
|
||||
// Create a vocabulary.
|
||||
$vocabulary = Vocabulary::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'description' => $this->randomMachineName(),
|
||||
'vid' => mb_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.
|
||||
*/
|
||||
public function createTerm(Vocabulary $vocabulary, $values = []) {
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Tests;
|
||||
|
||||
@trigger_error(__NAMESPACE__ . '\TaxonomyTranslationTestTrait is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\taxonomy\Functional\TaxonomyTranslationTestTrait', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
|
||||
|
||||
/**
|
||||
* Provides common testing base for translated taxonomy terms.
|
||||
*
|
||||
* @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0.
|
||||
* Use \Drupal\Tests\taxonomy\Functional\TaxonomyTranslationTestTrait
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
protected function setUpTermReferenceField() {
|
||||
$handler_settings = [
|
||||
'target_bundles' => [
|
||||
$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, [
|
||||
'type' => 'entity_reference_autocomplete_tags',
|
||||
])
|
||||
->save();
|
||||
entity_get_display('node', 'article', 'default')
|
||||
->setComponent($this->termFieldName, [
|
||||
'type' => 'entity_reference_label',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?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 vocabulary entity type.
|
||||
*
|
||||
* @see \Drupal\taxonomy\Entity\Vocabulary
|
||||
*/
|
||||
class VocabularyAccessControlHandler extends EntityAccessControlHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
switch ($operation) {
|
||||
case 'access taxonomy overview':
|
||||
case 'view':
|
||||
return AccessResult::allowedIfHasPermissions($account, ['access taxonomy overview', 'administer taxonomy'], 'OR');
|
||||
|
||||
default:
|
||||
return parent::checkAccess($entity, $operation, $account);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
158
2017/web/core/modules/taxonomy/src/VocabularyForm.php
Normal file
158
2017/web/core/modules/taxonomy/src/VocabularyForm.php
Normal file
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\BundleEntityFormBase;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\language\Entity\ContentLanguageSettings;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Base form for vocabulary edit forms.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class VocabularyForm extends BundleEntityFormBase {
|
||||
|
||||
/**
|
||||
* The vocabulary storage.
|
||||
*
|
||||
* @var \Drupal\taxonomy\VocabularyStorageInterface
|
||||
*/
|
||||
protected $vocabularyStorage;
|
||||
|
||||
/**
|
||||
* Constructs a new vocabulary form.
|
||||
*
|
||||
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
|
||||
* The vocabulary storage.
|
||||
*/
|
||||
public function __construct(VocabularyStorageInterface $vocabulary_storage) {
|
||||
$this->vocabularyStorage = $vocabulary_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity.manager')->getStorage('taxonomy_vocabulary')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$vocabulary = $this->entity;
|
||||
if ($vocabulary->isNew()) {
|
||||
$form['#title'] = $this->t('Add vocabulary');
|
||||
}
|
||||
else {
|
||||
$form['#title'] = $this->t('Edit vocabulary');
|
||||
}
|
||||
|
||||
$form['name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Name'),
|
||||
'#default_value' => $vocabulary->label(),
|
||||
'#maxlength' => 255,
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form['vid'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#default_value' => $vocabulary->id(),
|
||||
'#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'],
|
||||
'source' => ['name'],
|
||||
],
|
||||
];
|
||||
$form['description'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Description'),
|
||||
'#default_value' => $vocabulary->getDescription(),
|
||||
];
|
||||
|
||||
// $form['langcode'] is not wrapped in an
|
||||
// if ($this->moduleHandler->moduleExists('language')) check because the
|
||||
// language_select form element works also without the language module being
|
||||
// installed. https://www.drupal.org/node/1749954 documents the new element.
|
||||
$form['langcode'] = [
|
||||
'#type' => 'language_select',
|
||||
'#title' => $this->t('Vocabulary language'),
|
||||
'#languages' => LanguageInterface::STATE_ALL,
|
||||
'#default_value' => $vocabulary->language()->getId(),
|
||||
];
|
||||
if ($this->moduleHandler->moduleExists('language')) {
|
||||
$form['default_terms_language'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Term language'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['default_terms_language']['default_language'] = [
|
||||
'#type' => 'language_configuration',
|
||||
'#entity_information' => [
|
||||
'entity_type' => 'taxonomy_term',
|
||||
'bundle' => $vocabulary->id(),
|
||||
],
|
||||
'#default_value' => ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', $vocabulary->id()),
|
||||
];
|
||||
}
|
||||
// Set the hierarchy to "multiple parents" by default. This simplifies the
|
||||
// vocabulary form and standardizes the term form.
|
||||
$form['hierarchy'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => '0',
|
||||
];
|
||||
|
||||
$form = parent::form($form, $form_state);
|
||||
return $this->protectBundleIdElement($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$vocabulary = $this->entity;
|
||||
|
||||
// Prevent leading and trailing spaces in vocabulary names.
|
||||
$vocabulary->set('name', trim($vocabulary->label()));
|
||||
|
||||
$status = $vocabulary->save();
|
||||
$edit_link = $this->entity->link($this->t('Edit'));
|
||||
switch ($status) {
|
||||
case SAVED_NEW:
|
||||
$this->messenger()->addStatus($this->t('Created new vocabulary %name.', ['%name' => $vocabulary->label()]));
|
||||
$this->logger('taxonomy')->notice('Created new vocabulary %name.', ['%name' => $vocabulary->label(), 'link' => $edit_link]);
|
||||
$form_state->setRedirectUrl($vocabulary->urlInfo('overview-form'));
|
||||
break;
|
||||
|
||||
case SAVED_UPDATED:
|
||||
$this->messenger()->addStatus($this->t('Updated vocabulary %name.', ['%name' => $vocabulary->label()]));
|
||||
$this->logger('taxonomy')->notice('Updated vocabulary %name.', ['%name' => $vocabulary->label(), 'link' => $edit_link]);
|
||||
$form_state->setRedirectUrl($vocabulary->urlInfo('collection'));
|
||||
break;
|
||||
}
|
||||
|
||||
$form_state->setValue('vid', $vocabulary->id());
|
||||
$form_state->set('vid', $vocabulary->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the vocabulary already exists.
|
||||
*
|
||||
* @param string $vid
|
||||
* The vocabulary ID.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the vocabulary exists, FALSE otherwise.
|
||||
*/
|
||||
public function exists($vid) {
|
||||
$action = $this->vocabularyStorage->load($vid);
|
||||
return !empty($action);
|
||||
}
|
||||
|
||||
}
|
57
2017/web/core/modules/taxonomy/src/VocabularyInterface.php
Normal file
57
2017/web/core/modules/taxonomy/src/VocabularyInterface.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a taxonomy vocabulary entity.
|
||||
*/
|
||||
interface VocabularyInterface extends ConfigEntityInterface {
|
||||
|
||||
/**
|
||||
* Denotes that no term in the vocabulary has a parent.
|
||||
*/
|
||||
const HIERARCHY_DISABLED = 0;
|
||||
|
||||
/**
|
||||
* Denotes that one or more terms in the vocabulary has a single parent.
|
||||
*/
|
||||
const HIERARCHY_SINGLE = 1;
|
||||
|
||||
/**
|
||||
* Denotes that one or more terms in the vocabulary have multiple parents.
|
||||
*/
|
||||
const HIERARCHY_MULTIPLE = 2;
|
||||
|
||||
/**
|
||||
* Returns the vocabulary hierarchy.
|
||||
*
|
||||
* @return int
|
||||
* The vocabulary hierarchy.
|
||||
*/
|
||||
public function getHierarchy();
|
||||
|
||||
/**
|
||||
* Sets the vocabulary hierarchy.
|
||||
*
|
||||
* @param int $hierarchy
|
||||
* The hierarchy type of vocabulary.
|
||||
* Possible values:
|
||||
* - VocabularyInterface::HIERARCHY_DISABLED: No parents.
|
||||
* - VocabularyInterface::HIERARCHY_SINGLE: Single parent.
|
||||
* - VocabularyInterface::HIERARCHY_MULTIPLE: Multiple parents.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHierarchy($hierarchy);
|
||||
|
||||
/**
|
||||
* Returns the vocabulary description.
|
||||
*
|
||||
* @return string
|
||||
* The vocabulary description.
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
}
|
211
2017/web/core/modules/taxonomy/src/VocabularyListBuilder.php
Normal file
211
2017/web/core/modules/taxonomy/src/VocabularyListBuilder.php
Normal file
|
@ -0,0 +1,211 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Config\Entity\DraggableListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a class to build a listing of taxonomy vocabulary entities.
|
||||
*
|
||||
* @see \Drupal\taxonomy\Entity\Vocabulary
|
||||
*/
|
||||
class VocabularyListBuilder extends DraggableListBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $entitiesKey = 'vocabularies';
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The messenger.
|
||||
*
|
||||
* @var \Drupal\Core\Messenger\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* Constructs a new VocabularyListBuilder object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type,
|
||||
AccountInterface $current_user,
|
||||
EntityTypeManagerInterface $entity_type_manager,
|
||||
RendererInterface $renderer = NULL,
|
||||
MessengerInterface $messenger) {
|
||||
parent::__construct($entity_type, $entity_type_manager->getStorage($entity_type->id()));
|
||||
|
||||
$this->currentUser = $current_user;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->renderer = $renderer;
|
||||
$this->messenger = $messenger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('current_user'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('renderer'),
|
||||
$container->get('messenger')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'taxonomy_overview_vocabularies';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOperations(EntityInterface $entity) {
|
||||
$operations = parent::getDefaultOperations($entity);
|
||||
|
||||
if (isset($operations['edit'])) {
|
||||
$operations['edit']['title'] = t('Edit vocabulary');
|
||||
}
|
||||
|
||||
if ($entity->access('access taxonomy overview')) {
|
||||
$operations['list'] = [
|
||||
'title' => t('List terms'),
|
||||
'weight' => 0,
|
||||
'url' => $entity->toUrl('overview-form'),
|
||||
];
|
||||
}
|
||||
|
||||
$taxonomy_term_access_control_handler = $this->entityTypeManager->getAccessControlHandler('taxonomy_term');
|
||||
if ($taxonomy_term_access_control_handler->createAccess($entity->id())) {
|
||||
$operations['add'] = [
|
||||
'title' => t('Add terms'),
|
||||
'weight' => 10,
|
||||
'url' => Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $entity->id()]),
|
||||
];
|
||||
}
|
||||
|
||||
unset($operations['delete']);
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['label'] = t('Vocabulary name');
|
||||
$header['description'] = t('Description');
|
||||
|
||||
if ($this->currentUser->hasPermission('administer vocabularies') && !empty($this->weightKey)) {
|
||||
$header['weight'] = t('Weight');
|
||||
}
|
||||
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row['label'] = $entity->label();
|
||||
$row['description']['data'] = ['#markup' => $entity->getDescription()];
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$entities = $this->load();
|
||||
// If there are not multiple vocabularies, disable dragging by unsetting the
|
||||
// weight key.
|
||||
if (count($entities) <= 1) {
|
||||
unset($this->weightKey);
|
||||
}
|
||||
$build = parent::render();
|
||||
|
||||
// If the weight key was unset then the table is in the 'table' key,
|
||||
// otherwise in vocabularies. The empty message is only needed if the table
|
||||
// is possibly empty, so there is no need to support the vocabularies key
|
||||
// here.
|
||||
if (isset($build['table'])) {
|
||||
$access_control_handler = $this->entityTypeManager->getAccessControlHandler('taxonomy_vocabulary');
|
||||
$create_access = $access_control_handler->createAccess(NULL, NULL, [], TRUE);
|
||||
$this->renderer->addCacheableDependency($build['table'], $create_access);
|
||||
if ($create_access->isAllowed()) {
|
||||
$build['table']['#empty'] = t('No vocabularies available. <a href=":link">Add vocabulary</a>.', [
|
||||
':link' => Url::fromRoute('entity.taxonomy_vocabulary.add_form')->toString(),
|
||||
]);
|
||||
}
|
||||
else {
|
||||
$build['table']['#empty'] = t('No vocabularies available.');
|
||||
}
|
||||
}
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
$form['vocabularies']['#attributes'] = ['id' => 'taxonomy'];
|
||||
$form['actions']['submit']['#value'] = t('Save');
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitForm($form, $form_state);
|
||||
|
||||
$this->messenger->addStatus($this->t('The configuration options have been saved.'));
|
||||
}
|
||||
|
||||
}
|
32
2017/web/core/modules/taxonomy/src/VocabularyStorage.php
Normal file
32
2017/web/core/modules/taxonomy/src/VocabularyStorage.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityStorage;
|
||||
|
||||
/**
|
||||
* Defines a storage handler class for taxonomy vocabularies.
|
||||
*/
|
||||
class VocabularyStorage extends ConfigEntityStorage implements VocabularyStorageInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetCache(array $ids = NULL) {
|
||||
drupal_static_reset('taxonomy_vocabulary_get_names');
|
||||
parent::resetCache($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getToplevelTids($vids) {
|
||||
$tids = \Drupal::entityQuery('taxonomy_term')
|
||||
->condition('vid', $vids, 'IN')
|
||||
->condition('parent.target_id', 0)
|
||||
->execute();
|
||||
|
||||
return array_values($tids);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for vocabulary entity storage classes.
|
||||
*/
|
||||
interface VocabularyStorageInterface extends ConfigEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Gets top-level term IDs of vocabularies.
|
||||
*
|
||||
* @param array $vids
|
||||
* Array of vocabulary IDs.
|
||||
*
|
||||
* @return array
|
||||
* Array of top-level term IDs.
|
||||
*/
|
||||
public function getToplevelTids($vids);
|
||||
|
||||
}
|
62
2017/web/core/modules/taxonomy/taxonomy.es6.js
Normal file
62
2017/web/core/modules/taxonomy/taxonomy.es6.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* @file
|
||||
* Taxonomy behaviors.
|
||||
*/
|
||||
|
||||
(function($, Drupal) {
|
||||
/**
|
||||
* Move a block in the blocks table from one region to another.
|
||||
*
|
||||
* This behavior is dependent on the tableDrag behavior, since it uses the
|
||||
* objects initialized in that behavior to update the row.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches the drag behavior to a applicable table element.
|
||||
*/
|
||||
Drupal.behaviors.termDrag = {
|
||||
attach(context, settings) {
|
||||
const backStep = settings.taxonomy.backStep;
|
||||
const forwardStep = settings.taxonomy.forwardStep;
|
||||
// Get the blocks tableDrag object.
|
||||
const tableDrag = Drupal.tableDrag.taxonomy;
|
||||
const $table = $('#taxonomy');
|
||||
const rows = $table.find('tr').length;
|
||||
|
||||
// When a row is swapped, keep previous and next page classes set.
|
||||
tableDrag.row.prototype.onSwap = function(swappedRow) {
|
||||
$table
|
||||
.find('tr.taxonomy-term-preview')
|
||||
.removeClass('taxonomy-term-preview');
|
||||
$table
|
||||
.find('tr.taxonomy-term-divider-top')
|
||||
.removeClass('taxonomy-term-divider-top');
|
||||
$table
|
||||
.find('tr.taxonomy-term-divider-bottom')
|
||||
.removeClass('taxonomy-term-divider-bottom');
|
||||
|
||||
const tableBody = $table[0].tBodies[0];
|
||||
if (backStep) {
|
||||
for (let n = 0; n < backStep; n++) {
|
||||
$(tableBody.rows[n]).addClass('taxonomy-term-preview');
|
||||
}
|
||||
$(tableBody.rows[backStep - 1]).addClass('taxonomy-term-divider-top');
|
||||
$(tableBody.rows[backStep]).addClass('taxonomy-term-divider-bottom');
|
||||
}
|
||||
|
||||
if (forwardStep) {
|
||||
for (let k = rows - forwardStep - 1; k < rows - 1; k++) {
|
||||
$(tableBody.rows[k]).addClass('taxonomy-term-preview');
|
||||
}
|
||||
$(tableBody.rows[rows - forwardStep - 2]).addClass(
|
||||
'taxonomy-term-divider-top',
|
||||
);
|
||||
$(tableBody.rows[rows - forwardStep - 1]).addClass(
|
||||
'taxonomy-term-divider-bottom',
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
})(jQuery, Drupal);
|
10
2017/web/core/modules/taxonomy/taxonomy.info.yml
Normal file
10
2017/web/core/modules/taxonomy/taxonomy.info.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: Taxonomy
|
||||
type: module
|
||||
description: 'Enables the categorization of content.'
|
||||
package: Core
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- drupal:node
|
||||
- drupal:text
|
||||
configure: entity.taxonomy_vocabulary.collection
|
188
2017/web/core/modules/taxonomy/taxonomy.install
Normal file
188
2017/web/core/modules/taxonomy/taxonomy.install
Normal file
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the taxonomy module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Site\Settings;
|
||||
|
||||
/**
|
||||
* Convert the custom taxonomy term hierarchy storage to a default storage.
|
||||
*/
|
||||
function taxonomy_update_8501() {
|
||||
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
|
||||
/** @var \Drupal\Core\Field\BaseFieldDefinition $field_storage_definition */
|
||||
$field_storage_definition = $definition_update_manager->getFieldStorageDefinition('parent', 'taxonomy_term');
|
||||
$field_storage_definition->setCustomStorage(FALSE);
|
||||
$definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy hierarchy from {taxonomy_term_hierarchy} to {taxonomy_term__parent}.
|
||||
*/
|
||||
function taxonomy_update_8502(&$sandbox) {
|
||||
$database = \Drupal::database();
|
||||
|
||||
if (!isset($sandbox['current'])) {
|
||||
// Set batch ops sandbox.
|
||||
$sandbox['current'] = 0;
|
||||
$sandbox['tid'] = -1;
|
||||
$sandbox['delta'] = 0;
|
||||
$sandbox['limit'] = Settings::get('entity_update_batch_size', 50);
|
||||
|
||||
// Count records using a join, as there might be orphans in the hierarchy
|
||||
// table. See https://www.drupal.org/project/drupal/issues/2997982.
|
||||
$select = $database->select('taxonomy_term_hierarchy', 'h');
|
||||
$select->join('taxonomy_term_data', 'd', 'h.tid = d.tid');
|
||||
$sandbox['max'] = $select
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
}
|
||||
|
||||
// Save the hierarchy.
|
||||
$select = $database->select('taxonomy_term_hierarchy', 'h');
|
||||
$select->join('taxonomy_term_data', 'd', 'h.tid = d.tid');
|
||||
$hierarchy = $select
|
||||
->fields('h', ['tid', 'parent'])
|
||||
->fields('d', ['vid', 'langcode'])
|
||||
->range($sandbox['current'], $sandbox['limit'])
|
||||
->orderBy('tid', 'ASC')
|
||||
->orderBy('parent', 'ASC')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
// Restore data.
|
||||
$insert = $database->insert('taxonomy_term__parent')
|
||||
->fields(['bundle', 'entity_id', 'revision_id', 'langcode', 'delta', 'parent_target_id']);
|
||||
|
||||
foreach ($hierarchy as $row) {
|
||||
if ($row->tid !== $sandbox['tid']) {
|
||||
$sandbox['delta'] = 0;
|
||||
$sandbox['tid'] = $row->tid;
|
||||
}
|
||||
|
||||
$insert->values([
|
||||
'bundle' => $row->vid,
|
||||
'entity_id' => $row->tid,
|
||||
'revision_id' => $row->tid,
|
||||
'langcode' => $row->langcode,
|
||||
'delta' => $sandbox['delta'],
|
||||
'parent_target_id' => $row->parent,
|
||||
]);
|
||||
|
||||
$sandbox['delta']++;
|
||||
$sandbox['current']++;
|
||||
}
|
||||
|
||||
$insert->execute();
|
||||
|
||||
$sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['current'] / $sandbox['max']);
|
||||
|
||||
if ($sandbox['#finished'] >= 1) {
|
||||
// Update the entity type because the 'taxonomy_term_hierarchy' table is no
|
||||
// longer part of its shared tables schema.
|
||||
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$definition_update_manager->updateEntityType($definition_update_manager->getEntityType('taxonomy_term'));
|
||||
|
||||
// \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::onEntityTypeUpdate()
|
||||
// only deletes *known* entity tables (i.e. the base, data and revision
|
||||
// tables), so we have to drop it manually.
|
||||
$database->schema()->dropTable('taxonomy_term_hierarchy');
|
||||
|
||||
return t('Taxonomy term hierarchy has been converted to default entity reference storage.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update views to use {taxonomy_term__parent} in relationships.
|
||||
*/
|
||||
function taxonomy_update_8503() {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
|
||||
foreach ($config_factory->listAll('views.view.') as $id) {
|
||||
$view = $config_factory->getEditable($id);
|
||||
|
||||
foreach (array_keys($view->get('display')) as $display_id) {
|
||||
$changed = FALSE;
|
||||
|
||||
foreach (['relationships', 'filters', 'arguments'] as $handler_type) {
|
||||
$base_path = "display.$display_id.display_options.$handler_type";
|
||||
$handlers = $view->get($base_path);
|
||||
|
||||
if (!$handlers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($handlers as $handler_key => $handler_config) {
|
||||
$table_path = "$base_path.$handler_key.table";
|
||||
$field_path = "$base_path.$handler_key.field";
|
||||
$table = $view->get($table_path);
|
||||
$field = $view->get($field_path);
|
||||
|
||||
if (($table && ($table === 'taxonomy_term_hierarchy')) && ($field && ($field === 'parent'))) {
|
||||
$view->set($table_path, 'taxonomy_term__parent');
|
||||
$view->set($field_path, 'parent_target_id');
|
||||
|
||||
$changed = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($changed) {
|
||||
$view->save(TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the publishing status fields to taxonomy terms.
|
||||
*/
|
||||
function taxonomy_update_8601() {
|
||||
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$entity_type = $definition_update_manager->getEntityType('taxonomy_term');
|
||||
|
||||
// Bail out early if a field named 'status' is already installed.
|
||||
if ($definition_update_manager->getFieldStorageDefinition('status', 'taxonomy_term')) {
|
||||
$message = \Drupal::state()->get('taxonomy_update_8601_skip_message', t('The publishing status field has <strong>not</strong> been added to taxonomy terms. See <a href=":link">this page</a> for more information on how to install it.', [
|
||||
':link' => 'https://www.drupal.org/node/2985366',
|
||||
]));
|
||||
return $message;
|
||||
}
|
||||
|
||||
// Add the 'published' entity key to the taxonomy_term entity type.
|
||||
$entity_keys = $entity_type->getKeys();
|
||||
$entity_keys['published'] = 'status';
|
||||
$entity_type->set('entity_keys', $entity_keys);
|
||||
|
||||
$definition_update_manager->updateEntityType($entity_type);
|
||||
|
||||
// Add the status field.
|
||||
$status = BaseFieldDefinition::create('boolean')
|
||||
->setLabel(t('Publishing status'))
|
||||
->setDescription(t('A boolean indicating the published state.'))
|
||||
->setRevisionable(TRUE)
|
||||
->setTranslatable(TRUE)
|
||||
->setDefaultValue(TRUE);
|
||||
|
||||
$has_content_translation_status_field = $definition_update_manager->getFieldStorageDefinition('content_translation_status', 'taxonomy_term');
|
||||
if ($has_content_translation_status_field) {
|
||||
$status->setInitialValueFromField('content_translation_status', TRUE);
|
||||
}
|
||||
else {
|
||||
$status->setInitialValue(TRUE);
|
||||
}
|
||||
$definition_update_manager->installFieldStorageDefinition('status', 'taxonomy_term', 'taxonomy_term', $status);
|
||||
|
||||
// Uninstall the 'content_translation_status' field if needed.
|
||||
if ($has_content_translation_status_field) {
|
||||
$content_translation_status = $definition_update_manager->getFieldStorageDefinition('content_translation_status', 'taxonomy_term');
|
||||
$definition_update_manager->uninstallFieldStorageDefinition($content_translation_status);
|
||||
}
|
||||
|
||||
return t('The publishing status field has been added to taxonomy terms.');
|
||||
}
|
42
2017/web/core/modules/taxonomy/taxonomy.js
Normal file
42
2017/web/core/modules/taxonomy/taxonomy.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal) {
|
||||
Drupal.behaviors.termDrag = {
|
||||
attach: function attach(context, settings) {
|
||||
var backStep = settings.taxonomy.backStep;
|
||||
var forwardStep = settings.taxonomy.forwardStep;
|
||||
|
||||
var tableDrag = Drupal.tableDrag.taxonomy;
|
||||
var $table = $('#taxonomy');
|
||||
var rows = $table.find('tr').length;
|
||||
|
||||
tableDrag.row.prototype.onSwap = function (swappedRow) {
|
||||
$table.find('tr.taxonomy-term-preview').removeClass('taxonomy-term-preview');
|
||||
$table.find('tr.taxonomy-term-divider-top').removeClass('taxonomy-term-divider-top');
|
||||
$table.find('tr.taxonomy-term-divider-bottom').removeClass('taxonomy-term-divider-bottom');
|
||||
|
||||
var tableBody = $table[0].tBodies[0];
|
||||
if (backStep) {
|
||||
for (var n = 0; n < backStep; n++) {
|
||||
$(tableBody.rows[n]).addClass('taxonomy-term-preview');
|
||||
}
|
||||
$(tableBody.rows[backStep - 1]).addClass('taxonomy-term-divider-top');
|
||||
$(tableBody.rows[backStep]).addClass('taxonomy-term-divider-bottom');
|
||||
}
|
||||
|
||||
if (forwardStep) {
|
||||
for (var k = rows - forwardStep - 1; k < rows - 1; k++) {
|
||||
$(tableBody.rows[k]).addClass('taxonomy-term-preview');
|
||||
}
|
||||
$(tableBody.rows[rows - forwardStep - 2]).addClass('taxonomy-term-divider-top');
|
||||
$(tableBody.rows[rows - forwardStep - 1]).addClass('taxonomy-term-divider-bottom');
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
})(jQuery, Drupal);
|
12
2017/web/core/modules/taxonomy/taxonomy.libraries.yml
Normal file
12
2017/web/core/modules/taxonomy/taxonomy.libraries.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
drupal.taxonomy:
|
||||
version: VERSION
|
||||
js:
|
||||
taxonomy.js: {}
|
||||
css:
|
||||
component:
|
||||
css/taxonomy.theme.css: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/drupal
|
||||
- core/drupalSettings
|
||||
- core/drupal.tabledrag
|
11
2017/web/core/modules/taxonomy/taxonomy.links.action.yml
Normal file
11
2017/web/core/modules/taxonomy/taxonomy.links.action.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
entity.taxonomy_vocabulary.add_form:
|
||||
route_name: entity.taxonomy_vocabulary.add_form
|
||||
title: 'Add vocabulary'
|
||||
appears_on:
|
||||
- entity.taxonomy_vocabulary.collection
|
||||
|
||||
entity.taxonomy_term.add_form:
|
||||
route_name: entity.taxonomy_term.add_form
|
||||
title: 'Add term'
|
||||
appears_on:
|
||||
- entity.taxonomy_vocabulary.overview_form
|
17
2017/web/core/modules/taxonomy/taxonomy.links.contextual.yml
Normal file
17
2017/web/core/modules/taxonomy/taxonomy.links.contextual.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
entity.taxonomy_term.edit_form:
|
||||
title: Edit
|
||||
group: taxonomy_term
|
||||
route_name: entity.taxonomy_term.edit_form
|
||||
weight: 10
|
||||
|
||||
entity.taxonomy_term.delete_form:
|
||||
title: Delete
|
||||
group: taxonomy_term
|
||||
route_name: entity.taxonomy_term.delete_form
|
||||
weight: 20
|
||||
|
||||
entity.taxonomy_vocabulary.delete_form:
|
||||
title: Delete
|
||||
group: taxonomy_vocabulary
|
||||
route_name: entity.taxonomy_vocabulary.delete_form
|
||||
weight: 20
|
5
2017/web/core/modules/taxonomy/taxonomy.links.menu.yml
Normal file
5
2017/web/core/modules/taxonomy/taxonomy.links.menu.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
entity.taxonomy_vocabulary.collection:
|
||||
title: Taxonomy
|
||||
parent: system.admin_structure
|
||||
description: 'Manage tagging, categorization, and classification of your content.'
|
||||
route_name: entity.taxonomy_vocabulary.collection
|
19
2017/web/core/modules/taxonomy/taxonomy.links.task.yml
Normal file
19
2017/web/core/modules/taxonomy/taxonomy.links.task.yml
Normal file
|
@ -0,0 +1,19 @@
|
|||
entity.taxonomy_term.canonical:
|
||||
title: 'View'
|
||||
route_name: entity.taxonomy_term.canonical
|
||||
base_route: entity.taxonomy_term.canonical
|
||||
|
||||
entity.taxonomy_term.edit_form:
|
||||
title: 'Edit'
|
||||
route_name: entity.taxonomy_term.edit_form
|
||||
base_route: entity.taxonomy_term.canonical
|
||||
|
||||
entity.taxonomy_vocabulary.overview_form:
|
||||
title: 'List'
|
||||
route_name: entity.taxonomy_vocabulary.overview_form
|
||||
base_route: entity.taxonomy_vocabulary.overview_form
|
||||
|
||||
entity.taxonomy_vocabulary.edit_form:
|
||||
title: 'Edit'
|
||||
route_name: entity.taxonomy_vocabulary.edit_form
|
||||
base_route: entity.taxonomy_vocabulary.overview_form
|
606
2017/web/core/modules/taxonomy/taxonomy.module
Normal file
606
2017/web/core/modules/taxonomy/taxonomy.module
Normal file
|
@ -0,0 +1,606 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables the organization of content into categories.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Tags;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\taxonomy\TermInterface;
|
||||
use Drupal\taxonomy\VocabularyInterface;
|
||||
|
||||
/**
|
||||
* Denotes that no term in the vocabulary has a parent.
|
||||
*
|
||||
* @deprecated in Drupal 8.2.x and will be removed before 9.0.0. Use
|
||||
* \Drupal\taxonomy\VocabularyInterface::HIERARCHY_DISABLED instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2807795
|
||||
*/
|
||||
const TAXONOMY_HIERARCHY_DISABLED = 0;
|
||||
|
||||
/**
|
||||
* Denotes that one or more terms in the vocabulary has a single parent.
|
||||
*
|
||||
* @deprecated in Drupal 8.2.x and will be removed before 9.0.0. Use
|
||||
* \Drupal\taxonomy\VocabularyInterface::HIERARCHY_SINGLE instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2807795
|
||||
*/
|
||||
const TAXONOMY_HIERARCHY_SINGLE = 1;
|
||||
|
||||
/**
|
||||
* Denotes that one or more terms in the vocabulary have multiple parents.
|
||||
*
|
||||
* @deprecated in Drupal 8.2.x and will be removed before 9.0.0. Use
|
||||
* \Drupal\taxonomy\VocabularyInterface::HIERARCHY_MULTIPLE instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2807795
|
||||
*/
|
||||
const TAXONOMY_HIERARCHY_MULTIPLE = 2;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function taxonomy_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.taxonomy':
|
||||
$field_ui_url = \Drupal::moduleHandler()->moduleExists('field_ui') ? \Drupal::url('help.page', ['name' => 'field_ui']) : '#';
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Taxonomy module allows users who have permission to create and edit content to categorize (tag) content of that type. Users who have the <em>Administer vocabularies and terms</em> <a href=":permissions" title="Taxonomy module permissions">permission</a> can add <em>vocabularies</em> that contain a set of related <em>terms</em>. The terms in a vocabulary can either be pre-set by an administrator or built gradually as content is added and edited. Terms may be organized hierarchically if desired.', [':permissions' => \Drupal::url('user.admin_permissions', [], ['fragment' => 'module-taxonomy'])]) . '</p>';
|
||||
$output .= '<p>' . t('For more information, see the <a href=":taxonomy">online documentation for the Taxonomy module</a>.', [':taxonomy' => 'https://www.drupal.org/documentation/modules/taxonomy/']) . '</p>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . t('Managing vocabularies') . '</dt>';
|
||||
$output .= '<dd>' . t('Users who have the <em>Administer vocabularies and terms</em> permission can add and edit vocabularies from the <a href=":taxonomy_admin">Taxonomy administration page</a>. Vocabularies can be deleted from their <em>Edit vocabulary</em> page. Users with the <em>Taxonomy term: Administer fields</em> permission may add additional fields for terms in that vocabulary using the <a href=":field_ui">Field UI module</a>.', [':taxonomy_admin' => \Drupal::url('entity.taxonomy_vocabulary.collection'), ':field_ui' => $field_ui_url]) . '</dd>';
|
||||
$output .= '<dt>' . t('Managing terms') . '</dt>';
|
||||
$output .= '<dd>' . t('Users who have the <em>Administer vocabularies and terms</em> permission or the <em>Edit terms</em> permission for a particular vocabulary can add, edit, and organize the terms in a vocabulary from a vocabulary\'s term listing page, which can be accessed by going to the <a href=":taxonomy_admin">Taxonomy administration page</a> and clicking <em>List terms</em> in the <em>Operations</em> column. Users must have the <em>Administer vocabularies and terms</em> permission or the <em>Delete terms</em> permission for a particular vocabulary to delete terms.', [':taxonomy_admin' => \Drupal::url('entity.taxonomy_vocabulary.collection')]) . ' </dd>';
|
||||
$output .= '<dt>' . t('Classifying entity content') . '</dt>';
|
||||
$output .= '<dd>' . t('A user with the <em>Administer fields</em> permission for a certain entity type may add <em>Taxonomy term</em> reference fields to the entity type, which will allow entities to be classified using taxonomy terms. See the <a href=":entity_reference">Entity Reference help</a> for more information about reference fields. See the <a href=":field">Field module help</a> and the <a href=":field_ui">Field UI help</a> pages for general information on fields and how to create and manage them.', [':field_ui' => $field_ui_url, ':field' => \Drupal::url('help.page', ['name' => 'field']), ':entity_reference' => \Drupal::url('help.page', ['name' => 'entity_reference'])]) . '</dd>';
|
||||
$output .= '<dt>' . t('Adding new terms during content creation') . '</dt>';
|
||||
$output .= '<dd>' . t("Allowing users to add new terms gradually builds a vocabulary as content is added and edited. Users can add new terms if either of the two <em>Autocomplete</em> widgets is chosen for the Taxonomy term reference field in the <em>Manage form display</em> page for the field. You will also need to enable the <em>Create referenced entities if they don't already exist</em> option, and restrict the field to one vocabulary.") . '</dd>';
|
||||
$output .= '<dt>' . t('Configuring displays and form displays') . '</dt>';
|
||||
$output .= '<dd>' . t('See the <a href=":entity_reference">Entity Reference help</a> page for the field widgets and formatters that can be configured for any reference field on the <em>Manage display</em> and <em>Manage form display</em> pages. Taxonomy additionally provides an <em>RSS category</em> formatter that displays nothing when the entity item is displayed as HTML, but displays an RSS category instead of a list when the entity item is displayed in an RSS feed.', [':entity_reference' => \Drupal::url('help.page', ['name' => 'entity_reference'])]) . '</li>';
|
||||
$output .= '</ul>';
|
||||
$output .= '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
|
||||
case 'entity.taxonomy_vocabulary.collection':
|
||||
$output = '<p>' . t('Taxonomy is for categorizing content. Terms are grouped into vocabularies. For example, a vocabulary called "Fruit" would contain the terms "Apple" and "Banana".') . '</p>';
|
||||
return $output;
|
||||
|
||||
case 'entity.taxonomy_vocabulary.overview_form':
|
||||
$vocabulary = $route_match->getParameter('taxonomy_vocabulary');
|
||||
if (\Drupal::currentUser()->hasPermission('administer taxonomy') || \Drupal::currentUser()->hasPermission('edit terms in ' . $vocabulary->id())) {
|
||||
switch ($vocabulary->getHierarchy()) {
|
||||
case VocabularyInterface::HIERARCHY_DISABLED:
|
||||
return '<p>' . t('You can reorganize the terms in %capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', ['%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label()]) . '</p>';
|
||||
case VocabularyInterface::HIERARCHY_SINGLE:
|
||||
return '<p>' . t('%capital_name contains terms grouped under parent terms. You can reorganize the terms in %capital_name using their drag-and-drop handles.', ['%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label()]) . '</p>';
|
||||
case VocabularyInterface::HIERARCHY_MULTIPLE:
|
||||
return '<p>' . t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents is not supported, but you can re-enable drag-and-drop support by editing each term to include only a single parent.', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '</p>';
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch ($vocabulary->getHierarchy()) {
|
||||
case VocabularyInterface::HIERARCHY_DISABLED:
|
||||
return '<p>' . t('%capital_name contains the following terms.', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '</p>';
|
||||
case VocabularyInterface::HIERARCHY_SINGLE:
|
||||
return '<p>' . t('%capital_name contains terms grouped under parent terms', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '</p>';
|
||||
case VocabularyInterface::HIERARCHY_MULTIPLE:
|
||||
return '<p>' . t('%capital_name contains terms with multiple parents.', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity URI callback.
|
||||
*/
|
||||
function taxonomy_term_uri($term) {
|
||||
return new Url('entity.taxonomy_term.canonical', [
|
||||
'taxonomy_term' => $term->id(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_page_attachments_alter().
|
||||
*/
|
||||
function taxonomy_page_attachments_alter(array &$page) {
|
||||
$route_match = \Drupal::routeMatch();
|
||||
if ($route_match->getRouteName() == 'entity.taxonomy_term.canonical' && ($term = $route_match->getParameter('taxonomy_term')) && $term instanceof TermInterface) {
|
||||
foreach ($term->uriRelationships() as $rel) {
|
||||
// Set the URI relationships, like canonical.
|
||||
$page['#attached']['html_head_link'][] = [
|
||||
[
|
||||
'rel' => $rel,
|
||||
'href' => $term->url($rel),
|
||||
],
|
||||
TRUE,
|
||||
];
|
||||
|
||||
// Set the term path as the canonical URL to prevent duplicate content.
|
||||
if ($rel == 'canonical') {
|
||||
// Set the non-aliased canonical path as a default shortlink.
|
||||
$page['#attached']['html_head_link'][] = [
|
||||
[
|
||||
'rel' => 'shortlink',
|
||||
'href' => $term->url($rel, ['alias' => TRUE]),
|
||||
],
|
||||
TRUE,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function taxonomy_theme() {
|
||||
return [
|
||||
'taxonomy_term' => [
|
||||
'render element' => 'elements',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and updates the hierarchy flag of a vocabulary.
|
||||
*
|
||||
* Checks the current parents of all terms in a vocabulary and updates the
|
||||
* vocabulary's hierarchy setting to the lowest possible level. If no term
|
||||
* has parent terms then the vocabulary will be given a hierarchy of
|
||||
* VocabularyInterface::HIERARCHY_DISABLED. If any term has a single parent then
|
||||
* the vocabulary will be given a hierarchy of
|
||||
* VocabularyInterface::HIERARCHY_SINGLE. If any term has multiple parents then
|
||||
* the vocabulary will be given a hierarchy of
|
||||
* VocabularyInterface::HIERARCHY_MULTIPLE.
|
||||
*
|
||||
* @param \Drupal\taxonomy\VocabularyInterface $vocabulary
|
||||
* A taxonomy vocabulary entity.
|
||||
* @param $changed_term
|
||||
* An array of the term structure that was updated.
|
||||
*
|
||||
* @return
|
||||
* An integer that represents the level of the vocabulary's hierarchy.
|
||||
*/
|
||||
function taxonomy_check_vocabulary_hierarchy(VocabularyInterface $vocabulary, $changed_term) {
|
||||
$tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vocabulary->id());
|
||||
$hierarchy = VocabularyInterface::HIERARCHY_DISABLED;
|
||||
foreach ($tree as $term) {
|
||||
// Update the changed term with the new parent value before comparison.
|
||||
if ($term->tid == $changed_term['tid']) {
|
||||
$term = (object) $changed_term;
|
||||
$term->parents = $term->parent;
|
||||
}
|
||||
// Check this term's parent count.
|
||||
if (count($term->parents) > 1) {
|
||||
$hierarchy = VocabularyInterface::HIERARCHY_MULTIPLE;
|
||||
break;
|
||||
}
|
||||
elseif (count($term->parents) == 1 && !isset($term->parents[0])) {
|
||||
$hierarchy = VocabularyInterface::HIERARCHY_SINGLE;
|
||||
}
|
||||
}
|
||||
if ($hierarchy != $vocabulary->getHierarchy()) {
|
||||
$vocabulary->setHierarchy($hierarchy);
|
||||
$vocabulary->save();
|
||||
}
|
||||
|
||||
return $hierarchy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array which displays a term detail page.
|
||||
*
|
||||
* @param \Drupal\taxonomy\Entity\Term $term
|
||||
* A taxonomy term object.
|
||||
* @param string $view_mode
|
||||
* View mode; e.g., 'full', 'teaser', etc.
|
||||
* @param string $langcode
|
||||
* (optional) A language code to use for rendering. Defaults to the global
|
||||
* content language of the current request.
|
||||
*
|
||||
* @return array
|
||||
* A $page element suitable for use by
|
||||
* \Drupal\Core\Render\RendererInterface::render().
|
||||
*/
|
||||
function taxonomy_term_view(Term $term, $view_mode = 'full', $langcode = NULL) {
|
||||
return entity_view($term, $view_mode, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a drupal_render() style array from an array of loaded terms.
|
||||
*
|
||||
* @param array $terms
|
||||
* An array of taxonomy terms as returned by Term::loadMultiple().
|
||||
* @param string $view_mode
|
||||
* View mode; e.g., 'full', 'teaser', etc.
|
||||
* @param string $langcode
|
||||
* (optional) A language code to use for rendering. Defaults to the global
|
||||
* content language of the current request.
|
||||
*
|
||||
* @return array
|
||||
* An array in the format expected by
|
||||
* \Drupal\Core\Render\RendererInterface::render().
|
||||
*/
|
||||
function taxonomy_term_view_multiple(array $terms, $view_mode = 'full', $langcode = NULL) {
|
||||
return entity_view_multiple($terms, $view_mode, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme_suggestions_HOOK().
|
||||
*/
|
||||
function taxonomy_theme_suggestions_taxonomy_term(array $variables) {
|
||||
$suggestions = [];
|
||||
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
$term = $variables['elements']['#taxonomy_term'];
|
||||
|
||||
$suggestions[] = 'taxonomy_term__' . $term->bundle();
|
||||
$suggestions[] = 'taxonomy_term__' . $term->id();
|
||||
|
||||
return $suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for taxonomy term templates.
|
||||
*
|
||||
* Default template: taxonomy-term.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - elements: An associative array containing the taxonomy term and any
|
||||
* fields attached to the term. Properties used:
|
||||
* - #taxonomy_term: A \Drupal\taxonomy\TermInterface object.
|
||||
* - #view_mode: The current view mode for this taxonomy term, e.g.
|
||||
* 'full' or 'teaser'.
|
||||
* - attributes: HTML attributes for the containing element.
|
||||
*/
|
||||
function template_preprocess_taxonomy_term(&$variables) {
|
||||
$variables['view_mode'] = $variables['elements']['#view_mode'];
|
||||
$variables['term'] = $variables['elements']['#taxonomy_term'];
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
$term = $variables['term'];
|
||||
|
||||
$variables['url'] = $term->url();
|
||||
// We use name here because that is what appears in the UI.
|
||||
$variables['name'] = $variables['elements']['name'];
|
||||
unset($variables['elements']['name']);
|
||||
$variables['page'] = $variables['view_mode'] == 'full' && taxonomy_term_is_page($term);
|
||||
|
||||
// Helpful $content variable for templates.
|
||||
$variables['content'] = [];
|
||||
foreach (Element::children($variables['elements']) as $key) {
|
||||
$variables['content'][$key] = $variables['elements'][$key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current page is the page of the passed-in term.
|
||||
*
|
||||
* @param \Drupal\taxonomy\Entity\Term $term
|
||||
* A taxonomy term entity.
|
||||
*/
|
||||
function taxonomy_term_is_page(Term $term) {
|
||||
if (\Drupal::routeMatch()->getRouteName() == 'entity.taxonomy_term.canonical' && $page_term_id = \Drupal::routeMatch()->getRawParameter('taxonomy_term')) {
|
||||
return $page_term_id == $term->id();
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all static cache variables for terms.
|
||||
*/
|
||||
function taxonomy_terms_static_reset() {
|
||||
\Drupal::entityManager()->getStorage('taxonomy_term')->resetCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all static cache variables for vocabularies.
|
||||
*
|
||||
* @param $ids
|
||||
* An array of ids to reset in the entity cache.
|
||||
*/
|
||||
function taxonomy_vocabulary_static_reset(array $ids = NULL) {
|
||||
\Drupal::entityManager()->getStorage('taxonomy_vocabulary')->resetCache($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get names for all taxonomy vocabularies.
|
||||
*
|
||||
* @return array
|
||||
* A list of existing vocabulary IDs.
|
||||
*/
|
||||
function taxonomy_vocabulary_get_names() {
|
||||
$names = &drupal_static(__FUNCTION__);
|
||||
|
||||
if (!isset($names)) {
|
||||
$names = [];
|
||||
$config_names = \Drupal::configFactory()->listAll('taxonomy.vocabulary.');
|
||||
foreach ($config_names as $config_name) {
|
||||
$id = substr($config_name, strlen('taxonomy.vocabulary.'));
|
||||
$names[$id] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to map a string to an existing term, as for glossary use.
|
||||
*
|
||||
* Provides a case-insensitive and trimmed mapping, to maximize the
|
||||
* likelihood of a successful match.
|
||||
*
|
||||
* @param $name
|
||||
* Name of the term to search for.
|
||||
* @param $vocabulary
|
||||
* (optional) Vocabulary machine name to limit the search. Defaults to NULL.
|
||||
*
|
||||
* @return
|
||||
* An array of matching term objects.
|
||||
*/
|
||||
function taxonomy_term_load_multiple_by_name($name, $vocabulary = NULL) {
|
||||
$values = ['name' => trim($name)];
|
||||
if (isset($vocabulary)) {
|
||||
$vocabularies = taxonomy_vocabulary_get_names();
|
||||
if (isset($vocabularies[$vocabulary])) {
|
||||
$values['vid'] = $vocabulary;
|
||||
}
|
||||
else {
|
||||
// Return an empty array when filtering by a non-existing vocabulary.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return entity_load_multiple_by_properties('taxonomy_term', $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load multiple taxonomy terms based on certain conditions.
|
||||
*
|
||||
* This function should be used whenever you need to load more than one term
|
||||
* from the database. Terms are loaded into memory and will not require
|
||||
* database access if loaded again during the same page request.
|
||||
*
|
||||
* @see entity_load_multiple()
|
||||
* @see \Drupal\Core\Entity\Query\EntityQueryInterface
|
||||
*
|
||||
* @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
|
||||
* Use \Drupal\taxonomy\Entity\Term::loadMultiple().
|
||||
*
|
||||
* @param array $tids
|
||||
* (optional) An array of entity IDs. If omitted, all entities are loaded.
|
||||
*
|
||||
* @return array
|
||||
* An array of taxonomy term entities, indexed by tid. When no results are
|
||||
* found, an empty array is returned.
|
||||
*/
|
||||
function taxonomy_term_load_multiple(array $tids = NULL) {
|
||||
return Term::loadMultiple($tids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads multiple taxonomy vocabularies based on certain conditions.
|
||||
*
|
||||
* This function should be used whenever you need to load more than one
|
||||
* vocabulary from the database. Terms are loaded into memory and will not
|
||||
* require database access if loaded again during the same page request.
|
||||
*
|
||||
* @see entity_load_multiple()
|
||||
*
|
||||
* @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
|
||||
* Use \Drupal\taxonomy\Entity\Vocabulary::loadMultiple().
|
||||
*
|
||||
* @param array $vids
|
||||
* (optional) An array of entity IDs. If omitted, all entities are loaded.
|
||||
*
|
||||
* @return array
|
||||
* An array of vocabulary objects, indexed by vid.
|
||||
*/
|
||||
function taxonomy_vocabulary_load_multiple(array $vids = NULL) {
|
||||
return Vocabulary::loadMultiple($vids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the taxonomy vocabulary entity matching a vocabulary ID.
|
||||
*
|
||||
* @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
|
||||
* Use \Drupal\taxonomy\Entity\Vocabulary::load().
|
||||
*
|
||||
* @param int $vid
|
||||
* The vocabulary's ID.
|
||||
*
|
||||
* @return \Drupal\taxonomy\Entity\Vocabulary|null
|
||||
* The taxonomy vocabulary entity, if exists, NULL otherwise. Results are
|
||||
* statically cached.
|
||||
*/
|
||||
function taxonomy_vocabulary_load($vid) {
|
||||
return Vocabulary::load($vid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the taxonomy term entity matching a term ID.
|
||||
*
|
||||
* @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
|
||||
* Use \Drupal\taxonomy\Entity\Term::load().
|
||||
*
|
||||
* @param $tid
|
||||
* A term's ID
|
||||
*
|
||||
* @return \Drupal\taxonomy\Entity\Term|null
|
||||
* A taxonomy term entity, or NULL if the term was not found. Results are
|
||||
* statically cached.
|
||||
*/
|
||||
function taxonomy_term_load($tid) {
|
||||
if (!is_numeric($tid)) {
|
||||
return NULL;
|
||||
}
|
||||
return Term::load($tid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implodes a list of tags of a certain vocabulary into a string.
|
||||
*
|
||||
* @see \Drupal\Component\Utility\Tags::explode()
|
||||
*/
|
||||
function taxonomy_implode_tags($tags, $vid = NULL) {
|
||||
$typed_tags = [];
|
||||
foreach ($tags as $tag) {
|
||||
// Extract terms belonging to the vocabulary in question.
|
||||
if (!isset($vid) || $tag->bundle() == $vid) {
|
||||
// Make sure we have a completed loaded taxonomy term.
|
||||
if ($tag instanceof EntityInterface && $label = $tag->label()) {
|
||||
// Commas and quotes in tag names are special cases, so encode 'em.
|
||||
$typed_tags[] = Tags::encode($label);
|
||||
}
|
||||
}
|
||||
}
|
||||
return implode(', ', $typed_tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Title callback for term pages.
|
||||
*
|
||||
* @param \Drupal\taxonomy\Entity\Term $term
|
||||
* A taxonomy term entity.
|
||||
*
|
||||
* @return
|
||||
* The term name to be used as the page title.
|
||||
*/
|
||||
function taxonomy_term_title(Term $term) {
|
||||
return $term->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @defgroup taxonomy_index Taxonomy indexing
|
||||
* @{
|
||||
* Functions to maintain taxonomy indexing.
|
||||
*
|
||||
* Taxonomy uses default field storage to store canonical relationships
|
||||
* between terms and fieldable entities. However its most common use case
|
||||
* requires listing all content associated with a term or group of terms
|
||||
* sorted by creation date. To avoid slow queries due to joining across
|
||||
* multiple node and field tables with various conditions and order by criteria,
|
||||
* we maintain a denormalized table with all relationships between terms,
|
||||
* published nodes and common sort criteria such as status, sticky and created.
|
||||
* When using other field storage engines or alternative methods of
|
||||
* denormalizing this data you should set the
|
||||
* taxonomy.settings:maintain_index_table to '0' to avoid unnecessary writes in
|
||||
* SQL.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_insert() for node entities.
|
||||
*/
|
||||
function taxonomy_node_insert(EntityInterface $node) {
|
||||
// Add taxonomy index entries for the node.
|
||||
taxonomy_build_node_index($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and inserts taxonomy index entries for a given node.
|
||||
*
|
||||
* The index lists all terms that are related to a given node entity, and is
|
||||
* therefore maintained at the entity level.
|
||||
*
|
||||
* @param \Drupal\node\Entity\Node $node
|
||||
* The node entity.
|
||||
*/
|
||||
function taxonomy_build_node_index($node) {
|
||||
// We maintain a denormalized table of term/node relationships, containing
|
||||
// only data for current, published nodes.
|
||||
if (!\Drupal::config('taxonomy.settings')->get('maintain_index_table') || !(\Drupal::entityManager()->getStorage('node') instanceof SqlContentEntityStorage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$status = $node->isPublished();
|
||||
$sticky = (int) $node->isSticky();
|
||||
// We only maintain the taxonomy index for published nodes.
|
||||
if ($status && $node->isDefaultRevision()) {
|
||||
// Collect a unique list of all the term IDs from all node fields.
|
||||
$tid_all = [];
|
||||
$entity_reference_class = 'Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem';
|
||||
foreach ($node->getFieldDefinitions() as $field) {
|
||||
$field_name = $field->getName();
|
||||
$class = $field->getItemDefinition()->getClass();
|
||||
$is_entity_reference_class = ($class === $entity_reference_class) || is_subclass_of($class, $entity_reference_class);
|
||||
if ($is_entity_reference_class && $field->getSetting('target_type') == 'taxonomy_term') {
|
||||
foreach ($node->getTranslationLanguages() as $language) {
|
||||
foreach ($node->getTranslation($language->getId())->$field_name as $item) {
|
||||
if (!$item->isEmpty()) {
|
||||
$tid_all[$item->target_id] = $item->target_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Insert index entries for all the node's terms.
|
||||
if (!empty($tid_all)) {
|
||||
foreach ($tid_all as $tid) {
|
||||
db_merge('taxonomy_index')
|
||||
->key(['nid' => $node->id(), 'tid' => $tid, 'status' => $node->isPublished()])
|
||||
->fields(['sticky' => $sticky, 'created' => $node->getCreatedTime()])
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_update() for node entities.
|
||||
*/
|
||||
function taxonomy_node_update(EntityInterface $node) {
|
||||
// If we're not dealing with the default revision of the node, do not make any
|
||||
// change to the taxonomy index.
|
||||
if (!$node->isDefaultRevision()) {
|
||||
return;
|
||||
}
|
||||
taxonomy_delete_node_index($node);
|
||||
taxonomy_build_node_index($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_predelete() for node entities.
|
||||
*/
|
||||
function taxonomy_node_predelete(EntityInterface $node) {
|
||||
// Clean up the {taxonomy_index} table when nodes are deleted.
|
||||
taxonomy_delete_node_index($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes taxonomy index entries for a given node.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $node
|
||||
* The node entity.
|
||||
*/
|
||||
function taxonomy_delete_node_index(EntityInterface $node) {
|
||||
if (\Drupal::config('taxonomy.settings')->get('maintain_index_table')) {
|
||||
db_delete('taxonomy_index')->condition('nid', $node->id())->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_delete() for taxonomy_term entities.
|
||||
*/
|
||||
function taxonomy_taxonomy_term_delete(Term $term) {
|
||||
if (\Drupal::config('taxonomy.settings')->get('maintain_index_table')) {
|
||||
// Clean up the {taxonomy_index} table when terms are deleted.
|
||||
db_delete('taxonomy_index')->condition('tid', $term->id())->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup taxonomy_index".
|
||||
*/
|
9
2017/web/core/modules/taxonomy/taxonomy.permissions.yml
Normal file
9
2017/web/core/modules/taxonomy/taxonomy.permissions.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
administer taxonomy:
|
||||
title: 'Administer vocabularies and terms'
|
||||
|
||||
access taxonomy overview:
|
||||
title: 'Access the taxonomy vocabulary overview page'
|
||||
description: 'Get an overview of all taxonomy vocabularies.'
|
||||
|
||||
permission_callbacks:
|
||||
- Drupal\taxonomy\TaxonomyPermissions::permissions
|
127
2017/web/core/modules/taxonomy/taxonomy.post_update.php
Normal file
127
2017/web/core/modules/taxonomy/taxonomy.post_update.php
Normal file
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post update functions for Taxonomy.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
|
||||
use Drupal\views\ViewExecutable;
|
||||
|
||||
/**
|
||||
* Clear caches due to updated taxonomy entity views data.
|
||||
*/
|
||||
function taxonomy_post_update_clear_views_data_cache() {
|
||||
// An empty update will flush caches.
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear entity_bundle_field_definitions cache for new parent field settings.
|
||||
*/
|
||||
function taxonomy_post_update_clear_entity_bundle_field_definitions_cache() {
|
||||
// An empty update will flush caches.
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a 'published' = TRUE filter for all Taxonomy term views and converts
|
||||
* existing ones that were using the 'content_translation_status' field.
|
||||
*/
|
||||
function taxonomy_post_update_handle_publishing_status_addition_in_views(&$sandbox = NULL) {
|
||||
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$entity_type = $definition_update_manager->getEntityType('taxonomy_term');
|
||||
$published_key = $entity_type->getKey('published');
|
||||
|
||||
$status_filter = [
|
||||
'id' => 'status',
|
||||
'table' => 'taxonomy_term_field_data',
|
||||
'field' => $published_key,
|
||||
'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',
|
||||
'anonymous' => '0',
|
||||
'administrator' => '0',
|
||||
],
|
||||
],
|
||||
'is_grouped' => FALSE,
|
||||
'group_info' => [
|
||||
'label' => '',
|
||||
'description' => '',
|
||||
'identifier' => '',
|
||||
'optional' => TRUE,
|
||||
'widget' => 'select',
|
||||
'multiple' => FALSE,
|
||||
'remember' => FALSE,
|
||||
'default_group' => 'All',
|
||||
'default_group_multiple' => [],
|
||||
'group_items' => [],
|
||||
],
|
||||
'entity_type' => 'taxonomy_term',
|
||||
'entity_field' => $published_key,
|
||||
'plugin_id' => 'boolean',
|
||||
];
|
||||
|
||||
\Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function ($view) use ($published_key, $status_filter) {
|
||||
/** @var \Drupal\views\ViewEntityInterface $view */
|
||||
// Only alter taxonomy term views.
|
||||
if ($view->get('base_table') !== 'taxonomy_term_field_data') {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$displays = $view->get('display');
|
||||
foreach ($displays as $display_name => &$display) {
|
||||
// Update any existing 'content_translation_status fields.
|
||||
$fields = isset($display['display_options']['fields']) ? $display['display_options']['fields'] : [];
|
||||
foreach ($fields as $id => $field) {
|
||||
if (isset($field['field']) && $field['field'] == 'content_translation_status') {
|
||||
$fields[$id]['field'] = $published_key;
|
||||
}
|
||||
}
|
||||
$display['display_options']['fields'] = $fields;
|
||||
|
||||
// Update any existing 'content_translation_status sorts.
|
||||
$sorts = isset($display['display_options']['sorts']) ? $display['display_options']['sorts'] : [];
|
||||
foreach ($sorts as $id => $sort) {
|
||||
if (isset($sort['field']) && $sort['field'] == 'content_translation_status') {
|
||||
$sorts[$id]['field'] = $published_key;
|
||||
}
|
||||
}
|
||||
$display['display_options']['sorts'] = $sorts;
|
||||
|
||||
// Update any existing 'content_translation_status' filters or add a new
|
||||
// one if necessary.
|
||||
$filters = isset($display['display_options']['filters']) ? $display['display_options']['filters'] : [];
|
||||
$has_status_filter = FALSE;
|
||||
foreach ($filters as $id => $filter) {
|
||||
if (isset($filter['field']) && $filter['field'] == 'content_translation_status') {
|
||||
$filters[$id]['field'] = $published_key;
|
||||
$has_status_filter = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$has_status_filter) {
|
||||
$status_filter['id'] = ViewExecutable::generateHandlerId($published_key, $filters);
|
||||
$filters[$status_filter['id']] = $status_filter;
|
||||
}
|
||||
$display['display_options']['filters'] = $filters;
|
||||
}
|
||||
$view->set('display', $displays);
|
||||
|
||||
return TRUE;
|
||||
});
|
||||
}
|
39
2017/web/core/modules/taxonomy/taxonomy.routing.yml
Normal file
39
2017/web/core/modules/taxonomy/taxonomy.routing.yml
Normal file
|
@ -0,0 +1,39 @@
|
|||
entity.taxonomy_term.add_form:
|
||||
path: '/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/add'
|
||||
defaults:
|
||||
_controller: '\Drupal\taxonomy\Controller\TaxonomyController::addForm'
|
||||
_title: 'Add term'
|
||||
requirements:
|
||||
_entity_create_access: 'taxonomy_term:{taxonomy_vocabulary}'
|
||||
|
||||
entity.taxonomy_term.edit_form:
|
||||
path: '/taxonomy/term/{taxonomy_term}/edit'
|
||||
defaults:
|
||||
_entity_form: 'taxonomy_term.default'
|
||||
_title: 'Edit term'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'taxonomy_term.update'
|
||||
taxonomy_term: \d+
|
||||
|
||||
entity.taxonomy_term.delete_form:
|
||||
path: '/taxonomy/term/{taxonomy_term}/delete'
|
||||
defaults:
|
||||
_entity_form: 'taxonomy_term.delete'
|
||||
_title: 'Delete term'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'taxonomy_term.delete'
|
||||
taxonomy_term: \d+
|
||||
|
||||
entity.taxonomy_term.canonical:
|
||||
path: '/taxonomy/term/{taxonomy_term}'
|
||||
defaults:
|
||||
_entity_view: 'taxonomy_term.full'
|
||||
_title: 'Taxonomy term'
|
||||
_title_callback: '\Drupal\taxonomy\Controller\TaxonomyController::termTitle'
|
||||
requirements:
|
||||
_entity_access: 'taxonomy_term.view'
|
||||
taxonomy_term: \d+
|
6
2017/web/core/modules/taxonomy/taxonomy.services.yml
Normal file
6
2017/web/core/modules/taxonomy/taxonomy.services.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
services:
|
||||
taxonomy_term.breadcrumb:
|
||||
class: Drupal\taxonomy\TermBreadcrumbBuilder
|
||||
arguments: ['@entity.manager']
|
||||
tags:
|
||||
- { name: breadcrumb_builder, priority: 1002 }
|
192
2017/web/core/modules/taxonomy/taxonomy.tokens.inc
Normal file
192
2017/web/core/modules/taxonomy/taxonomy.tokens.inc
Normal file
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Builds placeholder replacement tokens for taxonomy terms and vocabularies.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
|
||||
/**
|
||||
* Implements hook_token_info().
|
||||
*/
|
||||
function taxonomy_token_info() {
|
||||
$types['term'] = [
|
||||
'name' => t("Taxonomy terms"),
|
||||
'description' => t("Tokens related to taxonomy terms."),
|
||||
'needs-data' => 'term',
|
||||
];
|
||||
$types['vocabulary'] = [
|
||||
'name' => t("Vocabularies"),
|
||||
'description' => t("Tokens related to taxonomy vocabularies."),
|
||||
'needs-data' => 'vocabulary',
|
||||
];
|
||||
|
||||
// Taxonomy term related variables.
|
||||
$term['tid'] = [
|
||||
'name' => t("Term ID"),
|
||||
'description' => t("The unique ID of the taxonomy term."),
|
||||
];
|
||||
$term['name'] = [
|
||||
'name' => t("Name"),
|
||||
'description' => t("The name of the taxonomy term."),
|
||||
];
|
||||
$term['description'] = [
|
||||
'name' => t("Description"),
|
||||
'description' => t("The optional description of the taxonomy term."),
|
||||
];
|
||||
$term['node-count'] = [
|
||||
'name' => t("Node count"),
|
||||
'description' => t("The number of nodes tagged with the taxonomy term."),
|
||||
];
|
||||
$term['url'] = [
|
||||
'name' => t("URL"),
|
||||
'description' => t("The URL of the taxonomy term."),
|
||||
];
|
||||
|
||||
// Taxonomy vocabulary related variables.
|
||||
$vocabulary['vid'] = [
|
||||
'name' => t("Vocabulary ID"),
|
||||
'description' => t("The unique ID of the taxonomy vocabulary."),
|
||||
];
|
||||
$vocabulary['name'] = [
|
||||
'name' => t("Name"),
|
||||
'description' => t("The name of the taxonomy vocabulary."),
|
||||
];
|
||||
$vocabulary['description'] = [
|
||||
'name' => t("Description"),
|
||||
'description' => t("The optional description of the taxonomy vocabulary."),
|
||||
];
|
||||
$vocabulary['node-count'] = [
|
||||
'name' => t("Node count"),
|
||||
'description' => t("The number of nodes tagged with terms belonging to the taxonomy vocabulary."),
|
||||
];
|
||||
$vocabulary['term-count'] = [
|
||||
'name' => t("Term count"),
|
||||
'description' => t("The number of terms belonging to the taxonomy vocabulary."),
|
||||
];
|
||||
|
||||
// Chained tokens for taxonomies
|
||||
$term['vocabulary'] = [
|
||||
'name' => t("Vocabulary"),
|
||||
'description' => t("The vocabulary the taxonomy term belongs to."),
|
||||
'type' => 'vocabulary',
|
||||
];
|
||||
$term['parent'] = [
|
||||
'name' => t("Parent term"),
|
||||
'description' => t("The parent term of the taxonomy term, if one exists."),
|
||||
'type' => 'term',
|
||||
];
|
||||
|
||||
return [
|
||||
'types' => $types,
|
||||
'tokens' => [
|
||||
'term' => $term,
|
||||
'vocabulary' => $vocabulary,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
function taxonomy_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$token_service = \Drupal::token();
|
||||
|
||||
$replacements = [];
|
||||
$taxonomy_storage = \Drupal::entityManager()->getStorage('taxonomy_term');
|
||||
if ($type == 'term' && !empty($data['term'])) {
|
||||
$term = $data['term'];
|
||||
|
||||
foreach ($tokens as $name => $original) {
|
||||
switch ($name) {
|
||||
case 'tid':
|
||||
$replacements[$original] = $term->id();
|
||||
break;
|
||||
|
||||
case 'name':
|
||||
$replacements[$original] = $term->getName();
|
||||
break;
|
||||
|
||||
case 'description':
|
||||
// "processed" returns a \Drupal\Component\Render\MarkupInterface via
|
||||
// check_markup().
|
||||
$replacements[$original] = $term->description->processed;
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
$replacements[$original] = $term->url('canonical', ['absolute' => TRUE]);
|
||||
break;
|
||||
|
||||
case 'node-count':
|
||||
$query = db_select('taxonomy_index');
|
||||
$query->condition('tid', $term->id());
|
||||
$query->addTag('term_node_count');
|
||||
$count = $query->countQuery()->execute()->fetchField();
|
||||
$replacements[$original] = $count;
|
||||
break;
|
||||
|
||||
case 'vocabulary':
|
||||
$vocabulary = Vocabulary::load($term->bundle());
|
||||
$bubbleable_metadata->addCacheableDependency($vocabulary);
|
||||
$replacements[$original] = $vocabulary->label();
|
||||
break;
|
||||
|
||||
case 'parent':
|
||||
if ($parents = $taxonomy_storage->loadParents($term->id())) {
|
||||
$parent = array_pop($parents);
|
||||
$bubbleable_metadata->addCacheableDependency($parent);
|
||||
$replacements[$original] = $parent->getName();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($vocabulary_tokens = $token_service->findWithPrefix($tokens, 'vocabulary')) {
|
||||
$vocabulary = Vocabulary::load($term->bundle());
|
||||
$replacements += $token_service->generate('vocabulary', $vocabulary_tokens, ['vocabulary' => $vocabulary], $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if (($vocabulary_tokens = $token_service->findWithPrefix($tokens, 'parent')) && $parents = $taxonomy_storage->loadParents($term->id())) {
|
||||
$parent = array_pop($parents);
|
||||
$replacements += $token_service->generate('term', $vocabulary_tokens, ['term' => $parent], $options, $bubbleable_metadata);
|
||||
}
|
||||
}
|
||||
|
||||
elseif ($type == 'vocabulary' && !empty($data['vocabulary'])) {
|
||||
$vocabulary = $data['vocabulary'];
|
||||
|
||||
foreach ($tokens as $name => $original) {
|
||||
switch ($name) {
|
||||
case 'vid':
|
||||
$replacements[$original] = $vocabulary->id();
|
||||
break;
|
||||
|
||||
case 'name':
|
||||
$replacements[$original] = $vocabulary->label();
|
||||
break;
|
||||
|
||||
case 'description':
|
||||
$build = ['#markup' => $vocabulary->getDescription()];
|
||||
// @todo Fix in https://www.drupal.org/node/2577827
|
||||
$replacements[$original] = \Drupal::service('renderer')->renderPlain($build);
|
||||
break;
|
||||
|
||||
case 'term-count':
|
||||
$replacements[$original] = \Drupal::entityQuery('taxonomy_term')
|
||||
->condition('vid', $vocabulary->id())
|
||||
->addTag('vocabulary_term_count')
|
||||
->count()
|
||||
->execute();
|
||||
break;
|
||||
|
||||
case 'node-count':
|
||||
$replacements[$original] = $taxonomy_storage->nodeCount($vocabulary->id());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $replacements;
|
||||
}
|
85
2017/web/core/modules/taxonomy/taxonomy.views.inc
Normal file
85
2017/web/core/modules/taxonomy/taxonomy.views.inc
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides views data for taxonomy.module.
|
||||
*/
|
||||
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_views_data_alter().
|
||||
*/
|
||||
function taxonomy_views_data_alter(&$data) {
|
||||
$data['node_field_data']['term_node_tid'] = [
|
||||
'title' => t('Taxonomy terms on node'),
|
||||
'help' => t('Relate nodes to taxonomy terms, specifying which vocabulary or vocabularies to use. This relationship will cause duplicated records if there are multiple terms.'),
|
||||
'relationship' => [
|
||||
'id' => 'node_term_data',
|
||||
'label' => t('term'),
|
||||
'base' => 'taxonomy_term_field_data',
|
||||
],
|
||||
'field' => [
|
||||
'title' => t('All taxonomy terms'),
|
||||
'help' => t('Display all taxonomy terms associated with a node from specified vocabularies.'),
|
||||
'id' => 'taxonomy_index_tid',
|
||||
'no group by' => TRUE,
|
||||
'click sortable' => FALSE,
|
||||
],
|
||||
];
|
||||
|
||||
$data['node_field_data']['term_node_tid_depth'] = [
|
||||
'help' => t('Display content if it has the selected taxonomy terms, or children of the selected terms. Due to additional complexity, this has fewer options than the versions without depth.'),
|
||||
'real field' => 'nid',
|
||||
'argument' => [
|
||||
'title' => t('Has taxonomy term ID (with depth)'),
|
||||
'id' => 'taxonomy_index_tid_depth',
|
||||
'accept depth modifier' => TRUE,
|
||||
],
|
||||
'filter' => [
|
||||
'title' => t('Has taxonomy terms (with depth)'),
|
||||
'id' => 'taxonomy_index_tid_depth',
|
||||
],
|
||||
];
|
||||
|
||||
$data['node_field_data']['term_node_tid_depth_modifier'] = [
|
||||
'title' => t('Has taxonomy term ID depth modifier'),
|
||||
'help' => t('Allows the "depth" for Taxonomy: Term ID (with depth) to be modified via an additional contextual filter value.'),
|
||||
'argument' => [
|
||||
'id' => 'taxonomy_index_tid_depth_modifier',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_views_data_alter().
|
||||
*
|
||||
* Views integration for entity reference fields which reference taxonomy terms.
|
||||
* Adds a term relationship to the default field data.
|
||||
*
|
||||
* @see views_field_default_views_data()
|
||||
*/
|
||||
function taxonomy_field_views_data_alter(array &$data, FieldStorageConfigInterface $field_storage) {
|
||||
if ($field_storage->getType() == 'entity_reference' && $field_storage->getSetting('target_type') == 'taxonomy_term') {
|
||||
foreach ($data as $table_name => $table_data) {
|
||||
foreach ($table_data as $field_name => $field_data) {
|
||||
if (isset($field_data['filter']) && $field_name != 'delta') {
|
||||
$data[$table_name][$field_name]['filter']['id'] = 'taxonomy_index_tid';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_views_plugins_argument_validator_alter().
|
||||
*
|
||||
* Extend the generic entity argument validator.
|
||||
*
|
||||
* @see \Drupal\views\Plugin\views\argument_validator\Entity
|
||||
*/
|
||||
function taxonomy_views_plugins_argument_validator_alter(array &$plugins) {
|
||||
$plugins['entity:taxonomy_term']['title'] = t('Taxonomy term ID');
|
||||
$plugins['entity:taxonomy_term']['class'] = 'Drupal\taxonomy\Plugin\views\argument_validator\Term';
|
||||
$plugins['entity:taxonomy_term']['provider'] = 'taxonomy';
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation to display a taxonomy term.
|
||||
*
|
||||
* Available variables:
|
||||
* - url: URL of the current term.
|
||||
* - name: Name of the current term.
|
||||
* - content: Items for the content of the term (fields and description).
|
||||
* Use 'content' to print them all, or print a subset such as
|
||||
* 'content.description'. Use the following code to exclude the
|
||||
* printing of a given child element:
|
||||
* @code
|
||||
* {{ content|without('description') }}
|
||||
* @endcode
|
||||
* - attributes: HTML attributes for the wrapper.
|
||||
* - page: Flag for the full page state.
|
||||
* - term: The taxonomy term entity, including:
|
||||
* - id: The ID of the taxonomy term.
|
||||
* - bundle: Machine name of the current vocabulary.
|
||||
* - view_mode: View mode, e.g. 'full', 'teaser', etc.
|
||||
*
|
||||
* @see template_preprocess_taxonomy_term()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
<div{{ attributes }}>
|
||||
{{ title_prefix }}
|
||||
{% if not page %}
|
||||
<h2><a href="{{ url }}">{{ name }}</a></h2>
|
||||
{% endif %}
|
||||
{{ title_suffix }}
|
||||
{{ content }}
|
||||
</div>
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains database additions to drupal-8.filled.standard.php.gz for testing
|
||||
* the upgrade path of https://www.drupal.org/project/drupal/issues/2981887.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Serialization\Yaml;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
$view_file = __DIR__ . '/views.view.test_taxonomy_term_view_with_content_translation_status.yml';
|
||||
$view_with_cts_config = Yaml::decode(file_get_contents($view_file));
|
||||
|
||||
$view_file = __DIR__ . '/views.view.test_taxonomy_term_view_without_content_translation_status.yml';
|
||||
$view_without_cts_config = Yaml::decode(file_get_contents($view_file));
|
||||
|
||||
$connection->insert('config')
|
||||
->fields(['collection', 'name', 'data'])
|
||||
->values([
|
||||
'collection' => '',
|
||||
'name' => 'views.view.test_taxonomy_term_view_with_content_translation_status',
|
||||
'data' => serialize($view_with_cts_config),
|
||||
])
|
||||
->values([
|
||||
'collection' => '',
|
||||
'name' => 'views.view.test_taxonomy_term_view_without_content_translation_status',
|
||||
'data' => serialize($view_without_cts_config),
|
||||
])
|
||||
->execute();
|
|
@ -0,0 +1,250 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- taxonomy
|
||||
- user
|
||||
id: test_taxonomy_term_view_with_content_translation_status
|
||||
label: 'Test taxonomy term view with content translation status'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: taxonomy_term_field_data
|
||||
base_field: tid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
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: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
name:
|
||||
id: name
|
||||
table: taxonomy_term_field_data
|
||||
field: name
|
||||
entity_type: taxonomy_term
|
||||
entity_field: name
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: true
|
||||
plugin_id: term_name
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
convert_spaces: false
|
||||
content_translation_status:
|
||||
id: content_translation_status
|
||||
table: taxonomy_term_field_data
|
||||
field: content_translation_status
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: boolean
|
||||
settings:
|
||||
format: true-false
|
||||
format_custom_true: ''
|
||||
format_custom_false: ''
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: taxonomy_term
|
||||
entity_field: content_translation_status
|
||||
plugin_id: field
|
||||
filters:
|
||||
content_translation_status:
|
||||
id: content_translation_status
|
||||
table: taxonomy_term_field_data
|
||||
field: content_translation_status
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: '='
|
||||
value: All
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: 'Translation status'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: content_translation_status_op
|
||||
identifier: content_translation_status
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: taxonomy_term
|
||||
entity_field: content_translation_status
|
||||
plugin_id: boolean
|
||||
sorts:
|
||||
content_translation_status:
|
||||
id: content_translation_status
|
||||
table: taxonomy_term_field_data
|
||||
field: content_translation_status
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
entity_type: taxonomy_term
|
||||
entity_field: content_translation_status
|
||||
plugin_id: standard
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- user.permissions
|
||||
tags: { }
|
|
@ -0,0 +1,128 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- taxonomy
|
||||
- user
|
||||
id: test_taxonomy_term_view_without_content_translation_status
|
||||
label: 'Test taxonomy term view without content translation status'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: taxonomy_term_field_data
|
||||
base_field: tid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
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: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
name:
|
||||
id: name
|
||||
table: taxonomy_term_field_data
|
||||
field: name
|
||||
entity_type: taxonomy_term
|
||||
entity_field: name
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: true
|
||||
plugin_id: term_name
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
convert_spaces: false
|
||||
filters: { }
|
||||
sorts: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- user.permissions
|
||||
tags: { }
|
|
@ -0,0 +1,7 @@
|
|||
taxonomy.vocabulary.*.third_party.taxonomy_crud:
|
||||
type: mapping
|
||||
label: 'Schema for taxonomy_crud module additions to vocabulary entity'
|
||||
mapping:
|
||||
foo:
|
||||
type: string
|
||||
label: 'Label for foo'
|
|
@ -0,0 +1,8 @@
|
|||
name: 'Taxonomy CRUD tests'
|
||||
type: module
|
||||
description: 'Provides 3rd party settings for vocabulary.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- drupal:taxonomy
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides hook implementations for testing purposes.
|
||||
*/
|
||||
|
||||
use Drupal\taxonomy\VocabularyInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_presave() for taxonomy_vocabulary entities.
|
||||
*/
|
||||
function taxonomy_crud_taxonomy_vocabulary_presave(VocabularyInterface $vocabulary) {
|
||||
$vocabulary->setThirdPartySetting('taxonomy_crud', 'foo', 'bar');
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue