Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: false
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node.full
|
||||
label: 'Full content'
|
||||
description: ''
|
||||
targetEntityType: node
|
||||
cache: true
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: false
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node.rss
|
||||
label: RSS
|
||||
description: ''
|
||||
targetEntityType: node
|
||||
cache: true
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: false
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node.search_index
|
||||
label: 'Search index'
|
||||
description: ''
|
||||
targetEntityType: node
|
||||
cache: true
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: false
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node.search_result
|
||||
label: 'Search result highlighting input'
|
||||
description: ''
|
||||
targetEntityType: node
|
||||
cache: true
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node.teaser
|
||||
label: Teaser
|
||||
description: ''
|
||||
targetEntityType: node
|
||||
cache: true
|
||||
@ -0,0 +1,18 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- text
|
||||
id: node.body
|
||||
field_name: body
|
||||
entity_type: node
|
||||
type: text_with_summary
|
||||
settings: { }
|
||||
module: text
|
||||
locked: false
|
||||
cardinality: 1
|
||||
translatable: true
|
||||
indexes: { }
|
||||
persist_with_no_fields: true
|
||||
custom_storage: false
|
||||
1
web/core/modules/node/config/install/node.settings.yml
Normal file
1
web/core/modules/node/config/install/node.settings.yml
Normal file
@ -0,0 +1 @@
|
||||
use_admin_theme: true
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node_delete_action
|
||||
label: 'Delete content'
|
||||
type: node
|
||||
plugin: entity:delete_action:node
|
||||
configuration: { }
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node_make_sticky_action
|
||||
label: 'Make content sticky'
|
||||
type: node
|
||||
plugin: node_make_sticky_action
|
||||
configuration: { }
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node_make_unsticky_action
|
||||
label: 'Make content unsticky'
|
||||
type: node
|
||||
plugin: node_make_unsticky_action
|
||||
configuration: { }
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node_promote_action
|
||||
label: 'Promote content to front page'
|
||||
type: node
|
||||
plugin: node_promote_action
|
||||
configuration: { }
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node_publish_action
|
||||
label: 'Publish content'
|
||||
type: node
|
||||
plugin: entity:publish_action:node
|
||||
configuration: { }
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node_save_action
|
||||
label: 'Save content'
|
||||
type: node
|
||||
plugin: entity:save_action:node
|
||||
configuration: { }
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node_unpromote_action
|
||||
label: 'Remove content from front page'
|
||||
type: node
|
||||
plugin: node_unpromote_action
|
||||
configuration: { }
|
||||
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node_unpublish_action
|
||||
label: 'Unpublish content'
|
||||
type: node
|
||||
plugin: entity:unpublish_action:node
|
||||
configuration: { }
|
||||
@ -0,0 +1,12 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node_search
|
||||
label: Content
|
||||
path: node
|
||||
weight: -10
|
||||
plugin: node_search
|
||||
configuration:
|
||||
rankings: { }
|
||||
245
web/core/modules/node/config/optional/views.view.archive.yml
Normal file
245
web/core/modules/node/config/optional/views.view.archive.yml
Normal file
@ -0,0 +1,245 @@
|
||||
langcode: en
|
||||
status: false
|
||||
dependencies:
|
||||
config:
|
||||
- core.entity_view_mode.node.teaser
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: archive
|
||||
label: Archive
|
||||
module: node
|
||||
description: 'All content, by month.'
|
||||
tag: default
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
id: default
|
||||
display_title: Default
|
||||
display_plugin: default
|
||||
position: 0
|
||||
display_options:
|
||||
title: 'Monthly archive'
|
||||
fields: { }
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
offset: 0
|
||||
pagination_heading_level: h4
|
||||
items_per_page: 10
|
||||
total_pages: 0
|
||||
id: 0
|
||||
tags:
|
||||
next: ››
|
||||
previous: ‹‹
|
||||
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
|
||||
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
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
empty: { }
|
||||
sorts:
|
||||
created:
|
||||
id: created
|
||||
table: node_field_data
|
||||
field: created
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: created
|
||||
plugin_id: date
|
||||
order: DESC
|
||||
expose:
|
||||
label: ''
|
||||
field_identifier: created
|
||||
exposed: false
|
||||
granularity: second
|
||||
arguments:
|
||||
created_year_month:
|
||||
id: created_year_month
|
||||
table: node_field_data
|
||||
field: created_year_month
|
||||
entity_type: node
|
||||
plugin_id: date_year_month
|
||||
default_action: summary
|
||||
exception:
|
||||
title_enable: true
|
||||
title_enable: true
|
||||
title: '{{ arguments.created_year_month }}'
|
||||
default_argument_type: fixed
|
||||
summary_options:
|
||||
override: true
|
||||
items_per_page: 30
|
||||
summary:
|
||||
sort_order: desc
|
||||
format: default_summary
|
||||
specify_validation: true
|
||||
filters:
|
||||
status:
|
||||
id: status
|
||||
table: node_field_data
|
||||
field: status
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
plugin_id: boolean
|
||||
value: '1'
|
||||
group: 0
|
||||
expose:
|
||||
operator: '0'
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
langcode:
|
||||
id: langcode
|
||||
table: node_field_data
|
||||
field: langcode
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: langcode
|
||||
plugin_id: language
|
||||
operator: in
|
||||
value:
|
||||
'***LANGUAGE_language_content***': '***LANGUAGE_language_content***'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
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: { }
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: 'entity:node'
|
||||
options:
|
||||
view_mode: teaser
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
query_comment: ''
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_tags: { }
|
||||
relationships: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
block_1:
|
||||
id: block_1
|
||||
display_title: Block
|
||||
display_plugin: block
|
||||
position: 1
|
||||
display_options:
|
||||
arguments:
|
||||
created_year_month:
|
||||
id: created_year_month
|
||||
table: node_field_data
|
||||
field: created_year_month
|
||||
entity_type: node
|
||||
plugin_id: date_year_month
|
||||
default_action: summary
|
||||
exception:
|
||||
title_enable: true
|
||||
title_enable: true
|
||||
title: '{{ arguments.created_year_month }}'
|
||||
default_argument_type: fixed
|
||||
summary_options:
|
||||
items_per_page: 30
|
||||
summary:
|
||||
format: default_summary
|
||||
specify_validation: true
|
||||
query:
|
||||
type: views_query
|
||||
options: { }
|
||||
defaults:
|
||||
arguments: false
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
id: page_1
|
||||
display_title: Page
|
||||
display_plugin: page
|
||||
position: 2
|
||||
display_options:
|
||||
query:
|
||||
type: views_query
|
||||
options: { }
|
||||
display_extenders: { }
|
||||
path: archive
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
685
web/core/modules/node/config/optional/views.view.content.yml
Normal file
685
web/core/modules/node/config/optional/views.view.content.yml
Normal file
@ -0,0 +1,685 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: content
|
||||
label: Content
|
||||
module: node
|
||||
description: 'Find and manage content.'
|
||||
tag: default
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
id: default
|
||||
display_title: Default
|
||||
display_plugin: default
|
||||
position: 0
|
||||
display_options:
|
||||
title: Content
|
||||
fields:
|
||||
node_bulk_form:
|
||||
id: node_bulk_form
|
||||
table: node
|
||||
field: node_bulk_form
|
||||
entity_type: node
|
||||
plugin_id: node_bulk_form
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
element_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: field
|
||||
label: Title
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
element_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: true
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: field
|
||||
label: 'Content type'
|
||||
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: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: false
|
||||
group_column: target_id
|
||||
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
|
||||
name:
|
||||
id: name
|
||||
table: users_field_data
|
||||
field: name
|
||||
relationship: uid
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
plugin_id: field
|
||||
label: Author
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
element_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: user_name
|
||||
status:
|
||||
id: status
|
||||
table: node_field_data
|
||||
field: status
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
plugin_id: field
|
||||
label: Status
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
element_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: boolean
|
||||
settings:
|
||||
format: custom
|
||||
format_custom_false: Unpublished
|
||||
format_custom_true: Published
|
||||
changed:
|
||||
id: changed
|
||||
table: node_field_data
|
||||
field: changed
|
||||
entity_type: node
|
||||
entity_field: changed
|
||||
plugin_id: field
|
||||
label: Updated
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
element_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: timestamp
|
||||
settings:
|
||||
date_format: short
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
tooltip:
|
||||
date_format: long
|
||||
custom_date_format: ''
|
||||
time_diff:
|
||||
enabled: false
|
||||
future_format: '@interval hence'
|
||||
past_format: '@interval ago'
|
||||
granularity: 2
|
||||
refresh: 60
|
||||
langcode:
|
||||
id: langcode
|
||||
table: node_field_data
|
||||
field: langcode
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: langcode
|
||||
plugin_id: field_language
|
||||
label: 'Language'
|
||||
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: true
|
||||
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: language
|
||||
settings:
|
||||
link_to_entity: false
|
||||
native_language: 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
|
||||
operations:
|
||||
id: operations
|
||||
table: node
|
||||
field: operations
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
plugin_id: entity_operations
|
||||
label: Operations
|
||||
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: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
destination: true
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
pagination_heading_level: h4
|
||||
items_per_page: 50
|
||||
tags:
|
||||
next: 'Next ›'
|
||||
previous: '‹ Previous'
|
||||
first: '« First'
|
||||
last: 'Last »'
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Filter
|
||||
reset_button: true
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content overview'
|
||||
cache:
|
||||
type: tag
|
||||
empty:
|
||||
area_text_custom:
|
||||
id: area_text_custom
|
||||
table: views
|
||||
field: area_text_custom
|
||||
plugin_id: text_custom
|
||||
empty: true
|
||||
content: 'No content available.'
|
||||
sorts: { }
|
||||
arguments: { }
|
||||
filters:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: string
|
||||
operator: contains
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: title_op
|
||||
label: Title
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: title_op
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: title
|
||||
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: { }
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: type_op
|
||||
label: 'Content type'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: type_op
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: type
|
||||
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: { }
|
||||
status:
|
||||
id: status
|
||||
table: node_field_data
|
||||
field: status
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
plugin_id: boolean
|
||||
operator: '='
|
||||
value: '1'
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: Status
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: status_op
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: status
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: true
|
||||
group_info:
|
||||
label: 'Published status'
|
||||
description: ''
|
||||
identifier: status
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items:
|
||||
1:
|
||||
title: Published
|
||||
operator: '='
|
||||
value: '1'
|
||||
2:
|
||||
title: Unpublished
|
||||
operator: '='
|
||||
value: '0'
|
||||
langcode:
|
||||
id: langcode
|
||||
table: node_field_data
|
||||
field: langcode
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: langcode
|
||||
plugin_id: language
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: langcode_op
|
||||
label: Language
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: langcode_op
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: langcode
|
||||
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: { }
|
||||
status_extra:
|
||||
id: status_extra
|
||||
table: node_field_data
|
||||
field: status_extra
|
||||
entity_type: node
|
||||
plugin_id: node_status
|
||||
operator: '='
|
||||
value: false
|
||||
group: 1
|
||||
expose:
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
columns:
|
||||
node_bulk_form: node_bulk_form
|
||||
title: title
|
||||
type: type
|
||||
name: name
|
||||
status: status
|
||||
changed: changed
|
||||
edit_node: edit_node
|
||||
delete_node: delete_node
|
||||
dropbutton: dropbutton
|
||||
timestamp: title
|
||||
default: changed
|
||||
info:
|
||||
node_bulk_form:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
title:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
type:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
name:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: priority-low
|
||||
status:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
changed:
|
||||
sortable: true
|
||||
default_sort_order: desc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: priority-low
|
||||
edit_node:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
delete_node:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
dropbutton:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
timestamp:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
override: true
|
||||
sticky: true
|
||||
summary: ''
|
||||
empty_table: true
|
||||
caption: ''
|
||||
description: ''
|
||||
class: ''
|
||||
row:
|
||||
type: fields
|
||||
query:
|
||||
type: views_query
|
||||
relationships:
|
||||
uid:
|
||||
id: uid
|
||||
table: node_field_data
|
||||
field: uid
|
||||
admin_label: author
|
||||
plugin_id: standard
|
||||
required: true
|
||||
show_admin_links: false
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
id: page_1
|
||||
display_title: Page
|
||||
display_plugin: page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: admin/content/node
|
||||
menu:
|
||||
type: 'default tab'
|
||||
title: Content
|
||||
description: ''
|
||||
weight: -10
|
||||
menu_name: admin
|
||||
context: ''
|
||||
tab_options:
|
||||
type: normal
|
||||
title: Content
|
||||
description: 'Find and manage content'
|
||||
weight: -10
|
||||
menu_name: admin
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
@ -0,0 +1,320 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: content_recent
|
||||
label: 'Recent content'
|
||||
module: node
|
||||
description: 'Recent content.'
|
||||
tag: default
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
id: default
|
||||
display_title: Default
|
||||
display_plugin: default
|
||||
position: 0
|
||||
display_options:
|
||||
title: 'Recent content'
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: field
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
trim: false
|
||||
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
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: true
|
||||
changed:
|
||||
id: changed
|
||||
table: node_field_data
|
||||
field: changed
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: changed
|
||||
plugin_id: field
|
||||
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: timestamp_ago
|
||||
settings: { }
|
||||
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
|
||||
pager:
|
||||
type: some
|
||||
options:
|
||||
offset: 0
|
||||
items_per_page: 10
|
||||
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
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
empty:
|
||||
area_text_custom:
|
||||
id: area_text_custom
|
||||
table: views
|
||||
field: area_text_custom
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
plugin_id: text_custom
|
||||
empty: true
|
||||
content: 'No content available.'
|
||||
tokenize: false
|
||||
sorts:
|
||||
changed:
|
||||
id: changed
|
||||
table: node_field_data
|
||||
field: changed
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: changed
|
||||
plugin_id: date
|
||||
order: DESC
|
||||
expose:
|
||||
label: ''
|
||||
field_identifier: changed
|
||||
exposed: false
|
||||
granularity: second
|
||||
arguments: { }
|
||||
filters:
|
||||
status_extra:
|
||||
id: status_extra
|
||||
table: node_field_data
|
||||
field: status_extra
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
plugin_id: node_status
|
||||
operator: '='
|
||||
value: false
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
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: { }
|
||||
langcode:
|
||||
id: langcode
|
||||
table: node_field_data
|
||||
field: langcode
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: langcode
|
||||
plugin_id: language
|
||||
operator: in
|
||||
value:
|
||||
'***LANGUAGE_language_content***': '***LANGUAGE_language_content***'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
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: { }
|
||||
style:
|
||||
type: html_list
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
type: ul
|
||||
wrapper_class: item-list
|
||||
class: ''
|
||||
row:
|
||||
type: fields
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
query_comment: ''
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_tags: { }
|
||||
relationships:
|
||||
uid:
|
||||
id: uid
|
||||
table: node_field_data
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: author
|
||||
entity_type: node
|
||||
entity_field: uid
|
||||
plugin_id: standard
|
||||
required: true
|
||||
use_more: false
|
||||
use_more_always: false
|
||||
use_more_text: More
|
||||
link_display: '0'
|
||||
link_url: ''
|
||||
header: { }
|
||||
footer: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- user
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
block_1:
|
||||
id: block_1
|
||||
display_title: Block
|
||||
display_plugin: block
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- user
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
310
web/core/modules/node/config/optional/views.view.frontpage.yml
Normal file
310
web/core/modules/node/config/optional/views.view.frontpage.yml
Normal file
@ -0,0 +1,310 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- core.entity_view_mode.node.rss
|
||||
- core.entity_view_mode.node.teaser
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: frontpage
|
||||
label: Frontpage
|
||||
module: node
|
||||
description: 'All content promoted to the front page.'
|
||||
tag: default
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
id: default
|
||||
display_title: Default
|
||||
display_plugin: default
|
||||
position: 0
|
||||
display_options:
|
||||
title: ''
|
||||
fields: { }
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
offset: 0
|
||||
pagination_heading_level: h4
|
||||
items_per_page: 10
|
||||
total_pages: 0
|
||||
id: 0
|
||||
tags:
|
||||
next: 'Next ›'
|
||||
previous: '‹ Previous'
|
||||
first: '« First'
|
||||
last: 'Last »'
|
||||
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
|
||||
quantity: 9
|
||||
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
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
empty:
|
||||
area_text_custom:
|
||||
id: area_text_custom
|
||||
table: views
|
||||
field: area_text_custom
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
plugin_id: text_custom
|
||||
label: ''
|
||||
empty: true
|
||||
content: 'No front page content has been created yet.<br/>Follow the <a target="_blank" href="https://www.drupal.org/docs/user_guide/en/index.html">User Guide</a> to start building your site.'
|
||||
tokenize: false
|
||||
node_listing_empty:
|
||||
id: node_listing_empty
|
||||
table: node
|
||||
field: node_listing_empty
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
plugin_id: node_listing_empty
|
||||
label: ''
|
||||
empty: true
|
||||
title:
|
||||
id: title
|
||||
table: views
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
plugin_id: title
|
||||
label: ''
|
||||
empty: true
|
||||
title: 'Welcome!'
|
||||
sorts:
|
||||
sticky:
|
||||
id: sticky
|
||||
table: node_field_data
|
||||
field: sticky
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: sticky
|
||||
plugin_id: standard
|
||||
order: DESC
|
||||
expose:
|
||||
label: ''
|
||||
field_identifier: sticky
|
||||
exposed: false
|
||||
created:
|
||||
id: created
|
||||
table: node_field_data
|
||||
field: created
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: created
|
||||
plugin_id: date
|
||||
order: DESC
|
||||
expose:
|
||||
label: ''
|
||||
field_identifier: created
|
||||
exposed: false
|
||||
granularity: second
|
||||
arguments: { }
|
||||
filters:
|
||||
promote:
|
||||
id: promote
|
||||
table: node_field_data
|
||||
field: promote
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: promote
|
||||
plugin_id: boolean
|
||||
operator: '='
|
||||
value: '1'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
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: { }
|
||||
status:
|
||||
id: status
|
||||
table: node_field_data
|
||||
field: status
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
plugin_id: boolean
|
||||
value: '1'
|
||||
group: 1
|
||||
expose:
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
langcode:
|
||||
id: langcode
|
||||
table: node_field_data
|
||||
field: langcode
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: langcode
|
||||
plugin_id: language
|
||||
operator: in
|
||||
value:
|
||||
'***LANGUAGE_language_content***': '***LANGUAGE_language_content***'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
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: { }
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: 'entity:node'
|
||||
options:
|
||||
view_mode: teaser
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
query_comment: ''
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_tags: { }
|
||||
relationships: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
feed_1:
|
||||
id: feed_1
|
||||
display_title: Feed
|
||||
display_plugin: feed
|
||||
position: 2
|
||||
display_options:
|
||||
pager:
|
||||
type: some
|
||||
options:
|
||||
offset: 0
|
||||
items_per_page: 10
|
||||
style:
|
||||
type: rss
|
||||
options:
|
||||
grouping: { }
|
||||
uses_fields: false
|
||||
description: ''
|
||||
row:
|
||||
type: node_rss
|
||||
options:
|
||||
relationship: none
|
||||
view_mode: rss
|
||||
display_extenders: { }
|
||||
path: rss.xml
|
||||
sitename_title: true
|
||||
displays:
|
||||
page_1: page_1
|
||||
default: ''
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
id: page_1
|
||||
display_title: Page
|
||||
display_plugin: page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: node
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
479
web/core/modules/node/config/optional/views.view.glossary.yml
Normal file
479
web/core/modules/node/config/optional/views.view.glossary.yml
Normal file
@ -0,0 +1,479 @@
|
||||
langcode: en
|
||||
status: false
|
||||
dependencies:
|
||||
config:
|
||||
- system.menu.main
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: glossary
|
||||
label: Glossary
|
||||
module: node
|
||||
description: 'All content, by letter.'
|
||||
tag: default
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
id: default
|
||||
display_title: Default
|
||||
display_plugin: default
|
||||
position: 0
|
||||
display_options:
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: field
|
||||
label: Title
|
||||
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: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
name:
|
||||
id: name
|
||||
table: users_field_data
|
||||
field: name
|
||||
relationship: uid
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
plugin_id: field
|
||||
label: Author
|
||||
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: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: user_name
|
||||
changed:
|
||||
id: changed
|
||||
table: node_field_data
|
||||
field: changed
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: changed
|
||||
plugin_id: field
|
||||
label: 'Last update'
|
||||
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: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: timestamp
|
||||
settings:
|
||||
date_format: long
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
tooltip:
|
||||
date_format: long
|
||||
custom_date_format: ''
|
||||
time_diff:
|
||||
enabled: false
|
||||
future_format: '@interval hence'
|
||||
past_format: '@interval ago'
|
||||
granularity: 2
|
||||
refresh: 60
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
offset: 0
|
||||
pagination_heading_level: h4
|
||||
items_per_page: 36
|
||||
total_pages: 0
|
||||
id: 0
|
||||
tags:
|
||||
next: ››
|
||||
previous: ‹‹
|
||||
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
|
||||
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
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
empty: { }
|
||||
sorts: { }
|
||||
arguments:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: string
|
||||
default_action: default
|
||||
exception:
|
||||
title_enable: true
|
||||
title_enable: false
|
||||
title: ''
|
||||
default_argument_type: fixed
|
||||
default_argument_options:
|
||||
argument: a
|
||||
summary_options: { }
|
||||
summary:
|
||||
format: default_summary
|
||||
specify_validation: true
|
||||
validate:
|
||||
type: none
|
||||
fail: 'not found'
|
||||
validate_options: { }
|
||||
glossary: true
|
||||
limit: 1
|
||||
case: upper
|
||||
path_case: lower
|
||||
transform_dash: false
|
||||
break_phrase: false
|
||||
filters:
|
||||
status:
|
||||
id: status
|
||||
table: node_field_data
|
||||
field: status
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
plugin_id: boolean
|
||||
value: '1'
|
||||
group: 1
|
||||
expose:
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
langcode:
|
||||
id: langcode
|
||||
table: node_field_data
|
||||
field: langcode
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: langcode
|
||||
plugin_id: language
|
||||
operator: in
|
||||
value:
|
||||
'***LANGUAGE_language_content***': '***LANGUAGE_language_content***'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
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: { }
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
columns:
|
||||
title: title
|
||||
name: name
|
||||
changed: changed
|
||||
default: title
|
||||
info:
|
||||
title:
|
||||
sortable: true
|
||||
separator: ''
|
||||
name:
|
||||
sortable: true
|
||||
separator: ''
|
||||
changed:
|
||||
sortable: true
|
||||
separator: ''
|
||||
override: true
|
||||
sticky: false
|
||||
summary: ''
|
||||
order: asc
|
||||
empty_table: false
|
||||
class: ''
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
default_field_elements: true
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
query_comment: ''
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_tags: { }
|
||||
relationships:
|
||||
uid:
|
||||
id: uid
|
||||
table: node_field_data
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: author
|
||||
plugin_id: standard
|
||||
required: false
|
||||
use_ajax: true
|
||||
header: { }
|
||||
footer: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
attachment_1:
|
||||
id: attachment_1
|
||||
display_title: Attachment
|
||||
display_plugin: attachment
|
||||
position: 2
|
||||
display_options:
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
items_per_page: 0
|
||||
arguments:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: string
|
||||
default_action: summary
|
||||
exception:
|
||||
title_enable: true
|
||||
title_enable: false
|
||||
title: ''
|
||||
default_argument_type: fixed
|
||||
default_argument_options:
|
||||
argument: a
|
||||
summary_options:
|
||||
items_per_page: 25
|
||||
inline: true
|
||||
separator: ' | '
|
||||
summary:
|
||||
format: unformatted_summary
|
||||
specify_validation: true
|
||||
validate:
|
||||
type: none
|
||||
fail: 'not found'
|
||||
validate_options: { }
|
||||
glossary: true
|
||||
limit: 1
|
||||
case: upper
|
||||
path_case: lower
|
||||
transform_dash: false
|
||||
break_phrase: false
|
||||
query:
|
||||
type: views_query
|
||||
options: { }
|
||||
defaults:
|
||||
arguments: false
|
||||
display_extenders: { }
|
||||
displays:
|
||||
default: default
|
||||
page_1: page_1
|
||||
inherit_arguments: false
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
id: page_1
|
||||
display_title: Page
|
||||
display_plugin: page
|
||||
position: 1
|
||||
display_options:
|
||||
query:
|
||||
type: views_query
|
||||
options: { }
|
||||
display_extenders: { }
|
||||
path: glossary
|
||||
menu:
|
||||
type: normal
|
||||
title: Glossary
|
||||
weight: 0
|
||||
menu_name: main
|
||||
parent: ''
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
94
web/core/modules/node/config/schema/node.schema.yml
Normal file
94
web/core/modules/node/config/schema/node.schema.yml
Normal file
@ -0,0 +1,94 @@
|
||||
# Schema for the configuration files of the node module.
|
||||
|
||||
node.settings:
|
||||
type: config_object
|
||||
label: 'Node settings'
|
||||
constraints:
|
||||
FullyValidatable: ~
|
||||
mapping:
|
||||
use_admin_theme:
|
||||
type: boolean
|
||||
label: 'Use administration theme when editing or creating content'
|
||||
|
||||
node.type.*:
|
||||
type: config_entity
|
||||
label: 'Content type'
|
||||
constraints:
|
||||
FullyValidatable: ~
|
||||
mapping:
|
||||
name:
|
||||
type: required_label
|
||||
label: 'Name'
|
||||
type:
|
||||
type: machine_name
|
||||
label: 'Machine-readable name'
|
||||
constraints:
|
||||
# Node type machine names are specifically limited to 32 characters.
|
||||
# @see \Drupal\node\Form\NodeTypeForm::form()
|
||||
Length:
|
||||
max: 32
|
||||
description:
|
||||
type: text
|
||||
label: 'Description'
|
||||
nullable: true
|
||||
constraints:
|
||||
NotBlank:
|
||||
allowNull: true
|
||||
help:
|
||||
type: text
|
||||
label: 'Explanation or submission guidelines'
|
||||
nullable: true
|
||||
constraints:
|
||||
NotBlank:
|
||||
allowNull: true
|
||||
new_revision:
|
||||
type: boolean
|
||||
label: 'Whether a new revision should be created by default'
|
||||
preview_mode:
|
||||
type: integer
|
||||
label: 'Preview before submitting'
|
||||
constraints:
|
||||
# These are the values of the DRUPAL_DISABLED, DRUPAL_OPTIONAL, and
|
||||
# DRUPAL_REQUIRED constants.
|
||||
# @see \Drupal\node\Form\NodeTypeForm::form()
|
||||
Choice: [0, 1, 2]
|
||||
display_submitted:
|
||||
type: boolean
|
||||
label: 'Display setting for author and date Submitted by post information'
|
||||
|
||||
# Plugin \Drupal\node\Plugin\Search\NodeSearch
|
||||
search.plugin.node_search:
|
||||
type: mapping
|
||||
label: 'Content search'
|
||||
mapping:
|
||||
rankings:
|
||||
type: sequence
|
||||
label: 'Content ranking'
|
||||
orderby: key
|
||||
sequence:
|
||||
type: integer
|
||||
label: 'Influence'
|
||||
|
||||
action.configuration.node_unpromote_action:
|
||||
type: action_configuration_default
|
||||
label: 'Demote selected content from front page configuration'
|
||||
|
||||
action.configuration.node_promote_action:
|
||||
type: action_configuration_default
|
||||
label: 'Promote selected content from front page configuration'
|
||||
|
||||
action.configuration.node_make_sticky_action:
|
||||
type: action_configuration_default
|
||||
label: 'Make selected content sticky configuration'
|
||||
|
||||
action.configuration.node_make_unsticky_action:
|
||||
type: action_configuration_default
|
||||
label: 'Make selected content unsticky configuration'
|
||||
|
||||
block.settings.node_syndicate_block:
|
||||
type: block_settings
|
||||
label: 'Syndicate block'
|
||||
mapping:
|
||||
block_count:
|
||||
type: integer
|
||||
label: 'Block count'
|
||||
148
web/core/modules/node/config/schema/node.views.schema.yml
Normal file
148
web/core/modules/node/config/schema/node.views.schema.yml
Normal file
@ -0,0 +1,148 @@
|
||||
# Schema for the views plugins of the Node module.
|
||||
|
||||
views.area.node_listing_empty:
|
||||
type: views_area
|
||||
label: 'Node link'
|
||||
|
||||
views.argument.node_nid:
|
||||
type: views_argument
|
||||
label: 'Node ID'
|
||||
mapping:
|
||||
break_phrase:
|
||||
type: boolean
|
||||
label: 'Allow multiple values'
|
||||
not:
|
||||
type: boolean
|
||||
label: 'Exclude'
|
||||
|
||||
views.argument.node_type:
|
||||
type: views_argument
|
||||
label: 'Node type'
|
||||
mapping:
|
||||
glossary:
|
||||
type: boolean
|
||||
label: 'Glossary mode'
|
||||
limit:
|
||||
type: integer
|
||||
label: 'Character limit'
|
||||
case:
|
||||
type: string
|
||||
label: 'Case'
|
||||
path_case:
|
||||
type: string
|
||||
label: 'Case in path'
|
||||
transform_dash:
|
||||
type: boolean
|
||||
label: 'Transform spaces to dashes in URL'
|
||||
break_phrase:
|
||||
type: boolean
|
||||
label: 'Allow multiple values'
|
||||
add_table:
|
||||
type: boolean
|
||||
label: 'Allow multiple filter values to work together'
|
||||
require_value:
|
||||
type: boolean
|
||||
label: 'Do not display items with no value in summary'
|
||||
|
||||
views.argument.node_uid_revision:
|
||||
type: views_argument
|
||||
label: 'Node user ID'
|
||||
mapping:
|
||||
break_phrase:
|
||||
type: boolean
|
||||
label: 'Allow multiple values'
|
||||
not:
|
||||
type: boolean
|
||||
label: 'Exclude'
|
||||
|
||||
views.argument.node_vid:
|
||||
type: views_argument
|
||||
label: 'Node revision ID'
|
||||
mapping:
|
||||
break_phrase:
|
||||
type: boolean
|
||||
label: 'Allow multiple values'
|
||||
not:
|
||||
type: boolean
|
||||
label: 'Exclude'
|
||||
|
||||
views.field.node:
|
||||
type: views_field
|
||||
label: 'Node'
|
||||
mapping:
|
||||
link_to_node:
|
||||
type: boolean
|
||||
label: 'Link this field to the original piece of content'
|
||||
|
||||
views.field.node_bulk_form:
|
||||
type: views_field_bulk_form
|
||||
label: 'Node bulk form'
|
||||
|
||||
views.field.node_revision_link:
|
||||
type: views_field
|
||||
label: 'Link to a node revision'
|
||||
mapping:
|
||||
text:
|
||||
type: label
|
||||
label: 'Text to display'
|
||||
|
||||
views.field.node_revision_link_delete:
|
||||
type: views_field
|
||||
label: 'Link to delete a node revision'
|
||||
mapping:
|
||||
text:
|
||||
type: label
|
||||
label: 'Text to display'
|
||||
|
||||
views.field.node_revision_link_revert:
|
||||
type: views_field
|
||||
label: 'Link to revert a node to a revision'
|
||||
mapping:
|
||||
text:
|
||||
type: label
|
||||
label: 'Text to display'
|
||||
|
||||
views.filter.node_access:
|
||||
type: views_filter
|
||||
label: 'Node access'
|
||||
|
||||
views.filter.node_status:
|
||||
type: views_filter
|
||||
label: 'Node status'
|
||||
|
||||
views.filter.node_uid_revision:
|
||||
type: views_filter
|
||||
label: 'Node revisions of an user'
|
||||
mapping:
|
||||
operator:
|
||||
type: string
|
||||
label: 'Operator'
|
||||
value:
|
||||
type: sequence
|
||||
label: 'Values'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Value'
|
||||
expose:
|
||||
type: mapping
|
||||
label: 'Expose'
|
||||
mapping:
|
||||
reduce:
|
||||
type: boolean
|
||||
label: 'Reduce'
|
||||
|
||||
views.filter_value.node_access:
|
||||
type: string
|
||||
label: 'Access'
|
||||
|
||||
views.filter_value.node_status:
|
||||
type: boolean
|
||||
label: 'Status'
|
||||
|
||||
views.row.node_rss:
|
||||
type: views_row
|
||||
label: 'Content options'
|
||||
mapping:
|
||||
view_mode:
|
||||
type: string
|
||||
label: 'Display type'
|
||||
11
web/core/modules/node/css/node.admin.css
Normal file
11
web/core/modules/node/css/node.admin.css
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @file
|
||||
* Styles for administration pages.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Revisions overview screen.
|
||||
*/
|
||||
.revision-current {
|
||||
background: #ffc;
|
||||
}
|
||||
71
web/core/modules/node/css/node.module.css
Normal file
71
web/core/modules/node/css/node.module.css
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @file
|
||||
* Styles for administration pages.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Node add/edit form layout
|
||||
*/
|
||||
|
||||
/* Narrow screens */
|
||||
.layout-region {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Wide screens */
|
||||
@media screen and (min-width: 780px), (orientation: landscape) and (min-device-height: 780px) {
|
||||
.layout-region-node-main,
|
||||
.layout-region-node-footer {
|
||||
float: left; /* LTR */
|
||||
box-sizing: border-box;
|
||||
width: 65%;
|
||||
padding-right: 2em; /* LTR */
|
||||
}
|
||||
|
||||
[dir="rtl"] .layout-region-node-main,
|
||||
[dir="rtl"] .layout-region-node-footer {
|
||||
float: right;
|
||||
padding-right: 0;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.layout-region-node-secondary {
|
||||
float: right; /* LTR */
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
[dir="rtl"] .layout-region-node-secondary {
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* @todo File an issue to add a standard class to all text-like inputs */
|
||||
.layout-region-node-secondary .form-autocomplete,
|
||||
.layout-region-node-secondary .form-text,
|
||||
.layout-region-node-secondary .form-tel,
|
||||
.layout-region-node-secondary .form-email,
|
||||
.layout-region-node-secondary .form-url,
|
||||
.layout-region-node-secondary .form-search,
|
||||
.layout-region-node-secondary .form-number,
|
||||
.layout-region-node-secondary .form-color,
|
||||
.layout-region-node-secondary textarea {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The vertical toolbar mode gets triggered for narrow screens, which throws off
|
||||
* the intent of media queries written for the viewport width. When the vertical
|
||||
* toolbar is on, we need to suppress layout for the original media width + the
|
||||
* toolbar width (240px). In this case, 240px + 780px.
|
||||
*/
|
||||
@media screen and (max-width: 1020px) {
|
||||
.toolbar-vertical.toolbar-tray-open .layout-region-node-main,
|
||||
.toolbar-vertical.toolbar-tray-open .layout-region-node-footer,
|
||||
.toolbar-vertical.toolbar-tray-open .layout-region-node-secondary {
|
||||
float: none;
|
||||
width: auto;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
29
web/core/modules/node/css/node.preview.css
Normal file
29
web/core/modules/node/css/node.preview.css
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @file
|
||||
* Styles for node preview page.
|
||||
*/
|
||||
|
||||
.node-preview-container {
|
||||
position: fixed;
|
||||
z-index: 499;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding-inline-start: var(--drupal-displace-offset-left, 10px);
|
||||
padding-inline-end: var(--drupal-displace-offset-right, 10px);
|
||||
padding-block: 10px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .node-preview-container {
|
||||
padding-inline-start: var(--drupal-displace-offset-right, 10px);
|
||||
padding-inline-end: var(--drupal-displace-offset-left, 10px);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 36em) {
|
||||
.node-preview-container .form-type-select {
|
||||
margin-left: 25%; /* LTR */
|
||||
}
|
||||
[dir="rtl"] .node-preview-container .form-type-select {
|
||||
margin-right: 25%;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
---
|
||||
label: 'Creating a content item'
|
||||
related:
|
||||
- node.overview
|
||||
- node.creating_type
|
||||
- path.creating_alias
|
||||
---
|
||||
{% set content_link_text %}
|
||||
{% trans %}Content{% endtrans %}
|
||||
{% endset %}
|
||||
{% set content_link = render_var(help_route_link(content_link_text, 'system.admin_content')) %}
|
||||
{% set content_permissions_link_text %}
|
||||
{% trans %}Access the Content overview page{% endtrans %}
|
||||
{% endset %}
|
||||
{% set content_permissions_link = render_var(help_route_link(content_permissions_link_text, 'user.admin_permissions.module', {'modules': 'node'})) %}
|
||||
{% set content_overview_topic = render_var(help_topic_link('node.overview')) %}
|
||||
<h2>{% trans %}Goal{% endtrans %}</h2>
|
||||
<p>{% trans %}Create and publish a content item. See {{ content_overview_topic }} for more about content types and content items.{% endtrans %}</p>
|
||||
<h2>{% trans %}Who can create content?{% endtrans %}</h2>
|
||||
<p>{% trans %}Users with the <em>{{ content_permissions_link }}</em> permission can visit the <em>Content</em> page as described in this topic. Each content type has its own create permissions. For example, to create content of type Article, a user would need the Article: Create new content permission. In addition, users with the <em>Bypass content access control</em> or <em>Administer content</em> permission can create content items of all types. Some contributed modules change the permission structure for creating content.{% endtrans %}</p>
|
||||
<h2>{% trans %}Steps{% endtrans %}</h2>
|
||||
<ol>
|
||||
<li>{% trans %}In the <em>Manage</em> administrative menu, navigate to <em>{{ content_link }}</em>.{% endtrans %}</li>
|
||||
<li>{% trans %}Click <em>Add content</em>.{% endtrans %}</li>
|
||||
<li>{% trans %}If there is more than one content type defined on your site that you have permission to create, click the name of the type of content you want to create.{% endtrans %}</li>
|
||||
<li>{% trans %}On the content edit form, enter the <em>Title</em> of your content, which will show as the page title when the content item is displayed on a page, and also as the label for the content item in administration screens.{% endtrans %}</li>
|
||||
<li>{% trans %}Fill in the other fields shown on the edit form for this specific content type.{% endtrans %}</li>
|
||||
<li>{% trans %}Leave the <em>Published</em> field checked to publish the content immediately, or uncheck it to make it unpublished. Unpublished content is generally not shown to non-administrative site users.{% endtrans %}</li>
|
||||
<li>{% trans %}Optionally, click <em>Preview</em> to preview the content.{% endtrans %}</li>
|
||||
<li>{% trans %}Click <em>Save</em>. You will see the content displayed on a page.{% endtrans %}</li>
|
||||
</ol>
|
||||
<h2>{% trans %}Additional resources{% endtrans %}</h2>
|
||||
<ul>
|
||||
<li><a href="https://www.drupal.org/docs/user_guide/en/content-chapter.html">{% trans %}Basic Page Management (Drupal User Guide){% endtrans %}</a></li>
|
||||
<li><a href="https://www.drupal.org/docs/user_guide/en/content-create.html">{% trans %}Creating a Content Item (Drupal User Guide){% endtrans %}</a></li>
|
||||
</ul>
|
||||
@ -0,0 +1,36 @@
|
||||
---
|
||||
label: 'Creating a content type'
|
||||
related:
|
||||
- node.overview
|
||||
- core.content_structure
|
||||
- field_ui.add_field
|
||||
- field_ui.manage_display
|
||||
- field_ui.manage_form
|
||||
---
|
||||
{% set content_permissions_link_text %}
|
||||
{% trans %}Administer content types{% endtrans %}
|
||||
{% endset %}
|
||||
{% set content_permissions_link = render_var(help_route_link(content_permissions_link_text, 'user.admin_permissions.module', {'modules': 'node'})) %}
|
||||
{% set content_types_link_text %}
|
||||
{% trans %}Content types{% endtrans %}
|
||||
{% endset %}
|
||||
{% set content_types_link = render_var(help_route_link(content_types_link_text, 'entity.node_type.collection')) %}
|
||||
{% set content_overview_topic = render_var(help_topic_link('node.overview')) %}
|
||||
<h2>{% trans %}Goal{% endtrans %}</h2>
|
||||
<p>{% trans %}Create a new content type. See {{ content_overview_topic }} for more about content types.{% endtrans %}</p>
|
||||
<h2>{% trans %}Who can create a content type?{% endtrans %}</h2>
|
||||
<p>{% trans %}Users with the <em>{{ content_permissions_link }}</em> permission (typically administrators) can create new content types.{% endtrans %}</p>
|
||||
<h2>{% trans %}Steps{% endtrans %}</h2>
|
||||
<ol>
|
||||
<li>{% trans %}In the <em>Manage</em> administrative menu, navigate to <em>Structure</em> > <em>{{ content_types_link }}</em>.{% endtrans %}</li>
|
||||
<li>{% trans %}Click <em>Add content type.</em>{% endtrans %}</li>
|
||||
<li>{% trans %}In the <em>Name</em> field, enter a name for the content type, which is how it will be listed in the administrative interface.{% endtrans %}</li>
|
||||
<li>{% trans %}Optionally, enter a <em>Description</em> for the content type. You may also want to adjust some of the settings in the vertical tabs section of the edit page.{% endtrans %}</li>
|
||||
<li>{% trans %}Click <em>Save and manage fields</em>. Your content type will be created, and assuming you have the core Field UI module installed, you will be taken to the <em>Manage fields</em> page for the content type. (If you do not have the core Field UI module installed, the button will say <em>Save</em> instead.){% endtrans %}</li>
|
||||
<li>{% trans %}If you have the core Field UI module installed, follow the steps in the related topics to add fields to the new content type, set up the editing form, and configure the display.{% endtrans %}</li>
|
||||
</ol>
|
||||
<h2>{% trans %}Additional resources{% endtrans %}</h2>
|
||||
<ul>
|
||||
<li><a href="https://www.drupal.org/docs/user_guide/en/structure-content-type.html">{% trans %}Adding a Content Type (Drupal User Guide){% endtrans %}</a></li>
|
||||
<li><a href="https://www.drupal.org/docs/user_guide/en/content-structure-chapter.html">{% trans %}Setting Up Content Structure (Drupal User Guide){% endtrans %}</a></li>
|
||||
</ul>
|
||||
39
web/core/modules/node/help_topics/node.editing.html.twig
Normal file
39
web/core/modules/node/help_topics/node.editing.html.twig
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
label: 'Editing a content item'
|
||||
related:
|
||||
- node.overview
|
||||
- node.creating_content
|
||||
---
|
||||
{% set content_link_text %}
|
||||
{% trans %}Content{% endtrans %}
|
||||
{% endset %}
|
||||
{% set content_link = render_var(help_route_link(content_link_text, 'system.admin_content')) %}
|
||||
{% set content_permissions_link_text %}
|
||||
{% trans %}Access the Content overview page{% endtrans %}
|
||||
{% endset %}
|
||||
{% set content_permissions_link = render_var(help_route_link(content_permissions_link_text, 'user.admin_permissions.module', {'modules': 'node'})) %}
|
||||
{% set node_overview_topic = render_var(help_topic_link('node.overview')) %}
|
||||
<h2>{% trans %}Goal{% endtrans %}</h2>
|
||||
<p>{% trans %}Find a content item and edit it, or update a group of content items in bulk. See {{ node_overview_topic }} for more about content types and content items.{% endtrans %}</p>
|
||||
<h2>{% trans %}Who can find and edit content?{% endtrans %}</h2>
|
||||
<p>{% trans %}Users with the <em>{{ content_permissions_link }}</em> permission can use the <em>Content</em> page to find content. Each content type has its own edit permissions. For example, to edit content of type Article, a user would need the <em>Article: Edit own content</em> permission to edit an article they created, or the <em>Article: Edit any content</em> permission to edit an article someone else created. In addition, users with the <em>Bypass content access control</em> or <em>Administer content</em> permission can edit content items of all types. Some contributed modules change the permission structure for editing content.{% endtrans %}</p>
|
||||
<h2>{% trans %}Steps{% endtrans %}</h2>
|
||||
<ol>
|
||||
<li>{% trans %}In the <em>Manage</em> administrative menu, navigate to <em>{{ content_link }}</em>.{% endtrans %}</li>
|
||||
<li>{% trans %}Optionally, use filters to reduce the list of content items shown:{% endtrans %}
|
||||
<ul>
|
||||
<li>{% trans %}<em>Title</em>: key word(s) from the title{% endtrans %}</li>
|
||||
<li>{% trans %}<em>Content type</em>{% endtrans %}</li>
|
||||
<li>{% trans %}<em>Published status</em>{% endtrans %}</li>
|
||||
<li>{% trans %}<em>Language</em>{% endtrans %}</li>
|
||||
</ul>
|
||||
{% trans %}If you enter or select filter values, click <em>Filter</em> to apply the filters.{% endtrans %}</li>
|
||||
<li>{% trans %}Optionally, sort the list by clicking a column header. Click again to reverse the order.{% endtrans %}</li>
|
||||
<li>{% trans %}To edit the title or other field values for one content item, click <em>Edit</em> in the row of the content item. Update the values and click <em>Save</em>.{% endtrans %}</li>
|
||||
<li>{% trans %}A few types of edits can be done in bulk to multiple content items. For example, to publish several unpublished content items, check the boxes in the left column (right column in right-to-left languages) to select the desired content items. For <em>Action</em>, select the <em>Publish content</em> action. Click <em>Apply to selected items</em> to make the change. The other actions under <em>Action</em> work in a similar manner.{% endtrans %}</li>
|
||||
</ol>
|
||||
<h2>{% trans %}Additional resources{% endtrans %}</h2>
|
||||
<ul>
|
||||
<li><a href="https://www.drupal.org/docs/user_guide/en/content-chapter.html">{% trans %}Basic Page Management (Drupal User Guide){% endtrans %}</a></li>
|
||||
<li><a href="https://www.drupal.org/docs/user_guide/en/content-edit.html">{% trans %}Editing a Content Item (Drupal User Guide){% endtrans %}</a></li>
|
||||
</ul>
|
||||
19
web/core/modules/node/help_topics/node.overview.html.twig
Normal file
19
web/core/modules/node/help_topics/node.overview.html.twig
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
label: 'Managing content'
|
||||
top_level: true
|
||||
related:
|
||||
- core.content_structure
|
||||
- field_ui.add_field
|
||||
- field_ui.manage_display
|
||||
- field_ui.manage_form
|
||||
- node.creating_type
|
||||
- node.creating_content
|
||||
- node.editing
|
||||
---
|
||||
{% set content_structure_topic = render_var(help_topic_link('core.content_structure')) %}
|
||||
<h2>{% trans %}What is a content item?{% endtrans %}</h2>
|
||||
<p>{% trans %}A <em>content item</em> is a type of content entity for page-level content, which can have fields that store text, HTML markup, images, attached files, and other data. See {{ content_structure_topic }} for more about content entities and fields.{% endtrans %}</p>
|
||||
<h2>{% trans %}What is a content type?{% endtrans %}</h2>
|
||||
<p>{% trans %}Content items are divided into <em>content types</em>, which are the entity sub-types for the content item entity type; each content type has its own fields and display settings. For example, you might set up content types for pages, articles, recipes, events, and blog entries on your web site.{% endtrans %}</p>
|
||||
<h2>{% trans %}Overview of managing content{% endtrans %}</h2>
|
||||
<p>{% trans %}The core Node module allows you to define content types, and add and edit content items. The core Field UI module allows you to attach fields to each content type and manage the edit form and display for each content type. See the related topics listed below for specific tasks. Many other core and contributed modules and installation profiles provide pre-defined content types, modify the permission structure for content items, and provide other functionality.{% endtrans %}</p>
|
||||
74
web/core/modules/node/js/content_types.js
Normal file
74
web/core/modules/node/js/content_types.js
Normal file
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @file
|
||||
* JavaScript for the node content editing form.
|
||||
*/
|
||||
|
||||
(function ($, Drupal) {
|
||||
/**
|
||||
* Behaviors for setting summaries on content type form.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches summary behaviors on content type edit forms.
|
||||
*/
|
||||
Drupal.behaviors.contentTypes = {
|
||||
attach(context) {
|
||||
const $context = $(context);
|
||||
// Provide the vertical tab summaries.
|
||||
$context.find('#edit-submission').drupalSetSummary((context) => {
|
||||
const values = [];
|
||||
values.push(
|
||||
Drupal.checkPlain($(context).find('#edit-title-label')[0].value) ||
|
||||
Drupal.t('Requires a title'),
|
||||
);
|
||||
return values.join(', ');
|
||||
});
|
||||
$context.find('#edit-workflow').drupalSetSummary((context) => {
|
||||
const values = [];
|
||||
$(context)
|
||||
.find('input[name^="options"]:checked')
|
||||
.next('label')
|
||||
.each(function () {
|
||||
values.push(Drupal.checkPlain(this.textContent));
|
||||
});
|
||||
if ($(context).find('#edit-options-status:checked').length === 0) {
|
||||
values.unshift(Drupal.t('Not published'));
|
||||
}
|
||||
return values.join(', ');
|
||||
});
|
||||
$('#edit-language', context).drupalSetSummary((context) => {
|
||||
const values = [];
|
||||
|
||||
values.push(
|
||||
$(
|
||||
'.js-form-item-language-configuration-langcode select option:selected',
|
||||
context,
|
||||
)[0].textContent,
|
||||
);
|
||||
|
||||
$('input:checked', context)
|
||||
.next('label')
|
||||
.each(function () {
|
||||
values.push(Drupal.checkPlain(this.textContent));
|
||||
});
|
||||
|
||||
return values.join(', ');
|
||||
});
|
||||
$context.find('#edit-display').drupalSetSummary((context) => {
|
||||
const values = [];
|
||||
const $editContext = $(context);
|
||||
$editContext
|
||||
.find('input:checked')
|
||||
.next('label')
|
||||
.each(function () {
|
||||
values.push(Drupal.checkPlain(this.textContent));
|
||||
});
|
||||
if ($editContext.find('#edit-display-submitted:checked').length === 0) {
|
||||
values.unshift(Drupal.t("Don't display post information"));
|
||||
}
|
||||
return values.join(', ');
|
||||
});
|
||||
},
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
57
web/core/modules/node/js/node.js
Normal file
57
web/core/modules/node/js/node.js
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @file
|
||||
* Defines JavaScript behaviors for the node module.
|
||||
*/
|
||||
|
||||
(function ($, Drupal, drupalSettings) {
|
||||
/**
|
||||
* Behaviors for tabs in the node edit form.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches summary behavior for tabs in the node edit form.
|
||||
*/
|
||||
Drupal.behaviors.nodeDetailsSummaries = {
|
||||
attach(context) {
|
||||
const $context = $(context);
|
||||
|
||||
$context.find('.node-form-author').drupalSetSummary((context) => {
|
||||
const nameElement = context.querySelector('.field--name-uid input');
|
||||
const name = nameElement?.value;
|
||||
const dateElement = context.querySelector('.field--name-created input');
|
||||
const date = dateElement?.value;
|
||||
|
||||
if (name && date) {
|
||||
return Drupal.t('By @name on @date', {
|
||||
'@name': name,
|
||||
'@date': date,
|
||||
});
|
||||
}
|
||||
if (name) {
|
||||
return Drupal.t('By @name', { '@name': name });
|
||||
}
|
||||
if (date) {
|
||||
return Drupal.t('Authored on @date', { '@date': date });
|
||||
}
|
||||
});
|
||||
|
||||
$context.find('.node-form-options').drupalSetSummary((context) => {
|
||||
const $optionsContext = $(context);
|
||||
const values = [];
|
||||
|
||||
if ($optionsContext.find('input:checked').length) {
|
||||
$optionsContext
|
||||
.find('input:checked')
|
||||
.next('label')
|
||||
.each(function () {
|
||||
values.push(Drupal.checkPlain(this.textContent.trim()));
|
||||
});
|
||||
return values.join(', ');
|
||||
}
|
||||
|
||||
return Drupal.t('Not promoted');
|
||||
});
|
||||
},
|
||||
};
|
||||
})(jQuery, Drupal, drupalSettings);
|
||||
117
web/core/modules/node/js/node.preview.js
Normal file
117
web/core/modules/node/js/node.preview.js
Normal file
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* @file
|
||||
* Preview behaviors.
|
||||
*/
|
||||
|
||||
(function ($, Drupal) {
|
||||
/**
|
||||
* Disables all non-relevant links in node previews.
|
||||
*
|
||||
* Destroys links (except local fragment identifiers such as href="#frag") in
|
||||
* node previews to prevent users from leaving the page.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches confirmation prompt for clicking links in node preview mode.
|
||||
* @prop {Drupal~behaviorDetach} detach
|
||||
* Detaches confirmation prompt for clicking links in node preview mode.
|
||||
*/
|
||||
Drupal.behaviors.nodePreviewDestroyLinks = {
|
||||
attach(context) {
|
||||
function clickPreviewModal(event) {
|
||||
// Only confirm leaving previews when left-clicking and user is not
|
||||
// pressing the ALT, CTRL, META (Command key on the Macintosh keyboard)
|
||||
// or SHIFT key.
|
||||
if (
|
||||
event.button === 0 &&
|
||||
!event.altKey &&
|
||||
!event.ctrlKey &&
|
||||
!event.metaKey &&
|
||||
!event.shiftKey
|
||||
) {
|
||||
event.preventDefault();
|
||||
const $previewDialog = $(
|
||||
`<div>${Drupal.theme('nodePreviewModal')}</div>`,
|
||||
).appendTo('body');
|
||||
const confirmationDialog = Drupal.dialog($previewDialog, {
|
||||
title: Drupal.t('Leave preview?'),
|
||||
buttons: [
|
||||
{
|
||||
text: Drupal.t('Cancel'),
|
||||
click() {
|
||||
confirmationDialog.close();
|
||||
},
|
||||
},
|
||||
{
|
||||
text: Drupal.t('Leave preview'),
|
||||
click() {
|
||||
window.top.location.href = event.currentTarget.href;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
confirmationDialog.showModal();
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.querySelector('.node-preview-container')) {
|
||||
return;
|
||||
}
|
||||
if (once('node-preview', 'html').length) {
|
||||
$(document).on(
|
||||
'click.preview',
|
||||
'a:not([href^="#"], .node-preview-container a)',
|
||||
clickPreviewModal,
|
||||
);
|
||||
}
|
||||
},
|
||||
detach(context, settings, trigger) {
|
||||
if (trigger === 'unload') {
|
||||
if (
|
||||
context.querySelector('.node-preview-container') &&
|
||||
once.remove('node-preview', 'html').length
|
||||
) {
|
||||
$(document).off('click.preview');
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Switch view mode.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches automatic submit on `formUpdated.preview` events.
|
||||
*/
|
||||
Drupal.behaviors.nodePreviewSwitchViewMode = {
|
||||
attach(context) {
|
||||
const autosubmit = once(
|
||||
'autosubmit',
|
||||
'[data-drupal-autosubmit]',
|
||||
context,
|
||||
);
|
||||
if (autosubmit.length) {
|
||||
$(autosubmit).on('formUpdated.preview', function () {
|
||||
$(this.form).trigger('submit');
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Theme function for node preview modal.
|
||||
*
|
||||
* @return {string}
|
||||
* Markup for the node preview modal.
|
||||
*/
|
||||
Drupal.theme.nodePreviewModal = function () {
|
||||
return `<p>${Drupal.t(
|
||||
'Leaving the preview will cause unsaved changes to be lost. Are you sure you want to leave the preview?',
|
||||
)}</p><small class="description">${Drupal.t(
|
||||
'CTRL+Left click will prevent this dialog from showing and proceed to the clicked link.',
|
||||
)}</small>`;
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
57
web/core/modules/node/migrations/d6_node.yml
Normal file
57
web/core/modules/node/migrations/d6_node.yml
Normal file
@ -0,0 +1,57 @@
|
||||
# cspell:ignore tnid
|
||||
id: d6_node
|
||||
label: Nodes
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
deriver: Drupal\node\Plugin\migrate\D6NodeDeriver
|
||||
source:
|
||||
plugin: d6_node
|
||||
process:
|
||||
# In D6, nodes always have a tnid, but it's zero for untranslated nodes.
|
||||
# We normalize it to equal the nid in that case.
|
||||
# @see \Drupal\node\Plugin\migrate\source\d6\Node::prepareRow().
|
||||
# If you are using this file to build a custom migration consider removing
|
||||
# the nid and vid fields to allow incremental migrations.
|
||||
nid: tnid
|
||||
vid: vid
|
||||
langcode:
|
||||
plugin: default_value
|
||||
source: language
|
||||
default_value: "und"
|
||||
title: title
|
||||
uid: node_uid
|
||||
status: status
|
||||
created: created
|
||||
changed: changed
|
||||
promote: promote
|
||||
sticky: sticky
|
||||
'body/format':
|
||||
plugin: migration_lookup
|
||||
migration: d6_filter_format
|
||||
source: format
|
||||
'body/value': body
|
||||
'body/summary': teaser
|
||||
revision_uid: revision_uid
|
||||
revision_log: log
|
||||
revision_timestamp: timestamp
|
||||
|
||||
# unmapped d6 fields.
|
||||
# tnid
|
||||
# translate
|
||||
# moderate
|
||||
# comment
|
||||
|
||||
destination:
|
||||
plugin: entity:node
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_user
|
||||
- d6_node_type
|
||||
- d6_node_settings
|
||||
- d6_filter_format
|
||||
optional:
|
||||
- d6_field_instance_widget_settings
|
||||
- d6_field_formatter_settings
|
||||
- d6_upload_field_instance
|
||||
59
web/core/modules/node/migrations/d6_node_complete.yml
Normal file
59
web/core/modules/node/migrations/d6_node_complete.yml
Normal file
@ -0,0 +1,59 @@
|
||||
# Migrates all revisions and all revision translations.
|
||||
# cspell:ignore tnid
|
||||
id: d6_node_complete
|
||||
label: Node Complete
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
class: Drupal\node\Plugin\migrate\D6NodeTranslation
|
||||
deriver: Drupal\node\Plugin\migrate\D6NodeDeriver
|
||||
source:
|
||||
plugin: d6_node_complete
|
||||
process:
|
||||
# If you are using this file to build a custom migration consider removing
|
||||
# the nid and vid fields to allow incremental migrations.
|
||||
# In D6, nodes always have a tnid, but it's zero for untranslated nodes.
|
||||
# We normalize it to equal the nid in that case.
|
||||
# @see \Drupal\node\Plugin\migrate\source\d6\Node::prepareRow().
|
||||
nid: tnid
|
||||
vid: vid
|
||||
langcode:
|
||||
plugin: default_value
|
||||
source: language
|
||||
default_value: "und"
|
||||
title: title
|
||||
uid: node_uid
|
||||
status: status
|
||||
created: created
|
||||
changed: timestamp
|
||||
promote: promote
|
||||
sticky: sticky
|
||||
'body/format':
|
||||
plugin: migration_lookup
|
||||
migration: d6_filter_format
|
||||
source: format
|
||||
'body/value': body
|
||||
'body/summary': teaser
|
||||
revision_uid: revision_uid
|
||||
revision_log: log
|
||||
revision_timestamp: timestamp
|
||||
content_translation_source: source_langcode
|
||||
# unmapped d6 fields.
|
||||
# translate
|
||||
# moderate
|
||||
# comment
|
||||
destination:
|
||||
plugin: entity_complete:node
|
||||
translations: true
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_user
|
||||
- d6_node_type
|
||||
- d6_node_settings
|
||||
- d6_filter_format
|
||||
- language
|
||||
optional:
|
||||
- d6_field_instance_widget_settings
|
||||
- d6_field_formatter_settings
|
||||
- d6_upload_field_instance
|
||||
44
web/core/modules/node/migrations/d6_node_revision.yml
Normal file
44
web/core/modules/node/migrations/d6_node_revision.yml
Normal file
@ -0,0 +1,44 @@
|
||||
# cspell:ignore tnid
|
||||
id: d6_node_revision
|
||||
label: Node revisions
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
deriver: Drupal\node\Plugin\migrate\D6NodeDeriver
|
||||
source:
|
||||
plugin: d6_node_revision
|
||||
process:
|
||||
# If you are using this file to build a custom migration consider removing
|
||||
# the nid and vid fields to allow incremental migrations.
|
||||
nid: nid
|
||||
vid: vid
|
||||
langcode:
|
||||
plugin: default_value
|
||||
source: language
|
||||
default_value: "und"
|
||||
title: title
|
||||
uid: node_uid
|
||||
status: status
|
||||
created: created
|
||||
changed: changed
|
||||
promote: promote
|
||||
sticky: sticky
|
||||
'body/format':
|
||||
plugin: migration_lookup
|
||||
migration: d6_filter_format
|
||||
source: format
|
||||
'body/value': body
|
||||
'body/summary': teaser
|
||||
revision_uid: revision_uid
|
||||
revision_log: log
|
||||
revision_timestamp: timestamp
|
||||
|
||||
# unmapped d6 fields.
|
||||
# tnid
|
||||
# translate
|
||||
# moderate
|
||||
# comment
|
||||
|
||||
destination:
|
||||
plugin: entity_revision:node
|
||||
23
web/core/modules/node/migrations/d6_node_setting_promote.yml
Normal file
23
web/core/modules/node/migrations/d6_node_setting_promote.yml
Normal file
@ -0,0 +1,23 @@
|
||||
id: d6_node_setting_promote
|
||||
label: Node type 'promote' setting
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_node_type
|
||||
constants:
|
||||
entity_type: node
|
||||
field_name: promote
|
||||
process:
|
||||
entity_type: 'constants/entity_type'
|
||||
bundle: type
|
||||
field_name: 'constants/field_name'
|
||||
label:
|
||||
plugin: default_value
|
||||
default_value: 'Promoted to front page'
|
||||
'default_value/0/value': 'options/promote'
|
||||
destination:
|
||||
plugin: entity:base_field_override
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_node_type
|
||||
23
web/core/modules/node/migrations/d6_node_setting_status.yml
Normal file
23
web/core/modules/node/migrations/d6_node_setting_status.yml
Normal file
@ -0,0 +1,23 @@
|
||||
id: d6_node_setting_status
|
||||
label: Node type 'status' setting
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_node_type
|
||||
constants:
|
||||
entity_type: node
|
||||
field_name: status
|
||||
process:
|
||||
entity_type: 'constants/entity_type'
|
||||
bundle: type
|
||||
field_name: 'constants/field_name'
|
||||
label:
|
||||
plugin: default_value
|
||||
default_value: 'Publishing status'
|
||||
'default_value/0/value': 'options/status'
|
||||
destination:
|
||||
plugin: entity:base_field_override
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_node_type
|
||||
23
web/core/modules/node/migrations/d6_node_setting_sticky.yml
Normal file
23
web/core/modules/node/migrations/d6_node_setting_sticky.yml
Normal file
@ -0,0 +1,23 @@
|
||||
id: d6_node_setting_sticky
|
||||
label: Node type 'sticky' setting
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_node_type
|
||||
constants:
|
||||
entity_type: node
|
||||
field_name: sticky
|
||||
process:
|
||||
entity_type: 'constants/entity_type'
|
||||
bundle: type
|
||||
field_name: 'constants/field_name'
|
||||
label:
|
||||
plugin: default_value
|
||||
default_value: 'Sticky at the top of lists'
|
||||
'default_value/0/value': 'options/sticky'
|
||||
destination:
|
||||
plugin: entity:base_field_override
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_node_type
|
||||
15
web/core/modules/node/migrations/d6_node_settings.yml
Normal file
15
web/core/modules/node/migrations/d6_node_settings.yml
Normal file
@ -0,0 +1,15 @@
|
||||
id: d6_node_settings
|
||||
label: Node configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- node_admin_theme
|
||||
source_module: node
|
||||
process:
|
||||
use_admin_theme: node_admin_theme
|
||||
destination:
|
||||
plugin: config
|
||||
config_name: node.settings
|
||||
43
web/core/modules/node/migrations/d6_node_type.yml
Normal file
43
web/core/modules/node/migrations/d6_node_type.yml
Normal file
@ -0,0 +1,43 @@
|
||||
id: d6_node_type
|
||||
label: Node type configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_node_type
|
||||
constants:
|
||||
preview: 1 # DRUPAL_OPTIONAL
|
||||
create_body: false
|
||||
process:
|
||||
type: type
|
||||
name: name
|
||||
module: module
|
||||
description: description
|
||||
help: help
|
||||
title_label: title_label
|
||||
'preview_mode': 'constants/preview'
|
||||
'display_submitted': display_submitted
|
||||
'new_revision': 'options/revision'
|
||||
'settings/node/options': options
|
||||
create_body: has_body
|
||||
create_body_label: body_label
|
||||
'third_party_settings/menu_ui/available_menus':
|
||||
plugin: static_map
|
||||
bypass: true
|
||||
source: available_menus
|
||||
map:
|
||||
main-menu: main
|
||||
management: admin
|
||||
navigation: tools
|
||||
user-menu: account
|
||||
'third_party_settings/menu_ui/parent':
|
||||
plugin: static_map
|
||||
bypass: true
|
||||
source: parent
|
||||
map:
|
||||
'main-menu:': 'main:'
|
||||
'management:': 'admin:'
|
||||
'navigation:': 'tools:'
|
||||
'user-menu:': 'account:'
|
||||
destination:
|
||||
plugin: entity:node_type
|
||||
39
web/core/modules/node/migrations/d6_view_modes.yml
Normal file
39
web/core/modules/node/migrations/d6_view_modes.yml
Normal file
@ -0,0 +1,39 @@
|
||||
id: d6_view_modes
|
||||
label: View modes
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_view_mode
|
||||
constants:
|
||||
entity_type: node
|
||||
status: true
|
||||
process:
|
||||
mode:
|
||||
plugin: static_map
|
||||
source: view_mode
|
||||
map:
|
||||
0: normal
|
||||
1: preview
|
||||
2: search_index
|
||||
3: search_result
|
||||
4: rss
|
||||
5: print
|
||||
teaser: teaser
|
||||
full: full
|
||||
label:
|
||||
plugin: static_map
|
||||
source: view_mode
|
||||
map:
|
||||
0: "Normal"
|
||||
1: "Preview"
|
||||
2: "Search index"
|
||||
3: "Search result"
|
||||
4: "RSS"
|
||||
5: "Print"
|
||||
teaser: "Teaser"
|
||||
full: "Full"
|
||||
targetEntityType: 'constants/entity_type'
|
||||
status: 'constants/status'
|
||||
destination:
|
||||
plugin: entity:entity_view_mode
|
||||
41
web/core/modules/node/migrations/d7_node.yml
Normal file
41
web/core/modules/node/migrations/d7_node.yml
Normal file
@ -0,0 +1,41 @@
|
||||
# cspell:ignore tnid
|
||||
id: d7_node
|
||||
label: Nodes
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Content
|
||||
deriver: Drupal\node\Plugin\migrate\D7NodeDeriver
|
||||
source:
|
||||
plugin: d7_node
|
||||
process:
|
||||
# If you are using this file to build a custom migration consider removing
|
||||
# the nid and vid fields to allow incremental migrations.
|
||||
# In D7, nodes always have a tnid, but it's zero for untranslated nodes.
|
||||
# We normalize it to equal the nid in that case.
|
||||
# @see \Drupal\node\Plugin\migrate\source\d7\Node::prepareRow().
|
||||
nid: tnid
|
||||
vid: vid
|
||||
langcode:
|
||||
plugin: default_value
|
||||
source: language
|
||||
default_value: "und"
|
||||
title: title
|
||||
uid: node_uid
|
||||
status: status
|
||||
created: created
|
||||
changed: changed
|
||||
promote: promote
|
||||
sticky: sticky
|
||||
revision_uid: revision_uid
|
||||
revision_log: log
|
||||
revision_timestamp: timestamp
|
||||
destination:
|
||||
plugin: entity:node
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_user
|
||||
- d7_node_type
|
||||
optional:
|
||||
- d7_field_instance
|
||||
- d7_comment_field_instance
|
||||
49
web/core/modules/node/migrations/d7_node_complete.yml
Normal file
49
web/core/modules/node/migrations/d7_node_complete.yml
Normal file
@ -0,0 +1,49 @@
|
||||
# Migrates all revisions and all revision translations.
|
||||
# cspell:ignore tnid
|
||||
id: d7_node_complete
|
||||
label: Node complete
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Content
|
||||
class: Drupal\node\Plugin\migrate\D7NodeTranslation
|
||||
deriver: Drupal\node\Plugin\migrate\D7NodeDeriver
|
||||
source:
|
||||
plugin: d7_node_complete
|
||||
process:
|
||||
# If you are using this file to build a custom migration consider removing
|
||||
# the nid and vid fields to allow incremental migrations.
|
||||
# In D7, nodes always have a tnid, but it's zero for untranslated nodes.
|
||||
# We normalize it to equal the nid in that case.
|
||||
# @see \Drupal\node\Plugin\migrate\source\d7\Node::prepareRow().
|
||||
nid: tnid
|
||||
vid: vid
|
||||
langcode:
|
||||
plugin: default_value
|
||||
source: language
|
||||
default_value: "und"
|
||||
title: title
|
||||
uid: node_uid
|
||||
status: status
|
||||
created: created
|
||||
changed: timestamp
|
||||
promote: promote
|
||||
sticky: sticky
|
||||
revision_uid: revision_uid
|
||||
revision_log: log
|
||||
revision_timestamp: timestamp
|
||||
content_translation_source: source_langcode
|
||||
# unmapped d6 fields.
|
||||
# translate
|
||||
# comment
|
||||
destination:
|
||||
plugin: entity_complete:node
|
||||
translations: true
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_user
|
||||
- d7_node_type
|
||||
- language
|
||||
optional:
|
||||
- d7_field_instance
|
||||
- d7_comment_field_instance
|
||||
33
web/core/modules/node/migrations/d7_node_revision.yml
Normal file
33
web/core/modules/node/migrations/d7_node_revision.yml
Normal file
@ -0,0 +1,33 @@
|
||||
id: d7_node_revision
|
||||
label: Node revisions
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Content
|
||||
deriver: Drupal\node\Plugin\migrate\D7NodeDeriver
|
||||
source:
|
||||
plugin: d7_node_revision
|
||||
process:
|
||||
# If you are using this file to build a custom migration consider removing
|
||||
# the nid and vid fields to allow incremental migrations.
|
||||
nid: nid
|
||||
vid: vid
|
||||
langcode:
|
||||
plugin: default_value
|
||||
source: language
|
||||
default_value: "und"
|
||||
title: title
|
||||
uid: node_uid
|
||||
status: status
|
||||
created: created
|
||||
changed: changed
|
||||
promote: promote
|
||||
sticky: sticky
|
||||
revision_uid: revision_uid
|
||||
revision_log: log
|
||||
revision_timestamp: timestamp
|
||||
destination:
|
||||
plugin: entity_revision:node
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_node
|
||||
15
web/core/modules/node/migrations/d7_node_settings.yml
Normal file
15
web/core/modules/node/migrations/d7_node_settings.yml
Normal file
@ -0,0 +1,15 @@
|
||||
id: d7_node_settings
|
||||
label: Node configuration
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- node_admin_theme
|
||||
source_module: node
|
||||
process:
|
||||
use_admin_theme: node_admin_theme
|
||||
destination:
|
||||
plugin: config
|
||||
config_name: node.settings
|
||||
29
web/core/modules/node/migrations/d7_node_title_label.yml
Normal file
29
web/core/modules/node/migrations/d7_node_title_label.yml
Normal file
@ -0,0 +1,29 @@
|
||||
id: d7_node_title_label
|
||||
label: Node title label
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d7_node_type
|
||||
constants:
|
||||
entity_type: node
|
||||
field_name: title
|
||||
process:
|
||||
label:
|
||||
-
|
||||
plugin: static_map
|
||||
source: title_label
|
||||
bypass: true
|
||||
map:
|
||||
Title: 0
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
entity_type: 'constants/entity_type'
|
||||
bundle: type
|
||||
field_name: 'constants/field_name'
|
||||
destination:
|
||||
plugin: entity:base_field_override
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_node_type
|
||||
40
web/core/modules/node/migrations/d7_node_type.yml
Normal file
40
web/core/modules/node/migrations/d7_node_type.yml
Normal file
@ -0,0 +1,40 @@
|
||||
id: d7_node_type
|
||||
label: Node type configuration
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d7_node_type
|
||||
constants:
|
||||
preview: 1 # DRUPAL_OPTIONAL
|
||||
process:
|
||||
type: type
|
||||
name: name
|
||||
description: description
|
||||
help: help
|
||||
title_label: title_label
|
||||
preview_mode: 'constants/preview'
|
||||
display_submitted: display_submitted
|
||||
new_revision: 'options/revision'
|
||||
create_body: create_body
|
||||
create_body_label: body_label
|
||||
'third_party_settings/menu_ui/available_menus':
|
||||
plugin: static_map
|
||||
bypass: true
|
||||
source: available_menus
|
||||
map:
|
||||
main-menu: main
|
||||
management: admin
|
||||
navigation: tools
|
||||
user-menu: account
|
||||
'third_party_settings/menu_ui/parent':
|
||||
plugin: static_map
|
||||
bypass: true
|
||||
source: parent
|
||||
map:
|
||||
'main-menu:0:': 'main:'
|
||||
'management:0:': 'admin:'
|
||||
'navigation:0:': 'tools:'
|
||||
'user-menu:0:': 'account:'
|
||||
destination:
|
||||
plugin: entity:node_type
|
||||
@ -0,0 +1,10 @@
|
||||
finished:
|
||||
6:
|
||||
# Blog requires node.
|
||||
blog: node
|
||||
content: node
|
||||
node: node
|
||||
7:
|
||||
# Blog requires node.
|
||||
blog: node
|
||||
node: node
|
||||
177
web/core/modules/node/node.admin.inc
Normal file
177
web/core/modules/node/node.admin.inc
Normal file
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
use Drupal\Core\Batch\BatchBuilder;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Updates all nodes in the passed-in array with the passed-in field values.
|
||||
*
|
||||
* IMPORTANT NOTE: This function is intended to work when called from a form
|
||||
* submission handler. Calling it outside of the form submission process may not
|
||||
* work correctly.
|
||||
*
|
||||
* @param array $nodes
|
||||
* Array of node nids or nodes to update.
|
||||
* @param array $updates
|
||||
* Array of key/value pairs with node field names and the value to update that
|
||||
* field to.
|
||||
* @param string $langcode
|
||||
* (optional) The language updates should be applied to. If none is specified
|
||||
* all available languages are processed.
|
||||
* @param bool $load
|
||||
* (optional) TRUE if $nodes contains an array of node IDs to be loaded, FALSE
|
||||
* if it contains fully loaded nodes. Defaults to FALSE.
|
||||
* @param bool $revisions
|
||||
* (optional) TRUE if $nodes contains an array of revision IDs instead of
|
||||
* node IDs. Defaults to FALSE; will be ignored if $load is FALSE.
|
||||
*/
|
||||
function node_mass_update(array $nodes, array $updates, $langcode = NULL, $load = FALSE, $revisions = FALSE): void {
|
||||
// We use batch processing to prevent timeout when updating a large number
|
||||
// of nodes.
|
||||
if (count($nodes) > 10) {
|
||||
$batch_builder = (new BatchBuilder())
|
||||
// The operations do not live in the .module file, so we need to
|
||||
// tell the batch engine which file to load before calling them.
|
||||
->setFile(\Drupal::service('extension.list.module')->getPath('node') . '/node.admin.inc')
|
||||
->addOperation('_node_mass_update_batch_process', [$nodes, $updates, $langcode, $load, $revisions])
|
||||
->setFinishCallback('_node_mass_update_batch_finished')
|
||||
->setTitle(t('Processing'))
|
||||
->setErrorMessage(t('The update has encountered an error.'))
|
||||
// We use a single multi-pass operation, so the default
|
||||
// 'Remaining x of y operations' message will be confusing here.
|
||||
->setProgressMessage('');
|
||||
batch_set($batch_builder->toArray());
|
||||
}
|
||||
else {
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('node');
|
||||
if ($load && !$revisions) {
|
||||
$nodes = $storage->loadMultiple($nodes);
|
||||
}
|
||||
foreach ($nodes as $node) {
|
||||
if ($load && $revisions) {
|
||||
$node = $storage->loadRevision($node);
|
||||
}
|
||||
_node_mass_update_helper($node, $updates, $langcode);
|
||||
}
|
||||
\Drupal::messenger()->addStatus(t('The update has been performed.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates individual nodes when fewer than 10 are queued.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* A node to update.
|
||||
* @param array $updates
|
||||
* Associative array of updates.
|
||||
* @param string $langcode
|
||||
* (optional) The language updates should be applied to. If none is specified
|
||||
* all available languages are processed.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface
|
||||
* An updated node object.
|
||||
*
|
||||
* @see node_mass_update()
|
||||
*/
|
||||
function _node_mass_update_helper(NodeInterface $node, array $updates, $langcode = NULL) {
|
||||
$langcodes = isset($langcode) ? [$langcode] : array_keys($node->getTranslationLanguages());
|
||||
// For efficiency manually save the original node before applying any changes.
|
||||
$node->setOriginal(clone $node);
|
||||
foreach ($langcodes as $langcode) {
|
||||
foreach ($updates as $name => $value) {
|
||||
$node->getTranslation($langcode)->$name = $value;
|
||||
}
|
||||
}
|
||||
$node->save();
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements callback_batch_operation().
|
||||
*
|
||||
* Executes a batch operation for node_mass_update().
|
||||
*
|
||||
* @param array $nodes
|
||||
* An array of node IDs.
|
||||
* @param array $updates
|
||||
* Associative array of updates.
|
||||
* @param string $langcode
|
||||
* The language updates should be applied to. If none is specified all
|
||||
* available languages are processed.
|
||||
* @param bool $load
|
||||
* TRUE if $nodes contains an array of node IDs to be loaded, FALSE if it
|
||||
* contains fully loaded nodes.
|
||||
* @param bool $revisions
|
||||
* (optional) TRUE if $nodes contains an array of revision IDs instead of
|
||||
* node IDs. Defaults to FALSE; will be ignored if $load is FALSE.
|
||||
* @param array|\ArrayAccess $context
|
||||
* An array of contextual key/values.
|
||||
*/
|
||||
function _node_mass_update_batch_process(array $nodes, array $updates, $langcode, $load, $revisions, &$context): void {
|
||||
if (!isset($context['sandbox']['progress'])) {
|
||||
$context['sandbox']['progress'] = 0;
|
||||
$context['sandbox']['max'] = count($nodes);
|
||||
$context['sandbox']['nodes'] = $nodes;
|
||||
}
|
||||
|
||||
// Process nodes by groups of 5.
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('node');
|
||||
$count = min(5, count($context['sandbox']['nodes']));
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
// For each nid, load the node, reset the values, and save it.
|
||||
$node = array_shift($context['sandbox']['nodes']);
|
||||
if ($load) {
|
||||
$node = $revisions ?
|
||||
$storage->loadRevision($node) : $storage->load($node);
|
||||
}
|
||||
$node = _node_mass_update_helper($node, $updates, $langcode);
|
||||
|
||||
// Store result for post-processing in the finished callback.
|
||||
$context['results'][] = Link::fromTextAndUrl($node->label(), $node->toUrl())->toString();
|
||||
|
||||
// Update our progress information.
|
||||
$context['sandbox']['progress']++;
|
||||
}
|
||||
|
||||
// Inform the batch engine that we are not finished,
|
||||
// and provide an estimation of the completion level we reached.
|
||||
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
|
||||
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements callback_batch_finished().
|
||||
*
|
||||
* Reports the 'finished' status of batch operation for node_mass_update().
|
||||
*
|
||||
* @param bool $success
|
||||
* A boolean indicating whether the batch mass update operation successfully
|
||||
* concluded.
|
||||
* @param string[] $results
|
||||
* An array of rendered links to nodes updated via the batch mode process.
|
||||
* @param array $operations
|
||||
* An array of function calls (not used in this function).
|
||||
*
|
||||
* @see _node_mass_update_batch_process()
|
||||
*/
|
||||
function _node_mass_update_batch_finished($success, $results, $operations): void {
|
||||
if ($success) {
|
||||
\Drupal::messenger()->addStatus(t('The update has been performed.'));
|
||||
}
|
||||
else {
|
||||
\Drupal::messenger()->addError(t('An error occurred and processing did not complete.'));
|
||||
$message = \Drupal::translation()->formatPlural(count($results), '1 item successfully processed:', '@count items successfully processed:');
|
||||
$item_list = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $results,
|
||||
];
|
||||
$message .= \Drupal::service('renderer')->render($item_list);
|
||||
\Drupal::messenger()->addStatus($message);
|
||||
}
|
||||
}
|
||||
433
web/core/modules/node/node.api.php
Normal file
433
web/core/modules/node/node.api.php
Normal file
@ -0,0 +1,433 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks specific to the Node module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Inform the node access system what permissions the user has.
|
||||
*
|
||||
* This hook is for implementation by node access modules. In this hook,
|
||||
* the module grants a user different "grant IDs" within one or more
|
||||
* "realms". In hook_node_access_records(), the realms and grant IDs are
|
||||
* associated with permission to view, edit, and delete individual nodes.
|
||||
*
|
||||
* Grant IDs can be arbitrarily defined by a node access module using a list of
|
||||
* integer IDs associated with users.
|
||||
*
|
||||
* A node access module may implement as many realms as necessary to properly
|
||||
* define the access privileges for the nodes. Note that the system makes no
|
||||
* distinction between published and unpublished nodes. It is the module's
|
||||
* responsibility to provide appropriate realms to limit access to unpublished
|
||||
* content.
|
||||
*
|
||||
* Node access records are stored in the {node_access} table and define which
|
||||
* grants are required to access a node. There is a special case for the view
|
||||
* operation -- a record with node ID 0 corresponds to a "view all" grant for
|
||||
* the realm and grant ID of that record. If there are no node access modules
|
||||
* enabled, the core node module adds a node ID 0 record for realm 'all'. Node
|
||||
* access modules can also grant "view all" permission on their custom realms;
|
||||
* for example, a module could create a record in {node_access} with:
|
||||
* @code
|
||||
* $record = [
|
||||
* 'nid' => 0,
|
||||
* 'gid' => 888,
|
||||
* 'realm' => 'example_realm',
|
||||
* 'grant_view' => 1,
|
||||
* 'grant_update' => 0,
|
||||
* 'grant_delete' => 0,
|
||||
* ];
|
||||
* \Drupal::database()->insert('node_access')->fields($record)->execute();
|
||||
* @endcode
|
||||
* And then in its hook_node_grants() implementation, it would need to return:
|
||||
* @code
|
||||
* if ($op == 'view') {
|
||||
* $grants['example_realm'] = [888];
|
||||
* }
|
||||
* @endcode
|
||||
* If you decide to do this, be aware that the node_access_rebuild() function
|
||||
* will erase any node ID 0 entry when it is called, so you will need to make
|
||||
* sure to restore your {node_access} record after node_access_rebuild() is
|
||||
* called.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account object whose grants are requested.
|
||||
* @param string $operation
|
||||
* The node operation to be performed, such as 'view', 'update', or 'delete'.
|
||||
*
|
||||
* @return array
|
||||
* An array whose keys are "realms" of grants, and whose values are arrays of
|
||||
* the grant IDs within this realm that this user is being granted.
|
||||
*
|
||||
* @see node_access_view_all_nodes()
|
||||
* @see node_access_rebuild()
|
||||
* @ingroup node_access
|
||||
*/
|
||||
function hook_node_grants(AccountInterface $account, $operation): array {
|
||||
$grants = [];
|
||||
if ($account->hasPermission('access private content')) {
|
||||
$grants['example'] = [1];
|
||||
}
|
||||
if ($account->id()) {
|
||||
$grants['example_author'] = [$account->id()];
|
||||
}
|
||||
return $grants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set permissions for a node to be written to the database.
|
||||
*
|
||||
* When a node is saved, a module implementing hook_node_access_records() will
|
||||
* be asked if it is interested in the access permissions for a node. If it is
|
||||
* interested, it must respond with an array of permissions arrays for that
|
||||
* node.
|
||||
*
|
||||
* Node access grants apply regardless of the published or unpublished status
|
||||
* of the node. Implementations must make sure not to grant access to
|
||||
* unpublished nodes if they don't want to change the standard access control
|
||||
* behavior. Your module may need to create a separate access realm to handle
|
||||
* access to unpublished nodes.
|
||||
*
|
||||
* Note that the grant values in the return value from your hook must be
|
||||
* integers and not boolean TRUE and FALSE.
|
||||
*
|
||||
* Each permissions item in the array is an array with the following elements:
|
||||
* - 'realm': The name of a realm that the module has defined in
|
||||
* hook_node_grants().
|
||||
* - 'gid': A 'grant ID' from hook_node_grants().
|
||||
* - 'grant_view': If set to 1 a user that has been identified as a member
|
||||
* of this gid within this realm can view this node. This should usually be
|
||||
* set to $node->isPublished(). Failure to do so may expose unpublished
|
||||
* content to some users.
|
||||
* - 'grant_update': If set to 1 a user that has been identified as a member
|
||||
* of this gid within this realm can edit this node.
|
||||
* - 'grant_delete': If set to 1 a user that has been identified as a member
|
||||
* of this gid within this realm can delete this node.
|
||||
* - langcode: (optional) The language code of a specific translation of the
|
||||
* node, if any. Modules may add this key to grant different access to
|
||||
* different translations of a node, such that (e.g.) a particular group is
|
||||
* granted access to edit the Catalan version of the node, but not the
|
||||
* Hungarian version. If no value is provided, the langcode is set
|
||||
* automatically from the $node parameter and the node's original language (if
|
||||
* specified) is used as a fallback. Only specify multiple grant records with
|
||||
* different languages for a node if the site has those languages configured.
|
||||
*
|
||||
* A "deny all" grant may be used to deny all access to a particular node or
|
||||
* node translation:
|
||||
* @code
|
||||
* $grants[] = [
|
||||
* 'realm' => 'all',
|
||||
* 'gid' => 0,
|
||||
* 'grant_view' => 0,
|
||||
* 'grant_update' => 0,
|
||||
* 'grant_delete' => 0,
|
||||
* 'langcode' => 'ca',
|
||||
* ];
|
||||
* @endcode
|
||||
* Note that another module node access module could override this by granting
|
||||
* access to one or more nodes, since grants are additive. To enforce that
|
||||
* access is denied in a particular case, use hook_node_access_records_alter().
|
||||
* Also note that a deny all is not written to the database; denies are
|
||||
* implicit.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node that has just been saved.
|
||||
*
|
||||
* @return array
|
||||
* An array of grants as defined above.
|
||||
*
|
||||
* @see hook_node_access_records_alter()
|
||||
* @ingroup node_access
|
||||
*/
|
||||
function hook_node_access_records(NodeInterface $node): array {
|
||||
$grants = [];
|
||||
// We only care about the node if it has been marked private. If not, it is
|
||||
// treated just like any other node and we completely ignore it.
|
||||
if ($node->private->value) {
|
||||
// Only published Catalan translations of private nodes should be viewable
|
||||
// to all users. If we fail to check $node->isPublished(), all users would
|
||||
// be able to view an unpublished node.
|
||||
if ($node->isPublished()) {
|
||||
$grants[] = [
|
||||
'realm' => 'example',
|
||||
'gid' => 1,
|
||||
'grant_view' => 1,
|
||||
'grant_update' => 0,
|
||||
'grant_delete' => 0,
|
||||
'langcode' => 'ca',
|
||||
];
|
||||
}
|
||||
// For the example_author array, the GID is equivalent to a UID, which
|
||||
// means there are many groups of just 1 user.
|
||||
// Note that an author can always view nodes they own, even if they have
|
||||
// status unpublished.
|
||||
if ($node->getOwnerId()) {
|
||||
$grants[] = [
|
||||
'realm' => 'example_author',
|
||||
'gid' => $node->getOwnerId(),
|
||||
'grant_view' => 1,
|
||||
'grant_update' => 1,
|
||||
'grant_delete' => 1,
|
||||
'langcode' => 'ca',
|
||||
];
|
||||
}
|
||||
}
|
||||
return $grants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter permissions for a node before it is written to the database.
|
||||
*
|
||||
* Node access modules establish rules for user access to content. Node access
|
||||
* records are stored in the {node_access} table and define which permissions
|
||||
* are required to access a node. This hook is invoked after node access modules
|
||||
* returned their requirements via hook_node_access_records(); doing so allows
|
||||
* modules to modify the $grants array by reference before it is stored, so
|
||||
* custom or advanced business logic can be applied.
|
||||
*
|
||||
* Upon viewing, editing or deleting a node, hook_node_grants() builds a
|
||||
* permissions array that is compared against the stored access records. The
|
||||
* user must have one or more matching permissions in order to complete the
|
||||
* requested operation.
|
||||
*
|
||||
* A module may deny all access to a node by setting $grants to an empty array.
|
||||
*
|
||||
* The preferred use of this hook is in a module that bridges multiple node
|
||||
* access modules with a configurable behavior, as shown in the example with the
|
||||
* 'is_preview' field.
|
||||
*
|
||||
* @param array $grants
|
||||
* The $grants array returned by hook_node_access_records().
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node for which the grants were acquired.
|
||||
*
|
||||
* @see hook_node_access_records()
|
||||
* @see hook_node_grants()
|
||||
* @see hook_node_grants_alter()
|
||||
* @ingroup node_access
|
||||
*/
|
||||
function hook_node_access_records_alter(&$grants, NodeInterface $node) {
|
||||
// Our module allows editors to mark specific articles with the 'is_preview'
|
||||
// field. If the node being saved has a TRUE value for that field, then only
|
||||
// our grants are retained, and other grants are removed. Doing so ensures
|
||||
// that our rules are enforced no matter what priority other grants are given.
|
||||
if ($node->is_preview) {
|
||||
// Our module grants are set in $grants['example'].
|
||||
$temp = $grants['example'];
|
||||
// Now remove all module grants but our own.
|
||||
$grants = ['example' => $temp];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter user access rules when trying to view, edit or delete a node.
|
||||
*
|
||||
* Node access modules establish rules for user access to content.
|
||||
* hook_node_grants() defines permissions for a user to view, edit or delete
|
||||
* nodes by building a $grants array that indicates the permissions assigned to
|
||||
* the user by each node access module. This hook is called to allow modules to
|
||||
* modify the $grants array by reference, so the interaction of multiple node
|
||||
* access modules can be altered or advanced business logic can be applied.
|
||||
*
|
||||
* The resulting grants are then checked against the records stored in the
|
||||
* {node_access} table to determine if the operation may be completed.
|
||||
*
|
||||
* A module may deny all access to a user by setting $grants to an empty array.
|
||||
*
|
||||
* Developers may use this hook to either add additional grants to a user or to
|
||||
* remove existing grants. These rules are typically based on either the
|
||||
* permissions assigned to a user role, or specific attributes of a user
|
||||
* account.
|
||||
*
|
||||
* @param array $grants
|
||||
* The $grants array returned by hook_node_grants().
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account requesting access to content.
|
||||
* @param string $operation
|
||||
* The operation being performed, 'view', 'update' or 'delete'.
|
||||
*
|
||||
* @see hook_node_grants()
|
||||
* @see hook_node_access_records()
|
||||
* @see hook_node_access_records_alter()
|
||||
* @ingroup node_access
|
||||
*/
|
||||
function hook_node_grants_alter(&$grants, AccountInterface $account, $operation) {
|
||||
// Our sample module never allows certain roles to edit or delete
|
||||
// content. Since some other node access modules might allow this
|
||||
// permission, we expressly remove it by returning an empty $grants
|
||||
// array for roles specified in our variable setting.
|
||||
|
||||
// Get our list of banned roles.
|
||||
$restricted = \Drupal::config('example.settings')->get('restricted_roles');
|
||||
|
||||
if ($operation != 'view' && !empty($restricted)) {
|
||||
// Now check the roles for this account against the restrictions.
|
||||
foreach ($account->getRoles() as $rid) {
|
||||
if (in_array($rid, $restricted)) {
|
||||
$grants = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on a node being displayed as a search result.
|
||||
*
|
||||
* This hook is invoked from the node search plugin during search execution,
|
||||
* after loading and rendering the node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node being displayed in a search result.
|
||||
*
|
||||
* @return array
|
||||
* Extra information to be displayed with search result. This information
|
||||
* should be presented as an associative array. It will be concatenated with
|
||||
* the post information (last updated, author) in the default search result
|
||||
* theming.
|
||||
*
|
||||
* @see template_preprocess_search_result()
|
||||
* @see search-result.html.twig
|
||||
*
|
||||
* @ingroup entity_crud
|
||||
*/
|
||||
function hook_node_search_result(NodeInterface $node): array {
|
||||
$rating = \Drupal::database()->query('SELECT SUM([points]) FROM {my_rating} WHERE [nid] = :nid', ['nid' => $node->id()])->fetchField();
|
||||
return ['rating' => \Drupal::translation()->formatPlural($rating, '1 point', '@count points')];
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on a node being indexed for searching.
|
||||
*
|
||||
* This hook is invoked during search indexing, after loading, and after the
|
||||
* result of rendering is added as $node->rendered to the node object.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node being indexed.
|
||||
*
|
||||
* @return string|\Stringable
|
||||
* Additional node information to be indexed.
|
||||
*
|
||||
* @ingroup entity_crud
|
||||
*/
|
||||
function hook_node_update_index(NodeInterface $node): string|\Stringable {
|
||||
$text = '';
|
||||
$ratings = \Drupal::database()->query('SELECT [title], [description] FROM {my_ratings} WHERE [nid] = :nid', [':nid' => $node->id()]);
|
||||
foreach ($ratings as $rating) {
|
||||
$text .= '<h2>' . Html::escape($rating->title) . '</h2>' . Xss::filter($rating->description);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide additional methods of scoring for core search results for nodes.
|
||||
*
|
||||
* A node's search score is used to rank it among other nodes matched by the
|
||||
* search, with the highest-ranked nodes appearing first in the search listing.
|
||||
*
|
||||
* For example, a module allowing users to vote on content could expose an
|
||||
* option to allow search results' rankings to be influenced by the average
|
||||
* voting score of a node.
|
||||
*
|
||||
* All scoring mechanisms are provided as options to site administrators, and
|
||||
* may be tweaked based on individual sites or disabled altogether if they do
|
||||
* not make sense. Individual scoring mechanisms, if enabled, are assigned a
|
||||
* weight from 1 to 10. The weight represents the factor of magnification of
|
||||
* the ranking mechanism, with higher-weighted ranking mechanisms having more
|
||||
* influence. In order for the weight system to work, each scoring mechanism
|
||||
* must return a value between 0 and 1 for every node. That value is then
|
||||
* multiplied by the administrator-assigned weight for the ranking mechanism,
|
||||
* and then the weighted scores from all ranking mechanisms are added, which
|
||||
* brings about the same result as a weighted average.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of ranking data. The keys should be strings,
|
||||
* corresponding to the internal name of the ranking mechanism, such as
|
||||
* 'recent', or 'comments'. The values should be arrays themselves, with the
|
||||
* following keys available:
|
||||
* - title: (required) The human readable name of the ranking mechanism.
|
||||
* - join: (optional) An array with information to join any additional
|
||||
* necessary table. This is not necessary if the table required is already
|
||||
* joined to by the base query, such as for the {node} table. Other tables
|
||||
* should use the full table name as an alias to avoid naming collisions.
|
||||
* - score: (required) The part of a query string to calculate the score for
|
||||
* the ranking mechanism based on values in the database. This does not need
|
||||
* to be wrapped in parentheses, as it will be done automatically; it also
|
||||
* does not need to take the weighted system into account, as it will be
|
||||
* done automatically. It does, however, need to calculate a decimal between
|
||||
* 0 and 1; be careful not to cast the entire score to an integer by
|
||||
* inadvertently introducing a variable argument.
|
||||
* - arguments: (optional) If any arguments are required for the score, they
|
||||
* can be specified in an array here.
|
||||
*
|
||||
* @ingroup entity_crud
|
||||
*/
|
||||
function hook_ranking(): array {
|
||||
$data = [];
|
||||
// If voting is disabled, we can avoid returning the array, no hard feelings.
|
||||
if (\Drupal::config('vote.settings')->get('node_enabled')) {
|
||||
$data += [
|
||||
'vote_average' => [
|
||||
'title' => t('Average vote'),
|
||||
// Note that we use i.sid, the search index's search item id, rather
|
||||
// than n.nid.
|
||||
'join' => [
|
||||
'type' => 'LEFT',
|
||||
'table' => 'vote_node_data',
|
||||
'alias' => 'vote_node_data',
|
||||
'on' => 'vote_node_data.nid = i.sid',
|
||||
],
|
||||
// The highest possible score should be 1, and the lowest possible
|
||||
// score, always 0, should be 0.
|
||||
'score' => 'vote_node_data.average / CAST(%f AS DECIMAL)',
|
||||
// Pass in the highest possible voting score as a decimal argument.
|
||||
'arguments' => [\Drupal::config('vote.settings')->get('score_max')],
|
||||
],
|
||||
];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the links of a node.
|
||||
*
|
||||
* @param array &$links
|
||||
* A renderable array representing the node links.
|
||||
* @param \Drupal\node\NodeInterface $entity
|
||||
* The node being rendered.
|
||||
* @param array &$context
|
||||
* Various aspects of the context in which the node links are going to be
|
||||
* displayed, with the following keys:
|
||||
* - 'view_mode': the view mode in which the node is being viewed.
|
||||
* - 'langcode': the language in which the node is being viewed.
|
||||
*
|
||||
* @see \Drupal\node\NodeViewBuilder::renderLinks()
|
||||
* @see \Drupal\node\NodeViewBuilder::buildLinks()
|
||||
* @see entity_crud
|
||||
*/
|
||||
function hook_node_links_alter(array &$links, NodeInterface $entity, array &$context) {
|
||||
$links['my_module'] = [
|
||||
'#theme' => 'links__node__my_module',
|
||||
'#attributes' => ['class' => ['links', 'inline']],
|
||||
'#links' => [
|
||||
'node-report' => [
|
||||
'title' => t('Report'),
|
||||
'url' => Url::fromRoute('node_test.report', ['node' => $entity->id()], ['query' => ['token' => \Drupal::getContainer()->get('csrf_token')->get("node/{$entity->id()}/report")]]),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
||||
8
web/core/modules/node/node.info.yml
Normal file
8
web/core/modules/node/node.info.yml
Normal file
@ -0,0 +1,8 @@
|
||||
name: Node
|
||||
type: module
|
||||
description: 'Manages the creation, configuration, and display of the main site content.'
|
||||
package: Core
|
||||
version: VERSION
|
||||
configure: entity.node_type.collection
|
||||
dependencies:
|
||||
- drupal:text
|
||||
133
web/core/modules/node/node.install
Normal file
133
web/core/modules/node/node.install
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the node module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function node_schema(): array {
|
||||
$schema['node_access'] = [
|
||||
'description' => 'Identifies which realm/grant pairs a user must possess in order to view, update, or delete specific nodes.',
|
||||
'fields' => [
|
||||
'nid' => [
|
||||
'description' => 'The {node}.nid this record affects.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
],
|
||||
'langcode' => [
|
||||
'description' => 'The {language}.langcode of this node.',
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 12,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
],
|
||||
'fallback' => [
|
||||
'description' => 'Boolean indicating whether this record should be used as a fallback if a language condition is not provided.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 1,
|
||||
'size' => 'tiny',
|
||||
],
|
||||
'gid' => [
|
||||
'description' => "The grant ID a user must possess in the specified realm to gain this row's privileges on the node.",
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
],
|
||||
'realm' => [
|
||||
'description' => 'The realm in which the user must possess the grant ID. Modules can define one or more realms by implementing hook_node_grants().',
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
],
|
||||
'grant_view' => [
|
||||
'description' => 'Boolean indicating whether a user with the realm/grant pair can view this node.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
],
|
||||
'grant_update' => [
|
||||
'description' => 'Boolean indicating whether a user with the realm/grant pair can edit this node.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
],
|
||||
'grant_delete' => [
|
||||
'description' => 'Boolean indicating whether a user with the realm/grant pair can delete this node.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
],
|
||||
],
|
||||
'primary key' => ['nid', 'gid', 'realm', 'langcode'],
|
||||
'foreign keys' => [
|
||||
'affected_node' => [
|
||||
'table' => 'node',
|
||||
'columns' => ['nid' => 'nid'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function node_install(): void {
|
||||
// Enable default permissions for system roles.
|
||||
// IMPORTANT: Modules SHOULD NOT automatically grant any user role access
|
||||
// permissions in hook_install().
|
||||
// However, the 'access content' permission is a very special case, since
|
||||
// there is hardly a point in installing the Node module without granting
|
||||
// these permissions. Doing so also allows tests to continue to operate as
|
||||
// expected without first having to manually grant these default permissions.
|
||||
if (\Drupal::moduleHandler()->moduleExists('user')) {
|
||||
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, ['access content']);
|
||||
user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, ['access content']);
|
||||
}
|
||||
|
||||
// Populate the node access table.
|
||||
Database::getConnection()->insert('node_access')
|
||||
->fields([
|
||||
'nid' => 0,
|
||||
'gid' => 0,
|
||||
'realm' => 'all',
|
||||
'grant_view' => 1,
|
||||
'grant_update' => 0,
|
||||
'grant_delete' => 0,
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function node_uninstall(): void {
|
||||
// Delete remaining general module variables.
|
||||
\Drupal::state()->delete('node.node_access_needs_rebuild');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_update_last_removed().
|
||||
*/
|
||||
function node_update_last_removed(): int {
|
||||
return 8700;
|
||||
}
|
||||
66
web/core/modules/node/node.libraries.yml
Normal file
66
web/core/modules/node/node.libraries.yml
Normal file
@ -0,0 +1,66 @@
|
||||
drupal.node:
|
||||
version: VERSION
|
||||
css:
|
||||
layout:
|
||||
css/node.module.css: {}
|
||||
js:
|
||||
js/node.js: {}
|
||||
dependencies:
|
||||
- core/drupal.entity-form
|
||||
- core/drupalSettings
|
||||
moved_files:
|
||||
node/node.js:
|
||||
deprecation_version: drupal:11.1.0
|
||||
removed_version: drupal:12.0.0
|
||||
deprecation_link: https://www.drupal.org/node/3471539
|
||||
js:
|
||||
node.js: 'js/node.js'
|
||||
|
||||
drupal.node.preview:
|
||||
version: VERSION
|
||||
css:
|
||||
theme:
|
||||
css/node.preview.css: {}
|
||||
js:
|
||||
js/node.preview.js: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/once
|
||||
- core/drupal
|
||||
- core/drupal.dialog
|
||||
- core/drupal.form
|
||||
moved_files:
|
||||
node/node.preview.js:
|
||||
deprecation_version: drupal:11.1.0
|
||||
removed_version: drupal:12.0.0
|
||||
deprecation_link: https://www.drupal.org/node/3471539
|
||||
js:
|
||||
node.preview.js: 'js/node.preview.js'
|
||||
|
||||
drupal.content_types:
|
||||
version: VERSION
|
||||
js:
|
||||
js/content_types.js: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/drupal
|
||||
- core/drupal.form
|
||||
moved_files:
|
||||
node/content_types.js:
|
||||
deprecation_version: drupal:11.1.0
|
||||
removed_version: drupal:12.0.0
|
||||
deprecation_link: https://www.drupal.org/node/3471539
|
||||
js:
|
||||
content_types.js: 'js/content_types.js'
|
||||
|
||||
form:
|
||||
version: VERSION
|
||||
css:
|
||||
layout:
|
||||
css/node.module.css: {}
|
||||
|
||||
drupal.node.admin:
|
||||
version: VERSION
|
||||
css:
|
||||
theme:
|
||||
css/node.admin.css: {}
|
||||
10
web/core/modules/node/node.links.action.yml
Normal file
10
web/core/modules/node/node.links.action.yml
Normal file
@ -0,0 +1,10 @@
|
||||
node.type_add:
|
||||
route_name: node.type_add
|
||||
title: 'Add content type'
|
||||
appears_on:
|
||||
- entity.node_type.collection
|
||||
node.add_page:
|
||||
route_name: node.add_page
|
||||
title: 'Add content'
|
||||
appears_on:
|
||||
- system.admin_content
|
||||
10
web/core/modules/node/node.links.contextual.yml
Normal file
10
web/core/modules/node/node.links.contextual.yml
Normal file
@ -0,0 +1,10 @@
|
||||
entity.node.edit_form:
|
||||
route_name: entity.node.edit_form
|
||||
group: node
|
||||
title: Edit
|
||||
|
||||
entity.node.delete_form:
|
||||
route_name: entity.node.delete_form
|
||||
group: node
|
||||
title: Delete
|
||||
weight: 10
|
||||
8
web/core/modules/node/node.links.menu.yml
Normal file
8
web/core/modules/node/node.links.menu.yml
Normal file
@ -0,0 +1,8 @@
|
||||
entity.node_type.collection:
|
||||
title: 'Content types'
|
||||
parent: system.admin_structure
|
||||
description: 'Create and manage fields, forms, and display settings for your content.'
|
||||
route_name: entity.node_type.collection
|
||||
node.add_page:
|
||||
title: 'Add content'
|
||||
route_name: node.add_page
|
||||
26
web/core/modules/node/node.links.task.yml
Normal file
26
web/core/modules/node/node.links.task.yml
Normal file
@ -0,0 +1,26 @@
|
||||
entity.node.canonical:
|
||||
route_name: entity.node.canonical
|
||||
base_route: entity.node.canonical
|
||||
title: 'View'
|
||||
entity.node.edit_form:
|
||||
route_name: entity.node.edit_form
|
||||
base_route: entity.node.canonical
|
||||
title: Edit
|
||||
entity.node.delete_form:
|
||||
route_name: entity.node.delete_form
|
||||
base_route: entity.node.canonical
|
||||
title: Delete
|
||||
weight: 10
|
||||
entity.node.version_history:
|
||||
route_name: entity.node.version_history
|
||||
base_route: entity.node.canonical
|
||||
title: 'Revisions'
|
||||
weight: 20
|
||||
entity.node_type.edit_form:
|
||||
title: 'Edit'
|
||||
route_name: entity.node_type.edit_form
|
||||
base_route: entity.node_type.edit_form
|
||||
entity.node_type.collection:
|
||||
title: List
|
||||
route_name: entity.node_type.collection
|
||||
base_route: entity.node_type.collection
|
||||
681
web/core/modules/node/node.module
Normal file
681
web/core/modules/node/node.module
Normal file
@ -0,0 +1,681 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Environment;
|
||||
use Drupal\Core\Batch\BatchBuilder;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Database\StatementInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\node\NodeTypeInterface;
|
||||
|
||||
/**
|
||||
* Gathers a listing of links to nodes.
|
||||
*
|
||||
* @param \Drupal\Core\Database\StatementInterface $result
|
||||
* A database result object from a query to fetch node entities. If your
|
||||
* query joins the {comment_entity_statistics} table so that the comment_count
|
||||
* field is available, a title attribute will be added to show the number of
|
||||
* comments.
|
||||
* @param string|null $title
|
||||
* (optional) A heading for the resulting list. NULL results in no heading.
|
||||
* Defaults to NULL.
|
||||
*
|
||||
* @return array|false
|
||||
* A renderable array containing a list of linked node titles fetched from
|
||||
* $result, or FALSE if there are no rows in $result.
|
||||
*/
|
||||
function node_title_list(StatementInterface $result, $title = NULL) {
|
||||
$items = [];
|
||||
$num_rows = FALSE;
|
||||
$nids = [];
|
||||
foreach ($result as $row) {
|
||||
// Do not use $node->label() or $node->toUrl() here, because we only have
|
||||
// database rows, not actual nodes.
|
||||
$nids[] = $row->nid;
|
||||
$options = !empty($row->comment_count) ? ['attributes' => ['title' => \Drupal::translation()->formatPlural($row->comment_count, '1 comment', '@count comments')]] : [];
|
||||
$items[] = Link::fromTextAndUrl($row->title, Url::fromRoute('entity.node.canonical', ['node' => $row->nid], $options))->toString();
|
||||
$num_rows = TRUE;
|
||||
}
|
||||
|
||||
return $num_rows ? ['#theme' => 'item_list__node', '#items' => $items, '#title' => $title, '#cache' => ['tags' => Cache::mergeTags(['node_list'], Cache::buildTags('node', $nids))]] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the type of marker to be displayed for a given node.
|
||||
*
|
||||
* @param int $nid
|
||||
* Node ID whose history supplies the "last viewed" timestamp.
|
||||
* @param int $timestamp
|
||||
* Time which is compared against node's "last viewed" timestamp.
|
||||
*
|
||||
* @return int
|
||||
* One of the MARK constants.
|
||||
*
|
||||
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement.
|
||||
* @see https://www.drupal.org/node/3514189
|
||||
*/
|
||||
function node_mark($nid, $timestamp) {
|
||||
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3514189', E_USER_DEPRECATED);
|
||||
if (\Drupal::currentUser()->isAnonymous() || !\Drupal::moduleHandler()->moduleExists('history')) {
|
||||
return MARK_READ;
|
||||
}
|
||||
$read_timestamp = history_read($nid);
|
||||
if ($read_timestamp === 0 && $timestamp > HISTORY_READ_LIMIT) {
|
||||
return MARK_NEW;
|
||||
}
|
||||
elseif ($timestamp > $read_timestamp && $timestamp > HISTORY_READ_LIMIT) {
|
||||
return MARK_UPDATED;
|
||||
}
|
||||
return MARK_READ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of available node type names.
|
||||
*
|
||||
* This list can include types that are queued for addition or deletion.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of node type labels, keyed by the node type name.
|
||||
*/
|
||||
function node_type_get_names() {
|
||||
return array_map(function ($bundle_info) {
|
||||
return $bundle_info['label'];
|
||||
}, \Drupal::service('entity_type.bundle.info')->getBundleInfo('node'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node type label for the passed node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* A node entity to return the node type's label for.
|
||||
*
|
||||
* @return string|false
|
||||
* The node type label or FALSE if the node type is not found.
|
||||
*
|
||||
* @todo Add this as generic helper method for config entities representing
|
||||
* entity bundles.
|
||||
*/
|
||||
function node_get_type_label(NodeInterface $node) {
|
||||
$type = NodeType::load($node->bundle());
|
||||
return $type ? $type->label() : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description callback: Returns the node type description.
|
||||
*
|
||||
* @param \Drupal\node\NodeTypeInterface $node_type
|
||||
* The node type object.
|
||||
*
|
||||
* @return string
|
||||
* The node type description.
|
||||
*/
|
||||
function node_type_get_description(NodeTypeInterface $node_type) {
|
||||
return $node_type->getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the default body field to a node type.
|
||||
*
|
||||
* @param \Drupal\node\NodeTypeInterface $type
|
||||
* A node type object.
|
||||
* @param string $label
|
||||
* (optional) The label for the body instance.
|
||||
*
|
||||
* @return \Drupal\field\Entity\FieldConfig
|
||||
* A Body field object.
|
||||
*/
|
||||
function node_add_body_field(NodeTypeInterface $type, $label = 'Body') {
|
||||
// Add or remove the body field, as needed.
|
||||
$field_storage = FieldStorageConfig::loadByName('node', 'body');
|
||||
$field = FieldConfig::loadByName('node', $type->id(), 'body');
|
||||
if (empty($field)) {
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $type->id(),
|
||||
'label' => $label,
|
||||
'settings' => [
|
||||
'display_summary' => TRUE,
|
||||
'allowed_formats' => [],
|
||||
],
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Assign widget settings for the default form mode.
|
||||
$display_repository->getFormDisplay('node', $type->id())
|
||||
->setComponent('body', [
|
||||
'type' => 'text_textarea_with_summary',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Assign display settings for the 'default' and 'teaser' view modes.
|
||||
$display_repository->getViewDisplay('node', $type->id())
|
||||
->setComponent('body', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'text_default',
|
||||
])
|
||||
->save();
|
||||
|
||||
// The teaser view mode is created by the Standard profile and therefore
|
||||
// might not exist.
|
||||
$view_modes = \Drupal::service('entity_display.repository')->getViewModes('node');
|
||||
if (isset($view_modes['teaser'])) {
|
||||
$display_repository->getViewDisplay('node', $type->id(), 'teaser')
|
||||
->setComponent('body', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'text_summary_or_trimmed',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current page is the full page view of the passed-in node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* A node entity.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this is a full page view, otherwise FALSE.
|
||||
*/
|
||||
function node_is_page(NodeInterface $node) {
|
||||
$route_match = \Drupal::routeMatch();
|
||||
if ($route_match->getRouteName() == 'entity.node.canonical') {
|
||||
$page_node = $route_match->getParameter('node');
|
||||
}
|
||||
return (!empty($page_node) ? $page_node->id() == $node->id() : FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for list of available node type templates.
|
||||
*
|
||||
* Default template: node-add-list.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - content: An array of content types.
|
||||
*
|
||||
* @see \Drupal\node\Controller\NodeController::addPage()
|
||||
*/
|
||||
function template_preprocess_node_add_list(&$variables): void {
|
||||
$variables['types'] = [];
|
||||
if (!empty($variables['content'])) {
|
||||
foreach ($variables['content'] as $type) {
|
||||
$variables['types'][$type->id()] = [
|
||||
'type' => $type->id(),
|
||||
'add_link' => Link::fromTextAndUrl($type->label(), Url::fromRoute('node.add', ['node_type' => $type->id()]))->toString(),
|
||||
'description' => [
|
||||
'#markup' => $type->getDescription(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_HOOK() for HTML document templates.
|
||||
*/
|
||||
function node_preprocess_html(&$variables): void {
|
||||
// If on an individual node page or node preview page, add the node type to
|
||||
// the body classes.
|
||||
if (($node = \Drupal::routeMatch()->getParameter('node')) || ($node = \Drupal::routeMatch()->getParameter('node_preview'))) {
|
||||
if ($node instanceof NodeInterface) {
|
||||
$variables['node_type'] = $node->getType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_HOOK() for block templates.
|
||||
*/
|
||||
function node_preprocess_block(&$variables): void {
|
||||
if ($variables['configuration']['provider'] == 'node') {
|
||||
switch ($variables['elements']['#plugin_id']) {
|
||||
case 'node_syndicate_block':
|
||||
$variables['attributes']['role'] = 'complementary';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme_suggestions_HOOK().
|
||||
*/
|
||||
function node_theme_suggestions_node(array $variables): array {
|
||||
$suggestions = [];
|
||||
$node = $variables['elements']['#node'];
|
||||
$sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
|
||||
|
||||
$suggestions[] = 'node__' . $sanitized_view_mode;
|
||||
$suggestions[] = 'node__' . $node->bundle();
|
||||
$suggestions[] = 'node__' . $node->bundle() . '__' . $sanitized_view_mode;
|
||||
$suggestions[] = 'node__' . $node->id();
|
||||
$suggestions[] = 'node__' . $node->id() . '__' . $sanitized_view_mode;
|
||||
|
||||
return $suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for node templates.
|
||||
*
|
||||
* Default template: node.html.twig.
|
||||
*
|
||||
* Most themes use their own copy of node.html.twig. The default is located
|
||||
* inside "/core/modules/node/templates/node.html.twig". Look in there for the
|
||||
* full list of variables.
|
||||
*
|
||||
* By default this function performs special preprocessing of some base fields
|
||||
* so they are available as variables in the template. For example 'title'
|
||||
* appears as 'label'. This preprocessing is skipped if:
|
||||
* - a module makes the field's display configurable via the field UI by means
|
||||
* of BaseFieldDefinition::setDisplayConfigurable()
|
||||
* - AND the additional entity type property
|
||||
* 'enable_base_field_custom_preprocess_skipping' has been set using
|
||||
* hook_entity_type_build().
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - elements: An array of elements to display in view mode.
|
||||
* - node: The node object.
|
||||
* - view_mode: View mode; e.g., 'full', 'teaser', etc.
|
||||
*
|
||||
* @see hook_entity_type_build()
|
||||
* @see \Drupal\Core\Field\BaseFieldDefinition::setDisplayConfigurable()
|
||||
*/
|
||||
function template_preprocess_node(&$variables): void {
|
||||
$variables['view_mode'] = $variables['elements']['#view_mode'];
|
||||
|
||||
// The teaser variable is deprecated.
|
||||
$variables['deprecations']['teaser'] = "'teaser' is deprecated in drupal:11.1.0 and is removed in drupal:12.0.0. Use 'view_mode' instead. See https://www.drupal.org/node/3458185";
|
||||
$variables['teaser'] = $variables['view_mode'] == 'teaser';
|
||||
|
||||
// The 'metadata' variable was originally added to support RDF, which has now
|
||||
// been moved to contrib. It was needed because it is not possible to
|
||||
// extend the markup of the 'submitted' variable generically.
|
||||
$variables['deprecations']['metadata'] = "'metadata' is deprecated in drupal:11.1.0 and is removed in drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3458638";
|
||||
|
||||
$variables['node'] = $variables['elements']['#node'];
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node = $variables['node'];
|
||||
$skip_custom_preprocessing = $node->getEntityType()->get('enable_base_field_custom_preprocess_skipping');
|
||||
|
||||
// Make created, uid and title fields available separately. Skip this custom
|
||||
// preprocessing if the field display is configurable and skipping has been
|
||||
// enabled.
|
||||
// @todo https://www.drupal.org/project/drupal/issues/3015623
|
||||
// Eventually delete this code and matching template lines. Using
|
||||
// $variables['content'] is more flexible and consistent.
|
||||
$submitted_configurable = $node->getFieldDefinition('created')->isDisplayConfigurable('view') || $node->getFieldDefinition('uid')->isDisplayConfigurable('view');
|
||||
if (!$skip_custom_preprocessing || !$submitted_configurable) {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
$variables['date'] = !empty($variables['elements']['created']) ? $renderer->render($variables['elements']['created']) : '';
|
||||
$variables['author_name'] = !empty($variables['elements']['uid']) ? $renderer->render($variables['elements']['uid']) : '';
|
||||
unset($variables['elements']['created'], $variables['elements']['uid']);
|
||||
}
|
||||
|
||||
if (isset($variables['elements']['title']) && (!$skip_custom_preprocessing || !$node->getFieldDefinition('title')->isDisplayConfigurable('view'))) {
|
||||
$variables['label'] = $variables['elements']['title'];
|
||||
unset($variables['elements']['title']);
|
||||
}
|
||||
|
||||
$variables['url'] = !$node->isNew() ? $node->toUrl('canonical')->toString() : NULL;
|
||||
|
||||
// The 'page' variable is set to TRUE in two occasions:
|
||||
// - The view mode is 'full' and we are on the 'node.view' route.
|
||||
// - The node is in preview and view mode is either 'full' or 'default'.
|
||||
$variables['page'] = ($variables['view_mode'] == 'full' && (node_is_page($node)) || (isset($node->in_preview) && in_array($node->preview_view_mode, ['full', 'default'])));
|
||||
|
||||
// Helpful $content variable for templates.
|
||||
$variables += ['content' => []];
|
||||
foreach (Element::children($variables['elements']) as $key) {
|
||||
$variables['content'][$key] = $variables['elements'][$key];
|
||||
}
|
||||
|
||||
if (isset($variables['date'])) {
|
||||
// Display post information on certain node types. This only occurs if
|
||||
// custom preprocessing occurred for both of the created and uid fields.
|
||||
// @todo https://www.drupal.org/project/drupal/issues/3015623
|
||||
// Eventually delete this code and matching template lines. Using a field
|
||||
// formatter is more flexible and consistent.
|
||||
$node_type = $node->type->entity;
|
||||
$variables['author_attributes'] = new Attribute();
|
||||
$variables['display_submitted'] = $node_type->displaySubmitted();
|
||||
if ($variables['display_submitted']) {
|
||||
if (theme_get_setting('features.node_user_picture')) {
|
||||
// To change user picture settings (e.g. image style), edit the
|
||||
// 'compact' view mode on the User entity. Note that the 'compact'
|
||||
// view mode might not be configured, so remember to always check the
|
||||
// theme setting first.
|
||||
if ($node_owner = $node->getOwner()) {
|
||||
$variables['author_picture'] = \Drupal::entityTypeManager()
|
||||
->getViewBuilder('user')
|
||||
->view($node_owner, 'compact');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for system_themes_admin_form().
|
||||
*
|
||||
* @see node_form_system_themes_admin_form_alter()
|
||||
*/
|
||||
function node_form_system_themes_admin_form_submit($form, FormStateInterface $form_state): void {
|
||||
\Drupal::configFactory()->getEditable('node.settings')
|
||||
->set('use_admin_theme', $form_state->getValue('use_admin_theme'))
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup node_access
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetches an array of permission IDs granted to the given user ID.
|
||||
*
|
||||
* The implementation here provides only the universal "all" grant. A node
|
||||
* access module should implement hook_node_grants() to provide a grant list for
|
||||
* the user.
|
||||
*
|
||||
* After the default grants have been loaded, we allow modules to alter the
|
||||
* grants array by reference. This hook allows for complex business logic to be
|
||||
* applied when integrating multiple node access modules.
|
||||
*
|
||||
* @param string $operation
|
||||
* The operation that the user is trying to perform.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account object for the user performing the operation.
|
||||
*
|
||||
* @return array
|
||||
* An associative array in which the keys are realms, and the values are
|
||||
* arrays of grants for those realms.
|
||||
*/
|
||||
function node_access_grants($operation, AccountInterface $account) {
|
||||
// Fetch node access grants from other modules.
|
||||
$grants = \Drupal::moduleHandler()->invokeAll('node_grants', [$account, $operation]);
|
||||
// Allow modules to alter the assigned grants.
|
||||
\Drupal::moduleHandler()->alter('node_grants', $grants, $account, $operation);
|
||||
|
||||
return array_merge(['all' => [0]], $grants);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the user has a global viewing grant for all nodes.
|
||||
*
|
||||
* Checks to see whether any module grants global 'view' access to a user
|
||||
* account; global 'view' access is encoded in the {node_access} table as a
|
||||
* grant with nid=0. If no node access modules are enabled, node.module defines
|
||||
* such a global 'view' access grant.
|
||||
*
|
||||
* This function is called when a node listing query is tagged with
|
||||
* 'node_access'; when this function returns TRUE, no node access joins are
|
||||
* added to the query.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountProxyInterface|null $account
|
||||
* (optional) The user object for the user whose access is being checked. If
|
||||
* omitted, the current user is used. Defaults to NULL.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if 'view' access to all nodes is granted, FALSE otherwise.
|
||||
*
|
||||
* @see hook_node_grants()
|
||||
* @see node_query_node_access_alter()
|
||||
*/
|
||||
function node_access_view_all_nodes($account = NULL) {
|
||||
|
||||
if (!$account) {
|
||||
$account = \Drupal::currentUser();
|
||||
}
|
||||
|
||||
// Statically cache results in an array keyed by $account->id().
|
||||
$access = &drupal_static(__FUNCTION__);
|
||||
if (isset($access[$account->id()])) {
|
||||
return $access[$account->id()];
|
||||
}
|
||||
|
||||
// If no modules implement the node access system, access is always TRUE.
|
||||
if (!\Drupal::moduleHandler()->hasImplementations('node_grants')) {
|
||||
$access[$account->id()] = TRUE;
|
||||
}
|
||||
else {
|
||||
$access[$account->id()] = \Drupal::entityTypeManager()->getAccessControlHandler('node')->checkAllGrants($account);
|
||||
}
|
||||
|
||||
return $access[$account->id()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles or reads the value of a flag for rebuilding the node access grants.
|
||||
*
|
||||
* When the flag is set, a message is displayed to users with 'access
|
||||
* administration pages' permission, pointing to the 'rebuild' confirm form.
|
||||
* This can be used as an alternative to direct node_access_rebuild calls,
|
||||
* allowing administrators to decide when they want to perform the actual
|
||||
* (possibly time consuming) rebuild.
|
||||
*
|
||||
* When unsure if the current user is an administrator, node_access_rebuild()
|
||||
* should be used instead.
|
||||
*
|
||||
* @param bool|null $rebuild
|
||||
* (optional) The boolean value to be written. Defaults to NULL, which returns
|
||||
* the current value.
|
||||
*
|
||||
* @return bool|null
|
||||
* The current value of the flag if no value was provided for $rebuild. If a
|
||||
* value was provided for $rebuild, nothing (NULL) is returned.
|
||||
*
|
||||
* @see node_access_rebuild()
|
||||
*/
|
||||
function node_access_needs_rebuild($rebuild = NULL) {
|
||||
if (!isset($rebuild)) {
|
||||
return \Drupal::state()->get('node.node_access_needs_rebuild', FALSE);
|
||||
}
|
||||
elseif ($rebuild) {
|
||||
\Drupal::state()->set('node.node_access_needs_rebuild', TRUE);
|
||||
}
|
||||
else {
|
||||
\Drupal::state()->delete('node.node_access_needs_rebuild');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the node access database.
|
||||
*
|
||||
* This rebuild is occasionally needed by modules that make system-wide changes
|
||||
* to access levels. When the rebuild is required by an admin-triggered action
|
||||
* (e.g module settings form), calling node_access_needs_rebuild(TRUE) instead
|
||||
* of node_access_rebuild() lets the user perform changes and actually rebuild
|
||||
* only once done.
|
||||
*
|
||||
* Note : As of Drupal 6, node access modules are not required to (and actually
|
||||
* should not) call node_access_rebuild() in hook_install/uninstall anymore.
|
||||
*
|
||||
* @param bool $batch_mode
|
||||
* (optional) Set to TRUE to process in 'batch' mode, spawning processing over
|
||||
* several HTTP requests (thus avoiding the risk of PHP timeout if the site
|
||||
* has a large number of nodes). hook_update_N() and any form submit handler
|
||||
* are safe contexts to use the 'batch mode'. Less decidable cases (such as
|
||||
* calls from hook_user(), hook_taxonomy(), etc.) might consider using the
|
||||
* non-batch mode. Defaults to FALSE. Calling this method multiple times in
|
||||
* the same request with $batch_mode set to TRUE will only result in one batch
|
||||
* set being added.
|
||||
*
|
||||
* @see node_access_needs_rebuild()
|
||||
*/
|
||||
function node_access_rebuild($batch_mode = FALSE): void {
|
||||
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
|
||||
/** @var \Drupal\node\NodeAccessControlHandlerInterface $access_control_handler */
|
||||
$access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node');
|
||||
|
||||
// If node_access_rebuild() fails to complete, and node_access_needs_rebuild
|
||||
// is not set to TRUE, the node_access table is left in an incomplete state.
|
||||
// Force node_access_needs_rebuild to TRUE once existing grants are deleted,
|
||||
// to signal that the node access table still needs to be rebuilt if this
|
||||
// function does not finish.
|
||||
node_access_needs_rebuild(TRUE);
|
||||
$access_control_handler->deleteGrants();
|
||||
|
||||
// Only recalculate if the site is using a node_access module.
|
||||
if (\Drupal::moduleHandler()->hasImplementations('node_grants')) {
|
||||
if ($batch_mode) {
|
||||
if (!BatchBuilder::isSetIdRegistered(__FUNCTION__)) {
|
||||
$batch_builder = (new BatchBuilder())
|
||||
->setTitle(t('Rebuilding content access permissions'))
|
||||
->addOperation('_node_access_rebuild_batch_operation', [])
|
||||
->setFinishCallback('_node_access_rebuild_batch_finished')
|
||||
->registerSetId(__FUNCTION__);
|
||||
batch_set($batch_builder->toArray());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Try to allocate enough time to rebuild node grants
|
||||
Environment::setTimeLimit(240);
|
||||
|
||||
// Rebuild newest nodes first so that recent content becomes available
|
||||
// quickly.
|
||||
$entity_query = \Drupal::entityQuery('node');
|
||||
$entity_query->sort('nid', 'DESC');
|
||||
// Disable access checking since all nodes must be processed even if the
|
||||
// user does not have access. And unless the current user has the bypass
|
||||
// node access permission, no nodes are accessible since the grants have
|
||||
// just been deleted.
|
||||
$entity_query->accessCheck(FALSE);
|
||||
$nids = $entity_query->execute();
|
||||
foreach ($nids as $nid) {
|
||||
$node_storage->resetCache([$nid]);
|
||||
$node = Node::load($nid);
|
||||
// To preserve database integrity, only write grants if the node
|
||||
// loads successfully.
|
||||
if (!empty($node)) {
|
||||
$grants = $access_control_handler->acquireGrants($node);
|
||||
\Drupal::service('node.grant_storage')->write($node, $grants);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Not using any node_access modules. Add the default grant.
|
||||
$access_control_handler->writeDefaultGrant();
|
||||
}
|
||||
|
||||
if (!isset($batch_builder)) {
|
||||
\Drupal::messenger()->addStatus(t('Content permissions have been rebuilt.'));
|
||||
node_access_needs_rebuild(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements callback_batch_operation().
|
||||
*
|
||||
* Performs batch operation for node_access_rebuild().
|
||||
*
|
||||
* This is a multistep operation: we go through all nodes by packs of 20. The
|
||||
* batch processing engine interrupts processing and sends progress feedback
|
||||
* after 1 second execution time.
|
||||
*
|
||||
* @param array $context
|
||||
* An array of contextual key/value information for rebuild batch process.
|
||||
*/
|
||||
function _node_access_rebuild_batch_operation(&$context): void {
|
||||
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
|
||||
if (empty($context['sandbox'])) {
|
||||
// Initiate multistep processing.
|
||||
$context['sandbox']['progress'] = 0;
|
||||
$context['sandbox']['current_node'] = 0;
|
||||
$context['sandbox']['max'] = \Drupal::entityQuery('node')->accessCheck(FALSE)->count()->execute();
|
||||
}
|
||||
|
||||
// Process the next 20 nodes.
|
||||
$limit = 20;
|
||||
$nids = \Drupal::entityQuery('node')
|
||||
->condition('nid', $context['sandbox']['current_node'], '>')
|
||||
->sort('nid', 'ASC')
|
||||
// Disable access checking since all nodes must be processed even if the
|
||||
// user does not have access. And unless the current user has the bypass
|
||||
// node access permission, no nodes are accessible since the grants have
|
||||
// just been deleted.
|
||||
->accessCheck(FALSE)
|
||||
->range(0, $limit)
|
||||
->execute();
|
||||
$node_storage->resetCache($nids);
|
||||
$nodes = Node::loadMultiple($nids);
|
||||
foreach ($nids as $nid) {
|
||||
// To preserve database integrity, only write grants if the node
|
||||
// loads successfully.
|
||||
if (!empty($nodes[$nid])) {
|
||||
$node = $nodes[$nid];
|
||||
/** @var \Drupal\node\NodeAccessControlHandlerInterface $access_control_handler */
|
||||
$access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node');
|
||||
$grants = $access_control_handler->acquireGrants($node);
|
||||
\Drupal::service('node.grant_storage')->write($node, $grants);
|
||||
}
|
||||
$context['sandbox']['progress']++;
|
||||
$context['sandbox']['current_node'] = $nid;
|
||||
}
|
||||
|
||||
// Multistep processing : report progress.
|
||||
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
|
||||
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements callback_batch_finished().
|
||||
*
|
||||
* Performs post-processing for node_access_rebuild().
|
||||
*
|
||||
* @param bool $success
|
||||
* A boolean indicating whether the re-build process has completed.
|
||||
* @param array $results
|
||||
* An array of results information.
|
||||
* @param array $operations
|
||||
* An array of function calls (not used in this function).
|
||||
*/
|
||||
function _node_access_rebuild_batch_finished($success, $results, $operations): void {
|
||||
if ($success) {
|
||||
\Drupal::messenger()->addStatus(t('The content access permissions have been rebuilt.'));
|
||||
node_access_needs_rebuild(FALSE);
|
||||
}
|
||||
else {
|
||||
\Drupal::messenger()->addError(t('The content access permissions have not been properly rebuilt.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup node_access".
|
||||
*/
|
||||
|
||||
/**
|
||||
* Marks a node to be re-indexed by the node_search plugin.
|
||||
*
|
||||
* @param int $nid
|
||||
* The node ID.
|
||||
*/
|
||||
function node_reindex_node_search($nid): void {
|
||||
if (\Drupal::moduleHandler()->moduleExists('search')) {
|
||||
// Reindex node context indexed by the node module search plugin.
|
||||
\Drupal::service('search.index')->markForReindex('node_search', $nid);
|
||||
}
|
||||
}
|
||||
28
web/core/modules/node/node.permissions.yml
Normal file
28
web/core/modules/node/node.permissions.yml
Normal file
@ -0,0 +1,28 @@
|
||||
bypass node access:
|
||||
title: 'Bypass content access control'
|
||||
description: 'View, edit and delete all content regardless of permission restrictions.'
|
||||
restrict access: true
|
||||
administer content types:
|
||||
title: 'Administer content types'
|
||||
description: 'Maintain the types of content available and the fields that are associated with those types.'
|
||||
restrict access: true
|
||||
administer nodes:
|
||||
title: 'Administer content'
|
||||
description: 'Promote, change ownership, edit revisions, and perform other tasks across all content types.'
|
||||
restrict access: true
|
||||
access content overview:
|
||||
title: 'Access the Content overview page'
|
||||
view own unpublished content:
|
||||
title: 'View own unpublished content'
|
||||
view all revisions:
|
||||
title: 'View all revisions'
|
||||
description: 'To view a revision, you also need permission to view the content item.'
|
||||
revert all revisions:
|
||||
title: 'Revert all revisions'
|
||||
description: 'To revert a revision, you also need permission to edit the content item.'
|
||||
delete all revisions:
|
||||
title: 'Delete all revisions'
|
||||
description: 'To delete a revision, you also need permission to delete the content item.'
|
||||
|
||||
permission_callbacks:
|
||||
- \Drupal\node\NodePermissions::nodeTypePermissions
|
||||
20
web/core/modules/node/node.post_update.php
Normal file
20
web/core/modules/node/node.post_update.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post update functions for Node.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_removed_post_updates().
|
||||
*/
|
||||
function node_removed_post_updates(): array {
|
||||
return [
|
||||
'node_post_update_configure_status_field_widget' => '9.0.0',
|
||||
'node_post_update_node_revision_views_data' => '9.0.0',
|
||||
'node_post_update_glossary_view_published' => '10.0.0',
|
||||
'node_post_update_rebuild_node_revision_routes' => '10.0.0',
|
||||
'node_post_update_modify_base_field_author_override' => '10.0.0',
|
||||
'node_post_update_set_node_type_description_and_help_to_null' => '11.0.0',
|
||||
];
|
||||
}
|
||||
167
web/core/modules/node/node.routing.yml
Normal file
167
web/core/modules/node/node.routing.yml
Normal file
@ -0,0 +1,167 @@
|
||||
node.multiple_delete_confirm:
|
||||
path: '/admin/content/node/delete'
|
||||
defaults:
|
||||
_form: '\Drupal\node\Form\DeleteMultiple'
|
||||
entity_type_id: 'node'
|
||||
requirements:
|
||||
_entity_delete_multiple_access: 'node'
|
||||
|
||||
entity.node.delete_multiple_form:
|
||||
path: '/admin/content/node/delete'
|
||||
defaults:
|
||||
_form: '\Drupal\node\Form\DeleteMultiple'
|
||||
entity_type_id: 'node'
|
||||
requirements:
|
||||
_entity_delete_multiple_access: 'node'
|
||||
|
||||
node.add_page:
|
||||
path: '/node/add'
|
||||
defaults:
|
||||
_title: 'Add content'
|
||||
_controller: '\Drupal\node\Controller\NodeController::addPage'
|
||||
options:
|
||||
_node_operation_route: TRUE
|
||||
requirements:
|
||||
_entity_create_any_access: 'node'
|
||||
|
||||
node.add:
|
||||
path: '/node/add/{node_type}'
|
||||
defaults:
|
||||
_entity_form: 'node.default'
|
||||
_title_callback: '\Drupal\node\Controller\NodeController::addPageTitle'
|
||||
requirements:
|
||||
_entity_create_access: 'node:{node_type}'
|
||||
options:
|
||||
_node_operation_route: TRUE
|
||||
parameters:
|
||||
node_type:
|
||||
type: entity:node_type
|
||||
with_config_overrides: TRUE
|
||||
|
||||
entity.node.preview:
|
||||
path: '/node/preview/{node_preview}/{view_mode_id}'
|
||||
defaults:
|
||||
_controller: '\Drupal\node\Controller\NodePreviewController::view'
|
||||
requirements:
|
||||
_node_preview_access: '{node_preview}'
|
||||
options:
|
||||
no_cache: TRUE
|
||||
parameters:
|
||||
node_preview:
|
||||
type: 'node_preview'
|
||||
|
||||
entity.node.version_history:
|
||||
path: '/node/{node}/revisions'
|
||||
defaults:
|
||||
_title: 'Revisions'
|
||||
_controller: '\Drupal\node\Controller\NodeController::revisionOverview'
|
||||
requirements:
|
||||
_entity_access: 'node.view all revisions'
|
||||
node: \d+
|
||||
options:
|
||||
_node_operation_route: TRUE
|
||||
parameters:
|
||||
node:
|
||||
type: entity:node
|
||||
|
||||
entity.node.revision:
|
||||
path: '/node/{node}/revisions/{node_revision}/view'
|
||||
defaults:
|
||||
_controller: '\Drupal\node\Controller\NodeController::revisionShow'
|
||||
_title_callback: '\Drupal\node\Controller\NodeController::revisionPageTitle'
|
||||
requirements:
|
||||
_entity_access: 'node_revision.view revision'
|
||||
node: \d+
|
||||
options:
|
||||
parameters:
|
||||
node:
|
||||
type: entity:node
|
||||
node_revision:
|
||||
type: entity_revision:node
|
||||
|
||||
node.revision_revert_confirm:
|
||||
path: '/node/{node}/revisions/{node_revision}/revert'
|
||||
defaults:
|
||||
_form: '\Drupal\node\Form\NodeRevisionRevertForm'
|
||||
_title: 'Revert to earlier revision'
|
||||
requirements:
|
||||
_entity_access: 'node_revision.revert revision'
|
||||
node: \d+
|
||||
options:
|
||||
_node_operation_route: TRUE
|
||||
parameters:
|
||||
node:
|
||||
type: entity:node
|
||||
node_revision:
|
||||
type: entity_revision:node
|
||||
|
||||
node.revision_revert_translation_confirm:
|
||||
path: '/node/{node}/revisions/{node_revision}/revert/{langcode}'
|
||||
defaults:
|
||||
_form: '\Drupal\node\Form\NodeRevisionRevertTranslationForm'
|
||||
_title: 'Revert to earlier revision of a translation'
|
||||
requirements:
|
||||
_entity_access: 'node_revision.revert revision'
|
||||
node: \d+
|
||||
options:
|
||||
_node_operation_route: TRUE
|
||||
parameters:
|
||||
node:
|
||||
type: entity:node
|
||||
node_revision:
|
||||
type: entity_revision:node
|
||||
|
||||
node.revision_delete_confirm:
|
||||
path: '/node/{node}/revisions/{node_revision}/delete'
|
||||
defaults:
|
||||
_form: '\Drupal\node\Form\NodeRevisionDeleteForm'
|
||||
_title: 'Delete earlier revision'
|
||||
requirements:
|
||||
_entity_access: 'node_revision.delete revision'
|
||||
node: \d+
|
||||
options:
|
||||
_node_operation_route: TRUE
|
||||
parameters:
|
||||
node:
|
||||
type: entity:node
|
||||
node_revision:
|
||||
type: entity_revision:node
|
||||
|
||||
entity.node_type.collection:
|
||||
path: '/admin/structure/types'
|
||||
defaults:
|
||||
_entity_list: 'node_type'
|
||||
_title: 'Content types'
|
||||
requirements:
|
||||
_permission: 'administer content types'
|
||||
|
||||
node.type_add:
|
||||
path: '/admin/structure/types/add'
|
||||
defaults:
|
||||
_entity_form: 'node_type.add'
|
||||
_title: 'Add content type'
|
||||
requirements:
|
||||
_permission: 'administer content types'
|
||||
|
||||
entity.node_type.edit_form:
|
||||
path: '/admin/structure/types/manage/{node_type}'
|
||||
defaults:
|
||||
_entity_form: 'node_type.edit'
|
||||
_title_callback: '\Drupal\Core\Entity\Controller\EntityController::title'
|
||||
requirements:
|
||||
_permission: 'administer content types'
|
||||
|
||||
entity.node_type.delete_form:
|
||||
path: '/admin/structure/types/manage/{node_type}/delete'
|
||||
defaults:
|
||||
_entity_form: 'node_type.delete'
|
||||
_title: 'Delete'
|
||||
requirements:
|
||||
_entity_access: 'node_type.delete'
|
||||
|
||||
node.configure_rebuild_confirm:
|
||||
path: '/admin/reports/status/rebuild'
|
||||
defaults:
|
||||
_form: '\Drupal\node\Form\RebuildPermissionsForm'
|
||||
requirements:
|
||||
_permission: 'administer nodes'
|
||||
48
web/core/modules/node/node.services.yml
Normal file
48
web/core/modules/node/node.services.yml
Normal file
@ -0,0 +1,48 @@
|
||||
parameters:
|
||||
node.moved_classes:
|
||||
'Drupal\node\NodeForm':
|
||||
class: 'Drupal\node\Form\NodeForm'
|
||||
deprecation_version: drupal:11.2.0
|
||||
removed_version: drupal:12.0.0
|
||||
change_record: https://www.drupal.org/node/3517871
|
||||
'Drupal\node\NodeTypeForm':
|
||||
class: 'Drupal\node\Form\NodeTypeForm'
|
||||
deprecation_version: drupal:11.2.0
|
||||
removed_version: drupal:12.0.0
|
||||
change_record: https://www.drupal.org/node/3517871
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
autoconfigure: true
|
||||
node.route_subscriber:
|
||||
class: Drupal\node\Routing\RouteSubscriber
|
||||
node.grant_storage:
|
||||
class: Drupal\node\NodeGrantDatabaseStorage
|
||||
arguments: ['@database', '@module_handler', '@language_manager']
|
||||
tags:
|
||||
- { name: backend_overridable }
|
||||
Drupal\node\NodeGrantDatabaseStorageInterface: '@node.grant_storage'
|
||||
access_check.node.preview:
|
||||
class: Drupal\node\Access\NodePreviewAccessCheck
|
||||
arguments: ['@entity_type.manager']
|
||||
tags:
|
||||
- { name: access_check, applies_to: _node_preview_access }
|
||||
node.admin_path.route_subscriber:
|
||||
class: Drupal\node\EventSubscriber\NodeAdminRouteSubscriber
|
||||
arguments: ['@config.factory', '@router.builder']
|
||||
node_preview:
|
||||
class: Drupal\node\ParamConverter\NodePreviewConverter
|
||||
arguments: ['@tempstore.private']
|
||||
tags:
|
||||
- { name: paramconverter }
|
||||
lazy: true
|
||||
cache_context.user.node_grants:
|
||||
class: Drupal\node\Cache\NodeAccessGrantsCacheContext
|
||||
arguments: ['@current_user']
|
||||
tags:
|
||||
- { name: cache.context }
|
||||
node.node_route_context:
|
||||
class: Drupal\node\ContextProvider\NodeRouteContext
|
||||
arguments: ['@current_route_match']
|
||||
tags:
|
||||
- { name: 'context_provider' }
|
||||
55
web/core/modules/node/src/Access/NodePreviewAccessCheck.php
Normal file
55
web/core/modules/node/src/Access/NodePreviewAccessCheck.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Access;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Determines access to node previews.
|
||||
*
|
||||
* @ingroup node_access
|
||||
*/
|
||||
class NodePreviewAccessCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs an EntityCreateAccessCheck object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager service.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access to the node preview page.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The currently logged in account.
|
||||
* @param \Drupal\node\NodeInterface $node_preview
|
||||
* The node that is being previewed.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(AccountInterface $account, NodeInterface $node_preview) {
|
||||
if ($node_preview->isNew()) {
|
||||
$access_controller = $this->entityTypeManager->getAccessControlHandler('node');
|
||||
return $access_controller->createAccess($node_preview->bundle(), $account, [], TRUE);
|
||||
}
|
||||
else {
|
||||
return $node_preview->access('update', $account, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
101
web/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php
Normal file
101
web/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Cache;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Cache\Context\CalculatedCacheContextInterface;
|
||||
use Drupal\Core\Cache\Context\UserCacheContextBase;
|
||||
|
||||
/**
|
||||
* Defines the node access view cache context service.
|
||||
*
|
||||
* Cache context ID: 'user.node_grants' (to vary by all operations' grants).
|
||||
* Calculated cache context ID: 'user.node_grants:%operation', e.g.
|
||||
* 'user.node_grants:view' (to vary by the view operation's grants).
|
||||
*
|
||||
* This allows for node access grants-sensitive caching when listing nodes.
|
||||
*
|
||||
* @see node_query_node_access_alter()
|
||||
* @ingroup node_access
|
||||
*/
|
||||
class NodeAccessGrantsCacheContext extends UserCacheContextBase implements CalculatedCacheContextInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getLabel() {
|
||||
return t("Content access view grants");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContext($operation = NULL) {
|
||||
// If the current user either can bypass node access then we don't need to
|
||||
// determine the exact node grants for the current user.
|
||||
if ($this->user->hasPermission('bypass node access')) {
|
||||
return 'all';
|
||||
}
|
||||
|
||||
// When no specific operation is specified, check the grants for all three
|
||||
// possible operations.
|
||||
if ($operation === NULL) {
|
||||
$result = [];
|
||||
foreach (['view', 'update', 'delete'] as $op) {
|
||||
$result[] = $this->checkNodeGrants($op);
|
||||
}
|
||||
return implode('-', $result);
|
||||
}
|
||||
else {
|
||||
return $this->checkNodeGrants($operation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the node grants for the given operation.
|
||||
*
|
||||
* @param string $operation
|
||||
* The operation to check the node grants for.
|
||||
*
|
||||
* @return string
|
||||
* The string representation of the cache context.
|
||||
*/
|
||||
protected function checkNodeGrants($operation) {
|
||||
// When checking the grants for the 'view' operation and the current user
|
||||
// has a global view grant (i.e. a view grant for node ID 0) — note that
|
||||
// this is automatically the case if no node access modules exist (no
|
||||
// hook_node_grants() implementations) then we don't need to determine the
|
||||
// exact node view grants for the current user.
|
||||
if ($operation === 'view' && node_access_view_all_nodes($this->user)) {
|
||||
return 'view.all';
|
||||
}
|
||||
|
||||
$grants = node_access_grants($operation, $this->user);
|
||||
$grants_context_parts = [];
|
||||
foreach ($grants as $realm => $gids) {
|
||||
$grants_context_parts[] = $realm . ':' . implode(',', $gids);
|
||||
}
|
||||
return $operation . '.' . implode(';', $grants_context_parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheableMetadata($operation = NULL) {
|
||||
$cacheable_metadata = new CacheableMetadata();
|
||||
|
||||
if (!\Drupal::moduleHandler()->hasImplementations('node_grants')) {
|
||||
return $cacheable_metadata;
|
||||
}
|
||||
|
||||
// The node grants may change if the user is updated. (The max-age is set to
|
||||
// zero below, but sites may override this cache context, and change it to a
|
||||
// non-zero value. In such cases, this cache tag is needed for correctness.)
|
||||
$cacheable_metadata->setCacheTags(['user:' . $this->user->id()]);
|
||||
|
||||
// If the site is using node grants, this cache context can not be
|
||||
// optimized.
|
||||
return $cacheable_metadata->setCacheMaxAge(0);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\ConfigTranslation;
|
||||
|
||||
use Drupal\config_translation\ConfigEntityMapper;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides a configuration mapper for node types.
|
||||
*/
|
||||
class NodeTypeMapper extends ConfigEntityMapper {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setEntity(ConfigEntityInterface $entity) {
|
||||
parent::setEntity($entity);
|
||||
|
||||
// Adds the title label to the translation form.
|
||||
$node_type = $entity->id();
|
||||
$config = $this->configFactory->get("core.base_field_override.node.$node_type.title");
|
||||
if (!$config->isNew()) {
|
||||
$this->addConfigName($config->getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\ContextProvider;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Plugin\Context\Context;
|
||||
use Drupal\Core\Plugin\Context\ContextProviderInterface;
|
||||
use Drupal\Core\Plugin\Context\EntityContext;
|
||||
use Drupal\Core\Plugin\Context\EntityContextDefinition;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Sets the current node as a context on node routes.
|
||||
*/
|
||||
class NodeRouteContext implements ContextProviderInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The route match object.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* Constructs a new NodeRouteContext.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match object.
|
||||
*/
|
||||
public function __construct(RouteMatchInterface $route_match) {
|
||||
$this->routeMatch = $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRuntimeContexts(array $unqualified_context_ids) {
|
||||
$result = [];
|
||||
$context_definition = EntityContextDefinition::create('node')->setRequired(FALSE);
|
||||
$value = NULL;
|
||||
if (($route_object = $this->routeMatch->getRouteObject())) {
|
||||
$route_contexts = $route_object->getOption('parameters');
|
||||
// Check for a node revision parameter first.
|
||||
if (isset($route_contexts['node_revision']) && $revision = $this->routeMatch->getParameter('node_revision')) {
|
||||
$value = $revision;
|
||||
}
|
||||
elseif (isset($route_contexts['node']) && $node = $this->routeMatch->getParameter('node')) {
|
||||
$value = $node;
|
||||
}
|
||||
elseif (isset($route_contexts['node_preview']) && $node = $this->routeMatch->getParameter('node_preview')) {
|
||||
$value = $node;
|
||||
}
|
||||
elseif ($this->routeMatch->getRouteName() == 'node.add') {
|
||||
$node_type = $this->routeMatch->getParameter('node_type');
|
||||
$value = Node::create(['type' => $node_type->id()]);
|
||||
}
|
||||
}
|
||||
|
||||
$cacheability = new CacheableMetadata();
|
||||
$cacheability->setCacheContexts(['route']);
|
||||
|
||||
$context = new Context($context_definition, $value);
|
||||
$context->addCacheableDependency($cacheability);
|
||||
$result['node'] = $context;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAvailableContexts() {
|
||||
$context = EntityContext::fromEntityTypeId('node', $this->t('Node from URL'));
|
||||
return ['node' => $context];
|
||||
}
|
||||
|
||||
}
|
||||
299
web/core/modules/node/src/Controller/NodeController.php
Normal file
299
web/core/modules/node/src/Controller/NodeController.php
Normal file
@ -0,0 +1,299 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Controller;
|
||||
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityRepositoryInterface;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\NodeStorageInterface;
|
||||
use Drupal\node\NodeTypeInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Returns responses for Node routes.
|
||||
*/
|
||||
class NodeController extends ControllerBase implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The entity repository service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityRepositoryInterface
|
||||
*/
|
||||
protected $entityRepository;
|
||||
|
||||
/**
|
||||
* Constructs a NodeController object.
|
||||
*
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter service.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
|
||||
* The entity repository.
|
||||
*/
|
||||
public function __construct(DateFormatterInterface $date_formatter, RendererInterface $renderer, EntityRepositoryInterface $entity_repository) {
|
||||
$this->dateFormatter = $date_formatter;
|
||||
$this->renderer = $renderer;
|
||||
$this->entityRepository = $entity_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays add content links for available content types.
|
||||
*
|
||||
* Redirects to node/add/[type] if only one content type is available.
|
||||
*
|
||||
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* A render array for a list of the node types that can be added; however,
|
||||
* if there is only one node type defined for the site, the function
|
||||
* will return a RedirectResponse to the node add page for that one node
|
||||
* type.
|
||||
*/
|
||||
public function addPage() {
|
||||
$definition = $this->entityTypeManager()->getDefinition('node_type');
|
||||
$build = [
|
||||
'#theme' => 'node_add_list',
|
||||
'#cache' => [
|
||||
'tags' => $this->entityTypeManager()->getDefinition('node_type')->getListCacheTags(),
|
||||
],
|
||||
];
|
||||
|
||||
$content = [];
|
||||
|
||||
$types = $this->entityTypeManager()->getStorage('node_type')->loadMultiple();
|
||||
uasort($types, [$definition->getClass(), 'sort']);
|
||||
// Only use node types the user has access to.
|
||||
foreach ($types as $type) {
|
||||
$access = $this->entityTypeManager()->getAccessControlHandler('node')->createAccess($type->id(), NULL, [], TRUE);
|
||||
if ($access->isAllowed()) {
|
||||
$content[$type->id()] = $type;
|
||||
}
|
||||
$this->renderer->addCacheableDependency($build, $access);
|
||||
}
|
||||
|
||||
// Bypass the node/add listing if only one content type is available.
|
||||
if (count($content) == 1) {
|
||||
$type = array_shift($content);
|
||||
return $this->redirect('node.add', ['node_type' => $type->id()]);
|
||||
}
|
||||
|
||||
$build['#content'] = $content;
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a node revision.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node_revision
|
||||
* The node revision.
|
||||
*
|
||||
* @return array
|
||||
* An array suitable for \Drupal\Core\Render\RendererInterface::render().
|
||||
*/
|
||||
public function revisionShow(NodeInterface $node_revision) {
|
||||
$node_view_controller = new NodeViewController($this->entityTypeManager(), $this->renderer, $this->currentUser(), $this->entityRepository);
|
||||
$page = $node_view_controller->view($node_revision);
|
||||
unset($page['nodes'][$node_revision->id()]['#cache']);
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page title callback for a node revision.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node_revision
|
||||
* The node revision.
|
||||
*
|
||||
* @return string
|
||||
* The page title.
|
||||
*/
|
||||
public function revisionPageTitle(NodeInterface $node_revision) {
|
||||
return $this->t('Revision of %title from %date', [
|
||||
'%title' => $node_revision->label(),
|
||||
'%date' => $this->dateFormatter->format($node_revision->getRevisionCreationTime()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an overview table of older revisions of a node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* A node object.
|
||||
*
|
||||
* @return array
|
||||
* An array as expected by \Drupal\Core\Render\RendererInterface::render().
|
||||
*/
|
||||
public function revisionOverview(NodeInterface $node) {
|
||||
// Always use the latest revision in the current content language to
|
||||
// determine if this node has translations. This supports showing the
|
||||
// correct translation revisions for translations that only have.
|
||||
// non-default revisions.
|
||||
$node = $this->entityRepository->getActive($node->getEntityTypeId(), $node->id());
|
||||
$langcode = $node->language()->getId();
|
||||
$language_name = $node->language()->getName();
|
||||
$languages = $node->getTranslationLanguages();
|
||||
$has_translations = (count($languages) > 1);
|
||||
$node_storage = $this->entityTypeManager()->getStorage('node');
|
||||
|
||||
$build['#title'] = $has_translations ? $this->t('@language_name revisions for %title', ['@language_name' => $language_name, '%title' => $node->label()]) : $this->t('Revisions for %title', ['%title' => $node->label()]);
|
||||
$header = [$this->t('Revision'), $this->t('Operations')];
|
||||
|
||||
$rows = [];
|
||||
$current_revision_displayed = FALSE;
|
||||
|
||||
foreach ($this->getRevisionIds($node, $node_storage) as $vid) {
|
||||
/** @var \Drupal\node\NodeInterface $revision */
|
||||
$revision = $node_storage->loadRevision($vid);
|
||||
// Only show revisions that are affected by the language that is being
|
||||
// displayed.
|
||||
if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
|
||||
$username = [
|
||||
'#theme' => 'username',
|
||||
'#account' => $revision->getRevisionUser(),
|
||||
];
|
||||
|
||||
// Use revision link to link to revisions that are not active.
|
||||
$date = $this->dateFormatter->format($revision->revision_timestamp->value, 'short');
|
||||
|
||||
// We treat also the latest translation-affecting revision as current
|
||||
// revision, if it was the default revision, as its values for the
|
||||
// current language will be the same of the current default revision in
|
||||
// this case.
|
||||
$is_current_revision = $revision->isDefaultRevision() || (!$current_revision_displayed && $revision->wasDefaultRevision());
|
||||
if (!$is_current_revision) {
|
||||
$link = Link::fromTextAndUrl($date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid]))->toString();
|
||||
}
|
||||
else {
|
||||
$link = $node->toLink($date)->toString();
|
||||
$current_revision_displayed = TRUE;
|
||||
}
|
||||
|
||||
$row = [];
|
||||
$column = [
|
||||
'data' => [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}',
|
||||
'#context' => [
|
||||
'date' => $link,
|
||||
'username' => $username,
|
||||
'message' => ['#markup' => $revision->revision_log->value, '#allowed_tags' => Xss::getHtmlTagList()],
|
||||
],
|
||||
],
|
||||
];
|
||||
// @todo Simplify once https://www.drupal.org/node/2334319 lands.
|
||||
$this->renderer->addCacheableDependency($column['data'], $username);
|
||||
$row[] = $column;
|
||||
|
||||
if ($is_current_revision) {
|
||||
$row[] = [
|
||||
'data' => [
|
||||
'#prefix' => '<em>',
|
||||
'#markup' => $this->t('Current revision'),
|
||||
'#suffix' => '</em>',
|
||||
],
|
||||
];
|
||||
|
||||
$rows[] = [
|
||||
'data' => $row,
|
||||
'class' => ['revision-current'],
|
||||
];
|
||||
}
|
||||
else {
|
||||
$links = [];
|
||||
if ($revision->access('revert revision')) {
|
||||
$links['revert'] = [
|
||||
'title' => $vid < $node->getRevisionId() ? $this->t('Revert') : $this->t('Set as current revision'),
|
||||
'url' => $has_translations ?
|
||||
Url::fromRoute('node.revision_revert_translation_confirm', ['node' => $node->id(), 'node_revision' => $vid, 'langcode' => $langcode]) :
|
||||
Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid]),
|
||||
];
|
||||
}
|
||||
|
||||
if ($revision->access('delete revision')) {
|
||||
$links['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid]),
|
||||
];
|
||||
}
|
||||
|
||||
$row[] = [
|
||||
'data' => [
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
],
|
||||
];
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$build['node_revisions_table'] = [
|
||||
'#theme' => 'table',
|
||||
'#rows' => $rows,
|
||||
'#header' => $header,
|
||||
'#attached' => [
|
||||
'library' => ['node/drupal.node.admin'],
|
||||
],
|
||||
'#attributes' => ['class' => ['node-revision-table']],
|
||||
];
|
||||
|
||||
$build['pager'] = ['#type' => 'pager'];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* The _title_callback for the node.add route.
|
||||
*
|
||||
* @param \Drupal\node\NodeTypeInterface $node_type
|
||||
* The current node.
|
||||
*
|
||||
* @return string
|
||||
* The page title.
|
||||
*/
|
||||
public function addPageTitle(NodeTypeInterface $node_type) {
|
||||
return $this->t('Create @name', ['@name' => $node_type->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of node revision IDs for a specific node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node entity.
|
||||
* @param \Drupal\node\NodeStorageInterface $node_storage
|
||||
* The node storage handler.
|
||||
*
|
||||
* @return int[]
|
||||
* Node revision IDs (in descending order).
|
||||
*/
|
||||
protected function getRevisionIds(NodeInterface $node, NodeStorageInterface $node_storage) {
|
||||
$result = $node_storage->getQuery()
|
||||
->accessCheck(TRUE)
|
||||
->allRevisions()
|
||||
->condition($node->getEntityType()->getKey('id'), $node->id())
|
||||
->sort($node->getEntityType()->getKey('revision'), 'DESC')
|
||||
->pager(50)
|
||||
->execute();
|
||||
return array_keys($result);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Controller;
|
||||
|
||||
use Drupal\Core\Entity\Controller\EntityViewController;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityRepositoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a controller to render a single node in preview.
|
||||
*/
|
||||
class NodePreviewController extends EntityViewController {
|
||||
|
||||
/**
|
||||
* The entity repository service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityRepositoryInterface
|
||||
*/
|
||||
protected $entityRepository;
|
||||
|
||||
/**
|
||||
* Creates a NodeViewController object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
|
||||
* The entity repository.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, EntityRepositoryInterface $entity_repository) {
|
||||
parent::__construct($entity_type_manager, $renderer);
|
||||
$this->entityRepository = $entity_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('renderer'),
|
||||
$container->get('entity.repository')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function view(EntityInterface $node_preview, $view_mode_id = 'full', $langcode = NULL) {
|
||||
$node_preview->preview_view_mode = $view_mode_id;
|
||||
$build = parent::view($node_preview, $view_mode_id);
|
||||
|
||||
$build['#attached']['library'][] = 'node/drupal.node.preview';
|
||||
|
||||
// Don't render cache previews.
|
||||
unset($build['#cache']);
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* The _title_callback for the page that renders a single node in preview.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $node_preview
|
||||
* The current node.
|
||||
*
|
||||
* @return string
|
||||
* The page title.
|
||||
*
|
||||
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
|
||||
* replacement.
|
||||
* @see https://www.drupal.org/node/3518065
|
||||
*/
|
||||
public function title(EntityInterface $node_preview) {
|
||||
@trigger_error(__METHOD__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3518065', E_USER_DEPRECATED);
|
||||
return $this->entityRepository->getTranslationFromContext($node_preview)->label();
|
||||
}
|
||||
|
||||
}
|
||||
82
web/core/modules/node/src/Controller/NodeViewController.php
Normal file
82
web/core/modules/node/src/Controller/NodeViewController.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Controller;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\Controller\EntityViewController;
|
||||
use Drupal\Core\Entity\EntityRepositoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a controller to render a single node.
|
||||
*/
|
||||
class NodeViewController extends EntityViewController {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The entity repository service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityRepositoryInterface
|
||||
*/
|
||||
protected $entityRepository;
|
||||
|
||||
/**
|
||||
* Creates a NodeViewController object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
|
||||
* The entity repository.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, AccountInterface $current_user, EntityRepositoryInterface $entity_repository) {
|
||||
parent::__construct($entity_type_manager, $renderer);
|
||||
$this->currentUser = $current_user;
|
||||
$this->entityRepository = $entity_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('renderer'),
|
||||
$container->get('current_user'),
|
||||
$container->get('entity.repository')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function view(EntityInterface $node, $view_mode = 'full', $langcode = NULL) {
|
||||
return parent::view($node, $view_mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* The _title_callback for the page that renders a single node.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $node
|
||||
* The current node.
|
||||
*
|
||||
* @return string
|
||||
* The page title.
|
||||
*/
|
||||
public function title(EntityInterface $node) {
|
||||
return $this->entityRepository->getTranslationFromContext($node)->label();
|
||||
}
|
||||
|
||||
}
|
||||
387
web/core/modules/node/src/Entity/Node.php
Normal file
387
web/core/modules/node/src/Entity/Node.php
Normal file
@ -0,0 +1,387 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Attribute\ContentEntityType;
|
||||
use Drupal\Core\Entity\EditorialContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\node\Form\DeleteMultiple;
|
||||
use Drupal\node\Form\NodeDeleteForm;
|
||||
use Drupal\node\Form\NodeForm;
|
||||
use Drupal\node\NodeAccessControlHandler;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\node\NodeListBuilder;
|
||||
use Drupal\node\NodeStorage;
|
||||
use Drupal\node\NodeStorageSchema;
|
||||
use Drupal\node\NodeTranslationHandler;
|
||||
use Drupal\node\NodeViewBuilder;
|
||||
use Drupal\node\NodeViewsData;
|
||||
use Drupal\user\EntityOwnerTrait;
|
||||
|
||||
/**
|
||||
* Defines the node entity class.
|
||||
*/
|
||||
#[ContentEntityType(
|
||||
id: 'node',
|
||||
label: new TranslatableMarkup('Content'),
|
||||
label_collection: new TranslatableMarkup('Content'),
|
||||
label_singular: new TranslatableMarkup('content item'),
|
||||
label_plural: new TranslatableMarkup('content items'),
|
||||
entity_keys: [
|
||||
'id' => 'nid',
|
||||
'revision' => 'vid',
|
||||
'bundle' => 'type',
|
||||
'label' => 'title',
|
||||
'langcode' => 'langcode',
|
||||
'uuid' => 'uuid',
|
||||
'status' => 'status',
|
||||
'published' => 'status',
|
||||
'uid' => 'uid',
|
||||
'owner' => 'uid',
|
||||
],
|
||||
handlers: [
|
||||
'storage' => NodeStorage::class,
|
||||
'storage_schema' => NodeStorageSchema::class,
|
||||
'view_builder' => NodeViewBuilder::class,
|
||||
'access' => NodeAccessControlHandler::class,
|
||||
'views_data' => NodeViewsData::class,
|
||||
'form' => [
|
||||
'default' => NodeForm::class,
|
||||
'delete' => NodeDeleteForm::class,
|
||||
'edit' => NodeForm::class,
|
||||
'delete-multiple-confirm' => DeleteMultiple::class,
|
||||
],
|
||||
'route_provider' => [
|
||||
'html' => NodeRouteProvider::class,
|
||||
],
|
||||
'list_builder' => NodeListBuilder::class,
|
||||
'translation' => NodeTranslationHandler::class,
|
||||
],
|
||||
links: [
|
||||
'canonical' => '/node/{node}',
|
||||
'delete-form' => '/node/{node}/delete',
|
||||
'delete-multiple-form' => '/admin/content/node/delete',
|
||||
'edit-form' => '/node/{node}/edit',
|
||||
'version-history' => '/node/{node}/revisions',
|
||||
'revision' => '/node/{node}/revisions/{node_revision}/view',
|
||||
'create' => '/node',
|
||||
],
|
||||
collection_permission: 'access content overview',
|
||||
permission_granularity: 'bundle',
|
||||
bundle_entity_type: 'node_type',
|
||||
bundle_label: new TranslatableMarkup('Content type'),
|
||||
base_table: 'node',
|
||||
data_table: 'node_field_data',
|
||||
revision_table: 'node_revision',
|
||||
revision_data_table: 'node_field_revision',
|
||||
translatable: TRUE,
|
||||
show_revision_ui: TRUE,
|
||||
label_count: [
|
||||
'singular' => '@count content item',
|
||||
'plural' => '@count content items',
|
||||
],
|
||||
field_ui_base_route: 'entity.node_type.edit_form',
|
||||
common_reference_target: TRUE,
|
||||
list_cache_contexts: ['user.node_grants:view'],
|
||||
revision_metadata_keys: [
|
||||
'revision_user' => 'revision_uid',
|
||||
'revision_created' => 'revision_timestamp',
|
||||
'revision_log_message' => 'revision_log',
|
||||
],
|
||||
)]
|
||||
class Node extends EditorialContentEntityBase implements NodeInterface {
|
||||
|
||||
use EntityOwnerTrait;
|
||||
|
||||
/**
|
||||
* Whether the node is being previewed or not.
|
||||
*
|
||||
* The variable is set to public as it will give a considerable performance
|
||||
* improvement. See https://www.drupal.org/node/2498919.
|
||||
*
|
||||
* @var true|null
|
||||
* TRUE if the node is being previewed and NULL if it is not.
|
||||
*/
|
||||
// phpcs:ignore Drupal.NamingConventions.ValidVariableName.LowerCamelName, Drupal.Commenting.VariableComment.Missing
|
||||
public $in_preview = NULL;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage) {
|
||||
parent::preSave($storage);
|
||||
|
||||
foreach (array_keys($this->getTranslationLanguages()) as $langcode) {
|
||||
$translation = $this->getTranslation($langcode);
|
||||
|
||||
// If no owner has been set explicitly, make the anonymous user the owner.
|
||||
if (!$translation->getOwner()) {
|
||||
$translation->setOwnerId(0);
|
||||
}
|
||||
}
|
||||
|
||||
// If no revision author has been set explicitly, make the node owner the
|
||||
// revision author.
|
||||
if (!$this->getRevisionUser()) {
|
||||
$this->setRevisionUserId($this->getOwnerId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
|
||||
parent::preSaveRevision($storage, $record);
|
||||
|
||||
if (!$this->isNewRevision() && $this->getOriginal() && (!isset($record->revision_log) || $record->revision_log === '')) {
|
||||
// If we are updating an existing node without adding a new revision, we
|
||||
// need to make sure $entity->revision_log is reset whenever it is empty.
|
||||
// Therefore, this code allows us to avoid clobbering an existing log
|
||||
// entry with an empty one.
|
||||
$record->revision_log = $this->getOriginal()->revision_log->value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
|
||||
if ($update && \Drupal::moduleHandler()->moduleExists('search')) {
|
||||
// Remove deleted translations from the search index.
|
||||
foreach ($this->translations as $langcode => $translation) {
|
||||
if ($translation['status'] === static::TRANSLATION_REMOVED) {
|
||||
\Drupal::service('search.index')->clear('node_search', $this->id(), $langcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parent::postSave($storage, $update);
|
||||
|
||||
// Update the node access table for this node, but only if it is the
|
||||
// default revision. There's no need to delete existing records if the node
|
||||
// is new.
|
||||
if ($this->isDefaultRevision()) {
|
||||
/** @var \Drupal\node\NodeAccessControlHandlerInterface $access_control_handler */
|
||||
$access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node');
|
||||
$grants = $access_control_handler->acquireGrants($this);
|
||||
\Drupal::service('node.grant_storage')->write($this, $grants, NULL, $update);
|
||||
}
|
||||
|
||||
// Reindex the node when it is updated. The node is automatically indexed
|
||||
// when it is added, simply by being added to the node table.
|
||||
if ($update) {
|
||||
node_reindex_node_search($this->id());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function preDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::preDelete($storage, $entities);
|
||||
|
||||
// Ensure that all nodes deleted are removed from the search index.
|
||||
if (\Drupal::hasService('search.index')) {
|
||||
/** @var \Drupal\search\SearchIndexInterface $search_index */
|
||||
$search_index = \Drupal::service('search.index');
|
||||
foreach ($entities as $entity) {
|
||||
$search_index->clear('node_search', $entity->nid->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $nodes) {
|
||||
parent::postDelete($storage, $nodes);
|
||||
\Drupal::service('node.grant_storage')->deleteNodeRecords(array_keys($nodes));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getType() {
|
||||
return $this->bundle();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($operation = 'view', ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
// This override exists to set the operation to the default value "view".
|
||||
return parent::access($operation, $account, $return_as_object);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle() {
|
||||
return $this->get('title')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTitle($title) {
|
||||
$this->set('title', $title);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCreatedTime() {
|
||||
return $this->get('created')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setCreatedTime($timestamp) {
|
||||
$this->set('created', $timestamp);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isPromoted() {
|
||||
return (bool) $this->get('promote')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPromoted($promoted) {
|
||||
$this->set('promote', $promoted ? NodeInterface::PROMOTED : NodeInterface::NOT_PROMOTED);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isSticky() {
|
||||
return (bool) $this->get('sticky')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSticky($sticky) {
|
||||
$this->set('sticky', $sticky ? NodeInterface::STICKY : NodeInterface::NOT_STICKY);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
$fields += static::ownerBaseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['title'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Title'))
|
||||
->setRequired(TRUE)
|
||||
->setTranslatable(TRUE)
|
||||
->setRevisionable(TRUE)
|
||||
->setSetting('max_length', 255)
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'string',
|
||||
'weight' => -5,
|
||||
])
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'string_textfield',
|
||||
'weight' => -5,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['uid']
|
||||
->setLabel(t('Authored by'))
|
||||
->setDescription(t('The username of the content author.'))
|
||||
->setRevisionable(TRUE)
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'author',
|
||||
'weight' => 0,
|
||||
])
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'entity_reference_autocomplete',
|
||||
'weight' => 5,
|
||||
'settings' => [
|
||||
'match_operator' => 'CONTAINS',
|
||||
'size' => '60',
|
||||
'placeholder' => '',
|
||||
],
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['status']
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'boolean_checkbox',
|
||||
'settings' => [
|
||||
'display_label' => TRUE,
|
||||
],
|
||||
'weight' => 120,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['created'] = BaseFieldDefinition::create('created')
|
||||
->setLabel(t('Authored on'))
|
||||
->setDescription(t('The date and time that the content was created.'))
|
||||
->setRevisionable(TRUE)
|
||||
->setTranslatable(TRUE)
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'timestamp',
|
||||
'weight' => 0,
|
||||
])
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'datetime_timestamp',
|
||||
'weight' => 10,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['changed'] = BaseFieldDefinition::create('changed')
|
||||
->setLabel(t('Changed'))
|
||||
->setDescription(t('The time that the node was last edited.'))
|
||||
->setRevisionable(TRUE)
|
||||
->setTranslatable(TRUE);
|
||||
|
||||
$fields['promote'] = BaseFieldDefinition::create('boolean')
|
||||
->setLabel(t('Promoted to front page'))
|
||||
->setRevisionable(TRUE)
|
||||
->setTranslatable(TRUE)
|
||||
->setDefaultValue(TRUE)
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'boolean_checkbox',
|
||||
'settings' => [
|
||||
'display_label' => TRUE,
|
||||
],
|
||||
'weight' => 15,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['sticky'] = BaseFieldDefinition::create('boolean')
|
||||
->setLabel(t('Sticky at top of lists'))
|
||||
->setRevisionable(TRUE)
|
||||
->setTranslatable(TRUE)
|
||||
->setDefaultValue(FALSE)
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'boolean_checkbox',
|
||||
'settings' => [
|
||||
'display_label' => TRUE,
|
||||
],
|
||||
'weight' => 16,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
49
web/core/modules/node/src/Entity/NodeRouteProvider.php
Normal file
49
web/core/modules/node/src/Entity/NodeRouteProvider.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Provides routes for nodes.
|
||||
*/
|
||||
class NodeRouteProvider implements EntityRouteProviderInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutes(EntityTypeInterface $entity_type) {
|
||||
$route_collection = new RouteCollection();
|
||||
$route = (new Route('/node/{node}'))
|
||||
->addDefaults([
|
||||
'_controller' => '\Drupal\node\Controller\NodeViewController::view',
|
||||
'_title_callback' => '\Drupal\node\Controller\NodeViewController::title',
|
||||
])
|
||||
->setRequirement('node', '\d+')
|
||||
->setRequirement('_entity_access', 'node.view');
|
||||
$route_collection->add('entity.node.canonical', $route);
|
||||
|
||||
$route = (new Route('/node/{node}/delete'))
|
||||
->addDefaults([
|
||||
'_entity_form' => 'node.delete',
|
||||
'_title' => 'Delete',
|
||||
])
|
||||
->setRequirement('node', '\d+')
|
||||
->setRequirement('_entity_access', 'node.delete')
|
||||
->setOption('_node_operation_route', TRUE);
|
||||
$route_collection->add('entity.node.delete_form', $route);
|
||||
|
||||
$route = (new Route('/node/{node}/edit'))
|
||||
->setDefault('_entity_form', 'node.edit')
|
||||
->setRequirement('_entity_access', 'node.update')
|
||||
->setRequirement('node', '\d+')
|
||||
->setOption('_node_operation_route', TRUE);
|
||||
$route_collection->add('entity.node.edit_form', $route);
|
||||
|
||||
return $route_collection;
|
||||
}
|
||||
|
||||
}
|
||||
217
web/core/modules/node/src/Entity/NodeType.php
Normal file
217
web/core/modules/node/src/Entity/NodeType.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Entity;
|
||||
|
||||
use Drupal\Core\Config\Action\Attribute\ActionMethod;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
|
||||
use Drupal\Core\Entity\Attribute\ConfigEntityType;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\node\Form\NodeTypeDeleteConfirm;
|
||||
use Drupal\node\NodeTypeAccessControlHandler;
|
||||
use Drupal\node\Form\NodeTypeForm;
|
||||
use Drupal\node\NodeTypeInterface;
|
||||
use Drupal\node\NodeTypeListBuilder;
|
||||
use Drupal\user\Entity\EntityPermissionsRouteProvider;
|
||||
|
||||
/**
|
||||
* Defines the Node type configuration entity.
|
||||
*/
|
||||
#[ConfigEntityType(
|
||||
id: 'node_type',
|
||||
label: new TranslatableMarkup('Content type'),
|
||||
label_collection: new TranslatableMarkup('Content types'),
|
||||
label_singular: new TranslatableMarkup('content type'),
|
||||
label_plural: new TranslatableMarkup('content types'),
|
||||
config_prefix: 'type',
|
||||
entity_keys: [
|
||||
'id' => 'type',
|
||||
'label' => 'name',
|
||||
],
|
||||
handlers: [
|
||||
'access' => NodeTypeAccessControlHandler::class,
|
||||
'form' => [
|
||||
'add' => NodeTypeForm::class,
|
||||
'edit' => NodeTypeForm::class,
|
||||
'delete' => NodeTypeDeleteConfirm::class,
|
||||
],
|
||||
'route_provider' => [
|
||||
'permissions' => EntityPermissionsRouteProvider::class,
|
||||
],
|
||||
'list_builder' => NodeTypeListBuilder::class,
|
||||
],
|
||||
links: [
|
||||
'edit-form' => '/admin/structure/types/manage/{node_type}',
|
||||
'delete-form' => '/admin/structure/types/manage/{node_type}/delete',
|
||||
'entity-permissions-form' => '/admin/structure/types/manage/{node_type}/permissions',
|
||||
'collection' => '/admin/structure/types',
|
||||
],
|
||||
admin_permission: 'administer content types',
|
||||
bundle_of: 'node',
|
||||
label_count: [
|
||||
'singular' => '@count content type',
|
||||
'plural' => '@count content types',
|
||||
],
|
||||
config_export: [
|
||||
'name',
|
||||
'type',
|
||||
'description',
|
||||
'help',
|
||||
'new_revision',
|
||||
'preview_mode',
|
||||
'display_submitted',
|
||||
],
|
||||
)]
|
||||
class NodeType extends ConfigEntityBundleBase implements NodeTypeInterface {
|
||||
|
||||
/**
|
||||
* The machine name of this node type.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @todo Rename to $id.
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The human-readable name of the node type.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @todo Rename to $label.
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* A brief description of this node type.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $description = NULL;
|
||||
|
||||
/**
|
||||
* Help information shown to the user when creating a Node of this type.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $help = NULL;
|
||||
|
||||
/**
|
||||
* Default value of the 'Create new revision' checkbox of this node type.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $new_revision = TRUE;
|
||||
|
||||
/**
|
||||
* The preview mode.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $preview_mode = DRUPAL_OPTIONAL;
|
||||
|
||||
/**
|
||||
* Display setting for author and date Submitted by post information.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $display_submitted = TRUE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function id() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isLocked() {
|
||||
$locked = \Drupal::state()->get('node.type.locked');
|
||||
return $locked[$this->id()] ?? FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[ActionMethod(adminLabel: new TranslatableMarkup('Automatically create new revisions'), pluralize: FALSE)]
|
||||
public function setNewRevision($new_revision) {
|
||||
$this->new_revision = $new_revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function displaySubmitted() {
|
||||
return $this->display_submitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[ActionMethod(adminLabel: new TranslatableMarkup('Set whether to display submission information'), pluralize: FALSE)]
|
||||
public function setDisplaySubmitted($display_submitted) {
|
||||
$this->display_submitted = $display_submitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPreviewMode() {
|
||||
return $this->preview_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[ActionMethod(adminLabel: new TranslatableMarkup('Set preview mode'), pluralize: FALSE)]
|
||||
public function setPreviewMode($preview_mode) {
|
||||
$this->preview_mode = $preview_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHelp() {
|
||||
return $this->help ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->description ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
|
||||
parent::postSave($storage, $update);
|
||||
|
||||
if ($update) {
|
||||
// Clear the cached field definitions as some settings affect the field
|
||||
// definitions.
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::postDelete($storage, $entities);
|
||||
|
||||
// Clear the node type cache to reflect the removal.
|
||||
$storage->resetCache(array_keys($entities));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function shouldCreateNewRevision() {
|
||||
return $this->new_revision;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Config\ConfigCrudEvent;
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Routing\RouteBuilderInterface;
|
||||
use Drupal\Core\Routing\RouteSubscriberBase;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Sets the _admin_route for specific node-related routes.
|
||||
*/
|
||||
class NodeAdminRouteSubscriber extends RouteSubscriberBase {
|
||||
|
||||
/**
|
||||
* The config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The router builder.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteBuilderInterface
|
||||
*/
|
||||
protected $routerBuilder;
|
||||
|
||||
/**
|
||||
* Constructs a new NodeAdminRouteSubscriber.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
* @param \Drupal\Core\Routing\RouteBuilderInterface $router_builder
|
||||
* The router builder service.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, RouteBuilderInterface $router_builder) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->routerBuilder = $router_builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function alterRoutes(RouteCollection $collection) {
|
||||
if ($this->configFactory->get('node.settings')->get('use_admin_theme')) {
|
||||
foreach ($collection->all() as $route) {
|
||||
if ($route->hasOption('_node_operation_route')) {
|
||||
$route->setOption('_admin_route', TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the router when node.settings:use_admin_theme is changed.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||
* The event object.
|
||||
*/
|
||||
public function onConfigSave(ConfigCrudEvent $event) {
|
||||
if ($event->getConfig()->getName() === 'node.settings' && $event->isChanged('use_admin_theme')) {
|
||||
$this->routerBuilder->setRebuildNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events = parent::getSubscribedEvents();
|
||||
$events[ConfigEvents::SAVE][] = ['onConfigSave', 0];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\EventSubscriber;
|
||||
|
||||
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\ParamConverter\ParamNotConvertedException;
|
||||
use Drupal\Core\Routing\UrlGeneratorInterface;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Redirect node translations that have been consolidated by migration.
|
||||
*
|
||||
* If we migrated node translations from Drupal 6 or 7, these nodes are now
|
||||
* combined with their source language node. Since there still might be
|
||||
* references to the URLs of these now consolidated nodes, this service catches
|
||||
* the 404s and try to redirect them to the right node in the right language.
|
||||
*
|
||||
* The mapping of the old nids to the new ones is made by the
|
||||
* NodeTranslationMigrateSubscriber class during the migration and is stored
|
||||
* in the "node_translation_redirect" key/value collection.
|
||||
*
|
||||
* @see \Drupal\node\NodeServiceProvider
|
||||
* @see \Drupal\node\EventSubscriber\NodeTranslationMigrateSubscriber
|
||||
*/
|
||||
class NodeTranslationExceptionSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The key value factory.
|
||||
*
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
|
||||
*/
|
||||
protected $keyValue;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The URL generator.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\UrlGeneratorInterface
|
||||
*/
|
||||
protected $urlGenerator;
|
||||
|
||||
/**
|
||||
* The state service.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Constructs the NodeTranslationExceptionSubscriber.
|
||||
*
|
||||
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value
|
||||
* The key value factory.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
|
||||
* The URL generator.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state service.
|
||||
*/
|
||||
public function __construct(KeyValueFactoryInterface $key_value, LanguageManagerInterface $language_manager, UrlGeneratorInterface $url_generator, StateInterface $state) {
|
||||
$this->keyValue = $key_value;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->urlGenerator = $url_generator;
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects not found node translations using the key value collection.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\ExceptionEvent $event
|
||||
* The exception event.
|
||||
*/
|
||||
public function onException(ExceptionEvent $event) {
|
||||
$exception = $event->getThrowable();
|
||||
|
||||
// If this is not a 404, we don't need to check for a redirection.
|
||||
if (!($exception instanceof NotFoundHttpException)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$previous_exception = $exception->getPrevious();
|
||||
if ($previous_exception instanceof ParamNotConvertedException) {
|
||||
$route_name = $previous_exception->getRouteName();
|
||||
$parameters = $previous_exception->getRawParameters();
|
||||
if ($route_name === 'entity.node.canonical' && isset($parameters['node'])) {
|
||||
// If the node_translation_redirect state is not set, we don't need to
|
||||
// check for a redirection.
|
||||
if (!$this->state->get('node_translation_redirect')) {
|
||||
return;
|
||||
}
|
||||
$old_nid = $parameters['node'];
|
||||
$collection = $this->keyValue->get('node_translation_redirect');
|
||||
if ($old_nid && $value = $collection->get($old_nid)) {
|
||||
[$nid, $langcode] = $value;
|
||||
$language = $this->languageManager->getLanguage($langcode);
|
||||
$url = $this->urlGenerator->generateFromRoute('entity.node.canonical', ['node' => $nid], ['language' => $language]);
|
||||
$response = new RedirectResponse($url, 301);
|
||||
$event->setResponse($response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events = [];
|
||||
|
||||
$events[KernelEvents::EXCEPTION] = ['onException'];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\EventSubscriber;
|
||||
|
||||
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Drupal\migrate\Event\EventBase;
|
||||
use Drupal\migrate\Event\MigrateEvents;
|
||||
use Drupal\migrate\Event\MigrateImportEvent;
|
||||
use Drupal\migrate\Event\MigratePostRowSaveEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Creates a key value collection for migrated node translation mappings.
|
||||
*
|
||||
* If we are migrating node translations from Drupal 6 or 7, these nodes will be
|
||||
* combined with their source node. Since there still might be references to the
|
||||
* URLs of these now consolidated nodes, this service saves the mapping between
|
||||
* the old nids to the new ones to be able to redirect them to the right node in
|
||||
* the right language.
|
||||
*
|
||||
* The mapping is stored in the "node_translation_redirect" key/value collection
|
||||
* and the redirection is made by the NodeTranslationExceptionSubscriber class.
|
||||
*
|
||||
* @see \Drupal\node\NodeServiceProvider
|
||||
* @see \Drupal\node\EventSubscriber\NodeTranslationExceptionSubscriber
|
||||
*/
|
||||
class NodeTranslationMigrateSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The key value factory.
|
||||
*
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
|
||||
*/
|
||||
protected $keyValue;
|
||||
|
||||
/**
|
||||
* The state service.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Constructs the NodeTranslationMigrateSubscriber.
|
||||
*
|
||||
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value
|
||||
* The key value factory.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state service.
|
||||
*/
|
||||
public function __construct(KeyValueFactoryInterface $key_value, StateInterface $state) {
|
||||
$this->keyValue = $key_value;
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check if we are migrating translated nodes.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\EventBase $event
|
||||
* The migrate event.
|
||||
*
|
||||
* @return bool
|
||||
* True if we are migrating translated nodes, false otherwise.
|
||||
*/
|
||||
protected function isNodeTranslationsMigration(EventBase $event) {
|
||||
$migration = $event->getMigration();
|
||||
$source_configuration = $migration->getSourceConfiguration();
|
||||
$destination_configuration = $migration->getDestinationConfiguration();
|
||||
return !empty($source_configuration['translations']) && $destination_configuration['plugin'] === 'entity:node';
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the old nid to the new one in the key value collection.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigratePostRowSaveEvent $event
|
||||
* The migrate post row save event.
|
||||
*/
|
||||
public function onPostRowSave(MigratePostRowSaveEvent $event) {
|
||||
if ($this->isNodeTranslationsMigration($event)) {
|
||||
$row = $event->getRow();
|
||||
$source = $row->getSource();
|
||||
$destination = $row->getDestination();
|
||||
$collection = $this->keyValue->get('node_translation_redirect');
|
||||
$collection->set($source['nid'], [$destination['nid'], $destination['langcode']]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the node_translation_redirect state to enable the redirects.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent $event
|
||||
* The migrate import event.
|
||||
*/
|
||||
public function onPostImport(MigrateImportEvent $event) {
|
||||
if ($this->isNodeTranslationsMigration($event)) {
|
||||
$this->state->set('node_translation_redirect', TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events = [];
|
||||
|
||||
$events[MigrateEvents::POST_ROW_SAVE] = ['onPostRowSave'];
|
||||
$events[MigrateEvents::POST_IMPORT] = ['onPostImport'];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
36
web/core/modules/node/src/Form/DeleteMultiple.php
Normal file
36
web/core/modules/node/src/Form/DeleteMultiple.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Form;
|
||||
|
||||
use Drupal\Core\Entity\Form\DeleteMultipleForm as EntityDeleteMultipleForm;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a node deletion confirmation form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class DeleteMultiple extends EntityDeleteMultipleForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('system.admin_content');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDeletedMessage($count) {
|
||||
return $this->formatPlural($count, 'Deleted @count content item.', 'Deleted @count content items.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getInaccessibleMessage($count) {
|
||||
return $this->formatPlural($count, "@count content item has not been deleted because you do not have the necessary permissions.", "@count content items have not been deleted because you do not have the necessary permissions.");
|
||||
}
|
||||
|
||||
}
|
||||
47
web/core/modules/node/src/Form/NodeDeleteForm.php
Normal file
47
web/core/modules/node/src/Form/NodeDeleteForm.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityDeleteForm;
|
||||
|
||||
/**
|
||||
* Provides a form for deleting a node.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class NodeDeleteForm extends ContentEntityDeleteForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDeletionMessage() {
|
||||
/** @var \Drupal\node\NodeInterface $entity */
|
||||
$entity = $this->getEntity();
|
||||
|
||||
$node_type_storage = $this->entityTypeManager->getStorage('node_type');
|
||||
$node_type = $node_type_storage->load($entity->bundle());
|
||||
|
||||
if (!$entity->isDefaultTranslation()) {
|
||||
return $this->t('@language translation of the @type %label has been deleted.', [
|
||||
'@language' => $entity->language()->getName(),
|
||||
'@type' => $node_type->label(),
|
||||
'%label' => $entity->label(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->t('The @type %title has been deleted.', [
|
||||
'@type' => $node_type->label(),
|
||||
'%title' => $this->getEntity()->label(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function logDeletionMessage() {
|
||||
/** @var \Drupal\node\NodeInterface $entity */
|
||||
$entity = $this->getEntity();
|
||||
$this->logger('content')->info('@type: deleted %title.', ['@type' => $entity->getType(), '%title' => $entity->label()]);
|
||||
}
|
||||
|
||||
}
|
||||
321
web/core/modules/node/src/Form/NodeForm.php
Normal file
321
web/core/modules/node/src/Form/NodeForm.php
Normal file
@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Form;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Entity\EntityRepositoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Form handler for the node edit forms.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class NodeForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* The tempstore factory.
|
||||
*
|
||||
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
|
||||
*/
|
||||
protected $tempStoreFactory;
|
||||
|
||||
/**
|
||||
* The Current User object.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* Constructs a NodeForm object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
|
||||
* The entity repository.
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The factory for the temp store object.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
|
||||
* The entity type bundle service.
|
||||
* @param \Drupal\Component\Datetime\TimeInterface $time
|
||||
* The time service.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter service.
|
||||
*/
|
||||
public function __construct(
|
||||
EntityRepositoryInterface $entity_repository,
|
||||
PrivateTempStoreFactory $temp_store_factory,
|
||||
EntityTypeBundleInfoInterface $entity_type_bundle_info,
|
||||
TimeInterface $time,
|
||||
AccountInterface $current_user,
|
||||
DateFormatterInterface $date_formatter,
|
||||
) {
|
||||
parent::__construct($entity_repository, $entity_type_bundle_info, $time);
|
||||
$this->tempStoreFactory = $temp_store_factory;
|
||||
$this->currentUser = $current_user;
|
||||
$this->dateFormatter = $date_formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity.repository'),
|
||||
$container->get('tempstore.private'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('datetime.time'),
|
||||
$container->get('current_user'),
|
||||
$container->get('date.formatter')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
// Try to restore from temp store, this must be done before calling
|
||||
// parent::form().
|
||||
$store = $this->tempStoreFactory->get('node_preview');
|
||||
|
||||
// Because of the temp store integration, this is not cacheable.
|
||||
// @todo add the correct cache contexts in https://www.drupal.org/project/drupal/issues/3397987
|
||||
$form['#cache']['max-age'] = 0;
|
||||
|
||||
// Attempt to load from preview when the uuid is present unless we are
|
||||
// rebuilding the form.
|
||||
$request_uuid = \Drupal::request()->query->get('uuid');
|
||||
if (!$form_state->isRebuilding() && $request_uuid && $preview = $store->get($request_uuid)) {
|
||||
/** @var \Drupal\Core\Form\FormStateInterface $preview */
|
||||
|
||||
$form_state->setStorage($preview->getStorage());
|
||||
$form_state->setUserInput($preview->getUserInput());
|
||||
|
||||
// Rebuild the form.
|
||||
$form_state->setRebuild();
|
||||
|
||||
// The combination of having user input and rebuilding the form means
|
||||
// that it will attempt to cache the form state which will fail if it is
|
||||
// a GET request.
|
||||
$form_state->setRequestMethod('POST');
|
||||
|
||||
$this->entity = $preview->getFormObject()->getEntity();
|
||||
$this->entity->in_preview = NULL;
|
||||
|
||||
$form_state->set('has_been_previewed', TRUE);
|
||||
}
|
||||
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node = $this->entity;
|
||||
|
||||
if ($this->operation == 'edit') {
|
||||
$form['#title'] = $this->t('<em>Edit @type</em> @title', [
|
||||
'@type' => node_get_type_label($node),
|
||||
'@title' => $node->label(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Changed must be sent to the client, for later overwrite error checking.
|
||||
$form['changed'] = [
|
||||
'#type' => 'hidden',
|
||||
'#default_value' => $node->getChangedTime(),
|
||||
];
|
||||
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
$form['advanced']['#attributes']['class'][] = 'entity-meta';
|
||||
|
||||
$form['meta'] = [
|
||||
'#type' => 'details',
|
||||
'#group' => 'advanced',
|
||||
'#weight' => -10,
|
||||
'#title' => $this->t('Status'),
|
||||
'#attributes' => ['class' => ['entity-meta__header']],
|
||||
'#tree' => TRUE,
|
||||
'#access' => $this->currentUser->hasPermission('administer nodes'),
|
||||
];
|
||||
$form['meta']['published'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $node->isPublished() ? $this->t('Published') : $this->t('Not published'),
|
||||
'#access' => !$node->isNew(),
|
||||
'#wrapper_attributes' => ['class' => ['entity-meta__title']],
|
||||
];
|
||||
$form['meta']['changed'] = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Last saved'),
|
||||
'#markup' => !$node->isNew() ? $this->dateFormatter->format($node->getChangedTime(), 'short') : $this->t('Not saved yet'),
|
||||
'#wrapper_attributes' => ['class' => ['entity-meta__last-saved']],
|
||||
];
|
||||
$form['meta']['author'] = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Author'),
|
||||
'#markup' => $node->getOwner()?->getAccountName(),
|
||||
'#wrapper_attributes' => ['class' => ['entity-meta__author']],
|
||||
];
|
||||
|
||||
$form['status']['#group'] = 'footer';
|
||||
|
||||
// Node author information for administrators.
|
||||
$form['author'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Authoring information'),
|
||||
'#group' => 'advanced',
|
||||
'#attributes' => [
|
||||
'class' => ['node-form-author'],
|
||||
],
|
||||
'#attached' => [
|
||||
'library' => ['node/drupal.node'],
|
||||
],
|
||||
'#weight' => 90,
|
||||
'#optional' => TRUE,
|
||||
];
|
||||
|
||||
if (isset($form['uid'])) {
|
||||
$form['uid']['#group'] = 'author';
|
||||
}
|
||||
|
||||
if (isset($form['created'])) {
|
||||
$form['created']['#group'] = 'author';
|
||||
}
|
||||
|
||||
// Node options for administrators.
|
||||
$form['options'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Promotion options'),
|
||||
'#group' => 'advanced',
|
||||
'#attributes' => [
|
||||
'class' => ['node-form-options'],
|
||||
],
|
||||
'#attached' => [
|
||||
'library' => ['node/drupal.node'],
|
||||
],
|
||||
'#weight' => 95,
|
||||
'#optional' => TRUE,
|
||||
];
|
||||
|
||||
if (isset($form['promote'])) {
|
||||
$form['promote']['#group'] = 'options';
|
||||
}
|
||||
|
||||
if (isset($form['sticky'])) {
|
||||
$form['sticky']['#group'] = 'options';
|
||||
}
|
||||
|
||||
$form['#attached']['library'][] = 'node/form';
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$element = parent::actions($form, $form_state);
|
||||
$node = $this->entity;
|
||||
$preview_mode = $node->type->entity->getPreviewMode();
|
||||
|
||||
$element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || $form_state->get('has_been_previewed');
|
||||
|
||||
$element['preview'] = [
|
||||
'#type' => 'submit',
|
||||
'#access' => $preview_mode != DRUPAL_DISABLED && ($node->access('create') || $node->access('update')),
|
||||
'#value' => $this->t('Preview'),
|
||||
'#weight' => 20,
|
||||
'#submit' => ['::submitForm', '::preview'],
|
||||
];
|
||||
|
||||
if (array_key_exists('delete', $element)) {
|
||||
$element['delete']['#weight'] = 100;
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for the 'preview' action.
|
||||
*
|
||||
* @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 preview(array $form, FormStateInterface $form_state) {
|
||||
$store = $this->tempStoreFactory->get('node_preview');
|
||||
$this->entity->in_preview = TRUE;
|
||||
$store->set($this->entity->uuid(), $form_state);
|
||||
|
||||
$route_parameters = [
|
||||
'node_preview' => $this->entity->uuid(),
|
||||
'view_mode_id' => 'full',
|
||||
];
|
||||
|
||||
$options = [];
|
||||
$query = $this->getRequest()->query;
|
||||
if ($query->has('destination')) {
|
||||
$options['query']['destination'] = $query->get('destination');
|
||||
$query->remove('destination');
|
||||
}
|
||||
$form_state->setRedirect('entity.node.preview', $route_parameters, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$node = $this->entity;
|
||||
$insert = $node->isNew();
|
||||
$node->save();
|
||||
$node_link = $node->toLink($this->t('View'))->toString();
|
||||
$context = ['@type' => $node->getType(), '%title' => $node->label(), 'link' => $node_link];
|
||||
$t_args = ['@type' => node_get_type_label($node), '%title' => $node->access('view') ? $node->toLink()->toString() : $node->label()];
|
||||
|
||||
if ($insert) {
|
||||
$this->logger('content')->info('@type: added %title.', $context);
|
||||
$this->messenger()->addStatus($this->t('@type %title has been created.', $t_args));
|
||||
}
|
||||
else {
|
||||
$this->logger('content')->info('@type: updated %title.', $context);
|
||||
$this->messenger()->addStatus($this->t('@type %title has been updated.', $t_args));
|
||||
}
|
||||
|
||||
if ($node->id()) {
|
||||
$form_state->setValue('nid', $node->id());
|
||||
$form_state->set('nid', $node->id());
|
||||
if ($node->access('view')) {
|
||||
$form_state->setRedirect(
|
||||
'entity.node.canonical',
|
||||
['node' => $node->id()],
|
||||
['language' => $node->language()]
|
||||
);
|
||||
}
|
||||
else {
|
||||
$form_state->setRedirect('<front>');
|
||||
}
|
||||
|
||||
// Remove the preview entry from the temp store, if any.
|
||||
$store = $this->tempStoreFactory->get('node_preview');
|
||||
$store->delete($node->uuid());
|
||||
}
|
||||
else {
|
||||
// In the unlikely case something went wrong on save, the node will be
|
||||
// rebuilt and node form redisplayed the same way as in preview.
|
||||
$this->messenger()->addError($this->t('The post could not be saved.'));
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
145
web/core/modules/node/src/Form/NodePreviewForm.php
Normal file
145
web/core/modules/node/src/Form/NodePreviewForm.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Form;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Contains a form for switching the view mode of a node during preview.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class NodePreviewForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The entity display repository.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
|
||||
*/
|
||||
protected $entityDisplayRepository;
|
||||
|
||||
/**
|
||||
* The config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_display.repository'),
|
||||
$container->get('config.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new NodePreviewForm.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
|
||||
* The entity display repository.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
*/
|
||||
public function __construct(EntityDisplayRepositoryInterface $entity_display_repository, ConfigFactoryInterface $config_factory) {
|
||||
$this->entityDisplayRepository = $entity_display_repository;
|
||||
$this->configFactory = $config_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'node_preview_form_select';
|
||||
}
|
||||
|
||||
/**
|
||||
* Form constructor.
|
||||
*
|
||||
* @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\Core\Entity\EntityInterface $node
|
||||
* The node being previews.
|
||||
*
|
||||
* @return array
|
||||
* The form structure.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ?EntityInterface $node = NULL) {
|
||||
$view_mode = $node->preview_view_mode;
|
||||
|
||||
$query_options = ['query' => ['uuid' => $node->uuid()]];
|
||||
$query = $this->getRequest()->query;
|
||||
if ($query->has('destination')) {
|
||||
$query_options['query']['destination'] = $query->get('destination');
|
||||
}
|
||||
|
||||
$form['backlink'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Back to content editing'),
|
||||
'#url' => $node->isNew() ? Url::fromRoute('node.add', ['node_type' => $node->bundle()]) : $node->toUrl('edit-form'),
|
||||
'#options' => ['attributes' => ['class' => ['node-preview-backlink']]] + $query_options,
|
||||
];
|
||||
|
||||
// Always show full as an option, even if the display is not enabled.
|
||||
$view_mode_options = ['full' => $this->t('Full')] + $this->entityDisplayRepository->getViewModeOptionsByBundle('node', $node->bundle());
|
||||
|
||||
// Unset view modes that are not used in the front end.
|
||||
unset($view_mode_options['default']);
|
||||
unset($view_mode_options['rss']);
|
||||
unset($view_mode_options['search_index']);
|
||||
|
||||
$form['uuid'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $node->uuid(),
|
||||
];
|
||||
|
||||
$form['view_mode'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('View mode'),
|
||||
'#options' => $view_mode_options,
|
||||
'#default_value' => $view_mode,
|
||||
'#attributes' => [
|
||||
'data-drupal-autosubmit' => TRUE,
|
||||
],
|
||||
];
|
||||
|
||||
$form['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Switch'),
|
||||
'#attributes' => [
|
||||
'class' => ['js-hide'],
|
||||
],
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$route_parameters = [
|
||||
'node_preview' => $form_state->getValue('uuid'),
|
||||
'view_mode_id' => $form_state->getValue('view_mode'),
|
||||
];
|
||||
|
||||
$options = [];
|
||||
$query = $this->getRequest()->query;
|
||||
if ($query->has('destination')) {
|
||||
$options['query']['destination'] = $query->get('destination');
|
||||
$query->remove('destination');
|
||||
}
|
||||
$form_state->setRedirect('entity.node.preview', $route_parameters, $options);
|
||||
}
|
||||
|
||||
}
|
||||
162
web/core/modules/node/src/Form/NodeRevisionDeleteForm.php
Normal file
162
web/core/modules/node/src/Form/NodeRevisionDeleteForm.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Form;
|
||||
|
||||
use Drupal\Core\Access\AccessManagerInterface;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a form for deleting a node revision.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class NodeRevisionDeleteForm extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The node revision.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $revision;
|
||||
|
||||
/**
|
||||
* The node storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $nodeStorage;
|
||||
|
||||
/**
|
||||
* The node type storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $nodeTypeStorage;
|
||||
|
||||
/**
|
||||
* The access manager.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessManagerInterface
|
||||
*/
|
||||
protected $accessManager;
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* Constructs a new NodeRevisionDeleteForm.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
|
||||
* The node storage.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $node_type_storage
|
||||
* The node type storage.
|
||||
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
|
||||
* The access manager.
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter service.
|
||||
*/
|
||||
public function __construct(EntityStorageInterface $node_storage, EntityStorageInterface $node_type_storage, AccessManagerInterface $access_manager, DateFormatterInterface $date_formatter) {
|
||||
$this->nodeStorage = $node_storage;
|
||||
$this->nodeTypeStorage = $node_type_storage;
|
||||
$this->accessManager = $access_manager;
|
||||
$this->dateFormatter = $date_formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
$entity_type_manager = $container->get('entity_type.manager');
|
||||
return new static(
|
||||
$entity_type_manager->getStorage('node'),
|
||||
$entity_type_manager->getStorage('node_type'),
|
||||
$container->get('access_manager'),
|
||||
$container->get('date.formatter')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'node_revision_delete_confirm';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete the revision from %revision-date?', [
|
||||
'%revision-date' => $this->dateFormatter->format($this->revision->getRevisionCreationTime()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.node.version_history', ['node' => $this->revision->id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ?NodeInterface $node_revision = NULL) {
|
||||
$this->revision = $node_revision;
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->nodeStorage;
|
||||
$storage->deleteRevision($this->revision->getRevisionId());
|
||||
|
||||
$this->logger('content')->info('@type: deleted %title revision %revision.', ['@type' => $this->revision->bundle(), '%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()]);
|
||||
$node_type = $this->nodeTypeStorage->load($this->revision->bundle())->label();
|
||||
$this->messenger()
|
||||
->addStatus($this->t('Revision from %revision-date of @type %title has been deleted.', [
|
||||
'%revision-date' => $this->dateFormatter->format($this->revision->getRevisionCreationTime()),
|
||||
'@type' => $node_type,
|
||||
'%title' => $this->revision->label(),
|
||||
]));
|
||||
// Set redirect to the revisions history page.
|
||||
$route_name = 'entity.node.version_history';
|
||||
$parameters = ['node' => $this->revision->id()];
|
||||
// If no revisions found, or the user does not have access to the revisions
|
||||
// page, then redirect to the canonical node page instead.
|
||||
if (!$this->accessManager->checkNamedRoute($route_name, $parameters) || count($this->nodeStorage->revisionIds($this->revision)) === 1) {
|
||||
$route_name = 'entity.node.canonical';
|
||||
}
|
||||
$form_state->setRedirect($route_name, $parameters);
|
||||
}
|
||||
|
||||
}
|
||||
167
web/core/modules/node/src/Form/NodeRevisionRevertForm.php
Normal file
167
web/core/modules/node/src/Form/NodeRevisionRevertForm.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Form;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a form for reverting a node revision.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class NodeRevisionRevertForm extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The node revision.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $revision;
|
||||
|
||||
/**
|
||||
* The node storage.
|
||||
*
|
||||
* @var \Drupal\node\NodeStorageInterface
|
||||
*/
|
||||
protected $nodeStorage;
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* The time service.
|
||||
*
|
||||
* @var \Drupal\Component\Datetime\TimeInterface
|
||||
*/
|
||||
protected $time;
|
||||
|
||||
/**
|
||||
* Constructs a new NodeRevisionRevertForm.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
|
||||
* The node storage.
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter service.
|
||||
* @param \Drupal\Component\Datetime\TimeInterface $time
|
||||
* The time service.
|
||||
*/
|
||||
public function __construct(EntityStorageInterface $node_storage, DateFormatterInterface $date_formatter, TimeInterface $time) {
|
||||
$this->nodeStorage = $node_storage;
|
||||
$this->dateFormatter = $date_formatter;
|
||||
$this->time = $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')->getStorage('node'),
|
||||
$container->get('date.formatter'),
|
||||
$container->get('datetime.time')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'node_revision_revert_confirm';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to revert to the revision from %revision-date?', ['%revision-date' => $this->dateFormatter->format($this->revision->getRevisionCreationTime())]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.node.version_history', ['node' => $this->revision->id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Revert');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ?NodeInterface $node_revision = NULL) {
|
||||
$this->revision = $node_revision;
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// The revision timestamp will be updated when the revision is saved. Keep
|
||||
// the original one for the confirmation message.
|
||||
$original_revision_timestamp = $this->revision->getRevisionCreationTime();
|
||||
|
||||
$this->revision = $this->prepareRevertedRevision($this->revision, $form_state);
|
||||
$this->revision->revision_log = $this->t('Copy of the revision from %date.', ['%date' => $this->dateFormatter->format($original_revision_timestamp)]);
|
||||
$this->revision->setRevisionUserId($this->currentUser()->id());
|
||||
$this->revision->setRevisionCreationTime($this->time->getRequestTime());
|
||||
$this->revision->setChangedTime($this->time->getRequestTime());
|
||||
$this->revision->save();
|
||||
|
||||
$this->logger('content')->info('@type: reverted %title revision %revision.', ['@type' => $this->revision->bundle(), '%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()]);
|
||||
$this->messenger()
|
||||
->addStatus($this->t('@type %title has been reverted to the revision from %revision-date.', [
|
||||
'@type' => node_get_type_label($this->revision),
|
||||
'%title' => $this->revision->label(),
|
||||
'%revision-date' => $this->dateFormatter->format($original_revision_timestamp),
|
||||
]));
|
||||
$form_state->setRedirect(
|
||||
'entity.node.version_history',
|
||||
['node' => $this->revision->id()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a revision to be reverted.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $revision
|
||||
* The revision to be reverted.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface
|
||||
* The prepared revision ready to be stored.
|
||||
*/
|
||||
protected function prepareRevertedRevision(NodeInterface $revision, FormStateInterface $form_state) {
|
||||
$revision->setNewRevision();
|
||||
$revision->isDefaultRevision(TRUE);
|
||||
|
||||
return $revision;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Form;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a form for reverting a node revision for a single translation.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class NodeRevisionRevertTranslationForm extends NodeRevisionRevertForm {
|
||||
|
||||
/**
|
||||
* The language to be reverted.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $langcode;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* Constructs a new NodeRevisionRevertTranslationForm.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
|
||||
* The node storage.
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter service.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Component\Datetime\TimeInterface $time
|
||||
* The time service.
|
||||
*/
|
||||
public function __construct(EntityStorageInterface $node_storage, DateFormatterInterface $date_formatter, LanguageManagerInterface $language_manager, TimeInterface $time) {
|
||||
parent::__construct($node_storage, $date_formatter, $time);
|
||||
$this->languageManager = $language_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')->getStorage('node'),
|
||||
$container->get('date.formatter'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('datetime.time')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'node_revision_revert_translation_confirm';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to revert @language translation to the revision from %revision-date?', ['@language' => $this->languageManager->getLanguageName($this->langcode), '%revision-date' => $this->dateFormatter->format($this->revision->getRevisionCreationTime())]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $node_revision = NULL, $langcode = NULL) {
|
||||
$this->langcode = $langcode;
|
||||
$form = parent::buildForm($form, $form_state, $node_revision);
|
||||
|
||||
// Unless untranslatable fields are configured to affect only the default
|
||||
// translation, we need to ask the user whether they should be included in
|
||||
// the revert process.
|
||||
$default_translation_affected = $this->revision->isDefaultTranslationAffectedOnly();
|
||||
$form['revert_untranslated_fields'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Revert content shared among translations'),
|
||||
'#default_value' => $default_translation_affected && $this->revision->getTranslation($this->langcode)->isDefaultTranslation(),
|
||||
'#access' => !$default_translation_affected,
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareRevertedRevision(NodeInterface $revision, FormStateInterface $form_state) {
|
||||
$revert_untranslated_fields = (bool) $form_state->getValue('revert_untranslated_fields');
|
||||
$translation = $revision->getTranslation($this->langcode);
|
||||
return $this->nodeStorage->createRevision($translation, TRUE, $revert_untranslated_fields);
|
||||
}
|
||||
|
||||
}
|
||||
34
web/core/modules/node/src/Form/NodeTypeDeleteConfirm.php
Normal file
34
web/core/modules/node/src/Form/NodeTypeDeleteConfirm.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityDeleteForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a form for content type deletion.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class NodeTypeDeleteConfirm extends EntityDeleteForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$num_nodes = $this->entityTypeManager->getStorage('node')->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('type', $this->entity->id())
|
||||
->count()
|
||||
->execute();
|
||||
if ($num_nodes) {
|
||||
$caption = '<p>' . $this->formatPlural($num_nodes, '%type is used by 1 piece of content on your site. You can not remove this content type until you have removed all of the %type content.', '%type is used by @count pieces of content on your site. You may not remove %type until you have removed all of the %type content.', ['%type' => $this->entity->label()]) . '</p>';
|
||||
$form['#title'] = $this->getQuestion();
|
||||
$form['description'] = ['#markup' => $caption];
|
||||
return $form;
|
||||
}
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
268
web/core/modules/node/src/Form/NodeTypeForm.php
Normal file
268
web/core/modules/node/src/Form/NodeTypeForm.php
Normal file
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Form;
|
||||
|
||||
use Drupal\Core\Entity\BundleEntityFormBase;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\language\Entity\ContentLanguageSettings;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Form handler for node type forms.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class NodeTypeForm extends BundleEntityFormBase {
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* Constructs the NodeTypeForm object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
|
||||
* The entity field manager.
|
||||
*/
|
||||
public function __construct(EntityFieldManagerInterface $entity_field_manager) {
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_field.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
$type = $this->entity;
|
||||
if ($this->operation == 'add') {
|
||||
$form['#title'] = $this->t('Add content type');
|
||||
$fields = $this->entityFieldManager->getBaseFieldDefinitions('node');
|
||||
// Create a node with a fake bundle using the type's UUID so that we can
|
||||
// get the default values for workflow settings.
|
||||
// @todo Make it possible to get default values without an entity.
|
||||
// https://www.drupal.org/node/2318187
|
||||
$node = $this->entityTypeManager->getStorage('node')->create(['type' => $type->uuid()]);
|
||||
}
|
||||
else {
|
||||
$form['#title'] = $this->t('Edit %label content type', ['%label' => $type->label()]);
|
||||
$fields = $this->entityFieldManager->getFieldDefinitions('node', $type->id());
|
||||
// Create a node to get the current values for workflow settings fields.
|
||||
$node = $this->entityTypeManager->getStorage('node')->create(['type' => $type->id()]);
|
||||
}
|
||||
|
||||
$form['name'] = [
|
||||
'#title' => $this->t('Name'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $type->label(),
|
||||
'#description' => $this->t('The human-readable name for this content type, displayed on the <em>Content types</em> page.'),
|
||||
'#required' => TRUE,
|
||||
'#size' => 30,
|
||||
];
|
||||
|
||||
$form['type'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#default_value' => $type->id(),
|
||||
'#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
|
||||
'#disabled' => $type->isLocked(),
|
||||
'#machine_name' => [
|
||||
'exists' => ['Drupal\node\Entity\NodeType', 'load'],
|
||||
'source' => ['name'],
|
||||
],
|
||||
'#description' => $this->t('Unique machine-readable name: lowercase letters, numbers, and underscores only.', [
|
||||
'%node-add' => $this->t('Add content'),
|
||||
]),
|
||||
];
|
||||
|
||||
$form['description'] = [
|
||||
'#title' => $this->t('Description'),
|
||||
'#type' => 'textarea',
|
||||
'#default_value' => $type->getDescription(),
|
||||
'#description' => $this->t('Displays on the <em>Content types</em> page.'),
|
||||
];
|
||||
|
||||
$form['additional_settings'] = [
|
||||
'#type' => 'vertical_tabs',
|
||||
'#attached' => [
|
||||
'library' => ['node/drupal.content_types'],
|
||||
],
|
||||
];
|
||||
|
||||
$form['submission'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Submission form settings'),
|
||||
'#group' => 'additional_settings',
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['submission']['title_label'] = [
|
||||
'#title' => $this->t('Title field label'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $fields['title']->getLabel(),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form['submission']['preview_mode'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Preview before submitting'),
|
||||
'#default_value' => $type->getPreviewMode(),
|
||||
'#options' => [
|
||||
DRUPAL_DISABLED => $this->t('Disabled'),
|
||||
DRUPAL_OPTIONAL => $this->t('Optional'),
|
||||
DRUPAL_REQUIRED => $this->t('Required'),
|
||||
],
|
||||
];
|
||||
$form['submission']['help'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Explanation or submission guidelines'),
|
||||
'#default_value' => $type->getHelp(),
|
||||
'#description' => $this->t('This text will be displayed at the top of the page when creating or editing content of this type.'),
|
||||
];
|
||||
$form['workflow'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Publishing options'),
|
||||
'#group' => 'additional_settings',
|
||||
];
|
||||
$workflow_options = [
|
||||
'status' => $node->status->value,
|
||||
'promote' => $node->promote->value,
|
||||
'sticky' => $node->sticky->value,
|
||||
'revision' => $type->shouldCreateNewRevision(),
|
||||
];
|
||||
// Prepare workflow options to be used for 'checkboxes' form element.
|
||||
$keys = array_keys(array_filter($workflow_options));
|
||||
$workflow_options = array_combine($keys, $keys);
|
||||
$form['workflow']['options'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Default options'),
|
||||
'#default_value' => $workflow_options,
|
||||
'#options' => [
|
||||
'status' => $this->t('Published'),
|
||||
'promote' => $this->t('Promoted to front page'),
|
||||
'sticky' => $this->t('Sticky at top of lists'),
|
||||
'revision' => $this->t('Create new revision'),
|
||||
],
|
||||
'#description' => $this->t('Users with sufficient access rights will be able to override these options.'),
|
||||
];
|
||||
if ($this->moduleHandler->moduleExists('language')) {
|
||||
$form['language'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Language settings'),
|
||||
'#group' => 'additional_settings',
|
||||
];
|
||||
|
||||
$language_configuration = ContentLanguageSettings::loadByEntityTypeBundle('node', $type->id());
|
||||
$form['language']['language_configuration'] = [
|
||||
'#type' => 'language_configuration',
|
||||
'#entity_information' => [
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $type->id(),
|
||||
],
|
||||
'#default_value' => $language_configuration,
|
||||
];
|
||||
}
|
||||
$form['display'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Display settings'),
|
||||
'#group' => 'additional_settings',
|
||||
];
|
||||
$form['display']['display_submitted'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Display author and date information'),
|
||||
'#default_value' => $type->displaySubmitted(),
|
||||
'#description' => $this->t('Author username and publish date will be displayed.'),
|
||||
];
|
||||
|
||||
return $this->protectBundleIdElement($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateForm($form, $form_state);
|
||||
|
||||
$id = trim($form_state->getValue('type'));
|
||||
// '0' is invalid, since elsewhere we check it using empty().
|
||||
if ($id == '0') {
|
||||
$form_state->setErrorByName('type', $this->t("Invalid machine-readable name. Enter a name other than %invalid.", ['%invalid' => $id]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildEntity(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\node\NodeTypeInterface $entity */
|
||||
$entity = parent::buildEntity($form, $form_state);
|
||||
|
||||
// The description and help text cannot be empty strings.
|
||||
if (trim($form_state->getValue('description')) === '') {
|
||||
$entity->set('description', NULL);
|
||||
}
|
||||
if (trim($form_state->getValue('help')) === '') {
|
||||
$entity->set('help', NULL);
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$type = $this->entity;
|
||||
$type->setNewRevision($form_state->getValue(['options', 'revision']));
|
||||
$type->set('type', trim($type->id()));
|
||||
$type->set('name', trim($type->label()));
|
||||
|
||||
$status = $type->save();
|
||||
|
||||
$t_args = ['%name' => $type->label()];
|
||||
|
||||
if ($status == SAVED_UPDATED) {
|
||||
$this->messenger()->addStatus($this->t('The content type %name has been updated.', $t_args));
|
||||
}
|
||||
elseif ($status == SAVED_NEW) {
|
||||
if (\Drupal::installProfile() === 'testing') {
|
||||
node_add_body_field($type);
|
||||
}
|
||||
$this->messenger()->addStatus($this->t('The content type %name has been added.', $t_args));
|
||||
$context = array_merge($t_args, ['link' => $type->toLink($this->t('View'), 'collection')->toString()]);
|
||||
$this->logger('node')->notice('Added content type %name.', $context);
|
||||
}
|
||||
|
||||
$fields = $this->entityFieldManager->getFieldDefinitions('node', $type->id());
|
||||
// Update title field definition.
|
||||
$title_field = $fields['title'];
|
||||
$title_label = $form_state->getValue('title_label');
|
||||
if ($title_field->getLabel() != $title_label) {
|
||||
$title_field->getConfig($type->id())->setLabel($title_label)->save();
|
||||
}
|
||||
// Update workflow options.
|
||||
// @todo Make it possible to get default values without an entity.
|
||||
// https://www.drupal.org/node/2318187
|
||||
$node = $this->entityTypeManager->getStorage('node')->create(['type' => $type->id()]);
|
||||
foreach (['status', 'promote', 'sticky'] as $field_name) {
|
||||
$value = (bool) $form_state->getValue(['options', $field_name]);
|
||||
if ($node->$field_name->value != $value) {
|
||||
$fields[$field_name]->getConfig($type->id())->setDefaultValue($value)->save();
|
||||
}
|
||||
}
|
||||
|
||||
$this->entityFieldManager->clearCachedFieldDefinitions();
|
||||
$form_state->setRedirectUrl($type->toUrl('collection'));
|
||||
}
|
||||
|
||||
}
|
||||
59
web/core/modules/node/src/Form/RebuildPermissionsForm.php
Normal file
59
web/core/modules/node/src/Form/RebuildPermissionsForm.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Form for rebuilding permissions.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RebuildPermissionsForm extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'node_configure_rebuild_confirm';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to rebuild the permissions on site content?');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('system.status');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Rebuild permissions');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->t('This action rebuilds all permissions on site content, and may be a lengthy process. This action cannot be undone.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
node_access_rebuild(TRUE);
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
78
web/core/modules/node/src/Hook/NodeHooks.php
Normal file
78
web/core/modules/node/src/Hook/NodeHooks.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\node\Hook;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\node\NodeStorageInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Hook implementations for the node module.
|
||||
*/
|
||||
class NodeHooks {
|
||||
|
||||
/**
|
||||
* The Node Storage.
|
||||
*
|
||||
* @var \Drupal\node\NodeStorageInterface
|
||||
*/
|
||||
protected NodeStorageInterface $nodeStorage;
|
||||
|
||||
/**
|
||||
* NodeHooks constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct(
|
||||
EntityTypeManagerInterface $entityTypeManager,
|
||||
protected ModuleHandlerInterface $moduleHandler,
|
||||
) {
|
||||
$this->nodeStorage = $entityTypeManager->getStorage('node');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_user_cancel().
|
||||
*
|
||||
* Unpublish nodes (current revisions).
|
||||
*/
|
||||
#[Hook('user_cancel')]
|
||||
public function userCancelBlockUnpublish($edit, UserInterface $account, $method): void {
|
||||
if ($method === 'user_cancel_block_unpublish') {
|
||||
$nids = $this->nodeStorage->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('uid', $account->id())
|
||||
->execute();
|
||||
$this->moduleHandler->invoke('node', 'mass_update', [$nids, ['status' => 0], NULL, TRUE]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_user_cancel().
|
||||
*
|
||||
* Anonymize all of the nodes for this old account.
|
||||
*/
|
||||
#[Hook('user_cancel')]
|
||||
public function userCancelReassign($edit, UserInterface $account, $method): void {
|
||||
if ($method === 'user_cancel_reassign') {
|
||||
$vids = $this->nodeStorage->userRevisionIds($account);
|
||||
$this->moduleHandler->invoke('node', 'mass_update', [$vids, ['uid' => 0, 'revision_uid' => 0], NULL, TRUE, TRUE]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_alter().
|
||||
*/
|
||||
#[Hook('block_alter')]
|
||||
public function blockAlter(&$definitions): void {
|
||||
// Hide the deprecated Syndicate block from the UI.
|
||||
$definitions['node_syndicate_block']['_block_ui_hidden'] = TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
561
web/core/modules/node/src/Hook/NodeHooks1.php
Normal file
561
web/core/modules/node/src/Hook/NodeHooks1.php
Normal file
@ -0,0 +1,561 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Hook;
|
||||
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\language\ConfigurableLanguageInterface;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Database\Query\AlterableInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\node\Form\NodePreviewForm;
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for node.
|
||||
*/
|
||||
class NodeHooks1 {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
#[Hook('help')]
|
||||
public function help($route_name, RouteMatchInterface $route_match): ?string {
|
||||
// Remind site administrators about the {node_access} table being flagged
|
||||
// for rebuild. We don't need to issue the message on the confirm form, or
|
||||
// while the rebuild is being processed.
|
||||
if ($route_name != 'node.configure_rebuild_confirm' && $route_name != 'system.batch_page.html' && $route_name != 'help.page.node' && $route_name != 'help.main' && \Drupal::currentUser()->hasPermission('administer nodes') && node_access_needs_rebuild()) {
|
||||
if ($route_name == 'system.status') {
|
||||
$message = $this->t('The content access permissions need to be rebuilt.');
|
||||
}
|
||||
else {
|
||||
$message = $this->t('The content access permissions need to be rebuilt. <a href=":node_access_rebuild">Rebuild permissions</a>.', [
|
||||
':node_access_rebuild' => Url::fromRoute('node.configure_rebuild_confirm')->toString(),
|
||||
]);
|
||||
}
|
||||
\Drupal::messenger()->addError($message);
|
||||
}
|
||||
switch ($route_name) {
|
||||
case 'help.page.node':
|
||||
$output = '';
|
||||
$output .= '<h2>' . $this->t('About') . '</h2>';
|
||||
$output .= '<p>' . $this->t('The Node module manages the creation, editing, deletion, settings, and display of the main site content. Content items managed by the Node module are typically displayed as pages on your site, and include a title, some meta-data (author, creation time, content type, etc.), and optional fields containing text or other data (fields are managed by the <a href=":field">Field module</a>). For more information, see the <a href=":node">online documentation for the Node module</a>.', [
|
||||
':node' => 'https://www.drupal.org/docs/core-modules-and-themes/core-modules/node-module',
|
||||
':field' => Url::fromRoute('help.page', [
|
||||
'name' => 'field',
|
||||
])->toString(),
|
||||
]) . '</p>';
|
||||
$output .= '<h2>' . $this->t('Uses') . '</h2>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . $this->t('Creating content') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('When new content is created, the Node module records basic information about the content, including the author, date of creation, and the <a href=":content-type">Content type</a>. It also manages the <em>publishing options</em>, which define whether or not the content is published, promoted to the front page of the site, and/or sticky at the top of content lists. Default settings can be configured for each <a href=":content-type">type of content</a> on your site.', [
|
||||
':content-type' => Url::fromRoute('entity.node_type.collection')->toString(),
|
||||
]) . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Creating custom content types') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('The Node module gives users with the <em>Administer content types</em> permission the ability to <a href=":content-new">create new content types</a> in addition to the default ones already configured. Creating custom content types gives you the flexibility to add <a href=":field">fields</a> and configure default settings that suit the differing needs of various site content.', [
|
||||
':content-new' => Url::fromRoute('node.type_add')->toString(),
|
||||
':field' => Url::fromRoute('help.page', [
|
||||
'name' => 'field',
|
||||
])->toString(),
|
||||
]) . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Administering content') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('The <a href=":content">Content</a> page lists your content, allowing you add new content, filter, edit or delete existing content, or perform bulk operations on existing content.', [':content' => Url::fromRoute('system.admin_content')->toString()]) . '</dd>';
|
||||
$output .= '<dt>' . $this->t('Creating revisions') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('The Node module also enables you to create multiple versions of any content, and revert to older versions using the <em>Revision information</em> settings.') . '</dd>';
|
||||
$output .= '<dt>' . $this->t('User permissions') . '</dt>';
|
||||
$output .= '<dd>' . $this->t('The Node module makes a number of permissions available for each content type, which can be set by role on the <a href=":permissions">permissions page</a>.', [
|
||||
':permissions' => Url::fromRoute('user.admin_permissions.module', [
|
||||
'modules' => 'node',
|
||||
])->toString(),
|
||||
]) . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
|
||||
case 'node.type_add':
|
||||
return '<p>' . $this->t('Individual content types can have different fields, behaviors, and permissions assigned to them.') . '</p>';
|
||||
|
||||
case 'entity.entity_form_display.node.default':
|
||||
case 'entity.entity_form_display.node.form_mode':
|
||||
$type = $route_match->getParameter('node_type');
|
||||
return '<p>' . $this->t('Content items can be edited using different form modes. Here, you can define which fields are shown and hidden when %type content is edited in each form mode, and define how the field form widgets are displayed in each form mode.', ['%type' => $type->label()]) . '</p>';
|
||||
|
||||
case 'entity.entity_view_display.node.default':
|
||||
case 'entity.entity_view_display.node.view_mode':
|
||||
$type = $route_match->getParameter('node_type');
|
||||
return '<p>' . $this->t('Content items can be displayed using different view modes: Teaser, Full content, Print, RSS, etc. <em>Teaser</em> is a short format that is typically used in lists of multiple content items. <em>Full content</em> is typically used when the content is displayed on its own page.') . '</p><p>' . $this->t('Here, you can define which fields are shown and hidden when %type content is displayed in each view mode, and define how the fields are displayed in each view mode.', ['%type' => $type->label()]) . '</p>';
|
||||
|
||||
case 'entity.node.version_history':
|
||||
return '<p>' . $this->t('Revisions allow you to track differences between multiple versions of your content, and revert to older versions.') . '</p>';
|
||||
|
||||
case 'entity.node.edit_form':
|
||||
$node = $route_match->getParameter('node');
|
||||
$type = NodeType::load($node->getType());
|
||||
$help = $type->getHelp();
|
||||
return !empty($help) ? Xss::filterAdmin($help) : '';
|
||||
|
||||
case 'node.add':
|
||||
$type = $route_match->getParameter('node_type');
|
||||
$help = $type->getHelp();
|
||||
return !empty($help) ? Xss::filterAdmin($help) : '';
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
#[Hook('theme')]
|
||||
public function theme() : array {
|
||||
return [
|
||||
'node' => [
|
||||
'render element' => 'elements',
|
||||
],
|
||||
'node_add_list' => [
|
||||
'variables' => [
|
||||
'content' => NULL,
|
||||
],
|
||||
],
|
||||
'node_edit_form' => [
|
||||
'render element' => 'form',
|
||||
],
|
||||
// @todo Delete the next three entries as part of
|
||||
// https://www.drupal.org/node/3015623
|
||||
'field__node__title' => [
|
||||
'base hook' => 'field',
|
||||
],
|
||||
'field__node__uid' => [
|
||||
'base hook' => 'field',
|
||||
],
|
||||
'field__node__created' => [
|
||||
'base hook' => 'field',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_view_display_alter().
|
||||
*/
|
||||
#[Hook('entity_view_display_alter')]
|
||||
public function entityViewDisplayAlter(EntityViewDisplayInterface $display, $context): void {
|
||||
if ($context['entity_type'] == 'node') {
|
||||
// Hide field labels in search index.
|
||||
if ($context['view_mode'] == 'search_index') {
|
||||
foreach ($display->getComponents() as $name => $options) {
|
||||
if (isset($options['label'])) {
|
||||
$options['label'] = 'hidden';
|
||||
$display->setComponent($name, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_local_tasks_alter().
|
||||
*/
|
||||
#[Hook('local_tasks_alter')]
|
||||
public function localTasksAlter(&$local_tasks) : void {
|
||||
// Removes 'Revisions' local task added by deriver. Local task
|
||||
// 'entity.node.version_history' will be replaced by
|
||||
// 'entity.version_history:node.version_history' after
|
||||
// https://www.drupal.org/project/drupal/issues/3153559.
|
||||
unset($local_tasks['entity.version_history:node.version_history']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_extra_field_info().
|
||||
*/
|
||||
#[Hook('entity_extra_field_info')]
|
||||
public function entityExtraFieldInfo(): array {
|
||||
$extra = [];
|
||||
$description = $this->t('Node module element');
|
||||
foreach (NodeType::loadMultiple() as $bundle) {
|
||||
$extra['node'][$bundle->id()]['display']['links'] = [
|
||||
'label' => $this->t('Links'),
|
||||
'description' => $description,
|
||||
'weight' => 100,
|
||||
'visible' => TRUE,
|
||||
];
|
||||
}
|
||||
return $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_cron().
|
||||
*/
|
||||
#[Hook('cron')]
|
||||
public function cron(): void {
|
||||
// Calculate the oldest and newest node created times, for use in search
|
||||
// rankings. (Note that field aliases have to be variables passed by
|
||||
// reference.)
|
||||
if (\Drupal::moduleHandler()->moduleExists('search')) {
|
||||
$min_alias = 'min_created';
|
||||
$max_alias = 'max_created';
|
||||
$result = \Drupal::entityQueryAggregate('node')->accessCheck(FALSE)->aggregate('created', 'MIN', NULL, $min_alias)->aggregate('created', 'MAX', NULL, $max_alias)->execute();
|
||||
if (isset($result[0])) {
|
||||
// Make an array with definite keys and store it in the state system.
|
||||
$array = ['min_created' => $result[0][$min_alias], 'max_created' => $result[0][$max_alias]];
|
||||
\Drupal::state()->set('node.min_max_update_time', $array);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ranking().
|
||||
*/
|
||||
#[Hook('ranking')]
|
||||
public function ranking(): array {
|
||||
// Create the ranking array and add the basic ranking options.
|
||||
$ranking = [
|
||||
'relevance' => [
|
||||
'title' => $this->t('Keyword relevance'),
|
||||
// Average relevance values hover around 0.15
|
||||
'score' => 'i.relevance',
|
||||
],
|
||||
'sticky' => [
|
||||
'title' => $this->t('Content is sticky at top of lists'),
|
||||
// The sticky flag is either 0 or 1, which is automatically normalized.
|
||||
'score' => 'n.sticky',
|
||||
],
|
||||
'promote' => [
|
||||
'title' => $this->t('Content is promoted to the front page'),
|
||||
// The promote flag is either 0 or 1, which is automatically normalized.
|
||||
'score' => 'n.promote',
|
||||
],
|
||||
];
|
||||
// Add relevance based on updated date, but only if it the scale values have
|
||||
// been calculated in node_cron().
|
||||
if ($node_min_max = \Drupal::state()->get('node.min_max_update_time')) {
|
||||
$ranking['recent'] = [
|
||||
'title' => $this->t('Recently created'),
|
||||
// Exponential decay with half life of 14% of the age range of nodes.
|
||||
'score' => 'EXP(-5 * (1 - (n.created - :node_oldest) / :node_range))',
|
||||
'arguments' => [
|
||||
':node_oldest' => $node_min_max['min_created'],
|
||||
':node_range' => max($node_min_max['max_created'] - $node_min_max['min_created'], 1),
|
||||
],
|
||||
];
|
||||
}
|
||||
return $ranking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_predelete() for user entities.
|
||||
*/
|
||||
#[Hook('user_predelete')]
|
||||
public function userPredelete($account): void {
|
||||
// Delete nodes (current revisions).
|
||||
// @todo Introduce node_mass_delete() or make node_mass_update() more flexible.
|
||||
$nids = \Drupal::entityQuery('node')->condition('uid', $account->id())->accessCheck(FALSE)->execute();
|
||||
// Delete old revisions.
|
||||
$storage_controller = \Drupal::entityTypeManager()->getStorage('node');
|
||||
$nodes = $storage_controller->loadMultiple($nids);
|
||||
$storage_controller->delete($nodes);
|
||||
$revisions = $storage_controller->userRevisionIds($account);
|
||||
foreach ($revisions as $revision) {
|
||||
$storage_controller->deleteRevision($revision);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_page_top().
|
||||
*/
|
||||
#[Hook('page_top')]
|
||||
public function pageTop(array &$page_top): void {
|
||||
// Add 'Back to content editing' link on preview page.
|
||||
$route_match = \Drupal::routeMatch();
|
||||
if ($route_match->getRouteName() == 'entity.node.preview') {
|
||||
$page_top['node_preview'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'node-preview-container',
|
||||
'container-inline',
|
||||
],
|
||||
],
|
||||
'view_mode' => \Drupal::formBuilder()->getForm(NodePreviewForm::class, $route_match->getParameter('node_preview')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*
|
||||
* Alters the theme form to use the admin theme on node editing.
|
||||
*
|
||||
* @see node_form_system_themes_admin_form_submit()
|
||||
*/
|
||||
#[Hook('form_system_themes_admin_form_alter')]
|
||||
public function formSystemThemesAdminFormAlter(&$form, FormStateInterface $form_state, $form_id) : void {
|
||||
$form['admin_theme']['use_admin_theme'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Use the administration theme when editing or creating content'),
|
||||
'#description' => $this->t('Control which roles can "View the administration theme" on the <a href=":permissions">Permissions page</a>.', [
|
||||
':permissions' => Url::fromRoute('user.admin_permissions.module', [
|
||||
'modules' => 'system',
|
||||
])->toString(),
|
||||
]),
|
||||
'#default_value' => \Drupal::configFactory()->getEditable('node.settings')->get('use_admin_theme'),
|
||||
];
|
||||
$form['#submit'][] = 'node_form_system_themes_admin_form_submit';
|
||||
}
|
||||
|
||||
/**
|
||||
* @defgroup node_access Node access rights
|
||||
* @{
|
||||
* The node access system determines who can do what to which nodes.
|
||||
*
|
||||
* In determining access rights for an existing node,
|
||||
* \Drupal\node\NodeAccessControlHandler first checks whether the user has the
|
||||
* "bypass node access" permission. Such users have unrestricted access to all
|
||||
* nodes. user 1 will always pass this check.
|
||||
*
|
||||
* Next, all implementations of hook_ENTITY_TYPE_access() for node will be
|
||||
* called. Each implementation may explicitly allow, explicitly forbid, or
|
||||
* ignore the access request. If at least one module says to forbid the
|
||||
* request, it will be rejected. If no modules deny the request and at least
|
||||
* one says to allow it, the request will be permitted.
|
||||
*
|
||||
* If all modules ignore the access request, then the node_access table is
|
||||
* used to determine access. All node access modules are queried using
|
||||
* hook_node_grants() to assemble a list of "grant IDs" for the user. This
|
||||
* list is compared against the table. If any row contains the node ID in
|
||||
* question (or 0, which stands for "all nodes"), one of the grant IDs
|
||||
* returned, and a value of TRUE for the operation in question, then access is
|
||||
* granted. Note that this table is a list of grants; any matching row is
|
||||
* sufficient to grant access to the node.
|
||||
*
|
||||
* In node listings (lists of nodes generated from a select query, such as the
|
||||
* default home page at path 'node', an RSS feed, a recent content block,
|
||||
* etc.), the process above is followed except that hook_ENTITY_TYPE_access()
|
||||
* is not called on each node for performance reasons and for proper
|
||||
* functioning of the pager system. When adding a node listing to your module,
|
||||
* be sure to use an entity query, which will add a tag of "node_access". This
|
||||
* will allow modules dealing with node access to ensure only nodes to which
|
||||
* the user has access are retrieved, through the use of
|
||||
* hook_query_TAG_alter(). See the @link entity_api Entity API topic @endlink
|
||||
* for more information on entity queries. Tagging a query with "node_access"
|
||||
* does not check the published/unpublished status of nodes, so the base query
|
||||
* is responsible for ensuring that unpublished nodes are not displayed to
|
||||
* inappropriate users.
|
||||
*
|
||||
* Note: Even a single module returning an AccessResultInterface object from
|
||||
* hook_ENTITY_TYPE_access() whose isForbidden() method equals TRUE will block
|
||||
* access to the node. Therefore, implementers should take care to not deny
|
||||
* access unless they really intend to. Unless a module wishes to actively
|
||||
* forbid access it should return an AccessResultInterface object whose
|
||||
* isAllowed() nor isForbidden() methods return TRUE, to allow other modules
|
||||
* or the node_access table to control access.
|
||||
*
|
||||
* Note also that access to create nodes is handled by
|
||||
* hook_ENTITY_TYPE_create_access().
|
||||
*
|
||||
* @see \Drupal\node\NodeAccessControlHandler
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_access().
|
||||
*/
|
||||
#[Hook('node_access')]
|
||||
public function nodeAccess(NodeInterface $node, $operation, AccountInterface $account): AccessResultInterface {
|
||||
$type = $node->bundle();
|
||||
// Note create access is handled by hook_ENTITY_TYPE_create_access().
|
||||
switch ($operation) {
|
||||
case 'update':
|
||||
$access = AccessResult::allowedIfHasPermission($account, 'edit any ' . $type . ' content');
|
||||
if (!$access->isAllowed() && $account->hasPermission('edit own ' . $type . ' content')) {
|
||||
$access = $access->orIf(AccessResult::allowedIf($account->id() == $node->getOwnerId())->cachePerUser()->addCacheableDependency($node));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
$access = AccessResult::allowedIfHasPermission($account, 'delete any ' . $type . ' content');
|
||||
if (!$access->isAllowed() && $account->hasPermission('delete own ' . $type . ' content')) {
|
||||
$access = $access->orIf(AccessResult::allowedIf($account->id() == $node->getOwnerId()))->cachePerUser()->addCacheableDependency($node);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$access = AccessResult::neutral();
|
||||
}
|
||||
return $access;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_query_TAG_alter().
|
||||
*
|
||||
* This is the hook_query_alter() for queries tagged with 'node_access'. It
|
||||
* adds node access checks for the user account given by the 'account'
|
||||
* meta-data (or current user if not provided), for an operation given by the
|
||||
* 'op' meta-data (or 'view' if not provided; other possible values are
|
||||
* 'update' and 'delete').
|
||||
*
|
||||
* Queries tagged with 'node_access' that are not against the {node} table
|
||||
* must add the base table as metadata. For example:
|
||||
* @code
|
||||
* $query
|
||||
* ->addTag('node_access')
|
||||
* ->addMetaData('base_table', 'taxonomy_index');
|
||||
* @endcode
|
||||
*/
|
||||
#[Hook('query_node_access_alter')]
|
||||
public function queryNodeAccessAlter(AlterableInterface $query): void {
|
||||
// Read meta-data from query, if provided.
|
||||
if (!($account = $query->getMetaData('account'))) {
|
||||
$account = \Drupal::currentUser();
|
||||
}
|
||||
if (!($op = $query->getMetaData('op'))) {
|
||||
$op = 'view';
|
||||
}
|
||||
// If $account can bypass node access, or there are no node access modules,
|
||||
// or the operation is 'view' and the $account has a global view grant
|
||||
// (such as a view grant for node ID 0), we don't need to alter the query.
|
||||
if ($account->hasPermission('bypass node access')) {
|
||||
return;
|
||||
}
|
||||
if (!\Drupal::moduleHandler()->hasImplementations('node_grants')) {
|
||||
return;
|
||||
}
|
||||
if ($op == 'view' && node_access_view_all_nodes($account)) {
|
||||
return;
|
||||
}
|
||||
$tables = $query->getTables();
|
||||
$base_table = $query->getMetaData('base_table');
|
||||
// If the base table is not given, default to one of the node base tables.
|
||||
if (!$base_table) {
|
||||
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
|
||||
$table_mapping = \Drupal::entityTypeManager()->getStorage('node')->getTableMapping();
|
||||
$node_base_tables = $table_mapping->getTableNames();
|
||||
foreach ($tables as $table_info) {
|
||||
if (!$table_info instanceof SelectInterface) {
|
||||
$table = $table_info['table'];
|
||||
// Ensure that 'node' and 'node_field_data' are always preferred over
|
||||
// 'node_revision' and 'node_field_revision'.
|
||||
if ($table == 'node' || $table == 'node_field_data') {
|
||||
$base_table = $table;
|
||||
break;
|
||||
}
|
||||
// If one of the node base tables are in the query, add it to the list
|
||||
// of possible base tables to join against.
|
||||
if (in_array($table, $node_base_tables)) {
|
||||
$base_table = $table;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Bail out if the base table is missing.
|
||||
if (!$base_table) {
|
||||
throw new \Exception('Query tagged for node access but there is no node table, specify the base_table using meta data.');
|
||||
}
|
||||
}
|
||||
// Update the query for the given storage method.
|
||||
\Drupal::service('node.grant_storage')->alterQuery($query, $tables, $op, $account, $base_table);
|
||||
// Bubble the 'user.node_grants:$op' cache context to the current render
|
||||
// context.
|
||||
$renderer = \Drupal::service('renderer');
|
||||
if ($renderer->hasRenderContext()) {
|
||||
$build = ['#cache' => ['contexts' => ['user.node_grants:' . $op]]];
|
||||
$renderer->render($build);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup node_access".
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_modules_installed().
|
||||
*/
|
||||
#[Hook('modules_installed')]
|
||||
public function modulesInstalled(array $modules): void {
|
||||
// Check if any of the newly enabled modules require the node_access table
|
||||
// to be rebuilt.
|
||||
if (!node_access_needs_rebuild() && \Drupal::moduleHandler()->hasImplementations('node_grants', $modules)) {
|
||||
node_access_needs_rebuild(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_modules_uninstalled().
|
||||
*/
|
||||
#[Hook('modules_uninstalled')]
|
||||
public function modulesUninstalled($modules): void {
|
||||
// Check whether any of the disabled modules implemented hook_node_grants(),
|
||||
// in which case the node access table needs to be rebuilt.
|
||||
foreach ($modules as $module) {
|
||||
// At this point, the module is already disabled, but its code is still
|
||||
// loaded in memory. Module functions must no longer be called. We only
|
||||
// check whether a hook implementation function exists and do not invoke
|
||||
// it. Node access also needs to be rebuilt if language module is disabled
|
||||
// to remove any language-specific grants.
|
||||
if (!node_access_needs_rebuild() && (\Drupal::moduleHandler()->hasImplementations('node_grants', $module) || $module == 'language')) {
|
||||
node_access_needs_rebuild(TRUE);
|
||||
}
|
||||
}
|
||||
// If there remains no more node_access module, rebuilding will be
|
||||
// straightforward, we can do it right now.
|
||||
if (node_access_needs_rebuild() && !\Drupal::moduleHandler()->hasImplementations('node_grants')) {
|
||||
node_access_rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_delete() for 'configurable_language'.
|
||||
*/
|
||||
#[Hook('configurable_language_delete')]
|
||||
public function configurableLanguageDelete(ConfigurableLanguageInterface $language): void {
|
||||
// On nodes with this language, unset the language.
|
||||
\Drupal::entityTypeManager()->getStorage('node')->clearRevisionsLanguage($language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_insert() for comment entities.
|
||||
*/
|
||||
#[Hook('comment_insert')]
|
||||
public function commentInsert($comment): void {
|
||||
// Reindex the node when comments are added.
|
||||
if ($comment->getCommentedEntityTypeId() == 'node') {
|
||||
node_reindex_node_search($comment->getCommentedEntityId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_update() for comment entities.
|
||||
*/
|
||||
#[Hook('comment_update')]
|
||||
public function commentUpdate($comment): void {
|
||||
// Reindex the node when comments are changed.
|
||||
if ($comment->getCommentedEntityTypeId() == 'node') {
|
||||
node_reindex_node_search($comment->getCommentedEntityId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_delete() for comment entities.
|
||||
*/
|
||||
#[Hook('comment_delete')]
|
||||
public function commentDelete($comment): void {
|
||||
// Reindex the node when comments are deleted.
|
||||
if ($comment->getCommentedEntityTypeId() == 'node') {
|
||||
node_reindex_node_search($comment->getCommentedEntityId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_config_translation_info_alter().
|
||||
*/
|
||||
#[Hook('config_translation_info_alter')]
|
||||
public function configTranslationInfoAlter(&$info): void {
|
||||
$info['node_type']['class'] = 'Drupal\node\ConfigTranslation\NodeTypeMapper';
|
||||
}
|
||||
|
||||
}
|
||||
155
web/core/modules/node/src/Hook/NodeRequirements.php
Normal file
155
web/core/modules/node/src/Hook/NodeRequirements.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\node\Hook;
|
||||
|
||||
use Drupal\Core\Extension\Requirement\RequirementSeverity;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleExtensionList;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Requirements for the Node module.
|
||||
*/
|
||||
class NodeRequirements {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
public function __construct(
|
||||
protected readonly EntityTypeManagerInterface $entityTypeManager,
|
||||
protected readonly ModuleHandlerInterface $moduleHandler,
|
||||
protected readonly TranslationInterface $translation,
|
||||
protected readonly ModuleExtensionList $moduleExtensionList,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Implements hook_runtime_requirements().
|
||||
*/
|
||||
#[Hook('runtime_requirements')]
|
||||
public function runtime(): array {
|
||||
$requirements = [];
|
||||
// Only show rebuild button if there are either 0, or 2 or more, rows
|
||||
// in the {node_access} table, or if there are modules that
|
||||
// implement hook_node_grants().
|
||||
$grant_count = $this->entityTypeManager->getAccessControlHandler('node')->countGrants();
|
||||
$has_node_grants_implementations = $this->moduleHandler->hasImplementations('node_grants');
|
||||
if ($grant_count != 1 || $has_node_grants_implementations) {
|
||||
$value = $this->translation->formatPlural($grant_count, 'One permission in use', '@count permissions in use', ['@count' => $grant_count]);
|
||||
}
|
||||
else {
|
||||
$value = $this->t('Disabled');
|
||||
}
|
||||
|
||||
$requirements['node_access'] = [
|
||||
'title' => $this->t('Node Access Permissions'),
|
||||
'value' => $value,
|
||||
'description' => $this->t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Rebuilding will remove all privileges to content and replace them with permissions based on the current modules and settings. Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed, content will automatically use the new permissions. <a href=":rebuild">Rebuild permissions</a>', [
|
||||
':rebuild' => Url::fromRoute('node.configure_rebuild_confirm')->toString(),
|
||||
]),
|
||||
];
|
||||
|
||||
// Report when the "Published status or admin user" has no impact on the
|
||||
// result of dependent views due to active node access modules.
|
||||
// @see https://www.drupal.org/node/3472976
|
||||
if ($has_node_grants_implementations && $this->moduleHandler->moduleExists('views')) {
|
||||
$node_status_filter_problematic_views = [];
|
||||
$query = $this->entityTypeManager->getStorage('view')->getQuery();
|
||||
$query->condition('status', TRUE);
|
||||
$query->accessCheck(FALSE);
|
||||
$active_view_ids = $query->execute();
|
||||
|
||||
$views_storage = $this->entityTypeManager->getStorage('view');
|
||||
foreach ($views_storage->loadMultiple($active_view_ids) as $view) {
|
||||
foreach ($view->get('display') as $display_id => $display) {
|
||||
if (array_key_exists('filters', $display['display_options'])) {
|
||||
foreach ($display['display_options']['filters'] as $filter) {
|
||||
if (array_key_exists('plugin_id', $filter) && $filter['plugin_id'] === 'node_status') {
|
||||
$node_status_filter_problematic_views[$view->id()][$display_id] = [
|
||||
'view_label' => $view->label(),
|
||||
'display_name' => $display['display_title'] ?? $display_id,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($node_status_filter_problematic_views !== []) {
|
||||
$node_access_implementations = [];
|
||||
$module_data = $this->moduleExtensionList->getAllInstalledInfo();
|
||||
foreach (['node_grants', 'node_grants_alter'] as $hook) {
|
||||
$this->moduleHandler->invokeAllWith(
|
||||
$hook,
|
||||
static function (callable $hook, string $module) use (&$node_access_implementations, $module_data) {
|
||||
$node_access_implementations[$module] = $module_data[$module]['name'];
|
||||
}
|
||||
);
|
||||
}
|
||||
uasort($node_access_implementations, 'strnatcasecmp');
|
||||
$views_ui_enabled = $this->moduleHandler->moduleExists('views_ui');
|
||||
$node_status_filter_problematic_views_list = [];
|
||||
foreach ($node_status_filter_problematic_views as $view_id => $displays) {
|
||||
foreach ($displays as $display_id => $info) {
|
||||
$text = "{$info['view_label']} ({$info['display_name']})";
|
||||
if ($views_ui_enabled) {
|
||||
$url = Url::fromRoute('entity.view.edit_display_form', [
|
||||
'view' => $view_id,
|
||||
'display_id' => $display_id,
|
||||
]);
|
||||
if ($url->access()) {
|
||||
$node_status_filter_problematic_views_list[] = Link::fromTextAndUrl($text, $url)->toString();
|
||||
}
|
||||
else {
|
||||
$node_status_filter_problematic_views_list[] = $text;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$node_status_filter_problematic_views_list[] = $text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$node_status_filter_problematic_views_count = count($node_status_filter_problematic_views_list);
|
||||
$node_status_filter_description_arguments = [
|
||||
'%modules' => implode(', ', $node_access_implementations),
|
||||
'%status_filter' => $this->t('Published status or admin user'),
|
||||
];
|
||||
|
||||
if ($node_status_filter_problematic_views_count > 1) {
|
||||
$node_status_filter_problematic_views_list = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $node_status_filter_problematic_views_list,
|
||||
];
|
||||
$node_status_filter_description_arguments['@views'] = \Drupal::service('renderer')->renderInIsolation($node_status_filter_problematic_views_list);
|
||||
}
|
||||
else {
|
||||
$node_status_filter_description_arguments['%view'] = reset($node_status_filter_problematic_views_list);
|
||||
}
|
||||
|
||||
$node_status_filter_description = new PluralTranslatableMarkup(
|
||||
$node_status_filter_problematic_views_count,
|
||||
'The %view view uses the %status_filter filter but it has no effect because the following module(s) control access: %modules. Review and consider removing the filter.',
|
||||
'The following views use the %status_filter filter but it has no effect because the following module(s) control access: %modules. Review and consider removing the filter from these views: @views',
|
||||
$node_status_filter_description_arguments,
|
||||
);
|
||||
|
||||
$requirements['node_status_filter'] = [
|
||||
'title' => $this->t('Content status filter'),
|
||||
'value' => $this->t('Redundant filters detected'),
|
||||
'description' => $node_status_filter_description,
|
||||
'severity' => RequirementSeverity::Warning,
|
||||
];
|
||||
}
|
||||
}
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
}
|
||||
38
web/core/modules/node/src/Hook/NodeThemeHooks.php
Normal file
38
web/core/modules/node/src/Hook/NodeThemeHooks.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\node\Hook;
|
||||
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for the node module.
|
||||
*/
|
||||
class NodeThemeHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_HOOK() for node field templates.
|
||||
*/
|
||||
#[Hook('preprocess_field__node')]
|
||||
public function preprocessFieldNode(&$variables): void {
|
||||
// Set a variable 'is_inline' in cases where inline markup is required,
|
||||
// without any block elements such as <div>.
|
||||
if ($variables['element']['#is_page_title'] ?? FALSE) {
|
||||
// Page title is always inline because it will be displayed inside <h1>.
|
||||
$variables['is_inline'] = TRUE;
|
||||
}
|
||||
elseif (in_array($variables['field_name'], ['created', 'uid', 'title'], TRUE)) {
|
||||
// Display created, uid and title fields inline because they will be
|
||||
// displayed inline by node.html.twig. Skip this if the field
|
||||
// display is configurable and skipping has been enabled.
|
||||
// @todo Delete as part of https://www.drupal.org/node/3015623
|
||||
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node = $variables['element']['#object'];
|
||||
$skip_custom_preprocessing = $node->getEntityType()->get('enable_base_field_custom_preprocess_skipping');
|
||||
$variables['is_inline'] = !$skip_custom_preprocessing || !$node->getFieldDefinition($variables['field_name'])->isDisplayConfigurable('view');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
202
web/core/modules/node/src/Hook/NodeTokensHooks.php
Normal file
202
web/core/modules/node/src/Hook/NodeTokensHooks.php
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Hook;
|
||||
|
||||
use Drupal\Core\Datetime\Entity\DateFormat;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for node.
|
||||
*/
|
||||
class NodeTokensHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_token_info().
|
||||
*/
|
||||
#[Hook('token_info')]
|
||||
public function tokenInfo(): array {
|
||||
$type = [
|
||||
'name' => $this->t('Nodes'),
|
||||
'description' => $this->t('Tokens related to individual content items, or "nodes".'),
|
||||
'needs-data' => 'node',
|
||||
];
|
||||
// Core tokens for nodes.
|
||||
$node['nid'] = [
|
||||
'name' => $this->t("Content ID"),
|
||||
'description' => $this->t('The unique ID of the content item, or "node".'),
|
||||
];
|
||||
$node['uuid'] = [
|
||||
'name' => $this->t('UUID'),
|
||||
'description' => $this->t('The UUID of the content item, or "node".'),
|
||||
];
|
||||
$node['vid'] = [
|
||||
'name' => $this->t("Revision ID"),
|
||||
'description' => $this->t("The unique ID of the node's latest revision."),
|
||||
];
|
||||
$node['type'] = ['name' => $this->t("Content type")];
|
||||
$node['type-name'] = [
|
||||
'name' => $this->t("Content type name"),
|
||||
'description' => $this->t("The human-readable name of the node type."),
|
||||
];
|
||||
$node['title'] = ['name' => $this->t("Title")];
|
||||
$node['body'] = ['name' => $this->t("Body"), 'description' => $this->t("The main body text of the node.")];
|
||||
$node['summary'] = [
|
||||
'name' => $this->t("Summary"),
|
||||
'description' => $this->t("The summary of the node's main body text."),
|
||||
];
|
||||
$node['langcode'] = [
|
||||
'name' => $this->t('Language code'),
|
||||
'description' => $this->t('The language code of the language the node is written in.'),
|
||||
];
|
||||
$node['published_status'] = [
|
||||
'name' => $this->t('Published'),
|
||||
'description' => $this->t('The publication status of the node ("Published" or "Unpublished").'),
|
||||
];
|
||||
$node['url'] = ['name' => $this->t("URL"), 'description' => $this->t("The URL of the node.")];
|
||||
$node['edit-url'] = ['name' => $this->t("Edit URL"), 'description' => $this->t("The URL of the node's edit page.")];
|
||||
// Chained tokens for nodes.
|
||||
$node['created'] = ['name' => $this->t("Date created"), 'type' => 'date'];
|
||||
$node['changed'] = [
|
||||
'name' => $this->t("Date changed"),
|
||||
'description' => $this->t("The date the node was most recently updated."),
|
||||
'type' => 'date',
|
||||
];
|
||||
$node['author'] = ['name' => $this->t("Author"), 'type' => 'user'];
|
||||
return ['types' => ['node' => $type], 'tokens' => ['node' => $node]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
#[Hook('tokens')]
|
||||
public function tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata): array {
|
||||
$token_service = \Drupal::token();
|
||||
$url_options = ['absolute' => TRUE];
|
||||
if (isset($options['langcode'])) {
|
||||
$url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
|
||||
$langcode = $options['langcode'];
|
||||
}
|
||||
else {
|
||||
$langcode = LanguageInterface::LANGCODE_DEFAULT;
|
||||
}
|
||||
$replacements = [];
|
||||
if ($type == 'node' && !empty($data['node'])) {
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node = $data['node'];
|
||||
foreach ($tokens as $name => $original) {
|
||||
switch ($name) {
|
||||
// Simple key values on the node.
|
||||
case 'nid':
|
||||
$replacements[$original] = $node->id();
|
||||
break;
|
||||
|
||||
case 'uuid':
|
||||
$replacements[$original] = $node->uuid();
|
||||
break;
|
||||
|
||||
case 'vid':
|
||||
$replacements[$original] = $node->getRevisionId();
|
||||
break;
|
||||
|
||||
case 'type':
|
||||
$replacements[$original] = $node->getType();
|
||||
break;
|
||||
|
||||
case 'type-name':
|
||||
$type_name = node_get_type_label($node);
|
||||
$replacements[$original] = $type_name;
|
||||
break;
|
||||
|
||||
case 'title':
|
||||
$replacements[$original] = $node->getTitle();
|
||||
break;
|
||||
|
||||
case 'body':
|
||||
case 'summary':
|
||||
$translation = \Drupal::service('entity.repository')->getTranslationFromContext($node, $langcode, ['operation' => 'node_tokens']);
|
||||
if ($translation->hasField('body') && ($items = $translation->get('body')) && !$items->isEmpty()) {
|
||||
$item = $items[0];
|
||||
// If the summary was requested and is not empty, use it.
|
||||
if ($name == 'summary' && !empty($item->summary)) {
|
||||
$output = $item->summary_processed;
|
||||
}
|
||||
else {
|
||||
$output = $item->processed;
|
||||
// A summary was requested.
|
||||
if ($name == 'summary') {
|
||||
// Generate an optionally trimmed summary of the body field.
|
||||
// Get the 'trim_length' size used for the 'teaser' mode, if
|
||||
// present, or use the default trim_length size.
|
||||
$display_options = \Drupal::service('entity_display.repository')->getViewDisplay('node', $node->getType(), 'teaser')->getComponent('body');
|
||||
if (isset($display_options['settings']['trim_length'])) {
|
||||
$length = $display_options['settings']['trim_length'];
|
||||
}
|
||||
else {
|
||||
$settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings('text_summary_or_trimmed');
|
||||
$length = $settings['trim_length'];
|
||||
}
|
||||
$output = text_summary($output, $item->format, $length);
|
||||
}
|
||||
}
|
||||
// "processed" returns a \Drupal\Component\Render\MarkupInterface
|
||||
// via check_markup().
|
||||
$replacements[$original] = $output;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'langcode':
|
||||
$replacements[$original] = $node->language()->getId();
|
||||
break;
|
||||
|
||||
case 'published_status':
|
||||
$replacements[$original] = $node->isPublished() ? $this->t('Published') : $this->t('Unpublished');
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
$replacements[$original] = $node->toUrl('canonical', $url_options)->toString();
|
||||
break;
|
||||
|
||||
case 'edit-url':
|
||||
$replacements[$original] = $node->toUrl('edit-form', $url_options)->toString();
|
||||
break;
|
||||
|
||||
// Default values for the chained tokens handled below.
|
||||
case 'author':
|
||||
$account = $node->getOwner() ?: User::load(0);
|
||||
$bubbleable_metadata->addCacheableDependency($account);
|
||||
$replacements[$original] = $account->label();
|
||||
break;
|
||||
|
||||
case 'created':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = \Drupal::service('date.formatter')->format($node->getCreatedTime(), 'medium', '', NULL, $langcode);
|
||||
break;
|
||||
|
||||
case 'changed':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = \Drupal::service('date.formatter')->format($node->getChangedTime(), 'medium', '', NULL, $langcode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($author_tokens = $token_service->findWithPrefix($tokens, 'author')) {
|
||||
$replacements += $token_service->generate('user', $author_tokens, ['user' => $node->getOwner()], $options, $bubbleable_metadata);
|
||||
}
|
||||
if ($created_tokens = $token_service->findWithPrefix($tokens, 'created')) {
|
||||
$replacements += $token_service->generate('date', $created_tokens, ['date' => $node->getCreatedTime()], $options, $bubbleable_metadata);
|
||||
}
|
||||
if ($changed_tokens = $token_service->findWithPrefix($tokens, 'changed')) {
|
||||
$replacements += $token_service->generate('date', $changed_tokens, ['date' => $node->getChangedTime()], $options, $bubbleable_metadata);
|
||||
}
|
||||
}
|
||||
return $replacements;
|
||||
}
|
||||
|
||||
}
|
||||
26
web/core/modules/node/src/Hook/NodeViewsExecutionHooks.php
Normal file
26
web/core/modules/node/src/Hook/NodeViewsExecutionHooks.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Hook;
|
||||
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for node.
|
||||
*/
|
||||
class NodeViewsExecutionHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_views_query_substitutions().
|
||||
*/
|
||||
#[Hook('views_query_substitutions')]
|
||||
public function viewsQuerySubstitutions(ViewExecutable $view): array {
|
||||
$account = \Drupal::currentUser();
|
||||
return [
|
||||
'***ADMINISTER_NODES***' => intval($account->hasPermission('administer nodes')),
|
||||
'***VIEW_OWN_UNPUBLISHED_NODES***' => intval($account->hasPermission('view own unpublished content')),
|
||||
'***BYPASS_NODE_ACCESS***' => intval($account->hasPermission('bypass node access')),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
60
web/core/modules/node/src/Hook/NodeViewsHooks.php
Normal file
60
web/core/modules/node/src/Hook/NodeViewsHooks.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node\Hook;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\views\Analyzer;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for node.
|
||||
*/
|
||||
class NodeViewsHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_views_analyze().
|
||||
*/
|
||||
#[Hook('views_analyze')]
|
||||
public function viewsAnalyze(ViewExecutable $view): array {
|
||||
$ret = [];
|
||||
// Check for something other than the default display:
|
||||
if ($view->storage->get('base_table') == 'node') {
|
||||
foreach ($view->displayHandlers as $display) {
|
||||
if (!$display->isDefaulted('access') || !$display->isDefaulted('filters')) {
|
||||
// Check for no access control
|
||||
$access = $display->getOption('access');
|
||||
if (empty($access['type']) || $access['type'] == 'none') {
|
||||
$anonymous_role = Role::load(RoleInterface::ANONYMOUS_ID);
|
||||
$anonymous_has_access = $anonymous_role && $anonymous_role->hasPermission('access content');
|
||||
$authenticated_role = Role::load(RoleInterface::AUTHENTICATED_ID);
|
||||
$authenticated_has_access = $authenticated_role && $authenticated_role->hasPermission('access content');
|
||||
if (!$anonymous_has_access || !$authenticated_has_access) {
|
||||
$ret[] = Analyzer::formatMessage($this->t('Some roles lack permission to access content, but display %display has no access control.', ['%display' => $display->display['display_title']]), 'warning');
|
||||
}
|
||||
$filters = $display->getOption('filters');
|
||||
foreach ($filters as $filter) {
|
||||
if ($filter['table'] == 'node' && ($filter['field'] == 'status' || $filter['field'] == 'status_extra')) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
$ret[] = Analyzer::formatMessage($this->t('Display %display has no access control but does not contain a filter for published nodes.', ['%display' => $display->display['display_title']]), 'warning');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($view->displayHandlers as $display) {
|
||||
if ($display->getPluginId() == 'page') {
|
||||
if ($display->getOption('path') == 'node/%') {
|
||||
$ret[] = Analyzer::formatMessage($this->t('Display %display has set node/% as path. This will not produce what you want. If you want to have multiple versions of the node view, use Layout Builder.', ['%display' => $display->display['display_title']]), 'warning');
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
}
|
||||
321
web/core/modules/node/src/NodeAccessControlHandler.php
Normal file
321
web/core/modules/node/src/NodeAccessControlHandler.php
Normal file
@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines the access control handler for the node entity type.
|
||||
*
|
||||
* @see \Drupal\node\Entity\Node
|
||||
* @ingroup node_access
|
||||
*/
|
||||
class NodeAccessControlHandler extends EntityAccessControlHandler implements NodeAccessControlHandlerInterface, EntityHandlerInterface {
|
||||
|
||||
/**
|
||||
* The node grant storage.
|
||||
*
|
||||
* @var \Drupal\node\NodeGrantDatabaseStorageInterface
|
||||
*/
|
||||
protected $grantStorage;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Map of revision operations.
|
||||
*
|
||||
* Keys contain revision operations, where values are an array containing the
|
||||
* permission operation and entity operation.
|
||||
*
|
||||
* Permission operation is used to build the required permission, e.g.
|
||||
* 'permissionOperation all revisions', 'permissionOperation type revisions'.
|
||||
*
|
||||
* Entity operation is used to determine access, e.g for 'delete revision'
|
||||
* operation, an account must also have access to 'delete' operation on an
|
||||
* entity.
|
||||
*/
|
||||
protected const REVISION_OPERATION_MAP = [
|
||||
'view all revisions' => ['view', 'view'],
|
||||
'view revision' => ['view', 'view'],
|
||||
'revert revision' => ['revert', 'update'],
|
||||
'delete revision' => ['delete', 'delete'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructs a NodeAccessControlHandler object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage
|
||||
* The node grant storage.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, NodeGrantDatabaseStorageInterface $grant_storage, EntityTypeManagerInterface $entity_type_manager) {
|
||||
parent::__construct($entity_type);
|
||||
$this->grantStorage = $grant_storage;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('node.grant_storage'),
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access(EntityInterface $entity, $operation, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$account = $this->prepareUser($account);
|
||||
|
||||
// Only bypass if not a revision operation, to retain compatibility.
|
||||
if ($account->hasPermission('bypass node access') && !isset(static::REVISION_OPERATION_MAP[$operation])) {
|
||||
$result = AccessResult::allowed()->cachePerPermissions();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
if (!$account->hasPermission('access content')) {
|
||||
$result = AccessResult::forbidden("The 'access content' permission is required.")->cachePerPermissions();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
$result = parent::access($entity, $operation, $account, TRUE)->cachePerPermissions();
|
||||
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAccess($entity_bundle = NULL, ?AccountInterface $account = NULL, array $context = [], $return_as_object = FALSE) {
|
||||
$account = $this->prepareUser($account);
|
||||
|
||||
if ($account->hasPermission('bypass node access')) {
|
||||
$result = AccessResult::allowed()->cachePerPermissions();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
if (!$account->hasPermission('access content')) {
|
||||
$result = AccessResult::forbidden("The 'access content' permission is required.")->cachePerPermissions();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
$result = parent::createAccess($entity_bundle, $account, $context, TRUE)->cachePerPermissions();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $node, $operation, AccountInterface $account) {
|
||||
assert($node instanceof NodeInterface);
|
||||
$cacheability = new CacheableMetadata();
|
||||
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
if ($operation === 'view') {
|
||||
$result = $this->checkViewAccess($node, $account, $cacheability);
|
||||
if ($result !== NULL) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
[$revision_permission_operation, $entity_operation] = static::REVISION_OPERATION_MAP[$operation] ?? [
|
||||
NULL,
|
||||
NULL,
|
||||
];
|
||||
|
||||
// Revision operations.
|
||||
if ($revision_permission_operation) {
|
||||
$cacheability->addCacheContexts(['user.permissions']);
|
||||
$bundle = $node->bundle();
|
||||
|
||||
// If user doesn't have any of these then quit.
|
||||
if (!$account->hasPermission("$revision_permission_operation all revisions") && !$account->hasPermission("$revision_permission_operation $bundle revisions") && !$account->hasPermission('administer nodes')) {
|
||||
return AccessResult::neutral()->addCacheableDependency($cacheability);
|
||||
}
|
||||
|
||||
// If the user has the view all revisions permission and this is the view
|
||||
// all revisions operation then we can allow access.
|
||||
if ($operation === 'view all revisions') {
|
||||
return AccessResult::allowed()->addCacheableDependency($cacheability);
|
||||
}
|
||||
|
||||
// If this is the default revision, return access denied for revert or
|
||||
// delete operations.
|
||||
$cacheability->addCacheableDependency($node);
|
||||
if ($node->isDefaultRevision() && ($operation === 'revert revision' || $operation === 'delete revision')) {
|
||||
return AccessResult::forbidden()->addCacheableDependency($cacheability);
|
||||
}
|
||||
elseif ($account->hasPermission('administer nodes')) {
|
||||
return AccessResult::allowed()->addCacheableDependency($cacheability);
|
||||
}
|
||||
|
||||
// First check the access to the default revision and finally, if the
|
||||
// node passed in is not the default revision then check access to
|
||||
// that, too.
|
||||
$node_storage = $this->entityTypeManager->getStorage($node->getEntityTypeId());
|
||||
$access = $this->access($node_storage->load($node->id()), $entity_operation, $account, TRUE);
|
||||
if (!$node->isDefaultRevision()) {
|
||||
$access = $access->andIf($this->access($node, $entity_operation, $account, TRUE));
|
||||
}
|
||||
return $access->addCacheableDependency($cacheability);
|
||||
}
|
||||
|
||||
// Evaluate node grants.
|
||||
$access_result = $this->grantStorage->access($node, $operation, $account);
|
||||
if ($access_result instanceof RefinableCacheableDependencyInterface) {
|
||||
$access_result->addCacheableDependency($cacheability);
|
||||
}
|
||||
return $access_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs view access checks.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node for which to check access.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The user for which to check access.
|
||||
* @param \Drupal\Core\Cache\CacheableMetadata $cacheability
|
||||
* Allows cacheability information bubble up from this method.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface|null
|
||||
* The calculated access result or null when no opinion.
|
||||
*/
|
||||
protected function checkViewAccess(NodeInterface $node, AccountInterface $account, CacheableMetadata $cacheability): ?AccessResultInterface {
|
||||
// If the node status changes, so does the outcome of the check below, so
|
||||
// we need to add the node as a cacheable dependency.
|
||||
$cacheability->addCacheableDependency($node);
|
||||
|
||||
if ($node->isPublished()) {
|
||||
return NULL;
|
||||
}
|
||||
$cacheability->addCacheContexts(['user.permissions']);
|
||||
|
||||
if (!$account->hasPermission('view own unpublished content')) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$cacheability->addCacheContexts(['user.roles:authenticated']);
|
||||
// The "view own unpublished content" permission must not be granted
|
||||
// to anonymous users for security reasons.
|
||||
if (!$account->isAuthenticated()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// When access is granted due to the 'view own unpublished content'
|
||||
// permission and for no other reason, node grants are bypassed. However,
|
||||
// to ensure the full set of cacheable metadata is available to variation
|
||||
// cache, additionally add the node_grants cache context so that if the
|
||||
// status or the owner of the node changes, cache redirects will continue to
|
||||
// reflect the latest state without needing to be invalidated.
|
||||
$cacheability->addCacheContexts(['user']);
|
||||
if ($this->moduleHandler->hasImplementations('node_grants')) {
|
||||
$cacheability->addCacheContexts(['user.node_grants:view']);
|
||||
}
|
||||
if ($account->id() != $node->getOwnerId()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return AccessResult::allowed()->addCacheableDependency($cacheability);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
|
||||
return AccessResult::allowedIf($account->hasPermission('create ' . $entity_bundle . ' content'))->cachePerPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) {
|
||||
// Only users with the administer nodes permission can edit administrative
|
||||
// fields.
|
||||
$administrative_fields = ['uid', 'status', 'created', 'promote', 'sticky'];
|
||||
if ($operation == 'edit' && in_array($field_definition->getName(), $administrative_fields, TRUE)) {
|
||||
return AccessResult::allowedIfHasPermission($account, 'administer nodes');
|
||||
}
|
||||
|
||||
// No user can change read only fields.
|
||||
$read_only_fields = ['revision_timestamp', 'revision_uid'];
|
||||
if ($operation == 'edit' && in_array($field_definition->getName(), $read_only_fields, TRUE)) {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
// Users have access to the revision_log field either if they have
|
||||
// administrative permissions or if the new revision option is enabled.
|
||||
if ($operation == 'edit' && $field_definition->getName() == 'revision_log') {
|
||||
if ($account->hasPermission('administer nodes')) {
|
||||
return AccessResult::allowed()->cachePerPermissions();
|
||||
}
|
||||
return AccessResult::allowedIf($items->getEntity()->type->entity->shouldCreateNewRevision())->cachePerPermissions();
|
||||
}
|
||||
return parent::checkFieldAccess($operation, $field_definition, $account, $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function acquireGrants(NodeInterface $node) {
|
||||
$grants = $this->moduleHandler->invokeAll('node_access_records', [$node]);
|
||||
// Let modules alter the grants.
|
||||
$this->moduleHandler->alter('node_access_records', $grants, $node);
|
||||
// If no grants are set and the node is published, then use the default
|
||||
// grant.
|
||||
if (empty($grants) && $node->isPublished()) {
|
||||
$grants[] = ['realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0];
|
||||
}
|
||||
return $grants;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function writeDefaultGrant() {
|
||||
$this->grantStorage->writeDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteGrants() {
|
||||
$this->grantStorage->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function countGrants() {
|
||||
return $this->grantStorage->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkAllGrants(AccountInterface $account) {
|
||||
return $this->grantStorage->checkAll($account);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Node specific entity access control methods.
|
||||
*
|
||||
* @ingroup node_access
|
||||
*/
|
||||
interface NodeAccessControlHandlerInterface {
|
||||
|
||||
/**
|
||||
* Gets the list of node access grants.
|
||||
*
|
||||
* This function is called to check the access grants for a node. It collects
|
||||
* all node access grants for the node from hook_node_access_records()
|
||||
* implementations, allows these grants to be altered via
|
||||
* hook_node_access_records_alter() implementations, and returns the grants to
|
||||
* the caller.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The $node to acquire grants for.
|
||||
*
|
||||
* @return array
|
||||
* The access rules for the node.
|
||||
*/
|
||||
public function acquireGrants(NodeInterface $node);
|
||||
|
||||
/**
|
||||
* Creates the default node access grant entry on the grant storage.
|
||||
*
|
||||
* @see \Drupal\node\NodeGrantDatabaseStorageInterface::writeDefault()
|
||||
*/
|
||||
public function writeDefaultGrant();
|
||||
|
||||
/**
|
||||
* Deletes all node access entries.
|
||||
*/
|
||||
public function deleteGrants();
|
||||
|
||||
/**
|
||||
* Counts available node grants.
|
||||
*
|
||||
* @return int
|
||||
* Returns the amount of node grants.
|
||||
*/
|
||||
public function countGrants();
|
||||
|
||||
/**
|
||||
* Checks all grants for a given account.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* A user object representing the user for whom the operation is to be
|
||||
* performed.
|
||||
*
|
||||
* @return int
|
||||
* Status of the access check.
|
||||
*/
|
||||
public function checkAllGrants(AccountInterface $account);
|
||||
|
||||
}
|
||||
334
web/core/modules/node/src/NodeGrantDatabaseStorage.php
Normal file
334
web/core/modules/node/src/NodeGrantDatabaseStorage.php
Normal file
@ -0,0 +1,334 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines a storage handler class that handles the node grants system.
|
||||
*
|
||||
* This is used to build node query access.
|
||||
*
|
||||
* @ingroup node_access
|
||||
*/
|
||||
class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* Constructs a NodeGrantDatabaseStorage object.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
*/
|
||||
public function __construct(Connection $database, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager) {
|
||||
$this->database = $database;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->languageManager = $language_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access(NodeInterface $node, $operation, AccountInterface $account) {
|
||||
// Grants only support these operations.
|
||||
if (!in_array($operation, ['view', 'update', 'delete'])) {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
// If no module implements the hook or the node does not have an id there is
|
||||
// no point in querying the database for access grants.
|
||||
if (!$this->moduleHandler->hasImplementations('node_grants') || $node->isNew()) {
|
||||
// Return the equivalent of the default grant, defined by
|
||||
// self::writeDefault().
|
||||
if ($operation === 'view') {
|
||||
$result = AccessResult::allowedIf($node->isPublished());
|
||||
if (!$node->isNew()) {
|
||||
$result->addCacheableDependency($node);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
// Check the database for potential access grants.
|
||||
$query = $this->database->select('node_access');
|
||||
$query->addExpression('1');
|
||||
// Only interested for granting in the current operation.
|
||||
$query->condition('grant_' . $operation, 1, '>=');
|
||||
// Check for grants for this node and the correct langcode. New translations
|
||||
// do not yet have a langcode and must check the fallback node record.
|
||||
$nids = $query->andConditionGroup()
|
||||
->condition('nid', $node->id());
|
||||
if (!$node->isNewTranslation()) {
|
||||
$nids->condition('langcode', $node->language()->getId());
|
||||
}
|
||||
else {
|
||||
$nids->condition('fallback', 1);
|
||||
}
|
||||
// If the node is published, also take the default grant into account. The
|
||||
// default is saved with a node ID of 0.
|
||||
$status = $node->isPublished();
|
||||
if ($status) {
|
||||
$nids = $query->orConditionGroup()
|
||||
->condition($nids)
|
||||
->condition('nid', 0);
|
||||
}
|
||||
$query->condition($nids);
|
||||
$query->range(0, 1);
|
||||
|
||||
$grants = $this->buildGrantsQueryCondition(node_access_grants($operation, $account));
|
||||
|
||||
if (count($grants) > 0) {
|
||||
$query->condition($grants);
|
||||
}
|
||||
|
||||
// Only the 'view' node grant can currently be cached; the others currently
|
||||
// don't have any cacheability metadata. Hopefully, we can add that in the
|
||||
// future, which would allow this access check result to be cacheable in all
|
||||
// cases. For now, this must remain marked as uncacheable, even when it is
|
||||
// theoretically cacheable, because we don't have the necessary metadata to
|
||||
// know it for a fact.
|
||||
$set_cacheability = function (AccessResult $access_result) use ($operation) {
|
||||
$access_result->addCacheContexts(['user.node_grants:' . $operation]);
|
||||
if ($operation !== 'view') {
|
||||
$access_result->setCacheMaxAge(0);
|
||||
}
|
||||
return $access_result;
|
||||
};
|
||||
|
||||
if ($query->execute()->fetchField()) {
|
||||
return $set_cacheability(AccessResult::allowed());
|
||||
}
|
||||
else {
|
||||
return $set_cacheability(AccessResult::neutral());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkAll(AccountInterface $account) {
|
||||
$query = $this->database->select('node_access');
|
||||
$query->addExpression('COUNT(*)');
|
||||
$query
|
||||
->condition('nid', 0)
|
||||
->condition('grant_view', 1, '>=');
|
||||
|
||||
$grants = $this->buildGrantsQueryCondition(node_access_grants('view', $account));
|
||||
|
||||
if (count($grants) > 0) {
|
||||
$query->condition($grants);
|
||||
}
|
||||
return $query->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alterQuery($query, array $tables, $operation, AccountInterface $account, $base_table) {
|
||||
if (!$langcode = $query->getMetaData('langcode')) {
|
||||
$langcode = FALSE;
|
||||
}
|
||||
|
||||
// Find all instances of the base table being joined which could appear
|
||||
// more than once in the query, and could be aliased. Join each one to
|
||||
// the node_access table.
|
||||
$grants = node_access_grants($operation, $account);
|
||||
// If any grant exists for the specified user, then user has access to the
|
||||
// node for the specified operation.
|
||||
$grant_conditions = $this->buildGrantsQueryCondition($grants);
|
||||
$grants_exist = count($grant_conditions->conditions()) > 0;
|
||||
|
||||
$is_multilingual = \Drupal::languageManager()->isMultilingual();
|
||||
foreach ($tables as $table_alias => $tableinfo) {
|
||||
$table = $tableinfo['table'];
|
||||
if (!($table instanceof SelectInterface) && $table == $base_table) {
|
||||
// Set the subquery.
|
||||
$subquery = $this->database->select('node_access', 'na')
|
||||
->fields('na', ['nid']);
|
||||
|
||||
// Attach conditions to the sub-query for nodes.
|
||||
if ($grants_exist) {
|
||||
$subquery->condition($grant_conditions);
|
||||
}
|
||||
$subquery->condition('na.grant_' . $operation, 1, '>=');
|
||||
|
||||
// Add langcode-based filtering if this is a multilingual site.
|
||||
if ($is_multilingual) {
|
||||
// If no specific langcode to check for is given, use the grant entry
|
||||
// which is set as a fallback.
|
||||
// If a specific langcode is given, use the grant entry for it.
|
||||
if ($langcode === FALSE) {
|
||||
$subquery->condition('na.fallback', 1, '=');
|
||||
}
|
||||
else {
|
||||
$subquery->condition('na.langcode', $langcode, '=');
|
||||
}
|
||||
}
|
||||
|
||||
$field = 'nid';
|
||||
// Now handle entities.
|
||||
$subquery->where("[$table_alias].[$field] = [na].[nid]");
|
||||
|
||||
if (empty($tableinfo['join type'])) {
|
||||
$query->exists($subquery);
|
||||
}
|
||||
else {
|
||||
// If this is a join, add the node access check to the join condition.
|
||||
// This requires using $query->getTables() to alter the table
|
||||
// information.
|
||||
$join_cond = $query
|
||||
->andConditionGroup()
|
||||
->exists($subquery);
|
||||
$join_cond->where($tableinfo['condition'], $query->getTables()[$table_alias]['arguments']);
|
||||
$query->getTables()[$table_alias]['arguments'] = [];
|
||||
$query->getTables()[$table_alias]['condition'] = $join_cond;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write(NodeInterface $node, array $grants, $realm = NULL, $delete = TRUE) {
|
||||
if ($delete) {
|
||||
$query = $this->database->delete('node_access')->condition('nid', $node->id());
|
||||
if ($realm) {
|
||||
$query->condition('realm', [$realm, 'all'], 'IN');
|
||||
}
|
||||
$query->execute();
|
||||
}
|
||||
// Only perform work when node_access modules are active.
|
||||
if (!empty($grants) && $this->moduleHandler->hasImplementations('node_grants')) {
|
||||
$query = $this->database->insert('node_access')->fields(['nid', 'langcode', 'fallback', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete']);
|
||||
// If we have defined a granted langcode, use it. But if not, add a grant
|
||||
// for every language this node is translated to.
|
||||
$fallback_langcode = $node->getUntranslated()->language()->getId();
|
||||
foreach ($grants as $grant) {
|
||||
if ($realm && $realm != $grant['realm']) {
|
||||
continue;
|
||||
}
|
||||
if (isset($grant['langcode'])) {
|
||||
$grant_languages = [$grant['langcode'] => $this->languageManager->getLanguage($grant['langcode'])];
|
||||
}
|
||||
else {
|
||||
$grant_languages = $node->getTranslationLanguages(TRUE);
|
||||
}
|
||||
foreach ($grant_languages as $grant_langcode => $grant_language) {
|
||||
// Only write grants; denies are implicit.
|
||||
if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
|
||||
$grant['nid'] = $node->id();
|
||||
$grant['langcode'] = $grant_langcode;
|
||||
// The record with the original langcode is used as the fallback.
|
||||
if ($grant['langcode'] == $fallback_langcode) {
|
||||
$grant['fallback'] = 1;
|
||||
}
|
||||
else {
|
||||
$grant['fallback'] = 0;
|
||||
}
|
||||
$query->values($grant);
|
||||
}
|
||||
}
|
||||
}
|
||||
$query->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete() {
|
||||
$this->database->truncate('node_access')->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function writeDefault() {
|
||||
$this->database->insert('node_access')
|
||||
->fields([
|
||||
'nid' => 0,
|
||||
'realm' => 'all',
|
||||
'gid' => 0,
|
||||
'grant_view' => 1,
|
||||
'grant_update' => 0,
|
||||
'grant_delete' => 0,
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count() {
|
||||
return $this->database->query('SELECT COUNT(*) FROM {node_access}')->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteNodeRecords(array $nids) {
|
||||
$this->database->delete('node_access')
|
||||
->condition('nid', $nids, 'IN')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query condition from an array of node access grants.
|
||||
*
|
||||
* @param array $node_access_grants
|
||||
* An array of grants, as returned by node_access_grants().
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\Condition
|
||||
* A condition object to be passed to $query->condition().
|
||||
*
|
||||
* @see node_access_grants()
|
||||
*/
|
||||
protected function buildGrantsQueryCondition(array $node_access_grants) {
|
||||
$grants = $this->database->condition('OR');
|
||||
foreach ($node_access_grants as $realm => $gids) {
|
||||
if (!empty($gids)) {
|
||||
$and = $this->database->condition('AND');
|
||||
$grants->condition($and
|
||||
->condition('gid', $gids, 'IN')
|
||||
->condition('realm', $realm)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $grants;
|
||||
}
|
||||
|
||||
}
|
||||
133
web/core/modules/node/src/NodeGrantDatabaseStorageInterface.php
Normal file
133
web/core/modules/node/src/NodeGrantDatabaseStorageInterface.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for node access grant storage.
|
||||
*
|
||||
* @ingroup node_access
|
||||
*/
|
||||
interface NodeGrantDatabaseStorageInterface {
|
||||
|
||||
/**
|
||||
* Checks all grants for a given account.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* A user object representing the user for whom the operation is to be
|
||||
* performed.
|
||||
*
|
||||
* @return int
|
||||
* Status of the access check.
|
||||
*/
|
||||
public function checkAll(AccountInterface $account);
|
||||
|
||||
/**
|
||||
* Alters a query when node access is required.
|
||||
*
|
||||
* @param mixed $query
|
||||
* Query that is being altered.
|
||||
* @param array $tables
|
||||
* A list of tables that need to be part of the alter.
|
||||
* @param string $operation
|
||||
* The operation to be performed on the node. Possible values are:
|
||||
* - "view".
|
||||
* - "update".
|
||||
* - "delete".
|
||||
* - "create".
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* A user object representing the user for whom the operation is to be
|
||||
* performed.
|
||||
* @param string $base_table
|
||||
* The base table of the query.
|
||||
*
|
||||
* @return int
|
||||
* Status of the access check.
|
||||
*/
|
||||
public function alterQuery($query, array $tables, $operation, AccountInterface $account, $base_table);
|
||||
|
||||
/**
|
||||
* Writes a list of grants to the database, deleting previously saved ones.
|
||||
*
|
||||
* If a realm is provided, it will only delete grants from that realm, but
|
||||
* it will always delete a grant from the 'all' realm. Modules that use
|
||||
* node access can use this method when doing mass updates due to widespread
|
||||
* permission changes.
|
||||
*
|
||||
* Note: Don't call this method directly from a contributed module. Call
|
||||
* \Drupal\node\NodeAccessControlHandlerInterface::acquireGrants() instead.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node whose grants are being written.
|
||||
* @param array $grants
|
||||
* A list of grants to write. Each grant is an array that must contain the
|
||||
* following keys: realm, gid, grant_view, grant_update, grant_delete.
|
||||
* The realm is specified by a particular module; the gid is as well, and
|
||||
* is a module-defined id to define grant privileges. each grant_* field
|
||||
* is a boolean value.
|
||||
* @param string $realm
|
||||
* (optional) If provided, read/write grants for that realm only. Defaults
|
||||
* to NULL.
|
||||
* @param bool $delete
|
||||
* (optional) If false, does not delete records. This is only for
|
||||
* optimization purposes, and assumes the caller has already performed a
|
||||
* mass delete of some form. Defaults to TRUE.
|
||||
*/
|
||||
public function write(NodeInterface $node, array $grants, $realm = NULL, $delete = TRUE);
|
||||
|
||||
/**
|
||||
* Deletes all node access entries.
|
||||
*/
|
||||
public function delete();
|
||||
|
||||
/**
|
||||
* Creates the default node access grant entry.
|
||||
*
|
||||
* The default node access grant is a special grant added to the node_access
|
||||
* table when no modules implement hook_node_grants. It grants view access
|
||||
* to any published node.
|
||||
*
|
||||
* @see self::access()
|
||||
*/
|
||||
public function writeDefault();
|
||||
|
||||
/**
|
||||
* Determines access to nodes based on node grants.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The entity for which to check 'create' access.
|
||||
* @param string $operation
|
||||
* The entity operation. Usually one of 'view', 'edit', 'create' or
|
||||
* 'delete'.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The user for which to check access.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result, either allowed or neutral. If there are no node
|
||||
* grants, the default grant defined by writeDefault() is applied.
|
||||
*
|
||||
* @see hook_node_grants()
|
||||
* @see hook_node_access_records()
|
||||
* @see \Drupal\node\NodeGrantDatabaseStorageInterface::writeDefault()
|
||||
*/
|
||||
public function access(NodeInterface $node, $operation, AccountInterface $account);
|
||||
|
||||
/**
|
||||
* Counts available node grants.
|
||||
*
|
||||
* @return int
|
||||
* Returns the amount of node grants.
|
||||
*/
|
||||
public function count();
|
||||
|
||||
/**
|
||||
* Remove the access records belonging to certain nodes.
|
||||
*
|
||||
* @param array $nids
|
||||
* A list of node IDs. The grant records belonging to these nodes will be
|
||||
* deleted.
|
||||
*/
|
||||
public function deleteNodeRecords(array $nids);
|
||||
|
||||
}
|
||||
150
web/core/modules/node/src/NodeInterface.php
Normal file
150
web/core/modules/node/src/NodeInterface.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node;
|
||||
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
use Drupal\Core\Entity\RevisionLogInterface;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a node entity.
|
||||
*/
|
||||
interface NodeInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface, RevisionLogInterface, EntityPublishedInterface {
|
||||
|
||||
/**
|
||||
* Denotes that the node is not published.
|
||||
*/
|
||||
const NOT_PUBLISHED = 0;
|
||||
|
||||
/**
|
||||
* Denotes that the node is published.
|
||||
*/
|
||||
const PUBLISHED = 1;
|
||||
|
||||
/**
|
||||
* Denotes that the node is not promoted to the front page.
|
||||
*/
|
||||
const NOT_PROMOTED = 0;
|
||||
|
||||
/**
|
||||
* Denotes that the node is promoted to the front page.
|
||||
*/
|
||||
const PROMOTED = 1;
|
||||
|
||||
/**
|
||||
* Denotes that the node is not sticky at the top of the page.
|
||||
*/
|
||||
const NOT_STICKY = 0;
|
||||
|
||||
/**
|
||||
* Denotes that the node is sticky at the top of the page.
|
||||
*/
|
||||
const STICKY = 1;
|
||||
|
||||
/**
|
||||
* Gets the node type.
|
||||
*
|
||||
* @return string
|
||||
* The node type.
|
||||
*/
|
||||
public function getType();
|
||||
|
||||
/**
|
||||
* Gets the node title.
|
||||
*
|
||||
* @return string|null
|
||||
* Title of the node, or NULL if the node doesn't yet have a title (for
|
||||
* example, if a new node is being previewed).
|
||||
*/
|
||||
public function getTitle();
|
||||
|
||||
/**
|
||||
* Sets the node title.
|
||||
*
|
||||
* @param string $title
|
||||
* The node title.
|
||||
*
|
||||
* @return $this
|
||||
* The called node entity.
|
||||
*/
|
||||
public function setTitle($title);
|
||||
|
||||
/**
|
||||
* Gets the node creation timestamp.
|
||||
*
|
||||
* @return int
|
||||
* Creation timestamp of the node.
|
||||
*/
|
||||
public function getCreatedTime();
|
||||
|
||||
/**
|
||||
* Sets the node creation timestamp.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* The node creation timestamp.
|
||||
*
|
||||
* @return $this
|
||||
* The called node entity.
|
||||
*/
|
||||
public function setCreatedTime($timestamp);
|
||||
|
||||
/**
|
||||
* Returns the node promotion status.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the node is promoted.
|
||||
*/
|
||||
public function isPromoted();
|
||||
|
||||
/**
|
||||
* Sets the node promoted status.
|
||||
*
|
||||
* @param bool $promoted
|
||||
* TRUE to set this node to promoted, FALSE to set it to not promoted.
|
||||
*
|
||||
* @return $this
|
||||
* The called node entity.
|
||||
*/
|
||||
public function setPromoted($promoted);
|
||||
|
||||
/**
|
||||
* Returns the node sticky status.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the node is sticky.
|
||||
*/
|
||||
public function isSticky();
|
||||
|
||||
/**
|
||||
* Sets the node sticky status.
|
||||
*
|
||||
* @param bool $sticky
|
||||
* TRUE to set this node to sticky, FALSE to set it to not sticky.
|
||||
*
|
||||
* @return $this
|
||||
* The called node entity.
|
||||
*/
|
||||
public function setSticky($sticky);
|
||||
|
||||
/**
|
||||
* Gets the node revision creation timestamp.
|
||||
*
|
||||
* @return int
|
||||
* The UNIX timestamp of when this revision was created.
|
||||
*/
|
||||
public function getRevisionCreationTime();
|
||||
|
||||
/**
|
||||
* Sets the node revision creation timestamp.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* The UNIX timestamp of when this revision was created.
|
||||
*
|
||||
* @return $this
|
||||
* The called node entity.
|
||||
*/
|
||||
public function setRevisionCreationTime($timestamp);
|
||||
|
||||
}
|
||||
113
web/core/modules/node/src/NodeListBuilder.php
Normal file
113
web/core/modules/node/src/NodeListBuilder.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node;
|
||||
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityListBuilder;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Routing\RedirectDestinationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a class to build a listing of node entities.
|
||||
*
|
||||
* @see \Drupal\node\Entity\Node
|
||||
*/
|
||||
class NodeListBuilder extends EntityListBuilder {
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* Constructs a new NodeListBuilder object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The entity storage class.
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter service.
|
||||
* @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
|
||||
* The redirect destination service.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, DateFormatterInterface $date_formatter, RedirectDestinationInterface $redirect_destination) {
|
||||
parent::__construct($entity_type, $storage);
|
||||
|
||||
$this->dateFormatter = $date_formatter;
|
||||
$this->redirectDestination = $redirect_destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('entity_type.manager')->getStorage($entity_type->id()),
|
||||
$container->get('date.formatter'),
|
||||
$container->get('redirect.destination')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
// Enable language column and filter if multiple languages are added.
|
||||
$header = [
|
||||
'title' => $this->t('Title'),
|
||||
'type' => [
|
||||
'data' => $this->t('Content type'),
|
||||
'class' => [RESPONSIVE_PRIORITY_MEDIUM],
|
||||
],
|
||||
'author' => [
|
||||
'data' => $this->t('Author'),
|
||||
'class' => [RESPONSIVE_PRIORITY_LOW],
|
||||
],
|
||||
'status' => $this->t('Status'),
|
||||
'changed' => [
|
||||
'data' => $this->t('Updated'),
|
||||
'class' => [RESPONSIVE_PRIORITY_LOW],
|
||||
],
|
||||
];
|
||||
if (\Drupal::languageManager()->isMultilingual()) {
|
||||
$header['language_name'] = [
|
||||
'data' => $this->t('Language'),
|
||||
'class' => [RESPONSIVE_PRIORITY_LOW],
|
||||
];
|
||||
}
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
/** @var \Drupal\node\NodeInterface $entity */
|
||||
$row['title']['data'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => $entity->label(),
|
||||
'#url' => $entity->toUrl(),
|
||||
];
|
||||
$row['type'] = node_get_type_label($entity);
|
||||
$row['author']['data'] = [
|
||||
'#theme' => 'username',
|
||||
'#account' => $entity->getOwner(),
|
||||
];
|
||||
$row['status'] = $entity->isPublished() ? $this->t('published') : $this->t('not published');
|
||||
$row['changed'] = $this->dateFormatter->format($entity->getChangedTime(), 'short');
|
||||
$language_manager = \Drupal::languageManager();
|
||||
if ($language_manager->isMultilingual()) {
|
||||
$row['language_name'] = $language_manager->getLanguageName($entity->language()->getId());
|
||||
}
|
||||
$row['operations']['data'] = $this->buildOperations($entity);
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
}
|
||||
91
web/core/modules/node/src/NodePermissions.php
Normal file
91
web/core/modules/node/src/NodePermissions.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\node;
|
||||
|
||||
use Drupal\Core\DependencyInjection\AutowireTrait;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\BundlePermissionHandlerTrait;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
|
||||
/**
|
||||
* Provides dynamic permissions for nodes of different types.
|
||||
*/
|
||||
class NodePermissions implements ContainerInjectionInterface {
|
||||
|
||||
use AutowireTrait;
|
||||
use BundlePermissionHandlerTrait;
|
||||
use StringTranslationTrait;
|
||||
|
||||
public function __construct(
|
||||
protected ?EntityTypeManagerInterface $entityTypeManager = NULL,
|
||||
) {
|
||||
if ($entityTypeManager === NULL) {
|
||||
@trigger_error('Calling ' . __METHOD__ . ' without the $entityTypeManager argument is deprecated in drupal:11.2.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/node/3515921', E_USER_DEPRECATED);
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of node type permissions.
|
||||
*
|
||||
* @return array
|
||||
* The node type permissions.
|
||||
*
|
||||
* @see \Drupal\user\PermissionHandlerInterface::getPermissions()
|
||||
*/
|
||||
public function nodeTypePermissions() {
|
||||
return $this->generatePermissions(
|
||||
$this->entityTypeManager->getStorage('node_type')->loadMultiple(),
|
||||
[$this, 'buildPermissions']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of node permissions for a given node type.
|
||||
*
|
||||
* @param \Drupal\node\Entity\NodeType $type
|
||||
* The node type.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of permission names and descriptions.
|
||||
*/
|
||||
protected function buildPermissions(NodeType $type) {
|
||||
$type_id = $type->id();
|
||||
$type_params = ['%type_name' => $type->label()];
|
||||
|
||||
return [
|
||||
"create $type_id content" => [
|
||||
'title' => $this->t('%type_name: Create new content', $type_params),
|
||||
],
|
||||
"edit own $type_id content" => [
|
||||
'title' => $this->t('%type_name: Edit own content', $type_params),
|
||||
'description' => $this->t('Note that anonymous users with this permission are able to edit any content created by any anonymous user.'),
|
||||
],
|
||||
"edit any $type_id content" => [
|
||||
'title' => $this->t('%type_name: Edit any content', $type_params),
|
||||
],
|
||||
"delete own $type_id content" => [
|
||||
'title' => $this->t('%type_name: Delete own content', $type_params),
|
||||
'description' => $this->t('Note that anonymous users with this permission are able to delete any content created by any anonymous user.'),
|
||||
],
|
||||
"delete any $type_id content" => [
|
||||
'title' => $this->t('%type_name: Delete any content', $type_params),
|
||||
],
|
||||
"view $type_id revisions" => [
|
||||
'title' => $this->t('%type_name: View revisions', $type_params),
|
||||
'description' => $this->t('To view a revision, you also need permission to view the content item.'),
|
||||
],
|
||||
"revert $type_id revisions" => [
|
||||
'title' => $this->t('%type_name: Revert revisions', $type_params),
|
||||
'description' => $this->t('To revert a revision, you also need permission to edit the content item.'),
|
||||
],
|
||||
"delete $type_id revisions" => [
|
||||
'title' => $this->t('%type_name: Delete revisions', $type_params),
|
||||
'description' => $this->t('To delete a revision, you also need permission to delete the content item.'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user