mirror of
https://gitlab.unicamp.br/infimecc_drupal11_modules/site_users.git
synced 2026-05-06 03:55:29 -03:00
Compare commits
14 Commits
0ce327026d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0403eaf008 | |||
| 34d0bd9543 | |||
| ef3fe2ab30 | |||
| 0a8620894a | |||
| d72f41de97 | |||
| 39de6a7493 | |||
| aa24bf79f8 | |||
| ca9b8e8d53 | |||
| 7b747e4eb2 | |||
| a500d9ec09 | |||
| 18a7aa81cb | |||
| 85bc63b250 | |||
| f4d6c49312 | |||
| c96268e09d |
12
composer.json
Normal file
12
composer.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "imecc/site_users",
|
||||||
|
"description": "Personalizações de usuários do site IMECC: campos, templates e sub-módulos de microsite, blog e conteúdo de usuário.",
|
||||||
|
"type": "drupal-custom-module",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"drupal/core": "^10.3 || ^11",
|
||||||
|
"imecc/site_tools": "*",
|
||||||
|
"imecc/structural_pages": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ photos:
|
|||||||
max_count: 5
|
max_count: 5
|
||||||
ldap_attribute: 'jpegPhoto'
|
ldap_attribute: 'jpegPhoto'
|
||||||
ldap_sync_enabled: false
|
ldap_sync_enabled: false
|
||||||
|
ldap_min_photo_size: 10240
|
||||||
user_editable_fields:
|
user_editable_fields:
|
||||||
field_user_name: true
|
field_user_name: true
|
||||||
field_user_phone: true
|
field_user_phone: true
|
||||||
@@ -9,3 +10,4 @@ user_editable_fields:
|
|||||||
field_user_social_links: true
|
field_user_social_links: true
|
||||||
field_user_photos: true
|
field_user_photos: true
|
||||||
role_view_modes: { }
|
role_view_modes: { }
|
||||||
|
add_content_links: [ ]
|
||||||
|
|||||||
14
config/optional/pathauto.pattern.user_site_mapping.yml
Normal file
14
config/optional/pathauto.pattern.user_site_mapping.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
module:
|
||||||
|
- pathauto
|
||||||
|
- user
|
||||||
|
id: user_site_mapping
|
||||||
|
label: 'User site mapping'
|
||||||
|
type: 'canonical_entities:user'
|
||||||
|
pattern: 'user/[user:name]'
|
||||||
|
selection_criteria: { }
|
||||||
|
selection_logic: and
|
||||||
|
weight: 0
|
||||||
|
relationships: { }
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
langcode: pt-br
|
|
||||||
status: true
|
|
||||||
dependencies:
|
|
||||||
config:
|
|
||||||
- user.role.posdoutorando
|
|
||||||
module:
|
|
||||||
- user
|
|
||||||
id: user_add_role_action.posdoutorando
|
|
||||||
label: 'Add the Posdoutorando role to the selected user(s)'
|
|
||||||
type: user
|
|
||||||
plugin: user_add_role_action
|
|
||||||
configuration:
|
|
||||||
rid: posdoutorando
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- user.role.postdoc
|
||||||
|
module:
|
||||||
|
- user
|
||||||
|
id: user_add_role_action.postdoc
|
||||||
|
label: 'Add the Pós-Doutorando role to the selected user(s)'
|
||||||
|
type: user
|
||||||
|
plugin: user_add_role_action
|
||||||
|
configuration:
|
||||||
|
rid: postdoc
|
||||||
@@ -2,12 +2,12 @@ langcode: pt-br
|
|||||||
status: true
|
status: true
|
||||||
dependencies:
|
dependencies:
|
||||||
config:
|
config:
|
||||||
- user.role.pesquisador
|
- user.role.researcher
|
||||||
module:
|
module:
|
||||||
- user
|
- user
|
||||||
id: user_add_role_action.pesquisador
|
id: user_add_role_action.researcher
|
||||||
label: 'Add the Pesquisador role to the selected user(s)'
|
label: 'Add the Pesquisador role to the selected user(s)'
|
||||||
type: user
|
type: user
|
||||||
plugin: user_add_role_action
|
plugin: user_add_role_action
|
||||||
configuration:
|
configuration:
|
||||||
rid: pesquisador
|
rid: researcher
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
langcode: pt-br
|
|
||||||
status: true
|
|
||||||
dependencies:
|
|
||||||
config:
|
|
||||||
- user.role.posdoutorando
|
|
||||||
module:
|
|
||||||
- user
|
|
||||||
id: user_remove_role_action.posdoutorando
|
|
||||||
label: 'Remover o papel Posdoutorando dos usuários selecionados'
|
|
||||||
type: user
|
|
||||||
plugin: user_remove_role_action
|
|
||||||
configuration:
|
|
||||||
rid: posdoutorando
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- user.role.postdoc
|
||||||
|
module:
|
||||||
|
- user
|
||||||
|
id: user_remove_role_action.postdoc
|
||||||
|
label: 'Remover o papel Pós-Doutorando dos usuários selecionados'
|
||||||
|
type: user
|
||||||
|
plugin: user_remove_role_action
|
||||||
|
configuration:
|
||||||
|
rid: postdoc
|
||||||
@@ -2,12 +2,12 @@ langcode: pt-br
|
|||||||
status: true
|
status: true
|
||||||
dependencies:
|
dependencies:
|
||||||
config:
|
config:
|
||||||
- user.role.pesquisador
|
- user.role.researcher
|
||||||
module:
|
module:
|
||||||
- user
|
- user
|
||||||
id: user_remove_role_action.pesquisador
|
id: user_remove_role_action.researcher
|
||||||
label: 'Remover o papel Pesquisador dos usuários selecionados'
|
label: 'Remover o papel Pesquisador dos usuários selecionados'
|
||||||
type: user
|
type: user
|
||||||
plugin: user_remove_role_action
|
plugin: user_remove_role_action
|
||||||
configuration:
|
configuration:
|
||||||
rid: pesquisador
|
rid: researcher
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
langcode: pt-br
|
langcode: pt-br
|
||||||
status: true
|
status: true
|
||||||
dependencies: { }
|
dependencies: { }
|
||||||
id: posdoutorando
|
id: postdoc
|
||||||
label: Posdoutorando
|
label: Pós-Doutorando
|
||||||
weight: 7
|
weight: 7
|
||||||
is_admin: false
|
is_admin: false
|
||||||
permissions: { }
|
permissions: { }
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
langcode: pt-br
|
langcode: pt-br
|
||||||
status: true
|
status: true
|
||||||
dependencies: { }
|
dependencies: { }
|
||||||
id: pesquisador
|
id: researcher
|
||||||
label: Pesquisador
|
label: Pesquisador
|
||||||
weight: 8
|
weight: 8
|
||||||
is_admin: false
|
is_admin: false
|
||||||
@@ -15,6 +15,9 @@ site_users.settings:
|
|||||||
ldap_sync_enabled:
|
ldap_sync_enabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
label: 'Enable LDAP photo synchronization'
|
label: 'Enable LDAP photo synchronization'
|
||||||
|
ldap_min_photo_size:
|
||||||
|
type: integer
|
||||||
|
label: 'Minimum LDAP photo size in bytes'
|
||||||
user_editable_fields:
|
user_editable_fields:
|
||||||
type: sequence
|
type: sequence
|
||||||
label: 'User-editable profile fields'
|
label: 'User-editable profile fields'
|
||||||
@@ -30,3 +33,25 @@ site_users.settings:
|
|||||||
sequence:
|
sequence:
|
||||||
type: string
|
type: string
|
||||||
label: 'View mode machine name'
|
label: 'View mode machine name'
|
||||||
|
add_content_links:
|
||||||
|
type: sequence
|
||||||
|
label: 'Add content menu items'
|
||||||
|
sequence:
|
||||||
|
type: mapping
|
||||||
|
label: 'Add content menu item'
|
||||||
|
mapping:
|
||||||
|
label:
|
||||||
|
type: label
|
||||||
|
label: 'Menu item label'
|
||||||
|
route_name:
|
||||||
|
type: string
|
||||||
|
label: 'Route name'
|
||||||
|
route_parameters:
|
||||||
|
type: sequence
|
||||||
|
label: 'Route parameters'
|
||||||
|
sequence:
|
||||||
|
type: string
|
||||||
|
label: 'Parameter value'
|
||||||
|
weight:
|
||||||
|
type: integer
|
||||||
|
label: 'Weight'
|
||||||
|
|||||||
12
modules/site_users_blog/composer.json
Normal file
12
modules/site_users_blog/composer.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "imecc/site_users_blog",
|
||||||
|
"description": "Blog por usuário integrado ao microsite pessoal.",
|
||||||
|
"type": "drupal-module",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"drupal/core": "^11",
|
||||||
|
"imecc/site_users": "*",
|
||||||
|
"imecc/structural_pages": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.field.node.blog_post.field_blog_image
|
||||||
|
- field.field.node.blog_post.field_blog_tags
|
||||||
|
- node.type.blog_post
|
||||||
|
module:
|
||||||
|
- image
|
||||||
|
- taxonomy
|
||||||
|
targetEntityType: node
|
||||||
|
bundle: blog_post
|
||||||
|
mode: default
|
||||||
|
content:
|
||||||
|
title:
|
||||||
|
type: string_textfield
|
||||||
|
weight: -5
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
size: 60
|
||||||
|
placeholder: ''
|
||||||
|
third_party_settings: {}
|
||||||
|
field_blog_image:
|
||||||
|
type: image_image
|
||||||
|
weight: 1
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
progress_indicator: throbber
|
||||||
|
preview_image_style: thumbnail
|
||||||
|
third_party_settings: {}
|
||||||
|
body:
|
||||||
|
type: text_textarea_with_summary
|
||||||
|
weight: 2
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
rows: 20
|
||||||
|
summary_rows: 5
|
||||||
|
placeholder: ''
|
||||||
|
show_summary: false
|
||||||
|
third_party_settings: {}
|
||||||
|
field_blog_tags:
|
||||||
|
type: entity_reference_autocomplete_tags
|
||||||
|
weight: 3
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
match_operator: CONTAINS
|
||||||
|
match_limit: 10
|
||||||
|
size: 60
|
||||||
|
placeholder: ''
|
||||||
|
third_party_settings: {}
|
||||||
|
status:
|
||||||
|
type: boolean_checkbox
|
||||||
|
weight: 10
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
display_label: true
|
||||||
|
third_party_settings: {}
|
||||||
|
hidden:
|
||||||
|
created: true
|
||||||
|
field_site_section: true
|
||||||
|
promote: true
|
||||||
|
sticky: true
|
||||||
|
uid: true
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.field.node.blog_post.field_blog_image
|
||||||
|
- field.field.node.blog_post.field_blog_tags
|
||||||
|
- node.type.blog_post
|
||||||
|
module:
|
||||||
|
- image
|
||||||
|
- taxonomy
|
||||||
|
- user
|
||||||
|
targetEntityType: node
|
||||||
|
bundle: blog_post
|
||||||
|
mode: default
|
||||||
|
content:
|
||||||
|
field_blog_image:
|
||||||
|
type: image
|
||||||
|
label: hidden
|
||||||
|
weight: 0
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
image_style: large
|
||||||
|
image_link: ''
|
||||||
|
image_loading:
|
||||||
|
attribute: lazy
|
||||||
|
third_party_settings: {}
|
||||||
|
body:
|
||||||
|
type: text_default
|
||||||
|
label: hidden
|
||||||
|
weight: 1
|
||||||
|
region: content
|
||||||
|
settings: {}
|
||||||
|
third_party_settings: {}
|
||||||
|
field_blog_tags:
|
||||||
|
type: entity_reference_label
|
||||||
|
label: above
|
||||||
|
weight: 2
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
link: true
|
||||||
|
third_party_settings: {}
|
||||||
|
links:
|
||||||
|
weight: 100
|
||||||
|
region: content
|
||||||
|
settings: {}
|
||||||
|
third_party_settings: {}
|
||||||
|
hidden:
|
||||||
|
field_site_section: true
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.field.node.blog_post.field_blog_image
|
||||||
|
- field.field.node.blog_post.field_blog_tags
|
||||||
|
- node.type.blog_post
|
||||||
|
module:
|
||||||
|
- image
|
||||||
|
- taxonomy
|
||||||
|
- user
|
||||||
|
targetEntityType: node
|
||||||
|
bundle: blog_post
|
||||||
|
mode: teaser
|
||||||
|
content:
|
||||||
|
field_blog_image:
|
||||||
|
type: image
|
||||||
|
label: hidden
|
||||||
|
weight: 0
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
image_style: medium
|
||||||
|
image_link: content
|
||||||
|
image_loading:
|
||||||
|
attribute: lazy
|
||||||
|
third_party_settings: {}
|
||||||
|
body:
|
||||||
|
type: text_summary_or_trimmed
|
||||||
|
label: hidden
|
||||||
|
weight: 1
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
trim_length: 300
|
||||||
|
third_party_settings: {}
|
||||||
|
field_blog_tags:
|
||||||
|
type: entity_reference_label
|
||||||
|
label: hidden
|
||||||
|
weight: 2
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
link: true
|
||||||
|
third_party_settings: {}
|
||||||
|
links:
|
||||||
|
weight: 100
|
||||||
|
region: content
|
||||||
|
settings: {}
|
||||||
|
third_party_settings: {}
|
||||||
|
hidden:
|
||||||
|
field_site_section: true
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.storage.node.field_blog_image
|
||||||
|
- node.type.blog_post
|
||||||
|
entity_type: node
|
||||||
|
field_name: field_blog_image
|
||||||
|
bundle: blog_post
|
||||||
|
label: 'Imagem de destaque'
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
translatable: true
|
||||||
|
default_value: {}
|
||||||
|
default_value_callback: ''
|
||||||
|
settings:
|
||||||
|
file_directory: 'blog/[date:custom:Y-m]'
|
||||||
|
file_extensions: 'png gif jpg jpeg webp'
|
||||||
|
max_filesize: ''
|
||||||
|
max_resolution: ''
|
||||||
|
min_resolution: ''
|
||||||
|
alt_field: true
|
||||||
|
alt_field_required: false
|
||||||
|
title_field: false
|
||||||
|
title_field_required: false
|
||||||
|
default_image:
|
||||||
|
uuid: null
|
||||||
|
alt: ''
|
||||||
|
title: ''
|
||||||
|
width: null
|
||||||
|
height: null
|
||||||
|
handler: default:file
|
||||||
|
handler_settings: {}
|
||||||
|
field_type: image
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.storage.node.field_blog_tags
|
||||||
|
- node.type.blog_post
|
||||||
|
- taxonomy.vocabulary.blog_tags
|
||||||
|
entity_type: node
|
||||||
|
field_name: field_blog_tags
|
||||||
|
bundle: blog_post
|
||||||
|
label: Assuntos
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
translatable: false
|
||||||
|
default_value: {}
|
||||||
|
default_value_callback: ''
|
||||||
|
settings:
|
||||||
|
handler: default:taxonomy_term
|
||||||
|
handler_settings:
|
||||||
|
target_bundles:
|
||||||
|
blog_tags: blog_tags
|
||||||
|
sort:
|
||||||
|
field: name
|
||||||
|
direction: asc
|
||||||
|
auto_create: true
|
||||||
|
auto_create_bundle: blog_tags
|
||||||
|
field_type: entity_reference
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.storage.node.field_site_section
|
||||||
|
- node.type.blog_post
|
||||||
|
module:
|
||||||
|
- dynamic_entity_reference
|
||||||
|
entity_type: node
|
||||||
|
field_name: field_site_section
|
||||||
|
bundle: blog_post
|
||||||
|
label: 'Seção do site'
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
translatable: false
|
||||||
|
default_value: {}
|
||||||
|
default_value_callback: ''
|
||||||
|
settings:
|
||||||
|
handler: default
|
||||||
|
handler_settings: {}
|
||||||
|
field_type: dynamic_entity_reference
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
module:
|
||||||
|
- image
|
||||||
|
entity_type: node
|
||||||
|
field_name: field_blog_image
|
||||||
|
type: image
|
||||||
|
settings:
|
||||||
|
uri_scheme: public
|
||||||
|
default_image:
|
||||||
|
uuid: null
|
||||||
|
alt: ''
|
||||||
|
title: ''
|
||||||
|
width: null
|
||||||
|
height: null
|
||||||
|
column_groups:
|
||||||
|
file:
|
||||||
|
label: File
|
||||||
|
columns:
|
||||||
|
- target_id
|
||||||
|
- width
|
||||||
|
- height
|
||||||
|
translatable: false
|
||||||
|
alt:
|
||||||
|
label: Alt
|
||||||
|
translatable: true
|
||||||
|
title:
|
||||||
|
label: Title
|
||||||
|
translatable: true
|
||||||
|
module: image
|
||||||
|
locked: false
|
||||||
|
cardinality: 1
|
||||||
|
translatable: true
|
||||||
|
indexes: {}
|
||||||
|
persist_with_no_fields: false
|
||||||
|
custom_storage: false
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
module:
|
||||||
|
- taxonomy
|
||||||
|
entity_type: node
|
||||||
|
field_name: field_blog_tags
|
||||||
|
type: entity_reference
|
||||||
|
settings:
|
||||||
|
target_type: taxonomy_term
|
||||||
|
module: core
|
||||||
|
locked: false
|
||||||
|
cardinality: -1
|
||||||
|
translatable: true
|
||||||
|
indexes: {}
|
||||||
|
persist_with_no_fields: false
|
||||||
|
custom_storage: false
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies: {}
|
||||||
|
name: 'Post de blog'
|
||||||
|
type: blog_post
|
||||||
|
description: 'Publicação em um blog de usuário.'
|
||||||
|
help: ''
|
||||||
|
new_revision: true
|
||||||
|
preview_mode: 1
|
||||||
|
display_submitted: true
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
allowed_roles: {}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies: {}
|
||||||
|
name: 'Assuntos (blog)'
|
||||||
|
vid: blog_tags
|
||||||
|
description: 'Categorias e assuntos para posts de blog.'
|
||||||
|
hierarchy: 0
|
||||||
|
weight: 0
|
||||||
235
modules/site_users_blog/config/install/views.view.user_blog.yml
Normal file
235
modules/site_users_blog/config/install/views.view.user_blog.yml
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- node.type.blog_post
|
||||||
|
module:
|
||||||
|
- node
|
||||||
|
- user
|
||||||
|
id: user_blog
|
||||||
|
label: 'Blog do usuário'
|
||||||
|
module: views
|
||||||
|
description: 'Lista posts do blog de um usuário, acessível em /user/{uid}/blog.'
|
||||||
|
tag: ''
|
||||||
|
base_table: node_field_data
|
||||||
|
base_field: nid
|
||||||
|
display:
|
||||||
|
default:
|
||||||
|
id: default
|
||||||
|
display_title: Default
|
||||||
|
display_plugin: default
|
||||||
|
position: 0
|
||||||
|
display_options:
|
||||||
|
title: Blog
|
||||||
|
pager:
|
||||||
|
type: full
|
||||||
|
options:
|
||||||
|
items_per_page: 10
|
||||||
|
offset: 0
|
||||||
|
id: 0
|
||||||
|
total_pages: null
|
||||||
|
tags:
|
||||||
|
previous: '‹ Anterior'
|
||||||
|
next: 'Próximo ›'
|
||||||
|
first: '« Primeira'
|
||||||
|
last: 'Última »'
|
||||||
|
expose:
|
||||||
|
items_per_page: false
|
||||||
|
items_per_page_label: 'Itens por página'
|
||||||
|
items_per_page_options: '5, 10, 25, 50'
|
||||||
|
items_per_page_options_all: false
|
||||||
|
items_per_page_options_all_label: '- Todos -'
|
||||||
|
offset: false
|
||||||
|
offset_label: Deslocamento
|
||||||
|
quantity: 9
|
||||||
|
style:
|
||||||
|
type: default
|
||||||
|
options:
|
||||||
|
grouping: []
|
||||||
|
row_class: ''
|
||||||
|
default_row_class: true
|
||||||
|
row:
|
||||||
|
type: 'entity:node'
|
||||||
|
options:
|
||||||
|
relationship: none
|
||||||
|
view_mode: teaser
|
||||||
|
fields: {}
|
||||||
|
filters:
|
||||||
|
status:
|
||||||
|
id: status
|
||||||
|
table: node_field_data
|
||||||
|
field: status
|
||||||
|
relationship: none
|
||||||
|
group_type: group
|
||||||
|
admin_label: ''
|
||||||
|
entity_type: node
|
||||||
|
entity_field: status
|
||||||
|
plugin_id: boolean
|
||||||
|
operator: '='
|
||||||
|
value: '1'
|
||||||
|
group: 1
|
||||||
|
exposed: false
|
||||||
|
expose:
|
||||||
|
operator: ''
|
||||||
|
is_grouped: false
|
||||||
|
type:
|
||||||
|
id: type
|
||||||
|
table: node_field_data
|
||||||
|
field: type
|
||||||
|
relationship: none
|
||||||
|
group_type: group
|
||||||
|
admin_label: ''
|
||||||
|
entity_type: node
|
||||||
|
entity_field: type
|
||||||
|
plugin_id: bundle
|
||||||
|
operator: in
|
||||||
|
value:
|
||||||
|
blog_post: blog_post
|
||||||
|
group: 1
|
||||||
|
exposed: false
|
||||||
|
expose:
|
||||||
|
operator_id: ''
|
||||||
|
label: ''
|
||||||
|
description: ''
|
||||||
|
use_operator: false
|
||||||
|
operator: ''
|
||||||
|
operator_limit_selection: false
|
||||||
|
operator_list: {}
|
||||||
|
identifier: ''
|
||||||
|
required: false
|
||||||
|
remember: false
|
||||||
|
multiple: false
|
||||||
|
remember_roles:
|
||||||
|
authenticated: authenticated
|
||||||
|
is_grouped: false
|
||||||
|
group_info:
|
||||||
|
label: ''
|
||||||
|
description: ''
|
||||||
|
identifier: ''
|
||||||
|
optional: true
|
||||||
|
widget: select
|
||||||
|
multiple: false
|
||||||
|
remember: false
|
||||||
|
default_group: All
|
||||||
|
default_group_multiple: {}
|
||||||
|
group_items: {}
|
||||||
|
sorts:
|
||||||
|
created:
|
||||||
|
id: created
|
||||||
|
table: node_field_data
|
||||||
|
field: created
|
||||||
|
relationship: none
|
||||||
|
group_type: group
|
||||||
|
admin_label: ''
|
||||||
|
entity_type: node
|
||||||
|
entity_field: created
|
||||||
|
plugin_id: date
|
||||||
|
order: DESC
|
||||||
|
expose:
|
||||||
|
label: ''
|
||||||
|
granularity: second
|
||||||
|
arguments:
|
||||||
|
uid:
|
||||||
|
id: uid
|
||||||
|
table: node_field_data
|
||||||
|
field: uid
|
||||||
|
relationship: none
|
||||||
|
group_type: group
|
||||||
|
admin_label: Usuário
|
||||||
|
entity_type: node
|
||||||
|
entity_field: uid
|
||||||
|
plugin_id: node_uid
|
||||||
|
default_action: 'not found'
|
||||||
|
exception:
|
||||||
|
value: all
|
||||||
|
title_enable: false
|
||||||
|
title: All
|
||||||
|
title_enable: false
|
||||||
|
title: ''
|
||||||
|
default_argument_type: raw
|
||||||
|
default_argument_options:
|
||||||
|
index: 1
|
||||||
|
use_alias: false
|
||||||
|
summary_options:
|
||||||
|
base_path: ''
|
||||||
|
count: true
|
||||||
|
items_per_page: 25
|
||||||
|
override: false
|
||||||
|
summary:
|
||||||
|
sort_order: asc
|
||||||
|
number_of_records: 0
|
||||||
|
format: default_summary
|
||||||
|
specify_validation: true
|
||||||
|
validate:
|
||||||
|
type: 'entity:user'
|
||||||
|
fail: 'not found'
|
||||||
|
validate_options:
|
||||||
|
access: false
|
||||||
|
operation: view
|
||||||
|
multiple: '0'
|
||||||
|
roles: {}
|
||||||
|
break_phrase: false
|
||||||
|
not: false
|
||||||
|
access:
|
||||||
|
type: perm
|
||||||
|
options:
|
||||||
|
perm: 'access content'
|
||||||
|
cache:
|
||||||
|
type: tag
|
||||||
|
options: {}
|
||||||
|
empty:
|
||||||
|
area_text_custom:
|
||||||
|
id: area_text_custom
|
||||||
|
table: views
|
||||||
|
field: area_text_custom
|
||||||
|
relationship: none
|
||||||
|
group_type: group
|
||||||
|
admin_label: ''
|
||||||
|
plugin_id: text_custom
|
||||||
|
content: 'Nenhum post publicado ainda.'
|
||||||
|
tokenize: false
|
||||||
|
empty: true
|
||||||
|
header: {}
|
||||||
|
footer: {}
|
||||||
|
relationships: {}
|
||||||
|
use_ajax: false
|
||||||
|
query:
|
||||||
|
type: views_query
|
||||||
|
options:
|
||||||
|
query_comment: ''
|
||||||
|
disable_sql_rewrite: false
|
||||||
|
distinct: false
|
||||||
|
replica: false
|
||||||
|
query_tags: []
|
||||||
|
exposed_form:
|
||||||
|
type: basic
|
||||||
|
options:
|
||||||
|
submit_button: Aplicar
|
||||||
|
reset_button: false
|
||||||
|
reset_button_label: Resetar
|
||||||
|
exposed_sorts_label: 'Ordenar por'
|
||||||
|
expose_sort_order: true
|
||||||
|
sort_asc_label: Crescente
|
||||||
|
sort_desc_label: Decrescente
|
||||||
|
use_more: false
|
||||||
|
use_more_always: true
|
||||||
|
use_more_text: mais
|
||||||
|
display_extenders: {}
|
||||||
|
rendering_language: '***LANGUAGE_language_interface***'
|
||||||
|
page_user_blog:
|
||||||
|
id: page_user_blog
|
||||||
|
display_title: 'Página do blog'
|
||||||
|
display_plugin: page
|
||||||
|
position: 1
|
||||||
|
display_options:
|
||||||
|
display_extenders: {}
|
||||||
|
path: 'user/%/blog'
|
||||||
|
menu:
|
||||||
|
type: none
|
||||||
|
title: ''
|
||||||
|
description: ''
|
||||||
|
expanded: false
|
||||||
|
parent: ''
|
||||||
|
weight: 0
|
||||||
|
context: '0'
|
||||||
|
menu_name: main
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
langcode: pt-br
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
module:
|
||||||
|
- node
|
||||||
|
- pathauto
|
||||||
|
- user
|
||||||
|
id: blog_post
|
||||||
|
label: 'Blog post'
|
||||||
|
type: 'canonical_entities:node'
|
||||||
|
pattern: 'user/[node:author:uid]/blog/[node:title]'
|
||||||
|
selection_criteria:
|
||||||
|
bundle:
|
||||||
|
id: 'entity_bundle:node'
|
||||||
|
negate: false
|
||||||
|
bundles:
|
||||||
|
blog_post: blog_post
|
||||||
|
selection_logic: and
|
||||||
|
weight: 0
|
||||||
|
relationships: {}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
site_users_blog.settings:
|
||||||
|
type: config_object
|
||||||
|
label: 'Configurações do blog de usuário'
|
||||||
|
mapping:
|
||||||
|
allowed_roles:
|
||||||
|
type: sequence
|
||||||
|
label: 'Roles com permissão para criar posts'
|
||||||
|
sequence:
|
||||||
|
type: boolean
|
||||||
12
modules/site_users_blog/site_users_blog.info.yml
Normal file
12
modules/site_users_blog/site_users_blog.info.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
name: 'Site Users — Blog'
|
||||||
|
type: module
|
||||||
|
description: 'Blog por usuário integrado ao microsite pessoal.'
|
||||||
|
package: 'Site Users'
|
||||||
|
core_version_requirement: ^11
|
||||||
|
dependencies:
|
||||||
|
- drupal:node
|
||||||
|
- drupal:taxonomy
|
||||||
|
- drupal:views
|
||||||
|
- drupal:user
|
||||||
|
- site_users:site_users
|
||||||
|
- structural_pages:structural_pages
|
||||||
79
modules/site_users_blog/site_users_blog.install
Normal file
79
modules/site_users_blog/site_users_blog.install
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Instalação do módulo site_users_blog.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_install().
|
||||||
|
*/
|
||||||
|
function site_users_blog_install(): void {
|
||||||
|
// Adiciona o campo body ao tipo blog_post.
|
||||||
|
$type = \Drupal::entityTypeManager()
|
||||||
|
->getStorage('node_type')
|
||||||
|
->load('blog_post');
|
||||||
|
|
||||||
|
if ($type) {
|
||||||
|
node_add_body_field($type, 'Conteúdo');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adiciona field_site_section ao blog_post e preenche posts existentes.
|
||||||
|
*/
|
||||||
|
function site_users_blog_update_10001(): string {
|
||||||
|
$entity_type_manager = \Drupal::entityTypeManager();
|
||||||
|
|
||||||
|
// Cria a instância do campo se ainda não existir.
|
||||||
|
if (!\Drupal\field\Entity\FieldConfig::loadByName('node', 'blog_post', 'field_site_section')) {
|
||||||
|
\Drupal\field\Entity\FieldConfig::create([
|
||||||
|
'field_name' => 'field_site_section',
|
||||||
|
'entity_type' => 'node',
|
||||||
|
'bundle' => 'blog_post',
|
||||||
|
'label' => 'Seção do site',
|
||||||
|
'required' => FALSE,
|
||||||
|
'translatable' => FALSE,
|
||||||
|
])->save();
|
||||||
|
|
||||||
|
// Oculta o campo no formulário padrão.
|
||||||
|
$form_display = $entity_type_manager
|
||||||
|
->getStorage('entity_form_display')
|
||||||
|
->load('node.blog_post.default');
|
||||||
|
if ($form_display) {
|
||||||
|
$form_display->removeComponent('field_site_section')->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Oculta o campo nas exibições padrão e teaser.
|
||||||
|
foreach (['default', 'teaser'] as $mode) {
|
||||||
|
$view_display = $entity_type_manager
|
||||||
|
->getStorage('entity_view_display')
|
||||||
|
->load('node.blog_post.' . $mode);
|
||||||
|
if ($view_display) {
|
||||||
|
$view_display->removeComponent('field_site_section')->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preenche posts existentes que ainda não têm field_site_section.
|
||||||
|
$nids = $entity_type_manager->getStorage('node')->getQuery()
|
||||||
|
->condition('type', 'blog_post')
|
||||||
|
->notExists('field_site_section')
|
||||||
|
->accessCheck(FALSE)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
foreach ($nids as $nid) {
|
||||||
|
$node = $entity_type_manager->getStorage('node')->load($nid);
|
||||||
|
if ($node) {
|
||||||
|
$node->set('field_site_section', [
|
||||||
|
'target_type' => 'user',
|
||||||
|
'target_id' => $node->getOwnerId(),
|
||||||
|
]);
|
||||||
|
$node->save();
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "field_site_section adicionado ao blog_post; $count posts existentes atualizados.";
|
||||||
|
}
|
||||||
15
modules/site_users_blog/site_users_blog.links.menu.yml
Normal file
15
modules/site_users_blog/site_users_blog.links.menu.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
site_users_blog.add_blog_post:
|
||||||
|
title: 'Post de blog'
|
||||||
|
route_name: node.add
|
||||||
|
route_parameters:
|
||||||
|
node_type: blog_post
|
||||||
|
menu_name: account
|
||||||
|
parent: site_users.add_content
|
||||||
|
weight: 10
|
||||||
|
|
||||||
|
site_users_blog.settings:
|
||||||
|
title: 'Blog de usuário'
|
||||||
|
description: 'Configura roles com permissão para publicar posts de blog.'
|
||||||
|
route_name: site_users_blog.settings
|
||||||
|
parent: site_users_microsite.settings
|
||||||
|
weight: 10
|
||||||
102
modules/site_users_blog/site_users_blog.module
Normal file
102
modules/site_users_blog/site_users_blog.module
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Blog pessoal por usuário.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Drupal\Core\Access\AccessResult;
|
||||||
|
use Drupal\Core\Access\AccessResultInterface;
|
||||||
|
use Drupal\Core\Session\AccountInterface;
|
||||||
|
use Drupal\node\NodeInterface;
|
||||||
|
use Drupal\user\UserInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_node_presave().
|
||||||
|
*
|
||||||
|
* Preenche field_site_section com o autor do post para que o bloco de
|
||||||
|
* navegação do structural_pages encontre o usuário ancestral corretamente.
|
||||||
|
*/
|
||||||
|
function site_users_blog_node_presave(NodeInterface $node): void {
|
||||||
|
if ($node->bundle() !== 'blog_post') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$node->hasField('field_site_section') || !$node->get('field_site_section')->isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$node->set('field_site_section', [
|
||||||
|
'target_type' => 'user',
|
||||||
|
'target_id' => $node->getOwnerId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_node_access().
|
||||||
|
*
|
||||||
|
* Restringe a criação de blog_post às roles configuradas pelo administrador.
|
||||||
|
* Se nenhuma role estiver configurada, qualquer usuário autenticado pode criar.
|
||||||
|
*/
|
||||||
|
function site_users_blog_node_access(NodeInterface $node, string $op, AccountInterface $account): AccessResultInterface {
|
||||||
|
if ($node->bundle() !== 'blog_post' || $op !== 'create') {
|
||||||
|
return AccessResult::neutral();
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowed = \Drupal::config('site_users_blog.settings')->get('allowed_roles') ?? [];
|
||||||
|
$allowed = array_filter($allowed);
|
||||||
|
|
||||||
|
$result = AccessResult::neutral()
|
||||||
|
->addCacheTags(['config:site_users_blog.settings'])
|
||||||
|
->cachePerUser();
|
||||||
|
|
||||||
|
if (empty($allowed)) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_keys($allowed) as $role) {
|
||||||
|
if ($account->hasRole($role)) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AccessResult::forbidden()
|
||||||
|
->addCacheTags(['config:site_users_blog.settings'])
|
||||||
|
->cachePerUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_preprocess_structural_pages_menu().
|
||||||
|
*
|
||||||
|
* Adiciona o item "Blog" ao menu do microsite quando o usuário tem pelo menos
|
||||||
|
* um post publicado.
|
||||||
|
*/
|
||||||
|
function site_users_blog_preprocess_structural_pages_menu(array &$variables): void {
|
||||||
|
$user = site_users_get_microsite_user();
|
||||||
|
if ($user === NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nids = \Drupal::entityTypeManager()->getStorage('node')
|
||||||
|
->getQuery()
|
||||||
|
->condition('uid', $user->id())
|
||||||
|
->condition('type', 'blog_post')
|
||||||
|
->condition('status', 1)
|
||||||
|
->accessCheck(FALSE)
|
||||||
|
->range(0, 1)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
if (empty($nids)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = \Drupal\Core\Url::fromRoute('view.user_blog.page_user_blog', [
|
||||||
|
'arg_0' => $user->id(),
|
||||||
|
])->toString();
|
||||||
|
|
||||||
|
$variables['tree'][] = [
|
||||||
|
'id' => 0,
|
||||||
|
'title' => t('Blog'),
|
||||||
|
'url' => $url,
|
||||||
|
'is_redirect' => FALSE,
|
||||||
|
'children' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
3
modules/site_users_blog/site_users_blog.permissions.yml
Normal file
3
modules/site_users_blog/site_users_blog.permissions.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
administer site_users_blog settings:
|
||||||
|
title: 'Administrar configurações do blog de usuário'
|
||||||
|
restrict access: true
|
||||||
7
modules/site_users_blog/site_users_blog.routing.yml
Normal file
7
modules/site_users_blog/site_users_blog.routing.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
site_users_blog.settings:
|
||||||
|
path: '/admin/config/local-modules/site-users/blog'
|
||||||
|
defaults:
|
||||||
|
_form: '\Drupal\site_users_blog\Form\BlogSettingsForm'
|
||||||
|
_title: 'Blog de usuário'
|
||||||
|
requirements:
|
||||||
|
_permission: 'administer site_users_blog settings'
|
||||||
63
modules/site_users_blog/src/Form/BlogSettingsForm.php
Normal file
63
modules/site_users_blog/src/Form/BlogSettingsForm.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\site_users_blog\Form;
|
||||||
|
|
||||||
|
use Drupal\Core\Form\ConfigFormBase;
|
||||||
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
|
use Drupal\user\RoleInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formulário de configuração do blog de usuário.
|
||||||
|
*/
|
||||||
|
class BlogSettingsForm extends ConfigFormBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function getEditableConfigNames(): array {
|
||||||
|
return ['site_users_blog.settings'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getFormId(): string {
|
||||||
|
return 'site_users_blog_settings_form';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function buildForm(array $form, FormStateInterface $form_state): array {
|
||||||
|
$config = $this->config('site_users_blog.settings');
|
||||||
|
$allowed = $config->get('allowed_roles') ?? [];
|
||||||
|
|
||||||
|
$roles = user_roles(TRUE);
|
||||||
|
unset($roles[RoleInterface::AUTHENTICATED_ID]);
|
||||||
|
|
||||||
|
$options = array_map(fn($role) => $role->label(), $roles);
|
||||||
|
|
||||||
|
$form['allowed_roles'] = [
|
||||||
|
'#type' => 'checkboxes',
|
||||||
|
'#title' => $this->t('Roles que podem criar posts de blog'),
|
||||||
|
'#description' => $this->t('Nenhuma seleção significa que qualquer usuário autenticado pode criar posts.'),
|
||||||
|
'#options' => $options,
|
||||||
|
'#default_value' => array_keys(array_filter($allowed)),
|
||||||
|
];
|
||||||
|
|
||||||
|
return parent::buildForm($form, $form_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function submitForm(array &$form, FormStateInterface $form_state): void {
|
||||||
|
$selected = array_filter($form_state->getValue('allowed_roles'));
|
||||||
|
$this->config('site_users_blog.settings')
|
||||||
|
->set('allowed_roles', $selected)
|
||||||
|
->save();
|
||||||
|
|
||||||
|
parent::submitForm($form, $form_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\site_users_blog\Plugin\ParentEntityHandler;
|
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityInterface;
|
||||||
|
use Drupal\Core\Routing\RouteMatchInterface;
|
||||||
|
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||||
|
use Drupal\node\NodeInterface;
|
||||||
|
use Drupal\structural_pages\Attribute\ParentEntityHandler;
|
||||||
|
use Drupal\structural_pages\ParentEntityHandler\ParentEntityHandlerBase;
|
||||||
|
use Drupal\user\UserInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determina o usuário pai a partir de rotas de blog (post ou listagem).
|
||||||
|
*
|
||||||
|
* Cobre:
|
||||||
|
* - entity.node.canonical de blog_post → retorna o autor
|
||||||
|
* - view.user_blog.page_user_blog → retorna o usuário cujo UID é arg_0
|
||||||
|
*/
|
||||||
|
#[ParentEntityHandler(
|
||||||
|
id: 'blog_user',
|
||||||
|
label: new TranslatableMarkup('Blog do usuário'),
|
||||||
|
entity_type_id: 'user',
|
||||||
|
clears_site_section: FALSE,
|
||||||
|
sort_field: 'name',
|
||||||
|
weight: 20,
|
||||||
|
)]
|
||||||
|
class BlogUserHandler extends ParentEntityHandlerBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getEntityFromRoute(RouteMatchInterface $route_match): ?EntityInterface {
|
||||||
|
$route_name = $route_match->getRouteName() ?? '';
|
||||||
|
|
||||||
|
if ($route_name === 'entity.node.canonical') {
|
||||||
|
$node = $route_match->getParameter('node');
|
||||||
|
if ($node instanceof NodeInterface && $node->bundle() === 'blog_post') {
|
||||||
|
return $node->getOwner();
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($route_name === 'view.user_blog.page_user_blog') {
|
||||||
|
$uid = $route_match->getParameter('arg_0');
|
||||||
|
if (is_numeric($uid)) {
|
||||||
|
return $this->entityTypeManager->getStorage('user')->load($uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function handlesEntity(EntityInterface $entity): bool {
|
||||||
|
return $entity instanceof UserInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
12
modules/site_users_microsite/composer.json
Normal file
12
modules/site_users_microsite/composer.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "imecc/site_users_microsite",
|
||||||
|
"description": "Micro-site pessoal para usuários do site, com tema próprio e conteúdo por papel.",
|
||||||
|
"type": "drupal-module",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"drupal/core": "^10.3 || ^11",
|
||||||
|
"imecc/site_users": "*",
|
||||||
|
"imecc/structural_pages": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ name: 'Site Users Microsite'
|
|||||||
type: module
|
type: module
|
||||||
description: 'Micro-site pessoal para usuários do site, com tema próprio e conteúdo por papel.'
|
description: 'Micro-site pessoal para usuários do site, com tema próprio e conteúdo por papel.'
|
||||||
core_version_requirement: ^10 || ^11
|
core_version_requirement: ^10 || ^11
|
||||||
package: Custom
|
package: 'Site Users'
|
||||||
dependencies:
|
dependencies:
|
||||||
- drupal:user
|
- drupal:user
|
||||||
- drupal:node
|
- drupal:node
|
||||||
|
|||||||
@@ -4,3 +4,9 @@ site_users_microsite.settings:
|
|||||||
route_name: site_users_microsite.settings
|
route_name: site_users_microsite.settings
|
||||||
parent: site_users.settings
|
parent: site_users.settings
|
||||||
weight: 10
|
weight: 10
|
||||||
|
|
||||||
|
site_users_microsite.my_config:
|
||||||
|
title: 'Configuração'
|
||||||
|
route_name: site_users_microsite.my_config
|
||||||
|
menu_name: account
|
||||||
|
weight: 5
|
||||||
|
|||||||
@@ -18,17 +18,106 @@ function site_users_microsite_theme(): array {
|
|||||||
'photo_alt' => '',
|
'photo_alt' => '',
|
||||||
'name' => NULL,
|
'name' => NULL,
|
||||||
'bio' => NULL,
|
'bio' => NULL,
|
||||||
'phone' => NULL,
|
|
||||||
'email' => NULL,
|
'email' => NULL,
|
||||||
'homepage' => NULL,
|
'homepage' => NULL,
|
||||||
'lattes_id' => NULL,
|
'lattes_id' => NULL,
|
||||||
'orcid_id' => NULL,
|
'orcid_id' => NULL,
|
||||||
'mathscinet_id' => NULL,
|
'mathscinet_id' => NULL,
|
||||||
|
'department' => NULL,
|
||||||
|
'department_url' => NULL,
|
||||||
|
'work_phone' => NULL,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_preprocess_structural_pages_menu().
|
||||||
|
*
|
||||||
|
* Remove da árvore de navegação o nó configurado como homepage do microsite,
|
||||||
|
* já que esse conteúdo é exibido diretamente em /user/{id}.
|
||||||
|
*/
|
||||||
|
function site_users_microsite_preprocess_structural_pages_menu(array &$variables): void {
|
||||||
|
$user = site_users_get_microsite_user();
|
||||||
|
if ($user === NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$homepage_nid = \Drupal::service('user.data')
|
||||||
|
->get('site_users_microsite', $user->id(), 'homepage_nid');
|
||||||
|
if (!$homepage_nid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_site_users_microsite_remove_homepage_from_tree($variables['tree'], (int) $homepage_nid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove recursivamente o nó homepage da árvore do structural_pages_menu.
|
||||||
|
*/
|
||||||
|
function _site_users_microsite_remove_homepage_from_tree(array &$items, int $homepage_nid): void {
|
||||||
|
foreach ($items as $key => $item) {
|
||||||
|
if ((int) ($item['id'] ?? 0) === $homepage_nid) {
|
||||||
|
unset($items[$key]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!empty($item['children'])) {
|
||||||
|
_site_users_microsite_remove_homepage_from_tree($items[$key]['children'], $homepage_nid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_preprocess_block().
|
||||||
|
*
|
||||||
|
* Substitui o título do bloco "Título da Página" pelo título do nó homepage
|
||||||
|
* quando o usuário tiver ativado essa opção nas configurações do microsite.
|
||||||
|
* Sem nó homepage configurado (ou no fallback), mantém o comportamento padrão.
|
||||||
|
*/
|
||||||
|
function site_users_microsite_preprocess_block(&$variables): void {
|
||||||
|
if ($variables['plugin_id'] !== 'page_title_block') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$route_match = \Drupal::routeMatch();
|
||||||
|
$route_name = $route_match->getRouteName() ?? '';
|
||||||
|
|
||||||
|
// Rotas com título próprio não devem ser sobrescritas.
|
||||||
|
$excluded = [
|
||||||
|
'site_users_microsite.profile',
|
||||||
|
'site_users_microsite.settings',
|
||||||
|
'site_users_microsite.user_config',
|
||||||
|
];
|
||||||
|
|
||||||
|
$is_microsite = $route_name === 'entity.user.canonical'
|
||||||
|
|| str_starts_with($route_name, 'site_users_microsite.');
|
||||||
|
|
||||||
|
if (!$is_microsite || in_array($route_name, $excluded)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $route_match->getParameter('user');
|
||||||
|
if (!($user instanceof UserInterface)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userData = \Drupal::service('user.data');
|
||||||
|
|
||||||
|
if (!$userData->get('site_users_microsite', $user->id(), 'use_homepage_title')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$homepage_nid = $userData->get('site_users_microsite', $user->id(), 'homepage_nid');
|
||||||
|
if (!$homepage_nid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$node = \Drupal::entityTypeManager()->getStorage('node')->load($homepage_nid);
|
||||||
|
if ($node && $node->isPublished() && $node->access('view')) {
|
||||||
|
$variables['content']['#title'] = $node->label();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_preprocess_page().
|
* Implements hook_preprocess_page().
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ site_users_microsite.profile:
|
|||||||
path: '/user/{user}/profile'
|
path: '/user/{user}/profile'
|
||||||
defaults:
|
defaults:
|
||||||
_controller: '\Drupal\site_users_microsite\Controller\MicrositeHomeController::profile'
|
_controller: '\Drupal\site_users_microsite\Controller\MicrositeHomeController::profile'
|
||||||
_title_callback: '\Drupal\site_users_microsite\Controller\MicrositeHomeController::title'
|
_title_callback: '\Drupal\site_users_microsite\Controller\MicrositeHomeController::profileTitle'
|
||||||
requirements:
|
requirements:
|
||||||
_entity_access: 'user.view'
|
_entity_access: 'user.view'
|
||||||
user: \d+
|
user: \d+
|
||||||
@@ -24,6 +24,27 @@ site_users_microsite.content:
|
|||||||
user:
|
user:
|
||||||
type: entity:user
|
type: entity:user
|
||||||
|
|
||||||
|
site_users_microsite.user_config:
|
||||||
|
path: '/user/{user}/config'
|
||||||
|
defaults:
|
||||||
|
_form: '\Drupal\site_users_microsite\Form\MicrositeUserConfigForm'
|
||||||
|
_title: 'Microsite settings'
|
||||||
|
requirements:
|
||||||
|
_entity_access: 'user.update'
|
||||||
|
user: \d+
|
||||||
|
options:
|
||||||
|
parameters:
|
||||||
|
user:
|
||||||
|
type: entity:user
|
||||||
|
|
||||||
|
site_users_microsite.my_config:
|
||||||
|
path: '/user/microsite/config'
|
||||||
|
defaults:
|
||||||
|
_controller: '\Drupal\site_users_microsite\Controller\MicrositeHomeController::redirectToMyConfig'
|
||||||
|
_title: 'Microsite settings'
|
||||||
|
requirements:
|
||||||
|
_user_is_logged_in: 'TRUE'
|
||||||
|
|
||||||
site_users_microsite.settings:
|
site_users_microsite.settings:
|
||||||
path: '/admin/config/local-modules/site-users/microsite'
|
path: '/admin/config/local-modules/site-users/microsite'
|
||||||
defaults:
|
defaults:
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
services:
|
services:
|
||||||
|
site_users_microsite.path_processor:
|
||||||
|
class: Drupal\site_users_microsite\PathProcessor\MicrositeSubpagePathProcessor
|
||||||
|
arguments: ['@path_alias.manager', '@language_manager']
|
||||||
|
tags:
|
||||||
|
- { name: path_processor_inbound, priority: 200 }
|
||||||
|
- { name: path_processor_outbound, priority: 200 }
|
||||||
|
|
||||||
site_users_microsite.theme_negotiator:
|
site_users_microsite.theme_negotiator:
|
||||||
class: Drupal\site_users_microsite\Theme\MicrositeThemeNegotiator
|
class: Drupal\site_users_microsite\Theme\MicrositeThemeNegotiator
|
||||||
|
arguments: ['@path_alias.manager', '@path.current']
|
||||||
tags:
|
tags:
|
||||||
- { name: theme_negotiator, priority: 100 }
|
- { name: theme_negotiator, priority: 100 }
|
||||||
|
|
||||||
|
|||||||
@@ -4,25 +4,53 @@ namespace Drupal\site_users_microsite\Controller;
|
|||||||
|
|
||||||
use Drupal\Core\Controller\ControllerBase;
|
use Drupal\Core\Controller\ControllerBase;
|
||||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||||
|
use Drupal\user\UserDataInterface;
|
||||||
use Drupal\user\UserInterface;
|
use Drupal\user\UserInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller para a página inicial do micro-site do usuário.
|
* Controller para a página inicial do micro-site do usuário.
|
||||||
*
|
|
||||||
* Carrega e exibe o nó do tipo content_page cujo autor é o usuário
|
|
||||||
* da rota /user/{user}/home.
|
|
||||||
*/
|
*/
|
||||||
class MicrositeHomeController extends ControllerBase {
|
class MicrositeHomeController extends ControllerBase {
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected UserDataInterface $userData,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function create(ContainerInterface $container): static {
|
||||||
|
return new static(
|
||||||
|
$container->get('user.data'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Página inicial do micro-site.
|
* Página inicial do micro-site.
|
||||||
|
*
|
||||||
|
* Exibe o nó configurado pelo usuário ou, como fallback, o primeiro nó
|
||||||
|
* do tipo content_page publicado pelo usuário.
|
||||||
*/
|
*/
|
||||||
public function home(UserInterface $user): array {
|
public function home(UserInterface $user): array {
|
||||||
$cache = [
|
$cache = [
|
||||||
'tags' => ['node_list:content_page', 'user:' . $user->id()],
|
'tags' => [
|
||||||
|
'node_list:content_page',
|
||||||
|
'user:' . $user->id(),
|
||||||
|
'site_users_microsite_config:' . $user->id(),
|
||||||
|
],
|
||||||
'contexts' => ['route'],
|
'contexts' => ['route'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$homepage_nid = $this->userData->get('site_users_microsite', $user->id(), 'homepage_nid');
|
||||||
|
|
||||||
|
if ($homepage_nid) {
|
||||||
|
$node = $this->entityTypeManager()->getStorage('node')->load($homepage_nid);
|
||||||
|
if ($node && $node->isPublished() && $node->access('view')) {
|
||||||
|
$build = $this->entityTypeManager()->getViewBuilder('node')->view($node, 'full');
|
||||||
|
$build['#cache'] = $cache;
|
||||||
|
return $build;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$nids = $this->entityTypeManager()->getStorage('node')
|
$nids = $this->entityTypeManager()->getStorage('node')
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->condition('uid', $user->id())
|
->condition('uid', $user->id())
|
||||||
@@ -53,11 +81,29 @@ class MicrositeHomeController extends ControllerBase {
|
|||||||
return $this->entityTypeManager()->getViewBuilder('user')->view($user, 'full');
|
return $this->entityTypeManager()->getViewBuilder('user')->view($user, 'full');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redireciona para a página de configuração do microsite do usuário atual.
|
||||||
|
*
|
||||||
|
* Usada pelo link do menu da conta, que não suporta parâmetros dinâmicos.
|
||||||
|
*/
|
||||||
|
public function redirectToMyConfig(): RedirectResponse {
|
||||||
|
return $this->redirect('site_users_microsite.user_config', [
|
||||||
|
'user' => $this->currentUser()->id(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback de título para a página inicial.
|
* Callback de título para a página inicial.
|
||||||
*/
|
*/
|
||||||
public function title(UserInterface $user): TranslatableMarkup {
|
public function title(UserInterface $user): TranslatableMarkup {
|
||||||
return $this->t("@name", ['@name' => $user->getDisplayName()]);
|
return $this->t('@name', ['@name' => $user->getDisplayName()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback de título para a página de perfil.
|
||||||
|
*/
|
||||||
|
public function profileTitle(UserInterface $user): TranslatableMarkup {
|
||||||
|
return $this->t('Perfil de @name', ['@name' => $user->getDisplayName()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\site_users_microsite\Form;
|
||||||
|
|
||||||
|
use Drupal\Core\Cache\Cache;
|
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||||
|
use Drupal\Core\Form\FormBase;
|
||||||
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
|
use Drupal\user\UserDataInterface;
|
||||||
|
use Drupal\user\UserInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formulário de configuração do microsite pessoal do usuário.
|
||||||
|
*/
|
||||||
|
class MicrositeUserConfigForm extends FormBase {
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected EntityTypeManagerInterface $entityTypeManager,
|
||||||
|
protected UserDataInterface $userData,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function create(ContainerInterface $container): static {
|
||||||
|
return new static(
|
||||||
|
$container->get('entity_type.manager'),
|
||||||
|
$container->get('user.data'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getFormId(): string {
|
||||||
|
return 'site_users_microsite_user_config';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function buildForm(array $form, FormStateInterface $form_state, ?UserInterface $user = NULL): array {
|
||||||
|
if ($user === NULL) {
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
$form_state->set('user', $user);
|
||||||
|
|
||||||
|
$homepage_nid = $this->userData->get('site_users_microsite', $user->id(), 'homepage_nid');
|
||||||
|
$use_homepage_title = $this->userData->get('site_users_microsite', $user->id(), 'use_homepage_title');
|
||||||
|
|
||||||
|
$nids = $this->entityTypeManager->getStorage('node')
|
||||||
|
->getQuery()
|
||||||
|
->condition('uid', $user->id())
|
||||||
|
->condition('type', 'content_page')
|
||||||
|
->condition('status', 1)
|
||||||
|
->notExists('field_parent_page')
|
||||||
|
->accessCheck(TRUE)
|
||||||
|
->sort('title')
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$options = ['' => $this->t('— primeira página publicada —')];
|
||||||
|
if (!empty($nids)) {
|
||||||
|
$nodes = $this->entityTypeManager->getStorage('node')->loadMultiple($nids);
|
||||||
|
foreach ($nodes as $nid => $node) {
|
||||||
|
$options[$nid] = $node->label();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$form['#attributes']['class'][] = 'microsite-form';
|
||||||
|
$form['#attached']['library'][] = 'site_users_microsite_theme/form';
|
||||||
|
|
||||||
|
$form['homepage_nid'] = [
|
||||||
|
'#type' => 'select',
|
||||||
|
'#title' => $this->t('Homepage content'),
|
||||||
|
'#description' => $this->t('Select which content to display on your microsite homepage.'),
|
||||||
|
'#options' => $options,
|
||||||
|
'#default_value' => $homepage_nid ?? '',
|
||||||
|
];
|
||||||
|
|
||||||
|
$form['use_homepage_title'] = [
|
||||||
|
'#type' => 'checkbox',
|
||||||
|
'#title' => $this->t('Use homepage content title as page title'),
|
||||||
|
'#description' => $this->t('When checked, the title of the selected homepage content replaces the default page title (your display name).'),
|
||||||
|
'#default_value' => $use_homepage_title ?? FALSE,
|
||||||
|
];
|
||||||
|
|
||||||
|
$form['actions'] = ['#type' => 'actions'];
|
||||||
|
$form['actions']['submit'] = [
|
||||||
|
'#type' => 'submit',
|
||||||
|
'#value' => $this->t('Save configuration'),
|
||||||
|
];
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function submitForm(array &$form, FormStateInterface $form_state): void {
|
||||||
|
$user = $form_state->get('user');
|
||||||
|
$nid = $form_state->getValue('homepage_nid');
|
||||||
|
|
||||||
|
if (empty($nid)) {
|
||||||
|
$this->userData->delete('site_users_microsite', $user->id(), 'homepage_nid');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->userData->set('site_users_microsite', $user->id(), 'homepage_nid', (int) $nid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($form_state->getValue('use_homepage_title')) {
|
||||||
|
$this->userData->set('site_users_microsite', $user->id(), 'use_homepage_title', TRUE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->userData->delete('site_users_microsite', $user->id(), 'use_homepage_title');
|
||||||
|
}
|
||||||
|
|
||||||
|
Cache::invalidateTags(['site_users_microsite_config:' . $user->id()]);
|
||||||
|
$this->messenger()->addStatus($this->t('Configuration saved.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\site_users_microsite\PathProcessor;
|
||||||
|
|
||||||
|
use Drupal\Core\Language\LanguageInterface;
|
||||||
|
use Drupal\Core\Language\LanguageManagerInterface;
|
||||||
|
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
||||||
|
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
|
||||||
|
use Drupal\Core\Render\BubbleableMetadata;
|
||||||
|
use Drupal\path_alias\AliasManagerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processa subpáginas do microsite para funcionar com aliases de usuário.
|
||||||
|
*
|
||||||
|
* Converte /user/{username}/{subpage} <-> /user/{uid}/{subpage} de forma
|
||||||
|
* transparente, complementando o alias exato /user/{username} do Pathauto.
|
||||||
|
*/
|
||||||
|
class MicrositeSubpagePathProcessor implements InboundPathProcessorInterface, OutboundPathProcessorInterface {
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private AliasManagerInterface $aliasManager,
|
||||||
|
private LanguageManagerInterface $languageManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* Converte /user/{username}/{subpage} para /user/{uid}/{subpage}.
|
||||||
|
*/
|
||||||
|
public function processInbound($path, Request $request) {
|
||||||
|
if (!preg_match('#^/user/([^/]+)(/.+)$#', $path, $matches)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$segment = $matches[1];
|
||||||
|
$rest = $matches[2];
|
||||||
|
|
||||||
|
// Segmento numérico já é UID — nada a fazer.
|
||||||
|
if (is_numeric($segment)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$alias = '/user/' . $segment;
|
||||||
|
$system_path = $this->lookupSystemPath($alias);
|
||||||
|
|
||||||
|
if ($system_path !== $alias && preg_match('#^/user/\d+$#', $system_path)) {
|
||||||
|
return $system_path . $rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* Converte /user/{uid}/{subpage} para /user/{username}/{subpage}.
|
||||||
|
*/
|
||||||
|
public function processOutbound($path, &$options = [], ?Request $request = NULL, ?BubbleableMetadata $bubbleable_metadata = NULL) {
|
||||||
|
if (!preg_match('#^/user/(\d+)(/.+)$#', $path, $matches)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uid = $matches[1];
|
||||||
|
$rest = $matches[2];
|
||||||
|
$alias = $this->aliasManager->getAliasByPath('/user/' . $uid);
|
||||||
|
|
||||||
|
if ($alias !== '/user/' . $uid) {
|
||||||
|
if ($bubbleable_metadata) {
|
||||||
|
$bubbleable_metadata->addCacheContexts(['url.path']);
|
||||||
|
}
|
||||||
|
return $alias . $rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Busca o caminho interno para um alias tentando todos os idiomas.
|
||||||
|
*
|
||||||
|
* O alias manager só faz fallback para LANGCODE_NOT_SPECIFIED; aliases
|
||||||
|
* armazenados com 'en' não são encontrados quando o idioma atual é 'pt-br'.
|
||||||
|
*/
|
||||||
|
private function lookupSystemPath(string $alias): string {
|
||||||
|
$langcodes = array_keys($this->languageManager->getLanguages());
|
||||||
|
$langcodes[] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
|
||||||
|
|
||||||
|
foreach ($langcodes as $langcode) {
|
||||||
|
$system_path = $this->aliasManager->getPathByAlias($alias, $langcode);
|
||||||
|
if ($system_path !== $alias) {
|
||||||
|
return $system_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -66,12 +66,14 @@ class MicrositeHeaderBlock extends BlockBase implements ContainerFactoryPluginIn
|
|||||||
'#photo_alt' => $this->getPhotoAlt($user),
|
'#photo_alt' => $this->getPhotoAlt($user),
|
||||||
'#name' => $this->getFieldValue($user, 'field_user_name') ?: $user->getDisplayName(),
|
'#name' => $this->getFieldValue($user, 'field_user_name') ?: $user->getDisplayName(),
|
||||||
'#bio' => $this->getProcessedValue($user, 'field_user_bio'),
|
'#bio' => $this->getProcessedValue($user, 'field_user_bio'),
|
||||||
'#phone' => $this->getFieldValue($user, 'field_user_phone'),
|
|
||||||
'#email' => $user->getEmail(),
|
'#email' => $user->getEmail(),
|
||||||
'#homepage' => $this->getFieldUri($user, 'field_user_homepage'),
|
'#homepage' => $this->getFieldUri($user, 'field_user_homepage'),
|
||||||
'#lattes_id' => $this->getFieldValue($user, 'field_user_id_lattes'),
|
'#lattes_id' => $this->getFieldValue($user, 'field_user_id_lattes'),
|
||||||
'#orcid_id' => $this->getFieldValue($user, 'field_user_orcid'),
|
'#orcid_id' => $this->getFieldValue($user, 'field_user_orcid'),
|
||||||
'#mathscinet_id' => $this->getFieldValue($user, 'field_user_mathscinetid'),
|
'#mathscinet_id' => $this->getFieldValue($user, 'field_user_mathscinetid'),
|
||||||
|
'#department' => $this->getReferencedEntityLabel($user, 'field_user_department'),
|
||||||
|
'#department_url' => $this->getReferencedEntityUrl($user, 'field_user_department'),
|
||||||
|
'#work_phone' => $this->getFieldValue($user, 'field_user_work_phone'),
|
||||||
'#cache' => [
|
'#cache' => [
|
||||||
'tags' => $user->getCacheTags(),
|
'tags' => $user->getCacheTags(),
|
||||||
'contexts' => ['route'],
|
'contexts' => ['route'],
|
||||||
@@ -81,6 +83,10 @@ class MicrositeHeaderBlock extends BlockBase implements ContainerFactoryPluginIn
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retorna o usuário da rota atual.
|
* Retorna o usuário da rota atual.
|
||||||
|
*
|
||||||
|
* Primeiro tenta o parâmetro 'user' da rota (rotas próprias do microsite).
|
||||||
|
* Caso não exista (ex: rota entity.node.canonical acessada via alias
|
||||||
|
* /user/{id}/...), extrai o ID do alias do caminho atual.
|
||||||
*/
|
*/
|
||||||
protected function getUser(): ?UserInterface {
|
protected function getUser(): ?UserInterface {
|
||||||
$user = $this->routeMatch->getParameter('user');
|
$user = $this->routeMatch->getParameter('user');
|
||||||
@@ -90,6 +96,11 @@ class MicrositeHeaderBlock extends BlockBase implements ContainerFactoryPluginIn
|
|||||||
if (is_numeric($user)) {
|
if (is_numeric($user)) {
|
||||||
return $this->entityTypeManager->getStorage('user')->load($user);
|
return $this->entityTypeManager->getStorage('user')->load($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (function_exists('site_users_get_microsite_user')) {
|
||||||
|
return site_users_get_microsite_user();
|
||||||
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +147,36 @@ class MicrositeHeaderBlock extends BlockBase implements ContainerFactoryPluginIn
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna o label da entidade referenciada por um campo entity_reference.
|
||||||
|
*/
|
||||||
|
protected function getReferencedEntityLabel(UserInterface $user, string $field_name): ?string {
|
||||||
|
if (!$user->hasField($field_name) || $user->get($field_name)->isEmpty()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
$entity = $user->get($field_name)->entity;
|
||||||
|
return $entity ? $entity->label() : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna a URL canônica da entidade referenciada por um campo entity_reference.
|
||||||
|
*/
|
||||||
|
protected function getReferencedEntityUrl(UserInterface $user, string $field_name): ?string {
|
||||||
|
if (!$user->hasField($field_name) || $user->get($field_name)->isEmpty()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
$entity = $user->get($field_name)->entity;
|
||||||
|
if (!$entity || !$entity->hasLinkTemplate('canonical')) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return $entity->toUrl('canonical')->toString();
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retorna o valor de texto de um campo do usuário.
|
* Retorna o valor de texto de um campo do usuário.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,23 +2,80 @@
|
|||||||
|
|
||||||
namespace Drupal\site_users_microsite\Theme;
|
namespace Drupal\site_users_microsite\Theme;
|
||||||
|
|
||||||
|
use Drupal\Core\Path\CurrentPathStack;
|
||||||
use Drupal\Core\Routing\RouteMatchInterface;
|
use Drupal\Core\Routing\RouteMatchInterface;
|
||||||
use Drupal\Core\Theme\ThemeNegotiatorInterface;
|
use Drupal\Core\Theme\ThemeNegotiatorInterface;
|
||||||
|
use Drupal\path_alias\AliasManagerInterface;
|
||||||
|
use Drupal\user\UserInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aplica o tema microsite nas rotas de perfil de usuário e do micro-site.
|
* Aplica o tema microsite nas rotas de perfil de usuário e do micro-site.
|
||||||
*/
|
*/
|
||||||
class MicrositeThemeNegotiator implements ThemeNegotiatorInterface {
|
class MicrositeThemeNegotiator implements ThemeNegotiatorInterface {
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private AliasManagerInterface $aliasManager,
|
||||||
|
private CurrentPathStack $currentPath,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function applies(RouteMatchInterface $route_match): bool {
|
public function applies(RouteMatchInterface $route_match): bool {
|
||||||
$route_name = $route_match->getRouteName();
|
$route_name = $route_match->getRouteName() ?? '';
|
||||||
$excluded = ['site_users_microsite.settings'];
|
|
||||||
return ($route_name === 'entity.user.canonical'
|
// Rotas administrativas e de edição nunca recebem o tema do microsite.
|
||||||
|| str_starts_with($route_name, 'site_users_microsite.'))
|
$excluded = [
|
||||||
&& !in_array($route_name, $excluded);
|
'site_users_microsite.settings',
|
||||||
|
'site_users_microsite.user_config',
|
||||||
|
'site_users_microsite.my_config',
|
||||||
|
];
|
||||||
|
if (in_array($route_name, $excluded, TRUE)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
foreach (['entity.user.edit_', 'entity.user.cancel', 'user.admin'] as $prefix) {
|
||||||
|
if (str_starts_with($route_name, $prefix)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rota canônica e rotas próprias do microsite.
|
||||||
|
if ($route_name === 'entity.user.canonical') {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if (str_starts_with($route_name, 'site_users_microsite.')) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Qualquer rota com parâmetro 'user' (entidade) sob /user/{user}/.
|
||||||
|
$user = $route_match->getParameter('user');
|
||||||
|
if ($user instanceof UserInterface) {
|
||||||
|
$route = $route_match->getRouteObject();
|
||||||
|
$path = $route ? $route->getPath() : '';
|
||||||
|
if (str_starts_with($path, '/user/{user}/')) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nós cujo alias começa com /user/{uid}/ (ex.: structural_pages, blog).
|
||||||
|
if ($route_name === 'entity.node.canonical') {
|
||||||
|
$node = $route_match->getParameter('node');
|
||||||
|
if ($node) {
|
||||||
|
$nid = is_object($node) ? $node->id() : $node;
|
||||||
|
$alias = $this->aliasManager->getAliasByPath('/node/' . $nid);
|
||||||
|
if (preg_match('#^/user/\d+/#', $alias)) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Qualquer rota cujo path atual (já processado) seja /user/{uid}/...
|
||||||
|
// Cobre Views e outras rotas que não expõem parâmetro 'user' na rota.
|
||||||
|
if (preg_match('#^/user/\d+/#', $this->currentPath->getPath())) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -37,16 +37,26 @@
|
|||||||
|
|
||||||
<h1 class="msite-header-block__name">{{ name }}</h1>
|
<h1 class="msite-header-block__name">{{ name }}</h1>
|
||||||
|
|
||||||
|
{% if department %}
|
||||||
|
<div class="msite-header-block__department">
|
||||||
|
{% if department_url %}
|
||||||
|
<a href="{{ department_url }}">{{ department }}</a>
|
||||||
|
{% else %}
|
||||||
|
{{ department }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if bio %}
|
{% if bio %}
|
||||||
<div class="msite-header-block__bio">{{ bio|raw }}</div>
|
<div class="msite-header-block__bio">{{ bio|raw }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if phone or email %}
|
{% if work_phone or email %}
|
||||||
<ul class="msite-header-block__contact">
|
<ul class="msite-header-block__contact">
|
||||||
{% if phone %}
|
{% if work_phone %}
|
||||||
<li class="msite-header-block__contact-item">
|
<li class="msite-header-block__contact-item">
|
||||||
<span class="msite-header-block__contact-label">{{ 'Telefone'|t }}:</span>
|
<span class="msite-header-block__contact-label">{{ 'Telefone'|t }}:</span>
|
||||||
<a href="tel:{{ phone }}">{{ phone }}</a>
|
<a href="tel:{{ work_phone }}">{{ work_phone }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if email %}
|
{% if email %}
|
||||||
|
|||||||
12
modules/site_users_user_content/composer.json
Normal file
12
modules/site_users_user_content/composer.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "imecc/site_users_user_content",
|
||||||
|
"description": "Provides User entity support as parent type for Structural Pages module.",
|
||||||
|
"type": "drupal-module",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"drupal/core": "^10.3 || ^11",
|
||||||
|
"imecc/site_users": "*",
|
||||||
|
"imecc/structural_pages": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
name: 'Site Users User Content'
|
||||||
|
type: module
|
||||||
|
description: 'Provides User entity support as parent type for Structural Pages module.'
|
||||||
|
package: 'Site Users'
|
||||||
|
core_version_requirement: ^10.3 || ^11
|
||||||
|
dependencies:
|
||||||
|
- structural_pages:structural_pages
|
||||||
|
- site_users:site_users
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\site_users_user_content\Plugin\ParentEntityHandler;
|
||||||
|
|
||||||
|
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||||
|
use Drupal\structural_pages\Attribute\ParentEntityHandler;
|
||||||
|
use Drupal\structural_pages\ParentEntityHandler\ParentEntityHandlerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for user entities.
|
||||||
|
*
|
||||||
|
* Allows content_page nodes to use a user entity as their parent, enabling
|
||||||
|
* personal microsite content organisation without a site section.
|
||||||
|
*/
|
||||||
|
#[ParentEntityHandler(
|
||||||
|
id: 'user',
|
||||||
|
label: new TranslatableMarkup('Users (user)'),
|
||||||
|
entity_type_id: 'user',
|
||||||
|
clears_site_section: TRUE,
|
||||||
|
sort_field: 'name',
|
||||||
|
weight: 30,
|
||||||
|
)]
|
||||||
|
class UserHandler extends ParentEntityHandlerBase {}
|
||||||
@@ -2,7 +2,7 @@ name: Site Users
|
|||||||
type: module
|
type: module
|
||||||
description: 'Site user customizations, including fields and templates.'
|
description: 'Site user customizations, including fields and templates.'
|
||||||
core_version_requirement: ^10 || ^11
|
core_version_requirement: ^10 || ^11
|
||||||
package: Custom
|
package: 'Site Users'
|
||||||
dependencies:
|
dependencies:
|
||||||
- drupal:user
|
- drupal:user
|
||||||
- drupal:telephone
|
- drupal:telephone
|
||||||
|
|||||||
@@ -194,6 +194,79 @@ function site_users_install() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove mídias LDAP geradas sem UID (URI ldap_photo_.jpg/png) de todos os
|
||||||
|
* usuários e apaga os arquivos e mídias correspondentes.
|
||||||
|
*
|
||||||
|
* Causa: durante provisionamento LDAP, $account->id() era vazio antes do
|
||||||
|
* primeiro save, gerando URI única compartilhada por todos os usuários.
|
||||||
|
*/
|
||||||
|
function site_users_update_10011() {
|
||||||
|
$file_storage = \Drupal::entityTypeManager()->getStorage('file');
|
||||||
|
$media_storage = \Drupal::entityTypeManager()->getStorage('media');
|
||||||
|
$user_storage = \Drupal::entityTypeManager()->getStorage('user');
|
||||||
|
|
||||||
|
// Localiza arquivos com URI sem UID (ldap_photo_.jpg, ldap_photo_.png…).
|
||||||
|
$fids = $file_storage->getQuery()
|
||||||
|
->condition('uri', 'public://ldap_photos/ldap_photo_.', 'STARTS_WITH')
|
||||||
|
->accessCheck(FALSE)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
if (empty($fids)) {
|
||||||
|
return t('Nenhum arquivo LDAP sem UID encontrado.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$removed_media = 0;
|
||||||
|
$affected_users = 0;
|
||||||
|
|
||||||
|
foreach ($fids as $fid) {
|
||||||
|
$medias = $media_storage->loadByProperties([
|
||||||
|
'bundle' => 'image',
|
||||||
|
'field_media_image.target_id' => $fid,
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($medias as $media) {
|
||||||
|
$mid = (int) $media->id();
|
||||||
|
|
||||||
|
$uids = $user_storage->getQuery()
|
||||||
|
->condition('field_user_photos.target_id', $mid)
|
||||||
|
->accessCheck(FALSE)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
foreach ($uids as $uid) {
|
||||||
|
$user = $user_storage->load($uid);
|
||||||
|
if (!$user) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$photos = array_column($user->get('field_user_photos')->getValue(), 'target_id');
|
||||||
|
$photos = array_values(array_filter($photos, fn($id) => (int) $id !== $mid));
|
||||||
|
$user->set('field_user_photos', array_map(fn($id) => ['target_id' => $id], $photos));
|
||||||
|
|
||||||
|
if ((int) $user->get('field_user_default_photo')->target_id === $mid) {
|
||||||
|
$user->set('field_user_default_photo', empty($photos) ? NULL : $photos[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
$affected_users++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$media->delete();
|
||||||
|
$removed_media++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $file_storage->load($fid);
|
||||||
|
if ($file) {
|
||||||
|
$file->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t('Removidas @m mídias sem UID de @u usuários.', [
|
||||||
|
'@m' => $removed_media,
|
||||||
|
'@u' => $affected_users,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adiciona o campo field_user_default_photo para seleção de foto padrão.
|
* Adiciona o campo field_user_default_photo para seleção de foto padrão.
|
||||||
*/
|
*/
|
||||||
@@ -470,10 +543,10 @@ function site_users_update_10006() {
|
|||||||
$form_display->removeComponent('field_user_selected_view_mode')->save();
|
$form_display->removeComponent('field_user_selected_view_mode')->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Remover de todos os view displays existentes.
|
// 6. Remover de todos os view displays de usuário existentes.
|
||||||
$displays = $display_storage->loadMultiple();
|
$displays = $display_storage->loadByProperties(['targetEntityType' => 'user']);
|
||||||
foreach ($displays as $display) {
|
foreach ($displays as $display) {
|
||||||
if ($display->getTargetEntityTypeId() === 'user' && $display->getComponent('field_user_selected_view_mode')) {
|
if ($display->getComponent('field_user_selected_view_mode')) {
|
||||||
$display->removeComponent('field_user_selected_view_mode')->save();
|
$display->removeComponent('field_user_selected_view_mode')->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -614,6 +687,86 @@ function site_users_update_10009() {
|
|||||||
return t('Campo field_user_homepage criado com sucesso.');
|
return t('Campo field_user_homepage criado com sucesso.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove fotos LDAP placeholder (abaixo do tamanho mínimo configurado).
|
||||||
|
*
|
||||||
|
* Limpa field_user_photos e field_user_default_photo dos usuários afetados
|
||||||
|
* e apaga as entidades de mídia e arquivo correspondentes.
|
||||||
|
*/
|
||||||
|
function site_users_update_10010() {
|
||||||
|
$min_size = (int) (\Drupal::config('site_users.settings')->get('photos.ldap_min_photo_size') ?? 10240);
|
||||||
|
|
||||||
|
// Busca arquivos LDAP abaixo do tamanho mínimo.
|
||||||
|
$fids = \Drupal::entityTypeManager()->getStorage('file')->getQuery()
|
||||||
|
->condition('uri', 'public://ldap_photos/', 'STARTS_WITH')
|
||||||
|
->condition('filesize', $min_size, '<')
|
||||||
|
->accessCheck(FALSE)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
if (empty($fids)) {
|
||||||
|
return t('Nenhuma foto LDAP placeholder encontrada.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$media_storage = \Drupal::entityTypeManager()->getStorage('media');
|
||||||
|
$user_storage = \Drupal::entityTypeManager()->getStorage('user');
|
||||||
|
$removed_media = 0;
|
||||||
|
$affected_users = 0;
|
||||||
|
|
||||||
|
foreach ($fids as $fid) {
|
||||||
|
// Localiza a entidade de mídia que usa este arquivo.
|
||||||
|
$medias = $media_storage->loadByProperties([
|
||||||
|
'bundle' => 'image',
|
||||||
|
'field_media_image.target_id' => $fid,
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($medias as $media) {
|
||||||
|
$mid = (int) $media->id();
|
||||||
|
|
||||||
|
// Busca usuários que têm esta mídia em field_user_photos.
|
||||||
|
$uids = $user_storage->getQuery()
|
||||||
|
->condition('field_user_photos.target_id', $mid)
|
||||||
|
->accessCheck(FALSE)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
foreach ($uids as $uid) {
|
||||||
|
/** @var \Drupal\user\UserInterface $user */
|
||||||
|
$user = $user_storage->load($uid);
|
||||||
|
if (!$user) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a mídia de field_user_photos.
|
||||||
|
$photos = array_column($user->get('field_user_photos')->getValue(), 'target_id');
|
||||||
|
$photos = array_values(array_filter($photos, fn($id) => (int) $id !== $mid));
|
||||||
|
$user->set('field_user_photos', array_map(fn($id) => ['target_id' => $id], $photos));
|
||||||
|
|
||||||
|
// Limpa field_user_default_photo se apontar para esta mídia.
|
||||||
|
$default_mid = $user->get('field_user_default_photo')->target_id;
|
||||||
|
if ((int) $default_mid === $mid) {
|
||||||
|
$user->set('field_user_default_photo', empty($photos) ? NULL : $photos[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
$affected_users++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$media->delete();
|
||||||
|
$removed_media++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apaga o arquivo gerenciado.
|
||||||
|
$file = \Drupal::entityTypeManager()->getStorage('file')->load($fid);
|
||||||
|
if ($file) {
|
||||||
|
$file->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t('Removidas @m mídias placeholder de @u usuários.', [
|
||||||
|
'@m' => $removed_media,
|
||||||
|
'@u' => $affected_users,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Corrige mapeamentos LDAP com campos de string nulos na config ativa.
|
* Corrige mapeamentos LDAP com campos de string nulos na config ativa.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,3 +4,10 @@ site_users.settings:
|
|||||||
route_name: site_users.settings
|
route_name: site_users.settings
|
||||||
parent: site_tools.admin_config
|
parent: site_tools.admin_config
|
||||||
weight: 10
|
weight: 10
|
||||||
|
|
||||||
|
site_users.add_content:
|
||||||
|
title: 'Adicionar'
|
||||||
|
route_name: site_users.add_content
|
||||||
|
menu_name: account
|
||||||
|
weight: -5
|
||||||
|
expanded: true
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
|||||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||||
use Drupal\Core\Field\FieldItemListInterface;
|
use Drupal\Core\Field\FieldItemListInterface;
|
||||||
use Drupal\Core\Form\FormStateInterface;
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
|
use Drupal\Core\Menu\MenuLinkDefault;
|
||||||
use Drupal\Core\Session\AccountInterface;
|
use Drupal\Core\Session\AccountInterface;
|
||||||
use Drupal\Core\Url;
|
use Drupal\Core\Url;
|
||||||
use Drupal\field\FieldConfigInterface;
|
use Drupal\field\FieldConfigInterface;
|
||||||
@@ -529,11 +530,35 @@ function site_users_user_format_name_alter(&$name, $account) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna o usuário dono do microsite para a requisição atual.
|
||||||
|
*
|
||||||
|
* Tenta o parâmetro 'user' da rota (rotas próprias do microsite) e, se não
|
||||||
|
* encontrar, extrai o ID do alias do caminho atual (ex: /user/229/projetos).
|
||||||
|
*/
|
||||||
|
function site_users_get_microsite_user(): ?UserInterface {
|
||||||
|
$user = \Drupal::routeMatch()->getParameter('user');
|
||||||
|
if ($user instanceof UserInterface) {
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
if (is_numeric($user)) {
|
||||||
|
return \Drupal::entityTypeManager()->getStorage('user')->load($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
$alias = \Drupal::service('path_alias.manager')
|
||||||
|
->getAliasByPath(\Drupal::service('path.current')->getPath());
|
||||||
|
if (preg_match('#^/user/(\d+)(/|$)#', $alias, $matches)) {
|
||||||
|
return \Drupal::entityTypeManager()->getStorage('user')->load($matches[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_site_tools_share_links().
|
* Implements hook_site_tools_share_links().
|
||||||
*/
|
*/
|
||||||
function site_users_site_tools_share_links(): array {
|
function site_users_site_tools_share_links(): array {
|
||||||
$user = \Drupal::routeMatch()->getParameter('user');
|
$user = site_users_get_microsite_user();
|
||||||
|
|
||||||
if (!($user instanceof UserInterface)) {
|
if (!($user instanceof UserInterface)) {
|
||||||
return [];
|
return [];
|
||||||
@@ -604,3 +629,44 @@ function site_users_get_default_photo(UserInterface $user): ?MediaInterface {
|
|||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_menu_links_discovered_alter().
|
||||||
|
*
|
||||||
|
* Adiciona dinamicamente os subitens do menu "Adicionar" configurados em
|
||||||
|
* site_users.settings:add_content_links.
|
||||||
|
*/
|
||||||
|
function site_users_menu_links_discovered_alter(array &$links): void {
|
||||||
|
$items = \Drupal::config('site_users.settings')->get('add_content_links') ?? [];
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (empty($item['route_name']) || empty($item['label'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID estável derivado da rota e dos parâmetros.
|
||||||
|
$parts = [preg_replace('/[^a-z0-9]/', '_', strtolower($item['route_name']))];
|
||||||
|
foreach ($item['route_parameters'] ?? [] as $value) {
|
||||||
|
$parts[] = preg_replace('/[^a-z0-9]/', '_', strtolower((string) $value));
|
||||||
|
}
|
||||||
|
$id = 'site_users.add_content_child.' . implode('_', $parts);
|
||||||
|
|
||||||
|
$links[$id] = [
|
||||||
|
'id' => $id,
|
||||||
|
'title' => $item['label'],
|
||||||
|
'route_name' => $item['route_name'],
|
||||||
|
'route_parameters' => $item['route_parameters'] ?? [],
|
||||||
|
'menu_name' => 'account',
|
||||||
|
'parent' => 'site_users.add_content',
|
||||||
|
'weight' => (int) ($item['weight'] ?? 0),
|
||||||
|
'provider' => 'site_users',
|
||||||
|
'class' => MenuLinkDefault::class,
|
||||||
|
'form_class' => 'Drupal\Core\Menu\Form\MenuLinkDefaultForm',
|
||||||
|
'metadata' => [],
|
||||||
|
'options' => [],
|
||||||
|
'expanded' => FALSE,
|
||||||
|
'enabled' => TRUE,
|
||||||
|
'discovered' => TRUE,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,3 +5,11 @@ site_users.settings:
|
|||||||
_title: 'Site Users Settings'
|
_title: 'Site Users Settings'
|
||||||
requirements:
|
requirements:
|
||||||
_permission: 'administer site_users settings'
|
_permission: 'administer site_users settings'
|
||||||
|
|
||||||
|
site_users.add_content:
|
||||||
|
path: '/add-content'
|
||||||
|
defaults:
|
||||||
|
_controller: '\Drupal\site_users\Controller\AddContentController::redirectToFirst'
|
||||||
|
_title: 'Adicionar'
|
||||||
|
requirements:
|
||||||
|
_custom_access: 'site_users.add_content_access::access'
|
||||||
|
|||||||
@@ -6,3 +6,9 @@ services:
|
|||||||
- '@entity_type.manager'
|
- '@entity_type.manager'
|
||||||
- '@file.repository'
|
- '@file.repository'
|
||||||
- '@file_system'
|
- '@file_system'
|
||||||
|
|
||||||
|
site_users.add_content_access:
|
||||||
|
class: Drupal\site_users\Access\AddContentAccessCheck
|
||||||
|
arguments:
|
||||||
|
- '@config.factory'
|
||||||
|
- '@access_manager'
|
||||||
|
|||||||
47
src/Access/AddContentAccessCheck.php
Normal file
47
src/Access/AddContentAccessCheck.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\site_users\Access;
|
||||||
|
|
||||||
|
use Drupal\Core\Access\AccessManagerInterface;
|
||||||
|
use Drupal\Core\Access\AccessResult;
|
||||||
|
use Drupal\Core\Access\AccessResultInterface;
|
||||||
|
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||||
|
use Drupal\Core\Routing\Access\AccessInterface;
|
||||||
|
use Drupal\Core\Session\AccountInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grants access to the "Adicionar" parent menu route when any child is accessible.
|
||||||
|
*/
|
||||||
|
class AddContentAccessCheck implements AccessInterface {
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ConfigFactoryInterface $configFactory,
|
||||||
|
private readonly AccessManagerInterface $accessManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns allowed if the user can access at least one configured add route.
|
||||||
|
*/
|
||||||
|
public function access(AccountInterface $account): AccessResultInterface {
|
||||||
|
$items = $this->configFactory
|
||||||
|
->get('site_users.settings')
|
||||||
|
->get('add_content_links') ?? [];
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (empty($item['route_name'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$params = $item['route_parameters'] ?? [];
|
||||||
|
if ($this->accessManager->checkNamedRoute($item['route_name'], $params, $account)) {
|
||||||
|
return AccessResult::allowed()
|
||||||
|
->addCacheContexts(['user.permissions', 'user.roles']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AccessResult::forbidden()
|
||||||
|
->addCacheContexts(['user.permissions', 'user.roles']);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
48
src/Controller/AddContentController.php
Normal file
48
src/Controller/AddContentController.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\site_users\Controller;
|
||||||
|
|
||||||
|
use Drupal\Core\Access\AccessManagerInterface;
|
||||||
|
use Drupal\Core\Controller\ControllerBase;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirects to the first accessible add-content route for the current user.
|
||||||
|
*/
|
||||||
|
class AddContentController extends ControllerBase {
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly AccessManagerInterface $accessManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function create(ContainerInterface $container): static {
|
||||||
|
return new static(
|
||||||
|
$container->get('access_manager'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirects to the first accessible add route, or to the front page.
|
||||||
|
*/
|
||||||
|
public function redirectToFirst(): RedirectResponse {
|
||||||
|
$items = $this->config('site_users.settings')
|
||||||
|
->get('add_content_links') ?? [];
|
||||||
|
$account = $this->currentUser();
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (empty($item['route_name'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$params = $item['route_parameters'] ?? [];
|
||||||
|
if ($this->accessManager->checkNamedRoute($item['route_name'], $params, $account)) {
|
||||||
|
return $this->redirect($item['route_name'], $params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirect('<front>');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -118,6 +118,20 @@ class SiteUsersSettingsForm extends ConfigFormBase {
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$form['photos']['photos_ldap_min_photo_size'] = [
|
||||||
|
'#type' => 'number',
|
||||||
|
'#title' => $this->t('Minimum LDAP photo size (bytes)'),
|
||||||
|
'#description' => $this->t('Photos smaller than this size are ignored during LDAP sync, avoiding placeholder images. Default: 10240 (10 KB).'),
|
||||||
|
'#default_value' => $config->get('photos.ldap_min_photo_size') ?? 10240,
|
||||||
|
'#min' => 0,
|
||||||
|
'#required' => TRUE,
|
||||||
|
'#states' => [
|
||||||
|
'visible' => [
|
||||||
|
':input[name="photos_ldap_sync_enabled"]' => ['checked' => TRUE],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
// Fieldset para campos editáveis pelo próprio usuário.
|
// Fieldset para campos editáveis pelo próprio usuário.
|
||||||
$form['user_editable_fields'] = [
|
$form['user_editable_fields'] = [
|
||||||
'#type' => 'fieldset',
|
'#type' => 'fieldset',
|
||||||
@@ -177,6 +191,66 @@ class SiteUsersSettingsForm extends ConfigFormBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fieldset para itens do menu "Adicionar".
|
||||||
|
$form['add_content_links'] = [
|
||||||
|
'#type' => 'fieldset',
|
||||||
|
'#title' => $this->t('Add content menu items'),
|
||||||
|
'#description' => $this->t('Configure items shown under the "Adicionar" entry in the account menu. Each item points to an entity add route. Leave "Label" empty to remove the row.'),
|
||||||
|
'#tree' => TRUE,
|
||||||
|
];
|
||||||
|
|
||||||
|
$saved_items = $config->get('add_content_links') ?? [];
|
||||||
|
// Append one blank row for adding a new item.
|
||||||
|
$saved_items[] = ['label' => '', 'route_name' => '', 'route_parameters' => [], 'weight' => 0];
|
||||||
|
|
||||||
|
$form['add_content_links']['table'] = [
|
||||||
|
'#type' => 'table',
|
||||||
|
'#header' => [
|
||||||
|
$this->t('Label'),
|
||||||
|
$this->t('Route name'),
|
||||||
|
$this->t('Parameter name'),
|
||||||
|
$this->t('Parameter value'),
|
||||||
|
$this->t('Weight'),
|
||||||
|
],
|
||||||
|
'#empty' => $this->t('No items yet. Fill in the row below to add one.'),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($saved_items as $delta => $item) {
|
||||||
|
$params = $item['route_parameters'] ?? [];
|
||||||
|
$param_name = array_key_first($params) ?? '';
|
||||||
|
$param_value = $params[$param_name] ?? '';
|
||||||
|
|
||||||
|
$form['add_content_links']['table'][$delta]['label'] = [
|
||||||
|
'#type' => 'textfield',
|
||||||
|
'#default_value' => $item['label'] ?? '',
|
||||||
|
'#size' => 20,
|
||||||
|
'#placeholder' => $this->t('e.g. Artigo'),
|
||||||
|
];
|
||||||
|
$form['add_content_links']['table'][$delta]['route_name'] = [
|
||||||
|
'#type' => 'textfield',
|
||||||
|
'#default_value' => $item['route_name'] ?? '',
|
||||||
|
'#size' => 30,
|
||||||
|
'#placeholder' => 'e.g. node.add',
|
||||||
|
];
|
||||||
|
$form['add_content_links']['table'][$delta]['param_name'] = [
|
||||||
|
'#type' => 'textfield',
|
||||||
|
'#default_value' => $param_name,
|
||||||
|
'#size' => 20,
|
||||||
|
'#placeholder' => 'e.g. node_type',
|
||||||
|
];
|
||||||
|
$form['add_content_links']['table'][$delta]['param_value'] = [
|
||||||
|
'#type' => 'textfield',
|
||||||
|
'#default_value' => $param_value,
|
||||||
|
'#size' => 20,
|
||||||
|
'#placeholder' => 'e.g. article',
|
||||||
|
];
|
||||||
|
$form['add_content_links']['table'][$delta]['weight'] = [
|
||||||
|
'#type' => 'number',
|
||||||
|
'#default_value' => (int) ($item['weight'] ?? 0),
|
||||||
|
'#size' => 4,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return parent::buildForm($form, $form_state);
|
return parent::buildForm($form, $form_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +284,8 @@ class SiteUsersSettingsForm extends ConfigFormBase {
|
|||||||
$config
|
$config
|
||||||
->set('photos.max_count', $form_state->getValue('photos_max_count'))
|
->set('photos.max_count', $form_state->getValue('photos_max_count'))
|
||||||
->set('photos.ldap_sync_enabled', (bool) $form_state->getValue('photos_ldap_sync_enabled'))
|
->set('photos.ldap_sync_enabled', (bool) $form_state->getValue('photos_ldap_sync_enabled'))
|
||||||
->set('photos.ldap_attribute', $form_state->getValue('photos_ldap_attribute'));
|
->set('photos.ldap_attribute', $form_state->getValue('photos_ldap_attribute'))
|
||||||
|
->set('photos.ldap_min_photo_size', (int) $form_state->getValue('photos_ldap_min_photo_size'));
|
||||||
|
|
||||||
$definitions = \Drupal::service('entity_field.manager')
|
$definitions = \Drupal::service('entity_field.manager')
|
||||||
->getFieldDefinitions('user', 'user');
|
->getFieldDefinitions('user', 'user');
|
||||||
@@ -230,6 +305,32 @@ class SiteUsersSettingsForm extends ConfigFormBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Salvar add_content_links: ignorar linhas sem label ou route_name.
|
||||||
|
$links_raw = $form_state->getValue(['add_content_links', 'table']) ?? [];
|
||||||
|
$add_content_links = [];
|
||||||
|
foreach ($links_raw as $row) {
|
||||||
|
$label = trim($row['label'] ?? '');
|
||||||
|
$route_name = trim($row['route_name'] ?? '');
|
||||||
|
if ($label === '' || $route_name === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$params = [];
|
||||||
|
$param_name = trim($row['param_name'] ?? '');
|
||||||
|
$param_value = trim($row['param_value'] ?? '');
|
||||||
|
if ($param_name !== '') {
|
||||||
|
$params[$param_name] = $param_value;
|
||||||
|
}
|
||||||
|
$add_content_links[] = [
|
||||||
|
'label' => $label,
|
||||||
|
'route_name' => $route_name,
|
||||||
|
'route_parameters' => $params,
|
||||||
|
'weight' => (int) ($row['weight'] ?? 0),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Reordena por weight.
|
||||||
|
usort($add_content_links, fn($a, $b) => $a['weight'] <=> $b['weight']);
|
||||||
|
$config->set('add_content_links', $add_content_links);
|
||||||
|
|
||||||
// Salvar role_view_modes: apenas os valores marcados (filtrar 0).
|
// Salvar role_view_modes: apenas os valores marcados (filtrar 0).
|
||||||
$role_view_modes_raw = $form_state->getValue('role_view_modes') ?? [];
|
$role_view_modes_raw = $form_state->getValue('role_view_modes') ?? [];
|
||||||
$roles = \Drupal\user\Entity\Role::loadMultiple();
|
$roles = \Drupal\user\Entity\Role::loadMultiple();
|
||||||
|
|||||||
18
src/Plugin/Menu/AddContentMenuLink.php
Normal file
18
src/Plugin/Menu/AddContentMenuLink.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\site_users\Plugin\Menu;
|
||||||
|
|
||||||
|
use Drupal\Core\Menu\MenuLinkDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides derived menu links for add-content actions in the account menu.
|
||||||
|
*
|
||||||
|
* @MenuLink(
|
||||||
|
* id = "site_users.add_content_child",
|
||||||
|
* deriver = "Drupal\site_users\Plugin\Menu\AddContentMenuLinkDeriver"
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class AddContentMenuLink extends MenuLinkDefault {
|
||||||
|
}
|
||||||
64
src/Plugin/Menu/AddContentMenuLinkDeriver.php
Normal file
64
src/Plugin/Menu/AddContentMenuLinkDeriver.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\site_users\Plugin\Menu;
|
||||||
|
|
||||||
|
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||||
|
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||||
|
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates account menu "Adicionar" child links from site_users.settings.
|
||||||
|
*/
|
||||||
|
class AddContentMenuLinkDeriver extends DeriverBase implements ContainerDeriverInterface {
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ConfigFactoryInterface $configFactory,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function create(ContainerInterface $container, $base_plugin_id): static {
|
||||||
|
return new static($container->get('config.factory'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getDerivativeDefinitions($base_plugin_definition): array {
|
||||||
|
$this->derivatives = [];
|
||||||
|
|
||||||
|
$items = $this->configFactory
|
||||||
|
->get('site_users.settings')
|
||||||
|
->get('add_content_links') ?? [];
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (empty($item['route_name']) || empty($item['label'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $this->buildId($item);
|
||||||
|
$this->derivatives[$id] = $base_plugin_definition;
|
||||||
|
$this->derivatives[$id]['title'] = $item['label'];
|
||||||
|
$this->derivatives[$id]['route_name'] = $item['route_name'];
|
||||||
|
$this->derivatives[$id]['route_parameters'] = $item['route_parameters'] ?? [];
|
||||||
|
$this->derivatives[$id]['weight'] = (int) ($item['weight'] ?? 0);
|
||||||
|
$this->derivatives[$id]['menu_name'] = 'account';
|
||||||
|
$this->derivatives[$id]['parent'] = 'site_users.add_content';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->derivatives;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a stable derivative ID from the item's route and parameters.
|
||||||
|
*/
|
||||||
|
private function buildId(array $item): string {
|
||||||
|
$parts = [preg_replace('/[^a-z0-9]/', '_', strtolower($item['route_name']))];
|
||||||
|
foreach ($item['route_parameters'] ?? [] as $value) {
|
||||||
|
$parts[] = preg_replace('/[^a-z0-9]/', '_', strtolower((string) $value));
|
||||||
|
}
|
||||||
|
return implode('_', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -32,6 +32,11 @@ class LdapPhotoSyncService {
|
|||||||
* changed since the last sync (same MD5), no file or media write occurs.
|
* changed since the last sync (same MD5), no file or media write occurs.
|
||||||
*/
|
*/
|
||||||
public function syncFromLdapEntry(UserInterface $account, Entry $ldapEntry): void {
|
public function syncFromLdapEntry(UserInterface $account, Entry $ldapEntry): void {
|
||||||
|
// Usuário sem ID ainda não foi salvo — não é possível gerar URI única.
|
||||||
|
if (!$account->id()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$config = $this->configFactory->get('site_users.settings');
|
$config = $this->configFactory->get('site_users.settings');
|
||||||
|
|
||||||
if (!$config->get('photos.ldap_sync_enabled')) {
|
if (!$config->get('photos.ldap_sync_enabled')) {
|
||||||
@@ -48,6 +53,11 @@ class LdapPhotoSyncService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$min_size = (int) ($config->get('photos.ldap_min_photo_size') ?? 10240);
|
||||||
|
if ($min_size > 0 && strlen($binary) < $min_size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$extension = $this->detectExtension($binary);
|
$extension = $this->detectExtension($binary);
|
||||||
if (!$extension) {
|
if (!$extension) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
87
themes/site_users_microsite_theme/css/microsite-form.css
Normal file
87
themes/site_users_microsite_theme/css/microsite-form.css
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Microsite theme — estilos para formulários de configuração do usuário.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.microsite-form {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 600px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-form .form-item {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-form label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-form select,
|
||||||
|
.microsite-form input[type="text"],
|
||||||
|
.microsite-form input[type="email"],
|
||||||
|
.microsite-form textarea {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-family: inherit;
|
||||||
|
color: #222;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: border-color 0.15s, box-shadow 0.15s;
|
||||||
|
appearance: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-form select:focus,
|
||||||
|
.microsite-form input:focus,
|
||||||
|
.microsite-form textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: hsl(202, 79%, 50%);
|
||||||
|
box-shadow: 0 0 0 3px hsla(202, 79%, 50%, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-form .form-item__description,
|
||||||
|
.microsite-form .description {
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-form .form-actions {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 1.25rem;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-form .button,
|
||||||
|
.microsite-form input[type="submit"] {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.55rem 1.5rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: inherit;
|
||||||
|
color: #fff;
|
||||||
|
background: hsl(202, 79%, 50%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-form .button:hover,
|
||||||
|
.microsite-form input[type="submit"]:hover {
|
||||||
|
background: hsl(202, 79%, 42%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-form .button:focus,
|
||||||
|
.microsite-form input[type="submit"]:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px hsla(202, 79%, 50%, 0.35);
|
||||||
|
}
|
||||||
@@ -310,8 +310,42 @@ body.microsite {
|
|||||||
margin-inline-end: 18px;
|
margin-inline-end: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.microsite-top-bar li.menu__item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.microsite-top-bar ul.menu ul.menu {
|
.microsite-top-bar ul.menu ul.menu {
|
||||||
display: none;
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
min-width: 160px;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: hsl(201, 15%, 15%);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
|
||||||
|
z-index: 100;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-top-bar li.menu__item:hover > ul.menu,
|
||||||
|
.microsite-top-bar li.menu__item:focus-within > ul.menu {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-top-bar ul.menu ul.menu li.menu__item {
|
||||||
|
margin-inline-end: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-top-bar ul.menu ul.menu a.menu__link {
|
||||||
|
padding: 6px 16px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.microsite-top-bar ul.menu ul.menu a.menu__link:hover {
|
||||||
|
background-color: hsl(201, 15%, 25%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.microsite-top-bar a.menu__link {
|
.microsite-top-bar a.menu__link {
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ regions:
|
|||||||
highlighted: Highlighted
|
highlighted: Highlighted
|
||||||
tabs: Tabs
|
tabs: Tabs
|
||||||
messages: Messages
|
messages: Messages
|
||||||
|
content_above: Content Above
|
||||||
content: Content
|
content: Content
|
||||||
|
content_below: Content Below
|
||||||
sidebar: Sidebar
|
sidebar: Sidebar
|
||||||
social: Social
|
social: Social
|
||||||
footer: Footer
|
footer: Footer
|
||||||
|
|||||||
@@ -6,3 +6,10 @@ global:
|
|||||||
js/social-bar.js: {}
|
js/social-bar.js: {}
|
||||||
dependencies:
|
dependencies:
|
||||||
- core/drupal
|
- core/drupal
|
||||||
|
|
||||||
|
form:
|
||||||
|
css:
|
||||||
|
theme:
|
||||||
|
css/microsite-form.css: {}
|
||||||
|
dependencies:
|
||||||
|
- site_users_microsite_theme/global
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
|
|
||||||
<div class="microsite-main-wrapper{% if page.sidebar %} microsite-main-wrapper--has-sidebar{% endif %}">
|
<div class="microsite-main-wrapper{% if page.sidebar %} microsite-main-wrapper--has-sidebar{% endif %}">
|
||||||
<main id="main-content" class="microsite-main" role="main">
|
<main id="main-content" class="microsite-main" role="main">
|
||||||
|
{{ page.content_above }}
|
||||||
{{ page.content }}
|
{{ page.content }}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -90,6 +91,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if page.content_below %}
|
||||||
|
<div class="microsite-content-below">
|
||||||
|
{{ page.content_below }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if page.footer %}
|
{% if page.footer %}
|
||||||
<footer class="microsite-footer">
|
<footer class="microsite-footer">
|
||||||
{{ page.footer }}
|
{{ page.footer }}
|
||||||
|
|||||||
Reference in New Issue
Block a user