From bd24e6eb6b8e117bfdacad94de404b7830cd4271 Mon Sep 17 00:00:00 2001 From: "Quintino A. G. Souza" Date: Thu, 26 Feb 2026 08:06:10 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20View=20mode=20por=20role/usu=C3=A1rio?= =?UTF-8?q?=20com=20seletor=20de=20visibilidade=20no=20perfil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cria view mode e display 'restricted' (template mostra só username + mensagem) - Adiciona campo field_user_selected_view_mode (string, default: restricted) - update_10006: provisiona view mode, display e campo; inicializa role_view_modes - Seletor de visibilidade no form de edição do perfil (owner e admin) - hook_entity_view_mode_alter lê o campo e valida existência do display - Formulário de admin: checkboxes de view modes por role (exceto anonymous e authenticated) - Schema YAML completo para site_users.settings incluindo role_view_modes - Tradução pt-BR de todas as novas strings Co-Authored-By: Claude Sonnet 4.6 --- config/install/site_users.settings.yml | 1 + ...ser.user.field_user_selected_view_mode.yml | 20 +++ ...age.user.field_user_selected_view_mode.yml | 20 +++ config/schema/site_users.schema.yml | 32 ++++ site_users.install | 99 +++++++++++ site_users.module | 155 +++++++++++++++++- src/Form/SiteUsersSettingsForm.php | 64 ++++++++ templates/user--restricted.html.twig | 12 ++ translations/site_users.pt-br.po | 30 ++++ 9 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 config/optional/field.field.user.user.field_user_selected_view_mode.yml create mode 100644 config/optional/field.storage.user.field_user_selected_view_mode.yml create mode 100644 config/schema/site_users.schema.yml create mode 100644 templates/user--restricted.html.twig diff --git a/config/install/site_users.settings.yml b/config/install/site_users.settings.yml index df9b794..d94e22b 100644 --- a/config/install/site_users.settings.yml +++ b/config/install/site_users.settings.yml @@ -8,3 +8,4 @@ user_editable_fields: field_user_bio: true field_user_social_links: true field_user_photos: true +role_view_modes: { } diff --git a/config/optional/field.field.user.user.field_user_selected_view_mode.yml b/config/optional/field.field.user.user.field_user_selected_view_mode.yml new file mode 100644 index 0000000..72ddda6 --- /dev/null +++ b/config/optional/field.field.user.user.field_user_selected_view_mode.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_user_selected_view_mode + module: + - user +id: user.user.field_user_selected_view_mode +field_name: field_user_selected_view_mode +entity_type: user +bundle: user +label: 'Profile visibility' +description: 'Controls how this profile appears to other visitors.' +required: false +translatable: false +default_value: + - value: restricted +default_value_callback: '' +settings: { } +field_type: string diff --git a/config/optional/field.storage.user.field_user_selected_view_mode.yml b/config/optional/field.storage.user.field_user_selected_view_mode.yml new file mode 100644 index 0000000..1cf10e0 --- /dev/null +++ b/config/optional/field.storage.user.field_user_selected_view_mode.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - user +id: user.field_user_selected_view_mode +field_name: field_user_selected_view_mode +entity_type: user +type: string +settings: + max_length: 64 + is_ascii: true + case_sensitive: false +module: core +locked: false +cardinality: 1 +translatable: false +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/schema/site_users.schema.yml b/config/schema/site_users.schema.yml new file mode 100644 index 0000000..acf8135 --- /dev/null +++ b/config/schema/site_users.schema.yml @@ -0,0 +1,32 @@ +site_users.settings: + type: config_object + label: 'Site Users settings' + mapping: + photos: + type: mapping + label: 'Photo settings' + mapping: + max_count: + type: integer + label: 'Maximum number of photos' + ldap_attribute: + type: string + label: 'LDAP photo attribute' + ldap_sync_enabled: + type: boolean + label: 'Enable LDAP photo synchronization' + user_editable_fields: + type: sequence + label: 'User-editable profile fields' + sequence: + type: boolean + label: 'Field editable' + role_view_modes: + type: sequence + label: 'Allowed view modes per role' + sequence: + type: sequence + label: 'View modes for role' + sequence: + type: string + label: 'View mode machine name' diff --git a/site_users.install b/site_users.install index 59180d8..9e87799 100644 --- a/site_users.install +++ b/site_users.install @@ -165,6 +165,27 @@ function site_users_install() { $view_display->save(); } + // Criar view mode 'restricted'. + $view_mode_storage = \Drupal::entityTypeManager()->getStorage('entity_view_mode'); + if (!$view_mode_storage->load('user.restricted')) { + \Drupal\Core\Entity\Entity\EntityViewMode::create([ + 'id' => 'user.restricted', + 'label' => 'Restricted', + 'targetEntityType' => 'user', + ])->save(); + } + + // Criar view display 'restricted' vazio (template controla a exibição). + $display_storage = \Drupal::entityTypeManager()->getStorage('entity_view_display'); + if (!$display_storage->load('user.user.restricted')) { + \Drupal\Core\Entity\Entity\EntityViewDisplay::create([ + 'targetEntityType' => 'user', + 'bundle' => 'user', + 'mode' => 'restricted', + 'status' => TRUE, + ])->save(); + } + // Conceder permissão "access user profiles" ao papel anonymous. $anonymous_role = \Drupal\user\Entity\Role::load('anonymous'); if ($anonymous_role && !$anonymous_role->hasPermission('access user profiles')) { @@ -388,6 +409,84 @@ function site_users_update_10003() { return t('Nenhuma correção necessária nos mapeamentos LDAP.'); } +/** + * Cria view mode 'restricted', campo field_user_selected_view_mode e config role_view_modes. + */ +function site_users_update_10006() { + $view_mode_storage = \Drupal::entityTypeManager()->getStorage('entity_view_mode'); + $display_storage = \Drupal::entityTypeManager()->getStorage('entity_view_display'); + + // 1. Criar view mode 'restricted'. + if (!$view_mode_storage->load('user.restricted')) { + \Drupal\Core\Entity\Entity\EntityViewMode::create([ + 'id' => 'user.restricted', + 'label' => 'Restricted', + 'targetEntityType' => 'user', + ])->save(); + } + + // 2. Criar view display 'restricted' vazio (template controla a exibição). + if (!$display_storage->load('user.user.restricted')) { + \Drupal\Core\Entity\Entity\EntityViewDisplay::create([ + 'targetEntityType' => 'user', + 'bundle' => 'user', + 'mode' => 'restricted', + 'status' => TRUE, + ])->save(); + } + + // 3. Criar field storage se não existir. + if (!FieldStorageConfig::loadByName('user', 'field_user_selected_view_mode')) { + FieldStorageConfig::create([ + 'field_name' => 'field_user_selected_view_mode', + 'entity_type' => 'user', + 'type' => 'string', + 'settings' => [ + 'max_length' => 64, + 'is_ascii' => TRUE, + 'case_sensitive' => FALSE, + ], + 'cardinality' => 1, + 'translatable' => FALSE, + ])->save(); + } + + // 4. Criar field instance se não existir. + if (!FieldConfig::loadByName('user', 'user', 'field_user_selected_view_mode')) { + FieldConfig::create([ + 'field_name' => 'field_user_selected_view_mode', + 'entity_type' => 'user', + 'bundle' => 'user', + 'label' => 'Profile visibility', + 'description' => 'Controls how this profile appears to other visitors.', + 'required' => FALSE, + 'default_value' => [['value' => 'restricted']], + ])->save(); + } + + // 5. Remover do form display. + $form_display = EntityFormDisplay::load('user.user.default'); + if ($form_display) { + $form_display->removeComponent('field_user_selected_view_mode')->save(); + } + + // 6. Remover de todos os view displays existentes. + $displays = $display_storage->loadMultiple(); + foreach ($displays as $display) { + if ($display->getTargetEntityTypeId() === 'user' && $display->getComponent('field_user_selected_view_mode')) { + $display->removeComponent('field_user_selected_view_mode')->save(); + } + } + + // 7. Adicionar role_view_modes ao config se ausente. + $config = \Drupal::configFactory()->getEditable('site_users.settings'); + if ($config->get('role_view_modes') === NULL) { + $config->set('role_view_modes', [])->save(); + } + + return t("View mode 'restricted' e campo field_user_selected_view_mode criados."); +} + /** * Corrige mapeamentos LDAP com campos de string nulos na config ativa. */ diff --git a/site_users.module b/site_users.module index 795e61d..44305c8 100644 --- a/site_users.module +++ b/site_users.module @@ -28,6 +28,10 @@ function site_users_theme($existing, $type, $theme, $path) { 'template' => 'user--full', 'base hook' => 'user', ], + 'user__restricted' => [ + 'template' => 'user--restricted', + 'base hook' => 'user', + ], 'site_users_info_block' => [ 'template' => 'site-user-info-block', 'variables' => [ @@ -184,6 +188,131 @@ function site_users_form_user_form_alter(&$form, FormStateInterface $form_state, $form['#validate'][] = 'site_users_validate_photos_count'; _site_users_add_default_photo_selector($form, $form_state); } + _site_users_add_visibility_selector($form, $form_state); +} + +/** + * Adiciona o seletor de visibilidade do perfil ao formulário de usuário. + */ +function _site_users_add_visibility_selector(&$form, FormStateInterface $form_state) { + /** @var \Drupal\user\UserInterface $user */ + $user = $form_state->getFormObject()->getEntity(); + $current_user = \Drupal::currentUser(); + + $is_own = (int) $user->id() === (int) $current_user->id(); + $is_admin = $current_user->hasPermission('administer users'); + + if (!$is_own && !$is_admin) { + return; + } + + $options = _site_users_get_allowed_view_modes($user); + + // Always include 'restricted'. + $options = ['restricted' => t('Restricted (username only)')] + $options; + + $current_value = 'restricted'; + if ($user->hasField('field_user_selected_view_mode') && !$user->get('field_user_selected_view_mode')->isEmpty()) { + $current_value = $user->get('field_user_selected_view_mode')->value; + } + // Fallback to restricted if stored value is no longer allowed. + if (!isset($options[$current_value])) { + $current_value = 'restricted'; + } + + $form['profile_visibility'] = [ + '#type' => 'fieldset', + '#title' => t('Profile visibility'), + '#weight' => 20, + ]; + + $form['profile_visibility']['profile_view_mode'] = [ + '#type' => 'radios', + '#title' => t('How should my profile appear to other visitors?'), + '#description' => t('Choose the information displayed on your public profile page.'), + '#options' => $options, + '#default_value' => $current_value, + ]; + + // Prepend validate handler. + array_unshift($form['#validate'], '_site_users_validate_visibility_selector'); + + // Prepend submit handler before the default submit. + array_unshift($form['actions']['submit']['#submit'], '_site_users_presave_visibility_selector'); +} + +/** + * Retorna os view modes permitidos para os roles do usuário. + * + * @param \Drupal\user\UserInterface $user + * O usuário cujo perfil está sendo editado. + * + * @return array + * Associative array of machine_name => label. + */ +function _site_users_get_allowed_view_modes(UserInterface $user): array { + $config = \Drupal::config('site_users.settings'); + $role_view_modes = $config->get('role_view_modes') ?? []; + + $allowed = []; + foreach ($user->getRoles() as $role_id) { + if (!empty($role_view_modes[$role_id]) && is_array($role_view_modes[$role_id])) { + foreach ($role_view_modes[$role_id] as $vm) { + $allowed[$vm] = $vm; + } + } + } + + if (empty($allowed)) { + return []; + } + + // Load labels for the allowed view modes. + $view_mode_storage = \Drupal::entityTypeManager()->getStorage('entity_view_mode'); + $options = []; + foreach (array_keys($allowed) as $machine_name) { + $view_mode = $view_mode_storage->load('user.' . $machine_name); + $options[$machine_name] = $view_mode ? $view_mode->label() : $machine_name; + } + + return $options; +} + +/** + * Validate handler: confirma que o valor escolhido está na lista permitida. + */ +function _site_users_validate_visibility_selector(&$form, FormStateInterface $form_state) { + $chosen = $form_state->getValue('profile_view_mode'); + if ($chosen === NULL) { + return; + } + + /** @var \Drupal\user\UserInterface $user */ + $user = $form_state->getFormObject()->getEntity(); + $allowed = _site_users_get_allowed_view_modes($user); + $allowed['restricted'] = 'restricted'; + + if (!isset($allowed[$chosen])) { + $form_state->setErrorByName('profile_view_mode', t('Invalid profile visibility option selected.')); + } +} + +/** + * Submit handler (prepended): seta field_user_selected_view_mode no entity antes do save. + */ +function _site_users_presave_visibility_selector(&$form, FormStateInterface $form_state) { + $chosen = $form_state->getValue('profile_view_mode'); + if ($chosen === NULL) { + return; + } + + /** @var \Drupal\user\UserInterface $user */ + $user = $form_state->getFormObject()->getEntity(); + + if ($user->hasField('field_user_selected_view_mode')) { + $user->set('field_user_selected_view_mode', $chosen); + // Do not save here — the standard submit handler saves the entity. + } } /** @@ -338,9 +467,31 @@ function site_users_entity_view_mode_alter(string &$view_mode, EntityInterface $ $current_user = \Drupal::currentUser(); $is_own = (int) $entity->id() === (int) $current_user->id(); $is_admin = $current_user->hasPermission('administer users'); - if (!$is_own && !$is_admin) { - $view_mode = 'public'; + + if ($is_own || $is_admin) { + // Owner and admin always see the full view mode. + return; } + + // Read the chosen view mode from the profile being visited. + $chosen = 'restricted'; + if ($entity instanceof UserInterface + && $entity->hasField('field_user_selected_view_mode') + && !$entity->get('field_user_selected_view_mode')->isEmpty() + ) { + $chosen = $entity->get('field_user_selected_view_mode')->value; + } + + // Validate that a view display exists for the chosen value. + if ($chosen !== 'restricted') { + $display_id = 'user.user.' . $chosen; + $display_storage = \Drupal::entityTypeManager()->getStorage('entity_view_display'); + if (!$display_storage->load($display_id)) { + $chosen = 'restricted'; + } + } + + $view_mode = $chosen; } /** diff --git a/src/Form/SiteUsersSettingsForm.php b/src/Form/SiteUsersSettingsForm.php index c0a2b56..6b3db8e 100644 --- a/src/Form/SiteUsersSettingsForm.php +++ b/src/Form/SiteUsersSettingsForm.php @@ -145,9 +145,62 @@ class SiteUsersSettingsForm extends ConfigFormBase { ]; } + // Fieldset para view modes por role. + $form['role_view_modes'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Profile visibility options by role'), + '#description' => $this->t('Select which view modes each role can choose for their public profile. The "restricted" option is always available to all users.'), + '#tree' => TRUE, + ]; + + $available_view_modes = $this->getCustomUserViewModes(); + + if (empty($available_view_modes)) { + $form['role_view_modes']['_empty'] = [ + '#markup' => '

