Move into nested docroot

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

View file

@ -0,0 +1,409 @@
langcode: en
status: true
dependencies:
config:
- system.menu.main
module:
- content_moderation
- user
id: latest
label: Latest
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: full
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: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
quantity: 9
style:
type: table
row:
type: fields
fields:
nid:
id: nid
table: node_field_revision
field: nid
relationship: none
group_type: group
admin_label: ''
label: 'Node ID'
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: 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
vid:
id: vid
table: node_field_revision
field: vid
relationship: none
group_type: group
admin_label: ''
label: 'Revision ID'
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: 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: vid
plugin_id: field
title:
id: title
table: node_field_revision
field: title
entity_type: node
entity_field: title
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: false
plugin_id: field
relationship: none
group_type: group
admin_label: ''
label: Title
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: content_moderation_state_field_revision
field: moderation_state
relationship: moderation_state
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: target_id
type: entity_reference_label
settings:
link: true
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: content_moderation_state
entity_field: moderation_state
plugin_id: field
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
sorts: { }
title: Latest
header: { }
footer: { }
empty: { }
relationships:
moderation_state:
id: moderation_state
table: node_field_revision
field: moderation_state
relationship: none
group_type: group
admin_label: 'Content moderation state'
required: false
entity_type: node
plugin_id: standard
arguments: { }
display_extenders: { }
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- '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: latest
menu:
type: normal
title: Drafts
description: ''
expanded: false
parent: ''
weight: 0
context: '0'
menu_name: main
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }

View file

@ -0,0 +1,406 @@
langcode: en
status: true
dependencies:
module:
- content_moderation
- node
- user
id: test_content_moderation_base_table_test
label: test_content_moderation_base_table_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: 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_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: 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
moderation_state:
id: moderation_state
table: content_moderation_state_field_data
field: moderation_state
relationship: moderation_state
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: 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: content_moderation_state
entity_field: moderation_state
plugin_id: field
moderation_state_1:
id: moderation_state_1
table: content_moderation_state_field_revision
field: moderation_state
relationship: moderation_state
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: 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: content_moderation_state
entity_field: moderation_state
plugin_id: field
moderation_state_2:
id: moderation_state_2
table: content_moderation_state_field_revision
field: moderation_state
relationship: moderation_state_1
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: target_id
type: entity_reference_entity_id
settings: { }
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: content_moderation_state
entity_field: moderation_state
plugin_id: field
filters: { }
sorts:
created:
id: created
table: node_field_data
field: created
order: DESC
entity_type: node
entity_field: created
plugin_id: date
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
granularity: second
vid:
id: vid
table: node_field_data
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:
moderation_state:
id: moderation_state
table: node_field_data
field: moderation_state
relationship: none
group_type: group
admin_label: 'Content moderation state'
required: false
entity_type: node
plugin_id: standard
moderation_state_1:
id: moderation_state_1
table: node_field_revision
field: moderation_state
relationship: none
group_type: group
admin_label: 'Content moderation state (revision)'
required: false
entity_type: node
plugin_id: standard
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }

View file

@ -0,0 +1,447 @@
langcode: en
status: true
dependencies:
module:
- node
- user
id: test_content_moderation_latest_revision
label: test_content_moderation_latest_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: 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_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: 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
revision_id:
id: revision_id
table: content_revision_tracker
field: revision_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
title:
id: title
table: node_field_revision
field: title
relationship: latest_revision__node
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: string
settings:
link_to_entity: 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: title
plugin_id: field
moderation_state:
id: moderation_state
table: content_moderation_state_field_revision
field: moderation_state
relationship: moderation_state
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: target_id
type: entity_reference_entity_id
settings: { }
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: content_moderation_state
entity_field: moderation_state
plugin_id: field
moderation_state_1:
id: moderation_state_1
table: content_moderation_state_field_revision
field: moderation_state
relationship: moderation_state_1
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: target_id
type: entity_reference_entity_id
settings: { }
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: content_moderation_state
entity_field: moderation_state
plugin_id: field
filters: { }
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:
latest_revision__node:
id: latest_revision__node
table: content_revision_tracker
field: latest_revision__node
relationship: none
group_type: group
admin_label: 'Content latest revision'
required: false
plugin_id: standard
moderation_state_1:
id: moderation_state_1
table: node_field_revision
field: moderation_state
relationship: latest_revision__node
group_type: group
admin_label: 'Content moderation state (latest revision)'
required: false
entity_type: node
plugin_id: standard
moderation_state:
id: moderation_state
table: node_field_revision
field: moderation_state
relationship: none
group_type: group
admin_label: 'Content moderation state'
required: false
entity_type: node
plugin_id: standard
arguments: { }
display_extenders: { }
rendering_language: '***LANGUAGE_entity_default***'
cache_metadata:
max-age: -1
contexts:
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }

View file

@ -0,0 +1,315 @@
langcode: en
status: true
dependencies:
module:
- user
id: test_content_moderation_revision_test
label: test_content_moderation_revision_test
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:
vid:
id: vid
table: node_field_revision
field: vid
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: vid
plugin_id: field
moderation_state:
id: moderation_state
table: content_moderation_state_field_revision
field: moderation_state
relationship: moderation_state
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: target_id
type: entity_reference_entity_id
settings: { }
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: content_moderation_state
entity_field: moderation_state
plugin_id: field
revision_id:
id: revision_id
table: content_moderation_state_field_revision
field: revision_id
relationship: moderation_state
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: content_moderation_state
entity_field: revision_id
plugin_id: field
filters: { }
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:
moderation_state:
id: moderation_state
table: node_field_revision
field: moderation_state
relationship: none
group_type: group
admin_label: 'Content moderation state'
required: false
entity_type: node
plugin_id: standard
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }

View file

@ -0,0 +1,10 @@
name: 'Content moderation test views'
type: module
description: 'Provides default views for views Content moderation tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- content_moderation
- node
- views

View file

