Files
site_users/src/Form/SiteUsersSettingsForm.php
Quintino A. G. Souza 39de6a7493 Melhorias no microsite e sincronização de fotos LDAP
Fotos LDAP:
- Ignora sync quando conta ainda não tem UID (evitava URI compartilhada)
- Filtra fotos abaixo do tamanho mínimo configurável (padrão 10 KB)
- Adiciona campo ldap_min_photo_size nas configurações e schema
- Update 10010: remove fotos placeholder já existentes
- Update 10011: remove mídias com URI ldap_photo_.{ext} sem UID

Bloco de cabeçalho do microsite:
- Exibe departamento abaixo do nome, sem label, com link para a entidade
- Exibe telefone de trabalho (work_phone) no lugar de phone (restrito)

Página de perfil:
- Título fixo "Perfil de @name" via callback profileTitle()
- Exclui rota profile da substituição de título pelo nó homepage

Subpáginas com URL amigável:
- Adiciona MicrositeSubpagePathProcessor (inbound + outbound)
- Inbound: /user/{username}/{subpage} → /user/{uid}/{subpage}
- Outbound: /user/{uid}/{subpage} → /user/{username}/{subpage}
- Busca alias em todos os idiomas para contornar limitação do AliasManager

Tema do microsite em rotas externas:
- MicrositeThemeNegotiator cobre rotas com parâmetro user sob /user/{user}/
- Cobre nós do structural_pages cujo alias começa com /user/{uid}/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 15:29:40 -03:00

351 lines
12 KiB
PHP

<?php
namespace Drupal\site_users\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\field\FieldConfigInterface;
/**
* Formulário de configuração do módulo Site Users.
*/
class SiteUsersSettingsForm extends ConfigFormBase {
/**
* Returns all configurable user fields, discovered dynamically.
*
* @return array
* Associative array of field_name => label.
*/
protected function getEditableFields(): array {
$definitions = \Drupal::service('entity_field.manager')
->getFieldDefinitions('user', 'user');
$fields = [];
foreach ($definitions as $field_name => $definition) {
if ($definition instanceof FieldConfigInterface) {
$provider = $this->getFieldStorageProvider($field_name);
$fields[$field_name] = $this->t('@label [@module:@field]', [
'@label' => $definition->getLabel(),
'@module' => $provider,
'@field' => $field_name,
]);
}
}
ksort($fields);
return $fields;
}
/**
* Finds which module provides the field storage config file.
*
* @param string $field_name
* The field name.
*
* @return string
* The module name, or 'unknown' if not found.
*/
protected function getFieldStorageProvider(string $field_name): string {
$config_file = 'field.storage.user.' . $field_name . '.yml';
$module_handler = \Drupal::moduleHandler();
$root = \Drupal::root();
foreach ($module_handler->getModuleList() as $module_name => $module) {
foreach (['config/install', 'config/optional'] as $dir) {
if (file_exists($root . '/' . $module->getPath() . '/' . $dir . '/' . $config_file)) {
return $module_name;
}
}
}
return 'unknown';
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'site_users_settings_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['site_users.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('site_users.settings');
// Fieldset para configurações de fotos.
$form['photos'] = [
'#type' => 'fieldset',
'#title' => $this->t('Photo settings'),
'#collapsible' => FALSE,
];
$form['photos']['photos_max_count'] = [
'#type' => 'number',
'#title' => $this->t('Number of photos allowed'),
'#description' => $this->t('Maximum number of photos a user can add to the profile.'),
'#default_value' => $config->get('photos.max_count') ?? 5,
'#min' => 1,
'#max' => 100,
'#required' => TRUE,
];
$form['photos']['photos_ldap_sync_enabled'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable LDAP photo synchronization'),
'#description' => $this->t('When enabled, the user photo from LDAP is automatically added as the first profile photo on each login.'),
'#default_value' => $config->get('photos.ldap_sync_enabled') ?? FALSE,
];
$form['photos']['photos_ldap_attribute'] = [
'#type' => 'textfield',
'#title' => $this->t('LDAP photo attribute'),
'#description' => $this->t('Name of the LDAP attribute that contains the user photo (e.g., thumbnailPhoto, jpegPhoto).'),
'#default_value' => $config->get('photos.ldap_attribute') ?? 'jpegPhoto',
'#maxlength' => 255,
'#states' => [
'visible' => [
':input[name="photos_ldap_sync_enabled"]' => ['checked' => TRUE],
],
],
];
$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.
$form['user_editable_fields'] = [
'#type' => 'fieldset',
'#title' => $this->t('User-editable profile fields'),
'#description' => $this->t('Select which fields users can edit on their own profile. Fields protected by other modules remain non-editable regardless of this setting.'),
'#tree' => TRUE,
];
$definitions = \Drupal::service('entity_field.manager')
->getFieldDefinitions('user', 'user');
$access_handler = \Drupal::entityTypeManager()
->getAccessControlHandler('user');
foreach ($this->getEditableFields() as $field_name => $label) {
$is_protected = $access_handler
->fieldAccess('edit', $definitions[$field_name], NULL, NULL, TRUE)
->isForbidden();
$form['user_editable_fields'][$field_name] = [
'#type' => 'checkbox',
'#title' => $label,
'#default_value' => $config->get('user_editable_fields.' . $field_name) ?? TRUE,
'#disabled' => $is_protected,
'#description' => $is_protected ? $this->t('Protected by another module.') : '',
];
}
// 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' => '<p>' . $this->t('No custom view modes found for user entities. Create view modes at <a href=":url">Manage display</a>.', [
':url' => '/admin/config/people/accounts/display',
]) . '</p>',
];
}
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) ?? [],
];
}
}
// 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);
}
/**
* 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}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$config = $this->config('site_users.settings');
$config
->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_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')
->getFieldDefinitions('user', 'user');
$access_handler = \Drupal::entityTypeManager()
->getAccessControlHandler('user');
$editable = $form_state->getValue('user_editable_fields');
foreach (array_keys($this->getEditableFields()) as $field_name) {
$is_protected = $access_handler
->fieldAccess('edit', $definitions[$field_name], NULL, NULL, TRUE)
->isForbidden();
if ($is_protected) {
// Campos protegidos por outros módulos são sempre não-editáveis.
$config->set('user_editable_fields.' . $field_name, FALSE);
}
elseif (isset($editable[$field_name])) {
$config->set('user_editable_fields.' . $field_name, (bool) $editable[$field_name]);
}
}
// 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).
$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);
}
}