Move all files to 2017/

This commit is contained in:
Oliver Davies 2025-09-29 22:25:17 +01:00
parent ac7370f67f
commit 2875863330
15717 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,9 @@
langcode: en
status: true
dependencies:
module:
- user
id: user.register
label: Register
targetEntityType: user
cache: true

View file

@ -0,0 +1,9 @@
langcode: en
status: true
dependencies:
module:
- user
id: user.compact
label: Compact
targetEntityType: user
cache: true

View file

@ -0,0 +1,9 @@
langcode: en
status: false
dependencies:
module:
- user
id: user.full
label: 'User account'
targetEntityType: user
cache: true

View file

@ -0,0 +1,10 @@
langcode: en
status: true
dependencies:
module:
- user
id: user_block_user_action
label: 'Block the selected user(s)'
type: user
plugin: user_block_user_action
configuration: { }

View file

@ -0,0 +1,10 @@
langcode: en
status: true
dependencies:
module:
- user
id: user_cancel_user_action
label: 'Cancel the selected user account(s)'
type: user
plugin: user_cancel_user_action
configuration: { }

View file

@ -0,0 +1,10 @@
langcode: en
status: true
dependencies:
module:
- user
id: user_unblock_user_action
label: 'Unblock the selected user(s)'
type: user
plugin: user_unblock_user_action
configuration: { }

View file

@ -0,0 +1,5 @@
uid_only: false
ip_limit: 50
ip_window: 3600
user_limit: 5
user_window: 21600

View file

@ -0,0 +1,28 @@
cancel_confirm:
body: "[user:display-name],\n\nA request to cancel your account has been made at [site:name].\n\nYou may now cancel your account on [site:url-brief] by clicking this link or copying and pasting it into your browser:\n\n[user:cancel-url]\n\nNOTE: The cancellation of your account is not reversible.\n\nThis link expires in one day and nothing will happen if it is not used.\n\n-- [site:name] team"
subject: 'Account cancellation request for [user:display-name] at [site:name]'
password_reset:
body: "[user:display-name],\n\nA request to reset the password for your account has been made at [site:name].\n\nYou may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used.\n\n-- [site:name] team"
subject: 'Replacement login information for [user:display-name] at [site:name]'
register_admin_created:
body: "[user:display-name],\n\nA site administrator at [site:name] has created an account for you. You may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n-- [site:name] team"
subject: 'An administrator created an account for you at [site:name]'
register_no_approval_required:
body: "[user:display-name],\n\nThank you for registering at [site:name]. You may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n-- [site:name] team"
subject: 'Account details for [user:display-name] at [site:name]'
register_pending_approval:
body: "[user:display-name],\n\nThank you for registering at [site:name]. Your application for an account is currently pending approval. Once it has been approved, you will receive another email containing information about how to log in, set your password, and other details.\n\n-- [site:name] team"
subject: 'Account details for [user:display-name] at [site:name] (pending admin approval)'
register_pending_approval_admin:
body: "[user:display-name] has applied for an account.\n\n[user:edit-url]"
subject: 'Account details for [user:display-name] at [site:name] (pending admin approval)'
status_activated:
body: "[user:display-name],\n\nYour account at [site:name] has been activated.\n\nYou may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:account-name]\npassword: Your password\n\n-- [site:name] team"
subject: 'Account details for [user:display-name] at [site:name] (approved)'
status_blocked:
body: "[user:display-name],\n\nYour account on [site:name] has been blocked.\n\n-- [site:name] team"
subject: 'Account details for [user:display-name] at [site:name] (blocked)'
status_canceled:
body: "[user:display-name],\n\nYour account on [site:name] has been canceled.\n\n-- [site:name] team"
subject: 'Account details for [user:display-name] at [site:name] (canceled)'
langcode: en

View file

@ -0,0 +1,8 @@
langcode: en
status: true
dependencies: { }
id: anonymous
label: 'Anonymous user'
weight: 0
is_admin: false
permissions: { }

View file

@ -0,0 +1,8 @@
langcode: en
status: true
dependencies: { }
id: authenticated
label: 'Authenticated user'
weight: 1
is_admin: false
permissions: { }

View file

@ -0,0 +1,16 @@
anonymous: Anonymous
verify_mail: true
notify:
cancel_confirm: true
password_reset: true
status_activated: true
status_blocked: false
status_canceled: false
register_admin_created: true
register_no_approval_required: true
register_pending_approval: true
register: visitors
cancel_method: user_cancel_block
password_reset_timeout: 86400
password_strength: true
langcode: en

View file

@ -0,0 +1,14 @@
langcode: en
status: true
dependencies:
module:
- user
id: user.user
targetEntityType: user
bundle: user
types:
- 'schema:Person'
fieldMappings:
name:
properties:
- 'schema:name'

View file

@ -0,0 +1,11 @@
langcode: en
status: true
dependencies:
module:
- user
id: user_search
label: Users
path: user
weight: 0
plugin: user_search
configuration: { }

View file

@ -0,0 +1,912 @@
langcode: en
status: true
dependencies:
module:
- user
id: user_admin_people
label: People
module: user
description: 'Find and manage people interacting with your site.'
tag: default
base_table: users_field_data
base_field: uid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'administer users'
cache:
type: tag
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: 0
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: false
summary: ''
columns:
user_bulk_form: user_bulk_form
name: name
status: status
rid: rid
created: created
access: access
edit_node: edit_node
dropbutton: dropbutton
info:
user_bulk_form:
align: ''
separator: ''
empty_column: false
responsive: ''
name:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
status:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: priority-low
rid:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: priority-low
created:
sortable: true
default_sort_order: desc
align: ''
separator: ''
empty_column: false
responsive: priority-low
access:
sortable: true
default_sort_order: desc
align: ''
separator: ''
empty_column: false
responsive: priority-low
edit_node:
align: ''
separator: ''
empty_column: false
responsive: priority-low
dropbutton:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
default: created
empty_table: true
row:
type: fields
fields:
user_bulk_form:
id: user_bulk_form
table: users
field: user_bulk_form
relationship: none
group_type: group
admin_label: ''
label: 'Bulk update'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
plugin_id: user_bulk_form
entity_type: user
name:
id: name
table: users_field_data
field: name
relationship: none
group_type: group
admin_label: ''
label: Username
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
plugin_id: field
type: user_name
entity_type: user
entity_field: name
status:
id: status
table: users_field_data
field: status
relationship: none
group_type: group
admin_label: ''
label: Status
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
plugin_id: field
type: boolean
settings:
format: custom
format_custom_true: Active
format_custom_false: Blocked
entity_type: user
entity_field: status
roles_target_id:
id: roles_target_id
table: user__roles
field: roles_target_id
relationship: none
group_type: group
admin_label: ''
label: Roles
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
type: ul
separator: ', '
plugin_id: user_roles
created:
id: created
table: users_field_data
field: created
relationship: none
group_type: group
admin_label: ''
label: 'Member for'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
type: timestamp_ago
settings:
future_format: '@interval'
past_format: '@interval'
granularity: 2
plugin_id: field
entity_type: user
entity_field: created
access:
id: access
table: users_field_data
field: access
relationship: none
group_type: group
admin_label: ''
label: 'Last access'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
type: timestamp_ago
settings:
future_format: '@interval hence'
past_format: '@interval ago'
granularity: 2
plugin_id: field
entity_type: user
entity_field: access
operations:
id: operations
table: users
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: user
plugin_id: entity_operations
mail:
id: mail
table: users_field_data
field: mail
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: true
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: basic_string
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
plugin_id: field
entity_type: user
entity_field: mail
filters:
combine:
id: combine
table: views
field: combine
relationship: none
group_type: group
admin_label: ''
operator: contains
value: ''
group: 1
exposed: true
expose:
operator_id: combine_op
label: 'Name or email contains'
description: ''
use_operator: false
operator: combine_op
identifier: user
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: { }
fields:
name: name
mail: mail
plugin_id: combine
status:
id: status
table: users_field_data
field: status
relationship: none
group_type: group
admin_label: ''
operator: '='
value: '1'
group: 1
exposed: true
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: status_op
identifier: status
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
is_grouped: true
group_info:
label: Status
description: ''
identifier: status
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items:
1:
title: Active
operator: '='
value: '1'
2:
title: Blocked
operator: '='
value: '0'
plugin_id: boolean
entity_type: user
entity_field: status
roles_target_id:
id: roles_target_id
table: user__roles
field: roles_target_id
relationship: none
group_type: group
admin_label: ''
operator: or
value: { }
group: 1
exposed: true
expose:
operator_id: roles_target_id_op
label: Role
description: ''
use_operator: false
operator: roles_target_id_op
identifier: role
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: { }
reduce_duplicates: false
plugin_id: user_roles
permission:
id: permission
table: user__roles
field: permission
relationship: none
group_type: group
admin_label: ''
operator: or
value: { }
group: 1
exposed: true
expose:
operator_id: permission_op
label: Permission
description: ''
use_operator: false
operator: permission_op
identifier: permission
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: { }
reduce_duplicates: false
plugin_id: user_permissions
default_langcode:
id: default_langcode
table: users_field_data
field: default_langcode
relationship: none
group_type: group
admin_label: ''
operator: '='
value: '1'
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: user
entity_field: default_langcode
plugin_id: boolean
uid_raw:
id: uid_raw
table: users_field_data
field: uid_raw
relationship: none
group_type: group
admin_label: ''
operator: '!='
value:
min: ''
max: ''
value: '0'
group: 1
exposed: false
expose:
operator_id: '0'
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: numeric
entity_type: user
sorts:
created:
id: created
table: users_field_data
field: created
relationship: none
group_type: group
admin_label: ''
order: DESC
exposed: false
expose:
label: ''
granularity: second
plugin_id: date
entity_type: user
entity_field: created
title: People
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 people available.'
plugin_id: text_custom
use_more: false
use_more_always: false
use_more_text: more
display_comment: ''
use_ajax: false
hide_attachment_summary: false
show_admin_links: true
group_by: false
link_url: ''
link_display: page_1
css_class: ''
filter_groups:
operator: AND
groups:
1: AND
display_extenders: { }
cache_metadata:
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
- user.permissions
max-age: 0
tags: { }
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
path: admin/people/list
show_admin_links: false
menu:
type: 'default tab'
title: List
description: 'Find and manage people interacting with your site.'
menu_name: admin
weight: -10
context: ''
tab_options:
type: normal
title: People
description: 'Manage user accounts, roles, and permissions.'
menu_name: admin
weight: 0
defaults:
show_admin_links: false
display_extenders: { }
cache_metadata:
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
- user.permissions
max-age: 0
tags: { }

View file

@ -0,0 +1,190 @@
langcode: en
status: true
dependencies:
module:
- user
id: who_s_new
label: 'Who''s new'
module: user
description: 'Shows a list of the newest user accounts on the site.'
tag: default
base_table: users_field_data
base_field: uid
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: 5
offset: 0
style:
type: html_list
row:
type: fields
fields:
name:
id: name
table: users_field_data
field: name
label: ''
plugin_id: field
type: user_name
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
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
entity_type: user
entity_field: name
filters:
status:
value: '1'
table: users_field_data
field: status
id: status
expose:
operator: '0'
group: 1
plugin_id: boolean
entity_type: user
entity_field: status
access:
id: access
table: users_field_data
field: access
relationship: none
group_type: group
admin_label: ''
operator: '>'
value:
min: ''
max: ''
value: '1970-01-01'
type: date
group: 1
exposed: false
expose:
operator_id: '0'
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: date
entity_type: user
entity_field: access
sorts:
created:
id: created
table: users_field_data
field: created
relationship: none
group_type: group
admin_label: ''
order: DESC
exposed: false
expose:
label: ''
granularity: second
plugin_id: date
entity_type: user
entity_field: created
title: 'Who''s new'
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- user.permissions
max-age: -1
tags: { }
block_1:
display_plugin: block
id: block_1
display_title: 'Who''s new'
position: 1
display_options:
display_description: 'A list of new users'
block_description: 'Who''s new'
block_category: User
display_extenders: { }
cache_metadata:
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- user.permissions
max-age: -1
tags: { }

View file

@ -0,0 +1,219 @@
langcode: en
status: true
dependencies:
module:
- user
id: who_s_online
label: 'Who''s online block'
module: user
description: 'Shows the user names of the most recently active users, and the total number of active users.'
tag: default
base_table: users_field_data
base_field: uid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access user profiles'
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: html_list
options:
grouping: { }
row_class: ''
default_row_class: true
type: ul
wrapper_class: item-list
class: ''
row:
type: fields
fields:
name:
id: name
table: users_field_data
field: name
label: ''
plugin_id: field
type: user_name
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
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
entity_type: user
entity_field: name
filters:
status:
value: '1'
table: users_field_data
field: status
id: status
expose:
operator: '0'
group: 1
plugin_id: boolean
entity_type: user
entity_field: status
access:
id: access
table: users_field_data
field: access
relationship: none
group_type: group
admin_label: ''
operator: '>='
value:
min: ''
max: ''
value: '-15 minutes'
type: offset
group: 1
exposed: false
expose:
operator_id: access_op
label: 'Last access'
description: 'A user is considered online for this long after they have last viewed a page.'
use_operator: false
operator: access_op
identifier: access
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: { }
plugin_id: date
entity_type: user
entity_field: access
sorts:
access:
id: access
table: users_field_data
field: access
order: DESC
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
granularity: second
plugin_id: date
entity_type: user
entity_field: access
title: 'Who''s online'
header:
result:
id: result
table: views
field: result
relationship: none
group_type: group
admin_label: ''
empty: false
content: 'There are currently @total users online.'
plugin_id: result
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: 'There are currently 0 users online.'
plugin_id: text_custom
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- user.permissions
max-age: -1
tags: { }
who_s_online_block:
display_plugin: block
id: who_s_online_block
display_title: 'Who''s online'
position: 1
display_options:
block_description: 'Who''s online'
display_description: 'A list of users that are currently logged in.'
display_extenders: { }
cache_metadata:
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- user.permissions
max-age: -1
tags: { }

View file

@ -0,0 +1,196 @@
# Schema for the configuration files of the User module.
user.settings:
type: config_object
label: 'User settings'
mapping:
anonymous:
type: label
label: 'Name'
verify_mail:
type: boolean
label: 'Require email verification when a visitor creates an account'
notify:
type: mapping
label: 'Notify user'
mapping:
cancel_confirm:
type: boolean
label: 'Account cancellation confirmation'
password_reset:
type: boolean
label: 'Notify user when password reset'
status_activated:
type: boolean
label: 'Notify user when account is activated'
status_blocked:
type: boolean
label: 'Account blocked'
status_canceled:
type: boolean
label: 'Account canceled'
register_admin_created:
type: boolean
label: 'Welcome (new user created by administrator)'
register_no_approval_required:
type: boolean
label: 'Welcome (no approval required)'
register_pending_approval:
type: boolean
label: 'Welcome (awaiting approval)'
register:
type: string
label: 'Who can register accounts?'
cancel_method:
type: string
label: 'When cancelling a user account'
password_reset_timeout:
type: integer
label: 'Password reset timeout'
password_strength:
type: boolean
label: 'Enable password strength indicator'
user.mail:
type: config_object
label: 'Email settings'
mapping:
cancel_confirm:
type: mail
label: 'Account cancellation confirmation'
password_reset:
type: mail
label: 'Password recovery'
register_admin_created:
type: mail
label: 'Account created by administrator'
register_no_approval_required:
type: mail
label: 'Registration confirmation (No approval required)'
register_pending_approval:
type: mail
label: 'Registration confirmation (Pending approval)'
register_pending_approval_admin:
type: mail
label: 'Admin (user awaiting approval)'
status_activated:
type: mail
label: 'Account activation'
status_blocked:
type: mail
label: 'Account blocked'
status_canceled:
type: mail
label: 'Account cancelled'
user.flood:
type: config_object
label: 'User flood settings'
mapping:
uid_only:
type: boolean
label: 'UID only identifier'
ip_limit:
type: integer
label: 'IP limit'
ip_window:
type: integer
label: 'IP window'
user_limit:
type: integer
label: 'User limit'
user_window:
type: integer
label: 'User window'
user.role.*:
type: config_entity
label: 'User role settings'
mapping:
id:
type: string
label: 'ID'
label:
type: label
label: 'Label'
weight:
type: integer
label: 'User role weight'
is_admin:
type: boolean
label: 'User is admin'
permissions:
type: sequence
label: 'Permissions'
sequence:
type: string
label: 'Permission'
action.configuration.user_add_role_action:
type: mapping
label: 'Configuration for the add role action'
mapping:
rid:
type: string
label: 'The ID of the role to add'
action.configuration.user_block_user_action:
type: action_configuration_default
label: 'Block the selected users configuration'
action.configuration.user_cancel_user_action:
type: action_configuration_default
label: 'Cancel the selected user accounts configuration'
action.configuration.user_remove_role_action:
type: mapping
label: 'Configuration for the remove role action'
mapping:
rid:
type: string
label: 'The ID of the role to remove'
action.configuration.user_unblock_user_action:
type: action_configuration_default
label: 'Unblock the selected users configuration'
search.plugin.user_search:
type: sequence
label: 'User search'
condition.plugin.user_role:
type: condition.plugin
mapping:
roles:
type: sequence
sequence:
type: string
# Schema for the entity reference 'default:user' selection handler settings.
entity_reference_selection.default:user:
type: entity_reference_selection.default
label: 'User selection handler settings'
mapping:
filter:
type: mapping
label: 'Filter settings'
mapping:
type:
type: string
label: 'Filter by'
role:
type: sequence
label: 'Restrict to the selected roles'
sequence:
type: string
label: 'Role'
include_anonymous:
type: boolean
label: 'Include the anonymous user in the matched entities.'
field.formatter.settings.user_name:
type: mapping
mapping:
link_to_entity:
type: boolean
label: 'Link to the user'

View file

@ -0,0 +1,104 @@
# Schema for the views plugins of the User module.
views.access.perm:
type: mapping
label: 'Permission'
mapping:
perm:
type: string
label: 'Permission'
views.access.role:
type: mapping
label: 'Roles'
mapping:
role:
type: sequence
label: 'List of roles'
sequence:
type: string
label: 'Role'
views.argument.user_uid:
type: views.argument.numeric
label: 'User ID'
views.argument.user__roles_rid:
type: views.argument.many_to_one
label: 'Role ID'
views.argument_validator.entity:user:
type: views.argument_validator_entity
label: 'User'
mapping:
restrict_roles:
type: boolean
label: 'Restrict user based on role'
roles:
type: sequence
label: 'Restrict to the selected roles'
sequence:
type: string
label: 'Role'
views.argument_default.user:
type: mapping
label: 'User ID from URL'
mapping:
user:
type: boolean
label: 'Also look for a node and use the node author'
views_field_user:
type: views_field
mapping:
link_to_user:
type: boolean
label: 'Link this field to its user'
views.field.user_permissions:
type: views.field.prerender_list
label: 'List of permission'
views.field.user_roles:
type: views.field.prerender_list
label: 'List of roles'
views.field.user:
type: views_field_user
label: 'User'
views.field.user_bulk_form:
type: views_field_bulk_form
label: 'User operations bulk form'
views.field.user_data:
type: views_field
label: 'User data field'
mapping:
data_module:
type: string
label: 'Module name'
data_name:
type: string
label: 'Name'
views.filter.user_current:
type: views.filter.boolean
label: 'Current user'
views.filter.user_name:
type: views.filter.in_operator
label: 'User name'
views.filter.user_permissions:
type: views.filter.many_to_one
label: 'Permission'
views.filter.user_roles:
type: views.filter.many_to_one
label: 'Role'
views.filter_value.user_current:
type: views.filter_value.boolean
label: 'Current user'

View file

@ -0,0 +1,22 @@
/**
* @file
* Admin styling for the User module.
*/
/* Permissions page */
.permissions .module {
font-weight: bold;
}
.permissions .permission {
padding-left: 1.5em; /* LTR */
}
[dir="rtl"] .permissions .permission {
padding-left: 0;
padding-right: 1.5em;
}
/* Account settings */
.user-admin-settings .details-description {
font-size: 0.85em;
padding-bottom: 0.5em;
}

View file

@ -0,0 +1,15 @@
/**
* @file
* Styling for the user module icons.
*/
/**
* Toolbar tab icon.
*/
.toolbar-bar .toolbar-icon-user:before {
background-image: url(../../../misc/icons/bebebe/person.svg);
}
.toolbar-bar .toolbar-icon-user:active:before,
.toolbar-bar .toolbar-icon-user.is-active:before {
background-image: url(../../../misc/icons/ffffff/person.svg);
}

View file

@ -0,0 +1,21 @@
/**
* @file
* Module styling for user module.
*/
.password-strength__title,
.password-strength__text {
display: inline;
}
.password-strength__meter {
height: 0.75em;
margin-top: 0.5em;
background-color: lightgray;
}
.password-strength__indicator {
height: 100%;
width: 0;
background-color: gray;
}
.password-confirm-match {
visibility: hidden;
}

View file

@ -0,0 +1,18 @@
id: d6_profile_values
label: Profile values
class: Drupal\user\Plugin\migrate\ProfileValues
migration_tags:
- Drupal 6
- Content
source:
plugin: d6_profile_field_values
process:
uid: uid
destination:
plugin: entity:user
migration_dependencies:
required:
- d6_user
- user_profile_field_instance
- user_profile_entity_display
- user_profile_entity_form_display

View file

@ -0,0 +1,56 @@
id: d6_user
label: User accounts
audit: true
migration_tags:
- Drupal 6
- Content
source:
plugin: d6_user
process:
# If you are using this file to build a custom migration consider removing
# the uid field to allow incremental migrations.
uid: uid
name: name
pass: pass
mail: mail
created: created
access: access
login: login
status: status
timezone:
plugin: user_update_7002
source: timezone
langcode:
plugin: user_langcode
source: language
fallback_to_site_default: false
preferred_langcode:
plugin: user_langcode
source: language
fallback_to_site_default: true
preferred_admin_langcode:
plugin: user_langcode
source: language
fallback_to_site_default: true
init: init
roles:
plugin: migration_lookup
migration: d6_user_role
source: roles
user_picture:
plugin: migration_lookup
migration: d6_user_picture_file
source: uid
no_stub: true
destination:
plugin: entity:user
md5_passwords: true
migration_dependencies:
required:
- d6_user_role
optional:
- language
- default_language
- d6_user_picture_file
- user_picture_entity_display
- user_picture_entity_form_display

View file

@ -0,0 +1,24 @@
id: d6_user_contact_settings
label: User contact settings
migration_tags:
- Drupal 6
- Content
source:
plugin: d6_user
constants:
key: contact
module: contact
process:
uid: uid
key: 'constants/key'
module: 'constants/module'
settings:
plugin: skip_row_if_not_set
index: contact
source: data
destination:
plugin: user_data
migration_dependencies:
required:
- d6_user

View file

@ -0,0 +1,69 @@
id: d6_user_mail
label: User mail configuration
migration_tags:
- Drupal 6
- Configuration
source:
plugin: variable
variables:
- user_mail_status_activated_subject
- user_mail_status_activated_body
- user_mail_password_reset_subject
- user_mail_password_reset_body
- user_mail_status_deleted_subject
- user_mail_status_deleted_body
- user_mail_register_admin_created_subject
- user_mail_register_admin_created_body
- user_mail_register_no_approval_required_subject
- user_mail_register_no_approval_required_body
- user_mail_register_pending_approval_subject
- user_mail_register_pending_approval_body
- user_mail_status_blocked_subject
- user_mail_status_blocked_body
source_module: user
process:
'status_activated/subject':
plugin: convert_tokens
source: user_mail_status_activated_subject
'status_activated/body':
plugin: convert_tokens
source: user_mail_status_activated_body
'password_reset/subject':
plugin: convert_tokens
source: user_mail_password_reset_subject
'password_reset/body':
plugin: convert_tokens
source: user_mail_password_reset_body
'cancel_confirm/subject':
plugin: convert_tokens
source: user_mail_status_deleted_subject
'cancel_confirm/body':
plugin: convert_tokens
source: user_mail_status_deleted_body
'register_admin_created/subject':
plugin: convert_tokens
source: user_mail_register_admin_created_subject
'register_admin_created/body':
plugin: convert_tokens
source: user_mail_register_admin_created_body
'register_no_approval_required/subject':
plugin: convert_tokens
source: user_mail_register_no_approval_required_subject
'register_no_approval_required/body':
plugin: convert_tokens
source: user_mail_register_no_approval_required_body
'register_pending_approval/subject':
plugin: convert_tokens
source: user_mail_register_pending_approval_subject
'register_pending_approval/body':
plugin: convert_tokens
source: user_mail_register_pending_approval_body
'status_blocked/subject':
plugin: convert_tokens
source: user_mail_status_blocked_subject
'status_blocked/body':
plugin: convert_tokens
source: user_mail_status_blocked_body
destination:
plugin: config
config_name: user.mail

