updated core from 8.4 to 8.5 : bug with login_destination
This commit is contained in:
@@ -0,0 +1,817 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_moderation
|
||||
- node
|
||||
- user
|
||||
id: moderated_content
|
||||
label: 'Moderated content'
|
||||
module: views
|
||||
description: 'Find and moderate content.'
|
||||
tag: ''
|
||||
base_table: node_field_revision
|
||||
base_field: vid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'view any unpublished content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: 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
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 50
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
tags:
|
||||
previous: '‹ Previous'
|
||||
next: 'Next ›'
|
||||
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
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
override: true
|
||||
sticky: true
|
||||
caption: ''
|
||||
summary: ''
|
||||
description: ''
|
||||
columns:
|
||||
title: title
|
||||
type: type
|
||||
name: name
|
||||
moderation_state: moderation_state
|
||||
changed: changed
|
||||
info:
|
||||
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: ''
|
||||
moderation_state:
|
||||
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: ''
|
||||
default: changed
|
||||
empty_table: true
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_revision
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
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: false
|
||||
ellipsis: false
|
||||
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: string
|
||||
settings:
|
||||
link_to_entity: true
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: field
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
relationship: nid
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
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
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: field
|
||||
name:
|
||||
id: name
|
||||
table: users_field_data
|
||||
field: name
|
||||
relationship: uid
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
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
|
||||
click_sort_column: value
|
||||
type: user_name
|
||||
settings:
|
||||
link_to_entity: true
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
plugin_id: field
|
||||
moderation_state:
|
||||
id: moderation_state
|
||||
table: node_field_revision
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Moderation state'
|
||||
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: content_moderation_state
|
||||
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
|
||||
entity_type: node
|
||||
plugin_id: field
|
||||
changed:
|
||||
id: changed
|
||||
table: node_field_revision
|
||||
field: changed
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Updated
|
||||
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: false
|
||||
ellipsis: false
|
||||
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: timestamp
|
||||
settings:
|
||||
date_format: short
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: node
|
||||
entity_field: changed
|
||||
plugin_id: field
|
||||
operations:
|
||||
id: operations
|
||||
table: node_revision
|
||||
field: operations
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
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
|
||||
entity_type: node
|
||||
plugin_id: entity_operations
|
||||
filters:
|
||||
latest_revision:
|
||||
id: latest_revision
|
||||
table: node_revision
|
||||
field: latest_revision
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: '='
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: node
|
||||
plugin_id: latest_revision
|
||||
title:
|
||||
id: title
|
||||
table: node_field_revision
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: contains
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: title_op
|
||||
label: Title
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: title_op
|
||||
identifier: title
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: string
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
relationship: nid
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: type_op
|
||||
label: 'Content type'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: type_op
|
||||
identifier: type
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
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: { }
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
moderation_state:
|
||||
id: moderation_state
|
||||
table: node_field_revision
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value:
|
||||
editorial-draft: editorial-draft
|
||||
editorial-archived: editorial-archived
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: moderation_state_op
|
||||
label: 'Moderation state'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: moderation_state_op
|
||||
identifier: moderation_state
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
reduce: true
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: node
|
||||
plugin_id: moderation_state_filter
|
||||
langcode:
|
||||
id: langcode
|
||||
table: node_field_revision
|
||||
field: langcode
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: langcode_op
|
||||
label: Language
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: langcode_op
|
||||
identifier: langcode
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
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: { }
|
||||
entity_type: node
|
||||
entity_field: langcode
|
||||
plugin_id: language
|
||||
moderation_state_1:
|
||||
id: moderation_state_1
|
||||
table: node_field_revision
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: 'not in'
|
||||
value:
|
||||
editorial-published: editorial-published
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: node
|
||||
plugin_id: moderation_state_filter
|
||||
sorts: { }
|
||||
title: 'Moderated content'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty:
|
||||
area_text_custom:
|
||||
id: area_text_custom
|
||||
table: views
|
||||
field: area_text_custom
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
empty: true
|
||||
tokenize: false
|
||||
content: 'No moderated content available. Only pending versions of content, such as drafts, are listed here.'
|
||||
plugin_id: text_custom
|
||||
relationships:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_revision
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: 'Get the actual content from a content revision.'
|
||||
required: false
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: standard
|
||||
uid:
|
||||
id: uid
|
||||
table: node_field_revision
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: User
|
||||
required: false
|
||||
entity_type: node
|
||||
entity_field: uid
|
||||
plugin_id: standard
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
moderated_content:
|
||||
display_plugin: page
|
||||
id: moderated_content
|
||||
display_title: 'Moderated content'
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: admin/content/moderated
|
||||
display_description: ''
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
@@ -0,0 +1,18 @@
|
||||
views.filter.latest_revision:
|
||||
type: views_filter
|
||||
label: 'Latest revision'
|
||||
mapping:
|
||||
value:
|
||||
type: string
|
||||
label: 'Value'
|
||||
|
||||
views.filter.moderation_state_filter:
|
||||
type: views.filter.in_operator
|
||||
label: 'Moderation state filter'
|
||||
mapping:
|
||||
limit_workflows:
|
||||
type: sequence
|
||||
label: 'Workflow'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Workflow'
|
||||
@@ -1,15 +1,15 @@
|
||||
name: 'Content Moderation'
|
||||
type: module
|
||||
description: 'Provides moderation states for content'
|
||||
description: 'Provides moderation states for content.'
|
||||
# version: VERSION
|
||||
# core: 8.x
|
||||
package: Core (Experimental)
|
||||
package: Core
|
||||
configure: entity.workflow.collection
|
||||
dependencies:
|
||||
- workflows
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-01-03
|
||||
version: '8.4.4'
|
||||
# Information added by Drupal.org packaging script on 2018-03-07
|
||||
version: '8.5.0'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1515021228
|
||||
datestamp: 1520457826
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
content_moderation.workflows:
|
||||
deriver: 'Drupal\content_moderation\Plugin\Derivative\DynamicLocalTasks'
|
||||
weight: 100
|
||||
|
||||
content_moderation.content:
|
||||
title: 'Overview'
|
||||
route_name: system.admin_content
|
||||
parent_id: system.admin_content
|
||||
|
||||
content_moderation.moderated_content:
|
||||
title: 'Moderated content'
|
||||
route_name: content_moderation.admin_moderated_content
|
||||
parent_id: system.admin_content
|
||||
weight: 1
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
use Drupal\content_moderation\EntityOperations;
|
||||
use Drupal\content_moderation\EntityTypeInfo;
|
||||
use Drupal\content_moderation\ContentPreprocess;
|
||||
use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublishNode;
|
||||
use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublishNode;
|
||||
use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish;
|
||||
use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublish;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
@@ -20,10 +20,12 @@ use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\workflows\WorkflowInterface;
|
||||
use Drupal\node\Plugin\Action\PublishNode;
|
||||
use Drupal\node\Plugin\Action\UnpublishNode;
|
||||
use Drupal\Core\Action\Plugin\Action\PublishAction;
|
||||
use Drupal\Core\Action\Plugin\Action\UnpublishAction;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
use Drupal\views\Entity\View;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
@@ -34,13 +36,20 @@ function content_moderation_help($route_name, RouteMatchInterface $route_match)
|
||||
case 'help.page.content_moderation':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Content Moderation module provides moderation for content by applying workflows to content. For more information, see the <a href=":content_moderation">online documentation for the Content Moderation module</a>.', [':content_moderation' => 'https://www.drupal.org/documentation/modules/content_moderation']) . '</p>';
|
||||
$output .= '<p>' . t('The Content Moderation module allows you to expand on Drupal\'s "unpublished" and "published" states for content. It allows you to have a published version that is live, but have a separate working copy that is undergoing review before it is published. This is achieved by using <a href=":workflows">Workflows</a> to apply different states and transitions to entities as needed. For more information, see the <a href=":content_moderation">online documentation for the Content Moderation module</a>.', [':content_moderation' => 'https://www.drupal.org/documentation/modules/content_moderation', ':workflows' => Url::fromRoute('help.page', ['name' => 'workflows'])->toString()]) . '</p>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . t('Configuring workflows') . '</dt>';
|
||||
$output .= '<dd>' . t('Enable the Workflow UI module to create, edit and delete content moderation workflows.') . '</p>';
|
||||
$output .= '<dt>' . t('Applying workflows') . '</dt>';
|
||||
$output .= '<dd>' . t('Content Moderation allows you to apply <a href=":workflows">Workflows</a> to content, custom blocks, and other <a href=":field_help" title="Field module help, with background on content entities">content entities</a>, to provide more fine-grained publishing options. For example, a Basic page might have states such as Draft and Published, with allowed transitions such as Draft to Published (making the current revision "live"), and Published to Draft (making a new draft revision of published content).', [':workflows' => Url::fromRoute('help.page', ['name' => 'workflows'])->toString(), ':field_help' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</dd>';
|
||||
if (\Drupal::moduleHandler()->moduleExists('views')) {
|
||||
$moderated_content_view = View::load('moderated_content');
|
||||
if (isset($moderated_content_view) && $moderated_content_view->status() === TRUE) {
|
||||
$output .= '<dt>' . t('Moderating content') . '</dt>';
|
||||
$output .= '<dd>' . t('You can view a list of content awaiting moderation on the <a href=":moderated">moderated content page</a>. This will show any content in an unpublished state, such as Draft or Archived, to help surface content that requires more work from content editors.', [':moderated' => Url::fromRoute('view.moderated_content.moderated_content')->toString()]) . '</dd>';
|
||||
}
|
||||
}
|
||||
$output .= '<dt>' . t('Configure Content Moderation permissions') . '</dt>';
|
||||
$output .= '<dd>' . t('Each transition is exposed as a permission. If a user has the permission for a transition, then they can move that node from the start state to the end state') . '</p>';
|
||||
$output .= '<dd>' . t('Each transition is exposed as a permission. If a user has the permission for a transition, they can use the transition to change the state of the content item, from Draft to Published.') . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
}
|
||||
@@ -118,6 +127,15 @@ function content_moderation_entity_translation_delete(EntityInterface $translati
|
||||
->entityTranslationDelete($translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_prepare_form().
|
||||
*/
|
||||
function content_moderation_entity_prepare_form(EntityInterface $entity, $operation, FormStateInterface $form_state) {
|
||||
\Drupal::service('class_resolver')
|
||||
->getInstanceFromDefinition(EntityTypeInfo::class)
|
||||
->entityPrepareForm($entity, $operation, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_alter().
|
||||
*/
|
||||
@@ -227,13 +245,15 @@ function content_moderation_action_info_alter(&$definitions) {
|
||||
|
||||
// The publish/unpublish actions are not valid on moderated entities. So swap
|
||||
// their implementations out for alternates that will become a no-op on a
|
||||
// moderated node. If another module has already swapped out those classes,
|
||||
// moderated entity. If another module has already swapped out those classes,
|
||||
// though, we'll be polite and do nothing.
|
||||
if (isset($definitions['node_publish_action']['class']) && $definitions['node_publish_action']['class'] == PublishNode::class) {
|
||||
$definitions['node_publish_action']['class'] = ModerationOptOutPublishNode::class;
|
||||
}
|
||||
if (isset($definitions['node_unpublish_action']['class']) && $definitions['node_unpublish_action']['class'] == UnpublishNode::class) {
|
||||
$definitions['node_unpublish_action']['class'] = ModerationOptOutUnpublishNode::class;
|
||||
foreach ($definitions as &$definition) {
|
||||
if ($definition['id'] === 'entity:publish_action' && $definition['class'] == PublishAction::class) {
|
||||
$definition['class'] = ModerationOptOutPublish::class;
|
||||
}
|
||||
if ($definition['id'] === 'entity:unpublish_action' && $definition['class'] == UnpublishAction::class) {
|
||||
$definition['class'] = ModerationOptOutUnpublish::class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +261,7 @@ function content_moderation_action_info_alter(&$definitions) {
|
||||
* Implements hook_entity_bundle_info_alter().
|
||||
*/
|
||||
function content_moderation_entity_bundle_info_alter(&$bundles) {
|
||||
$translatable = FALSE;
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
|
||||
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
|
||||
@@ -249,10 +270,18 @@ function content_moderation_entity_bundle_info_alter(&$bundles) {
|
||||
foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle_id) {
|
||||
if (isset($bundles[$entity_type_id][$bundle_id])) {
|
||||
$bundles[$entity_type_id][$bundle_id]['workflow'] = $workflow->id();
|
||||
// If we have even one moderation-enabled translatable bundle, we need
|
||||
// to make the moderation state bundle translatable as well, to enable
|
||||
// the revision translation merge logic also for content moderation
|
||||
// state revisions.
|
||||
if (!empty($bundles[$entity_type_id][$bundle_id]['translatable'])) {
|
||||
$translatable = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$bundles['content_moderation_state']['content_moderation_state']['translatable'] = $translatable;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,13 +319,3 @@ function content_moderation_workflow_update(WorkflowInterface $entity) {
|
||||
// Clear field cache so extra field is added or removed.
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_rest_resource_alter().
|
||||
*/
|
||||
function content_moderation_rest_resource_alter(&$definitions) {
|
||||
// ContentModerationState is an internal entity type. Therefore it should not
|
||||
// be exposed via REST.
|
||||
// @see \Drupal\content_moderation\ContentModerationStateAccessControlHandler
|
||||
unset($definitions['entity:content_moderation_state']);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post update functions for the Content Moderation module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Synchronize moderation state default revisions with their host entities.
|
||||
*/
|
||||
function content_moderation_post_update_update_cms_default_revisions(&$sandbox) {
|
||||
// For every moderated entity, identify the default revision ID, track the
|
||||
// corresponding "content_moderation_state" revision and save it as the new
|
||||
// default revision, if needed.
|
||||
|
||||
// Initialize sandbox info.
|
||||
$entity_type_id = &$sandbox['entity_type_id'];
|
||||
if (!isset($entity_type_id)) {
|
||||
$sandbox['bundles'] = [];
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
|
||||
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
|
||||
$plugin = $workflow->getTypePlugin();
|
||||
foreach ($plugin->getEntityTypes() as $entity_type_id) {
|
||||
$sandbox['entity_type_ids'][$entity_type_id] = $entity_type_id;
|
||||
foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle) {
|
||||
$sandbox['bundles'][$entity_type_id][$bundle] = $bundle;
|
||||
}
|
||||
}
|
||||
}
|
||||
$sandbox['offset'] = 0;
|
||||
$sandbox['limit'] = Settings::get('entity_update_batch_size', 50);
|
||||
$sandbox['total'] = count($sandbox['entity_type_ids']);
|
||||
$entity_type_id = array_shift($sandbox['entity_type_ids']);
|
||||
}
|
||||
|
||||
// If there are no moderated bundles or we processed all of them, we are done.
|
||||
$entity_type_manager = \Drupal::entityTypeManager();
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $content_moderation_state_storage */
|
||||
$content_moderation_state_storage = $entity_type_manager->getStorage('content_moderation_state');
|
||||
if (!$entity_type_id) {
|
||||
$content_moderation_state_storage->resetCache();
|
||||
$sandbox['#finished'] = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve a batch of moderated entities to be processed.
|
||||
$storage = $entity_type_manager->getStorage($entity_type_id);
|
||||
$entity_type = $entity_type_manager->getDefinition($entity_type_id);
|
||||
$query = $storage->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->sort($entity_type->getKey('id'))
|
||||
->range($sandbox['offset'], $sandbox['limit']);
|
||||
$bundle_key = $entity_type->getKey('bundle');
|
||||
if ($bundle_key && !empty($sandbox['bundles'][$entity_type_id])) {
|
||||
$bundles = array_keys($sandbox['bundles'][$entity_type_id]);
|
||||
$query->condition($bundle_key, $bundles, 'IN');
|
||||
}
|
||||
$entity_ids = $query->execute();
|
||||
|
||||
// Compute progress status and skip to the next entity type, if needed.
|
||||
$sandbox['#finished'] = ($sandbox['total'] - count($sandbox['entity_type_ids']) - 1) / $sandbox['total'];
|
||||
if (!$entity_ids) {
|
||||
$sandbox['offset'] = 0;
|
||||
$entity_type_id = array_shift($sandbox['entity_type_ids']) ?: FALSE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the "content_moderation_state" revisions corresponding to the
|
||||
// moderated entity default revisions.
|
||||
$result = $content_moderation_state_storage->getQuery()
|
||||
->allRevisions()
|
||||
->condition('content_entity_type_id', $entity_type_id)
|
||||
->condition('content_entity_revision_id', array_keys($entity_ids), 'IN')
|
||||
->execute();
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $revisions */
|
||||
$revisions = $content_moderation_state_storage->loadMultipleRevisions(array_keys($result));
|
||||
|
||||
// Update "content_moderation_state" data.
|
||||
foreach ($revisions as $revision) {
|
||||
if (!$revision->isDefaultRevision()) {
|
||||
$revision->setNewRevision(FALSE);
|
||||
$revision->isDefaultRevision(TRUE);
|
||||
$content_moderation_state_storage->save($revision);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear static cache to avoid memory issues.
|
||||
$storage->resetCache($entity_ids);
|
||||
|
||||
$sandbox['offset'] += $sandbox['limit'];
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
content_moderation.admin_moderated_content:
|
||||
path: '/admin/content/moderated'
|
||||
defaults:
|
||||
_controller: '\Drupal\content_moderation\Controller\ModeratedContentController::nodeListing'
|
||||
_title: 'Moderated content'
|
||||
requirements:
|
||||
_permission: 'view any unpublished content'
|
||||
|
||||
content_moderation.workflow_type_edit_form:
|
||||
path: '/admin/config/workflow/workflows/manage/{workflow}/type/{entity_type_id}'
|
||||
defaults:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
paramconverter.latest_revision:
|
||||
class: Drupal\content_moderation\ParamConverter\EntityRevisionConverter
|
||||
arguments: ['@entity.manager', '@content_moderation.moderation_information']
|
||||
parent: paramconverter.entity
|
||||
tags:
|
||||
- { name: paramconverter, priority: 5 }
|
||||
content_moderation.state_transition_validation:
|
||||
@@ -20,3 +20,8 @@ services:
|
||||
arguments: ['@config.manager', '@entity_type.manager']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
content_moderation.route_subscriber:
|
||||
class: Drupal\content_moderation\Routing\ContentModerationRouteSubscriber
|
||||
arguments: ['@entity_type.manager']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @file
|
||||
* Component styles for the content_moderation module.
|
||||
*/
|
||||
ul.entity-moderation-form {
|
||||
.entity-moderation-form {
|
||||
list-style: none;
|
||||
display: -webkit-flex; /* Safari */
|
||||
display: flex;
|
||||
@@ -12,31 +12,27 @@ ul.entity-moderation-form {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
ul.entity-moderation-form li {
|
||||
.entity-moderation-form__item {
|
||||
margin-right: 2em;
|
||||
display: table;
|
||||
}
|
||||
|
||||
ul.entity-moderation-form li:last-child {
|
||||
.entity-moderation-form__item:last-child {
|
||||
-webkit-align-self: flex-end; /* Safari */
|
||||
align-self: flex-end;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
ul.entity-moderation-form .form-item {
|
||||
.entity-moderation-form .form-item {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
ul.entity-moderation-form .form-item label {
|
||||
.entity-moderation-form .form-item label {
|
||||
padding-bottom: 0.25em;
|
||||
display: table;
|
||||
}
|
||||
|
||||
ul.entity-moderation-form input[type=submit] {
|
||||
.entity-moderation-form input[type=submit] {
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
|
||||
ul.entity-moderation-form .btn {
|
||||
margin-bottom: 1.1em;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* @file
|
||||
* Theme styles for the content_moderation module.
|
||||
*/
|
||||
ul.entity-moderation-form {
|
||||
.entity-moderation-form {
|
||||
border: 1px dashed #bbb;
|
||||
margin: 2em 0em;
|
||||
margin: 2em 0;
|
||||
background: #fff;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Controller;
|
||||
|
||||
use Drupal\content_moderation\ModeratedNodeListBuilder;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
|
||||
/**
|
||||
* Defines a controller to list moderated nodes.
|
||||
*/
|
||||
class ModeratedContentController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Provides the listing page for moderated nodes.
|
||||
*
|
||||
* @return array
|
||||
* A render array as expected by drupal_render().
|
||||
*/
|
||||
public function nodeListing() {
|
||||
$entity_type = $this->entityTypeManager()->getDefinition('node');
|
||||
|
||||
return $this->entityTypeManager()->createHandlerInstance(ModeratedNodeListBuilder::class, $entity_type)->render();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,7 @@ use Drupal\user\UserInterface;
|
||||
* data_table = "content_moderation_state_field_data",
|
||||
* revision_data_table = "content_moderation_state_field_revision",
|
||||
* translatable = TRUE,
|
||||
* internal = TRUE,
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "revision" = "revision_id",
|
||||
@@ -221,4 +222,16 @@ class ContentModerationState extends ContentEntityBase implements ContentModerat
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getFieldsToSkipFromTranslationChangesCheck() {
|
||||
$field_names = parent::getFieldsToSkipFromTranslationChangesCheck();
|
||||
// We need to skip the parent entity revision ID, since that will always
|
||||
// change on every save, otherwise every translation would be marked as
|
||||
// affected regardless of actual changes.
|
||||
$field_names[] = 'content_entity_revision_id';
|
||||
return $field_names;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,11 +35,6 @@ class ModerationHandler implements ModerationHandlerInterface, EntityHandlerInte
|
||||
// This is probably not necessary if configuration is setup correctly.
|
||||
$entity->setNewRevision(TRUE);
|
||||
$entity->isDefaultRevision($default_revision);
|
||||
if ($entity->hasField('revision_translation_affected')) {
|
||||
// @todo remove this when revision and translation issues have been
|
||||
// resolved. https://www.drupal.org/node/2860097
|
||||
$entity->set('revision_translation_affected', TRUE);
|
||||
}
|
||||
|
||||
// Update publishing status if it can be updated and if it needs updating.
|
||||
if (($entity instanceof EntityPublishedInterface) && $entity->isPublished() !== $published_state) {
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ class EntityModerationRouteProvider implements EntityRouteProviderInterface, Ent
|
||||
->setOption('parameters', [
|
||||
$entity_type_id => [
|
||||
'type' => 'entity:' . $entity_type_id,
|
||||
'load_pending_revision' => 1,
|
||||
'load_latest_revision' => TRUE,
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\content_moderation\Form\EntityModerationForm;
|
||||
use Drupal\Core\Routing\RouteBuilderInterface;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -48,6 +50,13 @@ class EntityOperations implements ContainerInjectionInterface {
|
||||
*/
|
||||
protected $bundleInfo;
|
||||
|
||||
/**
|
||||
* The router builder service.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteBuilderInterface
|
||||
*/
|
||||
protected $routerBuilder;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityOperations object.
|
||||
*
|
||||
@@ -59,12 +68,15 @@ class EntityOperations implements ContainerInjectionInterface {
|
||||
* The form builder.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
|
||||
* The entity bundle information service.
|
||||
* @param \Drupal\Core\Routing\RouteBuilderInterface $router_builder
|
||||
* The router builder service.
|
||||
*/
|
||||
public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, EntityTypeBundleInfoInterface $bundle_info) {
|
||||
public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, EntityTypeBundleInfoInterface $bundle_info, RouteBuilderInterface $router_builder) {
|
||||
$this->moderationInfo = $moderation_info;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->formBuilder = $form_builder;
|
||||
$this->bundleInfo = $bundle_info;
|
||||
$this->routerBuilder = $router_builder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +87,8 @@ class EntityOperations implements ContainerInjectionInterface {
|
||||
$container->get('content_moderation.moderation_information'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('form_builder'),
|
||||
$container->get('entity_type.bundle.info')
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('router.builder')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,10 +111,9 @@ class EntityOperations implements ContainerInjectionInterface {
|
||||
$current_state = $workflow->getTypePlugin()
|
||||
->getState($entity->moderation_state->value);
|
||||
|
||||
// This entity is default if it is new, a new translation, the default
|
||||
// revision, or the default revision is not published.
|
||||
// This entity is default if it is new, the default revision, or the
|
||||
// default revision is not published.
|
||||
$update_default_revision = $entity->isNew()
|
||||
|| $entity->isNewTranslation()
|
||||
|| $current_state->isDefaultRevisionState()
|
||||
|| !$this->moderationInfo->isDefaultRevisionPublished($entity);
|
||||
|
||||
@@ -134,6 +146,12 @@ class EntityOperations implements ContainerInjectionInterface {
|
||||
if ($this->moderationInfo->isModeratedEntity($entity)) {
|
||||
$this->updateOrCreateFromEntity($entity);
|
||||
}
|
||||
// When updating workflow settings for Content Moderation, we need to
|
||||
// rebuild routes as we may be enabling new entity types and the related
|
||||
// entity forms.
|
||||
elseif ($entity instanceof Workflow && $entity->getTypePlugin()->getPluginId() == 'content_moderation') {
|
||||
$this->routerBuilder->setRebuildNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,9 +165,10 @@ class EntityOperations implements ContainerInjectionInterface {
|
||||
$entity_revision_id = $entity->getRevisionId();
|
||||
$workflow = $this->moderationInfo->getWorkflowForEntity($entity);
|
||||
$content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = $this->entityTypeManager->getStorage('content_moderation_state');
|
||||
|
||||
if (!($content_moderation_state instanceof ContentModerationStateInterface)) {
|
||||
$storage = $this->entityTypeManager->getStorage('content_moderation_state');
|
||||
$content_moderation_state = $storage->create([
|
||||
'content_entity_type_id' => $entity->getEntityTypeId(),
|
||||
'content_entity_id' => $entity->id(),
|
||||
@@ -159,11 +178,6 @@ class EntityOperations implements ContainerInjectionInterface {
|
||||
]);
|
||||
$content_moderation_state->workflow->target_id = $workflow->id();
|
||||
}
|
||||
elseif ($content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
|
||||
// If a new revision of the content has been created, add a new content
|
||||
// moderation state revision.
|
||||
$content_moderation_state->setNewRevision(TRUE);
|
||||
}
|
||||
|
||||
// Sync translations.
|
||||
if ($entity->getEntityType()->hasKey('langcode')) {
|
||||
@@ -176,6 +190,12 @@ class EntityOperations implements ContainerInjectionInterface {
|
||||
}
|
||||
}
|
||||
|
||||
// If a new revision of the content has been created, add a new content
|
||||
// moderation state revision.
|
||||
if (!$content_moderation_state->isNew() && $content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
|
||||
$content_moderation_state = $storage->createRevision($content_moderation_state, $entity->isDefaultRevision());
|
||||
}
|
||||
|
||||
// Create the ContentModerationState entity for the inserted entity.
|
||||
$moderation_state = $entity->moderation_state->value;
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
@@ -245,27 +265,31 @@ class EntityOperations implements ContainerInjectionInterface {
|
||||
* @see EntityFieldManagerInterface::getExtraFields()
|
||||
*/
|
||||
public function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
if (!$this->moderationInfo->isModeratedEntity($entity)) {
|
||||
return;
|
||||
}
|
||||
if (!$this->moderationInfo->isLatestRevision($entity)) {
|
||||
if (isset($entity->in_preview) && $entity->in_preview) {
|
||||
return;
|
||||
}
|
||||
if ($this->moderationInfo->isLiveRevision($entity)) {
|
||||
// If the component is not defined for this display, we have nothing to do.
|
||||
if (!$display->getComponent('content_moderation_control')) {
|
||||
return;
|
||||
}
|
||||
// Don't display the moderation form when when:
|
||||
// - The revision is not translation affected.
|
||||
// - There are more than one translation languages.
|
||||
// - The entity has pending revisions.
|
||||
if (!$this->moderationInfo->isPendingRevisionAllowed($entity)) {
|
||||
// The moderation form should be displayed only when viewing the latest
|
||||
// (translation-affecting) revision, unless it was created as published
|
||||
// default revision.
|
||||
if (!$entity->isLatestRevision() && !$entity->isLatestTranslationAffectedRevision()) {
|
||||
return;
|
||||
}
|
||||
if (($entity->isDefaultRevision() || $entity->wasDefaultRevision()) && ($moderation_state = $entity->get('moderation_state')->value)) {
|
||||
$workflow = $this->moderationInfo->getWorkflowForEntity($entity);
|
||||
if ($workflow->getTypePlugin()->getState($moderation_state)->isPublishedState()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$component = $display->getComponent('content_moderation_control');
|
||||
if ($component) {
|
||||
$build['content_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
|
||||
}
|
||||
$build['content_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@ use Drupal\Core\Entity\BundleEntityFormBase;
|
||||
use Drupal\Core\Entity\ContentEntityFormInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Form\FormInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
@@ -268,6 +270,40 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the entity form entity object with a proper revision object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity being edited.
|
||||
* @param string $operation
|
||||
* The entity form operation.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*
|
||||
* @see hook_entity_prepare_form()
|
||||
*/
|
||||
public function entityPrepareForm(EntityInterface $entity, $operation, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
|
||||
$form_object = $form_state->getFormObject();
|
||||
|
||||
if ($this->isModeratedEntityEditForm($form_object) && !$entity->isNew()) {
|
||||
// Generate a proper revision object for the current entity. This allows
|
||||
// to correctly handle translatable entities having pending revisions.
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $new_revision */
|
||||
$new_revision = $storage->createRevision($entity, FALSE);
|
||||
|
||||
// Restore the revision ID as other modules may expect to find it still
|
||||
// populated. This will reset the "new revision" flag, however the entity
|
||||
// object will be marked as a new revision again on submit.
|
||||
// @see \Drupal\Core\Entity\ContentEntityForm::buildEntity()
|
||||
$revision_key = $new_revision->getEntityType()->getKey('revision');
|
||||
$new_revision->set($revision_key, $new_revision->getLoadedRevisionId());
|
||||
$form_object->setEntity($new_revision);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alters bundle forms to enforce revision handling.
|
||||
*
|
||||
@@ -291,57 +327,15 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
||||
$this->entityTypeManager->getHandler($config_entity_type->getBundleOf(), 'moderation')->enforceRevisionsBundleFormAlter($form, $form_state, $form_id);
|
||||
}
|
||||
}
|
||||
elseif ($form_object instanceof ContentEntityFormInterface && in_array($form_object->getOperation(), ['edit', 'default'])) {
|
||||
elseif ($this->isModeratedEntityEditForm($form_object)) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $form_object->getEntity();
|
||||
if ($this->moderationInfo->isModeratedEntity($entity)) {
|
||||
$this->entityTypeManager
|
||||
->getHandler($entity->getEntityTypeId(), 'moderation')
|
||||
->enforceRevisionsEntityFormAlter($form, $form_state, $form_id);
|
||||
|
||||
if (!$this->moderationInfo->isPendingRevisionAllowed($entity)) {
|
||||
$latest_revision = $this->moderationInfo->getLatestRevision($entity->getEntityTypeId(), $entity->id());
|
||||
if ($entity->bundle()) {
|
||||
$bundle_type_id = $entity->getEntityType()->getBundleEntityType();
|
||||
$bundle = $this->entityTypeManager->getStorage($bundle_type_id)->load($entity->bundle());
|
||||
$type_label = $bundle->label();
|
||||
}
|
||||
else {
|
||||
$type_label = $entity->getEntityType()->getLabel();
|
||||
}
|
||||
|
||||
$translation = $this->moderationInfo->getAffectedRevisionTranslation($latest_revision);
|
||||
$args = [
|
||||
'@type_label' => $type_label,
|
||||
'@latest_revision_edit_url' => $translation->toUrl('edit-form', ['language' => $translation->language()])->toString(),
|
||||
'@latest_revision_delete_url' => $translation->toUrl('delete-form', ['language' => $translation->language()])->toString(),
|
||||
];
|
||||
$label = $this->t('Unable to save this @type_label.', $args);
|
||||
$message = $this->t('<a href="@latest_revision_edit_url">Publish</a> or <a href="@latest_revision_delete_url">delete</a> the latest revision to allow all workflow transitions.', $args);
|
||||
$full_message = $this->t('Unable to save this @type_label. <a href="@latest_revision_edit_url">Publish</a> or <a href="@latest_revision_delete_url">delete</a> the latest revision to allow all workflow transitions.', $args);
|
||||
drupal_set_message($full_message, 'error');
|
||||
|
||||
$form['moderation_state']['#access'] = FALSE;
|
||||
$form['actions']['#access'] = FALSE;
|
||||
$form['invalid_transitions'] = [
|
||||
'label' => [
|
||||
'#type' => 'item',
|
||||
'#prefix' => '<strong class="label">',
|
||||
'#markup' => $label,
|
||||
'#suffix' => '</strong>',
|
||||
],
|
||||
'message' => [
|
||||
'#type' => 'item',
|
||||
'#markup' => $message,
|
||||
],
|
||||
'#weight' => 999,
|
||||
'#no_valid_transitions' => TRUE,
|
||||
];
|
||||
|
||||
if ($form['footer']) {
|
||||
$form['invalid_transitions']['#group'] = 'footer';
|
||||
}
|
||||
}
|
||||
|
||||
// Submit handler to redirect to the latest version, if available.
|
||||
$form['actions']['submit']['#submit'][] = [EntityTypeInfo::class, 'bundleFormRedirect'];
|
||||
|
||||
@@ -351,15 +345,30 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
||||
$form['moderation_state']['#group'] = 'footer';
|
||||
}
|
||||
|
||||
// Duplicate the label of the current moderation state to the meta
|
||||
// region, if available.
|
||||
// If the publishing status exists in the meta region, replace it with
|
||||
// the current state instead.
|
||||
if (isset($form['meta']['published'])) {
|
||||
$form['meta']['published']['#markup'] = $form['moderation_state']['widget'][0]['current']['#markup'];
|
||||
$form['meta']['published']['#markup'] = $this->moderationInfo->getWorkflowForEntity($entity)->getTypePlugin()->getState($entity->moderation_state->value)->label();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the specified form allows to edit a moderated entity.
|
||||
*
|
||||
* @param \Drupal\Core\Form\FormInterface $form_object
|
||||
* The form object.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the form should get form moderation, FALSE otherwise.
|
||||
*/
|
||||
protected function isModeratedEntityEditForm(FormInterface $form_object) {
|
||||
return $form_object instanceof ContentEntityFormInterface &&
|
||||
in_array($form_object->getOperation(), ['edit', 'default'], TRUE) &&
|
||||
$this->moderationInfo->isModeratedEntity($form_object->getEntity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect content entity edit forms on save, if there is a pending revision.
|
||||
*
|
||||
|
||||
@@ -17,6 +17,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* The form for editing entity types associated with a workflow.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ContentModerationConfigureEntityTypesForm extends FormBase {
|
||||
|
||||
|
||||
@@ -8,12 +8,14 @@ use Drupal\Core\Entity\RevisionLogInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\content_moderation\ModerationInformationInterface;
|
||||
use Drupal\content_moderation\StateTransitionValidation;
|
||||
use Drupal\content_moderation\StateTransitionValidationInterface;
|
||||
use Drupal\workflows\Transition;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* The EntityModerationForm provides a simple UI for changing moderation state.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class EntityModerationForm extends FormBase {
|
||||
|
||||
@@ -34,7 +36,7 @@ class EntityModerationForm extends FormBase {
|
||||
/**
|
||||
* The moderation state transition validation service.
|
||||
*
|
||||
* @var \Drupal\content_moderation\StateTransitionValidation
|
||||
* @var \Drupal\content_moderation\StateTransitionValidationInterface
|
||||
*/
|
||||
protected $validation;
|
||||
|
||||
@@ -43,15 +45,15 @@ class EntityModerationForm extends FormBase {
|
||||
*
|
||||
* @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
|
||||
* The moderation information service.
|
||||
* @param \Drupal\content_moderation\StateTransitionValidation $validation
|
||||
* @param \Drupal\content_moderation\StateTransitionValidationInterface $validation
|
||||
* The moderation state transition validation service.
|
||||
* @param \Drupal\Component\Datetime\Time $time
|
||||
* The time service.
|
||||
*/
|
||||
public function __construct(ModerationInformationInterface $moderation_info, StateTransitionValidation $validation, Time $time) {
|
||||
public function __construct(ModerationInformationInterface $moderation_info, StateTransitionValidationInterface $validation, Time $time) {
|
||||
$this->moderationInfo = $moderation_info;
|
||||
$this->time = $time;
|
||||
$this->validation = $validation;
|
||||
$this->time = $time;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,6 +138,9 @@ class EntityModerationForm extends FormBase {
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $form_state->get('entity');
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
|
||||
$entity = $storage->createRevision($entity, $entity->isDefaultRevision());
|
||||
|
||||
$new_state = $form_state->getValue('new_state');
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_moderation;
|
||||
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\RedirectDestinationInterface;
|
||||
use Drupal\node\NodeListBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a class to build a listing of moderated node entities.
|
||||
*/
|
||||
class ModeratedNodeListBuilder extends NodeListBuilder {
|
||||
|
||||
/**
|
||||
* The entity storage class.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\RevisionableStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a new ModeratedNodeListBuilder 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.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, DateFormatterInterface $date_formatter, RedirectDestinationInterface $redirect_destination, EntityTypeManagerInterface $entity_type_manager) {
|
||||
parent::__construct($entity_type, $storage, $date_formatter, $redirect_destination);
|
||||
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('entity.manager')->getStorage($entity_type->id()),
|
||||
$container->get('date.formatter'),
|
||||
$container->get('redirect.destination'),
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load() {
|
||||
$revision_ids = $this->getEntityRevisionIds();
|
||||
return $this->storage->loadMultipleRevisions($revision_ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads entity revision IDs using a pager sorted by the entity revision ID.
|
||||
*
|
||||
* @return array
|
||||
* An array of entity revision IDs.
|
||||
*/
|
||||
protected function getEntityRevisionIds() {
|
||||
$query = $this->entityTypeManager->getStorage('content_moderation_state')->getAggregateQuery()
|
||||
->aggregate('content_entity_id', 'MAX')
|
||||
->groupBy('content_entity_revision_id')
|
||||
->condition('content_entity_type_id', $this->entityTypeId)
|
||||
->condition('moderation_state', 'published', '<>')
|
||||
->sort('content_entity_revision_id', 'DESC');
|
||||
|
||||
// Only add the pager if a limit is specified.
|
||||
if ($this->limit) {
|
||||
$query->pager($this->limit);
|
||||
}
|
||||
|
||||
$result = $query->execute();
|
||||
|
||||
return $result ? array_column($result, 'content_entity_revision_id') : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header = parent::buildHeader();
|
||||
$header['status'] = $this->t('Moderation state');
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row = parent::buildRow($entity);
|
||||
$row['status'] = $entity->moderation_state->value;
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$build = parent::render();
|
||||
$build['table']['#empty'] = $this->t('There is no moderated @label yet. Only pending versions of @label, such as drafts, are listed here.', ['@label' => $this->entityType->getLabel()]);
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -127,13 +127,6 @@ class ModerationInformation implements ModerationInformationInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isPendingRevisionAllowed(ContentEntityInterface $entity) {
|
||||
return !(!$entity->isRevisionTranslationAffected() && count($entity->getTranslationLanguages()) > 1 && $this->hasPendingRevision($entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -145,8 +138,20 @@ class ModerationInformation implements ModerationInformationInterface {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasPendingRevision(ContentEntityInterface $entity) {
|
||||
return $this->isModeratedEntity($entity)
|
||||
&& !($this->getLatestRevisionId($entity->getEntityTypeId(), $entity->id()) == $this->getDefaultRevisionId($entity->getEntityTypeId(), $entity->id()));
|
||||
$result = FALSE;
|
||||
if ($this->isModeratedEntity($entity)) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
|
||||
$latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $entity->language()->getId());
|
||||
$default_revision_id = $entity->isDefaultRevision() && !$entity->isNewRevision() && ($revision_id = $entity->getRevisionId()) ?
|
||||
$revision_id : $this->getDefaultRevisionId($entity->getEntityTypeId(), $entity->id());
|
||||
if ($latest_revision_id != $default_revision_id) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $latest_revision */
|
||||
$latest_revision = $storage->loadRevision($latest_revision_id);
|
||||
$result = !$latest_revision->wasDefaultRevision();
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,9 +177,15 @@ class ModerationInformation implements ModerationInformationInterface {
|
||||
// Loop through each language that has a translation.
|
||||
foreach ($default_revision->getTranslationLanguages() as $language) {
|
||||
// Load the translated revision.
|
||||
$language_revision = $default_revision->getTranslation($language->getId());
|
||||
$translation = $default_revision->getTranslation($language->getId());
|
||||
// If the moderation state is empty, it was not stored yet so no point
|
||||
// in doing further work.
|
||||
$moderation_state = $translation->moderation_state->value;
|
||||
if (!$moderation_state) {
|
||||
continue;
|
||||
}
|
||||
// Return TRUE if a translation with a published state is found.
|
||||
if ($workflow->getTypePlugin()->getState($language_revision->moderation_state->value)->isPublishedState()) {
|
||||
if ($workflow->getTypePlugin()->getState($moderation_state)->isPublishedState()) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,19 +100,6 @@ interface ModerationInformationInterface {
|
||||
*/
|
||||
public function getAffectedRevisionTranslation(ContentEntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Determines if pending revisions are allowed.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The content entity.
|
||||
*
|
||||
* @return bool
|
||||
* If pending revisions are allowed.
|
||||
*/
|
||||
public function isPendingRevisionAllowed(ContentEntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Determines if an entity is a latest revision.
|
||||
*
|
||||
|
||||
@@ -2,104 +2,29 @@
|
||||
|
||||
namespace Drupal\content_moderation\ParamConverter;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\ParamConverter\EntityConverter;
|
||||
use Drupal\Core\TypedData\TranslatableInterface;
|
||||
use Drupal\content_moderation\ModerationInformationInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Defines a class for making sure the edit-route loads the current draft.
|
||||
*
|
||||
* @internal
|
||||
* This class only exists to provide backwards compatibility with the
|
||||
* load_pending_revision flag, the predecessor to load_latest_revision. The
|
||||
* core entity converter now natively loads the latest revision of an entity
|
||||
* when the load_latest_revision flag is present. This flag is also added
|
||||
* automatically to all entity forms.
|
||||
*/
|
||||
class EntityRevisionConverter extends EntityConverter {
|
||||
|
||||
/**
|
||||
* Moderation information service.
|
||||
*
|
||||
* @var \Drupal\content_moderation\ModerationInformationInterface
|
||||
*/
|
||||
protected $moderationInformation;
|
||||
|
||||
/**
|
||||
* EntityRevisionConverter constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager, needed by the parent class.
|
||||
* @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
|
||||
* The moderation info utility service.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entity_manager, ModerationInformationInterface $moderation_info) {
|
||||
parent::__construct($entity_manager);
|
||||
$this->moderationInformation = $moderation_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies($definition, $name, Route $route) {
|
||||
return $this->hasPendingRevisionFlag($definition) || $this->isEditFormPage($route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the route definition includes a pending revision flag.
|
||||
*
|
||||
* This is a custom flag defined by the Content Moderation module to load
|
||||
* pending revisions rather than the default revision on a given route.
|
||||
*
|
||||
* @param array $definition
|
||||
* The parameter definition provided in the route options.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the pending revision flag is set, FALSE otherwise.
|
||||
*/
|
||||
protected function hasPendingRevisionFlag(array $definition) {
|
||||
return (isset($definition['load_pending_revision']) && $definition['load_pending_revision']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given route is the edit-form for an entity.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route definition.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the route is the edit form of an entity, FALSE otherwise.
|
||||
*/
|
||||
protected function isEditFormPage(Route $route) {
|
||||
if ($default = $route->getDefault('_entity_form')) {
|
||||
// If no operation is provided, use 'default'.
|
||||
$default .= '.default';
|
||||
list($entity_type_id, $operation) = explode('.', $default);
|
||||
if (!$this->entityManager->hasDefinition($entity_type_id)) {
|
||||
return FALSE;
|
||||
}
|
||||
$entity_type = $this->entityManager->getDefinition($entity_type_id);
|
||||
return $operation == 'edit' && $entity_type && $entity_type->isRevisionable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function convert($value, $definition, $name, array $defaults) {
|
||||
$entity = parent::convert($value, $definition, $name, $defaults);
|
||||
|
||||
if ($entity && $this->moderationInformation->isModeratedEntity($entity) && !$this->moderationInformation->isLatestRevision($entity)) {
|
||||
$entity_type_id = $this->getEntityTypeFromDefaults($definition, $name, $defaults);
|
||||
$latest_revision = $this->moderationInformation->getLatestRevision($entity_type_id, $value);
|
||||
|
||||
if ($latest_revision instanceof EntityInterface) {
|
||||
// If the entity type is translatable, ensure we return the proper
|
||||
// translation object for the current context.
|
||||
if ($entity instanceof TranslatableInterface) {
|
||||
$latest_revision = $this->entityManager->getTranslationFromContext($latest_revision, NULL, ['operation' => 'entity_upcast']);
|
||||
}
|
||||
$entity = $latest_revision;
|
||||
}
|
||||
if (!empty($definition['load_pending_revision'])) {
|
||||
@trigger_error('The load_pending_revision flag has been deprecated. You should use load_latest_revision instead.', E_USER_DEPRECATED);
|
||||
$definition['load_latest_revision'] = TRUE;
|
||||
}
|
||||
|
||||
return $entity;
|
||||
return parent::convert($value, $definition, $name, $defaults);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Action\Plugin\Action\PublishAction;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\content_moderation\ModerationInformationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Alternate action plugin that can opt-out of modifying moderated entities.
|
||||
*
|
||||
* @see \Drupal\Core\Action\Plugin\Action\PublishAction
|
||||
*/
|
||||
class ModerationOptOutPublish extends PublishAction implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* Moderation information service.
|
||||
*
|
||||
* @var \Drupal\content_moderation\ModerationInformationInterface
|
||||
*/
|
||||
protected $moderationInfo;
|
||||
|
||||
/**
|
||||
* Bundle info service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $bundleInfo;
|
||||
|
||||
/**
|
||||
* ModerationOptOutPublish constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
|
||||
* The moderation information service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
|
||||
* Bundle info service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ModerationInformationInterface $moderation_info, EntityTypeBundleInfoInterface $bundle_info) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
|
||||
$this->moderationInfo = $moderation_info;
|
||||
$this->bundleInfo = $bundle_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration, $plugin_id, $plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('content_moderation.moderation_information'),
|
||||
$container->get('entity_type.bundle.info')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($entity, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
if ($entity && $this->moderationInfo->isModeratedEntity($entity)) {
|
||||
$bundle_info = $this->bundleInfo->getBundleInfo($entity->getEntityTypeId());
|
||||
$bundle_label = $bundle_info[$entity->bundle()]['label'];
|
||||
drupal_set_message($this->t("@bundle @label were skipped as they are under moderation and may not be directly published.", ['@bundle' => $bundle_label, '@label' => $entity->getEntityType()->getPluralLabel()]), 'warning');
|
||||
$result = AccessResult::forbidden();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
return parent::access($entity, $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,65 +2,28 @@
|
||||
|
||||
namespace Drupal\content_moderation\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\node\Plugin\Action\PublishNode;
|
||||
use Drupal\content_moderation\ModerationInformationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
|
||||
/**
|
||||
* Alternate action plugin that can opt-out of modifying moderated entities.
|
||||
*
|
||||
* @see \Drupal\node\Plugin\Action\PublishNode
|
||||
* @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
|
||||
* Use \Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish
|
||||
* instead.
|
||||
*
|
||||
* @see \Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish
|
||||
* @see https://www.drupal.org/node/2919303
|
||||
*/
|
||||
class ModerationOptOutPublishNode extends PublishNode implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* Moderation information service.
|
||||
*
|
||||
* @var \Drupal\content_moderation\ModerationInformationInterface
|
||||
*/
|
||||
protected $moderationInfo;
|
||||
|
||||
/**
|
||||
* ModerationOptOutPublishNode constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
|
||||
* The moderation information service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ModerationInformationInterface $moderation_info) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->moderationInfo = $moderation_info;
|
||||
}
|
||||
class ModerationOptOutPublishNode extends ModerationOptOutPublish {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration, $plugin_id, $plugin_definition,
|
||||
$container->get('content_moderation.moderation_information')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($entity, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
/** @var \Drupal\node\NodeInterface $entity */
|
||||
if ($entity && $this->moderationInfo->isModeratedEntity($entity)) {
|
||||
drupal_set_message($this->t("@bundle @label were skipped as they are under moderation and may not be directly published.", ['@bundle' => node_get_type_label($entity), '@label' => $entity->getEntityType()->getPluralLabel()]), 'warning');
|
||||
$result = AccessResult::forbidden();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
return parent::access($entity, $account, $return_as_object);
|
||||
public function __construct($configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ModerationInformationInterface $moderation_info, EntityTypeBundleInfoInterface $bundle_info) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $moderation_info, $bundle_info);
|
||||
@trigger_error(__NAMESPACE__ . '\ModerationOptOutPublishNode is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Action\Plugin\Action\UnpublishAction;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\content_moderation\ModerationInformationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Alternate action plugin that can opt-out of modifying moderated entities.
|
||||
*
|
||||
* @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
|
||||
*/
|
||||
class ModerationOptOutUnpublish extends UnpublishAction implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* Moderation information service.
|
||||
*
|
||||
* @var \Drupal\content_moderation\ModerationInformationInterface
|
||||
*/
|
||||
protected $moderationInfo;
|
||||
|
||||
/**
|
||||
* Bundle info service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $bundleInfo;
|
||||
|
||||
/**
|
||||
* ModerationOptOutUnpublish constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
|
||||
* The moderation information service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
|
||||
* Bundle info service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ModerationInformationInterface $moderation_info, EntityTypeBundleInfoInterface $bundle_info) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
|
||||
$this->moderationInfo = $moderation_info;
|
||||
$this->bundleInfo = $bundle_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration, $plugin_id, $plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('content_moderation.moderation_information'),
|
||||
$container->get('entity_type.bundle.info')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($entity, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
if ($entity && $this->moderationInfo->isModeratedEntity($entity)) {
|
||||
$bundle_info = $this->bundleInfo->getBundleInfo($entity->getEntityTypeId());
|
||||
$bundle_label = $bundle_info[$entity->bundle()]['label'];
|
||||
drupal_set_message($this->t("@bundle @label were skipped as they are under moderation and may not be directly unpublished.", ['@bundle' => $bundle_label, '@label' => $entity->getEntityType()->getPluralLabel()]), 'warning');
|
||||
$result = AccessResult::forbidden();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
return parent::access($entity, $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
+12
-49
@@ -2,65 +2,28 @@
|
||||
|
||||
namespace Drupal\content_moderation\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\node\Plugin\Action\UnpublishNode;
|
||||
use Drupal\content_moderation\ModerationInformationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
|
||||
/**
|
||||
* Alternate action plugin that can opt-out of modifying moderated entities.
|
||||
*
|
||||
* @see \Drupal\node\Plugin\Action\UnpublishNode
|
||||
* @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
|
||||
* Use \Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublish
|
||||
* instead.
|
||||
*
|
||||
* @see \Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish
|
||||
* @see https://www.drupal.org/node/2919303
|
||||
*/
|
||||
class ModerationOptOutUnpublishNode extends UnpublishNode implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* Moderation information service.
|
||||
*
|
||||
* @var \Drupal\content_moderation\ModerationInformationInterface
|
||||
*/
|
||||
protected $moderationInfo;
|
||||
|
||||
/**
|
||||
* ModerationOptOutUnpublishNode constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
|
||||
* The moderation information service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ModerationInformationInterface $moderation_info) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->moderationInfo = $moderation_info;
|
||||
}
|
||||
class ModerationOptOutUnpublishNode extends ModerationOptOutUnpublish {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration, $plugin_id, $plugin_definition,
|
||||
$container->get('content_moderation.moderation_information')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($entity, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
/** @var \Drupal\node\NodeInterface $entity */
|
||||
if ($entity && $this->moderationInfo->isModeratedEntity($entity)) {
|
||||
drupal_set_message($this->t("@bundle @label were skipped as they are under moderation and may not be directly unpublished.", ['@bundle' => node_get_type_label($entity), '@label' => $entity->getEntityType()->getPluralLabel()]), 'warning');
|
||||
$result = AccessResult::forbidden();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
return parent::access($entity, $account, $return_as_object);
|
||||
public function __construct($configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ModerationInformationInterface $moderation_info, EntityTypeBundleInfoInterface $bundle_info) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $moderation_info, $bundle_info);
|
||||
@trigger_error(__NAMESPACE__ . '\ModerationOptOutUnpublishNode is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublish instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-4
@@ -11,7 +11,7 @@ use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\content_moderation\ModerationInformation;
|
||||
use Drupal\content_moderation\StateTransitionValidation;
|
||||
use Drupal\content_moderation\StateTransitionValidationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -51,7 +51,7 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
|
||||
/**
|
||||
* Moderation state transition validation service.
|
||||
*
|
||||
* @var \Drupal\content_moderation\StateTransitionValidation
|
||||
* @var \Drupal\content_moderation\StateTransitionValidationInterface
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
@@ -74,10 +74,10 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
|
||||
* Entity type manager.
|
||||
* @param \Drupal\content_moderation\ModerationInformation $moderation_information
|
||||
* Moderation information service.
|
||||
* @param \Drupal\content_moderation\StateTransitionValidation $validator
|
||||
* @param \Drupal\content_moderation\StateTransitionValidationInterface $validator
|
||||
* Moderation state transition validation service.
|
||||
*/
|
||||
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ModerationInformation $moderation_information, StateTransitionValidation $validator) {
|
||||
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ModerationInformation $moderation_information, StateTransitionValidationInterface $validator) {
|
||||
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->currentUser = $current_user;
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Drupal\content_moderation\Plugin\Field;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
use Drupal\Core\Field\FieldItemList;
|
||||
use Drupal\Core\TypedData\ComputedItemListTrait;
|
||||
|
||||
/**
|
||||
* A computed field that provides a content entity's moderation state.
|
||||
@@ -14,6 +15,38 @@ use Drupal\Core\Field\FieldItemList;
|
||||
*/
|
||||
class ModerationStateFieldItemList extends FieldItemList {
|
||||
|
||||
use ComputedItemListTrait {
|
||||
ensureComputedValue as traitEnsureComputedValue;
|
||||
get as traitGet;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function computeValue() {
|
||||
$moderation_state = $this->getModerationStateId();
|
||||
// Do not store NULL values, in the case where an entity does not have a
|
||||
// moderation workflow associated with it, we do not create list items for
|
||||
// the computed field.
|
||||
if ($moderation_state) {
|
||||
// An entity can only have a single moderation state.
|
||||
$this->list[0] = $this->createItem(0, $moderation_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function ensureComputedValue() {
|
||||
// If the moderation state field is set to an empty value, always recompute
|
||||
// the state. Empty is not a valid moderation state value, when none is
|
||||
// present the default state is used.
|
||||
if (!isset($this->list[0]) || $this->list[0]->isEmpty()) {
|
||||
$this->valueComputed = FALSE;
|
||||
}
|
||||
$this->traitEnsureComputedValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the moderation state ID linked to a content entity revision.
|
||||
*
|
||||
@@ -90,32 +123,7 @@ class ModerationStateFieldItemList extends FieldItemList {
|
||||
if ($index !== 0) {
|
||||
throw new \InvalidArgumentException('An entity can not have multiple moderation states at the same time.');
|
||||
}
|
||||
$this->computeModerationFieldItemList();
|
||||
return isset($this->list[$index]) ? $this->list[$index] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIterator() {
|
||||
$this->computeModerationFieldItemList();
|
||||
return parent::getIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate the moderation field item list.
|
||||
*/
|
||||
protected function computeModerationFieldItemList() {
|
||||
// Compute the value of the moderation state.
|
||||
$index = 0;
|
||||
if (!isset($this->list[$index]) || $this->list[$index]->isEmpty()) {
|
||||
|
||||
$moderation_state = $this->getModerationStateId();
|
||||
// Do not store NULL values in the static cache.
|
||||
if ($moderation_state) {
|
||||
$this->list[$index] = $this->createItem($index, $moderation_state);
|
||||
}
|
||||
}
|
||||
return $this->traitGet($index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,7 +141,11 @@ class ModerationStateFieldItemList extends FieldItemList {
|
||||
public function setValue($values, $notify = TRUE) {
|
||||
parent::setValue($values, $notify);
|
||||
|
||||
if (isset($this->list[0])) {
|
||||
// If the parent created a field item and if the parent should be notified
|
||||
// about the change (e.g. this is not initialized with the current value),
|
||||
// update the moderated entity.
|
||||
if (isset($this->list[0]) && $notify) {
|
||||
$this->valueComputed = TRUE;
|
||||
$this->updateModeratedEntity($this->list[0]->value);
|
||||
}
|
||||
}
|
||||
@@ -157,10 +169,9 @@ class ModerationStateFieldItemList extends FieldItemList {
|
||||
/** @var \Drupal\content_moderation\ContentModerationState $current_state */
|
||||
$current_state = $workflow->getTypePlugin()->getState($moderation_state_id);
|
||||
|
||||
// This entity is default if it is new, a new translation, the default
|
||||
// revision state, or the default revision is not published.
|
||||
// This entity is default if it is new, the default revision state, or the
|
||||
// default revision is not published.
|
||||
$update_default_revision = $entity->isNew()
|
||||
|| $entity->isNewTranslation()
|
||||
|| $current_state->isDefaultRevisionState()
|
||||
|| !$content_moderation_info->isDefaultRevisionPublished($entity);
|
||||
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Plugin\views\filter;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\views\Plugin\DependentWithRemovalPluginInterface;
|
||||
use Drupal\views\Plugin\views\filter\InOperator;
|
||||
use Drupal\views\Views;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a filter for the moderation state of an entity.
|
||||
*
|
||||
* @ingroup views_filter_handlers
|
||||
*
|
||||
* @ViewsFilter("moderation_state_filter")
|
||||
*/
|
||||
class ModerationStateFilter extends InOperator implements DependentWithRemovalPluginInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $valueFormType = 'select';
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The bundle information service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $bundleInfo;
|
||||
|
||||
/**
|
||||
* The storage handler of the workflow entity type.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $workflowStorage;
|
||||
|
||||
/**
|
||||
* Creates an instance of ModerationStateFilter.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info, EntityStorageInterface $workflow_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->bundleInfo = $bundle_info;
|
||||
$this->workflowStorage = $workflow_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('entity_type.manager')->getStorage('workflow')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return Cache::mergeTags(parent::getCacheTags(), $this->entityTypeManager->getDefinition('workflow')->getListCacheTags());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return Cache::mergeContexts(parent::getCacheContexts(), $this->entityTypeManager->getDefinition('workflow')->getListCacheContexts());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValueOptions() {
|
||||
if (isset($this->valueOptions)) {
|
||||
return $this->valueOptions;
|
||||
}
|
||||
$this->valueOptions = [];
|
||||
|
||||
// Find all workflows which are moderating entity types of the same type the
|
||||
// view is displaying.
|
||||
foreach ($this->workflowStorage->loadByProperties(['type' => 'content_moderation']) as $workflow) {
|
||||
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModerationInterface $workflow_type */
|
||||
$workflow_type = $workflow->getTypePlugin();
|
||||
if (in_array($this->getEntityType(), $workflow_type->getEntityTypes(), TRUE)) {
|
||||
foreach ($workflow_type->getStates() as $state_id => $state) {
|
||||
$this->valueOptions[$workflow->label()][implode('-', [$workflow->id(), $state_id])] = $state->label();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->valueOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function ensureMyTable() {
|
||||
if (!isset($this->tableAlias)) {
|
||||
$table_alias = $this->query->ensureTable($this->table, $this->relationship);
|
||||
|
||||
// Filter the moderation states of the content via the
|
||||
// ContentModerationState field revision table, joining either the entity
|
||||
// field data or revision table. This allows filtering states against
|
||||
// either the default or latest revision, depending on the relationship of
|
||||
// the filter.
|
||||
$left_entity_type = $this->entityTypeManager->getDefinition($this->getEntityType());
|
||||
$entity_type = $this->entityTypeManager->getDefinition('content_moderation_state');
|
||||
$configuration = [
|
||||
'table' => $entity_type->getRevisionDataTable(),
|
||||
'field' => 'content_entity_revision_id',
|
||||
'left_table' => $table_alias,
|
||||
'left_field' => $left_entity_type->getKey('revision'),
|
||||
'extra' => [
|
||||
[
|
||||
'field' => 'content_entity_type_id',
|
||||
'value' => $left_entity_type->id(),
|
||||
],
|
||||
],
|
||||
];
|
||||
if ($left_entity_type->isTranslatable()) {
|
||||
$configuration['extra'][] = [
|
||||
'field' => $entity_type->getKey('langcode'),
|
||||
'left_field' => $left_entity_type->getKey('langcode'),
|
||||
];
|
||||
}
|
||||
$join = Views::pluginManager('join')->createInstance('standard', $configuration);
|
||||
$this->tableAlias = $this->query->addRelationship('content_moderation_state', $join, 'content_moderation_state_field_revision');
|
||||
}
|
||||
|
||||
return $this->tableAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function opSimple() {
|
||||
if (empty($this->value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ensureMyTable();
|
||||
|
||||
$entity_type = $this->entityTypeManager->getDefinition($this->getEntityType());
|
||||
if ($entity_type->hasKey('bundle')) {
|
||||
// Get a list of bundles that are being moderated by the workflows
|
||||
// configured in this filter.
|
||||
$workflow_ids = $this->getWorkflowIds();
|
||||
$moderated_bundles = [];
|
||||
foreach ($this->bundleInfo->getBundleInfo($this->getEntityType()) as $bundle_id => $bundle) {
|
||||
if (isset($bundle['workflow']) && in_array($bundle['workflow'], $workflow_ids, TRUE)) {
|
||||
$moderated_bundles[] = $bundle_id;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a list of moderated bundles, restrict the query to show only
|
||||
// entities in those bundles.
|
||||
if ($moderated_bundles) {
|
||||
$entity_base_table_alias = $this->table;
|
||||
|
||||
// The bundle field of an entity type is not revisionable so we need to
|
||||
// join the base table.
|
||||
$entity_base_table = $entity_type->getBaseTable();
|
||||
$entity_revision_base_table = $entity_type->isTranslatable() ? $entity_type->getRevisionDataTable() : $entity_type->getRevisionTable();
|
||||
if ($this->table === $entity_revision_base_table) {
|
||||
$configuration = [
|
||||
'table' => $entity_base_table,
|
||||
'field' => $entity_type->getKey('id'),
|
||||
'left_table' => $entity_revision_base_table,
|
||||
'left_field' => $entity_type->getKey('id'),
|
||||
'type' => 'INNER',
|
||||
];
|
||||
|
||||
$join = Views::pluginManager('join')->createInstance('standard', $configuration);
|
||||
$entity_base_table_alias = $this->query->addRelationship($entity_base_table, $join, $entity_revision_base_table);
|
||||
}
|
||||
|
||||
$this->query->addWhere($this->options['group'], "$entity_base_table_alias.{$entity_type->getKey('bundle')}", $moderated_bundles, 'IN');
|
||||
}
|
||||
// Otherwise, force the query to return an empty result.
|
||||
else {
|
||||
$this->query->addWhereExpression($this->options['group'], '1 = 0');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->operator === 'in') {
|
||||
$operator = "=";
|
||||
}
|
||||
else {
|
||||
$operator = "<>";
|
||||
}
|
||||
|
||||
// The values are strings composed from the workflow ID and the state ID, so
|
||||
// we need to create a complex WHERE condition.
|
||||
$field = new Condition('OR');
|
||||
foreach ((array) $this->value as $value) {
|
||||
list($workflow_id, $state_id) = explode('-', $value, 2);
|
||||
|
||||
$and = new Condition('AND');
|
||||
$and
|
||||
->condition("$this->tableAlias.workflow", $workflow_id, '=')
|
||||
->condition("$this->tableAlias.$this->realField", $state_id, $operator);
|
||||
|
||||
$field->condition($and);
|
||||
}
|
||||
|
||||
$this->query->addWhere($this->options['group'], $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
|
||||
if ($workflow_ids = $this->getWorkflowIds()) {
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
foreach ($this->workflowStorage->loadMultiple($workflow_ids) as $workflow) {
|
||||
$dependencies[$workflow->getConfigDependencyKey()][] = $workflow->getConfigDependencyName();
|
||||
}
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
// See if this handler is responsible for any of the dependencies being
|
||||
// removed. If this is the case, indicate that this handler needs to be
|
||||
// removed from the View.
|
||||
$remove = FALSE;
|
||||
// Get all the current dependencies for this handler.
|
||||
$current_dependencies = $this->calculateDependencies();
|
||||
foreach ($current_dependencies as $group => $dependency_list) {
|
||||
// Check if any of the handler dependencies match the dependencies being
|
||||
// removed.
|
||||
foreach ($dependency_list as $config_key) {
|
||||
if (isset($dependencies[$group]) && array_key_exists($config_key, $dependencies[$group])) {
|
||||
// This handlers dependency matches a dependency being removed,
|
||||
// indicate that this handler needs to be removed.
|
||||
$remove = TRUE;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $remove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of Workflow IDs configured for this filter.
|
||||
*
|
||||
* @return array
|
||||
* And array of workflow IDs.
|
||||
*/
|
||||
protected function getWorkflowIds() {
|
||||
$workflow_ids = [];
|
||||
foreach ((array) $this->value as $value) {
|
||||
list($workflow_id) = explode('-', $value, 2);
|
||||
$workflow_ids[] = $workflow_id;
|
||||
}
|
||||
|
||||
return array_unique($workflow_ids);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_moderation\Routing;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\RouteSubscriberBase;
|
||||
use Drupal\Core\Routing\RoutingEvents;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Subscriber for moderated revisionable entity forms.
|
||||
*
|
||||
* @internal
|
||||
* There is ongoing discussion about how pending revisions should behave.
|
||||
* The logic enabling pending revision support is likely to change once a
|
||||
* decision is made.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2940575
|
||||
*/
|
||||
class ContentModerationRouteSubscriber extends RouteSubscriberBase {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* An associative array of moderated entity types keyed by ID.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\ContentEntityTypeInterface[]
|
||||
*/
|
||||
protected $moderatedEntityTypes;
|
||||
|
||||
/**
|
||||
* ContentModerationRouteSubscriber constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function alterRoutes(RouteCollection $collection) {
|
||||
foreach ($collection as $route) {
|
||||
$this->setLatestRevisionFlag($route);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure revisionable entities load the latest revision on entity forms.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route object.
|
||||
*/
|
||||
protected function setLatestRevisionFlag(Route $route) {
|
||||
if (!$entity_form = $route->getDefault('_entity_form')) {
|
||||
return;
|
||||
}
|
||||
// Only set the flag on entity types which are revisionable.
|
||||
list($entity_type) = explode('.', $entity_form, 2);
|
||||
if (!isset($this->getModeratedEntityTypes()[$entity_type]) || !$this->getModeratedEntityTypes()[$entity_type]->isRevisionable()) {
|
||||
return;
|
||||
}
|
||||
$parameters = $route->getOption('parameters') ?: [];
|
||||
foreach ($parameters as &$parameter) {
|
||||
if ($parameter['type'] === 'entity:' . $entity_type && !isset($parameter['load_latest_revision'])) {
|
||||
$parameter['load_latest_revision'] = TRUE;
|
||||
}
|
||||
}
|
||||
$route->setOption('parameters', $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the moderated entity types.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityTypeInterface[]
|
||||
* An associative array of moderated entity types keyed by ID.
|
||||
*/
|
||||
protected function getModeratedEntityTypes() {
|
||||
if (!isset($this->moderatedEntityTypes)) {
|
||||
$entity_types = $this->entityTypeManager->getDefinitions();
|
||||
/** @var \Drupal\workflows\WorkflowInterface $workflow */
|
||||
foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
|
||||
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
|
||||
$plugin = $workflow->getTypePlugin();
|
||||
foreach ($plugin->getEntityTypes() as $entity_type_id) {
|
||||
$this->moderatedEntityTypes[$entity_type_id] = $entity_types[$entity_type_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->moderatedEntityTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events = parent::getSubscribedEvents();
|
||||
// This needs to run after that EntityResolverManager has set the route
|
||||
// entity type.
|
||||
$events[RoutingEvents::ALTER] = ['onAlterRoutes', -200];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -78,7 +78,12 @@ class ViewsData {
|
||||
],
|
||||
],
|
||||
],
|
||||
'field' => ['default_formatter' => 'content_moderation_state'],
|
||||
'field' => [
|
||||
'id' => 'field',
|
||||
'default_formatter' => 'content_moderation_state',
|
||||
'field_name' => 'moderation_state',
|
||||
],
|
||||
'filter' => ['id' => 'moderation_state_filter', 'allow empty' => TRUE],
|
||||
];
|
||||
|
||||
$revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable();
|
||||
@@ -97,7 +102,12 @@ class ViewsData {
|
||||
],
|
||||
],
|
||||
],
|
||||
'field' => ['default_formatter' => 'content_moderation_state'],
|
||||
'field' => [
|
||||
'id' => 'field',
|
||||
'default_formatter' => 'content_moderation_state',
|
||||
'field_name' => 'moderation_state',
|
||||
],
|
||||
'filter' => ['id' => 'moderation_state_filter', 'allow empty' => TRUE],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{{ attach_library('content_moderation/content_moderation') }}
|
||||
<ul class="entity-moderation-form">
|
||||
<li>{{ form.current }}</li>
|
||||
<li>{{ form.new_state }}</li>
|
||||
<li>{{ form.revision_log }}</li>
|
||||
<li>{{ form.submit }}</li>
|
||||
<li class="entity-moderation-form__item">{{ form.current }}</li>
|
||||
<li class="entity-moderation-form__item">{{ form.new_state }}</li>
|
||||
<li class="entity-moderation-form__item">{{ form.revision_log }}</li>
|
||||
<li class="entity-moderation-form__item">{{ form.submit }}</li>
|
||||
</ul>
|
||||
{{ form|without('current', 'new_state', 'revision_log', 'submit') }}
|
||||
|
||||
Vendored
+665
File diff suppressed because one or more lines are too long
Vendored
+804
@@ -0,0 +1,804 @@
|
||||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
/**
|
||||
* @file
|
||||
* Content for the update path test in #2941736.
|
||||
*
|
||||
* @see \Drupal\Tests\content_moderation\Functional\DefaultContentModerationStateRevisionUpdateTest.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
$connection->insert('block_content')
|
||||
->fields(array(
|
||||
'id',
|
||||
'revision_id',
|
||||
'type',
|
||||
'uuid',
|
||||
'langcode',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '1',
|
||||
'type' => 'test_block_content',
|
||||
'uuid' => '811fac6c-8184-4de5-99eb-9e70d28709f4',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '2',
|
||||
'revision_id' => '3',
|
||||
'type' => 'test_block_content',
|
||||
'uuid' => 'b89f025c-0538-4075-bd8e-96acf74211c9',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '5',
|
||||
'type' => 'test_block_content',
|
||||
'uuid' => '62e428e1-88a6-478c-a8c6-a554ca2332ae',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('block_content_field_data')
|
||||
->fields(array(
|
||||
'id',
|
||||
'revision_id',
|
||||
'type',
|
||||
'langcode',
|
||||
'info',
|
||||
'changed',
|
||||
'default_langcode',
|
||||
'revision_translation_affected',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '1',
|
||||
'type' => 'test_block_content',
|
||||
'langcode' => 'en',
|
||||
'info' => 'draft pending revision',
|
||||
'changed' => '1517725800',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '2',
|
||||
'revision_id' => '3',
|
||||
'type' => 'test_block_content',
|
||||
'langcode' => 'en',
|
||||
'info' => 'published default revision',
|
||||
'changed' => '1517725800',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '5',
|
||||
'type' => 'test_block_content',
|
||||
'langcode' => 'en',
|
||||
'info' => 'archived default revision',
|
||||
'changed' => '1517725800',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('block_content_field_revision')
|
||||
->fields(array(
|
||||
'id',
|
||||
'revision_id',
|
||||
'langcode',
|
||||
'info',
|
||||
'changed',
|
||||
'default_langcode',
|
||||
'revision_translation_affected',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '1',
|
||||
'langcode' => 'en',
|
||||
'info' => 'draft pending revision',
|
||||
'changed' => '1517725800',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '2',
|
||||
'langcode' => 'en',
|
||||
'info' => 'draft pending revision',
|
||||
'changed' => '1517725800',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '2',
|
||||
'revision_id' => '3',
|
||||
'langcode' => 'en',
|
||||
'info' => 'published default revision',
|
||||
'changed' => '1517725800',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '4',
|
||||
'langcode' => 'en',
|
||||
'info' => 'archived default revision',
|
||||
'changed' => '1517725800',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '5',
|
||||
'langcode' => 'en',
|
||||
'info' => 'archived default revision',
|
||||
'changed' => '1517725800',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('block_content_revision')
|
||||
->fields(array(
|
||||
'id',
|
||||
'revision_id',
|
||||
'langcode',
|
||||
'revision_user',
|
||||
'revision_created',
|
||||
'revision_log',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '1',
|
||||
'langcode' => 'en',
|
||||
'revision_user' => NULL,
|
||||
'revision_created' => '1517725800',
|
||||
'revision_log' => NULL,
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '2',
|
||||
'langcode' => 'en',
|
||||
'revision_user' => NULL,
|
||||
'revision_created' => '1517725800',
|
||||
'revision_log' => NULL,
|
||||
))
|
||||
->values(array(
|
||||
'id' => '2',
|
||||
'revision_id' => '3',
|
||||
'langcode' => 'en',
|
||||
'revision_user' => NULL,
|
||||
'revision_created' => '1517725800',
|
||||
'revision_log' => NULL,
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '4',
|
||||
'langcode' => 'en',
|
||||
'revision_user' => NULL,
|
||||
'revision_created' => '1517725800',
|
||||
'revision_log' => NULL,
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '5',
|
||||
'langcode' => 'en',
|
||||
'revision_user' => NULL,
|
||||
'revision_created' => '1517725800',
|
||||
'revision_log' => NULL,
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->delete('config')
|
||||
->condition('name', ['workflows.workflow.editorial'], 'IN')
|
||||
->execute();
|
||||
|
||||
$connection->insert('config')
|
||||
->fields(array(
|
||||
'collection',
|
||||
'name',
|
||||
'data',
|
||||
))
|
||||
->values(array(
|
||||
'collection' => '',
|
||||
'name' => 'block_content.type.test_block_content',
|
||||
'data' => 'a:8:{s:4:"uuid";s:36:"966baba6-525e-48fe-b8c5-a5f131b1857f";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:0:{}s:2:"id";s:18:"test_block_content";s:5:"label";s:18:"Test Block Content";s:8:"revision";N;s:11:"description";N;}',
|
||||
))
|
||||
->values(array(
|
||||
'collection' => '',
|
||||
'name' => 'workflows.workflow.editorial',
|
||||
'data' => 'a:9:{s:4:"uuid";s:36:"08b548c7-ff59-468b-9347-7d697680d035";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:6:"config";a:2:{i:0;s:37:"block_content.type.test_block_content";i:1;s:17:"node.type.article";}s:6:"module";a:1:{i:0;s:18:"content_moderation";}}s:5:"_core";a:1:{s:19:"default_config_hash";s:43:"T_JxNjYlfoRBi7Bj1zs5Xv9xv1btuBkKp5C1tNrjMhI";}s:2:"id";s:9:"editorial";s:5:"label";s:9:"Editorial";s:4:"type";s:18:"content_moderation";s:13:"type_settings";a:3:{s:6:"states";a:3:{s:8:"archived";a:4:{s:5:"label";s:8:"Archived";s:6:"weight";i:5;s:9:"published";b:0;s:16:"default_revision";b:1;}s:5:"draft";a:4:{s:5:"label";s:5:"Draft";s:9:"published";b:0;s:16:"default_revision";b:0;s:6:"weight";i:-5;}s:9:"published";a:4:{s:5:"label";s:9:"Published";s:9:"published";b:1;s:16:"default_revision";b:1;s:6:"weight";i:0;}}s:11:"transitions";a:5:{s:7:"archive";a:4:{s:5:"label";s:7:"Archive";s:4:"from";a:1:{i:0;s:9:"published";}s:2:"to";s:8:"archived";s:6:"weight";i:2;}s:14:"archived_draft";a:4:{s:5:"label";s:16:"Restore to Draft";s:4:"from";a:1:{i:0;s:8:"archived";}s:2:"to";s:5:"draft";s:6:"weight";i:3;}s:18:"archived_published";a:4:{s:5:"label";s:7:"Restore";s:4:"from";a:1:{i:0;s:8:"archived";}s:2:"to";s:9:"published";s:6:"weight";i:4;}s:16:"create_new_draft";a:4:{s:5:"label";s:16:"Create New Draft";s:2:"to";s:5:"draft";s:6:"weight";i:0;s:4:"from";a:2:{i:0;s:5:"draft";i:1;s:9:"published";}}s:7:"publish";a:4:{s:5:"label";s:7:"Publish";s:2:"to";s:9:"published";s:6:"weight";i:1;s:4:"from";a:2:{i:0;s:5:"draft";i:1;s:9:"published";}}}s:12:"entity_types";a:2:{s:13:"block_content";a:1:{i:0;s:18:"test_block_content";}s:4:"node";a:1:{i:0;s:7:"article";}}}}',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('content_moderation_state')
|
||||
->fields(array(
|
||||
'id',
|
||||
'revision_id',
|
||||
'uuid',
|
||||
'langcode',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '2',
|
||||
'uuid' => '3ce04732-f65f-4937-aa49-821f5842ae06',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '2',
|
||||
'revision_id' => '3',
|
||||
'uuid' => 'a6507b55-3001-4748-8d32-f4fa47319754',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '5',
|
||||
'uuid' => '112d2bd2-552b-4e2f-9a6d-526740ba1b38',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '4',
|
||||
'revision_id' => '7',
|
||||
'uuid' => 'a85d0d06-e046-4509-b9b4-75d78dcdd91e',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '5',
|
||||
'revision_id' => '8',
|
||||
'uuid' => '3797f5de-116b-4d75-b7e3-5206e6f97c41',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '6',
|
||||
'revision_id' => '10',
|
||||
'uuid' => '8d9b11c1-8ddf-4c61-bb8d-9ac724e28d9e',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('content_moderation_state_field_data')
|
||||
->fields(array(
|
||||
'id',
|
||||
'revision_id',
|
||||
'langcode',
|
||||
'uid',
|
||||
'workflow',
|
||||
'moderation_state',
|
||||
'content_entity_type_id',
|
||||
'content_entity_id',
|
||||
'content_entity_revision_id',
|
||||
'default_langcode',
|
||||
'revision_translation_affected',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '2',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'draft',
|
||||
'content_entity_type_id' => 'node',
|
||||
'content_entity_id' => '1',
|
||||
'content_entity_revision_id' => '2',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '2',
|
||||
'revision_id' => '3',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'published',
|
||||
'content_entity_type_id' => 'node',
|
||||
'content_entity_id' => '2',
|
||||
'content_entity_revision_id' => '3',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '5',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'archived',
|
||||
'content_entity_type_id' => 'node',
|
||||
'content_entity_id' => '3',
|
||||
'content_entity_revision_id' => '5',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '4',
|
||||
'revision_id' => '7',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'draft',
|
||||
'content_entity_type_id' => 'block_content',
|
||||
'content_entity_id' => '1',
|
||||
'content_entity_revision_id' => '2',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '5',
|
||||
'revision_id' => '8',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'published',
|
||||
'content_entity_type_id' => 'block_content',
|
||||
'content_entity_id' => '2',
|
||||
'content_entity_revision_id' => '3',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '6',
|
||||
'revision_id' => '10',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'archived',
|
||||
'content_entity_type_id' => 'block_content',
|
||||
'content_entity_id' => '3',
|
||||
'content_entity_revision_id' => '5',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('content_moderation_state_field_revision')
|
||||
->fields(array(
|
||||
'id',
|
||||
'revision_id',
|
||||
'langcode',
|
||||
'uid',
|
||||
'workflow',
|
||||
'moderation_state',
|
||||
'content_entity_type_id',
|
||||
'content_entity_id',
|
||||
'content_entity_revision_id',
|
||||
'default_langcode',
|
||||
'revision_translation_affected',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '1',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'published',
|
||||
'content_entity_type_id' => 'node',
|
||||
'content_entity_id' => '1',
|
||||
'content_entity_revision_id' => '1',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '2',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'draft',
|
||||
'content_entity_type_id' => 'node',
|
||||
'content_entity_id' => '1',
|
||||
'content_entity_revision_id' => '2',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '2',
|
||||
'revision_id' => '3',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'published',
|
||||
'content_entity_type_id' => 'node',
|
||||
'content_entity_id' => '2',
|
||||
'content_entity_revision_id' => '3',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '4',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'published',
|
||||
'content_entity_type_id' => 'node',
|
||||
'content_entity_id' => '3',
|
||||
'content_entity_revision_id' => '4',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '5',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'archived',
|
||||
'content_entity_type_id' => 'node',
|
||||
'content_entity_id' => '3',
|
||||
'content_entity_revision_id' => '5',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '4',
|
||||
'revision_id' => '6',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'published',
|
||||
'content_entity_type_id' => 'block_content',
|
||||
'content_entity_id' => '1',
|
||||
'content_entity_revision_id' => '1',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '4',
|
||||
'revision_id' => '7',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'draft',
|
||||
'content_entity_type_id' => 'block_content',
|
||||
'content_entity_id' => '1',
|
||||
'content_entity_revision_id' => '2',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '5',
|
||||
'revision_id' => '8',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'published',
|
||||
'content_entity_type_id' => 'block_content',
|
||||
'content_entity_id' => '2',
|
||||
'content_entity_revision_id' => '3',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '6',
|
||||
'revision_id' => '9',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'published',
|
||||
'content_entity_type_id' => 'block_content',
|
||||
'content_entity_id' => '3',
|
||||
'content_entity_revision_id' => '4',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '6',
|
||||
'revision_id' => '10',
|
||||
'langcode' => 'en',
|
||||
'uid' => '0',
|
||||
'workflow' => 'editorial',
|
||||
'moderation_state' => 'archived',
|
||||
'content_entity_type_id' => 'block_content',
|
||||
'content_entity_id' => '3',
|
||||
'content_entity_revision_id' => '5',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('content_moderation_state_revision')
|
||||
->fields(array(
|
||||
'id',
|
||||
'revision_id',
|
||||
'langcode',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '1',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '1',
|
||||
'revision_id' => '2',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '2',
|
||||
'revision_id' => '3',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '4',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '3',
|
||||
'revision_id' => '5',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '4',
|
||||
'revision_id' => '6',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '4',
|
||||
'revision_id' => '7',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '5',
|
||||
'revision_id' => '8',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '6',
|
||||
'revision_id' => '9',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '6',
|
||||
'revision_id' => '10',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('key_value')
|
||||
->fields(array(
|
||||
'collection',
|
||||
'name',
|
||||
'value',
|
||||
))
|
||||
->values(array(
|
||||
'collection' => 'config.entity.key_store.block_content_type',
|
||||
'name' => 'uuid:966baba6-525e-48fe-b8c5-a5f131b1857f',
|
||||
'value' => 'a:1:{i:0;s:37:"block_content.type.test_block_content";}',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('node')
|
||||
->fields(array(
|
||||
'nid',
|
||||
'vid',
|
||||
'type',
|
||||
'uuid',
|
||||
'langcode',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '1',
|
||||
'vid' => '1',
|
||||
'type' => 'article',
|
||||
'uuid' => '11143847-fe18-4808-a797-8b15966adf4c',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '2',
|
||||
'vid' => '3',
|
||||
'type' => 'article',
|
||||
'uuid' => '336e6941-9340-419e-a763-65d4c11ea031',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '3',
|
||||
'vid' => '5',
|
||||
'type' => 'article',
|
||||
'uuid' => '3eebe337-f977-4a32-94d2-4095947f125d',
|
||||
'langcode' => 'en',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('node_field_data')
|
||||
->fields(array(
|
||||
'nid',
|
||||
'vid',
|
||||
'type',
|
||||
'langcode',
|
||||
'status',
|
||||
'title',
|
||||
'uid',
|
||||
'created',
|
||||
'changed',
|
||||
'promote',
|
||||
'sticky',
|
||||
'default_langcode',
|
||||
'revision_translation_affected',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '1',
|
||||
'vid' => '1',
|
||||
'type' => 'article',
|
||||
'langcode' => 'en',
|
||||
'status' => '1',
|
||||
'title' => 'draft pending revision',
|
||||
'uid' => '0',
|
||||
'created' => '1517725800',
|
||||
'changed' => '1517725800',
|
||||
'promote' => '1',
|
||||
'sticky' => '0',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '2',
|
||||
'vid' => '3',
|
||||
'type' => 'article',
|
||||
'langcode' => 'en',
|
||||
'status' => '1',
|
||||
'title' => 'published default revision',
|
||||
'uid' => '0',
|
||||
'created' => '1517725800',
|
||||
'changed' => '1517725800',
|
||||
'promote' => '1',
|
||||
'sticky' => '0',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '3',
|
||||
'vid' => '5',
|
||||
'type' => 'article',
|
||||
'langcode' => 'en',
|
||||
'status' => '0',
|
||||
'title' => 'archived default revision',
|
||||
'uid' => '0',
|
||||
'created' => '1517725800',
|
||||
'changed' => '1517725800',
|
||||
'promote' => '1',
|
||||
'sticky' => '0',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('node_field_revision')
|
||||
->fields(array(
|
||||
'nid',
|
||||
'vid',
|
||||
'langcode',
|
||||
'status',
|
||||
'title',
|
||||
'uid',
|
||||
'created',
|
||||
'changed',
|
||||
'promote',
|
||||
'sticky',
|
||||
'default_langcode',
|
||||
'revision_translation_affected',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '1',
|
||||
'vid' => '1',
|
||||
'langcode' => 'en',
|
||||
'status' => '1',
|
||||
'title' => 'draft pending revision',
|
||||
'uid' => '0',
|
||||
'created' => '1517725800',
|
||||
'changed' => '1517725800',
|
||||
'promote' => '1',
|
||||
'sticky' => '0',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '1',
|
||||
'vid' => '2',
|
||||
'langcode' => 'en',
|
||||
'status' => '0',
|
||||
'title' => 'draft pending revision',
|
||||
'uid' => '0',
|
||||
'created' => '1517725800',
|
||||
'changed' => '1517725800',
|
||||
'promote' => '1',
|
||||
'sticky' => '0',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '2',
|
||||
'vid' => '3',
|
||||
'langcode' => 'en',
|
||||
'status' => '1',
|
||||
'title' => 'published default revision',
|
||||
'uid' => '0',
|
||||
'created' => '1517725800',
|
||||
'changed' => '1517725800',
|
||||
'promote' => '1',
|
||||
'sticky' => '0',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '3',
|
||||
'vid' => '4',
|
||||
'langcode' => 'en',
|
||||
'status' => '1',
|
||||
'title' => 'archived default revision',
|
||||
'uid' => '0',
|
||||
'created' => '1517725800',
|
||||
'changed' => '1517725800',
|
||||
'promote' => '1',
|
||||
'sticky' => '0',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '3',
|
||||
'vid' => '5',
|
||||
'langcode' => 'en',
|
||||
'status' => '0',
|
||||
'title' => 'archived default revision',
|
||||
'uid' => '0',
|
||||
'created' => '1517725800',
|
||||
'changed' => '1517725800',
|
||||
'promote' => '1',
|
||||
'sticky' => '0',
|
||||
'default_langcode' => '1',
|
||||
'revision_translation_affected' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->insert('node_revision')
|
||||
->fields(array(
|
||||
'nid',
|
||||
'vid',
|
||||
'langcode',
|
||||
'revision_uid',
|
||||
'revision_timestamp',
|
||||
'revision_log',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '1',
|
||||
'vid' => '1',
|
||||
'langcode' => 'en',
|
||||
'revision_uid' => '0',
|
||||
'revision_timestamp' => '1517725800',
|
||||
'revision_log' => NULL,
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '1',
|
||||
'vid' => '2',
|
||||
'langcode' => 'en',
|
||||
'revision_uid' => '0',
|
||||
'revision_timestamp' => '1517725800',
|
||||
'revision_log' => NULL,
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '2',
|
||||
'vid' => '3',
|
||||
'langcode' => 'en',
|
||||
'revision_uid' => '0',
|
||||
'revision_timestamp' => '1517725800',
|
||||
'revision_log' => NULL,
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '3',
|
||||
'vid' => '4',
|
||||
'langcode' => 'en',
|
||||
'revision_uid' => '0',
|
||||
'revision_timestamp' => '1517725800',
|
||||
'revision_log' => NULL,
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '3',
|
||||
'vid' => '5',
|
||||
'langcode' => 'en',
|
||||
'revision_uid' => '0',
|
||||
'revision_timestamp' => '1517725800',
|
||||
'revision_log' => NULL,
|
||||
))
|
||||
->execute();
|
||||
+3
-3
@@ -8,8 +8,8 @@ dependencies:
|
||||
- content_moderation
|
||||
- node
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-01-03
|
||||
version: '8.4.4'
|
||||
# Information added by Drupal.org packaging script on 2018-03-07
|
||||
version: '8.5.0'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1515021228
|
||||
datestamp: 1520457826
|
||||
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_moderation
|
||||
- node
|
||||
- user
|
||||
id: test_content_moderation_field_state_test
|
||||
label: test_content_moderation_field_state_test
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: some
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
default_field_elements: true
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
settings:
|
||||
link_to_entity: true
|
||||
plugin_id: field
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
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
|
||||
moderation_state:
|
||||
id: moderation_state
|
||||
table: node_field_data
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: content_moderation_state
|
||||
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
|
||||
entity_type: node
|
||||
plugin_id: field
|
||||
filters: { }
|
||||
sorts: { }
|
||||
title: test_content_moderation_field_state_test
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: test-content-moderation-field-state-test
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_moderation
|
||||
- node
|
||||
- user
|
||||
id: test_content_moderation_state_filter_base_table
|
||||
label: test_content_moderation_state_filter_base_table
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: field
|
||||
filters:
|
||||
moderation_state:
|
||||
id: moderation_state
|
||||
table: node_field_data
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: moderation_state_op
|
||||
label: 'Default Revision State'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: moderation_state_op
|
||||
identifier: default_revision_state
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
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: { }
|
||||
entity_type: node
|
||||
plugin_id: moderation_state_filter
|
||||
moderation_state_1:
|
||||
id: moderation_state_1
|
||||
table: node_field_data
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: 'not empty'
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: node
|
||||
plugin_id: moderation_state_filter
|
||||
sorts:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: standard
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags:
|
||||
- 'config:workflow_list'
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: filter-test-path
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags:
|
||||
- 'config:workflow_list'
|
||||
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_moderation
|
||||
- node
|
||||
- user
|
||||
id: test_content_moderation_state_filter_base_table_filter_on_revision
|
||||
label: test_content_moderation_state_filter_base_table_filter_on_revision
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: field
|
||||
filters:
|
||||
moderation_state:
|
||||
id: moderation_state
|
||||
table: node_field_revision
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: moderation_state_op
|
||||
label: 'Moderation state'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: moderation_state_op
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
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: { }
|
||||
entity_type: node
|
||||
plugin_id: moderation_state_filter
|
||||
sorts:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: standard
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags:
|
||||
- 'config:workflow_list'
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: filter-on-revision-test-path
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags:
|
||||
- 'config:workflow_list'
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_moderation
|
||||
- entity_test
|
||||
id: test_content_moderation_state_filter_entity_test
|
||||
label: test_content_moderation_state_filter_entity_test
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: entity_test_no_bundle
|
||||
base_field: id
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
entity_id:
|
||||
id: entity_id
|
||||
table: content_revision_tracker
|
||||
field: entity_id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: standard
|
||||
filters:
|
||||
moderation_state:
|
||||
id: moderation_state
|
||||
table: entity_test_no_bundle
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: moderation_state_op
|
||||
label: 'Moderation state'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: moderation_state_op
|
||||
identifier: moderation_state
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
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: { }
|
||||
entity_type: entity_test_no_bundle
|
||||
plugin_id: moderation_state_filter
|
||||
sorts: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
tags: { }
|
||||
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_moderation
|
||||
- user
|
||||
id: test_content_moderation_state_filter_revision_table
|
||||
label: test_content_moderation_state_filter_revision_table
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_revision
|
||||
base_field: vid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'view all revisions'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_revision
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: true
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: field
|
||||
filters:
|
||||
moderation_state:
|
||||
id: moderation_state
|
||||
table: node_field_revision
|
||||
field: moderation_state
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: moderation_state_op
|
||||
label: 'Moderation state'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: moderation_state_op
|
||||
identifier: moderation_state
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
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: { }
|
||||
entity_type: node
|
||||
plugin_id: moderation_state_filter
|
||||
sorts:
|
||||
vid:
|
||||
id: vid
|
||||
table: node_field_revision
|
||||
field: vid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
entity_type: node
|
||||
entity_field: vid
|
||||
plugin_id: standard
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
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: { }
|
||||
|
||||
+4
-3
@@ -8,9 +8,10 @@ dependencies:
|
||||
- content_moderation
|
||||
- node
|
||||
- views
|
||||
- entity_test
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-01-03
|
||||
version: '8.4.4'
|
||||
# Information added by Drupal.org packaging script on 2018-03-07
|
||||
version: '8.5.0'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1515021228
|
||||
datestamp: 1520457826
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Test updating the ContentModerationState entity default revisions.
|
||||
*
|
||||
* @group Update
|
||||
* @see content_moderation_post_update_update_cms_default_revisions
|
||||
*/
|
||||
class DefaultContentModerationStateRevisionUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
|
||||
__DIR__ . '/../../fixtures/update/drupal-8.4.0-content_moderation_installed.php',
|
||||
__DIR__ . '/../../fixtures/update/drupal-8.default-cms-entity-id-2941736.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updating the default revision.
|
||||
*/
|
||||
public function testUpdateDefaultRevision() {
|
||||
$this->runUpdates();
|
||||
|
||||
foreach (['node', 'block_content'] as $entity_type_id) {
|
||||
$draft_pending_revision = $this->getEntityByLabel($entity_type_id, 'draft pending revision');
|
||||
$this->assertFalse($draft_pending_revision->isLatestRevision());
|
||||
$this->assertCompositeEntityMatchesDefaultRevisionId($draft_pending_revision);
|
||||
|
||||
$published_default_revision = $this->getEntityByLabel($entity_type_id, 'published default revision');
|
||||
$this->assertTrue($published_default_revision->isLatestRevision());
|
||||
$this->assertCompositeEntityMatchesDefaultRevisionId($published_default_revision);
|
||||
|
||||
$archived_default_revision = $this->getEntityByLabel($entity_type_id, 'archived default revision');
|
||||
$this->assertTrue($archived_default_revision->isLatestRevision());
|
||||
$this->assertCompositeEntityMatchesDefaultRevisionId($archived_default_revision);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert for the given entity, the default revision ID matches.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity to use for the assertion.
|
||||
*/
|
||||
protected function assertCompositeEntityMatchesDefaultRevisionId(ContentEntityInterface $entity) {
|
||||
$entity_type_manager = $this->container->get('entity_type.manager');
|
||||
$entity_list = $entity_type_manager->getStorage('content_moderation_state')
|
||||
->loadByProperties([
|
||||
'content_entity_type_id' => $entity->getEntityTypeId(),
|
||||
'content_entity_id' => $entity->id(),
|
||||
]);
|
||||
$content_moderation_state_entity = array_shift($entity_list);
|
||||
$this->assertEquals($entity->getLoadedRevisionId(), $content_moderation_state_entity->content_entity_revision_id->value);
|
||||
|
||||
// Check that the data table records were updated correctly.
|
||||
/** @var \Drupal\Core\Database\Connection $database */
|
||||
$database = $this->container->get('database');
|
||||
$query = 'SELECT * FROM {content_moderation_state_field_data} WHERE id = :id';
|
||||
$records = $database->query($query, [':id' => $content_moderation_state_entity->id()])
|
||||
->fetchAllAssoc('langcode');
|
||||
foreach ($records as $langcode => $record) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $translation */
|
||||
$translation = $content_moderation_state_entity->getTranslation($langcode);
|
||||
foreach ((array) $record as $field_name => $value) {
|
||||
if ($translation->hasField($field_name)) {
|
||||
$items = $translation->get($field_name)->getValue();
|
||||
$this->assertEquals(current($items[0]), $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an entity by label.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param string $label
|
||||
* The label of the entity to load.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityInterface
|
||||
* The loaded entity.
|
||||
*/
|
||||
protected function getEntityByLabel($entity_type_id, $label) {
|
||||
$entity_type_manager = $this->container->get('entity_type.manager');
|
||||
$label_field = $entity_type_manager->getDefinition($entity_type_id)->getKey('label');
|
||||
$entity_list = $entity_type_manager->getStorage($entity_type_id)
|
||||
->loadByProperties([$label_field => $label]);
|
||||
return array_shift($entity_list);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests moderated content administration page functionality.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ModeratedContentViewTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* A user with permission to bypass access content.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['content_moderation', 'node', 'views'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page'])->save();
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article'])->save();
|
||||
$this->drupalCreateContentType(['type' => 'unmoderated_type', 'name' => 'Unmoderated type'])->save();
|
||||
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'page');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article');
|
||||
$workflow->save();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(['access administration pages', 'view any unpublished content', 'administer nodes', 'bypass node access']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the moderated content page.
|
||||
*/
|
||||
public function testModeratedContentPage() {
|
||||
$assert_sesison = $this->assertSession();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Use an explicit changed time to ensure the expected order in the content
|
||||
// admin listing. We want these to appear in the table in the same order as
|
||||
// they appear in the following code, and the 'moderated_content' view has a
|
||||
// table style configuration with a default sort on the 'changed' field
|
||||
// descending.
|
||||
$time = \Drupal::time()->getRequestTime();
|
||||
$excluded_nodes['published_page'] = $this->drupalCreateNode(['type' => 'page', 'changed' => $time--, 'moderation_state' => 'published']);
|
||||
$excluded_nodes['published_article'] = $this->drupalCreateNode(['type' => 'article', 'changed' => $time--, 'moderation_state' => 'published']);
|
||||
|
||||
$excluded_nodes['unmoderated_type'] = $this->drupalCreateNode(['type' => 'unmoderated_type', 'changed' => $time--]);
|
||||
$excluded_nodes['unmoderated_type']->setNewRevision(TRUE);
|
||||
$excluded_nodes['unmoderated_type']->isDefaultRevision(FALSE);
|
||||
$excluded_nodes['unmoderated_type']->changed->value = $time--;
|
||||
$excluded_nodes['unmoderated_type']->save();
|
||||
|
||||
$nodes['published_then_draft_article'] = $this->drupalCreateNode(['type' => 'article', 'changed' => $time--, 'moderation_state' => 'published', 'title' => 'first article - published']);
|
||||
$nodes['published_then_draft_article']->setNewRevision(TRUE);
|
||||
$nodes['published_then_draft_article']->setTitle('first article - draft');
|
||||
$nodes['published_then_draft_article']->moderation_state->value = 'draft';
|
||||
$nodes['published_then_draft_article']->changed->value = $time--;
|
||||
$nodes['published_then_draft_article']->save();
|
||||
|
||||
$nodes['published_then_archived_article'] = $this->drupalCreateNode(['type' => 'article', 'changed' => $time--, 'moderation_state' => 'published']);
|
||||
$nodes['published_then_archived_article']->setNewRevision(TRUE);
|
||||
$nodes['published_then_archived_article']->moderation_state->value = 'archived';
|
||||
$nodes['published_then_archived_article']->changed->value = $time--;
|
||||
$nodes['published_then_archived_article']->save();
|
||||
|
||||
$nodes['draft_article'] = $this->drupalCreateNode(['type' => 'article', 'changed' => $time--, 'moderation_state' => 'draft']);
|
||||
$nodes['draft_page_1'] = $this->drupalCreateNode(['type' => 'page', 'changed' => $time--, 'moderation_state' => 'draft']);
|
||||
$nodes['draft_page_2'] = $this->drupalCreateNode(['type' => 'page', 'changed' => $time, 'moderation_state' => 'draft']);
|
||||
|
||||
// Verify view, edit, and delete links for any content.
|
||||
$this->drupalGet('admin/content/moderated');
|
||||
$assert_sesison->statusCodeEquals(200);
|
||||
|
||||
// Check that nodes with pending revisions appear in the view.
|
||||
$node_type_labels = $this->xpath('//td[contains(@class, "views-field-type")]');
|
||||
$delta = 0;
|
||||
foreach ($nodes as $node) {
|
||||
$assert_sesison->linkByHrefExists('node/' . $node->id());
|
||||
$assert_sesison->linkByHrefExists('node/' . $node->id() . '/edit');
|
||||
$assert_sesison->linkByHrefExists('node/' . $node->id() . '/delete');
|
||||
// Verify that we can see the content type label.
|
||||
$this->assertEquals($node->type->entity->label(), trim($node_type_labels[$delta]->getText()));
|
||||
$delta++;
|
||||
}
|
||||
|
||||
// Check that nodes that are not moderated or do not have a pending revision
|
||||
// do not appear in the view.
|
||||
foreach ($excluded_nodes as $node) {
|
||||
$assert_sesison->linkByHrefNotExists('node/' . $node->id());
|
||||
}
|
||||
|
||||
// Check that the latest revision is displayed.
|
||||
$assert_sesison->pageTextContains('first article - draft');
|
||||
$assert_sesison->pageTextNotContains('first article - published');
|
||||
|
||||
// Verify filtering by moderation state.
|
||||
$this->drupalGet('admin/content/moderated', ['query' => ['moderation_state' => 'editorial-draft']]);
|
||||
|
||||
$assert_sesison->linkByHrefExists('node/' . $nodes['published_then_draft_article']->id() . '/edit');
|
||||
$assert_sesison->linkByHrefExists('node/' . $nodes['draft_article']->id() . '/edit');
|
||||
$assert_sesison->linkByHrefExists('node/' . $nodes['draft_page_1']->id() . '/edit');
|
||||
$assert_sesison->linkByHrefExists('node/' . $nodes['draft_page_1']->id() . '/edit');
|
||||
$assert_sesison->linkByHrefNotExists('node/' . $nodes['published_then_archived_article']->id() . '/edit');
|
||||
|
||||
// Verify filtering by moderation state and content type.
|
||||
$this->drupalGet('admin/content/moderated', ['query' => ['moderation_state' => 'editorial-draft', 'type' => 'page']]);
|
||||
|
||||
$assert_sesison->linkByHrefExists('node/' . $nodes['draft_page_1']->id() . '/edit');
|
||||
$assert_sesison->linkByHrefExists('node/' . $nodes['draft_page_2']->id() . '/edit');
|
||||
$assert_sesison->linkByHrefNotExists('node/' . $nodes['published_then_draft_article']->id() . '/edit');
|
||||
$assert_sesison->linkByHrefNotExists('node/' . $nodes['published_then_archived_article']->id() . '/edit');
|
||||
$assert_sesison->linkByHrefNotExists('node/' . $nodes['draft_article']->id() . '/edit');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Tests the moderation form, specifically on nodes.
|
||||
@@ -79,6 +81,21 @@ class ModerationFormTest extends ModerationStateTestBase {
|
||||
$this->assertResponse(200);
|
||||
$this->assertField('edit-new-state', 'The node view page has a moderation form.');
|
||||
|
||||
// Preview the draft.
|
||||
$this->drupalPostForm($edit_path, [
|
||||
'body[0][value]' => 'Second version of the content.',
|
||||
'moderation_state[0][state]' => 'draft',
|
||||
], t('Preview'));
|
||||
|
||||
// The preview view should not have a moderation form.
|
||||
$preview_url = Url::fromRoute('entity.node.preview', [
|
||||
'node_preview' => $node->uuid(),
|
||||
'view_mode_id' => 'full',
|
||||
]);
|
||||
$this->assertResponse(200);
|
||||
$this->assertUrl($preview_url);
|
||||
$this->assertNoField('edit-new-state', 'The node preview page has no moderation form.');
|
||||
|
||||
// The latest version page should not show, because there is still no
|
||||
// pending revision.
|
||||
$this->drupalGet($latest_version_path);
|
||||
@@ -295,14 +312,6 @@ class ModerationFormTest extends ModerationStateTestBase {
|
||||
$this->drupalGet($latest_version_path, ['language' => $french]);
|
||||
$this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
|
||||
|
||||
// It should not be possible to add a new english revision.
|
||||
$this->drupalGet($edit_path);
|
||||
$this->assertSession()->fieldNotExists('moderation_state[0][state]');
|
||||
$this->assertSession()->pageTextContains('Unable to save this Moderated content.');
|
||||
|
||||
$this->clickLink('Publish');
|
||||
$this->assertSession()->fieldValueEquals('body[0][value]', 'Third version of the content.');
|
||||
|
||||
$this->drupalGet($edit_path);
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->buttonExists('Delete');
|
||||
@@ -323,7 +332,7 @@ class ModerationFormTest extends ModerationStateTestBase {
|
||||
$this->drupalGet($latest_version_path, ['language' => $french]);
|
||||
$this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
|
||||
|
||||
// Now we can publish the english (revision 5).
|
||||
// Publish the English pending revision (revision 5).
|
||||
$this->drupalGet($edit_path);
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
|
||||
@@ -336,13 +345,13 @@ class ModerationFormTest extends ModerationStateTestBase {
|
||||
$this->drupalGet($latest_version_path);
|
||||
$this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
|
||||
|
||||
// Make sure we're allowed to create a pending french revision.
|
||||
// Make sure we are allowed to create a pending French revision.
|
||||
$this->drupalGet($edit_path, ['language' => $french]);
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
|
||||
|
||||
// Add a english pending revision (revision 6).
|
||||
// Add an English pending revision (revision 6).
|
||||
$this->drupalGet($edit_path);
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
|
||||
@@ -354,16 +363,10 @@ class ModerationFormTest extends ModerationStateTestBase {
|
||||
|
||||
$this->drupalGet($latest_version_path);
|
||||
$this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
|
||||
|
||||
// Make sure we're not allowed to create a pending french revision.
|
||||
$this->drupalGet($edit_path, ['language' => $french]);
|
||||
$this->assertSession()->fieldNotExists('moderation_state[0][state]');
|
||||
$this->assertSession()->pageTextContains('Unable to save this Moderated content.');
|
||||
|
||||
$this->drupalGet($latest_version_path, ['language' => $french]);
|
||||
$this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
|
||||
|
||||
// We should be able to publish the english pending revision (revision 7)
|
||||
// Publish the English pending revision (revision 7)
|
||||
$this->drupalGet($edit_path);
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
|
||||
@@ -376,45 +379,18 @@ class ModerationFormTest extends ModerationStateTestBase {
|
||||
$this->drupalGet($latest_version_path);
|
||||
$this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
|
||||
|
||||
// Make sure we're allowed to create a pending french revision.
|
||||
// Make sure we are allowed to create a pending French revision.
|
||||
$this->drupalGet($edit_path, ['language' => $french]);
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
|
||||
|
||||
// Make sure we're allowed to create a pending english revision.
|
||||
// Make sure we are allowed to create a pending English revision.
|
||||
$this->drupalGet($edit_path);
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
|
||||
|
||||
// Create new moderated content. (revision 1).
|
||||
$this->drupalPostForm('node/add/moderated_content', [
|
||||
'title[0][value]' => 'Second moderated content',
|
||||
'body[0][value]' => 'First version of the content.',
|
||||
'moderation_state[0][state]' => 'published',
|
||||
], t('Save'));
|
||||
|
||||
$node = $this->drupalGetNodeByTitle('Second moderated content');
|
||||
$this->assertTrue($node->language(), 'en');
|
||||
$edit_path = sprintf('node/%d/edit', $node->id());
|
||||
$translate_path = sprintf('node/%d/translations/add/en/fr', $node->id());
|
||||
|
||||
// Add a pending revision (revision 2).
|
||||
$this->drupalGet($edit_path);
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'published');
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
|
||||
$this->drupalPostForm(NULL, [
|
||||
'body[0][value]' => 'Second version of the content.',
|
||||
'moderation_state[0][state]' => 'draft',
|
||||
], t('Save'));
|
||||
|
||||
// It shouldn't be possible to translate as we have a pending revision.
|
||||
$this->drupalGet($translate_path);
|
||||
$this->assertSession()->fieldNotExists('moderation_state[0][state]');
|
||||
$this->assertSession()->pageTextContains('Unable to save this Moderated content.');
|
||||
|
||||
// Create new moderated content (revision 1).
|
||||
$this->drupalPostForm('node/add/moderated_content', [
|
||||
'title[0][value]' => 'Third moderated content',
|
||||
@@ -444,11 +420,6 @@ class ModerationFormTest extends ModerationStateTestBase {
|
||||
'moderation_state[0][state]' => 'draft',
|
||||
], t('Save (this translation)'));
|
||||
|
||||
// Editing the original translation should not be possible.
|
||||
$this->drupalGet($edit_path);
|
||||
$this->assertSession()->fieldNotExists('moderation_state[0][state]');
|
||||
$this->assertSession()->pageTextContains('Unable to save this Moderated content.');
|
||||
|
||||
// Updating and publishing the french translation is still possible.
|
||||
$this->drupalGet($edit_path, ['language' => $french]);
|
||||
$this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
|
||||
@@ -468,6 +439,23 @@ class ModerationFormTest extends ModerationStateTestBase {
|
||||
], t('Save (this translation)'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the moderation_state field when an alternative widget is set.
|
||||
*/
|
||||
public function testAlternativeModerationStateWidget() {
|
||||
$entity_form_display = EntityFormDisplay::load('node.moderated_content.default');
|
||||
$entity_form_display->setComponent('moderation_state', [
|
||||
'type' => 'string_textfield',
|
||||
'region' => 'content',
|
||||
]);
|
||||
$entity_form_display->save();
|
||||
$this->drupalPostForm('node/add/moderated_content', [
|
||||
'title[0][value]' => 'Test content',
|
||||
'moderation_state[0][value]' => 'published',
|
||||
], 'Save');
|
||||
$this->assertSession()->pageTextContains('Moderated content Test content has been created.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that workflows and states can not be deleted if they are in use.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Test content_moderation functionality with localization and translation.
|
||||
*
|
||||
@@ -22,19 +24,23 @@ class ModerationLocaleTest extends ModerationStateTestBase {
|
||||
];
|
||||
|
||||
/**
|
||||
* Tests article translations can be moderated separately.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testTranslateModeratedContent() {
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->rootUser);
|
||||
|
||||
// Enable moderation on Article node type.
|
||||
$this->createContentTypeFromUi('Article', 'article', TRUE);
|
||||
|
||||
// Add French language.
|
||||
$edit = [
|
||||
'predefined_langcode' => 'fr',
|
||||
];
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
// Add French and Italian languages.
|
||||
foreach (['fr', 'it'] as $langcode) {
|
||||
$edit = [
|
||||
'predefined_langcode' => $langcode,
|
||||
];
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
}
|
||||
|
||||
// Enable content translation on articles.
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
@@ -48,7 +54,12 @@ class ModerationLocaleTest extends ModerationStateTestBase {
|
||||
// Adding languages requires a container rebuild in the test running
|
||||
// environment so that multilingual services are used.
|
||||
$this->rebuildContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests article translations can be moderated separately.
|
||||
*/
|
||||
public function testTranslateModeratedContent() {
|
||||
// Create a published article in English.
|
||||
$edit = [
|
||||
'title[0][value]' => 'Published English node',
|
||||
@@ -191,4 +202,358 @@ class ModerationLocaleTest extends ModerationStateTestBase {
|
||||
$this->assertFalse($french_node->isPublished());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that individual translations can be moderated independently.
|
||||
*/
|
||||
public function testLanguageIndependentContentModeration() {
|
||||
// Create a published article in English (revision 1).
|
||||
$this->drupalGet('node/add/article');
|
||||
$node = $this->submitNodeForm('Test 1.1 EN', 'published');
|
||||
$this->assertNotLatestVersionPage($node);
|
||||
|
||||
$edit_path = $node->toUrl('edit-form');
|
||||
$translate_path = $node->toUrl('drupal:content-translation-overview');
|
||||
|
||||
// Create a new English draft (revision 2).
|
||||
$this->drupalGet($edit_path);
|
||||
$this->submitNodeForm('Test 1.2 EN', 'draft', TRUE);
|
||||
$this->assertLatestVersionPage($node);
|
||||
|
||||
// Add a French translation draft (revision 3).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Add'));
|
||||
$this->submitNodeForm('Test 1.3 FR', 'draft');
|
||||
$fr_node = $this->loadTranslation($node, 'fr');
|
||||
$this->assertLatestVersionPage($fr_node);
|
||||
$this->assertModerationForm($node);
|
||||
|
||||
// Add an Italian translation draft (revision 4).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Add'));
|
||||
$this->submitNodeForm('Test 1.4 IT', 'draft');
|
||||
$it_node = $this->loadTranslation($node, 'it');
|
||||
$this->assertLatestVersionPage($it_node);
|
||||
$this->assertModerationForm($node);
|
||||
$this->assertModerationForm($fr_node);
|
||||
|
||||
// Publish the English draft (revision 5).
|
||||
$this->drupalGet($edit_path);
|
||||
$this->submitNodeForm('Test 1.5 EN', 'published', TRUE);
|
||||
$this->assertNotLatestVersionPage($node);
|
||||
$this->assertModerationForm($fr_node);
|
||||
$this->assertModerationForm($it_node);
|
||||
|
||||
// Publish the Italian draft (revision 6).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Edit'), 2);
|
||||
$this->submitNodeForm('Test 1.6 IT', 'published');
|
||||
$this->assertNotLatestVersionPage($it_node);
|
||||
$this->assertNoModerationForm($node);
|
||||
$this->assertModerationForm($fr_node);
|
||||
|
||||
// Publish the French draft (revision 7).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Edit'), 1);
|
||||
$this->submitNodeForm('Test 1.7 FR', 'published');
|
||||
$this->assertNotLatestVersionPage($fr_node);
|
||||
$this->assertNoModerationForm($node);
|
||||
$this->assertNoModerationForm($it_node);
|
||||
|
||||
// Create an Italian draft (revision 8).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Edit'), 2);
|
||||
$this->submitNodeForm('Test 1.8 IT', 'draft');
|
||||
$this->assertLatestVersionPage($it_node);
|
||||
$this->assertNoModerationForm($node);
|
||||
$this->assertNoModerationForm($fr_node);
|
||||
|
||||
// Create a French draft (revision 9).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Edit'), 1);
|
||||
$this->submitNodeForm('Test 1.9 FR', 'draft');
|
||||
$this->assertLatestVersionPage($fr_node);
|
||||
$this->assertNoModerationForm($node);
|
||||
$this->assertModerationForm($it_node);
|
||||
|
||||
// Create an English draft (revision 10).
|
||||
$this->drupalGet($edit_path);
|
||||
$this->submitNodeForm('Test 1.10 EN', 'draft');
|
||||
$this->assertLatestVersionPage($node);
|
||||
$this->assertModerationForm($fr_node);
|
||||
$this->assertModerationForm($it_node);
|
||||
|
||||
// Now start from a draft article in English (revision 1).
|
||||
$this->drupalGet('node/add/article');
|
||||
$node2 = $this->submitNodeForm('Test 2.1 EN', 'draft', TRUE);
|
||||
$this->assertNotLatestVersionPage($node2, TRUE);
|
||||
|
||||
$edit_path = $node2->toUrl('edit-form');
|
||||
$translate_path = $node2->toUrl('drupal:content-translation-overview');
|
||||
|
||||
// Add a French translation (revision 2).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Add'));
|
||||
$this->submitNodeForm('Test 2.2 FR', 'draft');
|
||||
$fr_node2 = $this->loadTranslation($node2, 'fr');
|
||||
$this->assertNotLatestVersionPage($fr_node2, TRUE);
|
||||
$this->assertModerationForm($node2, FALSE);
|
||||
|
||||
// Add an Italian translation (revision 3).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Add'));
|
||||
$this->submitNodeForm('Test 2.3 IT', 'draft');
|
||||
$it_node2 = $this->loadTranslation($node2, 'it');
|
||||
$this->assertNotLatestVersionPage($it_node2, TRUE);
|
||||
$this->assertModerationForm($node2, FALSE);
|
||||
$this->assertModerationForm($fr_node2, FALSE);
|
||||
|
||||
// Publish the English draft (revision 4).
|
||||
$this->drupalGet($edit_path);
|
||||
$this->submitNodeForm('Test 2.4 EN', 'published', TRUE);
|
||||
$this->assertNotLatestVersionPage($node2);
|
||||
$this->assertModerationForm($fr_node2, FALSE);
|
||||
$this->assertModerationForm($it_node2, FALSE);
|
||||
|
||||
// Publish the Italian draft (revision 5).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Edit'), 2);
|
||||
$this->submitNodeForm('Test 2.5 IT', 'published');
|
||||
$this->assertNotLatestVersionPage($it_node2);
|
||||
$this->assertNoModerationForm($node2);
|
||||
$this->assertModerationForm($fr_node2, FALSE);
|
||||
|
||||
// Publish the French draft (revision 6).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Edit'), 1);
|
||||
$this->submitNodeForm('Test 2.6 FR', 'published');
|
||||
$this->assertNotLatestVersionPage($fr_node2);
|
||||
$this->assertNoModerationForm($node2);
|
||||
$this->assertNoModerationForm($it_node2);
|
||||
|
||||
// Now that all revision translations are published, verify that the
|
||||
// moderation form is never displayed on revision pages.
|
||||
/** @var \Drupal\node\NodeStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
foreach (range(11, 16) as $revision_id) {
|
||||
/** @var \Drupal\node\NodeInterface $revision */
|
||||
$revision = $storage->loadRevision($revision_id);
|
||||
foreach ($revision->getTranslationLanguages() as $langcode => $language) {
|
||||
if ($revision->isRevisionTranslationAffected()) {
|
||||
$this->drupalGet($revision->toUrl('revision'));
|
||||
$this->assertFalse($this->hasModerationForm(), 'Moderation form is not displayed correctly for revision ' . $revision_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create an Italian draft (revision 7).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Edit'), 2);
|
||||
$this->submitNodeForm('Test 2.7 IT', 'draft');
|
||||
$this->assertLatestVersionPage($it_node2);
|
||||
$this->assertNoModerationForm($node2);
|
||||
$this->assertNoModerationForm($fr_node2);
|
||||
|
||||
// Create a French draft (revision 8).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Edit'), 1);
|
||||
$this->submitNodeForm('Test 2.8 FR', 'draft');
|
||||
$this->assertLatestVersionPage($fr_node2);
|
||||
$this->assertNoModerationForm($node2);
|
||||
$this->assertModerationForm($it_node2);
|
||||
|
||||
// Create an English draft (revision 9).
|
||||
$this->drupalGet($edit_path);
|
||||
$this->submitNodeForm('Test 2.9 EN', 'draft', TRUE);
|
||||
$this->assertLatestVersionPage($node2);
|
||||
$this->assertModerationForm($fr_node2);
|
||||
$this->assertModerationForm($it_node2);
|
||||
|
||||
// Now publish a draft in another language first and verify that the
|
||||
// moderation form is not displayed on the English node view page.
|
||||
$this->drupalGet('node/add/article');
|
||||
$node3 = $this->submitNodeForm('Test 3.1 EN', 'published');
|
||||
$this->assertNotLatestVersionPage($node3);
|
||||
|
||||
$edit_path = $node3->toUrl('edit-form');
|
||||
$translate_path = $node3->toUrl('drupal:content-translation-overview');
|
||||
|
||||
// Create an English draft (revision 2).
|
||||
$this->drupalGet($edit_path);
|
||||
$this->submitNodeForm('Test 3.2 EN', 'draft', TRUE);
|
||||
$this->assertLatestVersionPage($node3);
|
||||
|
||||
// Add a French translation (revision 3).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Add'));
|
||||
$this->submitNodeForm('Test 3.3 FR', 'draft');
|
||||
$fr_node3 = $this->loadTranslation($node3, 'fr');
|
||||
$this->assertLatestVersionPage($fr_node3);
|
||||
$this->assertModerationForm($node3);
|
||||
|
||||
// Publish the French draft (revision 4).
|
||||
$this->drupalGet($translate_path);
|
||||
$this->clickLink(t('Edit'), 1);
|
||||
$this->submitNodeForm('Test 3.4 FR', 'published');
|
||||
$this->assertNotLatestVersionPage($fr_node3);
|
||||
$this->assertModerationForm($node3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that new translation values are populated properly.
|
||||
*/
|
||||
public function testNewTranslationSourceValues() {
|
||||
// Create a published article in Italian (revision 1).
|
||||
$this->drupalGet('node/add/article');
|
||||
$node = $this->submitNodeForm('Test 1.1 IT', 'published', TRUE, 'it');
|
||||
$this->assertNotLatestVersionPage($node);
|
||||
|
||||
// Create a new draft (revision 2).
|
||||
$this->drupalGet($node->toUrl('edit-form'));
|
||||
$this->submitNodeForm('Test 1.2 IT', 'draft', TRUE);
|
||||
$this->assertLatestVersionPage($node);
|
||||
|
||||
// Create an English draft (revision 3) and verify that the Italian draft
|
||||
// values are used as source values.
|
||||
$url = $node->toUrl('drupal:content-translation-add');
|
||||
$url->setRouteParameter('source', 'it');
|
||||
$url->setRouteParameter('target', 'en');
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->pageTextContains('Test 1.2 IT');
|
||||
$this->submitNodeForm('Test 1.3 EN', 'draft');
|
||||
$this->assertLatestVersionPage($node);
|
||||
|
||||
// Create a French draft (without saving) and verify that the Italian draft
|
||||
// values are used as source values.
|
||||
$url->setRouteParameter('target', 'fr');
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->pageTextContains('Test 1.2 IT');
|
||||
|
||||
// Now switch source language and verify that the English draft values are
|
||||
// used as source values.
|
||||
$url->setRouteParameter('source', 'en');
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->pageTextContains('Test 1.3 EN');
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the node form at the current URL with the specified values.
|
||||
*
|
||||
* @param string $title
|
||||
* The node title.
|
||||
* @param string $moderation_state
|
||||
* The moderation state.
|
||||
* @param bool $default_translation
|
||||
* (optional) Whether we are editing the default translation.
|
||||
* @param string|null $langcode
|
||||
* (optional) The node language. Defaults to English.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface|null
|
||||
* A node object if a new one is being created, NULL otherwise.
|
||||
*/
|
||||
protected function submitNodeForm($title, $moderation_state, $default_translation = FALSE, $langcode = 'en') {
|
||||
$is_new = strpos($this->getSession()->getCurrentUrl(), '/node/add/') !== FALSE;
|
||||
$edit = [
|
||||
'title[0][value]' => $title,
|
||||
'moderation_state[0][state]' => $moderation_state,
|
||||
];
|
||||
if ($is_new) {
|
||||
$default_translation = TRUE;
|
||||
$edit['langcode[0][value]'] = $langcode;
|
||||
}
|
||||
$submit = $default_translation ? t('Save') : t('Save (this translation)');
|
||||
$this->drupalPostForm(NULL, $edit, $submit);
|
||||
$message = $is_new ? "Article $title has been created." : "Article $title has been updated.";
|
||||
$this->assertSession()->pageTextContains($message);
|
||||
return $is_new ? $this->drupalGetNodeByTitle($title) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the node translation for the specified language.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* A node object.
|
||||
* @param string $langcode
|
||||
* The translation language code.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface
|
||||
* The node translation object.
|
||||
*/
|
||||
protected function loadTranslation(NodeInterface $node, $langcode) {
|
||||
/** @var \Drupal\node\NodeStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node = $storage->loadRevision($storage->getLatestRevisionId($node->id()));
|
||||
return $node->getTranslation($langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that this is the "latest version" page for the specified node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* A node object.
|
||||
*/
|
||||
public function assertLatestVersionPage(NodeInterface $node) {
|
||||
$this->assertEquals($node->toUrl('latest-version')->setAbsolute()->toString(), $this->getSession()->getCurrentUrl());
|
||||
$this->assertModerationForm($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that this is not the "latest version" page for the specified node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* A node object.
|
||||
* @param bool $moderation_form
|
||||
* (optional) Whether the page should contain the moderation form. Defaults
|
||||
* to FALSE.
|
||||
*/
|
||||
public function assertNotLatestVersionPage(NodeInterface $node, $moderation_form = FALSE) {
|
||||
$this->assertNotEquals($node->toUrl('latest-version')->setAbsolute()->toString(), $this->getSession()->getCurrentUrl());
|
||||
if ($moderation_form) {
|
||||
$this->assertModerationForm($node, FALSE);
|
||||
}
|
||||
else {
|
||||
$this->assertNoModerationForm($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the moderation form is displayed for the specified node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* A node object.
|
||||
* @param bool $latest_tab
|
||||
* (optional) Whether the node form is expected to be displayed on the
|
||||
* latest version page or on the node view page. Defaults to the former.
|
||||
*/
|
||||
public function assertModerationForm(NodeInterface $node, $latest_tab = TRUE) {
|
||||
$this->drupalGet($node->toUrl());
|
||||
$this->assertEquals(!$latest_tab, $this->hasModerationForm());
|
||||
$this->drupalGet($node->toUrl('latest-version'));
|
||||
$this->assertEquals($latest_tab, $this->hasModerationForm());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the moderation form is not displayed for the specified node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* A node object.
|
||||
*/
|
||||
public function assertNoModerationForm(NodeInterface $node) {
|
||||
$this->drupalGet($node->toUrl());
|
||||
$this->assertFalse($this->hasModerationForm());
|
||||
$this->drupalGet($node->toUrl('latest-version'));
|
||||
$this->assertEquals(403, $this->getSession()->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the page contains the moderation form.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the moderation form could be find in the page, FALSE otherwise.
|
||||
*/
|
||||
public function hasModerationForm() {
|
||||
return (bool) $this->xpath('//ul[@class="entity-moderation-form"]');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ class ModerationRevisionRevertTest extends BrowserTestBase {
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'moderated_bundle');
|
||||
$workflow->save();
|
||||
|
||||
/** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */
|
||||
$router_builder = $this->container->get('router.builder');
|
||||
$router_builder->rebuildIfNeeded();
|
||||
|
||||
$admin = $this->drupalCreateUser([
|
||||
'access content overview',
|
||||
'administer nodes',
|
||||
|
||||
@@ -132,6 +132,9 @@ abstract class ModerationStateTestBase extends BrowserTestBase {
|
||||
// @see content_moderation_workflow_insert()
|
||||
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
/** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */
|
||||
$router_builder = $this->container->get('router.builder');
|
||||
$router_builder->rebuildIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\views\Functional\ViewTestBase;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests the views 'moderation_state_filter' filter plugin.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\content_moderation\Plugin\views\filter\ModerationStateFilter
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ViewsModerationStateFilterTest extends ViewTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_moderation_test_views',
|
||||
'node',
|
||||
'content_moderation',
|
||||
'workflows',
|
||||
'workflow_type_test',
|
||||
'entity_test',
|
||||
'language',
|
||||
'content_translation',
|
||||
'views_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE) {
|
||||
parent::setUp(FALSE);
|
||||
|
||||
NodeType::create([
|
||||
'type' => 'example_a',
|
||||
])->save();
|
||||
NodeType::create([
|
||||
'type' => 'example_b',
|
||||
])->save();
|
||||
|
||||
$new_workflow = Workflow::create([
|
||||
'type' => 'content_moderation',
|
||||
'id' => 'new_workflow',
|
||||
'label' => 'New workflow',
|
||||
]);
|
||||
$new_workflow->getTypePlugin()->addState('bar', 'Bar');
|
||||
$new_workflow->save();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer workflows', 'administer views']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the dependency handling of the moderation state filter.
|
||||
*
|
||||
* @covers ::calculateDependencies
|
||||
* @covers ::onDependencyRemoval
|
||||
*/
|
||||
public function testModerationStateFilterDependencyHandling() {
|
||||
// First, check that the view doesn't have any config dependency when there
|
||||
// are no states configured in the filter.
|
||||
$view_id = 'test_content_moderation_state_filter_base_table';
|
||||
$view = Views::getView($view_id);
|
||||
|
||||
$this->assertWorkflowDependencies([], $view);
|
||||
$this->assertTrue($view->storage->status());
|
||||
|
||||
// Configure the Editorial workflow for a node bundle, set the filter value
|
||||
// to use one of its states and check that the workflow is now a dependency
|
||||
// of the view.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
|
||||
'bundles[example_a]' => TRUE,
|
||||
], 'Save');
|
||||
|
||||
$edit['options[value][]'] = ['editorial-published'];
|
||||
$this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
$view = Views::getView($view_id);
|
||||
$this->assertWorkflowDependencies(['editorial'], $view);
|
||||
$this->assertTrue($view->storage->status());
|
||||
|
||||
// Create another workflow and repeat the checks above.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/add', [
|
||||
'label' => 'Translation',
|
||||
'id' => 'translation',
|
||||
'workflow_type' => 'content_moderation',
|
||||
], 'Save');
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/translation/add_state', [
|
||||
'label' => 'Needs Review',
|
||||
'id' => 'needs_review',
|
||||
], 'Save');
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/translation/type/node', [
|
||||
'bundles[example_b]' => TRUE,
|
||||
], 'Save');
|
||||
|
||||
$edit['options[value][]'] = ['editorial-published', 'translation-needs_review'];
|
||||
$this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
$view = Views::getView($view_id);
|
||||
$this->assertWorkflowDependencies(['editorial', 'translation'], $view);
|
||||
$this->assertTrue(isset($view->storage->getDisplay('default')['display_options']['filters']['moderation_state']));
|
||||
$this->assertTrue($view->storage->status());
|
||||
|
||||
// Remove the 'Translation' workflow.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/translation/delete', [], 'Delete');
|
||||
|
||||
// Check that the view has been disabled, the filter has been deleted, the
|
||||
// view can be saved and there are no more config dependencies.
|
||||
$view = Views::getView($view_id);
|
||||
$this->assertFalse($view->storage->status());
|
||||
$this->assertFalse(isset($view->storage->getDisplay('default')['display_options']['filters']['moderation_state']));
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
$this->assertWorkflowDependencies([], $view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the moderation state filter when the configured workflow is changed.
|
||||
*
|
||||
* @dataProvider providerTestWorkflowChanges
|
||||
*/
|
||||
public function testWorkflowChanges($view_id, $filter_name) {
|
||||
// Update the view and make the default filter not exposed anymore,
|
||||
// otherwise all results will be shown when there are no more moderated
|
||||
// bundles left.
|
||||
$this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", [], 'Hide filter');
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
// First, apply the Editorial workflow to both of our content types.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
|
||||
'bundles[example_a]' => TRUE,
|
||||
'bundles[example_b]' => TRUE,
|
||||
], 'Save');
|
||||
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
|
||||
|
||||
// Add a few nodes in various moderation states.
|
||||
$this->createNode(['type' => 'example_a', 'moderation_state' => 'published']);
|
||||
$this->createNode(['type' => 'example_b', 'moderation_state' => 'published']);
|
||||
$archived_node_a = $this->createNode(['type' => 'example_a', 'moderation_state' => 'archived']);
|
||||
$archived_node_b = $this->createNode(['type' => 'example_b', 'moderation_state' => 'archived']);
|
||||
|
||||
// Configure the view to only show nodes in the 'archived' moderation state.
|
||||
$edit['options[value][]'] = ['editorial-archived'];
|
||||
$this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
// Check that only the archived nodes from both bundles are displayed by the
|
||||
// view.
|
||||
$view = Views::getView($view_id);
|
||||
$this->executeView($view);
|
||||
$this->assertIdenticalResultset($view, [['nid' => $archived_node_a->id()], ['nid' => $archived_node_b->id()]], ['nid' => 'nid']);
|
||||
|
||||
// Remove the Editorial workflow from one of the bundles.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
|
||||
'bundles[example_a]' => TRUE,
|
||||
'bundles[example_b]' => FALSE,
|
||||
], 'Save');
|
||||
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
|
||||
|
||||
$view = Views::getView($view_id);
|
||||
$this->executeView($view);
|
||||
$this->assertIdenticalResultset($view, [['nid' => $archived_node_a->id()]], ['nid' => 'nid']);
|
||||
|
||||
// Check that the view can still be edited and saved without any
|
||||
// intervention.
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
// Remove the Editorial workflow from both bundles.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
|
||||
'bundles[example_a]' => FALSE,
|
||||
'bundles[example_b]' => FALSE,
|
||||
], 'Save');
|
||||
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
|
||||
|
||||
$view = Views::getView($view_id);
|
||||
$this->executeView($view);
|
||||
|
||||
// Check that the view doesn't return any result.
|
||||
$this->assertEmpty($view->result);
|
||||
|
||||
// Check that the view can not be edited without any intervention anymore
|
||||
// because the user needs to fix the filter.
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
$this->assertSession()->pageTextContains("No valid values found on filter: $filter_name.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testWorkflowChanges.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of view IDs.
|
||||
*/
|
||||
public function providerTestWorkflowChanges() {
|
||||
return [
|
||||
'view on base table, filter on base table' => [
|
||||
'test_content_moderation_state_filter_base_table',
|
||||
'Content: Moderation state'
|
||||
],
|
||||
'view on base table, filter on revision table' => [
|
||||
'test_content_moderation_state_filter_base_table_filter_on_revision',
|
||||
'Content revision: Moderation state'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the content moderation state filter caching is correct.
|
||||
*/
|
||||
public function testFilterRenderCache() {
|
||||
// Initially all states of the workflow are displayed.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/type/node', [
|
||||
'bundles[example_a]' => TRUE,
|
||||
], 'Save');
|
||||
$this->assertFilterStates(['All', 'editorial-draft', 'editorial-published', 'editorial-archived']);
|
||||
|
||||
// Adding a new state to the editorial workflow will display that state in
|
||||
// the list of filters.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/add_state', [
|
||||
'label' => 'Foo',
|
||||
'id' => 'foo',
|
||||
], 'Save');
|
||||
$this->assertFilterStates(['All', 'editorial-draft', 'editorial-published', 'editorial-archived', 'editorial-foo']);
|
||||
|
||||
// Adding a second workflow to nodes will also show new states.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/new_workflow/type/node', [
|
||||
'bundles[example_b]' => TRUE,
|
||||
], 'Save');
|
||||
$this->assertFilterStates(['All', 'editorial-draft', 'editorial-published', 'editorial-archived', 'editorial-foo', 'new_workflow-draft', 'new_workflow-published', 'new_workflow-bar']);
|
||||
|
||||
// Add a few more states and change the exposed filter to allow multiple
|
||||
// selections so we can check that the size of the select element does not
|
||||
// exceed 8 options.
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/add_state', [
|
||||
'label' => 'Foo 2',
|
||||
'id' => 'foo2',
|
||||
], 'Save');
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/editorial/add_state', [
|
||||
'label' => 'Foo 3',
|
||||
'id' => 'foo3',
|
||||
], 'Save');
|
||||
|
||||
$view_id = 'test_content_moderation_state_filter_base_table';
|
||||
$edit['options[expose][multiple]'] = TRUE;
|
||||
$this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/filter/moderation_state", $edit, 'Apply');
|
||||
$this->drupalPostForm("admin/structure/views/view/$view_id", [], 'Save');
|
||||
|
||||
$this->assertFilterStates(['editorial-draft', 'editorial-published', 'editorial-archived', 'editorial-foo', 'editorial-foo2', 'editorial-foo3', 'new_workflow-draft', 'new_workflow-published', 'new_workflow-bar'], TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the states which appear in the filter.
|
||||
*
|
||||
* @param array $states
|
||||
* The states which should appear in the filter.
|
||||
* @param bool $check_size
|
||||
* (optional) Whether to check that size of the select element is not
|
||||
* greater than 8. Defaults to FALSE.
|
||||
*/
|
||||
protected function assertFilterStates($states, $check_size = FALSE) {
|
||||
$this->drupalGet('/filter-test-path');
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Check that the select contains the correct number of options.
|
||||
$assert_session->elementsCount('css', '#edit-default-revision-state option', count($states));
|
||||
|
||||
// Check that the size of the select element does not exceed 8 options.
|
||||
if ($check_size) {
|
||||
$this->assertGreaterThan(8, count($states));
|
||||
$assert_session->elementAttributeContains('css', '#edit-default-revision-state', 'size', 8);
|
||||
}
|
||||
|
||||
// Check that an option exists for each of the expected states.
|
||||
foreach ($states as $state) {
|
||||
$assert_session->optionExists('Default Revision State', $state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the views dependencies on workflow config entities.
|
||||
*
|
||||
* @param string[] $workflow_ids
|
||||
* An array of workflow IDs to check.
|
||||
* @param \Drupal\views\ViewExecutable $view
|
||||
* An executable View object.
|
||||
*/
|
||||
protected function assertWorkflowDependencies(array $workflow_ids, ViewExecutable $view) {
|
||||
$dependencies = $view->getDependencies();
|
||||
|
||||
$expected = [];
|
||||
foreach (Workflow::loadMultiple($workflow_ids) as $workflow) {
|
||||
$expected[] = $workflow->getConfigDependencyName();
|
||||
}
|
||||
|
||||
if ($expected) {
|
||||
$this->assertSame($expected, $dependencies['config']);
|
||||
}
|
||||
else {
|
||||
$this->assertTrue(!isset($dependencies['config']));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@ class ContentModerationStateResourceTest extends KernelTestBase {
|
||||
public static $modules = ['serialization', 'rest', 'content_moderation'];
|
||||
|
||||
/**
|
||||
* @see content_moderation_rest_resource_alter()
|
||||
* @see \Drupal\content_moderation\Entity\ContentModerationState
|
||||
*/
|
||||
public function testCreateContentModerationStateResource() {
|
||||
$this->setExpectedException(PluginNotFoundException::class, 'The "entity:content_moderation_state" plugin does not exist.');
|
||||
|
||||
@@ -174,12 +174,11 @@ class ContentModerationStateTest extends KernelTestBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests removal of content moderation state entity field data.
|
||||
* Tests removal of content moderation state entity.
|
||||
*
|
||||
* @dataProvider basicModerationTestCases
|
||||
*/
|
||||
public function testContentModerationStateDataRemoval($entity_type_id) {
|
||||
// Test content moderation state deletion.
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $this->createEntity($entity_type_id);
|
||||
$entity->save();
|
||||
@@ -187,44 +186,80 @@ class ContentModerationStateTest extends KernelTestBase {
|
||||
$entity->delete();
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
|
||||
$this->assertFalse($content_moderation_state);
|
||||
}
|
||||
|
||||
// Test content moderation state revision deletion.
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity2 */
|
||||
$entity2 = $this->createEntity($entity_type_id);
|
||||
$entity2->save();
|
||||
$revision = clone $entity2;
|
||||
/**
|
||||
* Tests removal of content moderation state entity revisions.
|
||||
*
|
||||
* @dataProvider basicModerationTestCases
|
||||
*/
|
||||
public function testContentModerationStateRevisionDataRemoval($entity_type_id) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $this->createEntity($entity_type_id);
|
||||
$entity->save();
|
||||
$revision = clone $entity;
|
||||
$revision->isDefaultRevision(FALSE);
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($revision);
|
||||
$this->assertTrue($content_moderation_state);
|
||||
$entity2 = $this->reloadEntity($entity2);
|
||||
$entity2->setNewRevision(TRUE);
|
||||
$entity2->save();
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$entity->setNewRevision(TRUE);
|
||||
$entity->save();
|
||||
$entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
$entity_storage->deleteRevision($revision->getRevisionId());
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($revision);
|
||||
$this->assertFalse($content_moderation_state);
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity2);
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
|
||||
$this->assertTrue($content_moderation_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests removal of content moderation state pending entity revisions.
|
||||
*
|
||||
* @dataProvider basicModerationTestCases
|
||||
*/
|
||||
public function testContentModerationStatePendingRevisionDataRemoval($entity_type_id) {
|
||||
$entity = $this->createEntity($entity_type_id);
|
||||
$entity->moderation_state = 'published';
|
||||
$entity->save();
|
||||
$entity->setNewRevision(TRUE);
|
||||
$entity->moderation_state = 'draft';
|
||||
$entity->save();
|
||||
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
|
||||
$this->assertTrue($content_moderation_state);
|
||||
|
||||
$entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
$entity_storage->deleteRevision($entity->getRevisionId());
|
||||
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
|
||||
$this->assertFalse($content_moderation_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests removal of content moderation state translations.
|
||||
*
|
||||
* @dataProvider basicModerationTestCases
|
||||
*/
|
||||
public function testContentModerationStateTranslationDataRemoval($entity_type_id) {
|
||||
// Test content moderation state translation deletion.
|
||||
if ($this->entityTypeManager->getDefinition($entity_type_id)->isTranslatable()) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity3 */
|
||||
$entity3 = $this->createEntity($entity_type_id);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $this->createEntity($entity_type_id);
|
||||
$langcode = 'it';
|
||||
ConfigurableLanguage::createFromLangcode($langcode)
|
||||
->save();
|
||||
$entity3->save();
|
||||
$translation = $entity3->addTranslation($langcode, ['title' => 'Titolo test']);
|
||||
$entity->save();
|
||||
$translation = $entity->addTranslation($langcode, ['title' => 'Titolo test']);
|
||||
// Make sure we add values for all of the required fields.
|
||||
if ($entity_type_id == 'block_content') {
|
||||
$translation->info = $this->randomString();
|
||||
}
|
||||
$translation->save();
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity3);
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
|
||||
$this->assertTrue($content_moderation_state->hasTranslation($langcode));
|
||||
$entity3->removeTranslation($langcode);
|
||||
$entity3->save();
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity3);
|
||||
$entity->removeTranslation($langcode);
|
||||
$entity->save();
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
|
||||
$this->assertFalse($content_moderation_state->hasTranslation($langcode));
|
||||
}
|
||||
}
|
||||
@@ -536,6 +571,48 @@ class ContentModerationStateTest extends KernelTestBase {
|
||||
], $workflow->getDependencies());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the revision default state of the moderation state entity revisions.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The ID of entity type to be tested.
|
||||
*
|
||||
* @dataProvider basicModerationTestCases
|
||||
*/
|
||||
public function testRevisionDefaultState($entity_type_id) {
|
||||
// Check that the revision default state of the moderated entity and the
|
||||
// content moderation state entity always match.
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $cms_storage */
|
||||
$cms_storage = $this->entityTypeManager->getStorage('content_moderation_state');
|
||||
|
||||
$entity = $this->createEntity($entity_type_id);
|
||||
$entity->get('moderation_state')->value = 'published';
|
||||
$storage->save($entity);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
|
||||
$cms_entity = $cms_storage->loadUnchanged(1);
|
||||
$this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
|
||||
|
||||
$entity->get('moderation_state')->value = 'published';
|
||||
$storage->save($entity);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
|
||||
$cms_entity = $cms_storage->loadUnchanged(1);
|
||||
$this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
|
||||
|
||||
$entity->get('moderation_state')->value = 'draft';
|
||||
$storage->save($entity);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
|
||||
$cms_entity = $cms_storage->loadUnchanged(1);
|
||||
$this->assertEquals($entity->getLoadedRevisionId() - 1, $cms_entity->get('content_entity_revision_id')->value);
|
||||
|
||||
$entity->get('moderation_state')->value = 'published';
|
||||
$storage->save($entity);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
|
||||
$cms_entity = $cms_storage->loadUnchanged(1);
|
||||
$this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an entity.
|
||||
*
|
||||
|
||||
@@ -2,21 +2,22 @@
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\content_moderation\ParamConverter\EntityRevisionConverter
|
||||
* @group content_moderation
|
||||
* @group legacy
|
||||
*/
|
||||
class EntityRevisionConverterTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'user',
|
||||
'entity_test',
|
||||
'system',
|
||||
'content_moderation',
|
||||
'node',
|
||||
@@ -28,74 +29,36 @@ class EntityRevisionConverterTest extends KernelTestBase {
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('content_moderation_state');
|
||||
$this->installSchema('system', 'router');
|
||||
$this->installSchema('system', 'sequences');
|
||||
$this->installSchema('node', 'node_access');
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::convert
|
||||
* @expectedDeprecationMessage The load_pending_revision flag has been deprecated. You should use load_latest_revision instead.
|
||||
*/
|
||||
public function testConvertNonRevisionableEntityType() {
|
||||
$entity_test = EntityTest::create([
|
||||
'name' => 'test',
|
||||
]);
|
||||
|
||||
$entity_test->save();
|
||||
|
||||
/** @var \Symfony\Component\Routing\RouterInterface $router */
|
||||
$router = \Drupal::service('router.no_access_checks');
|
||||
$result = $router->match('/entity_test/' . $entity_test->id());
|
||||
|
||||
$this->assertInstanceOf(EntityTest::class, $result['entity_test']);
|
||||
$this->assertEquals($entity_test->getRevisionId(), $result['entity_test']->getRevisionId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::convert
|
||||
*/
|
||||
public function testConvertWithRevisionableEntityType() {
|
||||
$this->installConfig(['content_moderation']);
|
||||
$node_type = NodeType::create([
|
||||
public function testDeprecatedLoadPendingRevisionFlag() {
|
||||
NodeType::create([
|
||||
'type' => 'article',
|
||||
]);
|
||||
$node_type->save();
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article');
|
||||
$workflow->save();
|
||||
])->save();
|
||||
|
||||
$revision_ids = [];
|
||||
$node = Node::create([
|
||||
'title' => 'test',
|
||||
'type' => 'article',
|
||||
]);
|
||||
$node->moderation_state->value = 'published';
|
||||
$node->save();
|
||||
|
||||
$revision_ids[] = $node->getRevisionId();
|
||||
|
||||
$node->isDefaultRevision(FALSE);
|
||||
$node->setNewRevision(TRUE);
|
||||
$node->save();
|
||||
$revision_ids[] = $node->getRevisionId();
|
||||
|
||||
$node->setNewRevision(TRUE);
|
||||
$node->moderation_state->value = 'draft';
|
||||
$node->save();
|
||||
$revision_ids[] = $node->getRevisionId();
|
||||
|
||||
/** @var \Symfony\Component\Routing\RouterInterface $router */
|
||||
$router = \Drupal::service('router.no_access_checks');
|
||||
$result = $router->match('/node/' . $node->id() . '/edit');
|
||||
|
||||
$this->assertInstanceOf(Node::class, $result['node']);
|
||||
$this->assertEquals($revision_ids[2], $result['node']->getRevisionId());
|
||||
$this->assertFalse($result['node']->isDefaultRevision());
|
||||
$converted = $this->container->get('paramconverter.latest_revision')->convert($node->id(), [
|
||||
'load_pending_revision' => TRUE,
|
||||
'type' => 'entity:node',
|
||||
], 'node', []);
|
||||
$this->assertEquals($converted->getLoadedRevisionId(), $node->getLoadedRevisionId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-4
@@ -181,7 +181,7 @@ class EntityStateChangeValidationTest extends KernelTestBase {
|
||||
]);
|
||||
$node->save();
|
||||
|
||||
$node_fr = $node->addTranslation('fr');
|
||||
$node_fr = $node->addTranslation('fr', $node->toArray());
|
||||
$node_fr->setTitle('French Published Node');
|
||||
$node_fr->save();
|
||||
$this->assertEquals('published', $node_fr->moderation_state->value);
|
||||
@@ -207,7 +207,7 @@ class EntityStateChangeValidationTest extends KernelTestBase {
|
||||
$this->assertCount(0, $violations);
|
||||
|
||||
// From the latest french revision, there should also be no violation.
|
||||
$node_fr = $node->getTranslation('fr');
|
||||
$node_fr = Node::load($node->id())->getTranslation('fr');
|
||||
$this->assertEquals('published', $node_fr->moderation_state->value);
|
||||
$node_fr->moderation_state = 'archived';
|
||||
$violations = $node_fr->validate();
|
||||
@@ -217,7 +217,7 @@ class EntityStateChangeValidationTest extends KernelTestBase {
|
||||
/**
|
||||
* Tests that content without prior moderation information can be moderated.
|
||||
*/
|
||||
public function testLegacyContent() {
|
||||
public function testExistingContentWithNoModeration() {
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'example',
|
||||
]);
|
||||
@@ -251,7 +251,7 @@ class EntityStateChangeValidationTest extends KernelTestBase {
|
||||
/**
|
||||
* Tests that content without prior moderation information can be translated.
|
||||
*/
|
||||
public function testLegacyMultilingualContent() {
|
||||
public function testExistingMultilingualContentWithNoModeration() {
|
||||
// Enable French.
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
|
||||
$this->installEntitySchema('content_moderation_state');
|
||||
$this->installConfig('content_moderation');
|
||||
|
||||
NodeType::create([
|
||||
'type' => 'unmoderated',
|
||||
])->save();
|
||||
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'example',
|
||||
]);
|
||||
@@ -79,6 +83,51 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
|
||||
$this->assertEquals(['draft'], $states);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getValue
|
||||
*/
|
||||
public function testGetValue() {
|
||||
$this->assertEquals([['value' => 'draft']], $this->testNode->moderation_state->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGet() {
|
||||
$this->assertEquals('draft', $this->testNode->moderation_state->get(0)->value);
|
||||
$this->setExpectedException(\InvalidArgumentException::class);
|
||||
$this->testNode->moderation_state->get(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the computed field when it is unset or set to an empty value.
|
||||
*/
|
||||
public function testSetEmptyState() {
|
||||
$this->testNode->moderation_state->value = '';
|
||||
$this->assertEquals('draft', $this->testNode->moderation_state->value);
|
||||
|
||||
$this->testNode->moderation_state = '';
|
||||
$this->assertEquals('draft', $this->testNode->moderation_state->value);
|
||||
|
||||
unset($this->testNode->moderation_state);
|
||||
$this->assertEquals('draft', $this->testNode->moderation_state->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the list class with a non moderated entity.
|
||||
*/
|
||||
public function testNonModeratedEntity() {
|
||||
$unmoderated_node = Node::create([
|
||||
'type' => 'unmoderated',
|
||||
'title' => 'Test title',
|
||||
]);
|
||||
$unmoderated_node->save();
|
||||
$this->assertEquals(0, $unmoderated_node->moderation_state->count());
|
||||
|
||||
$unmoderated_node->moderation_state = NULL;
|
||||
$this->assertEquals(0, $unmoderated_node->moderation_state->count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that moderation state changes also change the related entity state.
|
||||
*/
|
||||
@@ -124,4 +173,15 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
|
||||
$this->assertTrue($test_node->isPublished());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the moderation_state field after an entity has been serialized.
|
||||
*/
|
||||
public function testEntityUnserialize() {
|
||||
$this->testNode->moderation_state->value = 'draft';
|
||||
$unserialized = unserialize(serialize($this->testNode));
|
||||
|
||||
$this->assertEquals('Test title', $unserialized->title->value);
|
||||
$this->assertEquals('draft', $unserialized->moderation_state->value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -108,14 +108,12 @@ class ViewsDataIntegrationTest extends ViewsKernelTestBase {
|
||||
$expected_result = [
|
||||
[
|
||||
'nid' => $node->id(),
|
||||
// @todo I would have expected that the content_moderation_state default
|
||||
// revision is the same one as in the node, but it isn't.
|
||||
// Joins from the base table to the default revision of the
|
||||
// content_moderation.
|
||||
'moderation_state' => 'draft',
|
||||
'moderation_state' => 'published',
|
||||
// Joins from the revision table to the default revision of the
|
||||
// content_moderation.
|
||||
'moderation_state_1' => 'draft',
|
||||
'moderation_state_1' => 'published',
|
||||
// Joins from the revision table to the revision of the
|
||||
// content_moderation.
|
||||
'moderation_state_2' => 'published',
|
||||
@@ -124,4 +122,27 @@ class ViewsDataIntegrationTest extends ViewsKernelTestBase {
|
||||
$this->assertIdenticalResultset($view, $expected_result, ['nid' => 'nid', 'moderation_state' => 'moderation_state', 'moderation_state_1' => 'moderation_state_1', 'moderation_state_2' => 'moderation_state_2']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the content moderation state views field.
|
||||
*/
|
||||
public function testContentModerationStateField() {
|
||||
$node = Node::create([
|
||||
'type' => 'page',
|
||||
'title' => 'Test title',
|
||||
]);
|
||||
$node->moderation_state->value = 'published';
|
||||
$node->save();
|
||||
|
||||
$view = Views::getView('test_content_moderation_field_state_test');
|
||||
$view->execute();
|
||||
|
||||
$expected_result = [
|
||||
[
|
||||
'title' => 'Test title',
|
||||
'moderation_state' => 'published',
|
||||
],
|
||||
];
|
||||
$this->assertIdenticalResultset($view, $expected_result, ['title' => 'title', 'moderation_state' => 'moderation_state']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestNoBundle;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Tests the views 'moderation_state_filter' filter plugin.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\content_moderation\Plugin\views\filter\ModerationStateFilter
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ViewsModerationStateFilterTest extends ViewsKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_moderation_test_views',
|
||||
'node',
|
||||
'content_moderation',
|
||||
'workflows',
|
||||
'workflow_type_test',
|
||||
'entity_test',
|
||||
'language',
|
||||
'content_translation',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE) {
|
||||
parent::setUp(FALSE);
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('content_moderation_state');
|
||||
$this->installEntitySchema('entity_test_no_bundle');
|
||||
$this->installSchema('node', 'node_access');
|
||||
$this->installConfig('content_moderation_test_views');
|
||||
$this->installConfig('content_moderation');
|
||||
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'example',
|
||||
]);
|
||||
$node_type->save();
|
||||
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'another_example',
|
||||
]);
|
||||
$node_type->save();
|
||||
|
||||
$node_type = NodeType::create([
|
||||
'type' => 'example_non_moderated',
|
||||
]);
|
||||
$node_type->save();
|
||||
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the content moderation state filter.
|
||||
*/
|
||||
public function testStateFilterViewsRelationship() {
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->getTypePlugin()->addState('translated_draft', 'Bar');
|
||||
$configuration = $workflow->getTypePlugin()->getConfiguration();
|
||||
$configuration['states']['translated_draft'] += [
|
||||
'published' => FALSE,
|
||||
'default_revision' => FALSE,
|
||||
];
|
||||
$workflow->getTypePlugin()->setConfiguration($configuration);
|
||||
$workflow->save();
|
||||
|
||||
// Create a published default revision and one forward draft revision.
|
||||
$node = Node::create([
|
||||
'type' => 'example',
|
||||
'title' => 'Test Node',
|
||||
'moderation_state' => 'published',
|
||||
]);
|
||||
$node->save();
|
||||
$node->setNewRevision();
|
||||
$node->moderation_state = 'draft';
|
||||
$node->save();
|
||||
|
||||
// Create a draft default revision.
|
||||
$second_node = Node::create([
|
||||
'type' => 'example',
|
||||
'title' => 'Second Node',
|
||||
'moderation_state' => 'draft',
|
||||
]);
|
||||
$second_node->save();
|
||||
|
||||
// Create a published default revision.
|
||||
$third_node = Node::create([
|
||||
'type' => 'example',
|
||||
'title' => 'Third node',
|
||||
'moderation_state' => 'published',
|
||||
]);
|
||||
$third_node->save();
|
||||
|
||||
// Add a non-moderated node.
|
||||
$fourth_node = Node::create([
|
||||
'type' => 'example_non_moderated',
|
||||
'title' => 'Fourth node',
|
||||
]);
|
||||
$fourth_node->save();
|
||||
|
||||
// Create a translated published revision.
|
||||
$translated_forward_revision = $third_node->addTranslation('fr');
|
||||
$translated_forward_revision->title = 'Translated Node';
|
||||
$translated_forward_revision->setNewRevision(TRUE);
|
||||
$translated_forward_revision->moderation_state = 'translated_draft';
|
||||
$translated_forward_revision->save();
|
||||
|
||||
// The three default revisions are listed when no filter is specified.
|
||||
$this->assertNodesWithFilters([$node, $second_node, $third_node], []);
|
||||
|
||||
// The default revision of node one and three are published.
|
||||
$this->assertNodesWithFilters([$node, $third_node], [
|
||||
'default_revision_state' => 'editorial-published',
|
||||
]);
|
||||
|
||||
// The default revision of node two is draft.
|
||||
$this->assertNodesWithFilters([$second_node], [
|
||||
'default_revision_state' => 'editorial-draft',
|
||||
]);
|
||||
|
||||
// Test the same three revisions on a view displaying content revisions.
|
||||
// Both nodes have one draft revision.
|
||||
$this->assertNodesWithFilters([$node, $second_node], [
|
||||
'moderation_state' => 'editorial-draft',
|
||||
], 'test_content_moderation_state_filter_revision_table');
|
||||
// Creating a new forward revision of node three, creates a second published
|
||||
// revision of of the original language, hence there are two published
|
||||
// revisions of node three.
|
||||
$this->assertNodesWithFilters([$node, $third_node, $third_node], [
|
||||
'moderation_state' => 'editorial-published',
|
||||
], 'test_content_moderation_state_filter_revision_table');
|
||||
// There is a single forward translated revision with a new state, which is
|
||||
// also filterable.
|
||||
$this->assertNodesWithFilters([$translated_forward_revision], [
|
||||
'moderation_state' => 'editorial-translated_draft',
|
||||
], 'test_content_moderation_state_filter_revision_table');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the moderation filter with a non-translatable entity type.
|
||||
*/
|
||||
public function testNonTranslatableEntityType() {
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_no_bundle', 'entity_test_no_bundle');
|
||||
$workflow->save();
|
||||
|
||||
$test_entity = EntityTestNoBundle::create([
|
||||
'moderation_state' => 'draft',
|
||||
]);
|
||||
$test_entity->save();
|
||||
|
||||
$view = Views::getView('test_content_moderation_state_filter_entity_test');
|
||||
$view->setExposedInput([
|
||||
'moderation_state' => 'editorial-draft',
|
||||
]);
|
||||
$view->execute();
|
||||
$this->assertIdenticalResultset($view, [['id' => $test_entity->id()]], ['id' => 'id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the list of states in the filter plugin.
|
||||
*/
|
||||
public function testStateFilterStatesList() {
|
||||
// By default a view of nodes will not have states to filter.
|
||||
$this->assertPluginStates([]);
|
||||
|
||||
// Adding a content type to the editorial workflow will enable all of the
|
||||
// editorial states.
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->save();
|
||||
$this->assertPluginStates([
|
||||
'Editorial' => [
|
||||
'editorial-draft' => 'Draft',
|
||||
'editorial-published' => 'Published',
|
||||
'editorial-archived' => 'Archived',
|
||||
],
|
||||
]);
|
||||
|
||||
// Adding a workflow which is not content moderation will not add any
|
||||
// additional states to the views filter.
|
||||
$workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_complex_test']);
|
||||
$workflow->getTypePlugin()->addState('draft', 'Draft');
|
||||
$workflow->save();
|
||||
$this->assertPluginStates([
|
||||
'Editorial' => [
|
||||
'editorial-draft' => 'Draft',
|
||||
'editorial-published' => 'Published',
|
||||
'editorial-archived' => 'Archived',
|
||||
],
|
||||
]);
|
||||
|
||||
// Adding a new content moderation workflow will add additional states to
|
||||
// filter.
|
||||
$workflow = Workflow::create(['id' => 'moderation_test', 'type' => 'content_moderation', 'label' => 'Moderation test']);
|
||||
$workflow->getTypePlugin()->addState('foo', 'Foo State');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
|
||||
$workflow->save();
|
||||
$this->assertPluginStates([
|
||||
'Editorial' => [
|
||||
'editorial-draft' => 'Draft',
|
||||
'editorial-published' => 'Published',
|
||||
'editorial-archived' => 'Archived',
|
||||
],
|
||||
'Moderation test' => [
|
||||
'moderation_test-foo' => 'Foo State',
|
||||
'moderation_test-draft' => 'Draft',
|
||||
'moderation_test-published' => 'Published',
|
||||
],
|
||||
]);
|
||||
|
||||
// Deleting a workflow will remove the states from the filter.
|
||||
$workflow = Workflow::load('moderation_test');
|
||||
$workflow->delete();
|
||||
$this->assertPluginStates([
|
||||
'Editorial' => [
|
||||
'editorial-draft' => 'Draft',
|
||||
'editorial-published' => 'Published',
|
||||
'editorial-archived' => 'Archived',
|
||||
],
|
||||
]);
|
||||
|
||||
// Deleting a state from a workflow will remove the state from the filter.
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->deleteState('archived');
|
||||
$workflow->save();
|
||||
$this->assertPluginStates([
|
||||
'Editorial' => [
|
||||
'editorial-draft' => 'Draft',
|
||||
'editorial-published' => 'Published',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the plugin states.
|
||||
*
|
||||
* @param string[] $states
|
||||
* The states which should appear in the filter.
|
||||
*/
|
||||
protected function assertPluginStates($states) {
|
||||
$plugin = Views::pluginManager('filter')->createInstance('moderation_state_filter', []);
|
||||
$view = Views::getView('test_content_moderation_state_filter_base_table');
|
||||
$plugin->init($view, $view->getDisplay());
|
||||
$this->assertEquals($states, $plugin->getValueOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the nodes appear when the test view is executed.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface[] $nodes
|
||||
* Nodes to assert are in the views result.
|
||||
* @param array $filters
|
||||
* An array of filters to apply to the view.
|
||||
* @param string $view_id
|
||||
* The view to execute for the results.
|
||||
*/
|
||||
protected function assertNodesWithFilters(array $nodes, array $filters, $view_id = 'test_content_moderation_state_filter_base_table') {
|
||||
$view = Views::getView($view_id);
|
||||
$view->setExposedInput($filters);
|
||||
$view->execute();
|
||||
|
||||
// Verify the join configuration.
|
||||
$query = $view->getQuery();
|
||||
$join = $query->getTableInfo('content_moderation_state')['join'];
|
||||
$configuration = $join->configuration;
|
||||
$this->assertEquals('content_moderation_state_field_revision', $configuration['table']);
|
||||
$this->assertEquals('content_entity_revision_id', $configuration['field']);
|
||||
$this->assertEquals('vid', $configuration['left_field']);
|
||||
$this->assertEquals('content_entity_type_id', $configuration['extra'][0]['field']);
|
||||
$this->assertEquals('node', $configuration['extra'][0]['value']);
|
||||
$this->assertEquals('langcode', $configuration['extra'][1]['field']);
|
||||
$this->assertEquals('langcode', $configuration['extra'][1]['left_field']);
|
||||
|
||||
$expected_result = [];
|
||||
foreach ($nodes as $node) {
|
||||
$expected_result[] = ['nid' => $node->id()];
|
||||
}
|
||||
$this->assertIdenticalResultset($view, $expected_result, ['nid' => 'nid']);
|
||||
}
|
||||
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Unit;
|
||||
|
||||
use Drupal\content_moderation\Routing\ContentModerationRouteSubscriber;
|
||||
use Drupal\Core\Entity\Entity;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\RouteBuildEvent;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\content_moderation\Routing\ContentModerationRouteSubscriber
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ContentModerationRouteSubscriberTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The test content moderation route subscriber.
|
||||
*
|
||||
* @var \Drupal\content_moderation\Routing\ContentModerationRouteSubscriber
|
||||
*/
|
||||
protected $routeSubscriber;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
|
||||
$entity_type_manager = $this->createMock(EntityTypeManagerInterface::class);
|
||||
$this->routeSubscriber = new ContentModerationRouteSubscriber($entity_type_manager);
|
||||
$this->setupEntityTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity manager mock returning entity type objects.
|
||||
*/
|
||||
protected function setupEntityTypes() {
|
||||
$definition = $this->createMock(EntityTypeInterface::class);
|
||||
$definition->expects($this->any())
|
||||
->method('getClass')
|
||||
->will($this->returnValue(SimpleTestEntity::class));
|
||||
$definition->expects($this->any())
|
||||
->method('isRevisionable')
|
||||
->willReturn(FALSE);
|
||||
$revisionable_definition = $this->createMock(EntityTypeInterface::class);
|
||||
$revisionable_definition->expects($this->any())
|
||||
->method('getClass')
|
||||
->will($this->returnValue(SimpleTestEntity::class));
|
||||
$revisionable_definition->expects($this->any())
|
||||
->method('isRevisionable')
|
||||
->willReturn(TRUE);
|
||||
$entity_types = [
|
||||
'entity_test' => $definition,
|
||||
'entity_test_rev' => $revisionable_definition,
|
||||
];
|
||||
|
||||
$reflector = new \ReflectionProperty($this->routeSubscriber, 'moderatedEntityTypes');
|
||||
$reflector->setAccessible(TRUE);
|
||||
$reflector->setValue($this->routeSubscriber, $entity_types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for ::testSetLatestRevisionFlag.
|
||||
*/
|
||||
public function setLatestRevisionFlagTestCases() {
|
||||
return [
|
||||
'Entity parameter not on an entity form' => [
|
||||
[],
|
||||
[
|
||||
'entity_test' => [
|
||||
'type' => 'entity:entity_test_rev',
|
||||
],
|
||||
],
|
||||
],
|
||||
'Entity parameter on an entity form' => [
|
||||
[
|
||||
'_entity_form' => 'entity_test_rev.edit'
|
||||
],
|
||||
[
|
||||
'entity_test_rev' => [
|
||||
'type' => 'entity:entity_test_rev',
|
||||
],
|
||||
],
|
||||
[
|
||||
'entity_test_rev' => [
|
||||
'type' => 'entity:entity_test_rev',
|
||||
'load_latest_revision' => TRUE,
|
||||
],
|
||||
],
|
||||
],
|
||||
'Entity form with no operation' => [
|
||||
[
|
||||
'_entity_form' => 'entity_test_rev'
|
||||
],
|
||||
[
|
||||
'entity_test_rev' => [
|
||||
'type' => 'entity:entity_test_rev',
|
||||
],
|
||||
],
|
||||
[
|
||||
'entity_test_rev' => [
|
||||
'type' => 'entity:entity_test_rev',
|
||||
'load_latest_revision' => TRUE,
|
||||
],
|
||||
],
|
||||
],
|
||||
'Non-moderated entity form' => [
|
||||
[
|
||||
'_entity_form' => 'entity_test_mulrev'
|
||||
],
|
||||
[
|
||||
'entity_test_mulrev' => [
|
||||
'type' => 'entity:entity_test_mulrev',
|
||||
],
|
||||
],
|
||||
],
|
||||
'Multiple entity parameters on an entity form' => [
|
||||
[
|
||||
'_entity_form' => 'entity_test_rev.edit'
|
||||
],
|
||||
[
|
||||
'entity_test_rev' => [
|
||||
'type' => 'entity:entity_test_rev',
|
||||
],
|
||||
'node' => [
|
||||
'type' => 'entity:node',
|
||||
],
|
||||
],
|
||||
[
|
||||
'entity_test_rev' => [
|
||||
'type' => 'entity:entity_test_rev',
|
||||
'load_latest_revision' => TRUE,
|
||||
],
|
||||
'node' => [
|
||||
'type' => 'entity:node',
|
||||
],
|
||||
],
|
||||
],
|
||||
'Overridden load_latest_revision flag does not change' => [
|
||||
[
|
||||
'_entity_form' => 'entity_test_rev.edit'
|
||||
],
|
||||
[
|
||||
'entity_test_rev' => [
|
||||
'type' => 'entity:entity_test_rev',
|
||||
'load_latest_revision' => FALSE,
|
||||
],
|
||||
],
|
||||
],
|
||||
'Non-revisionable entity type will not change' => [
|
||||
[
|
||||
'_entity_form' => 'entity_test.edit'
|
||||
],
|
||||
[
|
||||
'entity_test' => [
|
||||
'type' => 'entity:entity_test',
|
||||
],
|
||||
],
|
||||
FALSE,
|
||||
FALSE,
|
||||
],
|
||||
'Overridden load_latest_revision flag does not change with multiple parameters' => [
|
||||
[
|
||||
'_entity_form' => 'entity_test_rev.edit'
|
||||
],
|
||||
[
|
||||
'entity_test_rev' => [
|
||||
'type' => 'entity:entity_test_rev',
|
||||
],
|
||||
'node' => [
|
||||
'type' => 'entity:node',
|
||||
'load_latest_revision' => FALSE,
|
||||
],
|
||||
],
|
||||
[
|
||||
'entity_test_rev' => [
|
||||
'type' => 'entity:entity_test_rev',
|
||||
'load_latest_revision' => TRUE,
|
||||
],
|
||||
'node' => [
|
||||
'type' => 'entity:node',
|
||||
'load_latest_revision' => FALSE,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the "load_latest_revision" flag is handled correctly.
|
||||
*
|
||||
* @param array $defaults
|
||||
* The route defaults.
|
||||
* @param array $parameters
|
||||
* The route parameters.
|
||||
* @param array|bool $expected_parameters
|
||||
* (optional) The expected route parameters. Defaults to FALSE.
|
||||
*
|
||||
* @covers ::setLatestRevisionFlag
|
||||
*
|
||||
* @dataProvider setLatestRevisionFlagTestCases
|
||||
*/
|
||||
public function testSetLatestRevisionFlag($defaults, $parameters, $expected_parameters = FALSE) {
|
||||
$route = new Route('/foo/{entity_test}', $defaults, [], [
|
||||
'parameters' => $parameters,
|
||||
]);
|
||||
|
||||
$route_collection = new RouteCollection();
|
||||
$route_collection->add('test', $route);
|
||||
$event = new RouteBuildEvent($route_collection);
|
||||
$this->routeSubscriber->onAlterRoutes($event);
|
||||
|
||||
// If expected parameters have not been provided, assert they are unchanged.
|
||||
$this->assertEquals($expected_parameters ?: $parameters, $route->getOption('parameters'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A concrete entity.
|
||||
*/
|
||||
class SimpleTestEntity extends Entity {
|
||||
}
|
||||
Reference in New Issue
Block a user