@ -0,0 +1,128 @@
<?php
namespace Drupal\Tests\content_moderation\Functional;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the "Latest Revision" views filter.
*
* @group content_moderation
*/
class LatestRevisionViewsFilterTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'content_moderation_test_views',
'content_moderation',
];
/**
* Tests view shows the correct node IDs.
*/
public function testViewShowsCorrectNids() {
$node_type = $this->createNodeType('Test', 'test');
$permissions = [
'access content',
'view all revisions',
];
$editor1 = $this->drupalCreateUser($permissions);
$this->drupalLogin($editor1);
// Make a pre-moderation node.
/** @var Node $node_0 */
$node_0 = Node::create([
'type' => 'test',
'title' => 'Node 0 - Rev 1',
'uid' => $editor1->id(),
]);
$node_0->save();
// Now enable moderation for subsequent nodes.
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->save();
// Make a node that is only ever in Draft.
/** @var Node $node_1 */
$node_1 = Node::create([
'type' => 'test',
'title' => 'Node 1 - Rev 1',
'uid' => $editor1->id(),
]);
$node_1->moderation_state->target_id = 'draft';
$node_1->save();
// Make a node that is in Draft, then Published.
/** @var Node $node_2 */
$node_2 = Node::create([
'type' => 'test',
'title' => 'Node 2 - Rev 1',
'uid' => $editor1->id(),
]);
$node_2->moderation_state->target_id = 'draft';
$node_2->save();
$node_2->setTitle('Node 2 - Rev 2');
$node_2->moderation_state->target_id = 'published';
$node_2->save();
// Make a node that is in Draft, then Published, then Draft.
/** @var Node $node_3 */
$node_3 = Node::create([
'type' => 'test',
'title' => 'Node 3 - Rev 1',
'uid' => $editor1->id(),
]);
$node_3->moderation_state->target_id = 'draft';
$node_3->save();
$node_3->setTitle('Node 3 - Rev 2');
$node_3->moderation_state->target_id = 'published';
$node_3->save();
$node_3->setTitle('Node 3 - Rev 3');
$node_3->moderation_state->target_id = 'draft';
$node_3->save();
// Now show the View, and confirm that only the correct titles are showing.
$this->drupalGet('/latest');
$page = $this->getSession()->getPage();
$this->assertEquals(200, $this->getSession()->getStatusCode());
$this->assertTrue($page->hasContent('Node 1 - Rev 1'));
$this->assertTrue($page->hasContent('Node 2 - Rev 2'));
$this->assertTrue($page->hasContent('Node 3 - Rev 3'));
$this->assertFalse($page->hasContent('Node 2 - Rev 1'));
$this->assertFalse($page->hasContent('Node 3 - Rev 1'));
$this->assertFalse($page->hasContent('Node 3 - Rev 2'));
$this->assertFalse($page->hasContent('Node 0 - Rev 1'));
}
/**
* Creates a new node type.
*
* @param string $label
* The human-readable label of the type to create.
* @param string $machine_name
* The machine name of the type to create.
*
* @return NodeType
* The node type just created.
*/
protected function createNodeType($label, $machine_name) {
/** @var NodeType $node_type */
$node_type = NodeType::create([
'type' => $machine_name,
'label' => $label,
]);
$node_type->save();
return $node_type;
}
}

View file

@ -0,0 +1,107 @@
<?php
namespace Drupal\Tests\content_moderation\Functional;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the view access control handler for moderation state entities.
*
* @group content_moderation
*/
class ModerationStateAccessTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'content_moderation_test_views',
'content_moderation',
];
/**
* Test the view operation access handler with the view permission.
*/
public function testViewShowsCorrectStates() {
$node_type_id = 'test';
$this->createNodeType('Test', $node_type_id);
$permissions = [
'access content',
'view all revisions',
'view moderation states',
];
$editor1 = $this->drupalCreateUser($permissions);
$this->drupalLogin($editor1);
$node_1 = Node::create([
'type' => $node_type_id,
'title' => 'Draft node',
'uid' => $editor1->id(),
]);
$node_1->moderation_state->target_id = 'draft';
$node_1->save();
$node_2 = Node::create([
'type' => $node_type_id,
'title' => 'Published node',
'uid' => $editor1->id(),
]);
$node_2->moderation_state->target_id = 'published';
$node_2->save();
// Resave the node with a new state.
$node_2->setTitle('Archived node');
$node_2->moderation_state->target_id = 'archived';
$node_2->save();
// Now show the View, and confirm that the state labels are showing.
$this->drupalGet('/latest');
$page = $this->getSession()->getPage();
$this->assertTrue($page->hasLink('Draft'));
$this->assertTrue($page->hasLink('Archived'));
$this->assertFalse($page->hasLink('Published'));
// Now log in as an admin and test the same thing.
$permissions = [
'access content',
'view all revisions',
'administer moderation states',
];
$admin1 = $this->drupalCreateUser($permissions);
$this->drupalLogin($admin1);
$this->drupalGet('/latest');
$page = $this->getSession()->getPage();
$this->assertEquals(200, $this->getSession()->getStatusCode());
$this->assertTrue($page->hasLink('Draft'));
$this->assertTrue($page->hasLink('Archived'));
$this->assertFalse($page->hasLink('Published'));
}
/**
* Creates a new node type.
*
* @param string $label
* The human-readable label of the type to create.
* @param string $machine_name
* The machine name of the type to create.
*
* @return NodeType
* The node type just created.
*/
protected function createNodeType($label, $machine_name) {
/** @var NodeType $node_type */
$node_type = NodeType::create([
'type' => $machine_name,
'label' => $label,
]);
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->save();
return $node_type;
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\config\Tests\SchemaCheckTestTrait;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\NodeType;
use Drupal\content_moderation\Entity\ModerationState;
use Drupal\content_moderation\Entity\ModerationStateTransition;
/**
* Ensures that content moderation schema is correct.
*
* @group content_moderation
*/
class ContentModerationSchemaTest extends KernelTestBase {
use SchemaCheckTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = [
'content_moderation',
'node',
'user',
'block_content',
'system',
];
/**
* Tests content moderation default schema.
*/
public function testContentModerationDefaultConfig() {
$this->installConfig(['content_moderation']);
$typed_config = \Drupal::service('config.typed');
$moderation_states = ModerationState::loadMultiple();
foreach ($moderation_states as $moderation_state) {
$this->assertConfigSchema($typed_config, $moderation_state->getEntityType()->getConfigPrefix() . '.' . $moderation_state->id(), $moderation_state->toArray());
}
$moderation_state_transitions = ModerationStateTransition::loadMultiple();
foreach ($moderation_state_transitions as $moderation_state_transition) {
$this->assertConfigSchema($typed_config, $moderation_state_transition->getEntityType()->getConfigPrefix() . '.' . $moderation_state_transition->id(), $moderation_state_transition->toArray());
}
}
/**
* Tests content moderation third party schema for node types.
*/
public function testContentModerationNodeTypeConfig() {
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installConfig(['content_moderation']);
$typed_config = \Drupal::service('config.typed');
$moderation_states = ModerationState::loadMultiple();
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($moderation_states));
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', '');
$node_type->save();
$this->assertConfigSchema($typed_config, $node_type->getEntityType()->getConfigPrefix() . '.' . $node_type->id(), $node_type->toArray());
}
/**
* Tests content moderation third party schema for block content types.
*/
public function testContentModerationBlockContentTypeConfig() {
$this->installEntitySchema('block_content');
$this->installEntitySchema('user');
$this->installConfig(['content_moderation']);
$typed_config = \Drupal::service('config.typed');
$moderation_states = ModerationState::loadMultiple();
$block_content_type = BlockContentType::create([
'id' => 'basic',
'label' => 'basic',
'revision' => TRUE,
]);
$block_content_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$block_content_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($moderation_states));
$block_content_type->setThirdPartySetting('content_moderation', 'default_moderation_state', '');
$block_content_type->save();
$this->assertConfigSchema($typed_config, $block_content_type->getEntityType()->getConfigPrefix() . '.' . $block_content_type->id(), $block_content_type->toArray());
}
}

View file