View file

@ -0,0 +1,44 @@
id: d6_user_picture_file
label: User pictures
migration_tags:
- Drupal 6
- Content
source:
plugin: d6_user_picture_file
constants:
is_public: true
# source_base_path must be set by the tool configuring this migration. It
# represents the fully qualified path relative to which URIs in the files
# table are specified, and must end with a /.
source_base_path: ''
process:
filename: filename
uid: uid
source_full_path:
-
plugin: concat
delimiter: /
source:
- constants/source_base_path
- picture
-
plugin: urlencode
destination_full_path:
plugin: file_uri
source:
- picture
- file_directory_path
- temp_directory_path
- 'constants/is_public'
uri:
plugin: file_copy
source:
- '@source_full_path'
- '@destination_full_path'
destination:
plugin: entity:file
migration_dependencies:
# Every migration that references a file by Drupal 6 fid should specify d6_file as an
# optional dependency.
optional:
- d6_file

View file

@ -0,0 +1,42 @@
id: d6_user_role
label: User roles
migration_tags:
- Drupal 6
- Configuration
source:
plugin: d6_user_role
process:
id:
-
plugin: machine_name
source: name
-
plugin: user_update_8002
label: name
permissions:
-
plugin: static_map
source: permissions
bypass: true
map:
'use PHP for block visibility': 'use PHP for settings'
'administer site-wide contact form': 'administer contact forms'
'post comments without approval': 'skip comment approval'
'edit own blog entries': 'edit own blog content'
'edit any blog entry': 'edit any blog content'
'delete own blog entries': 'delete own blog content'
'delete any blog entry': 'delete any blog content'
'create forum topics': 'create forum content'
'delete any forum topic': 'delete any forum content'
'delete own forum topics': 'delete own forum content'
'edit any forum topic': 'edit any forum content'
'edit own forum topics': 'edit own forum content'
- plugin: system_update_7000
- plugin: node_update_7008
- plugin: flatten
- plugin: filter_format_permission
destination:
plugin: entity:user_role
migration_dependencies:
required:
- d6_filter_format

View file

@ -0,0 +1,30 @@
id: d6_user_settings
label: User configuration
migration_tags:
- Drupal 6
- Configuration
source:
plugin: variable
variables:
- user_mail_status_blocked_notify
- user_mail_status_activated_notify
- user_email_verification
- user_register
- anonymous
source_module: user
process:
'notify/status_blocked': user_mail_status_blocked_notify
'notify/status_activated': user_mail_status_activated_notify
verify_mail: user_email_verification
register:
plugin: static_map
source: user_register
default_value: visitors_admin_approval
map:
2: visitors_admin_approval
1: visitors
0: admin_only
anonymous: anonymous
destination:
plugin: config
config_name: user.settings

View file

@ -0,0 +1,59 @@
id: d7_user
label: User accounts
audit: true
migration_tags:
- Drupal 7
- Content
class: Drupal\user\Plugin\migrate\User
source:
plugin: d7_user
process:
# If you are using this file to build a custom migration consider removing
# the uid field to allow incremental migrations.
uid: uid
name: name
pass: pass
mail: mail
created: created
access: access
login: login
status: status
timezone: timezone
langcode:
plugin: user_langcode
source: entity_language
fallback_to_site_default: false
preferred_langcode:
plugin: user_langcode
source: language
fallback_to_site_default: true
preferred_admin_langcode:
plugin: user_langcode
source: language
fallback_to_site_default: true
init: init
roles:
plugin: migration_lookup
migration: d7_user_role
source: roles
user_picture:
-
plugin: default_value
source: picture
default_value: null
-
plugin: migration_lookup
migration: d7_file
destination:
plugin: entity:user
migration_dependencies:
required:
- d7_user_role
optional:
- d7_field_instance
- d7_file
- language
- default_language
- user_picture_field_instance
- user_picture_entity_display
- user_picture_entity_form_display

View file

@ -0,0 +1,23 @@
id: d7_user_flood
label: User flood configuration
migration_tags:
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- user_failed_login_identifier_uid_only
- user_failed_login_ip_limit
- user_failed_login_ip_window
- user_failed_login_user_window
- user_failed_login_user_limit
source_module: user
process:
uid_only: user_failed_login_identifier_uid_only
ip_limit: user_failed_login_ip_limit
ip_window: user_failed_login_ip_window
user_limit: user_failed_login_user_limit
user_window: user_failed_login_user_window
destination:
plugin: config
config_name: user.flood

View file

@ -0,0 +1,41 @@
id: d7_user_mail
label: User mail configuration
migration_tags:
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- user_mail_status_activated_subject
- user_mail_status_activated_body
- user_mail_password_reset_subject
- user_mail_password_reset_body
- user_mail_status_canceled_subject
- user_mail_status_canceled_body
- user_mail_register_admin_created_subject
- user_mail_register_admin_created_body
- user_mail_register_no_approval_required_subject
- user_mail_register_no_approval_required_body
- user_mail_register_pending_approval_subject
- user_mail_register_pending_approval_body
- user_mail_status_blocked_subject
- user_mail_status_blocked_body
source_module: user
process:
'status_activated/subject': user_mail_status_activated_subject
'status_activated/body': user_mail_status_activated_body
'password_reset/subject': user_mail_password_reset_subject
'password_reset/body': user_mail_password_reset_body
'cancel_confirm/subject': user_mail_status_canceled_subject
'cancel_confirm/body': user_mail_status_canceled_body
'register_admin_created/subject': user_mail_register_admin_created_subject
'register_admin_created/body': user_mail_register_admin_created_body
'register_no_approval_required/subject': user_mail_register_no_approval_required_subject
'register_no_approval_required/body': user_mail_register_no_approval_required_body
'register_pending_approval/subject': user_mail_register_pending_approval_subject
'register_pending_approval/body': user_mail_register_pending_approval_body
'status_blocked/subject': user_mail_status_blocked_subject
'status_blocked/body': user_mail_status_blocked_body
destination:
plugin: config
config_name: user.mail

View file

@ -0,0 +1,40 @@
id: d7_user_role
label: User roles
migration_tags:
- Drupal 7
- Configuration
source:
plugin: d7_user_role
process:
id:
-
plugin: machine_name
source: name
-
plugin: user_update_8002
label: name
permissions:
-
plugin: static_map
source: permissions
bypass: true
map:
'use PHP for block visibility': 'use PHP for settings'
'administer site-wide contact form': 'administer contact forms'
'post comments without approval': 'skip comment approval'
'edit own blog entries': 'edit own blog content'
'edit any blog entry': 'edit any blog content'
'delete own blog entries': 'delete own blog content'
'delete any blog entry': 'delete any blog content'
'create forum topics': 'create forum content'
'delete any forum topic': 'delete any forum content'
'delete own forum topics': 'delete own forum content'
'edit any forum topic': 'edit any forum content'
'edit own forum topics': 'edit own forum content'
- plugin: flatten
weight: weight
destination:
plugin: entity:user_role
migration_dependencies:
optional:
- d7_filter_format

View file

@ -0,0 +1,32 @@
id: user_picture_entity_display
label: User picture display configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: user_picture_instance
constants:
entity_type: user
bundle: user
view_mode: default
name: user_picture
type: image
options:
label: hidden
settings:
image_style: ''
image_link: content
process:
entity_type: 'constants/entity_type'
bundle: 'constants/bundle'
view_mode: 'constants/view_mode'
field_name: 'constants/name'
type: 'constants/type'
options: 'constants/options'
'options/type': '@type'
destination:
plugin: component_entity_display
migration_dependencies:
required:
- user_picture_field_instance

View file

@ -0,0 +1,31 @@
id: user_picture_entity_form_display
label: User picture form display configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: user_picture_instance
constants:
entity_type: user
bundle: user
form_mode: default
name: user_picture
type: image_image
options:
settings:
progress_indicator: throbber
preview_image_style: thumbnail
process:
entity_type: 'constants/entity_type'
bundle: 'constants/bundle'
field_name: 'constants/name'
form_mode: 'constants/form_mode'
type: 'constants/type'
options: 'constants/options'
'options/type': '@type'
destination:
plugin: component_entity_form_display
migration_dependencies:
required:
- user_picture_field_instance

View file

@ -0,0 +1,26 @@
id: user_picture_field
label: User picture field configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
# We do an empty source and a proper destination to have an idmap for
# dependencies.
plugin: md_empty
constants:
entity_type: user
type: image
name: user_picture
cardinality: 1
source_module: user
process:
entity_type: 'constants/entity_type'
field_name: 'constants/name'
type: 'constants/type'
cardinality: 'constants/cardinality'
destination:
plugin: entity:field_storage_config
dependencies:
module:
- image

View file

@ -0,0 +1,32 @@
id: user_picture_field_instance
label: User picture field instance configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: user_picture_instance
constants:
entity_type: user
bundle: user
name: user_picture
settings:
file_extensions: 'png gif jpg jpeg'
alt_field: false
title_field: false
min_resolution: ''
alt_field_required: false
title_field_required: false
process:
entity_type: 'constants/entity_type'
bundle: 'constants/bundle'
field_name: 'constants/name'
settings: 'constants/settings'
'settings/file_directory': file_directory
'settings/max_filesize': max_filesize
'settings/max_resolution': max_resolution
destination:
plugin: entity:field_config
migration_dependencies:
required:
- user_picture_field

View file

@ -0,0 +1,56 @@
id: user_profile_entity_display
label: User profile display configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: profile_field
constants:
entity_type: user
bundle: user
view_mode: default
options:
label: hidden
settings: {}
process:
entity_type: 'constants/entity_type'
bundle: 'constants/bundle'
view_mode: 'constants/view_mode'
field_name:
-
plugin: migration_lookup
migration: user_profile_field
source: fid
-
plugin: skip_on_empty
method: row
-
plugin: extract
index:
- 1
type:
plugin: static_map
source: type
map:
checkbox: list_default
date: datetime_default
list: text_default
selection: list_default
textfield: text_default
textarea: text_default
url: link
options: 'constants/options'
'options/type': '@type'
hidden:
plugin: static_map
source: visibility
default_value: false
map:
1: true # PROFILE_PRIVATE
4: true # PROFILE_HIDDEN
destination:
plugin: component_entity_display
migration_dependencies:
required:
- user_profile_field_instance

View file

@ -0,0 +1,65 @@
id: user_profile_entity_form_display
label: User profile form display configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: profile_field
constants:
empty: {}
entity_type: user
bundle: user
form_mode: default
process:
entity_type: 'constants/entity_type'
bundle: 'constants/bundle'
field_name:
-
plugin: migration_lookup
migration: user_profile_field
source: fid
-
plugin: skip_on_empty
method: row
-
plugin: extract
index:
- 1
form_mode: 'constants/form_mode'
type:
plugin: static_map
source: type
map:
checkbox: boolean_checkbox
date: datetime_default
list: text_textfield
selection: options_select
textfield: text_textfield
textarea: text_textarea
url: link_default
options: 'constants/options'
'options/type': '@type'
'options/settings':
plugin: field_instance_widget_settings
source:
- '@type'
- 'constants/empty' # we don't have any settings.
'options/settings/display_label': # Single on/off checkboxes need to have display_label = true otherwise their label doesn't show.
plugin: static_map
default_value: false
source: type
map:
checkbox: true
hidden:
plugin: static_map
source: visibility
default_value: false
map:
1: true # PROFILE_PRIVATE
4: true # PROFILE_HIDDEN
destination:
plugin: component_entity_form_display
migration_dependencies:
required:
- user_profile_field_instance

View file

@ -0,0 +1,44 @@
id: user_profile_field
label: User profile field configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: profile_field
constants:
entity_type: user
process:
entity_type: 'constants/entity_type'
field_name:
-
plugin: machine_name
source: name
-
plugin: make_unique_entity_field
length: 30
entity_type: field_storage_config
field: field_name
type:
plugin: static_map
source: type
map:
checkbox: boolean
date: datetime
list: text
selection: list_string
textfield: text
textarea: text_long
url: link
settings:
plugin: profile_field_settings
source: type
'settings/allowed_values': options
cardinality:
plugin: static_map
default_value: 1
source: type
map:
list: -1
destination:
plugin: entity:field_storage_config

View file

