Files
site_users/site_users.module
Quintino A. G. Souza 5eca037739 feat: Configuração de campos editáveis pelo próprio usuário
Adiciona seção na tela de configurações do módulo que permite ao
administrador controlar quais campos do perfil cada usuário pode
editar no próprio perfil, independentemente das permissões de papel.

- site_users.settings.yml: novo grupo user_editable_fields (todos
  habilitados por padrão)
- SiteUsersSettingsForm: fieldset com checkboxes por campo
- site_users.module: site_users_check_profile_field_access() e
  site_users_check_photo_field_access() recebem field_name e
  consultam a config ao verificar 'edit own'; resultado inclui
  cache tag config:site_users.settings
- translations/site_users.pt-br.po: novas strings traduzidas

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 14:22:11 -03:00

360 lines
12 KiB
Plaintext

<?php
/**
* @file
* Módulo Site Users - customizações de usuários do site.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\media\MediaInterface;
use Drupal\user\UserInterface;
/**
* Implements hook_theme().
*/
function site_users_theme($existing, $type, $theme, $path) {
return [
'user__full' => [
'template' => 'user--full',
'base hook' => 'user',
],
'site_users_info_block' => [
'template' => 'site-user-info-block',
'variables' => [
'user_info' => [],
'user' => NULL,
],
],
];
}
/**
* Implements hook_theme_suggestions_HOOK_alter() for user templates.
*/
function site_users_theme_suggestions_user_alter(array &$suggestions, array $variables) {
$view_mode = $variables['elements']['#view_mode'] ?? 'default';
$suggestions[] = 'user__' . $view_mode;
}
/**
* Implements hook_entity_field_access().
*/
function site_users_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) {
// Apenas para entidade user.
if ($field_definition->getTargetEntityTypeId() !== 'user') {
return AccessResult::neutral();
}
// Lista de campos controlados pelo módulo.
$profile_fields = [
'field_user_name',
'field_user_phone',
'field_user_social_links',
'field_user_bio',
];
$photo_fields = [
'field_user_photos',
'field_user_default_photo',
];
$field_name = $field_definition->getName();
// Verificar se é um campo de perfil.
if (in_array($field_name, $profile_fields)) {
return site_users_check_profile_field_access($operation, $account, $items, $field_name);
}
// Verificar se é um campo de fotos.
if (in_array($field_name, $photo_fields)) {
return site_users_check_photo_field_access($operation, $account, $items, $field_name);
}
return AccessResult::neutral();
}
/**
* Verifica acesso aos campos de perfil.
*/
function site_users_check_profile_field_access($operation, AccountInterface $account, ?FieldItemListInterface $items = NULL, string $field_name = '') {
// Administradores têm acesso total.
if ($account->hasPermission('administer site_users settings')) {
return AccessResult::allowed()->cachePerPermissions();
}
// Determinar se é o próprio usuário.
$is_own = FALSE;
if ($items) {
$entity = $items->getEntity();
$is_own = ($entity->id() == $account->id());
}
if ($operation === 'view') {
// Pode ver qualquer perfil.
if ($account->hasPermission('view any user profile fields')) {
return AccessResult::allowed()->cachePerPermissions();
}
// Pode ver apenas o próprio perfil.
if ($is_own && $account->hasPermission('view own user profile fields')) {
return AccessResult::allowed()->cachePerPermissions()->cachePerUser();
}
return AccessResult::forbidden()->cachePerPermissions()->cachePerUser();
}
if ($operation === 'edit') {
// Pode editar qualquer perfil.
if ($account->hasPermission('edit any user profile fields')) {
return AccessResult::allowed()->cachePerPermissions();
}
// Pode editar apenas o próprio perfil, se o campo estiver habilitado na config.
if ($is_own && $account->hasPermission('edit own user profile fields')) {
$config = \Drupal::config('site_users.settings');
$field_enabled = $config->get('user_editable_fields.' . $field_name) ?? TRUE;
if ($field_enabled) {
return AccessResult::allowed()->cachePerPermissions()->cachePerUser()->addCacheTags(['config:site_users.settings']);
}
return AccessResult::forbidden()->cachePerPermissions()->cachePerUser()->addCacheTags(['config:site_users.settings']);
}
return AccessResult::forbidden()->cachePerPermissions()->cachePerUser();
}
return AccessResult::neutral();
}
/**
* Verifica acesso ao campo de fotos.
*/
function site_users_check_photo_field_access($operation, AccountInterface $account, ?FieldItemListInterface $items = NULL, string $field_name = '') {
// Administradores têm acesso total.
if ($account->hasPermission('administer site_users settings')) {
return AccessResult::allowed()->cachePerPermissions();
}
// Determinar se é o próprio usuário.
$is_own = FALSE;
if ($items) {
$entity = $items->getEntity();
$is_own = ($entity->id() == $account->id());
}
if ($operation === 'view') {
// Fotos seguem a mesma regra dos campos de perfil para visualização.
if ($account->hasPermission('view any user profile fields')) {
return AccessResult::allowed()->cachePerPermissions();
}
if ($is_own && $account->hasPermission('view own user profile fields')) {
return AccessResult::allowed()->cachePerPermissions()->cachePerUser();
}
return AccessResult::forbidden()->cachePerPermissions()->cachePerUser();
}
if ($operation === 'edit') {
// Pode gerenciar fotos de qualquer usuário.
if ($account->hasPermission('manage user photos')) {
return AccessResult::allowed()->cachePerPermissions();
}
// Pode gerenciar apenas as próprias fotos, se o campo estiver habilitado na config.
if ($is_own && $account->hasPermission('manage own user photos')) {
$config = \Drupal::config('site_users.settings');
$field_enabled = $config->get('user_editable_fields.' . $field_name) ?? TRUE;
if ($field_enabled) {
return AccessResult::allowed()->cachePerPermissions()->cachePerUser()->addCacheTags(['config:site_users.settings']);
}
return AccessResult::forbidden()->cachePerPermissions()->cachePerUser()->addCacheTags(['config:site_users.settings']);
}
return AccessResult::forbidden()->cachePerPermissions()->cachePerUser();
}
return AccessResult::neutral();
}
/**
* Implements hook_form_FORM_ID_alter() for user_form.
*/
function site_users_form_user_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if (isset($form['field_user_photos'])) {
$form['#validate'][] = 'site_users_validate_photos_count';
_site_users_add_default_photo_selector($form, $form_state);
}
}
/**
* Implements hook_form_FORM_ID_alter() for user_register_form.
*/
function site_users_form_user_register_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if (isset($form['field_user_photos'])) {
$form['#validate'][] = 'site_users_validate_photos_count';
_site_users_add_default_photo_selector($form, $form_state);
}
}
/**
* Adiciona o seletor de foto padrão ao formulário.
*/
function _site_users_add_default_photo_selector(&$form, FormStateInterface $form_state) {
/** @var \Drupal\user\UserInterface $user */
$user = $form_state->getFormObject()->getEntity();
// Obter fotos atuais do usuário.
$photos = [];
if ($user->hasField('field_user_photos') && !$user->get('field_user_photos')->isEmpty()) {
foreach ($user->get('field_user_photos')->referencedEntities() as $media) {
if ($media instanceof MediaInterface) {
$photos[$media->id()] = $media->label();
}
}
}
// Obter foto padrão atual.
$default_photo_id = NULL;
if ($user->hasField('field_user_default_photo') && !$user->get('field_user_default_photo')->isEmpty()) {
$default_photo_id = $user->get('field_user_default_photo')->target_id;
}
// Adicionar seletor de foto padrão após o campo de fotos.
$form['default_photo_selector'] = [
'#type' => 'container',
'#weight' => $form['field_user_photos']['#weight'] + 0.5 ?? 15,
'#states' => [
'visible' => [
':input[name="field_user_photos[selection]"]' => ['filled' => TRUE],
],
],
];
if (!empty($photos)) {
$form['default_photo_selector']['field_user_default_photo_select'] = [
'#type' => 'radios',
'#title' => t('Default photo'),
'#description' => t('Select the main profile photo.'),
'#options' => $photos,
'#default_value' => $default_photo_id,
];
}
else {
$form['default_photo_selector']['field_user_default_photo_select'] = [
'#type' => 'item',
'#title' => t('Default photo'),
'#markup' => t('Add photos to select one as default.'),
];
}
// Adicionar submit handler para salvar a foto padrão.
$form['actions']['submit']['#submit'][] = '_site_users_save_default_photo';
}
/**
* Submit handler para salvar a foto padrão selecionada.
*/
function _site_users_save_default_photo(&$form, FormStateInterface $form_state) {
$selected_photo = $form_state->getValue('field_user_default_photo_select');
/** @var \Drupal\user\UserInterface $user */
$user = $form_state->getFormObject()->getEntity();
if ($user->hasField('field_user_default_photo')) {
$user->set('field_user_default_photo', $selected_photo);
$user->save();
}
}
/**
* Validação customizada para quantidade máxima de fotos.
*/
function site_users_validate_photos_count(&$form, FormStateInterface $form_state) {
$photos = $form_state->getValue('field_user_photos');
if (empty($photos)) {
return;
}
// Contar fotos selecionadas (ignorar valores vazios).
$count = 0;
foreach ($photos as $delta => $photo) {
if (is_numeric($delta) && !empty($photo['target_id'])) {
$count++;
}
}
// Obter limite configurado.
$config = \Drupal::config('site_users.settings');
$max_count = $config->get('photos.max_count') ?? 5;
if ($count > $max_count) {
$form_state->setErrorByName('field_user_photos', t('You can add a maximum of @max photos. Currently @count photos are selected.', [
'@max' => $max_count,
'@count' => $count,
]));
}
}
/**
* Implements hook_ENTITY_TYPE_presave() for user entities.
*/
function site_users_user_presave(UserInterface $user) {
// Garantir consistência da foto padrão.
if (!$user->hasField('field_user_photos') || !$user->hasField('field_user_default_photo')) {
return;
}
// Obter IDs das fotos atuais.
$photo_ids = [];
foreach ($user->get('field_user_photos')->getValue() as $item) {
if (!empty($item['target_id'])) {
$photo_ids[] = $item['target_id'];
}
}
// Obter foto padrão atual.
$default_photo_id = $user->get('field_user_default_photo')->target_id;
// Se não há fotos, limpar foto padrão.
if (empty($photo_ids)) {
$user->set('field_user_default_photo', NULL);
return;
}
// Se a foto padrão não está entre as fotos, selecionar a primeira.
if (empty($default_photo_id) || !in_array($default_photo_id, $photo_ids)) {
$user->set('field_user_default_photo', $photo_ids[0]);
}
}
/**
* Obtém a foto padrão de um usuário.
*
* @param \Drupal\user\UserInterface $user
* O usuário.
*
* @return \Drupal\media\MediaInterface|null
* A entidade de mídia da foto padrão, ou NULL se não houver.
*/
function site_users_get_default_photo(UserInterface $user): ?MediaInterface {
if (!$user->hasField('field_user_default_photo') || !$user->hasField('field_user_photos')) {
return NULL;
}
// Tentar obter a foto padrão configurada.
if (!$user->get('field_user_default_photo')->isEmpty()) {
$default_photo = $user->get('field_user_default_photo')->entity;
if ($default_photo instanceof MediaInterface) {
return $default_photo;
}
}
// Fallback: retornar a primeira foto disponível.
if (!$user->get('field_user_photos')->isEmpty()) {
$first_photo = $user->get('field_user_photos')->first()->entity;
if ($first_photo instanceof MediaInterface) {
return $first_photo;
}
}
return NULL;
}