@ -0,0 +1,324 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\content_moderation\Entity\ContentModerationState;
use Drupal\content_moderation\Entity\ModerationState;
use Drupal\entity_test\Entity\EntityTestBundle;
use Drupal\entity_test\Entity\EntityTestWithBundle;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\node\NodeInterface;
/**
* Tests links between a content entity and a content_moderation_state entity.
*
* @group content_moderation
*/
class ContentModerationStateTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'entity_test',
'node',
'content_moderation',
'user',
'system',
'language',
'content_translation',
'text',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('node', 'node_access');
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test_with_bundle');
$this->installEntitySchema('content_moderation_state');
$this->installConfig('content_moderation');
}
/**
* Tests basic monolingual content moderation through the API.
*/
public function testBasicModeration() {
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
$node_type->save();
$node = Node::create([
'type' => 'example',
'title' => 'Test title',
]);
$node->save();
$node = $this->reloadNode($node);
$this->assertEquals('draft', $node->moderation_state->entity->id());
$published = ModerationState::load('published');
$node->moderation_state->entity = $published;
$node->save();
$node = $this->reloadNode($node);
$this->assertEquals('published', $node->moderation_state->entity->id());
// Change the state without saving the node.
$content_moderation_state = ContentModerationState::load(1);
$content_moderation_state->set('moderation_state', 'draft');
$content_moderation_state->setNewRevision(TRUE);
$content_moderation_state->save();
$node = $this->reloadNode($node, 3);
$this->assertEquals('draft', $node->moderation_state->entity->id());
$this->assertFalse($node->isPublished());
// Get the default revision.
$node = $this->reloadNode($node);
$this->assertTrue($node->isPublished());
$this->assertEquals(2, $node->getRevisionId());
$node->moderation_state->target_id = 'published';
$node->save();
$node = $this->reloadNode($node, 4);
$this->assertEquals('published', $node->moderation_state->entity->id());
// Get the default revision.
$node = $this->reloadNode($node);
$this->assertTrue($node->isPublished());
$this->assertEquals(4, $node->getRevisionId());
}
/**
* Tests basic multilingual content moderation through the API.
*/
public function testMultilingualModeration() {
// Enable French.
ConfigurableLanguage::createFromLangcode('fr')->save();
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
$node_type->save();
$english_node = Node::create([
'type' => 'example',
'title' => 'Test title',
]);
// Revision 1 (en).
$english_node
->setPublished(FALSE)
->save();
$this->assertEquals('draft', $english_node->moderation_state->entity->id());
$this->assertFalse($english_node->isPublished());
// Create a French translation.
$french_node = $english_node->addTranslation('fr', ['title' => 'French title']);
$french_node->setPublished(FALSE);
// Revision 1 (fr).
$french_node->save();
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
$this->assertEquals('draft', $french_node->moderation_state->entity->id());
$this->assertFalse($french_node->isPublished());
// Move English node to create another draft.
$english_node = $this->reloadNode($english_node);
$english_node->moderation_state->target_id = 'draft';
// Revision 2 (en, fr).
$english_node->save();
$english_node = $this->reloadNode($english_node);
$this->assertEquals('draft', $english_node->moderation_state->entity->id());
// French node should still be in draft.
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
$this->assertEquals('draft', $french_node->moderation_state->entity->id());
// Publish the French node.
$french_node->moderation_state->target_id = 'published';
// Revision 3 (en, fr).
$french_node->save();
$french_node = $this->reloadNode($french_node)->getTranslation('fr');
$this->assertTrue($french_node->isPublished());
$this->assertEquals('published', $french_node->moderation_state->entity->id());
$this->assertTrue($french_node->isPublished());
$english_node = $french_node->getTranslation('en');
$this->assertEquals('draft', $english_node->moderation_state->entity->id());
// Publish the English node.
$english_node->moderation_state->target_id = 'published';
// Revision 4 (en, fr).
$english_node->save();
$english_node = $this->reloadNode($english_node);
$this->assertTrue($english_node->isPublished());
// Move the French node back to draft.
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
$this->assertTrue($french_node->isPublished());
$french_node->moderation_state->target_id = 'draft';
// Revision 5 (en, fr).
$french_node->save();
$french_node = $this->reloadNode($english_node, 5)->getTranslation('fr');
$this->assertFalse($french_node->isPublished());
$this->assertTrue($french_node->getTranslation('en')->isPublished());
// Republish the French node.
$french_node->moderation_state->target_id = 'published';
// Revision 6 (en, fr).
$french_node->save();
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
$this->assertTrue($french_node->isPublished());
// Change the EN state without saving the node.
$content_moderation_state = ContentModerationState::load(1);
$content_moderation_state->set('moderation_state', 'draft');
$content_moderation_state->setNewRevision(TRUE);
// Revision 7 (en, fr).
$content_moderation_state->save();
$english_node = $this->reloadNode($french_node, $french_node->getRevisionId() + 1);
$this->assertEquals('draft', $english_node->moderation_state->entity->id());
$french_node = $this->reloadNode($english_node)->getTranslation('fr');
$this->assertEquals('published', $french_node->moderation_state->entity->id());
// This should unpublish the French node.
$content_moderation_state = ContentModerationState::load(1);
$content_moderation_state = $content_moderation_state->getTranslation('fr');
$content_moderation_state->set('moderation_state', 'draft');
$content_moderation_state->setNewRevision(TRUE);
// Revision 8 (en, fr).
$content_moderation_state->save();
$english_node = $this->reloadNode($english_node, $english_node->getRevisionId());
$this->assertEquals('draft', $english_node->moderation_state->entity->id());
$french_node = $this->reloadNode($english_node, '8')->getTranslation('fr');
$this->assertEquals('draft', $french_node->moderation_state->entity->id());
// Switching the moderation state to an unpublished state should update the
// entity.
$this->assertFalse($french_node->isPublished());
// Get the default english node.
$english_node = $this->reloadNode($english_node);
$this->assertTrue($english_node->isPublished());
$this->assertEquals(6, $english_node->getRevisionId());
}
/**
* Tests that a non-translatable entity type with a langcode can be moderated.
*/
public function testNonTranslatableEntityTypeModeration() {
// Make the 'entity_test_with_bundle' entity type revisionable.
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
$keys = $entity_type->getKeys();
$keys['revision'] = 'revision_id';
$entity_type->set('entity_keys', $keys);
\Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type);
\Drupal::entityDefinitionUpdateManager()->applyUpdates();
// Create a test bundle.
$entity_test_bundle = EntityTestBundle::create([
'id' => 'example',
]);
$entity_test_bundle->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$entity_test_bundle->setThirdPartySetting('content_moderation', 'allowed_moderation_states', [
'draft',
'published'
]);
$entity_test_bundle->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
$entity_test_bundle->save();
// Check that the tested entity type is not translatable.
$entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
$this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
// Create a test entity.
$entity_test_with_bundle = EntityTestWithBundle::create([
'type' => 'example'
]);
$entity_test_with_bundle->save();
$this->assertEquals('draft', $entity_test_with_bundle->moderation_state->entity->id());
$entity_test_with_bundle->moderation_state->target_id = 'published';
$entity_test_with_bundle->save();
$this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->entity->id());
}
/**
* Tests that a non-translatable entity type without a langcode can be
* moderated.
*/
public function testNonLangcodeEntityTypeModeration() {
// Make the 'entity_test_with_bundle' entity type revisionable and unset
// the langcode entity key.
$entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
$keys = $entity_type->getKeys();
$keys['revision'] = 'revision_id';
unset($keys['langcode']);
$entity_type->set('entity_keys', $keys);
\Drupal::state()->set('entity_test_with_bundle.entity_type', $entity_type);
\Drupal::entityDefinitionUpdateManager()->applyUpdates();
// Create a test bundle.
$entity_test_bundle = EntityTestBundle::create([
'id' => 'example',
]);
$entity_test_bundle->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$entity_test_bundle->setThirdPartySetting('content_moderation', 'allowed_moderation_states', [
'draft',
'published'
]);
$entity_test_bundle->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
$entity_test_bundle->save();
// Check that the tested entity type is not translatable.
$entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
$this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
// Create a test entity.
$entity_test_with_bundle = EntityTestWithBundle::create([
'type' => 'example'
]);
$entity_test_with_bundle->save();
$this->assertEquals('draft', $entity_test_with_bundle->moderation_state->entity->id());
$entity_test_with_bundle->moderation_state->target_id = 'published';
$entity_test_with_bundle->save();
$this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->entity->id());
}
/**
* Reloads the node after clearing the static cache.
*
* @param \Drupal\node\NodeInterface $node
* The node to reload.
* @param int|false $revision_id
* The specific revision ID to load. Defaults FALSE and just loads the
* default revision.
*
* @return \Drupal\node\NodeInterface
* The reloaded node.
*/
protected function reloadNode(NodeInterface $node, $revision_id = FALSE) {
$storage = \Drupal::entityTypeManager()->getStorage('node');
$storage->resetCache([$node->id()]);
if ($revision_id) {
return $storage->loadRevision($revision_id);
}
return $storage->load($node->id());
}
}