@ -0,0 +1,34 @@
id: user_profile_field_instance
label: User profile field instance configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: profile_field
constants:
entity_type: user
bundle: user
process:
entity_type: 'constants/entity_type'
bundle: 'constants/bundle'
label: title
description: explanation
field_name:
-
plugin: migration_lookup
migration: user_profile_field
source: fid
-
plugin: skip_on_empty
method: row
-
plugin: extract
index:
- 1
required: required
destination:
plugin: entity:field_config
migration_dependencies:
required:
- user_profile_field

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\user\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
/**
* Determines access to routes based on login status of current user.
*/
class LoginStatusCheck implements AccessInterface {
/**
* Checks access.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(AccountInterface $account, Route $route) {
$required_status = filter_var($route->getRequirement('_user_is_logged_in'), FILTER_VALIDATE_BOOLEAN);
$actual_status = $account->isAuthenticated();
$access_result = AccessResult::allowedIf($required_status === $actual_status)->addCacheContexts(['user.roles:authenticated']);
if (!$access_result->isAllowed()) {
$access_result->setReason($required_status === TRUE ? 'This route can only be accessed by authenticated users.' : 'This route can only be accessed by anonymous users.');
}
return $access_result;
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Drupal\user\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
/**
* Determines access to routes based on permissions defined via
* $module.permissions.yml files.
*/
class PermissionAccessCheck implements AccessInterface {
/**
* Checks access.
*
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Route $route, AccountInterface $account) {
$permission = $route->getRequirement('_permission');
if ($permission === NULL) {
return AccessResult::neutral();
}
// Allow to conjunct the permissions with OR ('+') or AND (',').
$split = explode(',', $permission);
if (count($split) > 1) {
return AccessResult::allowedIfHasPermissions($account, $split, 'AND');
}
else {
$split = explode('+', $permission);
return AccessResult::allowedIfHasPermissions($account, $split, 'OR');
}
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Drupal\user\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Access check for user registration routes.
*/
class RegisterAccessCheck implements AccessInterface {
/**
* Checks access.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(AccountInterface $account) {
$user_settings = \Drupal::config('user.settings');
return AccessResult::allowedIf($account->isAnonymous() && $user_settings->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY)->cacheUntilConfigurationChanges($user_settings);
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Drupal\user\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
/**
* Determines access to routes based on roles.
*
* You can specify the '_role' key on route requirements. If you specify a
* single role, users with that role with have access. If you specify multiple
* ones you can conjunct them with AND by using a "," and with OR by using "+".
*/
class RoleAccessCheck implements AccessInterface {
/**
* Checks access.
*
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Route $route, AccountInterface $account) {
// Requirements just allow strings, so this might be a comma separated list.
$rid_string = $route->getRequirement('_role');
$explode_and = array_filter(array_map('trim', explode(',', $rid_string)));
if (count($explode_and) > 1) {
$diff = array_diff($explode_and, $account->getRoles());
if (empty($diff)) {
return AccessResult::allowed()->addCacheContexts(['user.roles']);
}
}
else {
$explode_or = array_filter(array_map('trim', explode('+', $rid_string)));
$intersection = array_intersect($explode_or, $account->getRoles());
if (!empty($intersection)) {
return AccessResult::allowed()->addCacheContexts(['user.roles']);
}
}
// If there is no allowed role, give other access checks a chance.
return AccessResult::neutral()->addCacheContexts(['user.roles']);
}
}

View file

@ -0,0 +1,391 @@
<?php
namespace Drupal\user;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityConstraintViolationListInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUser;
use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUserAdmin;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for the user account forms.
*/
abstract class AccountForm extends ContentEntityForm {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new EntityForm object.
*
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle service.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
*/
public function __construct(EntityRepositoryInterface $entity_repository, LanguageManagerInterface $language_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
parent::__construct($entity_repository, $entity_type_bundle_info, $time);
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.repository'),
$container->get('language_manager'),
$container->get('entity_type.bundle.info'),
$container->get('datetime.time')
);
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
/** @var \Drupal\user\UserInterface $account */
$account = $this->entity;
$user = $this->currentUser();
$config = \Drupal::config('user.settings');
$form['#cache']['tags'] = $config->getCacheTags();
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
$register = $account->isAnonymous();
$admin = $user->hasPermission('administer users');
// Account information.
$form['account'] = [
'#type' => 'container',
'#weight' => -10,
];
// The mail field is NOT required if account originally had no mail set
// and the user performing the edit has 'administer users' permission.
// This allows users without email address to be edited and deleted.
// Also see \Drupal\user\Plugin\Validation\Constraint\UserMailRequired.
$form['account']['mail'] = [
'#type' => 'email',
'#title' => $this->t('Email address'),
'#description' => $this->t('A valid email address. All emails from the system will be sent to this address. The email address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by email.'),
'#required' => !(!$account->getEmail() && $admin),
'#default_value' => (!$register ? $account->getEmail() : ''),
];
// Only show name field on registration form or user can change own username.
$form['account']['name'] = [
'#type' => 'textfield',
'#title' => $this->t('Username'),
'#maxlength' => USERNAME_MAX_LENGTH,
'#description' => $this->t("Several special characters are allowed, including space, period (.), hyphen (-), apostrophe ('), underscore (_), and the @ sign."),
'#required' => TRUE,
'#attributes' => [
'class' => ['username'],
'autocorrect' => 'off',
'autocapitalize' => 'off',
'spellcheck' => 'false',
],
'#default_value' => (!$register ? $account->getAccountName() : ''),
'#access' => ($register || ($user->id() == $account->id() && $user->hasPermission('change own username')) || $admin),
];
// Display password field only for existing users or when user is allowed to
// assign a password during registration.
if (!$register) {
$form['account']['pass'] = [
'#type' => 'password_confirm',
'#size' => 25,
'#description' => $this->t('To change the current user password, enter the new password in both fields.'),
];
// To skip the current password field, the user must have logged in via a
// one-time link and have the token in the URL. Store this in $form_state
// so it persists even on subsequent Ajax requests.
if (!$form_state->get('user_pass_reset') && ($token = $this->getRequest()->get('pass-reset-token'))) {
$session_key = 'pass_reset_' . $account->id();
$user_pass_reset = isset($_SESSION[$session_key]) && Crypt::hashEquals($_SESSION[$session_key], $token);
$form_state->set('user_pass_reset', $user_pass_reset);
}
// The user must enter their current password to change to a new one.
if ($user->id() == $account->id()) {
$form['account']['current_pass'] = [
'#type' => 'password',
'#title' => $this->t('Current password'),
'#size' => 25,
'#access' => !$form_state->get('user_pass_reset'),
'#weight' => -5,
// Do not let web browsers remember this password, since we are
// trying to confirm that the person submitting the form actually
// knows the current one.
'#attributes' => ['autocomplete' => 'off'],
];
$form_state->set('user', $account);
// The user may only change their own password without their current
// password if they logged in via a one-time login link.
if (!$form_state->get('user_pass_reset')) {
$form['account']['current_pass']['#description'] = $this->t('Required if you want to change the %mail or %pass below. <a href=":request_new_url" title="Send password reset instructions via email.">Reset your password</a>.', [
'%mail' => $form['account']['mail']['#title'],
'%pass' => $this->t('Password'),
':request_new_url' => $this->url('user.pass'),
]);
}
}
}
elseif (!$config->get('verify_mail') || $admin) {
$form['account']['pass'] = [
'#type' => 'password_confirm',
'#size' => 25,
'#description' => $this->t('Provide a password for the new account in both fields.'),
'#required' => TRUE,
];
}
// When not building the user registration form, prevent web browsers from
// autofilling/prefilling the email, username, and password fields.
if (!$register) {
foreach (['mail', 'name', 'pass'] as $key) {
if (isset($form['account'][$key])) {
$form['account'][$key]['#attributes']['autocomplete'] = 'off';
}
}
}
if ($admin || !$register) {
$status = $account->get('status')->value;
}
else {
$status = $config->get('register') == USER_REGISTER_VISITORS ? 1 : 0;
}
$form['account']['status'] = [
'#type' => 'radios',
'#title' => $this->t('Status'),
'#default_value' => $status,
'#options' => [$this->t('Blocked'), $this->t('Active')],
'#access' => $admin,
];
$roles = array_map(['\Drupal\Component\Utility\Html', 'escape'], user_role_names(TRUE));
$form['account']['roles'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Roles'),
'#default_value' => (!$register ? $account->getRoles() : []),
'#options' => $roles,
'#access' => $roles && $user->hasPermission('administer permissions'),
];
// Special handling for the inevitable "Authenticated user" role.
$form['account']['roles'][RoleInterface::AUTHENTICATED_ID] = [
'#default_value' => TRUE,
'#disabled' => TRUE,
];
$form['account']['notify'] = [
'#type' => 'checkbox',
'#title' => $this->t('Notify user of new account'),
'#access' => $register && $admin,
];
$user_preferred_langcode = $register ? $language_interface->getId() : $account->getPreferredLangcode();
$user_preferred_admin_langcode = $register ? $language_interface->getId() : $account->getPreferredAdminLangcode(FALSE);
// Is the user preferred language added?
$user_language_added = FALSE;
if ($this->languageManager instanceof ConfigurableLanguageManagerInterface) {
$negotiator = $this->languageManager->getNegotiator();
$user_language_added = $negotiator && $negotiator->isNegotiationMethodEnabled(LanguageNegotiationUser::METHOD_ID, LanguageInterface::TYPE_INTERFACE);
}
$form['language'] = [
'#type' => $this->languageManager->isMultilingual() ? 'details' : 'container',
'#title' => $this->t('Language settings'),
'#open' => TRUE,
// Display language selector when either creating a user on the admin
// interface or editing a user account.
'#access' => !$register || $admin,
];
$form['language']['preferred_langcode'] = [
'#type' => 'language_select',
'#title' => $this->t('Site language'),
'#languages' => LanguageInterface::STATE_CONFIGURABLE,
'#default_value' => $user_preferred_langcode,
'#description' => $user_language_added ? $this->t("This account's preferred language for emails and site presentation.") : $this->t("This account's preferred language for emails."),
// This is used to explain that user preferred language and entity
// language are synchronized. It can be removed if a different behavior is
// desired.
'#pre_render' => ['user_langcode' => [$this, 'alterPreferredLangcodeDescription']],
];
// Only show the account setting for Administration pages language to users
// if one of the detection and selection methods uses it.
$show_admin_language = FALSE;
if ($account->hasPermission('access administration pages') && $this->languageManager instanceof ConfigurableLanguageManagerInterface) {
$negotiator = $this->languageManager->getNegotiator();
$show_admin_language = $negotiator && $negotiator->isNegotiationMethodEnabled(LanguageNegotiationUserAdmin::METHOD_ID);
}
$form['language']['preferred_admin_langcode'] = [
'#type' => 'language_select',
'#title' => $this->t('Administration pages language'),
'#languages' => LanguageInterface::STATE_CONFIGURABLE,
'#default_value' => $user_preferred_admin_langcode,
'#access' => $show_admin_language,
'#empty_option' => $this->t('- No preference -'),
'#empty_value' => '',
];
// User entities contain both a langcode property (for identifying the
// language of the entity data) and a preferred_langcode property (see
// above). Rather than provide a UI forcing the user to choose both
// separately, assume that the user profile data is in the user's preferred
// language. This entity builder provides that synchronization. For
// use-cases where this synchronization is not desired, a module can alter
// or remove this item.
$form['#entity_builders']['sync_user_langcode'] = '::syncUserLangcode';
return parent::form($form, $form_state, $account);
}
/**
* Alters the preferred language widget description.
*
* @param array $element
* The preferred language form element.
*
* @return array
* The preferred language form element.
*/
public function alterPreferredLangcodeDescription(array $element) {
// Only add to the description if the form element has a description.
if (isset($element['#description'])) {
$element['#description'] .= ' ' . $this->t("This is also assumed to be the primary language of this account's profile information.");
}
return $element;
}
/**
* Synchronizes preferred language and entity language.
*
* @param string $entity_type_id
* The entity type identifier.
* @param \Drupal\user\UserInterface $user
* The entity updated with the submitted values.
* @param array $form
* The complete form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function syncUserLangcode($entity_type_id, UserInterface $user, array &$form, FormStateInterface &$form_state) {
$user->getUntranslated()->langcode = $user->preferred_langcode;
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
// Change the roles array to a list of enabled roles.
// @todo: Alter the form state as the form values are directly extracted and
// set on the field, which throws an exception as the list requires
// numeric keys. Allow to override this per field. As this function is
// called twice, we have to prevent it from getting the array keys twice.
if (is_string(key($form_state->getValue('roles')))) {
$form_state->setValue('roles', array_keys(array_filter($form_state->getValue('roles'))));
}
/** @var \Drupal\user\UserInterface $account */
$account = parent::buildEntity($form, $form_state);
// Translate the empty value '' of language selects to an unset field.
foreach (['preferred_langcode', 'preferred_admin_langcode'] as $field_name) {
if ($form_state->getValue($field_name) === '') {
$account->$field_name = NULL;
}
}
// Set existing password if set in the form state.
$current_pass = trim($form_state->getValue('current_pass'));
if (strlen($current_pass) > 0) {
$account->setExistingPassword($current_pass);
}
// Skip the protected user field constraint if the user came from the
// password recovery page.
$account->_skipProtectedUserFieldConstraint = $form_state->get('user_pass_reset');
return $account;
}
/**
* {@inheritdoc}
*/
protected function getEditedFieldNames(FormStateInterface $form_state) {
return array_merge([
'name',
'pass',
'mail',
'timezone',
'langcode',
'preferred_langcode',
'preferred_admin_langcode',
], parent::getEditedFieldNames($form_state));
}
/**
* {@inheritdoc}
*/
protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
// Manually flag violations of fields not handled by the form display. This
// is necessary as entity form displays only flag violations for fields
// contained in the display.
$field_names = [
'name',
'pass',
'mail',
'timezone',
'langcode',
'preferred_langcode',
'preferred_admin_langcode',
];
foreach ($violations->getByFields($field_names) as $violation) {
list($field_name) = explode('.', $violation->getPropertyPath(), 2);
$form_state->setErrorByName($field_name, $violation->getMessage());
}
parent::flagViolations($violations, $form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
$user = $this->getEntity($form_state);
// If there's a session set to the users id, remove the password reset tag
// since a new password was saved.
if (isset($_SESSION['pass_reset_' . $user->id()])) {
unset($_SESSION['pass_reset_' . $user->id()]);
}
}
}

View file

@ -0,0 +1,481 @@
<?php
namespace Drupal\user;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure user settings for this site.
*
* @internal
*/
class AccountSettingsForm extends ConfigFormBase {
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The role storage used when changing the admin role.
*
* @var \Drupal\user\RoleStorageInterface
*/
protected $roleStorage;
/**
* Constructs a \Drupal\user\AccountSettingsForm object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\user\RoleStorageInterface $role_storage
* The role storage.
*/
public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, RoleStorageInterface $role_storage) {
parent::__construct($config_factory);
$this->moduleHandler = $module_handler;
$this->roleStorage = $role_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('module_handler'),
$container->get('entity.manager')->getStorage('user_role')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'user_admin_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
'system.site',
'user.mail',
'user.settings',
];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$config = $this->config('user.settings');
$mail_config = $this->config('user.mail');
$site_config = $this->config('system.site');
$form['#attached']['library'][] = 'user/drupal.user.admin';
// Settings for anonymous users.
$form['anonymous_settings'] = [
'#type' => 'details',
'#title' => $this->t('Anonymous users'),
'#open' => TRUE,
];
$form['anonymous_settings']['anonymous'] = [
'#type' => 'textfield',
'#title' => $this->t('Name'),
'#default_value' => $config->get('anonymous'),
'#description' => $this->t('The name used to indicate anonymous users.'),
'#required' => TRUE,
];
// Administrative role option.
$form['admin_role'] = [
'#type' => 'details',
'#title' => $this->t('Administrator role'),
'#open' => TRUE,
];
// Do not allow users to set the anonymous or authenticated user roles as the
// administrator role.
$roles = user_role_names(TRUE);
unset($roles[RoleInterface::AUTHENTICATED_ID]);
$admin_roles = $this->roleStorage->getQuery()
->condition('is_admin', TRUE)
->execute();
$default_value = reset($admin_roles);
$form['admin_role']['user_admin_role'] = [
'#type' => 'select',
'#title' => $this->t('Administrator role'),
'#empty_value' => '',
'#default_value' => $default_value,
'#options' => $roles,
'#description' => $this->t('This role will be automatically assigned new permissions whenever a module is enabled. Changing this setting will not affect existing permissions.'),
// Don't allow to select a single admin role in case multiple roles got
// marked as admin role already.
'#access' => count($admin_roles) <= 1,
];
// @todo Remove this check once language settings are generalized.
if ($this->moduleHandler->moduleExists('content_translation')) {
$form['language'] = [
'#type' => 'details',
'#title' => $this->t('Language settings'),
'#open' => TRUE,
'#tree' => TRUE,
];
$form_state->set(['content_translation', 'key'], 'language');
$form['language'] += content_translation_enable_widget('user', 'user', $form, $form_state);
}
// User registration settings.
$form['registration_cancellation'] = [
'#type' => 'details',
'#title' => $this->t('Registration and cancellation'),
'#open' => TRUE,
];
$form['registration_cancellation']['user_register'] = [
'#type' => 'radios',
'#title' => $this->t('Who can register accounts?'),
'#default_value' => $config->get('register'),
'#options' => [
USER_REGISTER_ADMINISTRATORS_ONLY => $this->t('Administrators only'),
USER_REGISTER_VISITORS => $this->t('Visitors'),
USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL => $this->t('Visitors, but administrator approval is required'),
],
];
$form['registration_cancellation']['user_email_verification'] = [
'#type' => 'checkbox',
'#title' => $this->t('Require email verification when a visitor creates an account'),
'#default_value' => $config->get('verify_mail'),
'#description' => $this->t('New users will be required to validate their email address prior to logging into the site, and will be assigned a system-generated password. With this setting disabled, users will be logged in immediately upon registering, and may select their own passwords during registration.'),
];
$form['registration_cancellation']['user_password_strength'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable password strength indicator'),
'#default_value' => $config->get('password_strength'),
];
$form['registration_cancellation']['user_cancel_method'] = [
'#type' => 'radios',
'#title' => $this->t('When cancelling a user account'),
'#default_value' => $config->get('cancel_method'),
'#description' => $this->t('Users with the %select-cancel-method or %administer-users <a href=":permissions-url">permissions</a> can override this default method.', ['%select-cancel-method' => $this->t('Select method for cancelling account'), '%administer-users' => $this->t('Administer users'), ':permissions-url' => $this->url('user.admin_permissions')]),
];
$form['registration_cancellation']['user_cancel_method'] += user_cancel_methods();
foreach (Element::children($form['registration_cancellation']['user_cancel_method']) as $key) {
// All account cancellation methods that specify #access cannot be
// configured as default method.
// @see hook_user_cancel_methods_alter()
if (isset($form['registration_cancellation']['user_cancel_method'][$key]['#access'])) {
$form['registration_cancellation']['user_cancel_method'][$key]['#access'] = FALSE;
}
}
// Default notifications address.
$form['mail_notification_address'] = [
'#type' => 'email',
'#title' => $this->t('Notification email address'),
'#default_value' => $site_config->get('mail_notification'),
'#description' => $this->t("The email address to be used as the 'from' address for all account notifications listed below. If <em>'Visitors, but administrator approval is required'</em> is selected above, a notification email will also be sent to this address for any new registrations. Leave empty to use the default system email address <em>(%site-email).</em>", ['%site-email' => $site_config->get('mail')]),
'#maxlength' => 180,
];
$form['email'] = [
'#type' => 'vertical_tabs',
'#title' => $this->t('Emails'),
];
// These email tokens are shared for all settings, so just define
// the list once to help ensure they stay in sync.
$email_token_help = $this->t('Available variables are: [site:name], [site:url], [user:display-name], [user:account-name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:cancel-url].');
$form['email_admin_created'] = [
'#type' => 'details',
'#title' => $this->t('Welcome (new user created by administrator)'),
'#open' => $config->get('register') == USER_REGISTER_ADMINISTRATORS_ONLY,
'#description' => $this->t('Edit the welcome email messages sent to new member accounts created by an administrator.') . ' ' . $email_token_help,
'#group' => 'email',
];
$form['email_admin_created']['user_mail_register_admin_created_subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Subject'),
'#default_value' => $mail_config->get('register_admin_created.subject'),
'#maxlength' => 180,
];
$form['email_admin_created']['user_mail_register_admin_created_body'] = [
'#type' => 'textarea',
'#title' => $this->t('Body'),
'#default_value' => $mail_config->get('register_admin_created.body'),
'#rows' => 15,
];
$form['email_pending_approval'] = [
'#type' => 'details',
'#title' => $this->t('Welcome (awaiting approval)'),
'#open' => $config->get('register') == USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL,
'#description' => $this->t('Edit the welcome email messages sent to new members upon registering, when administrative approval is required.') . ' ' . $email_token_help,
'#group' => 'email',
];
$form['email_pending_approval']['user_mail_register_pending_approval_subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Subject'),
'#default_value' => $mail_config->get('register_pending_approval.subject'),
'#maxlength' => 180,
];
$form['email_pending_approval']['user_mail_register_pending_approval_body'] = [
'#type' => 'textarea',
'#title' => $this->t('Body'),
'#default_value' => $mail_config->get('register_pending_approval.body'),
'#rows' => 8,
];
$form['email_pending_approval_admin'] = [
'#type' => 'details',
'#title' => $this->t('Admin (user awaiting approval)'),
'#open' => $config->get('register') == USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL,
'#description' => $this->t('Edit the email notifying the site administrator that there are new members awaiting administrative approval.') . ' ' . $email_token_help,
'#group' => 'email',
];
$form['email_pending_approval_admin']['register_pending_approval_admin_subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Subject'),
'#default_value' => $mail_config->get('register_pending_approval_admin.subject'),
'#maxlength' => 180,
];
$form['email_pending_approval_admin']['register_pending_approval_admin_body'] = [
'#type' => 'textarea',
'#title' => $this->t('Body'),
'#default_value' => $mail_config->get('register_pending_approval_admin.body'),
'#rows' => 8,
];
$form['email_no_approval_required'] = [
'#type' => 'details',
'#title' => $this->t('Welcome (no approval required)'),
'#open' => $config->get('register') == USER_REGISTER_VISITORS,
'#description' => $this->t('Edit the welcome email messages sent to new members upon registering, when no administrator approval is required.') . ' ' . $email_token_help,
'#group' => 'email',
];
$form['email_no_approval_required']['user_mail_register_no_approval_required_subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Subject'),
'#default_value' => $mail_config->get('register_no_approval_required.subject'),
'#maxlength' => 180,
];
$form['email_no_approval_required']['user_mail_register_no_approval_required_body'] = [
'#type' => 'textarea',
'#title' => $this->t('Body'),
'#default_value' => $mail_config->get('register_no_approval_required.body'),
'#rows' => 15,
];
$form['email_password_reset'] = [
'#type' => 'details',
'#title' => $this->t('Password recovery'),
'#description' => $this->t('Edit the email messages sent to users who request a new password.') . ' ' . $email_token_help,
'#group' => 'email',
'#weight' => 10,
];
$form['email_password_reset']['user_mail_password_reset_subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Subject'),
'#default_value' => $mail_config->get('password_reset.subject'),
'#maxlength' => 180,
];
$form['email_password_reset']['user_mail_password_reset_body'] = [
'#type' => 'textarea',
'#title' => $this->t('Body'),
'#default_value' => $mail_config->get('password_reset.body'),
'#rows' => 12,
];
$form['email_activated'] = [
'#type' => 'details',
'#title' => $this->t('Account activation'),
'#description' => $this->t('Enable and edit email messages sent to users upon account activation (when an administrator activates an account of a user who has already registered, on a site where administrative approval is required).') . ' ' . $email_token_help,
'#group' => 'email',
];
$form['email_activated']['user_mail_status_activated_notify'] = [
'#type' => 'checkbox',
'#title' => $this->t('Notify user when account is activated'),
'#default_value' => $config->get('notify.status_activated'),
];
$form['email_activated']['settings'] = [
'#type' => 'container',
'#states' => [
// Hide the additional settings when this email is disabled.
'invisible' => [
'input[name="user_mail_status_activated_notify"]' => ['checked' => FALSE],
],
],
];
$form['email_activated']['settings']['user_mail_status_activated_subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Subject'),
'#default_value' => $mail_config->get('status_activated.subject'),
'#maxlength' => 180,
];
$form['email_activated']['settings']['user_mail_status_activated_body'] = [
'#type' => 'textarea',
'#title' => $this->t('Body'),
'#default_value' => $mail_config->get('status_activated.body'),
'#rows' => 15,
];
$form['email_blocked'] = [
'#type' => 'details',
'#title' => $this->t('Account blocked'),
'#description' => $this->t('Enable and edit email messages sent to users when their accounts are blocked.') . ' ' . $email_token_help,
'#group' => 'email',
];
$form['email_blocked']['user_mail_status_blocked_notify'] = [
'#type' => 'checkbox',
'#title' => $this->t('Notify user when account is blocked'),
'#default_value' => $config->get('notify.status_blocked'),
];
$form['email_blocked']['settings'] = [
'#type' => 'container',
'#states' => [
// Hide the additional settings when the blocked email is disabled.
'invisible' => [
'input[name="user_mail_status_blocked_notify"]' => ['checked' => FALSE],
],
],
];
$form['email_blocked']['settings']['user_mail_status_blocked_subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Subject'),
'#default_value' => $mail_config->get('status_blocked.subject'),
'#maxlength' => 180,
];
$form['email_blocked']['settings']['user_mail_status_blocked_body'] = [
'#type' => 'textarea',
'#title' => $this->t('Body'),
'#default_value' => $mail_config->get('status_blocked.body'),
'#rows' => 3,
];
$form['email_cancel_confirm'] = [
'#type' => 'details',
'#title' => $this->t('Account cancellation confirmation'),
'#description' => $this->t('Edit the email messages sent to users when they attempt to cancel their accounts.') . ' ' . $email_token_help,
'#group' => 'email',
];
$form['email_cancel_confirm']['user_mail_cancel_confirm_subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Subject'),
'#default_value' => $mail_config->get('cancel_confirm.subject'),
'#maxlength' => 180,
];
$form['email_cancel_confirm']['user_mail_cancel_confirm_body'] = [
'#type' => 'textarea',
'#title' => $this->t('Body'),
'#default_value' => $mail_config->get('cancel_confirm.body'),
'#rows' => 3,
];
$form['email_canceled'] = [
'#type' => 'details',
'#title' => $this->t('Account canceled'),
'#description' => $this->t('Enable and edit email messages sent to users when their accounts are canceled.') . ' ' . $email_token_help,
'#group' => 'email',
];
$form['email_canceled']['user_mail_status_canceled_notify'] = [
'#type' => 'checkbox',
'#title' => $this->t('Notify user when account is canceled'),
'#default_value' => $config->get('notify.status_canceled'),
];
$form['email_canceled']['settings'] = [
'#type' => 'container',
'#states' => [
// Hide the settings when the cancel notify checkbox is disabled.
'invisible' => [
'input[name="user_mail_status_canceled_notify"]' => ['checked' => FALSE],
],
],
];
$form['email_canceled']['settings']['user_mail_status_canceled_subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Subject'),
'#default_value' => $mail_config->get('status_canceled.subject'),
'#maxlength' => 180,
];
$form['email_canceled']['settings']['user_mail_status_canceled_body'] = [
'#type' => 'textarea',
'#title' => $this->t('Body'),
'#default_value' => $mail_config->get('status_canceled.body'),
'#rows' => 3,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
$this->config('user.settings')
->set('anonymous', $form_state->getValue('anonymous'))
->set('register', $form_state->getValue('user_register'))
->set('password_strength', $form_state->getValue('user_password_strength'))
->set('verify_mail', $form_state->getValue('user_email_verification'))
->set('cancel_method', $form_state->getValue('user_cancel_method'))
->set('notify.status_activated', $form_state->getValue('user_mail_status_activated_notify'))
->set('notify.status_blocked', $form_state->getValue('user_mail_status_blocked_notify'))
->set('notify.status_canceled', $form_state->getValue('user_mail_status_canceled_notify'))
->save();
$this->config('user.mail')
->set('cancel_confirm.body', $form_state->getValue('user_mail_cancel_confirm_body'))
->set('cancel_confirm.subject', $form_state->getValue('user_mail_cancel_confirm_subject'))
->set('password_reset.body', $form_state->getValue('user_mail_password_reset_body'))
->set('password_reset.subject', $form_state->getValue('user_mail_password_reset_subject'))
->set('register_admin_created.body', $form_state->getValue('user_mail_register_admin_created_body'))
->set('register_admin_created.subject', $form_state->getValue('user_mail_register_admin_created_subject'))
->set('register_no_approval_required.body', $form_state->getValue('user_mail_register_no_approval_required_body'))
->set('register_no_approval_required.subject', $form_state->getValue('user_mail_register_no_approval_required_subject'))
->set('register_pending_approval.body', $form_state->getValue('user_mail_register_pending_approval_body'))
->set('register_pending_approval.subject', $form_state->getValue('user_mail_register_pending_approval_subject'))
->set('register_pending_approval_admin.body', $form_state->getValue('register_pending_approval_admin_body'))
->set('register_pending_approval_admin.subject', $form_state->getValue('register_pending_approval_admin_subject'))
->set('status_activated.body', $form_state->getValue('user_mail_status_activated_body'))
->set('status_activated.subject', $form_state->getValue('user_mail_status_activated_subject'))
->set('status_blocked.body', $form_state->getValue('user_mail_status_blocked_body'))
->set('status_blocked.subject', $form_state->getValue('user_mail_status_blocked_subject'))
->set('status_canceled.body', $form_state->getValue('user_mail_status_canceled_body'))
->set('status_canceled.subject', $form_state->getValue('user_mail_status_canceled_subject'))
->save();
$this->config('system.site')
->set('mail_notification', $form_state->getValue('mail_notification_address'))
->save();
// Change the admin role.
if ($form_state->hasValue('user_admin_role')) {
$admin_roles = $this->roleStorage->getQuery()
->condition('is_admin', TRUE)
->execute();
foreach ($admin_roles as $rid) {
$this->roleStorage->load($rid)->setIsAdmin(FALSE)->save();
}
$new_admin_role = $form_state->getValue('user_admin_role');
if ($new_admin_role) {
$this->roleStorage->load($new_admin_role)->setIsAdmin(TRUE)->save();
}
}
}
}

View file

