Adiciona sub-módulo site_users_blog e melhora negociador de tema

Sub-módulo site_users_blog:
- Tipo de conteúdo blog_post (título, corpo, imagem, assuntos)
- Vocabulário blog_tags para categorias
- Listagem em /user/{uid}/blog via Views com filtro contextual por autor
- Padrão Pathauto: user/[node:author:uid]/blog/[node:title]
- hook_node_presave: preenche field_site_section com o autor
- hook_node_access: restringe criação às roles configuradas
- hook_preprocess_structural_pages_menu: injeta item "Blog" quando
  usuário tem posts publicados
- Plugin BlogUserHandler: resolve usuário ancestral para rotas de blog
  (post individual e listagem Views)
- Link "Post de blog" no menu "Adicionar" da conta
- Página de configuração de roles permitidas
- Update 10001: adiciona field_site_section a posts existentes

MicrositeThemeNegotiator:
- Injeta path.current para cobrir rotas sem parâmetro 'user' (ex.: Views)
- Qualquer path /user/{uid}/... recebe o tema do microsite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-24 07:32:10 -03:00
parent 39de6a7493
commit d72f41de97
24 changed files with 931 additions and 2 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1 @@
allowed_roles: {}

View File

@@ -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

View 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