View file

@ -0,0 +1,197 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\content_moderation\Entity\ModerationState;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* @coversDefaultClass \Drupal\content_moderation\EntityOperations
*
* @group content_moderation
*/
class EntityOperationsTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'content_moderation',
'node',
'user',
'system',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('node');
$this->installSchema('node', 'node_access');
$this->installEntitySchema('user');
$this->installEntitySchema('content_moderation_state');
$this->installConfig('content_moderation');
$this->createNodeType();
}
/**
* Creates a page node type to test with, ensuring that it's moderated.
*/
protected function createNodeType() {
$node_type = NodeType::create([
'type' => 'page',
'label' => 'Page',
]);
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->save();
}
/**
* Verifies that the process of saving forward-revisions works as expected.
*/
public function testForwardRevisions() {
// Create a new node in draft.
$page = Node::create([
'type' => 'page',
'title' => 'A',
]);
$page->moderation_state->target_id = 'draft';
$page->save();
$id = $page->id();
// Verify the entity saved correctly, and that the presence of forward
// revisions doesn't affect the default node load.
/** @var Node $page */
$page = Node::load($id);
$this->assertEquals('A', $page->getTitle());
$this->assertTrue($page->isDefaultRevision());
$this->assertFalse($page->isPublished());
// Moderate the entity to published.
$page->setTitle('B');
$page->moderation_state->target_id = 'published';
$page->save();
// Verify the entity is now published and public.
$page = Node::load($id);
$this->assertEquals('B', $page->getTitle());
$this->assertTrue($page->isDefaultRevision());
$this->assertTrue($page->isPublished());
// Make a new forward-revision in Draft.
$page->setTitle('C');
$page->moderation_state->target_id = 'draft';
$page->save();
// Verify normal loads return the still-default previous version.
$page = Node::load($id);
$this->assertEquals('B', $page->getTitle());
// Verify we can load the forward revision, even if the mechanism is kind
// of gross. Note: revisionIds() is only available on NodeStorageInterface,
// so this won't work for non-nodes. We'd need to use entity queries. This
// is a core bug that should get fixed.
$storage = \Drupal::entityTypeManager()->getStorage('node');
$revision_ids = $storage->revisionIds($page);
sort($revision_ids);
$latest = end($revision_ids);
$page = $storage->loadRevision($latest);
$this->assertEquals('C', $page->getTitle());
$page->setTitle('D');
$page->moderation_state->target_id = 'published';
$page->save();
// Verify normal loads return the still-default previous version.
$page = Node::load($id);
$this->assertEquals('D', $page->getTitle());
$this->assertTrue($page->isDefaultRevision());
$this->assertTrue($page->isPublished());
// Now check that we can immediately add a new published revision over it.
$page->setTitle('E');
$page->moderation_state->target_id = 'published';
$page->save();
$page = Node::load($id);
$this->assertEquals('E', $page->getTitle());
$this->assertTrue($page->isDefaultRevision());
$this->assertTrue($page->isPublished());
}
/**
* Verifies that a newly-created node can go straight to published.
*/
public function testPublishedCreation() {
// Create a new node in draft.
$page = Node::create([
'type' => 'page',
'title' => 'A',
]);
$page->moderation_state->target_id = 'published';
$page->save();
$id = $page->id();
// Verify the entity saved correctly.
/** @var Node $page */
$page = Node::load($id);
$this->assertEquals('A', $page->getTitle());
$this->assertTrue($page->isDefaultRevision());
$this->assertTrue($page->isPublished());
}
/**
* Verifies that an unpublished state may be made the default revision.
*/
public function testArchive() {
$published_id = $this->randomMachineName();
$published_state = ModerationState::create([
'id' => $published_id,
'label' => $this->randomString(),
'published' => TRUE,
'default_revision' => TRUE,
]);
$published_state->save();
$archived_id = $this->randomMachineName();
$archived_state = ModerationState::create([
'id' => $archived_id,
'label' => $this->randomString(),
'published' => FALSE,
'default_revision' => TRUE,
]);
$archived_state->save();
$page = Node::create([
'type' => 'page',
'title' => $this->randomString(),
]);
$page->moderation_state->target_id = $published_id;
$page->save();
$id = $page->id();
// The newly-created page should already be published.
$page = Node::load($id);
$this->assertTrue($page->isPublished());
// When the page is moderated to the archived state, then the latest
// revision should be the default revision, and it should be unpublished.
$page->moderation_state->target_id = $archived_id;
$page->save();
$new_revision_id = $page->getRevisionId();
$storage = \Drupal::entityTypeManager()->getStorage('node');
$new_revision = $storage->loadRevision($new_revision_id);
$this->assertFalse($new_revision->isPublished());
$this->assertTrue($new_revision->isDefaultRevision());
}
}

View file

@ -0,0 +1,95 @@
<?php
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;
/**
* @coversDefaultClass \Drupal\content_moderation\ParamConverter\EntityRevisionConverter
* @group content_moderation
*/
class EntityRevisionConverterTest extends KernelTestBase {
public static $modules = [
'user',
'entity_test',
'system',
'content_moderation',
'node',
];
/**
* {@inheritdoc}
*/
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
*/
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() {
$node_type = NodeType::create([
'type' => 'article',
]);
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->save();
$revision_ids = [];
$node = Node::create([
'title' => 'test',
'type' => 'article',
]);
$node->save();
$revision_ids[] = $node->getRevisionId();
$node->setNewRevision(TRUE);
$node->save();
$revision_ids[] = $node->getRevisionId();
$node->setNewRevision(TRUE);
$node->isDefaultRevision(FALSE);
$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());
}
}