@ -0,0 +1,93 @@
<?php
namespace Drupal\user\Authentication\Provider;
use Drupal\Core\Authentication\AuthenticationProviderInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\UserSession;
use Drupal\Core\Session\SessionConfigurationInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
/**
* Cookie based authentication provider.
*/
class Cookie implements AuthenticationProviderInterface {
/**
* The session configuration.
*
* @var \Drupal\Core\Session\SessionConfigurationInterface
*/
protected $sessionConfiguration;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* Constructs a new cookie authentication provider.
*
* @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
* The session configuration.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*/
public function __construct(SessionConfigurationInterface $session_configuration, Connection $connection) {
$this->sessionConfiguration = $session_configuration;
$this->connection = $connection;
}
/**
* {@inheritdoc}
*/
public function applies(Request $request) {
return $request->hasSession() && $this->sessionConfiguration->hasSession($request);
}
/**
* {@inheritdoc}
*/
public function authenticate(Request $request) {
return $this->getUserFromSession($request->getSession());
}
/**
* Returns the UserSession object for the given session.
*
* @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
* The session.
*
* @return \Drupal\Core\Session\AccountInterface|null
* The UserSession object for the current user, or NULL if this is an
* anonymous session.
*/
protected function getUserFromSession(SessionInterface $session) {
if ($uid = $session->get('uid')) {
// @todo Load the User entity in SessionHandler so we don't need queries.
// @see https://www.drupal.org/node/2345611
$values = $this->connection
->query('SELECT * FROM {users_field_data} u WHERE u.uid = :uid AND u.default_langcode = 1', [':uid' => $uid])
->fetchAssoc();
// Check if the user data was found and the user is active.
if (!empty($values) && $values['status'] == 1) {
// Add the user's roles.
$rids = $this->connection
->query('SELECT roles_target_id FROM {user__roles} WHERE entity_id = :uid', [':uid' => $values['uid']])
->fetchCol();
$values['roles'] = array_merge([AccountInterface::AUTHENTICATED_ROLE], $rids);
return new UserSession($values);
}
}
// This is an anonymous session.
return NULL;
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace Drupal\user\ContextProvider;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Context\ContextProviderInterface;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Sets the current user as a context.
*/
class CurrentUserContext implements ContextProviderInterface {
use StringTranslationTrait;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* The user storage.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* Constructs a new CurrentUserContext.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(AccountInterface $account, EntityManagerInterface $entity_manager) {
$this->account = $account;
$this->userStorage = $entity_manager->getStorage('user');
}
/**
* {@inheritdoc}
*/
public function getRuntimeContexts(array $unqualified_context_ids) {
$current_user = $this->userStorage->load($this->account->id());
if ($current_user) {
// @todo Do not validate protected fields to avoid bug in TypedData,
// remove this in https://www.drupal.org/project/drupal/issues/2934192.
$current_user->_skipProtectedUserFieldConstraint = TRUE;
}
$context = EntityContext::fromEntity($current_user, $this->t('Current user'));
$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['user']);
$context->addCacheableDependency($cacheability);
$result = [
'current_user' => $context,
];
return $result;
}
/**
* {@inheritdoc}
*/
public function getAvailableContexts() {
return $this->getRuntimeContexts([]);
}
}

View file

@ -0,0 +1,407 @@
<?php
namespace Drupal\user\Controller;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Flood\FloodInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\user\UserAuthInterface;
use Drupal\user\UserInterface;
use Drupal\user\UserStorageInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Serializer;
/**
* Provides controllers for login, login status and logout via HTTP requests.
*/
class UserAuthenticationController extends ControllerBase implements ContainerInjectionInterface {
/**
* String sent in responses, to describe the user as being logged in.
*
* @var string
*/
const LOGGED_IN = 1;
/**
* String sent in responses, to describe the user as being logged out.
*
* @var string
*/
const LOGGED_OUT = 0;
/**
* The flood controller.
*
* @var \Drupal\Core\Flood\FloodInterface
*/
protected $flood;
/**
* The user storage.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* The CSRF token generator.
*
* @var \Drupal\Core\Access\CsrfTokenGenerator
*/
protected $csrfToken;
/**
* The user authentication.
*
* @var \Drupal\user\UserAuthInterface
*/
protected $userAuth;
/**
* The route provider.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* The serializer.
*
* @var \Symfony\Component\Serializer\Serializer
*/
protected $serializer;
/**
* The available serialization formats.
*
* @var array
*/
protected $serializerFormats = [];
/**
* A logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* Constructs a new UserAuthenticationController object.
*
* @param \Drupal\Core\Flood\FloodInterface $flood
* The flood controller.
* @param \Drupal\user\UserStorageInterface $user_storage
* The user storage.
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
* The CSRF token generator.
* @param \Drupal\user\UserAuthInterface $user_auth
* The user authentication.
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider.
* @param \Symfony\Component\Serializer\Serializer $serializer
* The serializer.
* @param array $serializer_formats
* The available serialization formats.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
*/
public function __construct(FloodInterface $flood, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthInterface $user_auth, RouteProviderInterface $route_provider, Serializer $serializer, array $serializer_formats, LoggerInterface $logger) {
$this->flood = $flood;
$this->userStorage = $user_storage;
$this->csrfToken = $csrf_token;
$this->userAuth = $user_auth;
$this->serializer = $serializer;
$this->serializerFormats = $serializer_formats;
$this->routeProvider = $route_provider;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
if ($container->hasParameter('serializer.formats') && $container->has('serializer')) {
$serializer = $container->get('serializer');
$formats = $container->getParameter('serializer.formats');
}
else {
$formats = ['json'];
$encoders = [new JsonEncoder()];
$serializer = new Serializer([], $encoders);
}
return new static(
$container->get('flood'),
$container->get('entity_type.manager')->getStorage('user'),
$container->get('csrf_token'),
$container->get('user.auth'),
$container->get('router.route_provider'),
$serializer,
$formats,
$container->get('logger.factory')->get('user')
);
}
/**
* Logs in a user.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*
* @return \Symfony\Component\HttpFoundation\Response
* A response which contains the ID and CSRF token.
*/
public function login(Request $request) {
$format = $this->getRequestFormat($request);
$content = $request->getContent();
$credentials = $this->serializer->decode($content, $format);
if (!isset($credentials['name']) && !isset($credentials['pass'])) {
throw new BadRequestHttpException('Missing credentials.');
}
if (!isset($credentials['name'])) {
throw new BadRequestHttpException('Missing credentials.name.');
}
if (!isset($credentials['pass'])) {
throw new BadRequestHttpException('Missing credentials.pass.');
}
$this->floodControl($request, $credentials['name']);
if ($this->userIsBlocked($credentials['name'])) {
throw new BadRequestHttpException('The user has not been activated or is blocked.');
}
if ($uid = $this->userAuth->authenticate($credentials['name'], $credentials['pass'])) {
$this->flood->clear('user.http_login', $this->getLoginFloodIdentifier($request, $credentials['name']));
/** @var \Drupal\user\UserInterface $user */
$user = $this->userStorage->load($uid);
$this->userLoginFinalize($user);
// Send basic metadata about the logged in user.
$response_data = [];
if ($user->get('uid')->access('view', $user)) {
$response_data['current_user']['uid'] = $user->id();
}
if ($user->get('roles')->access('view', $user)) {
$response_data['current_user']['roles'] = $user->getRoles();
}
if ($user->get('name')->access('view', $user)) {
$response_data['current_user']['name'] = $user->getAccountName();
}
$response_data['csrf_token'] = $this->csrfToken->get('rest');
$logout_route = $this->routeProvider->getRouteByName('user.logout.http');
// Trim '/' off path to match \Drupal\Core\Access\CsrfAccessCheck.
$logout_path = ltrim($logout_route->getPath(), '/');
$response_data['logout_token'] = $this->csrfToken->get($logout_path);
$encoded_response_data = $this->serializer->encode($response_data, $format);
return new Response($encoded_response_data);
}
$flood_config = $this->config('user.flood');
if ($identifier = $this->getLoginFloodIdentifier($request, $credentials['name'])) {
$this->flood->register('user.http_login', $flood_config->get('user_window'), $identifier);
}
// Always register an IP-based failed login event.
$this->flood->register('user.failed_login_ip', $flood_config->get('ip_window'));
throw new BadRequestHttpException('Sorry, unrecognized username or password.');
}
/**
* Resets a user password.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*
* @return \Symfony\Component\HttpFoundation\Response
* The response object.
*/
public function resetPassword(Request $request) {
$format = $this->getRequestFormat($request);
$content = $request->getContent();
$credentials = $this->serializer->decode($content, $format);
// Check if a name or mail is provided.
if (!isset($credentials['name']) && !isset($credentials['mail'])) {
throw new BadRequestHttpException('Missing credentials.name or credentials.mail');
}
// Load by name if provided.
if (isset($credentials['name'])) {
$users = $this->userStorage->loadByProperties(['name' => trim($credentials['name'])]);
}
elseif (isset($credentials['mail'])) {
$users = $this->userStorage->loadByProperties(['mail' => trim($credentials['mail'])]);
}
/** @var \Drupal\Core\Session\AccountInterface $account */
$account = reset($users);
if ($account && $account->id()) {
if ($this->userIsBlocked($account->getAccountName())) {
throw new BadRequestHttpException('The user has not been activated or is blocked.');
}
// Send the password reset email.
$mail = _user_mail_notify('password_reset', $account, $account->getPreferredLangcode());
if (empty($mail)) {
throw new BadRequestHttpException('Unable to send email. Contact the site administrator if the problem persists.');
}
else {
$this->logger->notice('Password reset instructions mailed to %name at %email.', ['%name' => $account->getAccountName(), '%email' => $account->getEmail()]);
return new Response();
}
}
// Error if no users found with provided name or mail.
throw new BadRequestHttpException('Unrecognized username or email address.');
}
/**
* Verifies if the user is blocked.
*
* @param string $name
* The username.
*
* @return bool
* TRUE if the user is blocked, otherwise FALSE.
*/
protected function userIsBlocked($name) {
return user_is_blocked($name);
}
/**
* Finalizes the user login.
*
* @param \Drupal\user\UserInterface $user
* The user.
*/
protected function userLoginFinalize(UserInterface $user) {
user_login_finalize($user);
}
/**
* Logs out a user.
*
* @return \Symfony\Component\HttpFoundation\Response
* The response object.
*/
public function logout() {
$this->userLogout();
return new Response(NULL, 204);
}
/**
* Logs the user out.
*/
protected function userLogout() {
user_logout();
}
/**
* Checks whether a user is logged in or not.
*
* @return \Symfony\Component\HttpFoundation\Response
* The response.
*/
public function loginStatus() {
if ($this->currentUser()->isAuthenticated()) {
$response = new Response(self::LOGGED_IN);
}
else {
$response = new Response(self::LOGGED_OUT);
}
$response->headers->set('Content-Type', 'text/plain');
return $response;
}
/**
* Gets the format of the current request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*
* @return string
* The format of the request.
*/
protected function getRequestFormat(Request $request) {
$format = $request->getRequestFormat();
if (!in_array($format, $this->serializerFormats)) {
throw new BadRequestHttpException("Unrecognized format: $format.");
}
return $format;
}
/**
* Enforces flood control for the current login request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
* @param string $username
* The user name sent for login credentials.
*/
protected function floodControl(Request $request, $username) {
$flood_config = $this->config('user.flood');
if (!$this->flood->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
throw new AccessDeniedHttpException('Access is blocked because of IP based flood prevention.', NULL, Response::HTTP_TOO_MANY_REQUESTS);
}
if ($identifier = $this->getLoginFloodIdentifier($request, $username)) {
// Don't allow login if the limit for this user has been reached.
// Default is to allow 5 failed attempts every 6 hours.
if (!$this->flood->isAllowed('user.http_login', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
if ($flood_config->get('uid_only')) {
$error_message = sprintf('There have been more than %s failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', $flood_config->get('user_limit'));
}
else {
$error_message = 'Too many failed login attempts from your IP address. This IP address is temporarily blocked.';
}
throw new AccessDeniedHttpException($error_message, NULL, Response::HTTP_TOO_MANY_REQUESTS);
}
}
}
/**
* Gets the login identifier for user login flood control.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
* @param string $username
* The username supplied in login credentials.
*
* @return string
* The login identifier or if the user does not exist an empty string.
*/
protected function getLoginFloodIdentifier(Request $request, $username) {
$flood_config = $this->config('user.flood');
$accounts = $this->userStorage->loadByProperties(['name' => $username, 'status' => 1]);
if ($account = reset($accounts)) {
if ($flood_config->get('uid_only')) {
// Register flood events based on the uid only, so they apply for any
// IP address. This is the most secure option.
$identifier = $account->id();
}
else {
// The default identifier is a combination of uid and IP address. This
// is less secure but more resistant to denial-of-service attacks that
// could lock out all users with public user names.
$identifier = $account->id() . '-' . $request->getClientIp();
}
return $identifier;
}
return '';
}
}

View file

@ -0,0 +1,330 @@
<?php
namespace Drupal\user\Controller;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\user\Form\UserPasswordResetForm;
use Drupal\user\UserDataInterface;
use Drupal\user\UserInterface;
use Drupal\user\UserStorageInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* Controller routines for user routes.
*/
class UserController extends ControllerBase {
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* The user storage.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* The user data service.
*
* @var \Drupal\user\UserDataInterface
*/
protected $userData;
/**
* A logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* Constructs a UserController object.
*
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Drupal\user\UserStorageInterface $user_storage
* The user storage.
* @param \Drupal\user\UserDataInterface $user_data
* The user data service.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
*/
public function __construct(DateFormatterInterface $date_formatter, UserStorageInterface $user_storage, UserDataInterface $user_data, LoggerInterface $logger) {
$this->dateFormatter = $date_formatter;
$this->userStorage = $user_storage;
$this->userData = $user_data;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('date.formatter'),
$container->get('entity.manager')->getStorage('user'),
$container->get('user.data'),
$container->get('logger.factory')->get('user')
);
}
/**
* Redirects to the user password reset form.
*
* In order to never disclose a reset link via a referrer header this
* controller must always return a redirect response.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
* @param int $uid
* User ID of the user requesting reset.
* @param int $timestamp
* The current timestamp.
* @param string $hash
* Login link hash.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* The redirect response.
*/
public function resetPass(Request $request, $uid, $timestamp, $hash) {
$account = $this->currentUser();
// When processing the one-time login link, we have to make sure that a user
// isn't already logged in.
if ($account->isAuthenticated()) {
// The current user is already logged in.
if ($account->id() == $uid) {
user_logout();
// We need to begin the redirect process again because logging out will
// destroy the session.
return $this->redirect(
'user.reset',
[
'uid' => $uid,
'timestamp' => $timestamp,
'hash' => $hash,
]
);
}
// A different user is already logged in on the computer.
else {
/** @var \Drupal\user\UserInterface $reset_link_user */
if ($reset_link_user = $this->userStorage->load($uid)) {
$this->messenger()
->addWarning($this->t('Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. Please <a href=":logout">log out</a> and try using the link again.',
[
'%other_user' => $account->getUsername(),
'%resetting_user' => $reset_link_user->getUsername(),
':logout' => $this->url('user.logout'),
]));
}
else {
// Invalid one-time link specifies an unknown user.
$this->messenger()->addError($this->t('The one-time login link you clicked is invalid.'));
}
return $this->redirect('<front>');
}
}
$session = $request->getSession();
$session->set('pass_reset_hash', $hash);
$session->set('pass_reset_timeout', $timestamp);
return $this->redirect(
'user.reset.form',
['uid' => $uid]
);
}
/**
* Returns the user password reset form.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
* @param int $uid
* User ID of the user requesting reset.
*
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
* The form structure or a redirect response.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* If the pass_reset_timeout or pass_reset_hash are not available in the
* session. Or if $uid is for a blocked user or invalid user ID.
*/
public function getResetPassForm(Request $request, $uid) {
$session = $request->getSession();
$timestamp = $session->get('pass_reset_timeout');
$hash = $session->get('pass_reset_hash');
// As soon as the session variables are used they are removed to prevent the
// hash and timestamp from being leaked unexpectedly. This could occur if
// the user does not click on the log in button on the form.
$session->remove('pass_reset_timeout');
$session->remove('pass_reset_hash');
if (!$hash || !$timestamp) {
throw new AccessDeniedHttpException();
}
/** @var \Drupal\user\UserInterface $user */
$user = $this->userStorage->load($uid);
if ($user === NULL || !$user->isActive()) {
// Blocked or invalid user ID, so deny access. The parameters will be in
// the watchdog's URL for the administrator to check.
throw new AccessDeniedHttpException();
}
// Time out, in seconds, until login URL expires.
$timeout = $this->config('user.settings')->get('password_reset_timeout');
$expiration_date = $user->getLastLoginTime() ? $this->dateFormatter->format($timestamp + $timeout) : NULL;
return $this->formBuilder()->getForm(UserPasswordResetForm::class, $user, $expiration_date, $timestamp, $hash);
}
/**
* Validates user, hash, and timestamp; logs the user in if correct.
*
* @param int $uid
* User ID of the user requesting reset.
* @param int $timestamp
* The current timestamp.
* @param string $hash
* Login link hash.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* Returns a redirect to the user edit form if the information is correct.
* If the information is incorrect redirects to 'user.pass' route with a
* message for the user.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* If $uid is for a blocked user or invalid user ID.
*/
public function resetPassLogin($uid, $timestamp, $hash) {
// The current user is not logged in, so check the parameters.
$current = REQUEST_TIME;
/** @var \Drupal\user\UserInterface $user */
$user = $this->userStorage->load($uid);
// Verify that the user exists and is active.
if ($user === NULL || !$user->isActive()) {
// Blocked or invalid user ID, so deny access. The parameters will be in
// the watchdog's URL for the administrator to check.
throw new AccessDeniedHttpException();
}
// Time out, in seconds, until login URL expires.
$timeout = $this->config('user.settings')->get('password_reset_timeout');
// No time out for first time login.
if ($user->getLastLoginTime() && $current - $timestamp > $timeout) {
$this->messenger()->addError($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));
return $this->redirect('user.pass');
}
elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp))) {
user_login_finalize($user);
$this->logger->notice('User %name used one-time login link at time %timestamp.', ['%name' => $user->getDisplayName(), '%timestamp' => $timestamp]);
$this->messenger()->addStatus($this->t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'));
// Let the user's password be changed without the current password
// check.
$token = Crypt::randomBytesBase64(55);
$_SESSION['pass_reset_' . $user->id()] = $token;
return $this->redirect(
'entity.user.edit_form',
['user' => $user->id()],
[
'query' => ['pass-reset-token' => $token],
'absolute' => TRUE,
]
);
}
$this->messenger()->addError($this->t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'));
return $this->redirect('user.pass');
}
/**
* Redirects users to their profile page.
*
* This controller assumes that it is only invoked for authenticated users.
* This is enforced for the 'user.page' route with the '_user_is_logged_in'
* requirement.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* Returns a redirect to the profile of the currently logged in user.
*/
public function userPage() {
return $this->redirect('entity.user.canonical', ['user' => $this->currentUser()->id()]);
}
/**
* Route title callback.
*
* @param \Drupal\user\UserInterface $user
* The user account.
*
* @return string|array
* The user account name as a render array or an empty string if $user is
* NULL.
*/
public function userTitle(UserInterface $user = NULL) {
return $user ? ['#markup' => $user->getDisplayName(), '#allowed_tags' => Xss::getHtmlTagList()] : '';
}
/**
* Logs the current user out.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirection to home page.
*/
public function logout() {
user_logout();
return $this->redirect('<front>');
}
/**
* Confirms cancelling a user account via an email link.
*
* @param \Drupal\user\UserInterface $user
* The user account.
* @param int $timestamp
* The timestamp.
* @param string $hashed_pass
* The hashed password.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect response.
*/
public function confirmCancel(UserInterface $user, $timestamp = 0, $hashed_pass = '') {
// Time out in seconds until cancel URL expires; 24 hours = 86400 seconds.
$timeout = 86400;
$current = REQUEST_TIME;
// Basic validation of arguments.
$account_data = $this->userData->get('user', $user->id());
if (isset($account_data['cancel_method']) && !empty($timestamp) && !empty($hashed_pass)) {
// Validate expiration and hashed password/login.
if ($timestamp <= $current && $current - $timestamp < $timeout && $user->id() && $timestamp >= $user->getLastLoginTime() && Crypt::hashEquals($hashed_pass, user_pass_rehash($user, $timestamp))) {
$edit = [
'user_cancel_notify' => isset($account_data['cancel_notify']) ? $account_data['cancel_notify'] : $this->config('user.settings')->get('notify.status_canceled'),
];
user_cancel($edit, $user->id(), $account_data['cancel_method']);
// Since user_cancel() is not invoked via Form API, batch processing
// needs to be invoked manually and should redirect to the front page
// after completion.
return batch_process('<front>');
}
else {
$this->messenger()->addError($this->t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'));
return $this->redirect('entity.user.cancel_form', ['user' => $user->id()], ['absolute' => TRUE]);
}
}
throw new AccessDeniedHttpException();
}
}

View file

@ -0,0 +1,196 @@
<?php
namespace Drupal\user\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\user\RoleInterface;
/**
* Defines the user role entity class.
*
* @ConfigEntityType(
* id = "user_role",
* label = @Translation("Role"),
* label_collection = @Translation("Roles"),
* label_singular = @Translation("role"),
* label_plural = @Translation("roles"),
* label_count = @PluralTranslation(
* singular = "@count role",
* plural = "@count roles",
* ),
* handlers = {
* "storage" = "Drupal\user\RoleStorage",
* "access" = "Drupal\user\RoleAccessControlHandler",
* "list_builder" = "Drupal\user\RoleListBuilder",
* "form" = {
* "default" = "Drupal\user\RoleForm",
* "delete" = "Drupal\Core\Entity\EntityDeleteForm"
* }
* },
* admin_permission = "administer permissions",
* config_prefix = "role",
* static_cache = TRUE,
* entity_keys = {
* "id" = "id",
* "weight" = "weight",
* "label" = "label"
* },
* links = {
* "delete-form" = "/admin/people/roles/manage/{user_role}/delete",
* "edit-form" = "/admin/people/roles/manage/{user_role}",
* "edit-permissions-form" = "/admin/people/permissions/{user_role}",
* "collection" = "/admin/people/roles",
* },
* config_export = {
* "id",
* "label",
* "weight",
* "is_admin",
* "permissions",
* }
* )
*/
class Role extends ConfigEntityBase implements RoleInterface {
/**
* The machine name of this role.
*
* @var string
*/
protected $id;
/**
* The human-readable label of this role.
*
* @var string
*/
protected $label;
/**
* The weight of this role in administrative listings.
*
* @var int
*/
protected $weight;
/**
* The permissions belonging to this role.
*
* @var array
*/
protected $permissions = [];
/**
* An indicator whether the role has all permissions.
*
* @var bool
*/
protected $is_admin;
/**
* {@inheritdoc}
*/
public function getPermissions() {
if ($this->isAdmin()) {
return [];
}
return $this->permissions;
}
/**
* {@inheritdoc}
*/
public function getWeight() {
return $this->get('weight');
}
/**
* {@inheritdoc}
*/
public function setWeight($weight) {
$this->set('weight', $weight);
return $this;
}
/**
* {@inheritdoc}
*/
public function hasPermission($permission) {
if ($this->isAdmin()) {
return TRUE;
}
return in_array($permission, $this->permissions);
}
/**
* {@inheritdoc}
*/
public function grantPermission($permission) {
if ($this->isAdmin()) {
return $this;
}
if (!$this->hasPermission($permission)) {
$this->permissions[] = $permission;
}
return $this;
}
/**
* {@inheritdoc}
*/
public function revokePermission($permission) {
if ($this->isAdmin()) {
return $this;
}
$this->permissions = array_diff($this->permissions, [$permission]);
return $this;
}
/**
* {@inheritdoc}
*/
public function isAdmin() {
return (bool) $this->is_admin;
}
/**
* {@inheritdoc}
*/
public function setIsAdmin($is_admin) {
$this->is_admin = $is_admin;
return $this;
}
/**
* {@inheritdoc}
*/
public static function postLoad(EntityStorageInterface $storage, array &$entities) {
parent::postLoad($storage, $entities);
// Sort the queried roles by their weight.
// See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
uasort($entities, 'static::sort');
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
if (!isset($this->weight) && ($roles = $storage->loadMultiple())) {
// Set a role weight to make this new role last.
$max = array_reduce($roles, function ($max, $role) {
return $max > $role->weight ? $max : $role->weight;
});
$this->weight = $max + 1;
}
if (!$this->isSyncing()) {
// Permissions are always ordered alphabetically to avoid conflicts in the
// exported configuration.
sort($this->permissions);
}
}
}

View file

@ -0,0 +1,582 @@
<?php
namespace Drupal\user\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Language\LanguageInterface;
use Drupal\user\RoleInterface;
use Drupal\user\StatusItem;
use Drupal\user\TimeZoneItem;
use Drupal\user\UserInterface;
/**
* Defines the user entity class.
*
* The base table name here is plural, despite Drupal table naming standards,
* because "user" is a reserved word in many databases.
*
* @ContentEntityType(
* id = "user",
* label = @Translation("User"),
* label_collection = @Translation("Users"),
* label_singular = @Translation("user"),
* label_plural = @Translation("users"),
* label_count = @PluralTranslation(
* singular = "@count user",
* plural = "@count users",
* ),
* handlers = {
* "storage" = "Drupal\user\UserStorage",
* "storage_schema" = "Drupal\user\UserStorageSchema",
* "access" = "Drupal\user\UserAccessControlHandler",
* "list_builder" = "Drupal\user\UserListBuilder",
* "views_data" = "Drupal\user\UserViewsData",
* "route_provider" = {
* "html" = "Drupal\user\Entity\UserRouteProvider",
* },
* "form" = {
* "default" = "Drupal\user\ProfileForm",
* "cancel" = "Drupal\user\Form\UserCancelForm",
* "register" = "Drupal\user\RegisterForm"
* },
* "translation" = "Drupal\user\ProfileTranslationHandler"
* },
* admin_permission = "administer users",
* base_table = "users",
* data_table = "users_field_data",
* label_callback = "user_format_name",
* translatable = TRUE,
* entity_keys = {
* "id" = "uid",
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* links = {
* "canonical" = "/user/{user}",
* "edit-form" = "/user/{user}/edit",
* "cancel-form" = "/user/{user}/cancel",
* "collection" = "/admin/people",
* },
* field_ui_base_route = "entity.user.admin_form",
* common_reference_target = TRUE
* )
*/
class User extends ContentEntityBase implements UserInterface {
use EntityChangedTrait;
/**
* Stores a reference for a reusable anonymous user entity.
*
* @var \Drupal\user\UserInterface
*/
protected static $anonymousUser;
/**
* {@inheritdoc}
*/
public function isNew() {
return !empty($this->enforceIsNew) || $this->id() === NULL;
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
// Make sure that the authenticated/anonymous roles are not persisted.
foreach ($this->get('roles') as $index => $item) {
if (in_array($item->target_id, [RoleInterface::ANONYMOUS_ID, RoleInterface::AUTHENTICATED_ID])) {
$this->get('roles')->offsetUnset($index);
}
}
// Store account cancellation information.
foreach (['user_cancel_method', 'user_cancel_notify'] as $key) {
if (isset($this->{$key})) {
\Drupal::service('user.data')->set('user', $this->id(), substr($key, 5), $this->{$key});
}
}
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
if ($update) {
$session_manager = \Drupal::service('session_manager');
// If the password has been changed, delete all open sessions for the
// user and recreate the current one.
if ($this->pass->value != $this->original->pass->value) {
$session_manager->delete($this->id());
if ($this->id() == \Drupal::currentUser()->id()) {
\Drupal::service('session')->migrate();
}
}
// If the user was blocked, delete the user's sessions to force a logout.
if ($this->original->status->value != $this->status->value && $this->status->value == 0) {
$session_manager->delete($this->id());
}
// Send emails after we have the new user object.
if ($this->status->value != $this->original->status->value) {
// The user's status is changing; conditionally send notification email.
$op = $this->status->value == 1 ? 'status_activated' : 'status_blocked';
_user_mail_notify($op, $this);
}
}
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
$uids = array_keys($entities);
\Drupal::service('user.data')->delete(NULL, $uids);
}
/**
* {@inheritdoc}
*/
public function getRoles($exclude_locked_roles = FALSE) {
$roles = [];
// Users with an ID always have the authenticated user role.
if (!$exclude_locked_roles) {
if ($this->isAuthenticated()) {
$roles[] = RoleInterface::AUTHENTICATED_ID;
}
else {
$roles[] = RoleInterface::ANONYMOUS_ID;
}
}
foreach ($this->get('roles') as $role) {
if ($role->target_id) {
$roles[] = $role->target_id;
}
}
return $roles;
}
/**
* {@inheritdoc}
*/
public function hasRole($rid) {
return in_array($rid, $this->getRoles());
}
/**
* {@inheritdoc}
*/
public function addRole($rid) {
if (in_array($rid, [RoleInterface::AUTHENTICATED_ID, RoleInterface::ANONYMOUS_ID])) {
throw new \InvalidArgumentException('Anonymous or authenticated role ID must not be assigned manually.');
}
$roles = $this->getRoles(TRUE);
$roles[] = $rid;
$this->set('roles', array_unique($roles));
}
/**
* {@inheritdoc}
*/
public function removeRole($rid) {
$this->set('roles', array_diff($this->getRoles(TRUE), [$rid]));
}
/**
* {@inheritdoc}
*/
public function hasPermission($permission) {
// User #1 has all privileges.
if ((int) $this->id() === 1) {
return TRUE;
}
return $this->getRoleStorage()->isPermissionInRoles($permission, $this->getRoles());
}
/**
* {@inheritdoc}
*/
public function getPassword() {
return $this->get('pass')->value;
}
/**
* {@inheritdoc}
*/
public function setPassword($password) {
$this->get('pass')->value = $password;
return $this;
}
/**
* {@inheritdoc}
*/
public function getEmail() {
return $this->get('mail')->value;
}
/**
* {@inheritdoc}
*/
public function setEmail($mail) {
$this->get('mail')->value = $mail;
return $this;
}
/**
* {@inheritdoc}
*/
public function getCreatedTime() {
return $this->get('created')->value;
}
/**
* {@inheritdoc}
*/
public function getLastAccessedTime() {
return $this->get('access')->value;
}
/**
* {@inheritdoc}
*/
public function setLastAccessTime($timestamp) {
$this->get('access')->value = $timestamp;
return $this;
}
/**
* {@inheritdoc}
*/
public function getLastLoginTime() {
return $this->get('login')->value;
}
/**
* {@inheritdoc}
*/
public function setLastLoginTime($timestamp) {
$this->get('login')->value = $timestamp;
return $this;
}
/**
* {@inheritdoc}
*/
public function isActive() {
return $this->get('status')->value == 1;
}
/**
* {@inheritdoc}
*/
public function isBlocked() {
return $this->get('status')->value == 0;
}
/**
* {@inheritdoc}
*/
public function activate() {
$this->get('status')->value = 1;
return $this;
}
/**
* {@inheritdoc}
*/
public function block() {
$this->get('status')->value = 0;
return $this;
}
/**
* {@inheritdoc}
*/
public function getTimeZone() {
return $this->get('timezone')->value;
}
/**
* {@inheritdoc}
*/
public function getPreferredLangcode($fallback_to_default = TRUE) {
$language_list = $this->languageManager()->getLanguages();
$preferred_langcode = $this->get('preferred_langcode')->value;
if (!empty($preferred_langcode) && isset($language_list[$preferred_langcode])) {
return $language_list[$preferred_langcode]->getId();
}
else {
return $fallback_to_default ? $this->languageManager()->getDefaultLanguage()->getId() : '';
}
}
/**
* {@inheritdoc}
*/
public function getPreferredAdminLangcode($fallback_to_default = TRUE) {
$language_list = $this->languageManager()->getLanguages();
$preferred_langcode = $this->get('preferred_admin_langcode')->value;
if (!empty($preferred_langcode) && isset($language_list[$preferred_langcode])) {
return $language_list[$preferred_langcode]->getId();
}
else {
return $fallback_to_default ? $this->languageManager()->getDefaultLanguage()->getId() : '';
}
}
/**
* {@inheritdoc}
*/
public function getInitialEmail() {
return $this->get('init')->value;
}
/**
* {@inheritdoc}
*/
public function isAuthenticated() {
return $this->id() > 0;
}
/**
* {@inheritdoc}
*/
public function isAnonymous() {
return $this->id() == 0;
}
/**
* {@inheritdoc}
*/
public function getUsername() {
return $this->getAccountName();
}
/**
* {@inheritdoc}
*/
public function getAccountName() {
return $this->get('name')->value ?: '';
}
/**
* {@inheritdoc}
*/
public function getDisplayName() {
$name = $this->getAccountName() ?: \Drupal::config('user.settings')->get('anonymous');
\Drupal::moduleHandler()->alter('user_format_name', $name, $this);
return $name;
}
/**
* {@inheritdoc}
*/
public function setUsername($username) {
$this->set('name', $username);
return $this;
}
/**
* {@inheritdoc}
*/
public function setExistingPassword($password) {
$this->get('pass')->existing = $password;
}
/**
* {@inheritdoc}
*/
public function checkExistingPassword(UserInterface $account_unchanged) {
return strlen($this->get('pass')->existing) > 0 && \Drupal::service('password')->check(trim($this->get('pass')->existing), $account_unchanged->getPassword());
}
/**
* Returns an anonymous user entity.
*
* @return \Drupal\user\UserInterface
* An anonymous user entity.
*/
public static function getAnonymousUser() {
if (!isset(static::$anonymousUser)) {
// @todo Use the entity factory once available, see
// https://www.drupal.org/node/1867228.
$entity_manager = \Drupal::entityManager();
$entity_type = $entity_manager->getDefinition('user');
$class = $entity_type->getClass();
static::$anonymousUser = new $class([
'uid' => [LanguageInterface::LANGCODE_DEFAULT => 0],
'name' => [LanguageInterface::LANGCODE_DEFAULT => ''],
// Explicitly set the langcode to ensure that field definitions do not
// need to be fetched to figure out a default.
'langcode' => [LanguageInterface::LANGCODE_DEFAULT => LanguageInterface::LANGCODE_NOT_SPECIFIED],
], $entity_type->id());
}
return clone static::$anonymousUser;
}
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
$fields = parent::baseFieldDefinitions($entity_type);
$fields['uid']->setLabel(t('User ID'))
->setDescription(t('The user ID.'));
$fields['uuid']->setDescription(t('The user UUID.'));
$fields['langcode']->setLabel(t('Language code'))
->setDescription(t('The user language code.'))
->setDisplayOptions('form', ['region' => 'hidden']);
$fields['preferred_langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Preferred language code'))
->setDescription(t("The user's preferred language code for receiving emails and viewing the site."))
// @todo: Define this via an options provider once
// https://www.drupal.org/node/2329937 is completed.
->addPropertyConstraints('value', [
'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedConfigurableLanguageCodes'],
]);
$fields['preferred_admin_langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Preferred admin language code'))
->setDescription(t("The user's preferred language code for viewing administration pages."))
// @todo: A default value of NULL is ignored, so we have to specify
// an empty field item structure instead. Fix this in
// https://www.drupal.org/node/2318605.
->setDefaultValue([0 => ['value' => NULL]])
// @todo: Define this via an options provider once
// https://www.drupal.org/node/2329937 is completed.
->addPropertyConstraints('value', [
'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedConfigurableLanguageCodes'],
]);
// The name should not vary per language. The username is the visual
// identifier for a user and needs to be consistent in all languages.
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setDescription(t('The name of this user.'))
->setRequired(TRUE)
->setConstraints([
// No Length constraint here because the UserName constraint also covers
// that.
'UserName' => [],
'UserNameUnique' => [],
]);
$fields['name']->getItemDefinition()->setClass('\Drupal\user\UserNameItem');
$fields['pass'] = BaseFieldDefinition::create('password')
->setLabel(t('Password'))
->setDescription(t('The password of this user (hashed).'))
->addConstraint('ProtectedUserField');
$fields['mail'] = BaseFieldDefinition::create('email')
->setLabel(t('Email'))
->setDescription(t('The email of this user.'))
->setDefaultValue('')
->addConstraint('UserMailUnique')
->addConstraint('UserMailRequired')
->addConstraint('ProtectedUserField');
$fields['timezone'] = BaseFieldDefinition::create('string')
->setLabel(t('Timezone'))
->setDescription(t('The timezone of this user.'))
->setSetting('max_length', 32)
// @todo: Define this via an options provider once
// https://www.drupal.org/node/2329937 is completed.
->addPropertyConstraints('value', [
'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedTimezones'],
]);
$fields['timezone']->getItemDefinition()->setClass(TimeZoneItem::class);
$fields['status'] = BaseFieldDefinition::create('boolean')
->setLabel(t('User status'))
->setDescription(t('Whether the user is active or blocked.'))
->setDefaultValue(FALSE);
$fields['status']->getItemDefinition()->setClass(StatusItem::class);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Created'))
->setDescription(t('The time that the user was created.'));
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time that the user was last edited.'))
->setTranslatable(TRUE);
$fields['access'] = BaseFieldDefinition::create('timestamp')
->setLabel(t('Last access'))
->setDescription(t('The time that the user last accessed the site.'))
->setDefaultValue(0);
$fields['login'] = BaseFieldDefinition::create('timestamp')
->setLabel(t('Last login'))
->setDescription(t('The time that the user last logged in.'))
->setDefaultValue(0);
$fields['init'] = BaseFieldDefinition::create('email')
->setLabel(t('Initial email'))
->setDescription(t('The email address used for initial account creation.'))
->setDefaultValue('');
$fields['roles'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Roles'))
->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED)
->setDescription(t('The roles the user has.'))
->setSetting('target_type', 'user_role');
return $fields;
}
/**
* Returns the role storage object.
*
* @return \Drupal\user\RoleStorageInterface
* The role storage object.
*/
protected function getRoleStorage() {
return \Drupal::entityManager()->getStorage('user_role');
}
/**
* Defines allowed timezones for the field's AllowedValues constraint.
*
* @return string[]
* The allowed values.
*/
public static function getAllowedTimezones() {
return array_keys(system_time_zones());
}
/**
* Defines allowed configurable language codes for AllowedValues constraints.
*
* @return string[]
* The allowed values.
*/
public static function getAllowedConfigurableLanguageCodes() {
return array_keys(\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_CONFIGURABLE));
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace Drupal\user\Entity;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides routes for the user entity.
*/
class UserRouteProvider implements EntityRouteProviderInterface {
/**
* {@inheritdoc}
*/
public function getRoutes(EntityTypeInterface $entity_type) {
$route_collection = new RouteCollection();
$route = (new Route('/user/{user}'))
->setDefaults([
'_entity_view' => 'user.full',
'_title_callback' => 'Drupal\user\Controller\UserController::userTitle',
])
->setRequirement('user', '\d+')
->setRequirement('_entity_access', 'user.view');
$route_collection->add('entity.user.canonical', $route);
$route = (new Route('/user/{user}/edit'))
->setDefaults([
'_entity_form' => 'user.default',
'_title_callback' => 'Drupal\user\Controller\UserController::userTitle',
])
->setOption('_admin_route', TRUE)
->setRequirement('user', '\d+')
->setRequirement('_entity_access', 'user.update');
$route_collection->add('entity.user.edit_form', $route);
$route = (new Route('/user/{user}/cancel'))
->setDefaults([
'_title' => 'Cancel account',
'_entity_form' => 'user.cancel',
])
->setOption('_admin_route', TRUE)
->setRequirement('user', '\d+')
->setRequirement('_entity_access', 'user.delete');
$route_collection->add('entity.user.cancel_form', $route);
return $route_collection;
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace Drupal\user;
/**
* Defines a common interface for entities that have an owner.
*
* An owner is someone who has primary control over an entity, similar to
* owners in Unix file system access. This may or may not be the entity's
* original author. The owner may also have less permissions than other users,
* such as administrators.
*/
interface EntityOwnerInterface {
/**
* Returns the entity owner's user entity.
*
* @return \Drupal\user\UserInterface
* The owner user entity.
*/
public function getOwner();
/**
* Sets the entity owner's user entity.
*
* @param \Drupal\user\UserInterface $account
* The owner user entity.
*
* @return $this
*/
public function setOwner(UserInterface $account);
/**
* Returns the entity owner's user ID.
*
* @return int|null
* The owner user ID, or NULL in case the user ID field has not been set on
* the entity.
*/
public function getOwnerId();
/**
* Sets the entity owner's user ID.
*
* @param int $uid
* The owner user id.
*
* @return $this
*/
public function setOwnerId($uid);
}

View file

@ -0,0 +1,86 @@
<?php
namespace Drupal\user\EventSubscriber;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\UrlGeneratorTrait;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Redirects users when access is denied.
*
* Anonymous users are taken to the login page when attempting to access the
* user profile pages. Authenticated users are redirected from the login form to
* their profile page and from the user registration form to their profile edit
* form.
*/
class AccessDeniedSubscriber implements EventSubscriberInterface {
use UrlGeneratorTrait;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* Constructs a new redirect subscriber.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* The URL generator.
*/
public function __construct(AccountInterface $account, UrlGeneratorInterface $url_generator) {
$this->account = $account;
$this->setUrlGenerator($url_generator);
}
/**
* Redirects users when access is denied.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function onException(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
if ($exception instanceof AccessDeniedHttpException) {
$route_name = RouteMatch::createFromRequest($event->getRequest())->getRouteName();
if ($this->account->isAuthenticated()) {
switch ($route_name) {
case 'user.login';
// Redirect an authenticated user to the profile page.
$event->setResponse($this->redirect('entity.user.canonical', ['user' => $this->account->id()]));
break;
case 'user.register';
// Redirect an authenticated user to the profile form.
$event->setResponse($this->redirect('entity.user.edit_form', ['user' => $this->account->id()]));
break;
}
}
elseif ($route_name === 'user.page') {
$event->setResponse($this->redirect('user.login'));
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Use a higher priority than
// \Drupal\Core\EventSubscriber\ExceptionLoggingSubscriber, because there's
// no need to log the exception if we can redirect.
$events[KernelEvents::EXCEPTION][] = ['onException', 75];
return $events;
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Drupal\user\EventSubscriber;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\UrlGeneratorTrait;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Site\MaintenanceModeInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Maintenance mode subscriber to log out users.
*/
class MaintenanceModeSubscriber implements EventSubscriberInterface {
use UrlGeneratorTrait;
/**
* The maintenance mode.
*
* @var \Drupal\Core\Site\MaintenanceMode
*/
protected $maintenanceMode;
/**
* The current account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* Constructs a new MaintenanceModeSubscriber.
*
* @param \Drupal\Core\Site\MaintenanceModeInterface $maintenance_mode
* The maintenance mode.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
*/
public function __construct(MaintenanceModeInterface $maintenance_mode, AccountInterface $account) {
$this->maintenanceMode = $maintenance_mode;
$this->account = $account;
}
/**
* Logout users if site is in maintenance mode.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The event to process.
*/
public function onKernelRequestMaintenance(GetResponseEvent $event) {
$request = $event->getRequest();
$route_match = RouteMatch::createFromRequest($request);
if ($this->maintenanceMode->applies($route_match)) {
// If the site is offline, log out unprivileged users.
if ($this->account->isAuthenticated() && !$this->maintenanceMode->exempt($this->account)) {
user_logout();
// Redirect to homepage.
$event->setResponse($this->redirect($this->url('<front>')));
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = ['onKernelRequestMaintenance', 31];
return $events;
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Drupal\user\EventSubscriber;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Site\Settings;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Updates the current user's last access time.
*/
class UserRequestSubscriber implements EventSubscriberInterface {
/**
* The current account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new UserRequestSubscriber.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(AccountInterface $account, EntityManagerInterface $entity_manager) {
$this->account = $account;
$this->entityManager = $entity_manager;
}
/**
* Updates the current user's last access time.
*
* @param \Symfony\Component\HttpKernel\Event\PostResponseEvent $event
* The event to process.
*/
public function onKernelTerminate(PostResponseEvent $event) {
if ($this->account->isAuthenticated() && REQUEST_TIME - $this->account->getLastAccessedTime() > Settings::get('session_write_interval', 180)) {
// Do that no more than once per 180 seconds.
/** @var \Drupal\user\UserStorageInterface $storage */
$storage = $this->entityManager->getStorage('user');
$storage->updateLastAccessTimestamp($this->account, REQUEST_TIME);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Should go before other subscribers start to write their caches. Notably
// before \Drupal\Core\EventSubscriber\KernelDestructionSubscriber to
// prevent instantiation of destructed services.
$events[KernelEvents::TERMINATE][] = ['onKernelTerminate', 300];
return $events;
}
}

View file

@ -0,0 +1,152 @@
<?php
namespace Drupal\user\Form;
use Drupal\Core\Entity\ContentEntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a confirmation form for cancelling user account.
*
* @internal
*/
class UserCancelForm extends ContentEntityConfirmFormBase {
/**
* Available account cancellation methods.
*
* @var array
*/
protected $cancelMethods;
/**
* The user being cancelled.
*
* @var \Drupal\user\UserInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
public function getQuestion() {
if ($this->entity->id() == $this->currentUser()->id()) {
return $this->t('Are you sure you want to cancel your account?');
}
return $this->t('Are you sure you want to cancel the account %name?', ['%name' => $this->entity->label()]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return $this->entity->urlInfo();
}
/**
* {@inheritdoc}
*/
public function getDescription() {
$description = '';
$default_method = $this->config('user.settings')->get('cancel_method');
if ($this->currentUser()->hasPermission('administer users') || $this->currentUser()->hasPermission('select account cancellation method')) {
$description = $this->t('Select the method to cancel the account above.');
}
// Options supplied via user_cancel_methods() can have a custom
// #confirm_description property for the confirmation form description.
elseif (isset($this->cancelMethods[$default_method]['#confirm_description'])) {
$description = $this->cancelMethods[$default_method]['#confirm_description'];
}
return $description . ' ' . $this->t('This action cannot be undone.');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Cancel account');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$user = $this->currentUser();
$this->cancelMethods = user_cancel_methods();
// Display account cancellation method selection, if allowed.
$admin_access = $user->hasPermission('administer users');
$form['user_cancel_method'] = [
'#type' => 'radios',
'#title' => ($this->entity->id() == $user->id() ? $this->t('When cancelling your account') : $this->t('When cancelling the account')),
'#access' => $admin_access || $user->hasPermission('select account cancellation method'),
];
$form['user_cancel_method'] += $this->cancelMethods;
// Allow user administrators to skip the account cancellation confirmation
// mail (by default), as long as they do not attempt to cancel their own
// account.
$override_access = $admin_access && ($this->entity->id() != $user->id());
$form['user_cancel_confirm'] = [
'#type' => 'checkbox',
'#title' => $this->t('Require email confirmation to cancel account'),
'#default_value' => !$override_access,
'#access' => $override_access,
'#description' => $this->t('When enabled, the user must confirm the account cancellation via email.'),
];
// Also allow to send account canceled notification mail, if enabled.
$default_notify = $this->config('user.settings')->get('notify.status_canceled');
$form['user_cancel_notify'] = [
'#type' => 'checkbox',
'#title' => $this->t('Notify user when account is canceled'),
'#default_value' => ($override_access ? FALSE : $default_notify),
'#access' => $override_access && $default_notify,
'#description' => $this->t('When enabled, the user will receive an email notification after the account has been canceled.'),
];
// Always provide entity id in the same form key as in the entity edit form.
$form['uid'] = ['#type' => 'value', '#value' => $this->entity->id()];
// Store the user permissions so that it can be altered in hook_form_alter()
// if desired.
$form['access'] = [
'#type' => 'value',
'#value' => $user->hasPermission('administer users'),
];
$form = parent::buildForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Cancel account immediately, if the current user has administrative
// privileges, no confirmation mail shall be sent, and the user does not
// attempt to cancel the own account.
if (!$form_state->isValueEmpty('access') && $form_state->isValueEmpty('user_cancel_confirm') && $this->entity->id() != $this->currentUser()->id()) {
user_cancel($form_state->getValues(), $this->entity->id(), $form_state->getValue('user_cancel_method'));
$form_state->setRedirectUrl($this->entity->urlInfo('collection'));
}
else {
// Store cancelling method and whether to notify the user in
// $this->entity for
// \Drupal\user\Controller\UserController::confirmCancel().
$this->entity->user_cancel_method = $form_state->getValue('user_cancel_method');
$this->entity->user_cancel_notify = $form_state->getValue('user_cancel_notify');
$this->entity->save();
_user_mail_notify('cancel_confirm', $this->entity);
$this->messenger()->addStatus($this->t('A confirmation request to cancel your account has been sent to your email address.'));
$this->logger('user')->notice('Sent account cancellation request to %name %email.', ['%name' => $this->entity->label(), '%email' => '<' . $this->entity->getEmail() . '>']);
$form_state->setRedirect(
'entity.user.canonical',
['user' => $this->entity->id()]
);
}
}
}

View file

@ -0,0 +1,256 @@
<?php
namespace Drupal\user\Form;
use Drupal\Core\Flood\FloodInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\user\UserAuthInterface;
use Drupal\user\UserStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a user login form.
*
* @internal
*/
class UserLoginForm extends FormBase {
/**
* The flood service.
*
* @var \Drupal\Core\Flood\FloodInterface
*/
protected $flood;
/**
* The user storage.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* The user authentication object.
*
* @var \Drupal\user\UserAuthInterface
*/
protected $userAuth;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a new UserLoginForm.
*
* @param \Drupal\Core\Flood\FloodInterface $flood
* The flood service.
* @param \Drupal\user\UserStorageInterface $user_storage
* The user storage.
* @param \Drupal\user\UserAuthInterface $user_auth
* The user authentication object.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/
public function __construct(FloodInterface $flood, UserStorageInterface $user_storage, UserAuthInterface $user_auth, RendererInterface $renderer) {
$this->flood = $flood;
$this->userStorage = $user_storage;
$this->userAuth = $user_auth;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('flood'),
$container->get('entity.manager')->getStorage('user'),
$container->get('user.auth'),
$container->get('renderer')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'user_login_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('system.site');
// Display login form:
$form['name'] = [
'#type' => 'textfield',
'#title' => $this->t('Username'),
'#size' => 60,
'#maxlength' => USERNAME_MAX_LENGTH,
'#description' => $this->t('Enter your @s username.', ['@s' => $config->get('name')]),
'#required' => TRUE,
'#attributes' => [
'autocorrect' => 'none',
'autocapitalize' => 'none',
'spellcheck' => 'false',
'autofocus' => 'autofocus',
],
];
$form['pass'] = [
'#type' => 'password',
'#title' => $this->t('Password'),
'#size' => 60,
'#description' => $this->t('Enter the password that accompanies your username.'),
'#required' => TRUE,
];
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = ['#type' => 'submit', '#value' => $this->t('Log in')];
$form['#validate'][] = '::validateName';
$form['#validate'][] = '::validateAuthentication';
$form['#validate'][] = '::validateFinal';
$this->renderer->addCacheableDependency($form, $config);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$account = $this->userStorage->load($form_state->get('uid'));
// A destination was set, probably on an exception controller,
if (!$this->getRequest()->request->has('destination')) {
$form_state->setRedirect(
'entity.user.canonical',
['user' => $account->id()]
);
}
else {
$this->getRequest()->query->set('destination', $this->getRequest()->request->get('destination'));
}
user_login_finalize($account);
}
/**
* Sets an error if supplied username has been blocked.
*/
public function validateName(array &$form, FormStateInterface $form_state) {
if (!$form_state->isValueEmpty('name') && user_is_blocked($form_state->getValue('name'))) {
// Blocked in user administration.
$form_state->setErrorByName('name', $this->t('The username %name has not been activated or is blocked.', ['%name' => $form_state->getValue('name')]));
}
}
/**
* Checks supplied username/password against local users table.
*
* If successful, $form_state->get('uid') is set to the matching user ID.
*/
public function validateAuthentication(array &$form, FormStateInterface $form_state) {
$password = trim($form_state->getValue('pass'));
$flood_config = $this->config('user.flood');
if (!$form_state->isValueEmpty('name') && strlen($password) > 0) {
// Do not allow any login from the current user's IP if the limit has been
// reached. Default is 50 failed attempts allowed in one hour. This is
// independent of the per-user limit to catch attempts from one IP to log
// in to many different user accounts. We have a reasonably high limit
// since there may be only one apparent IP for all users at an institution.
if (!$this->flood->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
$form_state->set('flood_control_triggered', 'ip');
return;
}
$accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name'), 'status' => 1]);
$account = reset($accounts);
if ($account) {
if ($flood_config->get('uid_only')) {
// Register flood events based on the uid only, so they apply for any
// IP address. This is the most secure option.
$identifier = $account->id();
}
else {
// The default identifier is a combination of uid and IP address. This
// is less secure but more resistant to denial-of-service attacks that
// could lock out all users with public user names.
$identifier = $account->id() . '-' . $this->getRequest()->getClientIP();
}
$form_state->set('flood_control_user_identifier', $identifier);
// Don't allow login if the limit for this user has been reached.
// Default is to allow 5 failed attempts every 6 hours.
if (!$this->flood->isAllowed('user.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
$form_state->set('flood_control_triggered', 'user');
return;
}
}
// We are not limited by flood control, so try to authenticate.
// Store $uid in form state as a flag for self::validateFinal().
$uid = $this->userAuth->authenticate($form_state->getValue('name'), $password);
$form_state->set('uid', $uid);
}
}
/**
* Checks if user was not authenticated, or if too many logins were attempted.
*
* This validation function should always be the last one.
*/
public function validateFinal(array &$form, FormStateInterface $form_state) {
$flood_config = $this->config('user.flood');
if (!$form_state->get('uid')) {
// Always register an IP-based failed login event.
$this->flood->register('user.failed_login_ip', $flood_config->get('ip_window'));
// Register a per-user failed login event.
if ($flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
$this->flood->register('user.failed_login_user', $flood_config->get('user_window'), $flood_control_user_identifier);
}
if ($flood_control_triggered = $form_state->get('flood_control_triggered')) {
if ($flood_control_triggered == 'user') {
$form_state->setErrorByName('name', $this->formatPlural($flood_config->get('user_limit'), 'There has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', 'There have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', [':url' => $this->url('user.pass')]));
}
else {
// We did not find a uid, so the limit is IP-based.
$form_state->setErrorByName('name', $this->t('Too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', [':url' => $this->url('user.pass')]));
}
}
else {
// Use $form_state->getUserInput() in the error message to guarantee
// that we send exactly what the user typed in. The value from
// $form_state->getValue() may have been modified by validation
// handlers that ran earlier than this one.
$user_input = $form_state->getUserInput();
$query = isset($user_input['name']) ? ['name' => $user_input['name']] : [];
$form_state->setErrorByName('name', $this->t('Unrecognized username or password. <a href=":password">Forgot your password?</a>', [':password' => $this->url('user.pass', [], ['query' => $query])]));
$accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name')]);
if (!empty($accounts)) {
$this->logger('user')->notice('Login attempt failed for %user.', ['%user' => $form_state->getValue('name')]);
}
else {
// If the username entered is not a valid user,
// only store the IP address.
$this->logger('user')->notice('Login attempt failed from %ip.', ['%ip' => $this->getRequest()->getClientIp()]);
}
}
}
elseif ($flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
// Clear past failures for this user so as not to block a user who might
// log in and out more than once in an hour.
$this->flood->clear('user.failed_login_user', $flood_control_user_identifier);
}
}
}

View file

@ -0,0 +1,209 @@
<?php
namespace Drupal\user\Form;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\user\UserStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a confirmation form for cancelling multiple user accounts.
*
* @internal
*/
class UserMultipleCancelConfirm extends ConfirmFormBase {
/**
* The temp store factory.
*
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* The user storage.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new UserMultipleCancelConfirm.
*
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The temp store factory.
* @param \Drupal\user\UserStorageInterface $user_storage
* The user storage.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(PrivateTempStoreFactory $temp_store_factory, UserStorageInterface $user_storage, EntityManagerInterface $entity_manager) {
$this->tempStoreFactory = $temp_store_factory;
$this->userStorage = $user_storage;
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('tempstore.private'),
$container->get('entity.manager')->getStorage('user'),
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'user_multiple_cancel_confirm';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to cancel these user accounts?');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('entity.user.collection');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Cancel accounts');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Retrieve the accounts to be canceled from the temp store.
/* @var \Drupal\user\Entity\User[] $accounts */
$accounts = $this->tempStoreFactory
->get('user_user_operations_cancel')
->get($this->currentUser()->id());
if (!$accounts) {
return $this->redirect('entity.user.collection');
}
$root = NULL;
$names = [];
$form['accounts'] = ['#tree' => TRUE];
foreach ($accounts as $account) {
$uid = $account->id();
$names[$uid] = $account->label();
// Prevent user 1 from being canceled.
if ($uid <= 1) {
$root = intval($uid) === 1 ? $account : $root;
continue;
}
$form['accounts'][$uid] = [
'#type' => 'hidden',
'#value' => $uid,
];
}
$form['account']['names'] = [
'#theme' => 'item_list',
'#items' => $names,
];
// Output a notice that user 1 cannot be canceled.
if (isset($root)) {
$redirect = (count($accounts) == 1);
$message = $this->t('The user account %name cannot be canceled.', ['%name' => $root->label()]);
$this->messenger()->addMessage($message, $redirect ? MessengerInterface::TYPE_ERROR : MessengerInterface::TYPE_WARNING);
// If only user 1 was selected, redirect to the overview.
if ($redirect) {
return $this->redirect('entity.user.collection');
}
}
$form['operation'] = ['#type' => 'hidden', '#value' => 'cancel'];
$form['user_cancel_method'] = [
'#type' => 'radios',
'#title' => $this->t('When cancelling these accounts'),
];
$form['user_cancel_method'] += user_cancel_methods();
// Allow to send the account cancellation confirmation mail.
$form['user_cancel_confirm'] = [
'#type' => 'checkbox',
'#title' => $this->t('Require email confirmation to cancel account'),
'#default_value' => FALSE,
'#description' => $this->t('When enabled, the user must confirm the account cancellation via email.'),
];
// Also allow to send account canceled notification mail, if enabled.
$form['user_cancel_notify'] = [
'#type' => 'checkbox',
'#title' => $this->t('Notify user when account is canceled'),
'#default_value' => FALSE,
'#access' => $this->config('user.settings')->get('notify.status_canceled'),
'#description' => $this->t('When enabled, the user will receive an email notification after the account has been canceled.'),
];
$form = parent::buildForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$current_user_id = $this->currentUser()->id();
// Clear out the accounts from the temp store.
$this->tempStoreFactory->get('user_user_operations_cancel')->delete($current_user_id);
if ($form_state->getValue('confirm')) {
foreach ($form_state->getValue('accounts') as $uid => $value) {
// Prevent programmatic form submissions from cancelling user 1.
if ($uid <= 1) {
continue;
}
// Prevent user administrators from deleting themselves without confirmation.
if ($uid == $current_user_id) {
$admin_form_mock = [];
$admin_form_state = $form_state;
$admin_form_state->unsetValue('user_cancel_confirm');
// The $user global is not a complete user entity, so load the full
// entity.
$account = $this->userStorage->load($uid);
$admin_form = $this->entityManager->getFormObject('user', 'cancel');
$admin_form->setEntity($account);
// Calling this directly required to init form object with $account.
$admin_form->buildForm($admin_form_mock, $admin_form_state);
$admin_form->submitForm($admin_form_mock, $admin_form_state);
}
else {
user_cancel($form_state->getValues(), $uid, $form_state->getValue('user_cancel_method'));
}
}
}
$form_state->setRedirect('entity.user.collection');
}
}

View file

@ -0,0 +1,149 @@
<?php
namespace Drupal\user\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\Element\Email;
use Drupal\user\UserStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a user password reset form.
*
* @internal
*/
class UserPasswordForm extends FormBase {
/**
* The user storage.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a UserPasswordForm object.
*
* @param \Drupal\user\UserStorageInterface $user_storage
* The user storage.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(UserStorageInterface $user_storage, LanguageManagerInterface $language_manager) {
$this->userStorage = $user_storage;
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')->getStorage('user'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'user_pass';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['name'] = [
'#type' => 'textfield',
'#title' => $this->t('Username or email address'),
'#size' => 60,
'#maxlength' => max(USERNAME_MAX_LENGTH, Email::EMAIL_MAX_LENGTH),
'#required' => TRUE,
'#attributes' => [
'autocorrect' => 'off',
'autocapitalize' => 'off',
'spellcheck' => 'false',
'autofocus' => 'autofocus',
],
];
// Allow logged in users to request this also.
$user = $this->currentUser();
if ($user->isAuthenticated()) {
$form['name']['#type'] = 'value';
$form['name']['#value'] = $user->getEmail();
$form['mail'] = [
'#prefix' => '<p>',
'#markup' => $this->t('Password reset instructions will be mailed to %email. You must log out to use the password reset link in the email.', ['%email' => $user->getEmail()]),
'#suffix' => '</p>',
];
}
else {
$form['mail'] = [
'#prefix' => '<p>',
'#markup' => $this->t('Password reset instructions will be sent to your registered email address.'),
'#suffix' => '</p>',
];
$form['name']['#default_value'] = $this->getRequest()->query->get('name');
}
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = ['#type' => 'submit', '#value' => $this->t('Submit')];
$form['#cache']['contexts'][] = 'url.query_args';
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$name = trim($form_state->getValue('name'));
// Try to load by email.
$users = $this->userStorage->loadByProperties(['mail' => $name]);
if (empty($users)) {
// No success, try to load by name.
$users = $this->userStorage->loadByProperties(['name' => $name]);
}
$account = reset($users);
if ($account && $account->id()) {
// Blocked accounts cannot request a new password.
if (!$account->isActive()) {
$form_state->setErrorByName('name', $this->t('%name is blocked or has not been activated yet.', ['%name' => $name]));
}
else {
$form_state->setValueForElement(['#parents' => ['account']], $account);
}
}
else {
$form_state->setErrorByName('name', $this->t('%name is not recognized as a username or an email address.', ['%name' => $name]));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$langcode = $this->languageManager->getCurrentLanguage()->getId();
$account = $form_state->getValue('account');
// Mail one time login URL and instructions using current language.
$mail = _user_mail_notify('password_reset', $account, $langcode);
if (!empty($mail)) {
$this->logger('user')->notice('Password reset instructions mailed to %name at %email.', ['%name' => $account->getUsername(), '%email' => $account->getEmail()]);
$this->messenger()->addStatus($this->t('Further instructions have been sent to your email address.'));
}
$form_state->setRedirect('user.page');
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Drupal\user\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Url;
/**
* Form controller for the user password forms.
*
* @internal
*/
class UserPasswordResetForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'user_pass_reset';
}
/**
* {@inheritdoc}
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param \Drupal\Core\Session\AccountInterface $user
* User requesting reset.
* @param string $expiration_date
* Formatted expiration date for the login link, or NULL if the link does
* not expire.
* @param int $timestamp
* The current timestamp.
* @param string $hash
* Login link hash.
*/
public function buildForm(array $form, FormStateInterface $form_state, AccountInterface $user = NULL, $expiration_date = NULL, $timestamp = NULL, $hash = NULL) {
if ($expiration_date) {
$form['message'] = ['#markup' => $this->t('<p>This is a one-time login for %user_name and will expire on %expiration_date.</p><p>Click on this button to log in to the site and change your password.</p>', ['%user_name' => $user->getUsername(), '%expiration_date' => $expiration_date])];
$form['#title'] = $this->t('Reset password');
}
else {
// No expiration for first time login.
$form['message'] = ['#markup' => $this->t('<p>This is a one-time login for %user_name.</p><p>Click on this button to log in to the site and change your password.</p>', ['%user_name' => $user->getUsername()])];
$form['#title'] = $this->t('Set password');
}
$form['help'] = ['#markup' => '<p>' . $this->t('This login can be used only once.') . '</p>'];
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Log in'),
];
$form['#action'] = Url::fromRoute('user.reset.login', [
'uid' => $user->id(),
'timestamp' => $timestamp,
'hash' => $hash,
])->toString();
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// This form works by submitting the hash and timestamp to the user.reset
// route with a 'login' action.
}
}

View file

@ -0,0 +1,222 @@
<?php
namespace Drupal\user\Form;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\PermissionHandlerInterface;
use Drupal\user\RoleStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the user permissions administration form.
*
* @internal
*/
class UserPermissionsForm extends FormBase {
/**
* The permission handler.
*
* @var \Drupal\user\PermissionHandlerInterface
*/
protected $permissionHandler;
/**
* The role storage.
*
* @var \Drupal\user\RoleStorageInterface
*/
protected $roleStorage;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a new UserPermissionsForm.
*
* @param \Drupal\user\PermissionHandlerInterface $permission_handler
* The permission handler.
* @param \Drupal\user\RoleStorageInterface $role_storage
* The role storage.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(PermissionHandlerInterface $permission_handler, RoleStorageInterface $role_storage, ModuleHandlerInterface $module_handler) {
$this->permissionHandler = $permission_handler;
$this->roleStorage = $role_storage;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('user.permissions'),
$container->get('entity.manager')->getStorage('user_role'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'user_admin_permissions';
}
/**
* Gets the roles to display in this form.
*
* @return \Drupal\user\RoleInterface[]
* An array of role objects.
*/
protected function getRoles() {
return $this->roleStorage->loadMultiple();
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$role_names = [];
$role_permissions = [];
$admin_roles = [];
foreach ($this->getRoles() as $role_name => $role) {
// Retrieve role names for columns.
$role_names[$role_name] = $role->label();
// Fetch permissions for the roles.
$role_permissions[$role_name] = $role->getPermissions();
$admin_roles[$role_name] = $role->isAdmin();
}
// Store $role_names for use when saving the data.
$form['role_names'] = [
'#type' => 'value',
'#value' => $role_names,
];
// Render role/permission overview:
$hide_descriptions = system_admin_compact_mode();
$form['system_compact_link'] = [
'#id' => FALSE,
'#type' => 'system_compact_link',
];
$form['permissions'] = [
'#type' => 'table',
'#header' => [$this->t('Permission')],
'#id' => 'permissions',
'#attributes' => ['class' => ['permissions', 'js-permissions']],
'#sticky' => TRUE,
];
foreach ($role_names as $name) {
$form['permissions']['#header'][] = [
'data' => $name,
'class' => ['checkbox'],
];
}
$permissions = $this->permissionHandler->getPermissions();
$permissions_by_provider = [];
foreach ($permissions as $permission_name => $permission) {
$permissions_by_provider[$permission['provider']][$permission_name] = $permission;
}
// Move the access content permission to the Node module if it is installed.
if ($this->moduleHandler->moduleExists('node')) {
// Insert 'access content' before the 'view own unpublished content' key
// in order to maintain the UI even though the permission is provided by
// the system module.
$keys = array_keys($permissions_by_provider['node']);
$offset = (int) array_search('view own unpublished content', $keys);
$permissions_by_provider['node'] = array_merge(
array_slice($permissions_by_provider['node'], 0, $offset),
['access content' => $permissions_by_provider['system']['access content']],
array_slice($permissions_by_provider['node'], $offset)
);
unset($permissions_by_provider['system']['access content']);
}
foreach ($permissions_by_provider as $provider => $permissions) {
// Module name.
$form['permissions'][$provider] = [
[
'#wrapper_attributes' => [
'colspan' => count($role_names) + 1,
'class' => ['module'],
'id' => 'module-' . $provider,
],
'#markup' => $this->moduleHandler->getName($provider),
],
];
foreach ($permissions as $perm => $perm_item) {
// Fill in default values for the permission.
$perm_item += [
'description' => '',
'restrict access' => FALSE,
'warning' => !empty($perm_item['restrict access']) ? $this->t('Warning: Give to trusted roles only; this permission has security implications.') : '',
];
$form['permissions'][$perm]['description'] = [
'#type' => 'inline_template',
'#template' => '<div class="permission"><span class="title">{{ title }}</span>{% if description or warning %}<div class="description">{% if warning %}<em class="permission-warning">{{ warning }}</em> {% endif %}{{ description }}</div>{% endif %}</div>',
'#context' => [
'title' => $perm_item['title'],
],
];
// Show the permission description.
if (!$hide_descriptions) {
$form['permissions'][$perm]['description']['#context']['description'] = $perm_item['description'];
$form['permissions'][$perm]['description']['#context']['warning'] = $perm_item['warning'];
}
foreach ($role_names as $rid => $name) {
$form['permissions'][$perm][$rid] = [
'#title' => $name . ': ' . $perm_item['title'],
'#title_display' => 'invisible',
'#wrapper_attributes' => [
'class' => ['checkbox'],
],
'#type' => 'checkbox',
'#default_value' => in_array($perm, $role_permissions[$rid]) ? 1 : 0,
'#attributes' => ['class' => ['rid-' . $rid, 'js-rid-' . $rid]],
'#parents' => [$rid, $perm],
];
// Show a column of disabled but checked checkboxes.
if ($admin_roles[$rid]) {
$form['permissions'][$perm][$rid]['#disabled'] = TRUE;
$form['permissions'][$perm][$rid]['#default_value'] = TRUE;
}
}
}
}
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save permissions'),
'#button_type' => 'primary',
];
$form['#attached']['library'][] = 'user/drupal.user.permissions';
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
foreach ($form_state->getValue('role_names') as $role_name => $name) {
user_role_change_permissions($role_name, (array) $form_state->getValue($role_name));
}
$this->messenger()->addStatus($this->t('The changes have been saved.'));
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Drupal\user\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\RoleInterface;
/**
* Provides the user permissions administration form for a specific role.
*
* @internal
*/
class UserPermissionsRoleSpecificForm extends UserPermissionsForm {
/**
* The specific role for this form.
*
* @var \Drupal\user\RoleInterface
*/
protected $userRole;
/**
* {@inheritdoc}
*/
protected function getRoles() {
return [$this->userRole->id() => $this->userRole];
}
/**
* Builds the user permissions administration form for a specific role.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param \Drupal\user\RoleInterface|null $user_role
* (optional) The user role used for this form. Defaults to NULL.
*/
public function buildForm(array $form, FormStateInterface $form_state, RoleInterface $user_role = NULL) {
$this->userRole = $user_role;
return parent::buildForm($form, $form_state);
}
}

View file

@ -0,0 +1,5 @@
<?php
// @codingStandardsIgnoreFile
// This file is intentionally empty so that it overwrites when sites are
// updated from a zip/tarball without deleting the /core folder first.
// @todo: remove in 8.3.x

View file

@ -0,0 +1,241 @@
<?php
namespace Drupal\user;
use Drupal\Core\Discovery\YamlDiscovery;
use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
/**
* Provides the available permissions based on yml files.
*
* To define permissions you can use a $module.permissions.yml file. This file
* defines machine names, human-readable names, restrict access (if required for
* security warning), and optionally descriptions for each permission type. The
* machine names are the canonical way to refer to permissions for access
* checking.
*
* If your module needs to define dynamic permissions you can use the
* permission_callbacks key to declare a callable that will return an array of
* permissions, keyed by machine name. Each item in the array can contain the
* same keys as an entry in $module.permissions.yml.
*
* Here is an example from the core filter module (comments have been added):
* @code
* # The key is the permission machine name, and is required.
* administer filters:
* # (required) Human readable name of the permission used in the UI.
* title: 'Administer text formats and filters'
* # (optional) Additional description fo the permission used in the UI.
* description: 'Define how text is handled by combining filters into text formats.'
* # (optional) Boolean, when set to true a warning about site security will
* # be displayed on the Permissions page. Defaults to false.
* restrict access: false
*
* # An array of callables used to generate dynamic permissions.
* permission_callbacks:
* # Each item in the array should return an associative array with one or
* # more permissions following the same keys as the permission defined above.
* - Drupal\filter\FilterPermissions::permissions
* @endcode
*
* @see filter.permissions.yml
* @see \Drupal\filter\FilterPermissions
* @see user_api
*/
class PermissionHandler implements PermissionHandlerInterface {
use StringTranslationTrait;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The YAML discovery class to find all .permissions.yml files.
*
* @var \Drupal\Core\Discovery\YamlDiscovery
*/
protected $yamlDiscovery;
/**
* The controller resolver.
*
* @var \Drupal\Core\Controller\ControllerResolverInterface
*/
protected $controllerResolver;
/**
* Constructs a new PermissionHandler.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation.
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
* The controller resolver.
*/
public function __construct(ModuleHandlerInterface $module_handler, TranslationInterface $string_translation, ControllerResolverInterface $controller_resolver) {
// @todo It would be nice if you could pull all module directories from the
// container.
$this->moduleHandler = $module_handler;
$this->stringTranslation = $string_translation;
$this->controllerResolver = $controller_resolver;
}
/**
* Gets the YAML discovery.
*
* @return \Drupal\Core\Discovery\YamlDiscovery
* The YAML discovery.
*/
protected function getYamlDiscovery() {
if (!isset($this->yamlDiscovery)) {
$this->yamlDiscovery = new YamlDiscovery('permissions', $this->moduleHandler->getModuleDirectories());
}
return $this->yamlDiscovery;
}
/**
* {@inheritdoc}
*/
public function getPermissions() {
$all_permissions = $this->buildPermissionsYaml();
return $this->sortPermissions($all_permissions);
}
/**
* {@inheritdoc}
*/
public function moduleProvidesPermissions($module_name) {
// @TODO Static cache this information, see
// https://www.drupal.org/node/2339487
$permissions = $this->getPermissions();
foreach ($permissions as $permission) {
if ($permission['provider'] == $module_name) {
return TRUE;
}
}
return FALSE;
}
/**
* Builds all permissions provided by .permissions.yml files.
*
* @return array[]
* Each return permission is an array with the following keys:
* - title: The title of the permission.
* - description: The description of the permission, defaults to NULL.
* - provider: The provider of the permission.
*/
protected function buildPermissionsYaml() {
$all_permissions = [];
$all_callback_permissions = [];
foreach ($this->getYamlDiscovery()->findAll() as $provider => $permissions) {
// The top-level 'permissions_callback' is a list of methods in controller
// syntax, see \Drupal\Core\Controller\ControllerResolver. These methods
// should return an array of permissions in the same structure.
if (isset($permissions['permission_callbacks'])) {
foreach ($permissions['permission_callbacks'] as $permission_callback) {
$callback = $this->controllerResolver->getControllerFromDefinition($permission_callback);
if ($callback_permissions = call_user_func($callback)) {
// Add any callback permissions to the array of permissions. Any
// defaults can then get processed below.
foreach ($callback_permissions as $name => $callback_permission) {
if (!is_array($callback_permission)) {
$callback_permission = [
'title' => $callback_permission,
];
}
$callback_permission += [
'description' => NULL,
'provider' => $provider,
];
$all_callback_permissions[$name] = $callback_permission;
}
}
}
unset($permissions['permission_callbacks']);
}
foreach ($permissions as &$permission) {
if (!is_array($permission)) {
$permission = [
'title' => $permission,
];
}
$permission['title'] = $this->t($permission['title']);
$permission['description'] = isset($permission['description']) ? $this->t($permission['description']) : NULL;
$permission['provider'] = !empty($permission['provider']) ? $permission['provider'] : $provider;
}
$all_permissions += $permissions;
}
return $all_permissions + $all_callback_permissions;
}
/**
* Sorts the given permissions by provider name and title.
*
* @param array $all_permissions
* The permissions to be sorted.
*
* @return array[]
* Each return permission is an array with the following keys:
* - title: The title of the permission.
* - description: The description of the permission, defaults to NULL.
* - provider: The provider of the permission.
*/
protected function sortPermissions(array $all_permissions = []) {
// Get a list of all the modules providing permissions and sort by
// display name.
$modules = $this->getModuleNames();
uasort($all_permissions, function (array $permission_a, array $permission_b) use ($modules) {
if ($modules[$permission_a['provider']] == $modules[$permission_b['provider']]) {
return $permission_a['title'] > $permission_b['title'];
}
else {
return $modules[$permission_a['provider']] > $modules[$permission_b['provider']];
}
});
return $all_permissions;
}
/**
* Returns all module names.
*
* @return string[]
* Returns the human readable names of all modules keyed by machine name.
*/
protected function getModuleNames() {
$modules = [];
foreach (array_keys($this->moduleHandler->getModuleList()) as $module) {
$modules[$module] = $this->moduleHandler->getName($module);
}
asort($modules);
return $modules;
}
/**
* Wraps system_rebuild_module_data()
*
* @return \Drupal\Core\Extension\Extension[]
*/
protected function systemRebuildModuleData() {
return system_rebuild_module_data();
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace Drupal\user;
/**
* Defines an interface to list available permissions.
*/
interface PermissionHandlerInterface {
/**
* Gets all available permissions.
*
* @return array
* An array whose keys are permission names and whose corresponding values
* are arrays containing the following key-value pairs:
* - title: The human-readable name of the permission, to be shown on the
* permission administration page. This should be wrapped in the t()
* function so it can be translated.
* - description: (optional) A description of what the permission does. This
* should be wrapped in the t() function so it can be translated.
* - restrict access: (optional) A boolean which can be set to TRUE to
* indicate that site administrators should restrict access to this
* permission to trusted users. This should be used for permissions that
* have inherent security risks across a variety of potential use cases
* (for example, the "administer filters" and "bypass node access"
* permissions provided by Drupal core). When set to TRUE, a standard
* warning message defined in user_admin_permissions() will be displayed
* with the permission on the permission administration page. Defaults
* to FALSE.
* - warning: (optional) A translated warning message to display for this
* permission on the permission administration page. This warning
* overrides the automatic warning generated by 'restrict access' being
* set to TRUE. This should rarely be used, since it is important for all
* permissions to have a clear, consistent security warning that is the
* same across the site. Use the 'description' key instead to provide any
* information that is specific to the permission you are defining.
* - provider: (optional) The provider name of the permission.
*/
public function getPermissions();
/**
* Determines whether a module provides some permissions.
*
* @param string $module_name
* The module name.
*
* @return bool
* Returns TRUE if the module provides some permissions, otherwise FALSE.
*/
public function moduleProvidesPermissions($module_name);
}

View file

@ -0,0 +1,31 @@
<?php
namespace Drupal\user\Plugin\Action;
/**
* Adds a role to a user.
*
* @Action(
* id = "user_add_role_action",
* label = @Translation("Add a role to the selected users"),
* type = "user"
* )
*/
class AddRoleUser extends ChangeUserRoleBase {
/**
* {@inheritdoc}
*/
public function execute($account = NULL) {
$rid = $this->configuration['rid'];
// Skip adding the role to the user if they already have it.
if ($account !== FALSE && !$account->hasRole($rid)) {
// For efficiency manually save the original account before applying
// any changes.
$account->original = clone $account;
$account->addRole($rid);
$account->save();
}
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Drupal\user\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Blocks a user.
*
* @Action(
* id = "user_block_user_action",
* label = @Translation("Block the selected users"),
* type = "user"
* )
*/
class BlockUser extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($account = NULL) {
// Skip blocking user if they are already blocked.
if ($account !== FALSE && $account->isActive()) {
// For efficiency manually save the original account before applying any
// changes.
$account->original = clone $account;
$account->block();
$account->save();
}
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\user\UserInterface $object */
$access = $object->status->access('edit', $account, TRUE)
->andIf($object->access('update', $account, TRUE));
return $return_as_object ? $access : $access->isAllowed();
}
}

View file

@ -0,0 +1,93 @@
<?php
namespace Drupal\user\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Cancels a user account.
*
* @Action(
* id = "user_cancel_user_action",
* label = @Translation("Cancel the selected user accounts"),
* type = "user",
* confirm_form_route_name = "user.multiple_cancel_confirm"
* )
*/
class CancelUser extends ActionBase implements ContainerFactoryPluginInterface {
/**
* The tempstore factory.
*
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a CancelUser object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Session\AccountInterface $current_user
* Current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
$this->currentUser = $current_user;
$this->tempStoreFactory = $temp_store_factory;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('tempstore.private'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function executeMultiple(array $entities) {
$this->tempStoreFactory->get('user_user_operations_cancel')->set($this->currentUser->id(), $entities);
}
/**
* {@inheritdoc}
*/
public function execute($object = NULL) {
$this->executeMultiple([$object]);
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\user\UserInterface $object */
return $object->access('delete', $account, $return_as_object);
}
}

View file

@ -0,0 +1,102 @@
<?php
namespace Drupal\user\Plugin\Action;
use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Entity\DependencyTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\RoleInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a base class for operations to change a user's role.
*/
abstract class ChangeUserRoleBase extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
use DependencyTrait;
/**
* The user role entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeInterface $entity_type) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityType = $entity_type;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getDefinition('user_role')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'rid' => '',
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$roles = user_role_names(TRUE);
unset($roles[RoleInterface::AUTHENTICATED_ID]);
$form['rid'] = [
'#type' => 'radios',
'#title' => t('Role'),
'#options' => $roles,
'#default_value' => $this->configuration['rid'],
'#required' => TRUE,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['rid'] = $form_state->getValue('rid');
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
if (!empty($this->configuration['rid'])) {
$prefix = $this->entityType->getConfigPrefix() . '.';
$this->addDependency('config', $prefix . $this->configuration['rid']);
}
return $this->dependencies;
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\user\UserInterface $object */
$access = $object->access('update', $account, TRUE)
->andIf($object->roles->access('edit', $account, TRUE));
return $return_as_object ? $access : $access->isAllowed();
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace Drupal\user\Plugin\Action;
/**
* Removes a role from a user.
*
* @Action(
* id = "user_remove_role_action",
* label = @Translation("Remove a role from the selected users"),
* type = "user"
* )
*/
class RemoveRoleUser extends ChangeUserRoleBase {
/**
* {@inheritdoc}
*/
public function execute($account = NULL) {
$rid = $this->configuration['rid'];
// Skip removing the role from the user if they already don't have it.
if ($account !== FALSE && $account->hasRole($rid)) {
// For efficiency manually save the original account before applying
// any changes.
$account->original = clone $account;
$account->removeRole($rid);
$account->save();
}
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Drupal\user\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Unblocks a user.
*
* @Action(
* id = "user_unblock_user_action",
* label = @Translation("Unblock the selected users"),
* type = "user"
* )
*/
class UnblockUser extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($account = NULL) {
// Skip unblocking user if they are already unblocked.
if ($account !== FALSE && $account->isBlocked()) {
$account->activate();
$account->save();
}
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\user\UserInterface $object */
$access = $object->status->access('edit', $account, TRUE)
->andIf($object->access('update', $account, TRUE));
return $return_as_object ? $access : $access->isAllowed();
}
}

View file

@ -0,0 +1,161 @@
<?php
namespace Drupal\user\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\RedirectDestinationTrait;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Block\BlockBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'User login' block.
*
* @Block(
* id = "user_login_block",
* admin_label = @Translation("User login"),
* category = @Translation("Forms")
* )
*/
class UserLoginBlock extends BlockBase implements ContainerFactoryPluginInterface {
use RedirectDestinationTrait;
/**
* The route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Constructs a new UserLoginBlock instance.
*
* @param array $configuration
* The plugin configuration, i.e. an array with configuration values keyed
* by configuration option name. The special key 'context' may be used to
* initialize the defined contexts by setting it to an array of context
* values keyed by context names.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->routeMatch = $route_match;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('current_route_match')
);
}
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account) {
$route_name = $this->routeMatch->getRouteName();
if ($account->isAnonymous() && !in_array($route_name, ['user.login', 'user.logout'])) {
return AccessResult::allowed()
->addCacheContexts(['route.name', 'user.roles:anonymous']);
}
return AccessResult::forbidden();
}
/**
* {@inheritdoc}
*/
public function build() {
$form = \Drupal::formBuilder()->getForm('Drupal\user\Form\UserLoginForm');
unset($form['name']['#attributes']['autofocus']);
// When unsetting field descriptions, also unset aria-describedby attributes
// to avoid introducing an accessibility bug.
// @todo Do this automatically in https://www.drupal.org/node/2547063.
unset($form['name']['#description']);
unset($form['name']['#attributes']['aria-describedby']);
unset($form['pass']['#description']);
unset($form['pass']['#attributes']['aria-describedby']);
$form['name']['#size'] = 15;
$form['pass']['#size'] = 15;
// Instead of setting an actual action URL, we set the placeholder, which
// will be replaced at the very last moment. This ensures forms with
// dynamically generated action URLs don't have poor cacheability.
// Use the proper API to generate the placeholder, when we have one. See
// https://www.drupal.org/node/2562341. The placeholder uses a fixed string
// that is
// Crypt::hashBase64('\Drupal\user\Plugin\Block\UserLoginBlock::build');
// This is based on the implementation in
// \Drupal\Core\Form\FormBuilder::prepareForm(), but the user login block
// requires different behavior for the destination query argument.
$placeholder = 'form_action_p_4r8ITd22yaUvXM6SzwrSe9rnQWe48hz9k1Sxto3pBvE';
$form['#attached']['placeholders'][$placeholder] = [
'#lazy_builder' => ['\Drupal\user\Plugin\Block\UserLoginBlock::renderPlaceholderFormAction', []],
];
$form['#action'] = $placeholder;
// Build action links.
$items = [];
if (\Drupal::config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) {
$items['create_account'] = [
'#type' => 'link',
'#title' => $this->t('Create new account'),
'#url' => Url::fromRoute('user.register', [], [
'attributes' => [
'title' => $this->t('Create a new user account.'),
'class' => ['create-account-link'],
],
]),
];
}
$items['request_password'] = [
'#type' => 'link',
'#title' => $this->t('Reset your password'),
'#url' => Url::fromRoute('user.pass', [], [
'attributes' => [
'title' => $this->t('Send password reset instructions via email.'),
'class' => ['request-password-link'],
],
]),
];
return [
'user_login_form' => $form,
'user_links' => [
'#theme' => 'item_list',
'#items' => $items,
],
];
}
/**
* #lazy_builder callback; renders a form action URL including destination.
*
* @return array
* A renderable array representing the form action.
*
* @see \Drupal\Core\Form\FormBuilder::renderPlaceholderFormAction()
*/
public static function renderPlaceholderFormAction() {
return [
'#type' => 'markup',
'#markup' => Url::fromRoute('<current>', [], ['query' => \Drupal::destination()->getAsArray(), 'external' => FALSE])->toString(),
'#cache' => ['contexts' => ['url.path', 'url.query_args']],
];
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Drupal\user\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a 'User Role' condition.
*
* @Condition(
* id = "user_role",
* label = @Translation("User Role"),
* context = {
* "user" = @ContextDefinition("entity:user", label = @Translation("User"))
* }
* )
*/
class UserRole extends ConditionPluginBase {
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['roles'] = [
'#type' => 'checkboxes',
'#title' => $this->t('When the user has the following roles'),
'#default_value' => $this->configuration['roles'],
'#options' => array_map('\Drupal\Component\Utility\Html::escape', user_role_names()),
'#description' => $this->t('If you select no roles, the condition will evaluate to TRUE for all users.'),
];
return parent::buildConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'roles' => [],
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['roles'] = array_filter($form_state->getValue('roles'));
parent::submitConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function summary() {
// Use the role labels. They will be sanitized below.
$roles = array_intersect_key(user_role_names(), $this->configuration['roles']);
if (count($roles) > 1) {
$roles = implode(', ', $roles);
}
else {
$roles = reset($roles);
}
if (!empty($this->configuration['negate'])) {
return $this->t('The user is not a member of @roles', ['@roles' => $roles]);
}
else {
return $this->t('The user is a member of @roles', ['@roles' => $roles]);
}
}
/**
* {@inheritdoc}
*/
public function evaluate() {
if (empty($this->configuration['roles']) && !$this->isNegated()) {
return TRUE;
}
$user = $this->getContextValue('user');
return (bool) array_intersect($this->configuration['roles'], $user->getRoles());
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
// Optimize cache context, if a user cache context is provided, only use
// user.roles, since that's the only part this condition cares about.
$contexts = [];
foreach (parent::getCacheContexts() as $context) {
$contexts[] = $context == 'user' ? 'user.roles' : $context;
}
return $contexts;
}
}

View file

@ -0,0 +1,254 @@
<?php
namespace Drupal\user\Plugin\EntityReferenceSelection;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\RoleInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides specific access control for the user entity type.
*
* @EntityReferenceSelection(
* id = "default:user",
* label = @Translation("User selection"),
* entity_types = {"user"},
* group = "default",
* weight = 1
* )
*/
class UserSelection extends DefaultSelection {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* The user storage.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* Constructs a new UserSelection object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user, Connection $connection) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager, $module_handler, $current_user);
$this->connection = $connection;
$this->userStorage = $entity_manager->getStorage('user');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager'),
$container->get('module_handler'),
$container->get('current_user'),
$container->get('database')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'filter' => [
'type' => '_none',
'role' => NULL,
],
'include_anonymous' => TRUE,
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$configuration = $this->getConfiguration();
$form['include_anonymous'] = [
'#type' => 'checkbox',
'#title' => $this->t('Include the anonymous user.'),
'#default_value' => $configuration['include_anonymous'],
];
// Add user specific filter options.
$form['filter']['type'] = [
'#type' => 'select',
'#title' => $this->t('Filter by'),
'#options' => [
'_none' => $this->t('- None -'),
'role' => $this->t('User role'),
],
'#ajax' => TRUE,
'#limit_validation_errors' => [],
'#default_value' => $configuration['filter']['type'],
];
$form['filter']['settings'] = [
'#type' => 'container',
'#attributes' => ['class' => ['entity_reference-settings']],
'#process' => [['\Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem', 'formProcessMergeParent']],
];
if ($configuration['filter']['type'] == 'role') {
$form['filter']['settings']['role'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Restrict to the selected roles'),
'#required' => TRUE,
'#options' => array_diff_key(user_role_names(TRUE), [RoleInterface::AUTHENTICATED_ID => RoleInterface::AUTHENTICATED_ID]),
'#default_value' => $configuration['filter']['role'],
];
}
$form += parent::buildConfigurationForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
$query = parent::buildEntityQuery($match, $match_operator);
$configuration = $this->getConfiguration();
// Filter out the Anonymous user if the selection handler is configured to
// exclude it.
if (!$configuration['include_anonymous']) {
$query->condition('uid', 0, '<>');
}
// The user entity doesn't have a label column.
if (isset($match)) {
$query->condition('name', $match, $match_operator);
}
// Filter by role.
if (!empty($configuration['filter']['role'])) {
$query->condition('roles', $configuration['filter']['role'], 'IN');
}
// Adding the permission check is sadly insufficient for users: core
// requires us to also know about the concept of 'blocked' and 'active'.
if (!$this->currentUser->hasPermission('administer users')) {
$query->condition('status', 1);
}
return $query;
}
/**
* {@inheritdoc}
*/
public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
$user = parent::createNewEntity($entity_type_id, $bundle, $label, $uid);
// In order to create a referenceable user, it needs to be active.
if (!$this->currentUser->hasPermission('administer users')) {
/** @var \Drupal\user\UserInterface $user */
$user->activate();
}
return $user;
}
/**
* {@inheritdoc}
*/
public function validateReferenceableNewEntities(array $entities) {
$entities = parent::validateReferenceableNewEntities($entities);
// Mirror the conditions checked in buildEntityQuery().
if ($role = $this->getConfiguration()['filter']['role']) {
$entities = array_filter($entities, function ($user) use ($role) {
/** @var \Drupal\user\UserInterface $user */
return !empty(array_intersect($user->getRoles(), $role));
});
}
if (!$this->currentUser->hasPermission('administer users')) {
$entities = array_filter($entities, function ($user) {
/** @var \Drupal\user\UserInterface $user */
return $user->isActive();
});
}
return $entities;
}
/**
* {@inheritdoc}
*/
public function entityQueryAlter(SelectInterface $query) {
parent::entityQueryAlter($query);
// Bail out early if we do not need to match the Anonymous user.
if (!$this->getConfiguration()['include_anonymous']) {
return;
}
if ($this->currentUser->hasPermission('administer users')) {
// In addition, if the user is administrator, we need to make sure to
// match the anonymous user, that doesn't actually have a name in the
// database.
$conditions = &$query->conditions();
foreach ($conditions as $key => $condition) {
if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'users_field_data.name') {
// Remove the condition.
unset($conditions[$key]);
// Re-add the condition and a condition on uid = 0 so that we end up
// with a query in the form:
// WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0)
$or = new Condition('OR');
$or->condition($condition['field'], $condition['value'], $condition['operator']);
// Sadly, the Database layer doesn't allow us to build a condition
// in the form ':placeholder = :placeholder2', because the 'field'
// part of a condition is always escaped.
// As a (cheap) workaround, we separately build a condition with no
// field, and concatenate the field and the condition separately.
$value_part = new Condition('AND');
$value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
$value_part->compile($this->connection, $query);
$or->condition((new Condition('AND'))
->where(str_replace($query->escapeField('anonymous_name'), ':anonymous_name', (string) $value_part), $value_part->arguments() + [':anonymous_name' => \Drupal::config('user.settings')->get('anonymous')])
->condition('base_table.uid', 0)
);
$query->condition($or);
}
}
}
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\user\Plugin\Field\FieldFormatter;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase;
/**
* Plugin implementation of the 'author' formatter.
*
* @FieldFormatter(
* id = "author",
* label = @Translation("Author"),
* description = @Translation("Display the referenced author user entity."),
* field_types = {
* "entity_reference"
* }
* )
*/
class AuthorFormatter extends EntityReferenceFormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) {
/** @var $referenced_user \Drupal\user\UserInterface */
$elements[$delta] = [
'#theme' => 'username',
'#account' => $entity,
'#link_options' => ['attributes' => ['rel' => 'author']],
'#cache' => [
'tags' => $entity->getCacheTags(),
],
];
}
return $elements;
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
return $field_definition->getFieldStorageDefinition()->getSetting('target_type') == 'user';
}
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity) {
return $entity->access('view label', NULL, TRUE);
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace Drupal\user\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'user_name' formatter.
*
* @FieldFormatter(
* id = "user_name",
* label = @Translation("User name"),
* description = @Translation("Display the user or author name."),
* field_types = {
* "string"
* }
* )
*/
class UserNameFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$options = parent::defaultSettings();
$options['link_to_entity'] = TRUE;
return $options;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form['link_to_entity'] = [
'#type' => 'checkbox',
'#title' => $this->t('Link to the user'),
'#default_value' => $this->getSetting('link_to_entity'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
/** @var $user \Drupal\user\UserInterface */
if ($user = $item->getEntity()) {
if ($this->getSetting('link_to_entity')) {
$elements[$delta] = [
'#theme' => 'username',
'#account' => $user,
'#link_options' => ['attributes' => ['rel' => 'user']],
'#cache' => [
'tags' => $user->getCacheTags(),
],
];
}
else {
$elements[$delta] = [
'#markup' => $user->getDisplayName(),
'#cache' => [
'tags' => $user->getCacheTags(),
],
];
}
}
}
return $elements;
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
return $field_definition->getTargetEntityTypeId() === 'user' && $field_definition->getName() === 'name';
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Drupal\user\Plugin\LanguageNegotiation;
use Drupal\language\LanguageNegotiationMethodBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Class for identifying language from the user preferences.
*
* @LanguageNegotiation(
* id = \Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUser::METHOD_ID,
* weight = -4,
* name = @Translation("User"),
* description = @Translation("Follow the user's language preference.")
* )
*/
class LanguageNegotiationUser extends LanguageNegotiationMethodBase {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-user';
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$langcode = NULL;
// User preference (only for authenticated users).
if ($this->languageManager && $this->currentUser->isAuthenticated()) {
$preferred_langcode = $this->currentUser->getPreferredLangcode(FALSE);
$languages = $this->languageManager->getLanguages();
if (!empty($preferred_langcode) && isset($languages[$preferred_langcode])) {
$langcode = $preferred_langcode;
}
}
// No language preference from the user.
return $langcode;
}
}

View file

@ -0,0 +1,153 @@
<?php
namespace Drupal\user\Plugin\LanguageNegotiation;
use Drupal\Core\PathProcessor\PathProcessorManager;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\AdminContext;
use Drupal\Core\Routing\StackedRouteMatchInterface;
use Drupal\language\LanguageNegotiationMethodBase;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
/**
* Identifies admin language from the user preferences.
*
* @LanguageNegotiation(
* id = Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUserAdmin::METHOD_ID,
* types = {Drupal\Core\Language\LanguageInterface::TYPE_INTERFACE},
* weight = -10,
* name = @Translation("Account administration pages"),
* description = @Translation("Account administration pages language setting.")
* )
*/
class LanguageNegotiationUserAdmin extends LanguageNegotiationMethodBase implements ContainerFactoryPluginInterface {
/**
* The language negotiation method id.
*/
const METHOD_ID = 'language-user-admin';
/**
* The admin context.
*
* @var \Drupal\Core\Routing\AdminContext
*/
protected $adminContext;
/**
* The router.
*
* This is only used when called from an event subscriber, before the request
* has been populated with the route info.
*
* @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
*/
protected $router;
/**
* The path processor manager.
*
* @var \Drupal\Core\PathProcessor\PathProcessorManager
*/
protected $pathProcessorManager;
/**
* The stacked route match.
*
* @var \Drupal\Core\Routing\StackedRouteMatchInterface
*/
protected $stackedRouteMatch;
/**
* Constructs a new LanguageNegotiationUserAdmin instance.
*
* @param \Drupal\Core\Routing\AdminContext $admin_context
* The admin context.
* @param \Symfony\Component\Routing\Matcher\UrlMatcherInterface $router
* The router.
* @param \Drupal\Core\PathProcessor\PathProcessorManager $path_processor_manager
* The path processor manager.
* @param \Drupal\Core\Routing\StackedRouteMatchInterface $stacked_route_match
* The stacked route match.
*/
public function __construct(AdminContext $admin_context, UrlMatcherInterface $router, PathProcessorManager $path_processor_manager, StackedRouteMatchInterface $stacked_route_match) {
$this->adminContext = $admin_context;
$this->router = $router;
$this->pathProcessorManager = $path_processor_manager;
$this->stackedRouteMatch = $stacked_route_match;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$container->get('router.admin_context'),
$container->get('router'),
$container->get('path_processor_manager'),
$container->get('current_route_match')
);
}
/**
* {@inheritdoc}
*/
public function getLangcode(Request $request = NULL) {
$langcode = NULL;
// User preference (only for administrators).
if ($this->currentUser->hasPermission('access administration pages') && ($preferred_admin_langcode = $this->currentUser->getPreferredAdminLangcode(FALSE)) && $this->isAdminPath($request)) {
$langcode = $preferred_admin_langcode;
}
// Not an admin, no admin language preference or not on an admin path.
return $langcode;
}
/**
* Checks whether the given path is an administrative one.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*
* @return bool
* TRUE if the path is administrative, FALSE otherwise.
*/
protected function isAdminPath(Request $request) {
$result = FALSE;
if ($request && $this->adminContext) {
// If called from an event subscriber, the request may not have the route
// object yet (it is still being built), so use the router to look up
// based on the path.
$route_match = $this->stackedRouteMatch->getRouteMatchFromRequest($request);
if ($route_match && !$route_object = $route_match->getRouteObject()) {
try {
// Some inbound path processors make changes to the request. Make a
// copy as we're not actually routing the request so we do not want to
// make changes.
$cloned_request = clone $request;
// Process the path as an inbound path. This will remove any language
// prefixes and other path components that inbound processing would
// clear out, so we can attempt to load the route clearly.
$path = $this->pathProcessorManager->processInbound(urldecode(rtrim($cloned_request->getPathInfo(), '/')), $cloned_request);
$attributes = $this->router->match($path);
}
catch (ResourceNotFoundException $e) {
return FALSE;
}
catch (AccessDeniedHttpException $e) {
return FALSE;
}
$route_object = $attributes[RouteObjectInterface::ROUTE_OBJECT];
}
$result = $this->adminContext->isAdminRoute($route_object);
}
return $result;
}
}

View file

@ -0,0 +1,86 @@
<?php
namespace Drupal\user\Plugin\Menu;
use Drupal\Core\Menu\MenuLinkDefault;
use Drupal\Core\Menu\StaticMenuLinkOverridesInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* A menu link that shows "Log in" or "Log out" as appropriate.
*/
class LoginLogoutMenuLink extends MenuLinkDefault {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new LoginLogoutMenuLink.
*
* @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\Menu\StaticMenuLinkOverridesInterface $static_override
* The static override storage.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, StaticMenuLinkOverridesInterface $static_override, AccountInterface $current_user) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $static_override);
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('menu_link.static.overrides'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function getTitle() {
if ($this->currentUser->isAuthenticated()) {
return $this->t('Log out');
}
else {
return $this->t('Log in');
}
}
/**
* {@inheritdoc}
*/
public function getRouteName() {
if ($this->currentUser->isAuthenticated()) {
return 'user.logout';
}
else {
return 'user.login';
}
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return ['user.roles:authenticated'];
}
}

View file

@ -0,0 +1,178 @@
<?php
namespace Drupal\user\Plugin\Search;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\search\Plugin\SearchPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Executes a keyword search for users against the {users} database table.
*
* @SearchPlugin(
* id = "user_search",
* title = @Translation("Users")
* )
*/
class UserSearch extends SearchPluginBase implements AccessibleInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$container->get('database'),
$container->get('entity.manager'),
$container->get('module_handler'),
$container->get('current_user'),
$configuration,
$plugin_id,
$plugin_definition
);
}
/**
* Creates a UserSearch object.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @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.
*/
public function __construct(Connection $database, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user, array $configuration, $plugin_id, $plugin_definition) {
$this->database = $database;
$this->entityManager = $entity_manager;
$this->moduleHandler = $module_handler;
$this->currentUser = $current_user;
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->addCacheTags(['user_list']);
}
/**
* {@inheritdoc}
*/
public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
$result = AccessResult::allowedIf(!empty($account) && $account->hasPermission('access user profiles'))->cachePerPermissions();
return $return_as_object ? $result : $result->isAllowed();
}
/**
* {@inheritdoc}
*/
public function execute() {
$results = [];
if (!$this->isSearchExecutable()) {
return $results;
}
// Process the keywords.
$keys = $this->keywords;
// Escape for LIKE matching.
$keys = $this->database->escapeLike($keys);
// Replace wildcards with MySQL/PostgreSQL wildcards.
$keys = preg_replace('!\*+!', '%', $keys);
// Run the query to find matching users.
$query = $this->database
->select('users_field_data', 'users')
->extend('Drupal\Core\Database\Query\PagerSelectExtender');
$query->fields('users', ['uid']);
$query->condition('default_langcode', 1);
if ($this->currentUser->hasPermission('administer users')) {
// Administrators can also search in the otherwise private email field,
// and they don't need to be restricted to only active users.
$query->fields('users', ['mail']);
$query->condition($query->orConditionGroup()
->condition('name', '%' . $keys . '%', 'LIKE')
->condition('mail', '%' . $keys . '%', 'LIKE')
);
}
else {
// Regular users can only search via usernames, and we do not show them
// blocked accounts.
$query->condition('name', '%' . $keys . '%', 'LIKE')
->condition('status', 1);
}
$uids = $query
->limit(15)
->execute()
->fetchCol();
$accounts = $this->entityManager->getStorage('user')->loadMultiple($uids);
foreach ($accounts as $account) {
$result = [
'title' => $account->getDisplayName(),
'link' => $account->url('canonical', ['absolute' => TRUE]),
];
if ($this->currentUser->hasPermission('administer users')) {
$result['title'] .= ' (' . $account->getEmail() . ')';
}
$this->addCacheableDependency($account);
$results[] = $result;
}
return $results;
}
/**
* {@inheritdoc}
*/
public function getHelp() {
$help = [
'list' => [
'#theme' => 'item_list',
'#items' => [
$this->t('User search looks for user names and partial user names. Example: mar would match usernames mar, delmar, and maryjane.'),
$this->t('You can use * as a wildcard within your keyword. Example: m*r would match user names mar, delmar, and elementary.'),
],
],
];
return $help;
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\user\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Checks if the plain text password is provided for editing a protected field.
*
* @Constraint(
* id = "ProtectedUserField",
* label = @Translation("Password required for protected field change", context = "Validation")
* )
*/
class ProtectedUserFieldConstraint extends Constraint {
/**
* Violation message.
*
* @var string
*/
public $message = "Your current password is missing or incorrect; it's required to change the %name.";
}

View file

@ -0,0 +1,94 @@
<?php
namespace Drupal\user\Plugin\Validation\Constraint;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\user\UserStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the ProtectedUserFieldConstraint constraint.
*/
class ProtectedUserFieldConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
/**
* User storage handler.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* Constructs the object.
*
* @param \Drupal\user\UserStorageInterface $user_storage
* The user storage handler.
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* The current user.
*/
public function __construct(UserStorageInterface $user_storage, AccountProxyInterface $current_user) {
$this->userStorage = $user_storage;
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')->getStorage('user'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function validate($items, Constraint $constraint) {
if (!isset($items)) {
return;
}
/* @var \Drupal\Core\Field\FieldItemListInterface $items */
$field = $items->getFieldDefinition();
/* @var \Drupal\user\UserInterface $account */
$account = $items->getEntity();
if (!isset($account) || !empty($account->_skipProtectedUserFieldConstraint)) {
// Looks like we are validating a field not being part of a user, or the
// constraint should be skipped, so do nothing.
return;
}
// Only validate for existing entities and if this is the current user.
if (!$account->isNew() && $account->id() == $this->currentUser->id()) {
/* @var \Drupal\user\UserInterface $account_unchanged */
$account_unchanged = $this->userStorage
->loadUnchanged($account->id());
$changed = FALSE;
// Special case for the password, it being empty means that the existing
// password should not be changed, ignore empty password fields.
$value = $items->value;
if ($field->getName() != 'pass' || !empty($value)) {
// Compare the values of the field this is being validated on.
$changed = $items->getValue() != $account_unchanged->get($field->getName())->getValue();
}
if ($changed && (!$account->checkExistingPassword($account_unchanged))) {
$this->context->addViolation($constraint->message, ['%name' => $field->getLabel()]);
}
}
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace Drupal\user\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Checks if the user's email address is provided if required.
*
* The user mail field is NOT required if account originally had no mail set
* and the user performing the edit has 'administer users' permission.
* This allows users without email address to be edited and deleted.
*
* @Constraint(
* id = "UserMailRequired",
* label = @Translation("User email required", context = "Validation")
* )
*/
class UserMailRequired extends Constraint {
/**
* Violation message. Use the same message as FormValidator.
*
* Note that the name argument is not sanitized so that translators only have
* one string to translate. The name is sanitized in self::validate().
*
* @var string
*/
public $message = '@name field is required.';
}

View file

@ -0,0 +1,39 @@
<?php
namespace Drupal\user\Plugin\Validation\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Constraint;
/**
* Checks if the user's email address is provided if required.
*
* The user mail field is NOT required if account originally had no mail set
* and the user performing the edit has 'administer users' permission.
* This allows users without email address to be edited and deleted.
*/
class UserMailRequiredValidator extends ConstraintValidator {
/**
* {@inheritdoc}
*/
public function validate($items, Constraint $constraint) {
/** @var \Drupal\Core\Field\FieldItemListInterface $items */
/** @var \Drupal\user\UserInterface $account */
$account = $items->getEntity();
$existing_value = NULL;
if ($account->id()) {
$account_unchanged = \Drupal::entityManager()
->getStorage('user')
->loadUnchanged($account->id());
$existing_value = $account_unchanged->getEmail();
}
$required = !(!$existing_value && \Drupal::currentUser()->hasPermission('administer users'));
if ($required && (!isset($items) || $items->isEmpty())) {
$this->context->addViolation($constraint->message, ['@name' => $account->getFieldDefinition('mail')->getLabel()]);
}
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Drupal\user\Plugin\Validation\Constraint;
use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldConstraint;
/**
* Checks if a user's email address is unique on the site.
*
* @Constraint(
* id = "UserMailUnique",
* label = @Translation("User email unique", context = "Validation")
* )
*/
class UserMailUnique extends UniqueFieldConstraint {
public $message = 'The email address %value is already taken.';
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\user\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Checks if a value is a valid user name.
*
* @Constraint(
* id = "UserName",
* label = @Translation("User name", context = "Validation"),
* )
*/
class UserNameConstraint extends Constraint {
public $emptyMessage = 'You must enter a username.';
public $spaceBeginMessage = 'The username cannot begin with a space.';
public $spaceEndMessage = 'The username cannot end with a space.';
public $multipleSpacesMessage = 'The username cannot contain multiple spaces in a row.';
public $illegalMessage = 'The username contains an illegal character.';
public $tooLongMessage = 'The username %name is too long: it must be %max characters or less.';
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\user\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the UserName constraint.
*/
class UserNameConstraintValidator extends ConstraintValidator {
/**
* {@inheritdoc}
*/
public function validate($items, Constraint $constraint) {
if (!isset($items) || !$items->value) {
$this->context->addViolation($constraint->emptyMessage);
return;
}
$name = $items->first()->value;
if (substr($name, 0, 1) == ' ') {
$this->context->addViolation($constraint->spaceBeginMessage);
}
if (substr($name, -1) == ' ') {
$this->context->addViolation($constraint->spaceEndMessage);
}
if (strpos($name, ' ') !== FALSE) {
$this->context->addViolation($constraint->multipleSpacesMessage);
}
if (preg_match('/[^\x{80}-\x{F7} a-z0-9@+_.\'-]/i', $name)
|| preg_match(
// Non-printable ISO-8859-1 + NBSP
'/[\x{80}-\x{A0}' .
// Soft-hyphen
'\x{AD}' .
// Various space characters
'\x{2000}-\x{200F}' .
// Bidirectional text overrides
'\x{2028}-\x{202F}' .
// Various text hinting characters
'\x{205F}-\x{206F}' .
// Byte order mark
'\x{FEFF}' .
// Full-width latin
'\x{FF01}-\x{FF60}' .
// Replacement characters
'\x{FFF9}-\x{FFFD}' .
// NULL byte and control characters
'\x{0}-\x{1F}]/u',
$name)
) {
$this->context->addViolation($constraint->illegalMessage);
}
if (mb_strlen($name) > USERNAME_MAX_LENGTH) {
$this->context->addViolation($constraint->tooLongMessage, ['%name' => $name, '%max' => USERNAME_MAX_LENGTH]);
}
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Drupal\user\Plugin\Validation\Constraint;
use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldConstraint;
/**
* Checks if a user name is unique on the site.
*
* @Constraint(
* id = "UserNameUnique",
* label = @Translation("User name unique", context = "Validation"),
* )
*/
class UserNameUnique extends UniqueFieldConstraint {
public $message = 'The username %value is already taken.';
}

View file

@ -0,0 +1,68 @@
<?php
namespace Drupal\user\Plugin\migrate;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate\Plugin\Migration;
/**
* Plugin class for user migrations dealing with profile values.
*/
class ProfileValues extends Migration {
/**
* Flag determining whether the process plugin has been initialized.
*
* @var bool
*/
protected $init = FALSE;
/**
* {@inheritdoc}
*/
public function getProcess() {
if (!$this->init) {
$this->init = TRUE;
$definition['source'] = [
'plugin' => 'profile_field',
'ignore_map' => TRUE,
] + $this->source;
$definition['destination']['plugin'] = 'null';
$definition['idMap']['plugin'] = 'null';
try {
$profile_field_migration = $this->migrationPluginManager->createStubMigration($definition);
$migrate_executable = new MigrateExecutable($profile_field_migration);
$source_plugin = $profile_field_migration->getSourcePlugin();
$source_plugin->checkRequirements();
foreach ($source_plugin as $row) {
$name = $row->getSourceProperty('name');
$fid = $row->getSourceProperty('fid');
// The user profile field name can be greater than 32 characters. Use
// the migrated profile field name in the process pipeline.
$configuration =
[
'migration' => 'user_profile_field',
'source_ids' => $fid,
];
$plugin = $this->processPluginManager->createInstance('migration_lookup', $configuration, $profile_field_migration);
$new_value = $plugin->transform($fid, $migrate_executable, $row, 'tmp');
if (isset($new_value[1])) {
// Set the destination to the migrated profile field name.
$this->process[$new_value[1]] = $name;
}
else {
throw new MigrateSkipRowException("Can't migrate source field $name.");
}
}
}
catch (RequirementsException $e) {
// The checkRequirements() call will fail when the profile module does
// not exist on the source site.
}
}
return parent::getProcess();
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace Drupal\user\Plugin\migrate;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate_drupal\Plugin\migrate\FieldMigration;
/**
* Plugin class for Drupal 7 user migrations dealing with fields and profiles.
*/
class User extends FieldMigration {
/**
* {@inheritdoc}
*/
public function getProcess() {
if (!$this->init) {
$this->init = TRUE;
$definition['source'] = [
'entity_type' => 'user',
'ignore_map' => TRUE,
] + $this->source;
$definition['destination']['plugin'] = 'null';
$definition['idMap']['plugin'] = 'null';
if (\Drupal::moduleHandler()->moduleExists('field')) {
$definition['source']['plugin'] = 'd7_field_instance';
$field_migration = $this->migrationPluginManager->createStubMigration($definition);
foreach ($field_migration->getSourcePlugin() as $row) {
$field_name = $row->getSourceProperty('field_name');
$field_type = $row->getSourceProperty('type');
if (empty($field_type)) {
continue;
}
if ($this->fieldPluginManager->hasDefinition($field_type)) {
if (!isset($this->fieldPluginCache[$field_type])) {
$this->fieldPluginCache[$field_type] = $this->fieldPluginManager->createInstance($field_type, [], $this);
}
$info = $row->getSource();
$this->fieldPluginCache[$field_type]
->defineValueProcessPipeline($this, $field_name, $info);
}
else {
if ($this->cckPluginManager->hasDefinition($field_type)) {
if (!isset($this->cckPluginCache[$field_type])) {
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, [], $this);
}
$info = $row->getSource();
$this->cckPluginCache[$field_type]
->processCckFieldValues($this, $field_name, $info);
}
else {
$this->process[$field_name] = $field_name;
}
}
}
}
try {
$definition['source']['plugin'] = 'profile_field';
$profile_migration = $this->migrationPluginManager->createStubMigration($definition);
// Ensure that Profile is enabled in the source DB.
$profile_migration->checkRequirements();
foreach ($profile_migration->getSourcePlugin() as $row) {
$name = $row->getSourceProperty('name');
$this->process[$name] = $name;
}
}
catch (RequirementsException $e) {
// The checkRequirements() call will fail when the profile module does
// not exist on the source site.
}
}
return parent::getProcess();
}
}

View file

@ -0,0 +1,189 @@
<?php
namespace Drupal\user\Plugin\migrate\destination;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EmailItem;
use Drupal\Core\Password\PasswordInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a destination plugin for migrating user entities.
*
* Example:
*
* The example below migrates users and preserves original passwords from a
* source that has passwords as MD5 hashes without salt. The passwords will be
* salted and re-hashed before they are saved to the destination Drupal
* database. The MD5 hash used in the example is a hash of 'password'.
*
* The example uses the EmbeddedDataSource source plugin for the sake of
* simplicity. The mapping between old user_ids and new Drupal uids is saved in
* the migration map table.
* @code
* id: custom_user_migration
* label: Custom user migration
* source:
* plugin: embedded_data
* data_rows:
* -
* user_id: 1
* name: johnsmith
* mail: johnsmith@example.com
* hash: '5f4dcc3b5aa765d61d8327deb882cf99'
* ids:
* user_id:
* type: integer
* process:
* name: name
* mail: mail
* pass: hash
* status:
* plugin: default_value
* default_value: 1
* destination:
* plugin: entity:user
* md5_passwords: true
* @endcode
*
* For configuration options inherited from the parent class, refer to
* \Drupal\migrate\Plugin\migrate\destination\EntityContentBase.
*
* The example above is about migrating an MD5 password hash. For more examples
* on different password hash types and a list of other user properties, refer
* to the handbook documentation:
* @see https://www.drupal.org/docs/8/api/migrate-api/migrate-destination-plugins-examples/migrating-users
*
* @MigrateDestination(
* id = "entity:user"
* )
*/
class EntityUser extends EntityContentBase {
/**
* The password service class.
*
* @var \Drupal\Core\Password\PasswordInterface
*/
protected $password;
/**
* Builds an user entity destination.
*
* @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\migrate\Plugin\MigrationInterface $migration
* The migration.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The storage for this entity type.
* @param array $bundles
* The list of bundles this entity type has.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
* The field type plugin manager service.
* @param \Drupal\Core\Password\PasswordInterface $password
* The password service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, PasswordInterface $password) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager, $field_type_manager);
$this->password = $password;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
$entity_type = static::getEntityTypeId($plugin_id);
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('entity.manager')->getStorage($entity_type),
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
$container->get('entity.manager'),
$container->get('plugin.manager.field.field_type'),
$container->get('password')
);
}
/**
* {@inheritdoc}
* @throws \Drupal\migrate\MigrateException
*/
public function import(Row $row, array $old_destination_id_values = []) {
// Do not overwrite the root account password.
if ($row->getDestinationProperty('uid') == 1) {
$row->removeDestinationProperty('pass');
}
return parent::import($row, $old_destination_id_values);
}
/**
* {@inheritdoc}
*/
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = []) {
// Do not overwrite the root account password.
if ($entity->id() != 1) {
// Set the pre_hashed password so that the PasswordItem field does not hash
// already hashed passwords. If the md5_passwords configuration option is
// set we need to rehash the password and prefix with a U.
// @see \Drupal\Core\Field\Plugin\Field\FieldType\PasswordItem::preSave()
$entity->pass->pre_hashed = TRUE;
if (isset($this->configuration['md5_passwords'])) {
$entity->pass->value = 'U' . $this->password->hash($entity->pass->value);
}
}
return parent::save($entity, $old_destination_id_values);
}
/**
* {@inheritdoc}
*/
protected function processStubRow(Row $row) {
parent::processStubRow($row);
// Email address is not defined as required in the base field definition but
// is effectively required by the UserMailRequired constraint. This means
// that Entity::processStubRow() did not populate it - we do it here.
$field_definitions = $this->entityManager
->getFieldDefinitions($this->storage->getEntityTypeId(),
$this->getKey('bundle'));
$mail = EmailItem::generateSampleValue($field_definitions['mail']);
$row->setDestinationProperty('mail', reset($mail));
// @todo Work-around for https://www.drupal.org/node/2602066.
$name = $row->getDestinationProperty('name');
if (is_array($name)) {
$name = reset($name);
}
if (mb_strlen($name) > USERNAME_MAX_LENGTH) {
$row->setDestinationProperty('name', mb_substr($name, 0, USERNAME_MAX_LENGTH));
}
}
/**
* {@inheritdoc}
*/
public function getHighestId() {
$highest_id = parent::getHighestId();
// Every Drupal site must have a user with UID of 1 and it's normal for
// migrations to overwrite this user.
if ($highest_id === 1) {
return 0;
}
return $highest_id;
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Drupal\user\Plugin\migrate\destination;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\user\UserData as UserDataStorage;
use Drupal\migrate\Row;
use Drupal\migrate\Plugin\migrate\destination\DestinationBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
/**
* @MigrateDestination(
* id = "user_data"
* )
*/
class UserData extends DestinationBase implements ContainerFactoryPluginInterface {
/**
* @var \Drupal\user\UserData
*/
protected $userData;
/**
* Builds an user data entity destination.
*
* @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\migrate\Plugin\MigrationInterface $migration
* The migration.
* @param \Drupal\user\UserData $user_data
* The user data service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, UserDataStorage $user_data) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
$this->userData = $user_data;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('user.data')
);
}
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = []) {
$uid = $row->getDestinationProperty('uid');
$module = $row->getDestinationProperty('module');
$key = $row->getDestinationProperty('key');
$this->userData->set($module, $uid, $key, $row->getDestinationProperty('settings'));
return [$uid, $module, $key];
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['uid']['type'] = 'integer';
$ids['module']['type'] = 'string';
$ids['key']['type'] = 'string';
return $ids;
}
/**
* {@inheritdoc}
*/
public function fields(MigrationInterface $migration = NULL) {
return [
'uid' => 'The user id.',
'module' => 'The module name responsible for the settings.',
'key' => 'The setting key to save under.',
'settings' => 'The settings to save.',
];
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace Drupal\user\Plugin\migrate\process;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Plugin to replace !tokens with [tokens].
*
* @MigrateProcessPlugin(
* id = "convert_tokens",
* handle_multiples = TRUE
* )
*/
class ConvertTokens extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$tokens = [
'!site' => '[site:name]',
'!username' => '[user:name]',
'!mailto' => '[user:mail]',
'!login_uri' => '[site:login-url]',
'!uri_brief' => '[site:url-brief]',
'!edit_uri' => '[user:edit-url]',
'!login_url' => '[user:one-time-login-url]',
'!uri' => '[site:url]',
'!date' => '[date:medium]',
'!password' => '',
];
// Given that our source is a database column that could hold a NULL
// value, sometimes that filters down to here. str_replace() cannot
// handle NULLs as the subject, so we reset to an empty string.
if (is_null($value)) {
$value = '';
}
if (is_string($value)) {
return str_replace(array_keys($tokens), $tokens, $value);
}
else {
throw new MigrateException('Value must be a string.');
}
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\user\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* @MigrateProcessPlugin(
* id = "profile_field_settings"
* )
*/
class ProfileFieldSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($type, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$settings = [];
switch ($type) {
case 'date':
$settings['datetime_type'] = 'date';
break;
}
return $settings;
}
}

View file

@ -0,0 +1,86 @@
<?php
namespace Drupal\user\Plugin\migrate\process;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a process plugin for the user langcode.
*
* @MigrateProcessPlugin(
* id = "user_langcode"
* )
*/
class UserLangcode extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManager
*/
protected $languageManager;
/**
* Constructs a UserLangcode object.
*
* @param array $configuration
* Plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\Core\Language\LanguageManager $language_manager
* The language manager service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, LanguageManager $language_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (!isset($this->configuration['fallback_to_site_default'])) {
$this->configuration['fallback_to_site_default'] = TRUE;
}
// If the user's language is empty, it means the locale module was not
// installed, so the user's langcode should be English and the user's
// preferred_langcode and preferred_admin_langcode should fallback to the
// default language.
if (empty($value)) {
if ($this->configuration['fallback_to_site_default']) {
return $this->languageManager->getDefaultLanguage()->getId();
}
else {
return 'en';
}
}
// If the user's language does not exist, use the default language.
elseif ($this->languageManager->getLanguage($value) === NULL) {
return $this->languageManager->getDefaultLanguage()->getId();
}
// If the langcode is a valid one, just return it.
return $value;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Drupal\user\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Keep the predefined roles for rid 1 and 2.
*
* @MigrateProcessPlugin(
* id = "user_update_8002"
* )
*/
class UserUpdate8002 extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Keep the predefined roles for rid 1 and 2.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$rid = $row->getSourceProperty('rid');
$map = [
1 => 'anonymous',
2 => 'authenticated',
];
return isset($map[$rid]) ? $map[$rid] : $value;
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace Drupal\user\Plugin\migrate\process\d6;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Drupal\Core\Config\Config;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Converts user time zones from time zone offsets to time zone names.
*
* @MigrateProcessPlugin(
* id = "user_update_7002"
* )
*/
class UserUpdate7002 extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* System timezones.
*
* @var array
*/
protected static $timezones;
/**
* Contains the system.theme configuration object.
*
* @var \Drupal\Core\Config\Config
*/
protected $dateConfig;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, Config $date_config) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->dateConfig = $date_config;
if (!isset(static::$timezones)) {
static::$timezones = system_time_zones();
}
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('config.factory')->get('system.date')
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$timezone = NULL;
if ($row->hasSourceProperty('timezone_name')) {
if (isset(static::$timezones[$row->getSourceProperty('timezone_name')])) {
$timezone = $row->getSourceProperty('timezone_name');
}
}
if (!$timezone && $row->hasSourceProperty('event_timezone')) {
if (isset(static::$timezones[$row->getSourceProperty('event_timezone')])) {
$timezone = $row->getSourceProperty('event_timezone');
}
}
if ($timezone === NULL) {
$timezone = $this->dateConfig->get('timezone.default');
}
return $timezone;
}
}

View file

@ -0,0 +1,108 @@
<?php
namespace Drupal\user\Plugin\migrate\source;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
use Drupal\migrate\Row;
/**
* Profile field source from database.
*
* @MigrateSource(
* id = "profile_field",
* source_module = "profile"
* )
*/
class ProfileField extends DrupalSqlBase {
/**
* The source table containing profile field info.
*
* @var string
*/
protected $fieldTable;
/**
* The source table containing the profile values.
*
* @var string
*/
protected $valueTable;
/**
* {@inheritdoc}
*/
public function query() {
if (empty($this->fieldTable) || empty($this->valueTable)) {
if ($this->getModuleSchemaVersion('system') >= 7000) {
$this->fieldTable = 'profile_field';
$this->valueTable = 'profile_value';
}
else {
$this->fieldTable = 'profile_fields';
$this->valueTable = 'profile_values';
}
}
return $this->select($this->fieldTable, 'pf')->fields('pf');
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
if ($row->getSourceProperty('type') == 'selection') {
// Get the current options.
$current_options = preg_split("/[\r\n]+/", $row->getSourceProperty('options'));
// Select the list values from the profile_values table to ensure we get
// them all since they can get out of sync with profile_fields.
$options = $this->select($this->valueTable, 'pv')
->distinct()
->fields('pv', ['value'])
->condition('fid', $row->getSourceProperty('fid'))
->execute()
->fetchCol();
$options = array_merge($current_options, $options);
// array_combine() takes care of any duplicates options.
$row->setSourceProperty('options', array_combine($options, $options));
}
if ($row->getSourceProperty('type') == 'checkbox') {
// D6 profile checkboxes values are always 0 or 1 (with no labels), so we
// need to create two label-less options that will get 0 and 1 for their
// keys.
$row->setSourceProperty('options', [NULL, NULL]);
}
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'fid' => $this->t('Primary Key: Unique profile field ID.'),
'title' => $this->t('Title of the field shown to the end user.'),
'name' => $this->t('Internal name of the field used in the form HTML and URLs.'),
'explanation' => $this->t('Explanation of the field to end users.'),
'category' => $this->t('Profile category that the field will be grouped under.'),
'page' => $this->t("Title of page used for browsing by the field's value"),
'type' => $this->t('Type of form field.'),
'weight' => $this->t('Weight of field in relation to other profile fields.'),
'required' => $this->t('Whether the user is required to enter a value. (0 = no, 1 = yes)'),
'register' => $this->t('Whether the field is visible in the user registration form. (1 = yes, 0 = no)'),
'visibility' => $this->t('The level of visibility for the field. (0 = hidden, 1 = private, 2 = public on profile but not member list pages, 3 = public on profile and list pages)'),
'autocomplete' => $this->t('Whether form auto-completion is enabled. (0 = disabled, 1 = enabled)'),
'options' => $this->t('List of options to be used in a list selection field.'),
];
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['fid']['type'] = 'integer';
return $ids;
}
}

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