From d72f41de97376e66ec96be32519e498536d43ea1 Mon Sep 17 00:00:00 2001 From: "Quintino A. G. Souza" Date: Tue, 24 Mar 2026 07:32:10 -0300 Subject: [PATCH] =?UTF-8?q?Adiciona=20sub-m=C3=B3dulo=20site=5Fusers=5Fblo?= =?UTF-8?q?g=20e=20melhora=20negociador=20de=20tema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- ...ty_form_display.node.blog_post.default.yml | 63 +++++ ...ty_view_display.node.blog_post.default.yml | 48 ++++ ...ity_view_display.node.blog_post.teaser.yml | 49 ++++ ....field.node.blog_post.field_blog_image.yml | 34 +++ ...d.field.node.blog_post.field_blog_tags.yml | 27 ++ ...ield.node.blog_post.field_site_section.yml | 21 ++ .../field.storage.node.field_blog_image.yml | 37 +++ .../field.storage.node.field_blog_tags.yml | 17 ++ .../config/install/node.type.blog_post.yml | 10 + .../install/site_users_blog.settings.yml | 1 + .../install/taxonomy.vocabulary.blog_tags.yml | 8 + .../config/install/views.view.user_blog.yml | 235 ++++++++++++++++++ .../optional/pathauto.pattern.blog_post.yml | 20 ++ .../config/schema/site_users_blog.schema.yml | 9 + .../site_users_blog/site_users_blog.info.yml | 12 + .../site_users_blog/site_users_blog.install | 79 ++++++ .../site_users_blog.links.menu.yml | 15 ++ .../site_users_blog/site_users_blog.module | 102 ++++++++ .../site_users_blog.permissions.yml | 3 + .../site_users_blog.routing.yml | 7 + .../src/Form/BlogSettingsForm.php | 63 +++++ .../ParentEntityHandler/BlogUserHandler.php | 61 +++++ .../site_users_microsite.services.yml | 2 +- .../src/Theme/MicrositeThemeNegotiator.php | 10 +- 24 files changed, 931 insertions(+), 2 deletions(-) create mode 100644 modules/site_users_blog/config/install/core.entity_form_display.node.blog_post.default.yml create mode 100644 modules/site_users_blog/config/install/core.entity_view_display.node.blog_post.default.yml create mode 100644 modules/site_users_blog/config/install/core.entity_view_display.node.blog_post.teaser.yml create mode 100644 modules/site_users_blog/config/install/field.field.node.blog_post.field_blog_image.yml create mode 100644 modules/site_users_blog/config/install/field.field.node.blog_post.field_blog_tags.yml create mode 100644 modules/site_users_blog/config/install/field.field.node.blog_post.field_site_section.yml create mode 100644 modules/site_users_blog/config/install/field.storage.node.field_blog_image.yml create mode 100644 modules/site_users_blog/config/install/field.storage.node.field_blog_tags.yml create mode 100644 modules/site_users_blog/config/install/node.type.blog_post.yml create mode 100644 modules/site_users_blog/config/install/site_users_blog.settings.yml create mode 100644 modules/site_users_blog/config/install/taxonomy.vocabulary.blog_tags.yml create mode 100644 modules/site_users_blog/config/install/views.view.user_blog.yml create mode 100644 modules/site_users_blog/config/optional/pathauto.pattern.blog_post.yml create mode 100644 modules/site_users_blog/config/schema/site_users_blog.schema.yml create mode 100644 modules/site_users_blog/site_users_blog.info.yml create mode 100644 modules/site_users_blog/site_users_blog.install create mode 100644 modules/site_users_blog/site_users_blog.links.menu.yml create mode 100644 modules/site_users_blog/site_users_blog.module create mode 100644 modules/site_users_blog/site_users_blog.permissions.yml create mode 100644 modules/site_users_blog/site_users_blog.routing.yml create mode 100644 modules/site_users_blog/src/Form/BlogSettingsForm.php create mode 100644 modules/site_users_blog/src/Plugin/ParentEntityHandler/BlogUserHandler.php diff --git a/modules/site_users_blog/config/install/core.entity_form_display.node.blog_post.default.yml b/modules/site_users_blog/config/install/core.entity_form_display.node.blog_post.default.yml new file mode 100644 index 0000000..6931a71 --- /dev/null +++ b/modules/site_users_blog/config/install/core.entity_form_display.node.blog_post.default.yml @@ -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 diff --git a/modules/site_users_blog/config/install/core.entity_view_display.node.blog_post.default.yml b/modules/site_users_blog/config/install/core.entity_view_display.node.blog_post.default.yml new file mode 100644 index 0000000..9444b11 --- /dev/null +++ b/modules/site_users_blog/config/install/core.entity_view_display.node.blog_post.default.yml @@ -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 diff --git a/modules/site_users_blog/config/install/core.entity_view_display.node.blog_post.teaser.yml b/modules/site_users_blog/config/install/core.entity_view_display.node.blog_post.teaser.yml new file mode 100644 index 0000000..cc289f7 --- /dev/null +++ b/modules/site_users_blog/config/install/core.entity_view_display.node.blog_post.teaser.yml @@ -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 diff --git a/modules/site_users_blog/config/install/field.field.node.blog_post.field_blog_image.yml b/modules/site_users_blog/config/install/field.field.node.blog_post.field_blog_image.yml new file mode 100644 index 0000000..195e3d1 --- /dev/null +++ b/modules/site_users_blog/config/install/field.field.node.blog_post.field_blog_image.yml @@ -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 diff --git a/modules/site_users_blog/config/install/field.field.node.blog_post.field_blog_tags.yml b/modules/site_users_blog/config/install/field.field.node.blog_post.field_blog_tags.yml new file mode 100644 index 0000000..194e317 --- /dev/null +++ b/modules/site_users_blog/config/install/field.field.node.blog_post.field_blog_tags.yml @@ -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 diff --git a/modules/site_users_blog/config/install/field.field.node.blog_post.field_site_section.yml b/modules/site_users_blog/config/install/field.field.node.blog_post.field_site_section.yml new file mode 100644 index 0000000..8bf1a25 --- /dev/null +++ b/modules/site_users_blog/config/install/field.field.node.blog_post.field_site_section.yml @@ -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 diff --git a/modules/site_users_blog/config/install/field.storage.node.field_blog_image.yml b/modules/site_users_blog/config/install/field.storage.node.field_blog_image.yml new file mode 100644 index 0000000..e08c995 --- /dev/null +++ b/modules/site_users_blog/config/install/field.storage.node.field_blog_image.yml @@ -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 diff --git a/modules/site_users_blog/config/install/field.storage.node.field_blog_tags.yml b/modules/site_users_blog/config/install/field.storage.node.field_blog_tags.yml new file mode 100644 index 0000000..8970883 --- /dev/null +++ b/modules/site_users_blog/config/install/field.storage.node.field_blog_tags.yml @@ -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 diff --git a/modules/site_users_blog/config/install/node.type.blog_post.yml b/modules/site_users_blog/config/install/node.type.blog_post.yml new file mode 100644 index 0000000..d4233e4 --- /dev/null +++ b/modules/site_users_blog/config/install/node.type.blog_post.yml @@ -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 diff --git a/modules/site_users_blog/config/install/site_users_blog.settings.yml b/modules/site_users_blog/config/install/site_users_blog.settings.yml new file mode 100644 index 0000000..5742749 --- /dev/null +++ b/modules/site_users_blog/config/install/site_users_blog.settings.yml @@ -0,0 +1 @@ +allowed_roles: {} diff --git a/modules/site_users_blog/config/install/taxonomy.vocabulary.blog_tags.yml b/modules/site_users_blog/config/install/taxonomy.vocabulary.blog_tags.yml new file mode 100644 index 0000000..e5bcb27 --- /dev/null +++ b/modules/site_users_blog/config/install/taxonomy.vocabulary.blog_tags.yml @@ -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 diff --git a/modules/site_users_blog/config/install/views.view.user_blog.yml b/modules/site_users_blog/config/install/views.view.user_blog.yml new file mode 100644 index 0000000..442b286 --- /dev/null +++ b/modules/site_users_blog/config/install/views.view.user_blog.yml @@ -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 diff --git a/modules/site_users_blog/config/optional/pathauto.pattern.blog_post.yml b/modules/site_users_blog/config/optional/pathauto.pattern.blog_post.yml new file mode 100644 index 0000000..59ab258 --- /dev/null +++ b/modules/site_users_blog/config/optional/pathauto.pattern.blog_post.yml @@ -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: {} diff --git a/modules/site_users_blog/config/schema/site_users_blog.schema.yml b/modules/site_users_blog/config/schema/site_users_blog.schema.yml new file mode 100644 index 0000000..7dad947 --- /dev/null +++ b/modules/site_users_blog/config/schema/site_users_blog.schema.yml @@ -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 diff --git a/modules/site_users_blog/site_users_blog.info.yml b/modules/site_users_blog/site_users_blog.info.yml new file mode 100644 index 0000000..435af32 --- /dev/null +++ b/modules/site_users_blog/site_users_blog.info.yml @@ -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 diff --git a/modules/site_users_blog/site_users_blog.install b/modules/site_users_blog/site_users_blog.install new file mode 100644 index 0000000..3385906 --- /dev/null +++ b/modules/site_users_blog/site_users_blog.install @@ -0,0 +1,79 @@ +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."; +} diff --git a/modules/site_users_blog/site_users_blog.links.menu.yml b/modules/site_users_blog/site_users_blog.links.menu.yml new file mode 100644 index 0000000..8bb36a4 --- /dev/null +++ b/modules/site_users_blog/site_users_blog.links.menu.yml @@ -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 diff --git a/modules/site_users_blog/site_users_blog.module b/modules/site_users_blog/site_users_blog.module new file mode 100644 index 0000000..7c852a9 --- /dev/null +++ b/modules/site_users_blog/site_users_blog.module @@ -0,0 +1,102 @@ +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' => [], + ]; +} diff --git a/modules/site_users_blog/site_users_blog.permissions.yml b/modules/site_users_blog/site_users_blog.permissions.yml new file mode 100644 index 0000000..e9ff158 --- /dev/null +++ b/modules/site_users_blog/site_users_blog.permissions.yml @@ -0,0 +1,3 @@ +administer site_users_blog settings: + title: 'Administrar configurações do blog de usuário' + restrict access: true diff --git a/modules/site_users_blog/site_users_blog.routing.yml b/modules/site_users_blog/site_users_blog.routing.yml new file mode 100644 index 0000000..d829bb6 --- /dev/null +++ b/modules/site_users_blog/site_users_blog.routing.yml @@ -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' diff --git a/modules/site_users_blog/src/Form/BlogSettingsForm.php b/modules/site_users_blog/src/Form/BlogSettingsForm.php new file mode 100644 index 0000000..d89ac42 --- /dev/null +++ b/modules/site_users_blog/src/Form/BlogSettingsForm.php @@ -0,0 +1,63 @@ +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); + } + +} diff --git a/modules/site_users_blog/src/Plugin/ParentEntityHandler/BlogUserHandler.php b/modules/site_users_blog/src/Plugin/ParentEntityHandler/BlogUserHandler.php new file mode 100644 index 0000000..3c8e832 --- /dev/null +++ b/modules/site_users_blog/src/Plugin/ParentEntityHandler/BlogUserHandler.php @@ -0,0 +1,61 @@ +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; + } + +} diff --git a/modules/site_users_microsite/site_users_microsite.services.yml b/modules/site_users_microsite/site_users_microsite.services.yml index da26279..957c389 100644 --- a/modules/site_users_microsite/site_users_microsite.services.yml +++ b/modules/site_users_microsite/site_users_microsite.services.yml @@ -8,7 +8,7 @@ services: site_users_microsite.theme_negotiator: class: Drupal\site_users_microsite\Theme\MicrositeThemeNegotiator - arguments: ['@path_alias.manager'] + arguments: ['@path_alias.manager', '@path.current'] tags: - { name: theme_negotiator, priority: 100 } diff --git a/modules/site_users_microsite/src/Theme/MicrositeThemeNegotiator.php b/modules/site_users_microsite/src/Theme/MicrositeThemeNegotiator.php index cccfef1..14dbfb4 100644 --- a/modules/site_users_microsite/src/Theme/MicrositeThemeNegotiator.php +++ b/modules/site_users_microsite/src/Theme/MicrositeThemeNegotiator.php @@ -2,6 +2,7 @@ namespace Drupal\site_users_microsite\Theme; +use Drupal\Core\Path\CurrentPathStack; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Theme\ThemeNegotiatorInterface; use Drupal\path_alias\AliasManagerInterface; @@ -14,6 +15,7 @@ class MicrositeThemeNegotiator implements ThemeNegotiatorInterface { public function __construct( private AliasManagerInterface $aliasManager, + private CurrentPathStack $currentPath, ) {} /** @@ -55,7 +57,7 @@ class MicrositeThemeNegotiator implements ThemeNegotiatorInterface { } } - // Nós cujo alias começa com /user/{uid}/ (ex.: structural_pages). + // 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) { @@ -67,6 +69,12 @@ class MicrositeThemeNegotiator implements ThemeNegotiatorInterface { } } + // 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; }