View file

@ -0,0 +1,174 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* @coversDefaultClass \Drupal\content_moderation\Plugin\Validation\Constraint\ModerationStateConstraintValidator
* @group content_moderation
*/
class EntityStateChangeValidationTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'node',
'content_moderation',
'user',
'system',
'language',
'content_translation',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('node', 'node_access');
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installEntitySchema('content_moderation_state');
$this->installConfig('content_moderation');
}
/**
* Test valid transitions.
*
* @covers ::validate
*/
public function testValidTransition() {
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->save();
$node = Node::create([
'type' => 'example',
'title' => 'Test title',
]);
$node->moderation_state->target_id = 'draft';
$node->save();
$node->moderation_state->target_id = 'published';
$this->assertCount(0, $node->validate());
$node->save();
$this->assertEquals('published', $node->moderation_state->entity->id());
}
/**
* Test invalid transitions.
*
* @covers ::validate
*/
public function testInvalidTransition() {
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->save();
$node = Node::create([
'type' => 'example',
'title' => 'Test title',
]);
$node->moderation_state->target_id = 'draft';
$node->save();
$node->moderation_state->target_id = 'archived';
$violations = $node->validate();
$this->assertCount(1, $violations);
$this->assertEquals('Invalid state transition from <em class="placeholder">Draft</em> to <em class="placeholder">Archived</em>', $violations->get(0)->getMessage());
}
/**
* Tests that content without prior moderation information can be moderated.
*/
public function testLegacyContent() {
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->save();
/** @var \Drupal\node\NodeInterface $node */
$node = Node::create([
'type' => 'example',
'title' => 'Test title',
]);
$node->save();
$nid = $node->id();
// Enable moderation for our node type.
/** @var NodeType $node_type */
$node_type = NodeType::load('example');
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
$node_type->save();
$node = Node::load($nid);
// Having no previous state should not break validation.
$violations = $node->validate();
$this->assertCount(0, $violations);
// Having no previous state should not break saving the node.
$node->setTitle('New');
$node->save();
}
/**
* Tests that content without prior moderation information can be translated.
*/
public function testLegacyMultilingualContent() {
// Enable French.
ConfigurableLanguage::createFromLangcode('fr')->save();
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->save();
/** @var \Drupal\node\NodeInterface $node */
$node = Node::create([
'type' => 'example',
'title' => 'Test title',
'langcode' => 'en',
]);
$node->save();
$nid = $node->id();
$node = Node::load($nid);
// Creating a translation shouldn't break, even though there's no previous
// moderated revision for the new language.
$node_fr = $node->addTranslation('fr');
$node_fr->setTitle('Francais');
$node_fr->save();
// Enable moderation for our node type.
/** @var NodeType $node_type */
$node_type = NodeType::load('example');
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
$node_type->save();
// Reload the French version of the node.
$node = Node::load($nid);
$node_fr = $node->getTranslation('fr');
/** @var \Drupal\node\NodeInterface $node_fr */
$node_fr->setTitle('Nouveau');
$node_fr->save();
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\content_moderation\Entity\Handler\ModerationHandler;
use Drupal\content_moderation\EntityTypeInfo;
use Drupal\KernelTests\KernelTestBase;
/**
* @coversDefaultClass \Drupal\content_moderation\EntityTypeInfo
*
* @group content_moderation
*/
class EntityTypeInfoTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'content_moderation',
'entity_test',
];
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity type info class.
*
* @var \Drupal\content_moderation\EntityTypeInfo
*/
protected $entityTypeInfo;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->entityTypeInfo = $this->container->get('class_resolver')->getInstanceFromDefinition(EntityTypeInfo::class);
$this->entityTypeManager = $this->container->get('entity_type.manager');
}
/**
* @covers ::entityBaseFieldInfo
*/
public function testEntityBaseFieldInfo() {
$definition = $this->entityTypeManager->getDefinition('entity_test');
$definition->setHandlerClass('moderation', ModerationHandler::class);
$base_fields = $this->entityTypeInfo->entityBaseFieldInfo($definition);
$this->assertFalse($base_fields['moderation_state']->isReadOnly());
$this->assertTrue($base_fields['moderation_state']->isComputed());
$this->assertTrue($base_fields['moderation_state']->isTranslatable());
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\content_moderation\Entity\ModerationState;
/**
* @coversDefaultClass \Drupal\content_moderation\Entity\ModerationState
*
* @group content_moderation
*/
class ModerationStateEntityTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['content_moderation'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('moderation_state');
}
/**
* Verify moderation state methods based on entity properties.
*
* @covers ::isPublishedState
* @covers ::isDefaultRevisionState
*
* @dataProvider moderationStateProvider
*/
public function testModerationStateProperties($published, $default_revision, $is_published, $is_default) {
$moderation_state_id = $this->randomMachineName();
$moderation_state = ModerationState::create([
'id' => $moderation_state_id,
'label' => $this->randomString(),
'published' => $published,
'default_revision' => $default_revision,
]);
$moderation_state->save();
$moderation_state = ModerationState::load($moderation_state_id);
$this->assertEquals($is_published, $moderation_state->isPublishedState());
$this->assertEquals($is_default, $moderation_state->isDefaultRevisionState());
}
/**
* Data provider for ::testModerationStateProperties.
*/
public function moderationStateProvider() {
return [
// Draft, Needs review; should not touch the default revision.
[FALSE, FALSE, FALSE, FALSE],
// Published; this state should update and publish the default revision.
[TRUE, TRUE, TRUE, TRUE],
// Archive; this state should update but not publish the default revision.
[FALSE, TRUE, FALSE, TRUE],
// We try to prevent creating this state via the UI, but when a moderation
// state is a published state, it should also become the default revision.
[TRUE, FALSE, TRUE, TRUE],
];
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* @coversDefaultClass \Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList
*
* @group content_moderation
*/
class ModerationStateFieldItemListTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'node',
'content_moderation',
'user',
'system',
'language',
];
/**
* @var \Drupal\node\NodeInterface
*/
protected $testNode;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('node', 'node_access');
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installEntitySchema('content_moderation_state');
$this->installConfig('content_moderation');
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft']);
$node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
$node_type->save();
$this->testNode = Node::create([
'type' => 'example',
'title' => 'Test title',
]);
$this->testNode->save();
\Drupal::entityTypeManager()->getStorage('node')->resetCache();
$this->testNode = Node::load($this->testNode->id());
}
/**
* Test the field item list when accessing an index.
*/
public function testArrayIndex() {
$this->assertEquals('draft', $this->testNode->moderation_state[0]->entity->id());
}
/**
* Test the field item list when iterating.
*/
public function testArrayIteration() {
$states = [];
foreach ($this->testNode->moderation_state as $item) {
$states[] = $item->entity->id();
}
$this->assertEquals(['draft'], $states);
}
}

View file