' . $this->t('No custom view modes found for user entities. Create view modes at Manage display.', [ + ':url' => '/admin/config/people/accounts/display', + ]) . '

', + ]; + } + else { + $roles = \Drupal\user\Entity\Role::loadMultiple(); + foreach ($roles as $role_id => $role) { + if (in_array($role_id, ['anonymous', 'authenticated'])) { + continue; + } + $form['role_view_modes'][$role_id] = [ + '#type' => 'checkboxes', + '#title' => $role->label(), + '#options' => $available_view_modes, + '#default_value' => $config->get('role_view_modes.' . $role_id) ?? [], + ]; + } + } + return parent::buildForm($form, $form_state); } + /** + * Returns custom user view modes (excluding 'default' and 'restricted'). + * + * @return array + * Associative array of machine_name => label. + */ + protected function getCustomUserViewModes(): array { + $view_mode_storage = \Drupal::entityTypeManager()->getStorage('entity_view_mode'); + $view_modes = $view_mode_storage->loadByProperties(['targetEntityType' => 'user']); + $options = []; + foreach ($view_modes as $view_mode) { + $id_parts = explode('.', $view_mode->id()); + $machine_name = end($id_parts); + if ($machine_name === 'default' || $machine_name === 'restricted') { + continue; + } + $options[$machine_name] = $view_mode->label(); + } + return $options; + } + /** * {@inheritdoc} */ @@ -177,6 +230,17 @@ class SiteUsersSettingsForm extends ConfigFormBase { } } + // Salvar role_view_modes: apenas os valores marcados (filtrar 0). + $role_view_modes_raw = $form_state->getValue('role_view_modes') ?? []; + $roles = \Drupal\user\Entity\Role::loadMultiple(); + foreach ($roles as $role_id => $role) { + if ($role_id === 'anonymous' || !isset($role_view_modes_raw[$role_id])) { + continue; + } + $selected = array_values(array_filter($role_view_modes_raw[$role_id])); + $config->set('role_view_modes.' . $role_id, $selected); + } + $config->save(); parent::submitForm($form, $form_state); diff --git a/templates/user--restricted.html.twig b/templates/user--restricted.html.twig new file mode 100644 index 0000000..6d44b42 --- /dev/null +++ b/templates/user--restricted.html.twig @@ -0,0 +1,12 @@ +{# +/** + * @file + * Template for restricted user profile view mode. + * + * Shows only the display name and a message that the profile is not public. + */ +#} + +