@ -0,0 +1,159 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Views;
/**
* Tests the views integration of content_moderation.
*
* @group content_moderation
*/
class ViewsDataIntegrationTest extends ViewsKernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'content_moderation_test_views',
'node',
'content_moderation',
];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installEntitySchema('content_moderation_state');
$this->installSchema('node', 'node_access');
$this->installConfig('content_moderation_test_views');
$this->installConfig('content_moderation');
$node_type = NodeType::create([
'type' => 'page',
]);
$node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
$node_type->save();
}
/**
* Tests content_moderation_views_data().
*
* @see content_moderation_views_data()
*/
public function testViewsData() {
$node = Node::create([
'type' => 'page',
'title' => 'Test title first revision',
]);
$node->moderation_state->target_id = 'published';
$node->save();
$revision = clone $node;
$revision->setNewRevision(TRUE);
$revision->isDefaultRevision(FALSE);
$revision->title->value = 'Test title second revision';
$revision->moderation_state->target_id = 'draft';
$revision->save();
$view = Views::getView('test_content_moderation_latest_revision');
$view->execute();
// Ensure that the content_revision_tracker contains the right latest
// revision ID.
// Also ensure that the relationship back to the revision table contains the
// right latest revision.
$expected_result = [
[
'nid' => $node->id(),
'revision_id' => $revision->getRevisionId(),
'title' => $revision->label(),
'moderation_state_1' => 'draft',
'moderation_state' => 'published',
],
];
$this->assertIdenticalResultset($view, $expected_result, ['nid' => 'nid', 'content_revision_tracker_revision_id' => 'revision_id', 'moderation_state' => 'moderation_state', 'moderation_state_1' => 'moderation_state_1']);
}
/**
* Tests the join from the revision data table to the moderation state table.
*/
public function testContentModerationStateRevisionJoin() {
$node = Node::create([
'type' => 'page',
'title' => 'Test title first revision',
]);
$node->moderation_state->target_id = 'published';
$node->save();
$revision = clone $node;
$revision->setNewRevision(TRUE);
$revision->isDefaultRevision(FALSE);
$revision->title->value = 'Test title second revision';
$revision->moderation_state->target_id = 'draft';
$revision->save();
$view = Views::getView('test_content_moderation_revision_test');
$view->execute();
$expected_result = [
[
'revision_id' => $node->getRevisionId(),
'moderation_state' => 'published',
],
[
'revision_id' => $revision->getRevisionId(),
'moderation_state' => 'draft',
],
];
$this->assertIdenticalResultset($view, $expected_result, ['revision_id' => 'revision_id', 'moderation_state' => 'moderation_state']);
}
/**
* Tests the join from the data table to the moderation state table.
*/
public function testContentModerationStateBaseJoin() {
$node = Node::create([
'type' => 'page',
'title' => 'Test title first revision',
]);
$node->moderation_state->target_id = 'published';
$node->save();
$revision = clone $node;
$revision->setNewRevision(TRUE);
$revision->isDefaultRevision(FALSE);
$revision->title->value = 'Test title second revision';
$revision->moderation_state->target_id = 'draft';
$revision->save();
$view = Views::getView('test_content_moderation_base_table_test');
$view->execute();
$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',
// Joins from the revision table to the default revision of the
// content_moderation.
'moderation_state_1' => 'draft',
// Joins from the revision table to the revision of the
// content_moderation.
'moderation_state_2' => 'published',
],
];
$this->assertIdenticalResultset($view, $expected_result, ['nid' => 'nid', 'moderation_state' => 'moderation_state', 'moderation_state_1' => 'moderation_state_1', 'moderation_state_2' => 'moderation_state_2']);
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace Drupal\Tests\content_moderation\Unit;
use Drupal\content_moderation\ContentPreprocess;
use Drupal\Core\Routing\CurrentRouteMatch;
use Drupal\node\Entity\Node;
/**
* @coversDefaultClass \Drupal\content_moderation\ContentPreprocess
*
* @group content_moderation
*/
class ContentPreprocessTest extends \PHPUnit_Framework_TestCase {
/**
* @covers ::isLatestVersionPage
* @dataProvider routeNodeProvider
*/
public function testIsLatestVersionPage($route_name, $route_nid, $check_nid, $result, $message) {
$content_preprocess = new ContentPreprocess($this->setupCurrentRouteMatch($route_name, $route_nid));
$node = $this->setupNode($check_nid);
$this->assertEquals($result, $content_preprocess->isLatestVersionPage($node), $message);
}
/**
* Data provider for self::testIsLatestVersionPage().
*/
public function routeNodeProvider() {
return [
['entity.node.canonical', 1, 1, FALSE, 'Not on the latest version tab route.'],
['entity.node.latest_version', 1, 1, TRUE, 'On the latest version tab route, with the route node.'],
['entity.node.latest_version', 1, 2, FALSE, 'On the latest version tab route, with a different node.'],
];
}
/**
* Mock the current route matching object.
*
* @param string $route_name
* The route to mock.
* @param int $nid
* The node ID for mocking.
*
* @return \Drupal\Core\Routing\CurrentRouteMatch
* The mocked current route match object.
*/
protected function setupCurrentRouteMatch($route_name, $nid) {
$route_match = $this->prophesize(CurrentRouteMatch::class);
$route_match->getRouteName()->willReturn($route_name);
$route_match->getParameter('node')->willReturn($this->setupNode($nid));
return $route_match->reveal();
}
/**
* Mock a node object.
*
* @param int $nid
* The node ID to mock.
*
* @return \Drupal\node\Entity\Node
* The mocked node.
*/
protected function setupNode($nid) {
$node = $this->prophesize(Node::class);
$node->id()->willReturn($nid);
return $node->reveal();
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace Drupal\Tests\content_moderation\Unit;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Core\Access\AccessResultAllowed;
use Drupal\Core\Access\AccessResultForbidden;
use Drupal\Core\Routing\RouteMatch;
use Drupal\node\Entity\Node;
use Drupal\content_moderation\Access\LatestRevisionCheck;
use Drupal\content_moderation\ModerationInformation;
use Symfony\Component\Routing\Route;
/**
* @coversDefaultClass \Drupal\content_moderation\Access\LatestRevisionCheck
* @group content_moderation
*/
class LatestRevisionCheckTest extends \PHPUnit_Framework_TestCase {
/**
* Test the access check of the LatestRevisionCheck service.
*
* @param string $entity_class
* The class of the entity to mock.
* @param string $entity_type
* The machine name of the entity to mock.
* @param bool $has_forward
* Whether this entity should have a forward revision in the system.
* @param string $result_class
* The AccessResult class that should result. One of AccessResultAllowed,
* AccessResultForbidden, AccessResultNeutral.
*
* @dataProvider accessSituationProvider
*/
public function testLatestAccessPermissions($entity_class, $entity_type, $has_forward, $result_class) {
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $this->prophesize($entity_class);
$entity->getCacheContexts()->willReturn([]);
$entity->getCacheTags()->willReturn([]);
$entity->getCacheMaxAge()->willReturn(0);
/** @var \Drupal\content_moderation\ModerationInformation $mod_info */
$mod_info = $this->prophesize(ModerationInformation::class);
$mod_info->hasForwardRevision($entity->reveal())->willReturn($has_forward);
$route = $this->prophesize(Route::class);
$route->getOption('_content_moderation_entity_type')->willReturn($entity_type);
$route_match = $this->prophesize(RouteMatch::class);
$route_match->getParameter($entity_type)->willReturn($entity->reveal());
$lrc = new LatestRevisionCheck($mod_info->reveal());
/** @var \Drupal\Core\Access\AccessResult $result */
$result = $lrc->access($route->reveal(), $route_match->reveal());
$this->assertInstanceOf($result_class, $result);
}
/**
* Data provider for testLastAccessPermissions().
*/
public function accessSituationProvider() {
return [
[Node::class, 'node', TRUE, AccessResultAllowed::class],
[Node::class, 'node', FALSE, AccessResultForbidden::class],
[BlockContent::class, 'block_content', TRUE, AccessResultAllowed::class],
[BlockContent::class, 'block_content', FALSE, AccessResultForbidden::class],
];
}
}

View file

@ -0,0 +1,127 @@
<?php
namespace Drupal\Tests\content_moderation\Unit;
use Drupal\content_moderation\Entity\Handler\ModerationHandler;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\content_moderation\ModerationInformation;
/**
* @coversDefaultClass \Drupal\content_moderation\ModerationInformation
* @group content_moderation
*/
class ModerationInformationTest extends \PHPUnit_Framework_TestCase {
/**
* Builds a mock user.
*
* @return AccountInterface
* The mocked user.
*/
protected function getUser() {
return $this->prophesize(AccountInterface::class)->reveal();
}
/**
* Returns a mock Entity Type Manager.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $entity_bundle_storage
* Entity bundle storage.
*
* @return EntityTypeManagerInterface
* The mocked entity type manager.
*/
protected function getEntityTypeManager(EntityStorageInterface $entity_bundle_storage) {
$entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
$entity_type_manager->getStorage('entity_test_bundle')->willReturn($entity_bundle_storage);
return $entity_type_manager->reveal();
}
/**
* Sets up content moderation and entity manager mocking.
*
* @param bool $status
* TRUE if content_moderation should be enabled, FALSE if not.
*
* @return \Drupal\Core\Entity\EntityTypeManagerInterface
* The mocked entity type manager.
*/
public function setupModerationEntityManager($status) {
$bundle = $this->prophesize(ConfigEntityInterface::class);
$bundle->getThirdPartySetting('content_moderation', 'enabled', FALSE)->willReturn($status);
$entity_storage = $this->prophesize(EntityStorageInterface::class);
$entity_storage->load('test_bundle')->willReturn($bundle->reveal());
return $this->getEntityTypeManager($entity_storage->reveal());
}
/**
* @dataProvider providerBoolean
* @covers ::isModeratedEntity
*/
public function testIsModeratedEntity($status) {
$moderation_information = new ModerationInformation($this->setupModerationEntityManager($status), $this->getUser());
$entity_type = new ContentEntityType([
'id' => 'test_entity_type',
'bundle_entity_type' => 'entity_test_bundle',
'handlers' => ['moderation' => ModerationHandler::class],
]);
$entity = $this->prophesize(ContentEntityInterface::class);
$entity->getEntityType()->willReturn($entity_type);
$entity->bundle()->willReturn('test_bundle');
$this->assertEquals($status, $moderation_information->isModeratedEntity($entity->reveal()));
}
/**
* @covers ::isModeratedEntity
*/
public function testIsModeratedEntityForNonBundleEntityType() {
$entity_type = new ContentEntityType([
'id' => 'test_entity_type',
]);
$entity = $this->prophesize(ContentEntityInterface::class);
$entity->getEntityType()->willReturn($entity_type);
$entity->bundle()->willReturn('test_entity_type');
$entity_storage = $this->prophesize(EntityStorageInterface::class);
$entity_type_manager = $this->getEntityTypeManager($entity_storage->reveal());
$moderation_information = new ModerationInformation($entity_type_manager, $this->getUser());
$this->assertEquals(FALSE, $moderation_information->isModeratedEntity($entity->reveal()));
}
/**
* @dataProvider providerBoolean
* @covers ::shouldModerateEntitiesOfBundle
*/
public function testShouldModerateEntities($status) {
$entity_type = new ContentEntityType([
'id' => 'test_entity_type',
'bundle_entity_type' => 'entity_test_bundle',
'handlers' => ['moderation' => ModerationHandler::class],
]);
$moderation_information = new ModerationInformation($this->setupModerationEntityManager($status), $this->getUser());
$this->assertEquals($status, $moderation_information->shouldModerateEntitiesOfBundle($entity_type, 'test_bundle'));
}
/**
* Data provider for several tests.
*/
public function providerBoolean() {
return [
[FALSE],
[TRUE],
];
}
}

View file

@ -0,0 +1,297 @@
<?php
namespace Drupal\Tests\content_moderation\Unit;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Session\AccountInterface;
use Drupal\content_moderation\ModerationStateInterface;
use Drupal\content_moderation\ModerationStateTransitionInterface;
use Drupal\content_moderation\StateTransitionValidation;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\content_moderation\StateTransitionValidation
* @group content_moderation
*/
class StateTransitionValidationTest extends \PHPUnit_Framework_TestCase {
/**
* Builds a mock storage object for Transitions.
*
* @return EntityStorageInterface
* The mocked storage object for Transitions.
*/
protected function setupTransitionStorage() {
$entity_storage = $this->prophesize(EntityStorageInterface::class);
$list = $this->setupTransitionEntityList();
$entity_storage->loadMultiple()->willReturn($list);
$entity_storage->loadMultiple(Argument::type('array'))->will(function ($args) use ($list) {
$keys = $args[0];
if (empty($keys)) {
return $list;
}
$return = array_map(function($key) use ($list) {
return $list[$key];
}, $keys);
return $return;
});
return $entity_storage->reveal();
}
/**
* Builds an array of mocked Transition objects.
*
* @return ModerationStateTransitionInterface[]
* An array of mocked Transition objects.
*/
protected function setupTransitionEntityList() {
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
$transition->id()->willReturn('draft__needs_review');
$transition->getFromState()->willReturn('draft');
$transition->getToState()->willReturn('needs_review');
$list[$transition->reveal()->id()] = $transition->reveal();
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
$transition->id()->willReturn('needs_review__staging');
$transition->getFromState()->willReturn('needs_review');
$transition->getToState()->willReturn('staging');
$list[$transition->reveal()->id()] = $transition->reveal();
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
$transition->id()->willReturn('staging__published');
$transition->getFromState()->willReturn('staging');
$transition->getToState()->willReturn('published');
$list[$transition->reveal()->id()] = $transition->reveal();
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
$transition->id()->willReturn('needs_review__draft');
$transition->getFromState()->willReturn('needs_review');
$transition->getToState()->willReturn('draft');
$list[$transition->reveal()->id()] = $transition->reveal();
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
$transition->id()->willReturn('draft__draft');
$transition->getFromState()->willReturn('draft');
$transition->getToState()->willReturn('draft');
$list[$transition->reveal()->id()] = $transition->reveal();
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
$transition->id()->willReturn('needs_review__needs_review');
$transition->getFromState()->willReturn('needs_review');
$transition->getToState()->willReturn('needs_review');
$list[$transition->reveal()->id()] = $transition->reveal();
$transition = $this->prophesize(ModerationStateTransitionInterface::class);
$transition->id()->willReturn('published__published');
$transition->getFromState()->willReturn('published');
$transition->getToState()->willReturn('published');
$list[$transition->reveal()->id()] = $transition->reveal();
return $list;
}
/**
* Builds a mock storage object for States.
*
* @return EntityStorageInterface
* The mocked storage object for States.
*/
protected function setupStateStorage() {
$entity_storage = $this->prophesize(EntityStorageInterface::class);
$state = $this->prophesize(ModerationStateInterface::class);
$state->id()->willReturn('draft');
$state->label()->willReturn('Draft');
$state->isPublishedState()->willReturn(FALSE);
$state->isDefaultRevisionState()->willReturn(FALSE);
$states['draft'] = $state->reveal();
$state = $this->prophesize(ModerationStateInterface::class);
$state->id()->willReturn('needs_review');
$state->label()->willReturn('Needs Review');
$state->isPublishedState()->willReturn(FALSE);
$state->isDefaultRevisionState()->willReturn(FALSE);
$states['needs_review'] = $state->reveal();
$state = $this->prophesize(ModerationStateInterface::class);
$state->id()->willReturn('staging');
$state->label()->willReturn('Staging');
$state->isPublishedState()->willReturn(FALSE);
$state->isDefaultRevisionState()->willReturn(FALSE);
$states['staging'] = $state->reveal();
$state = $this->prophesize(ModerationStateInterface::class);
$state->id()->willReturn('published');
$state->label()->willReturn('Published');
$state->isPublishedState()->willReturn(TRUE);
$state->isDefaultRevisionState()->willReturn(TRUE);
$states['published'] = $state->reveal();
$state = $this->prophesize(ModerationStateInterface::class);
$state->id()->willReturn('archived');
$state->label()->willReturn('Archived');
$state->isPublishedState()->willReturn(TRUE);
$state->isDefaultRevisionState()->willReturn(TRUE);
$states['archived'] = $state->reveal();
$entity_storage->loadMultiple()->willReturn($states);
foreach ($states as $id => $state) {
$entity_storage->load($id)->willReturn($state);
}
return $entity_storage->reveal();
}
/**
* Builds a mocked Entity Type Manager.
*
* @return EntityTypeManagerInterface
* The mocked Entity Type Manager.
*/
protected function setupEntityTypeManager(EntityStorageInterface $storage) {
$entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
$entityTypeManager->getStorage('moderation_state')->willReturn($storage);
$entityTypeManager->getStorage('moderation_state_transition')->willReturn($this->setupTransitionStorage());
return $entityTypeManager->reveal();
}
/**
* Builds a mocked query factory that does nothing.
*
* @return QueryFactory
* The mocked query factory that does nothing.
*/
protected function setupQueryFactory() {
$factory = $this->prophesize(QueryFactory::class);
return $factory->reveal();
}
/**
* @covers ::isTransitionAllowed
* @covers ::calculatePossibleTransitions
*
* @dataProvider providerIsTransitionAllowedWithValidTransition
*/
public function testIsTransitionAllowedWithValidTransition($from_id, $to_id) {
$storage = $this->setupStateStorage();
$state_transition_validation = new StateTransitionValidation($this->setupEntityTypeManager($storage), $this->setupQueryFactory());
$this->assertTrue($state_transition_validation->isTransitionAllowed($storage->load($from_id), $storage->load($to_id)));
}
/**
* Data provider for self::testIsTransitionAllowedWithValidTransition().
*/
public function providerIsTransitionAllowedWithValidTransition() {
return [
['draft', 'draft'],
['draft', 'needs_review'],
['needs_review', 'needs_review'],
['needs_review', 'staging'],
['staging', 'published'],
['needs_review', 'draft'],
];
}
/**
* @covers ::isTransitionAllowed
* @covers ::calculatePossibleTransitions
*
* @dataProvider providerIsTransitionAllowedWithInValidTransition
*/
public function testIsTransitionAllowedWithInValidTransition($from_id, $to_id) {
$storage = $this->setupStateStorage();
$state_transition_validation = new StateTransitionValidation($this->setupEntityTypeManager($storage), $this->setupQueryFactory());
$this->assertFalse($state_transition_validation->isTransitionAllowed($storage->load($from_id), $storage->load($to_id)));
}
/**
* Data provider for self::testIsTransitionAllowedWithInValidTransition().
*/
public function providerIsTransitionAllowedWithInValidTransition() {
return [
['published', 'needs_review'],
['published', 'staging'],
['staging', 'needs_review'],
['staging', 'staging'],
['needs_review', 'published'],
['published', 'archived'],
['archived', 'published'],
];
}
/**
* Verifies user-aware transition validation.
*
* @param string $from_id
* The state to transition from.
* @param string $to_id
* The state to transition to.
* @param string $permission
* The permission to give the user, or not.
* @param bool $allowed
* Whether or not to grant a user this permission.
* @param bool $result
* Whether userMayTransition() is expected to return TRUE or FALSE.
*
* @dataProvider userTransitionsProvider
*/
public function testUserSensitiveValidTransitions($from_id, $to_id, $permission, $allowed, $result) {
$user = $this->prophesize(AccountInterface::class);
// The one listed permission will be returned as instructed; Any others are
// always denied.
$user->hasPermission($permission)->willReturn($allowed);
$user->hasPermission(Argument::type('string'))->willReturn(FALSE);
$storage = $this->setupStateStorage();
$validator = new Validator($this->setupEntityTypeManager($storage), $this->setupQueryFactory());
$this->assertEquals($result, $validator->userMayTransition($storage->load($from_id), $storage->load($to_id), $user->reveal()));
}
/**
* Data provider for the user transition test.
*/
public function userTransitionsProvider() {
// The user has the right permission, so let it through.
$ret[] = ['draft', 'draft', 'use draft__draft transition', TRUE, TRUE];
// The user doesn't have the right permission, block it.
$ret[] = ['draft', 'draft', 'use draft__draft transition', FALSE, FALSE];
// The user has some other permission that doesn't matter.
$ret[] = ['draft', 'draft', 'use draft__needs_review transition', TRUE, FALSE];
// The user has permission, but the transition isn't allowed anyway.
$ret[] = ['published', 'needs_review', 'use published__needs_review transition', TRUE, FALSE];
return $ret;
}
}
/**
* Testable subclass for selected tests.
*
* EntityQuery is beyond untestable, so we have to subclass and override the
* method that uses it.
*/
class Validator extends StateTransitionValidation {
/**
* {@inheritdoc}
*/
protected function getTransitionFromStates(ModerationStateInterface $from, ModerationStateInterface $to) {
if ($from->id() === 'draft' && $to->id() === 'draft') {
return $this->transitionStorage()->loadMultiple(['draft__draft'])[0];
}
}
}