{{ user.displayname }}

+

{{ 'This user has not made their profile public.'|t }}

+ diff --git a/translations/site_users.pt-br.po b/translations/site_users.pt-br.po index 733486c..84c8b06 100644 --- a/translations/site_users.pt-br.po +++ b/translations/site_users.pt-br.po @@ -138,6 +138,36 @@ msgstr "Selecione quais campos os usuários podem editar no próprio perfil. Cam msgid "Protected by another module." msgstr "Protegido por outro módulo." +# Settings form - role view modes +msgid "Profile visibility options by role" +msgstr "Opções de visibilidade do perfil por papel" + +msgid "Select which view modes each role can choose for their public profile. The \"restricted\" option is always available to all users." +msgstr "Selecione quais modos de exibição cada papel pode escolher para o seu perfil público. A opção \"restrito\" está sempre disponível para todos os usuários." + +msgid "No custom view modes found for user entities. Create view modes at Manage display." +msgstr "Nenhum modo de exibição customizado encontrado para usuários. Crie modos de exibição em Gerenciar exibição." + +# Form elements - visibility selector (site_users.module) +msgid "Profile visibility" +msgstr "Visibilidade do perfil" + +msgid "How should my profile appear to other visitors?" +msgstr "Como meu perfil deve aparecer para outros visitantes?" + +msgid "Choose the information displayed on your public profile page." +msgstr "Escolha as informações exibidas na página pública do seu perfil." + +msgid "Restricted (username only)" +msgstr "Restrito (somente nome de usuário)" + +msgid "Invalid profile visibility option selected." +msgstr "Opção de visibilidade de perfil inválida." + +# Template - restricted profile +msgid "This user has not made their profile public." +msgstr "Este usuário não tornou seu perfil público." + # Template msgid "Phone:" msgstr "Telefone:"