Move submódulos para modules/ seguindo convenção Drupal

ldap_departments_sync/ e ldap_research_groups_sync/ movidos para
modules/, padrão adotado por módulos contrib como Drupal Commerce.
Nenhum arquivo PHP ou YAML alterado — o Drupal descobre módulos
recursivamente pelo .info.yml independente do caminho.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 09:22:22 -03:00
parent c66edef0ac
commit 7e338677a3
98 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
# Changelog
All notable changes to the LDAP Departments Sync module will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0] - 2025-11-07
### Added
- Initial stable release of LDAP Departments Sync module
- LDAP server and query configuration with dynamic dropdowns
- Hierarchical group organization based on LDAP attributes
- Dynamic attribute mapping system for flexible field configuration
- User reference mapping with DN to Drupal User resolution
- Automatic search attribute detection from LDAP server configuration
- Comprehensive cron validation to prevent execution with incomplete configurations
- Administrative interface with management buttons for LDAP queries
- Extensive logging and error handling throughout the synchronization process
- Support for both manual and automatic synchronization via cron
### Features
- **LDAP Integration**: Full integration with `ldap_servers` and `ldap_query` modules
- **Dynamic Configuration**: AJAX-powered form with real-time updates based on selections
- **Hierarchical Groups**: Automatic parent-child relationship mapping from LDAP data
- **User Reference Mapping**: Convert LDAP DNs to Drupal User references automatically
- **Flexible Mapping**: Support for both simple text and user reference field mappings
- **Validation**: Comprehensive configuration validation before cron execution
- **Management Tools**: Direct links to edit LDAP queries
- **Error Recovery**: Robust error handling with detailed logging for troubleshooting
### Technical Details
- **Drupal Version**: Compatible with Drupal 11
- **Dependencies**: group, ldap_servers
- **Architecture**: Service-based with dependency injection
- **Configuration**: YAML-based with dynamic form interface
- **Security**: Defensive coding practices with proper input validation
### Configuration
- LDAP server selection with status verification
- Query selection filtered by server with real-time updates
- Dynamic attribute mapping with type-specific processing
- Hierarchical organization with parent/child attribute configuration
- User reference mapping with automatic search attribute detection
[1.0.0]: https://github.com/your-repo/ldap_departments_sync/releases/tag/v1.0.0

View File

@@ -0,0 +1,184 @@
# LDAP Departments Sync
Módulo Drupal para sincronização de departamentos do LDAP com grupos do Drupal.
## Funcionalidades
- Sincronização automática de departamentos do LDAP para grupos no Drupal
- Mapeamento flexível de atributos LDAP para campos de grupo
- Sincronização de membros de departamentos baseada em campos de usuário
- Suporte a hierarquia de departamentos
- Mapeamento dinâmico de papéis (roles) nos grupos baseado em campos
- Campos de referência a departamentos reutilizáveis para content types
## Requisitos
- Drupal 10.x ou 11.x
- Módulo LDAP (ldap_servers, ldap_query)
- Módulo Group
- Módulo Entity Reference Views Select (opcional, para widgets avançados)
- Módulo Telephone (para campo de telefone)
- Módulo Profile (opcional, para usar field_department em profiles)
## Instalação
1. Coloque o módulo no diretório `modules/custom/ldap_departments_sync`
2. Habilite o módulo: `drush en ldap_departments_sync -y`
3. Configure em `/admin/config/ldap/ldap_departments_sync`
### Instalação da Configuração Padrão
O módulo inclui configurações opcionais que podem ser instaladas via interface:
1. Acesse `/admin/config/ldap/ldap_departments_sync`
2. Na seção "Tipo de Grupo", clique em "Instalar Configuração Padrão"
3. Isso criará:
- Tipo de grupo "Departments"
- Campos de departamento (código, sigla, tipo, telefone, sala, e-mail, coordenadores)
- Roles do grupo (admin, member, etc.)
- Displays de visualização e formulário
## Configuração
### Mapeamento de Atributos LDAP
Configure os mapeamentos entre atributos LDAP e campos de grupo em:
`/admin/config/ldap/ldap_departments_sync`
Exemplo de configuração padrão:
- `imeccDepartmentCode``field_dept_code`
- `description``label`
- `cn``field_dept_acronym`
- `imeccDepartmentType``field_dept_type`
- `imeccDepartmentCoord``field_dept_coord` (referência a usuário)
- `imeccDepartmentAssocCoord``field_dept_coord_assoc` (referência a usuário)
### Sincronização de Membros
O módulo pode sincronizar automaticamente membros para os grupos baseado em:
- Matching entre `field_user_dept_code` (usuário) e `field_dept_code` (grupo)
- Atribuição automática de roles baseado em campos de usuário
### Hierarquia de Departamentos
Para habilitar hierarquia:
1. Marque "Enable Hierarchy"
2. Configure:
- **Parent Attribute**: atributo LDAP do pai (ex: `departmentNumber`)
- **Child Attribute**: atributo LDAP do filho para matching (ex: `imeccDepartmentCode`)
## Adicionando Campos de Referência a Departamentos
O módulo fornece um **handler de seleção customizado** que facilita a criação de campos de referência a departamentos em qualquer entidade (nodes, users, profiles, etc.).
### Como Adicionar um Campo de Departamento
1. Acesse a página de gerenciamento de campos da entidade desejada:
- **Content Types**: `Structure > Content types > [Tipo] > Manage fields`
- **User**: `Configuration > People > Account settings > Manage fields`
- **Profiles**: `Configuration > People > Profile types > [Tipo] > Manage fields`
2. Clique em **"Create a new field"**
3. Em **"Add a new field"**:
- Selecione **"Reference"** como categoria
- Escolha **"Other..."** (ou "Outro...")
- Preencha o **Label** (ex: "Departamento", "Department", "Setor", etc.)
- Clique em **"Continue"**
4. Configure o field storage:
- **Type of item to reference**: Selecione **"Group"** (Grupo)
- Clique em **"Save field settings"**
5. Configure as definições do campo:
- **Reference method**: Selecione **"Department selection"**
- **Group type**: Automaticamente pré-selecionado como **"Departments"**
- **Filter by department type**: (Opcional) Marque para filtrar por tipo
- **Allowed department types**: (Opcional) Selecione tipos permitidos:
- ☐ Acadêmico
- ☐ Administrativo
- Configure outras opções conforme necessário:
- **Required**: Se o campo é obrigatório
- **Help text**: Texto de ajuda
- **Default value**: Valor padrão
- Clique em **"Save settings"**
### Vantagens desta Abordagem
-**Nomes customizáveis**: Escolha qualquer machine name para o campo
-**Múltiplos campos**: Adicione vários campos de departamento no mesmo bundle com propósitos diferentes
-**Filtragem por tipo**: Opção de filtrar apenas departamentos acadêmicos ou administrativos
-**Pré-configurado**: O handler "Department selection" já vem configurado com as opções corretas
-**Flexível**: Use em qualquer tipo de entidade (nodes, users, profiles, custom entities, etc.)
### Exemplos de Uso
**Múltiplos campos no mesmo content type:**
```
Article:
- field_primary_department (obrigatório, todos os tipos)
- field_secondary_department (opcional, todos os tipos)
- field_academic_dept (opcional, apenas acadêmicos)
```
**Diferentes configurações por bundle:**
```
Student Profile:
- field_department (obrigatório, apenas acadêmicos)
Staff Profile:
- field_department (obrigatório, apenas administrativos)
```
## Sincronização Manual
Execute via Drush:
```bash
drush ldap-departments-sync
```
Ou via interface em `/admin/config/ldap/ldap_departments_sync` clicando em "Sync Now".
## Sincronização Automática (Cron)
O módulo sincroniza automaticamente durante a execução do cron do Drupal.
## Estrutura de Campos do Grupo
Campos padrão criados para o group type "departments":
- `field_dept_code` - Código do departamento (texto, obrigatório, único)
- `field_dept_acronym` - Sigla/acrônimo
- `field_dept_type` - Tipo (acadêmico/administrativo)
- `field_dept_phone` - Telefone
- `field_dept_room` - Sala
- `field_dept_mail` - E-mail
- `field_dept_coord` - Coordenador (referência a usuário)
- `field_dept_coord_assoc` - Coordenador associado (referência a usuário)
- `field_parent_group` - Grupo pai (para hierarquia)
## Campos de Usuário
Campos necessários no user entity para sincronização de membros:
- `field_user_dept_code` - Código do departamento do usuário
- `field_user_department` - Referência ao grupo de departamento
## Desenvolvimento
### Hooks Disponíveis
(Documentação futura)
### API
(Documentação futura)
## Licença
GPL-2.0-or-later
## Autor
Desenvolvido para o IME - UNICAMP

View File

@@ -0,0 +1,127 @@
langcode: pt-br
status: true
dependencies:
config:
- field.field.group.departments.field_dept_acronym
- field.field.group.departments.field_dept_code
- field.field.group.departments.field_dept_coord
- field.field.group.departments.field_dept_coord_assoc
- field.field.group.departments.field_dept_mail
- field.field.group.departments.field_dept_phone
- field.field.group.departments.field_dept_room
- field.field.group.departments.field_dept_type
- field.field.group.departments.field_parent_group
- group.type.departments
module:
- path
id: group.departments.default
targetEntityType: group
bundle: departments
mode: default
content:
field_dept_acronym:
type: string_textfield
weight: 123
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
field_dept_code:
type: string_textfield
weight: 122
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
field_dept_coord:
type: entity_reference_autocomplete
weight: 128
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
field_dept_coord_assoc:
type: entity_reference_autocomplete
weight: 129
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
field_dept_mail:
type: email_default
weight: 125
region: content
settings:
placeholder: ''
size: 60
third_party_settings: { }
field_dept_phone:
type: string_textfield
weight: 124
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
field_dept_room:
type: string_textfield
weight: 121
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
field_dept_type:
type: options_select
weight: 127
region: content
settings: { }
third_party_settings: { }
field_parent_group:
type: entity_reference_autocomplete
weight: 20
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
label:
type: string_textfield
weight: -5
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
langcode:
type: language_select
weight: 2
region: content
settings:
include_locked: true
third_party_settings: { }
path:
type: path
weight: 30
region: content
settings: { }
third_party_settings: { }
status:
type: boolean_checkbox
weight: 120
region: content
settings:
display_label: true
third_party_settings: { }
hidden:
uid: true

View File

@@ -0,0 +1,107 @@
langcode: pt-br
status: true
dependencies:
config:
- field.field.group.departments.field_dept_acronym
- field.field.group.departments.field_dept_code
- field.field.group.departments.field_dept_coord
- field.field.group.departments.field_dept_coord_assoc
- field.field.group.departments.field_dept_mail
- field.field.group.departments.field_dept_phone
- field.field.group.departments.field_dept_room
- field.field.group.departments.field_dept_type
- field.field.group.departments.field_parent_group
- group.type.departments
module:
- options
id: group.departments.default
targetEntityType: group
bundle: departments
mode: default
content:
field_dept_acronym:
type: string
label: above
settings:
link_to_entity: false
third_party_settings: { }
weight: -2
region: content
field_dept_code:
type: string
label: above
settings:
link_to_entity: false
third_party_settings: { }
weight: -3
region: content
field_dept_coord:
type: entity_reference_label
label: above
settings:
link: true
third_party_settings: { }
weight: 22
region: content
field_dept_coord_assoc:
type: entity_reference_label
label: above
settings:
link: true
third_party_settings: { }
weight: 23
region: content
field_dept_mail:
type: basic_string
label: above
settings: { }
third_party_settings: { }
weight: 0
region: content
field_dept_phone:
type: string
label: above
settings:
link_to_entity: false
third_party_settings: { }
weight: -1
region: content
field_dept_room:
type: string
label: above
settings:
link_to_entity: false
third_party_settings: { }
weight: -4
region: content
field_dept_type:
type: list_default
label: above
settings: { }
third_party_settings: { }
weight: 21
region: content
field_parent_group:
type: entity_reference_label
label: above
settings:
link: true
third_party_settings: { }
weight: 20
region: content
label:
type: string
label: hidden
settings:
link_to_entity: false
third_party_settings: { }
weight: -5
region: content
hidden:
changed: true
created: true
entity_print_view_epub: true
entity_print_view_pdf: true
entity_print_view_word_docx: true
langcode: true
uid: true

View File

@@ -0,0 +1,68 @@
langcode: en
status: true
dependencies:
config:
- field.field.user.user.field_user_category
- field.field.user.user.field_user_department
- field.field.user.user.field_user_dept_code
- field.field.user.user.field_user_work_phone
- field.field.user.user.user_picture
- image.style.thumbnail
module:
- image
- telephone
- user
_core:
default_config_hash: mZLyuWM9CQx2ZJVqFGSbzgFnHzudVbHBYmdU256A5Wk
id: user.user.default
targetEntityType: user
bundle: user
mode: default
content:
field_user_category:
type: string
label: above
settings:
link_to_entity: false
third_party_settings: { }
weight: 3
region: content
field_user_department:
type: entity_reference_label
label: above
settings:
link: true
third_party_settings: { }
weight: 10
region: content
field_user_work_phone:
type: telephone_link
label: above
settings:
title: ''
third_party_settings: { }
weight: 4
region: content
member_for:
settings: { }
third_party_settings: { }
weight: 1
region: content
user_picture:
type: image
label: hidden
settings:
image_link: content
image_style: thumbnail
image_loading:
attribute: lazy
third_party_settings: { }
weight: 0
region: content
hidden:
entity_print_view_epub: true
entity_print_view_pdf: true
entity_print_view_word_docx: true
field_user_dept_code: true
langcode: true

View File

@@ -0,0 +1,18 @@
langcode: pt-br
status: true
dependencies:
config:
- field.storage.group.field_dept_acronym
- group.type.departments
id: group.departments.field_dept_acronym
field_name: field_dept_acronym
entity_type: group
bundle: departments
label: Sigla
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: string

View File

@@ -0,0 +1,18 @@
langcode: pt-br
status: true
dependencies:
config:
- field.storage.group.field_dept_code
- group.type.departments
id: group.departments.field_dept_code
field_name: field_dept_code
entity_type: group
bundle: departments
label: Código
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: string

View File

@@ -0,0 +1,28 @@
langcode: pt-br
status: true
dependencies:
config:
- field.storage.group.field_dept_coord
- group.type.departments
id: group.departments.field_dept_coord
field_name: field_dept_coord
entity_type: group
bundle: departments
label: Coordenador
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
handler: 'default:user'
handler_settings:
target_bundles: null
sort:
field: _none
direction: ASC
auto_create: false
filter:
type: _none
include_anonymous: false
field_type: entity_reference

View File

@@ -0,0 +1,28 @@
langcode: pt-br
status: true
dependencies:
config:
- field.storage.group.field_dept_coord_assoc
- group.type.departments
id: group.departments.field_dept_coord_assoc
field_name: field_dept_coord_assoc
entity_type: group
bundle: departments
label: 'Coordenador Associado'
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
handler: 'default:user'
handler_settings:
target_bundles: null
sort:
field: _none
direction: ASC
auto_create: false
filter:
type: _none
include_anonymous: false
field_type: entity_reference

View File

@@ -0,0 +1,18 @@
langcode: pt-br
status: true
dependencies:
config:
- field.storage.group.field_dept_mail
- group.type.departments
id: group.departments.field_dept_mail
field_name: field_dept_mail
entity_type: group
bundle: departments
label: Email
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: email

View File

@@ -0,0 +1,18 @@
langcode: pt-br
status: true
dependencies:
config:
- field.storage.group.field_dept_phone
- group.type.departments
id: group.departments.field_dept_phone
field_name: field_dept_phone
entity_type: group
bundle: departments
label: Telefone
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: string

View File

@@ -0,0 +1,18 @@
langcode: pt-br
status: true
dependencies:
config:
- field.storage.group.field_dept_room
- group.type.departments
id: group.departments.field_dept_room
field_name: field_dept_room
entity_type: group
bundle: departments
label: Sala
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: string

View File

@@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
config:
- field.storage.group.field_dept_type
- group.type.departments
module:
- options
id: group.departments.field_dept_type
field_name: field_dept_type
entity_type: group
bundle: departments
label: 'Tipo'
description: 'Tipo do departamento (Administrativo ou Acadêmico)'
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: list_string

View File

@@ -0,0 +1,27 @@
langcode: pt-br
status: true
dependencies:
config:
- field.storage.group.field_parent_group
- group.type.departments
id: group.departments.field_parent_group
field_name: field_parent_group
entity_type: group
bundle: departments
label: 'Órgão superior'
description: 'Grupo pai na hierarquia de departamentos'
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
handler: 'default:group'
handler_settings:
target_bundles:
departments: departments
sort:
field: _none
direction: ASC
auto_create: false
auto_create_bundle: ''
field_type: entity_reference

View File

@@ -0,0 +1,19 @@
langcode: en
status: true
dependencies:
config:
- field.storage.user.field_user_category
module:
- user
id: user.user.field_user_category
field_name: field_user_category
entity_type: user
bundle: user
label: Category
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: string

View File

@@ -0,0 +1,24 @@
langcode: en
status: true
dependencies:
config:
- field.storage.user.field_user_department
module:
- user
id: user.user.field_user_department
field_name: field_user_department
entity_type: user
bundle: user
label: Departamento
description: 'Departamento do usuário sincronizado do LDAP'
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
handler: views
handler_settings:
view:
view_name: departments
display_name: entity_reference_1
field_type: entity_reference

View File

@@ -0,0 +1,19 @@
langcode: en
status: true
dependencies:
config:
- field.storage.user.field_user_dept_code
module:
- user
id: user.user.field_user_dept_code
field_name: field_user_dept_code
entity_type: user
bundle: user
label: 'Department Code'
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: string

View File

@@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
config:
- field.storage.user.field_user_work_phone
module:
- telephone
- user
id: user.user.field_user_work_phone
field_name: field_user_work_phone
entity_type: user
bundle: user
label: 'Work Phone'
description: 'Work phone number, populated from LDAP.'
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: telephone

View File

@@ -0,0 +1,20 @@
langcode: pt-br
status: true
dependencies:
module:
- group
id: group.field_dept_acronym
field_name: field_dept_acronym
entity_type: group
type: string
settings:
max_length: 255
case_sensitive: false
is_ascii: false
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,20 @@
langcode: pt-br
status: true
dependencies:
module:
- group
id: group.field_dept_code
field_name: field_dept_code
entity_type: group
type: string
settings:
max_length: 255
case_sensitive: false
is_ascii: false
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,19 @@
langcode: pt-br
status: true
dependencies:
module:
- group
- user
id: group.field_dept_coord
field_name: field_dept_coord
entity_type: group
type: entity_reference
settings:
target_type: user
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,19 @@
langcode: pt-br
status: true
dependencies:
module:
- group
- user
id: group.field_dept_coord_assoc
field_name: field_dept_coord_assoc
entity_type: group
type: entity_reference
settings:
target_type: user
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,17 @@
langcode: pt-br
status: true
dependencies:
module:
- group
id: group.field_dept_mail
field_name: field_dept_mail
entity_type: group
type: email
settings: { }
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,20 @@
langcode: pt-br
status: true
dependencies:
module:
- group
id: group.field_dept_phone
field_name: field_dept_phone
entity_type: group
type: string
settings:
max_length: 255
case_sensitive: false
is_ascii: false
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,20 @@
langcode: pt-br
status: true
dependencies:
module:
- group
id: group.field_dept_room
field_name: field_dept_room
entity_type: group
type: string
settings:
max_length: 255
case_sensitive: false
is_ascii: false
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,26 @@
langcode: pt-br
status: true
dependencies:
module:
- group
- options
id: group.field_dept_type
field_name: field_dept_type
entity_type: group
type: list_string
settings:
allowed_values:
-
value: academico
label: Acadêmico
-
value: administrativo
label: Administrativo
allowed_values_function: ''
module: options
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,18 @@
langcode: pt-br
status: true
dependencies:
module:
- group
id: group.field_parent_group
field_name: field_parent_group
entity_type: group
type: entity_reference
settings:
target_type: group
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
module:
- user
id: user.field_user_category
field_name: field_user_category
entity_type: user
type: string
settings:
max_length: 255
case_sensitive: false
is_ascii: false
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,19 @@
langcode: en
status: true
dependencies:
module:
- group
- user
id: user.field_user_department
field_name: field_user_department
entity_type: user
type: entity_reference
settings:
target_type: group
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
module:
- user
id: user.field_user_dept_code
field_name: field_user_dept_code
entity_type: user
type: string
settings:
max_length: 255
case_sensitive: false
is_ascii: false
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,18 @@
langcode: en
status: true
dependencies:
module:
- telephone
- user
id: user.field_user_work_phone
field_name: field_user_work_phone
entity_type: user
type: telephone
settings: { }
module: telephone
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,45 @@
langcode: pt-br
status: true
dependencies:
config:
- group.type.departments
id: departments-admin
label: Admin
weight: 100
admin: true
scope: individual
global_role: null
group_type: departments
permissions:
- administer members
- delete group
- edit group
- leave group
- view group
- view unpublished group
- access group_media overview
- access group_node overview
- create group_node:article entity
- create group_node:page entity
- create group_node:webform entity
- delete any group_node:article entity
- delete any group_node:page entity
- delete any group_node:webform entity
- delete own group_node:article entity
- delete own group_node:page entity
- delete own group_node:webform entity
- update any group_node:article entity
- update any group_node:page entity
- update any group_node:webform entity
- update own group_node:article entity
- update own group_node:page entity
- update own group_node:webform entity
- view group_node:article entity
- view group_node:page entity
- view group_node:webform entity
- view own unpublished group_node:article entity
- view own unpublished group_node:page entity
- view own unpublished group_node:webform entity
- view unpublished group_node:article entity
- view unpublished group_node:page entity
- view unpublished group_node:webform entity

View File

@@ -0,0 +1,46 @@
langcode: pt-br
status: true
dependencies:
config:
- group.type.departments
- user.role.administrator
id: departments-admin_in
label: Administrador
weight: 102
admin: true
scope: insider
global_role: administrator
group_type: departments
permissions:
- administer members
- delete group
- edit group
- leave group
- view group
- view unpublished group
- access group_media overview
- access group_node overview
- create group_node:article entity
- create group_node:page entity
- create group_node:webform entity
- delete any group_node:article entity
- delete any group_node:page entity
- delete any group_node:webform entity
- delete own group_node:article entity
- delete own group_node:page entity
- delete own group_node:webform entity
- update any group_node:article entity
- update any group_node:page entity
- update any group_node:webform entity
- update own group_node:article entity
- update own group_node:page entity
- update own group_node:webform entity
- view group_node:article entity
- view group_node:page entity
- view group_node:webform entity
- view own unpublished group_node:article entity
- view own unpublished group_node:page entity
- view own unpublished group_node:webform entity
- view unpublished group_node:article entity
- view unpublished group_node:page entity
- view unpublished group_node:webform entity

View File

@@ -0,0 +1,46 @@
langcode: pt-br
status: true
dependencies:
config:
- group.type.departments
- user.role.administrator
id: departments-admin_out
label: Administrador
weight: 101
admin: true
scope: outsider
global_role: administrator
group_type: departments
permissions:
- administer members
- delete group
- edit group
- join group
- view group
- view unpublished group
- access group_media overview
- access group_node overview
- create group_node:article entity
- create group_node:page entity
- create group_node:webform entity
- delete any group_node:article entity
- delete any group_node:page entity
- delete any group_node:webform entity
- delete own group_node:article entity
- delete own group_node:page entity
- delete own group_node:webform entity
- update any group_node:article entity
- update any group_node:page entity
- update any group_node:webform entity
- update own group_node:article entity
- update own group_node:page entity
- update own group_node:webform entity
- view group_node:article entity
- view group_node:page entity
- view group_node:webform entity
- view own unpublished group_node:article entity
- view own unpublished group_node:page entity
- view own unpublished group_node:webform entity
- view unpublished group_node:article entity
- view unpublished group_node:page entity
- view unpublished group_node:webform entity

View File

@@ -0,0 +1,17 @@
langcode: pt-br
status: true
dependencies:
config:
- group.type.departments
- user.role.anonymous
id: departments-anonymous
label: Anônimo
weight: -102
admin: false
scope: outsider
global_role: anonymous
group_type: departments
permissions:
- view group
- view group_node:article entity
- view group_node:page entity

View File

@@ -0,0 +1,36 @@
langcode: pt-br
status: true
dependencies:
config:
- group.type.departments
- user.role.authenticated
id: departments-member
label: Member
weight: -100
admin: false
scope: insider
global_role: authenticated
group_type: departments
permissions:
- view group
- access group_media overview
- access group_node overview
- create group_node:article entity
- create group_node:page entity
- create group_node:webform entity
- delete own group_node:article entity
- delete own group_node:page entity
- delete own group_node:webform entity
- update any group_node:webform entity
- update own group_node:article entity
- update own group_node:page entity
- update own group_node:webform entity
- view group_node:article entity
- view group_node:page entity
- view group_node:webform entity
- view own unpublished group_node:article entity
- view own unpublished group_node:page entity
- view own unpublished group_node:webform entity
- view unpublished group_node:article entity
- view unpublished group_node:page entity
- view unpublished group_node:webform entity

View File

@@ -0,0 +1,17 @@
langcode: pt-br
status: true
dependencies:
config:
- group.type.departments
- user.role.authenticated
id: departments-outsider
label: Outsider
weight: -101
admin: false
scope: outsider
global_role: authenticated
group_type: departments
permissions:
- view group
- view group_node:article entity
- view group_node:page entity

View File

@@ -0,0 +1,10 @@
langcode: pt-br
status: true
dependencies: { }
id: departments
label: Departamento
description: ''
new_revision: true
creator_membership: true
creator_wizard: true
creator_roles: { }

View File

@@ -0,0 +1,2 @@
label: 'Telefone Profissional'
description: 'Número de telefone profissional, populado via LDAP.'

View File

@@ -0,0 +1,73 @@
/**
* Role mapping table styles
*/
/* Limita a largura da tabela e permite scroll horizontal se necessário */
.role-mappings-table {
max-width: 100%;
table-layout: fixed;
}
/* Define larguras fixas para cada coluna */
.role-mappings-table th:nth-child(1),
.role-mappings-table td:nth-child(1) {
width: 20%;
min-width: 150px;
}
.role-mappings-table th:nth-child(2),
.role-mappings-table td:nth-child(2) {
width: 15%;
min-width: 130px;
}
.role-mappings-table th:nth-child(3),
.role-mappings-table td:nth-child(3) {
width: 25%;
min-width: 180px;
}
.role-mappings-table th:nth-child(4),
.role-mappings-table td:nth-child(4) {
width: 30%;
min-width: 200px;
}
.role-mappings-table th:nth-child(5),
.role-mappings-table td:nth-child(5) {
width: 10%;
min-width: 80px;
text-align: center;
}
/* Estilo dos selects para limitar largura e truncar texto */
.role-mapping-field-select {
max-width: 100%;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Estilo para options dentro dos selects - mostra texto completo no dropdown */
.role-mapping-field-select option {
white-space: normal;
overflow: visible;
text-overflow: clip;
}
/* Estilo do textarea */
.role-mapping-values-textarea {
max-width: 100%;
width: 100%;
min-height: 50px;
}
/* Responsividade: em telas menores, permite scroll horizontal */
@media (max-width: 1200px) {
.role-mappings-table {
display: block;
overflow-x: auto;
white-space: nowrap;
}
}

View File

@@ -0,0 +1,13 @@
name: LDAP Departments Sync
type: module
description: 'Sincroniza departamentos do servidor LDAP com grupos via cron'
version: '1.1.0'
core_version_requirement: ^11
package: Custom
dependencies:
- ldap_groups_sync:ldap_groups_sync
- drupal:options
- drupal:telephone
- group:group
- ldap:ldap_servers
- site_tools

View File

@@ -0,0 +1,106 @@
<?php
/**
* @file
* Arquivo de instalação para ldap_departments_sync.
*/
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Implements hook_install().
*/
function ldap_departments_sync_install() {
\Drupal::messenger()->addWarning(t('Please ensure your group type has the required fields: field_dept_code, field_dept_acronym, field_dept_type, field_dept_phone, field_dept_room, field_dept_mail, and field_parent_group (for hierarchy).'));
}
/**
* Renames field_user_phone_number to field_user_work_phone.
*/
function ldap_departments_sync_update_10001() {
// 1. Create new field storage.
if (!FieldStorageConfig::loadByName('user', 'field_user_work_phone')) {
FieldStorageConfig::create([
'field_name' => 'field_user_work_phone',
'entity_type' => 'user',
'type' => 'telephone',
'cardinality' => 1,
'translatable' => TRUE,
])->save();
}
// 2. Create new field instance.
if (!FieldConfig::loadByName('user', 'user', 'field_user_work_phone')) {
FieldConfig::create([
'field_name' => 'field_user_work_phone',
'entity_type' => 'user',
'bundle' => 'user',
'label' => 'Work Phone',
'description' => 'Work phone number, populated from LDAP.',
'required' => FALSE,
'translatable' => FALSE,
])->save();
}
// 3. Copy data directly via SQL — efficient and side-effect-free.
$database = \Drupal::database();
$schema = $database->schema();
foreach (['user__', 'user_revision__'] as $prefix) {
$old_table = $prefix . 'field_user_phone_number';
$new_table = $prefix . 'field_user_work_phone';
if ($schema->tableExists($old_table) && $schema->tableExists($new_table)) {
$database->query("
INSERT INTO {{$new_table}}
(bundle, deleted, entity_id, revision_id, langcode, delta, field_user_work_phone_value)
SELECT bundle, deleted, entity_id, revision_id, langcode, delta, field_user_phone_number_value
FROM {{$old_table}}
WHERE deleted = 0
");
}
}
// 4. Update form display.
$form_display = EntityFormDisplay::load('user.user.default');
if ($form_display) {
$component = $form_display->getComponent('field_user_phone_number');
if ($component) {
$form_display->removeComponent('field_user_phone_number');
$form_display->setComponent('field_user_work_phone', $component);
$form_display->save();
}
}
// 5. Update view display.
$view_display = EntityViewDisplay::load('user.user.default');
if ($view_display) {
$component = $view_display->getComponent('field_user_phone_number');
if ($component) {
$view_display->removeComponent('field_user_phone_number');
$view_display->setComponent('field_user_work_phone', $component);
$view_display->save();
}
}
// 6. Delete old field instance then storage.
if ($old_config = FieldConfig::loadByName('user', 'user', 'field_user_phone_number')) {
$old_config->delete();
}
if ($old_storage = FieldStorageConfig::loadByName('user', 'field_user_phone_number')) {
$old_storage->delete();
}
return t('Renamed field_user_phone_number to field_user_work_phone.');
}
/**
* Implements hook_uninstall().
*/
function ldap_departments_sync_uninstall() {
// Remove configurações
\Drupal::configFactory()->getEditable('ldap_departments_sync.settings')->delete();
\Drupal::messenger()->addStatus(t('Módulo LDAP Departments Sync desinstalado.'));
}

View File

@@ -0,0 +1,5 @@
role_mapping_styles:
version: 1.x
css:
theme:
css/role-mapping.css: {}

View File

@@ -0,0 +1 @@
# Menu entry removed: the parent module ldap_groups_sync provides the unified menu entry.

View File

@@ -0,0 +1,5 @@
ldap_departments_sync.tab.config:
title: 'Departments Sync'
route_name: ldap_departments_sync.config
base_route: ldap_groups_sync.config
weight: 10

View File

@@ -0,0 +1,291 @@
<?php
/**
* @file
* Module to synchronize departments from LDAP to groups.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\FieldConfigInterface;
use Drupal\ldap_departments_sync\LdapDepartmentsSync;
/**
* Implements hook_help().
*/
function ldap_departments_sync_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.ldap_departments_sync':
return '<p>' . t('This module synchronizes departments from an LDAP server to groups through cron.') . '</p>';
}
}
/**
* Implements hook_cron().
*/
function ldap_departments_sync_cron() {
// Check if module is properly configured
$config = \Drupal::config('ldap_departments_sync.settings');
$ldap_server_id = $config->get('ldap_server_id');
$ldap_query_id = $config->get('ldap_query_id');
// Validate LDAP server
try {
$entity_type_manager = \Drupal::entityTypeManager();
$server_storage = $entity_type_manager->getStorage('ldap_server');
$server = $server_storage->load($ldap_server_id);
if (!$server || !$server->get('status')) {
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: LDAP server "@server_id" not found or inactive. Configure at /admin/config/local-modules/ldap-departments-sync', [
'@server_id' => $ldap_server_id,
]);
return;
}
}
catch (\Exception $e) {
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: error checking LDAP server: @message', [
'@message' => $e->getMessage(),
]);
return;
}
// Validate LDAP query
try {
if ($entity_type_manager->hasDefinition('ldap_query_entity')) {
$query_storage = $entity_type_manager->getStorage('ldap_query_entity');
$query = $query_storage->load($ldap_query_id);
if (!$query || !$query->get('status')) {
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: LDAP query "@query_id" not found or inactive. Configure at /admin/config/local-modules/ldap-departments-sync', [
'@query_id' => $ldap_query_id,
]);
return;
}
}
else {
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: ldap_query module not available.');
return;
}
}
catch (\Exception $e) {
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: error checking LDAP query: @message', [
'@message' => $e->getMessage(),
]);
return;
}
// Validate group type
try {
$group_type_id = $config->get('group_type_id');
$group_type_storage = $entity_type_manager->getStorage('group_type');
$group_type = $group_type_storage->load($group_type_id);
if (!$group_type) {
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: group type "@type_id" not found. Configure at /admin/config/local-modules/ldap-departments-sync', [
'@type_id' => $group_type_id,
]);
return;
}
}
catch (\Exception $e) {
\Drupal::logger('ldap_departments_sync')->warning('Synchronization cancelled: error checking group type: @message', [
'@message' => $e->getMessage(),
]);
return;
}
// Get LDAP synchronization service
$ldap_sync = \Drupal::service('ldap_departments_sync.sync');
// Execute departments synchronization
try {
$ldap_sync->syncDepartments();
\Drupal::logger('ldap_departments_sync')->info('Departments synchronization executed successfully.');
}
catch (\Exception $e) {
\Drupal::logger('ldap_departments_sync')->error('Error in departments synchronization: @message', [
'@message' => $e->getMessage(),
]);
}
}
/**
* Implements hook_entity_access().
*
* Evaluates access rules configured in the module settings for view, update
* and delete operations on existing entities.
*/
function ldap_departments_sync_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
// Skip group-related entities to avoid conflicts with the group module.
$skip_types = ['group', 'group_content', 'group_relationship', 'group_role', 'group_type'];
if (in_array($entity->getEntityTypeId(), $skip_types, TRUE)) {
return AccessResult::neutral();
}
return \Drupal::service('ldap_departments_sync.access_rules')
->checkAccess($entity, $operation, $account);
}
/**
* Implements hook_entity_create_access().
*
* Evaluates access rules configured in the module settings for create
* operations on new entities.
*/
function ldap_departments_sync_entity_create_access(AccountInterface $account, array $context, $entity_bundle) {
$entity_type_id = $context['entity_type_id'] ?? '';
// Skip group-related entities.
$skip_types = ['group', 'group_content', 'group_relationship', 'group_role', 'group_type'];
if (in_array($entity_type_id, $skip_types, TRUE)) {
return AccessResult::neutral();
}
return \Drupal::service('ldap_departments_sync.access_rules')
->checkCreateAccess($account, $entity_type_id, $entity_bundle ?? '');
}
/**
* Implements hook_entity_field_access().
*
* Nega acesso de edição aos campos gerenciados pelo LDAP sync em formulários
* e APIs (REST, JSON:API). Saves programáticos não são afetados.
*
* Campos protegidos no usuário:
* - field_user_category: categoria do usuário, populado pelo ldap_user.
* - field_user_dept_code: código do departamento, populado pelo ldap_user.
* - field_user_department: referência ao grupo, populado pelo nosso sync.
* Proteger este campo evita o erro de validação "Esta entidade não pode
* ser referenciada" que ocorre quando o widget tenta validar o grupo
* referenciado contra o acesso do usuário atual.
* - field_user_work_phone: telefone de trabalho, populado pelo ldap_user.
*/
function ldap_departments_sync_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) {
$protected_user_fields = [
'field_user_category',
'field_user_dept_code',
'field_user_department',
'field_user_work_phone',
];
if (
$field_definition->getTargetEntityTypeId() === 'user' &&
in_array($field_definition->getName(), $protected_user_fields, TRUE) &&
$operation === 'edit'
) {
// Sempre nega edição via UI/API, inclusive para o uid 1.
// Saves programáticos (ldap_user, drush) não passam por este hook.
return AccessResult::forbidden('This field is managed exclusively by LDAP synchronization.');
}
return AccessResult::neutral();
}
/**
* Implements hook_user_presave().
*
* Protege os campos gerenciados pelo LDAP sync de alterações programáticas
* não autorizadas em usuários existentes. Permite:
* - Saves durante sync LDAP autorizado (LdapDepartmentsSync::$syncing TRUE)
* - Criação de novos usuários (provisionamento inicial via ldap_user)
* - Salvas por usuários com a permissão 'edit ldap managed user fields'
*/
function ldap_departments_sync_user_presave(\Drupal\user\UserInterface $user) {
// Permite durante qualquer sync LDAP autorizado.
if (LdapDepartmentsSync::isSyncing()) {
return;
}
// Permite na criação inicial do usuário (primeiro login LDAP).
if ($user->isNew()) {
return;
}
// Permite se o usuário atual tem a permissão de bypass.
if (\Drupal::currentUser()->hasPermission('edit ldap managed user fields')) {
return;
}
$original = $user->original;
if ($original === NULL) {
return;
}
// Campos gerenciados pelo LDAP que não podem ser alterados externamente.
$protected_fields = [
'field_user_category',
'field_user_dept_code',
'field_user_department',
'field_user_work_phone',
];
foreach ($protected_fields as $field_name) {
if (!$user->hasField($field_name)) {
continue;
}
$original_value = $original->get($field_name)->getValue();
$new_value = $user->get($field_name)->getValue();
if ($original_value !== $new_value) {
$user->set($field_name, $original_value);
\Drupal::logger('ldap_departments_sync')->warning(
'Tentativa não autorizada de alterar @field do usuário @username foi bloqueada. Use a sincronização LDAP para atualizar este campo.',
[
'@field' => $field_name,
'@username' => $user->getAccountName(),
]
);
}
}
}
/**
* Implements hook_ENTITY_TYPE_predelete() for user entities.
*
* Cleans up user references in department groups when a user is deleted.
*/
function ldap_departments_sync_user_predelete(\Drupal\user\UserInterface $user) {
$config = \Drupal::config('ldap_departments_sync.settings');
$group_type_id = $config->get('group_type_id') ?: 'departments';
// Fields that may contain user references
$user_reference_fields = [
'field_dept_coord',
'field_dept_coord_assoc',
];
try {
$group_storage = \Drupal::entityTypeManager()->getStorage('group');
$user_id = $user->id();
// Find groups that reference this user
foreach ($user_reference_fields as $field_name) {
$groups = $group_storage->loadByProperties([
'type' => $group_type_id,
$field_name => $user_id,
]);
foreach ($groups as $group) {
if ($group->hasField($field_name)) {
$group->set($field_name, NULL);
$group->save();
\Drupal::logger('ldap_departments_sync')->info('Removed user @username reference from @field in group @group', [
'@username' => $user->getAccountName(),
'@field' => $field_name,
'@group' => $group->label(),
]);
}
}
}
}
catch (\Exception $e) {
\Drupal::logger('ldap_departments_sync')->error('Error cleaning up user references: @message', [
'@message' => $e->getMessage(),
]);
}
}

View File

@@ -0,0 +1,9 @@
administer ldap departments sync:
title: 'Administrar LDAP Departments Sync'
description: 'Configurar sincronização de departamentos do LDAP'
restrict access: true
edit ldap managed user fields:
title: 'Editar campos gerenciados pelo LDAP no usuário'
description: 'Permite editar manualmente campos como field_user_dept_code que são normalmente gerenciados pelo LDAP. Use apenas em emergências.'
restrict access: true

View File

@@ -0,0 +1,7 @@
ldap_departments_sync.config:
path: '/admin/config/local-modules/ldap-departments-sync'
defaults:
_form: '\Drupal\ldap_departments_sync\Form\LdapDepartmentsSyncConfigForm'
_title: 'LDAP Departments Sync'
requirements:
_permission: 'administer ldap departments sync'

View File

@@ -0,0 +1,8 @@
services:
ldap_departments_sync.sync:
class: Drupal\ldap_departments_sync\LdapDepartmentsSync
arguments: ['@entity_type.manager', '@config.factory', '@logger.factory', '@ldap.bridge']
ldap_departments_sync.access_rules:
class: Drupal\ldap_groups_sync\GroupAccessRulesService
arguments: ['@config.factory', '@entity_type.manager', 'ldap_departments_sync.settings']

View File

@@ -0,0 +1,22 @@
<?php
namespace Drupal\ldap_departments_sync\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
* Controller para títulos traduzíveis.
*/
class LocalModulesController extends ControllerBase {
/**
* Retorna título traduzível para a página Local Modules.
*
* @return string
* Título traduzido.
*/
public function getTitle() {
return $this->t('Local Modules');
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Drupal\ldap_departments_sync\Form;
use Drupal\ldap_groups_sync\Form\AccessRuleFormBase;
/**
* Modal form for creating or editing a single access rule (Departments Sync).
*/
class AccessRuleForm extends AccessRuleFormBase {
/**
* {@inheritdoc}
*/
protected function getConfigName(): string {
return 'ldap_departments_sync.settings';
}
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'ldap_departments_sync_access_rule_form';
}
/**
* {@inheritdoc}
*/
protected function getAccessRulesRoute(): string {
return 'ldap_departments_sync.access_rules';
}
/**
* {@inheritdoc}
*/
protected function getDefaultGroupTypeId(): string {
return 'departments';
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Drupal\ldap_departments_sync\Form;
use Drupal\ldap_groups_sync\Form\AccessRulesFormBase;
/**
* Lists and manages access rules for the LDAP Departments Sync module.
*/
class AccessRulesForm extends AccessRulesFormBase {
/**
* {@inheritdoc}
*/
protected function getConfigName(): string {
return 'ldap_departments_sync.settings';
}
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'ldap_departments_sync_access_rules_form';
}
/**
* {@inheritdoc}
*/
protected function getAccessRuleFormRoute(): string {
return 'ldap_departments_sync.access_rule_form';
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
<?php
namespace Drupal\ldap_departments_sync\Plugin\EntityReferenceSelection;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'department' entity_reference selection.
*
* @EntityReferenceSelection(
* id = "ldap_departments_sync",
* label = @Translation("Department selection"),
* entity_types = {"group"},
* group = "ldap_departments_sync",
* weight = 1
* )
*/
class DepartmentSelection extends DefaultSelection {
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'filter_by_type' => FALSE,
'allowed_types' => [],
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
// Força departments como bundle padrão
$form['target_bundles']['#default_value'] = ['departments' => 'departments'];
$configuration = $this->getConfiguration();
$form['filter_by_type'] = [
'#type' => 'checkbox',
'#title' => $this->t('Filter by department type'),
'#description' => $this->t('Only show departments of specific types (field_dept_type).'),
'#default_value' => $configuration['filter_by_type'],
'#weight' => 10,
];
$form['allowed_types'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Allowed department types'),
'#options' => [
'academico' => $this->t('Acadêmico'),
'administrativo' => $this->t('Administrativo'),
],
'#default_value' => $configuration['allowed_types'],
'#weight' => 11,
'#states' => [
'visible' => [
':input[name="settings[handler_settings][filter_by_type]"]' => ['checked' => TRUE],
],
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::validateConfigurationForm($form, $form_state);
// Remove valores vazios de allowed_types
$allowed_types = $form_state->getValue(['settings', 'handler_settings', 'allowed_types']);
if (is_array($allowed_types)) {
$allowed_types = array_filter($allowed_types);
$form_state->setValue(['settings', 'handler_settings', 'allowed_types'], $allowed_types);
}
}
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
$query = parent::buildEntityQuery($match, $match_operator);
$configuration = $this->getConfiguration();
// Força apenas departments
$query->condition('type', 'departments');
// Filtra por tipo de departamento se configurado
if (!empty($configuration['filter_by_type']) && !empty($configuration['allowed_types'])) {
$allowed_types = array_filter($configuration['allowed_types']);
if (!empty($allowed_types)) {
$query->condition('field_dept_type', array_keys($allowed_types), 'IN');
}
}
return $query;
}
}

View File

@@ -0,0 +1,286 @@
# Portuguese (Brazil) translation for LDAP Departments Sync module
# Copyright (c) 2025
# This file is distributed under the same license as the LDAP Departments Sync module.
#
msgid ""
msgstr ""
"Project-Id-Version: LDAP Departments Sync 1.0.0\n"
"POT-Creation-Date: 2025-11-07 12:00+0000\n"
"PO-Revision-Date: 2025-11-07 12:00+0000\n"
"Language-Team: Portuguese (Brazil)\n"
"Language: pt-br\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: src/Controller/LocalModulesController.php
msgid "Local Modules"
msgstr "Módulos Locais"
#: ldap_departments_sync.routing.yml
msgid "LDAP Departments Sync"
msgstr "Sincronização de Departamentos LDAP"
#: ldap_departments_sync.module
msgid "This module synchronizes departments from an LDAP server to groups through cron."
msgstr "Este módulo sincroniza departamentos de um servidor LDAP para grupos através do cron."
#: src/Form/LdapDepartmentsSyncConfigForm.php
msgid "Manage Fields"
msgstr "Gerenciar Campos"
msgid "LDAP Server"
msgstr "Servidor LDAP"
msgid "Select the LDAP server configured for departments synchronization."
msgstr "Selecione o servidor LDAP configurado para sincronização de departamentos."
msgid "- Select a server -"
msgstr "- Selecione um servidor -"
msgid "No LDAP server found. <a href=\"@url\">Configure an LDAP server</a> first."
msgstr "Nenhum servidor LDAP encontrado. <a href=\"@url\">Configure um servidor LDAP</a> primeiro."
msgid "LDAP Query"
msgstr "Consulta LDAP"
msgid "Select the LDAP query that will be used to search for departments."
msgstr "Selecione a consulta LDAP que será usada para buscar departamentos."
msgid "- Select a query -"
msgstr "- Selecione uma query -"
msgid "No LDAP query found for the selected server. <a href=\"@url\">Create a new LDAP query</a>."
msgstr "Nenhuma query LDAP encontrada para o servidor selecionado. <a href=\"@url\">Crie uma nova query LDAP</a>."
msgid "Create New LDAP Query"
msgstr "Criar Nova Query LDAP"
msgid "Edit Selected Query"
msgstr "Editar Query Selecionada"
msgid "Hierarchy"
msgstr "Hierarquia"
msgid "Configure whether departments should be organized hierarchically based on LDAP attributes."
msgstr "Configure se os departamentos devem ser organizados hierarquicamente baseado em atributos LDAP."
msgid "Enable hierarchy"
msgstr "Habilitar hierarquização"
msgid "When enabled, departments will be organized hierarchically based on the specified parent and child attributes."
msgstr "Quando habilitado, os departamentos serão organizados hierarquicamente baseado nos atributos parent e child especificados."
msgid "Parent Attribute"
msgstr "Atributo Pai"
msgid "LDAP attribute that contains the parent department code (e.g. departmentNumber)."
msgstr "Atributo LDAP que contém o código do departamento pai (ex: departmentNumber)."
msgid "- Select an attribute -"
msgstr "- Selecione um atributo -"
msgid "Child Attribute"
msgstr "Atributo Filho"
msgid "LDAP attribute that contains the department's own code (e.g. imeccDepartmentCode)."
msgstr "Atributo LDAP que contém o código próprio do departamento (ex: imeccDepartmentCode)."
msgid "All elements with the same child value will be nested under the corresponding parent recursively."
msgstr "Todos os elementos com o mesmo valor de child serão aninhados sob o parent correspondente recursivamente."
msgid "Attribute Mapping"
msgstr "Mapeamento de Atributos"
msgid "Configure how LDAP attributes are mapped to group fields.<br><strong>Note:</strong> For \"User Reference\" mappings, the search attribute will be automatically obtained from the \"Account name attribute\" field (or \"Authentication name attribute\" as fallback) configured in the selected LDAP server."
msgstr "Configure como os atributos LDAP são mapeados para os campos do grupo.<br><strong>Nota:</strong> Para mapeamentos de \"Referência de Usuário\", o atributo de busca será automaticamente obtido do campo \"Account name attribute\" (ou \"Authentication name attribute\" como fallback) configurado no servidor LDAP selecionado."
msgid "Entity Field"
msgstr "Campo da Entidade"
msgid "Group Field"
msgstr "Campo do Grupo"
msgid "LDAP Attribute"
msgstr "Atributo LDAP"
msgid "Mapping Type"
msgstr "Tipo de Mapeamento"
msgid "Remove"
msgstr "Remover"
msgid "No mappings configured."
msgstr "Nenhum mapeamento configurado."
msgid "- Select a field -"
msgstr "- Selecione um campo -"
msgid "Simple (text)"
msgstr "Simples (texto)"
msgid "User Reference (DN → User)"
msgstr "Referência de Usuário (DN → User)"
msgid "Add Mapping"
msgstr "Adicionar Mapeamento"
msgid "Remove Selected"
msgstr "Remover Selecionados"
msgid "Save Configuration"
msgstr "Salvar Configurações"
msgid "Test Connection"
msgstr "Testar Conexão"
msgid "Synchronize Departments"
msgstr "Sincronizar Departamentos"
msgid "Error loading LDAP servers: @message"
msgstr "Erro ao carregar servidores LDAP: @message"
msgid "Error loading group fields: @message"
msgstr "Erro ao carregar campos do grupo: @message"
msgid "Error loading query attributes: @message"
msgstr "Erro ao carregar atributos da query: @message"
msgid "LDAP attribute is required when field is selected."
msgstr "Atributo LDAP é obrigatório quando campo está selecionado."
msgid "Group field is required when attribute is selected."
msgstr "Campo do grupo é obrigatório quando atributo está selecionado."
msgid "Configuration saved successfully."
msgstr "Configurações salvas com sucesso."
msgid "Group Role Mapping"
msgstr "Mapeamento de Papéis em Grupos"
msgid "Configure how user attributes map to group roles. Each role can have its own mapping criteria."
msgstr "Configure como atributos de usuário mapeiam para papéis em grupos. Cada papel pode ter seus próprios critérios de mapeamento."
msgid "Configure how to assign group roles based on LDAP attributes or user fields."
msgstr "Configure como atribuir papéis em grupos baseado em atributos LDAP ou campos de usuário."
msgid "Enable role mapping"
msgstr "Habilitar mapeamento de papéis"
msgid "When enabled, users will be assigned group roles based on the mappings below."
msgstr "Quando habilitado, os usuários receberão papéis em grupos baseado nos mapeamentos abaixo."
msgid "Group Role"
msgstr "Papel do Grupo"
msgid "Source"
msgstr "Origem"
msgid "LDAP Attribute"
msgstr "Atributo LDAP"
msgid "User Field"
msgstr "Campo do Usuário"
msgid "Values (comma-separated)"
msgstr "Valores (separados por vírgula)"
msgid "No role mappings configured."
msgstr "Nenhum mapeamento de papel configurado."
msgid "- Select a role -"
msgstr "- Selecione um papel -"
msgid "Add Role Mapping"
msgstr "Adicionar Mapeamento de Papel"
msgid "Remove Selected"
msgstr "Remover Selecionados"
msgid "Default Group Role"
msgstr "Papel Padrão do Grupo"
msgid "The default role to assign if no role mapping matches."
msgstr "O papel padrão a ser atribuído se nenhum mapeamento corresponder."
msgid "Enter the expected values for this field/attribute, separated by commas (e.g., admin,administrator,manager)."
msgstr "Digite os valores esperados para este campo/atributo, separados por vírgula (ex: admin,administrador,gerente)."
msgid "Internal"
msgstr "Interno"
msgid "Outsider"
msgstr "Externo"
msgid "- Select attribute/field -"
msgstr "- Selecione atributo/campo -"
msgid "Group Field Match"
msgstr "Correspondência de Campo do Grupo"
msgid "User Field / LDAP Attribute"
msgstr "Campo do Usuário / Atributo LDAP"
msgid "Group Field / Values"
msgstr "Campo do Grupo / Valores"
msgid "- Select group field -"
msgstr "- Selecione campo do grupo -"
msgid "- Select user field -"
msgstr "- Selecione campo do usuário -"
msgid "The default \"departments\" group type is not installed."
msgstr "O tipo de grupo padrão \"departments\" não está instalado."
msgid "Install Default Configuration"
msgstr "Instalar Configuração Padrão"
msgid "The button above will create:<ul><li>Group type \"departments\" with all required fields</li><li>User fields for department synchronization</li><li>Default group roles and displays</li></ul>Or you can <a href=\"@url\">create a custom group type</a> manually."
msgstr "O botão acima irá criar:<ul><li>Tipo de grupo \"departments\" com todos os campos necessários</li><li>Campos de usuário para sincronização de departamento</li><li>Papéis e visualizações de grupo padrão</li></ul>Ou você pode <a href=\"@url\">criar um tipo de grupo personalizado</a> manualmente."
msgid "Successfully installed @count configuration items. Group type \"departments\" and all required fields have been created. Please reload the page to see the changes."
msgstr "@count itens de configuração instalados com sucesso. O tipo de grupo \"departments\" e todos os campos necessários foram criados. Por favor, recarregue a página para ver as alterações."
msgid "All configurations already exist. Nothing was imported."
msgstr "Todas as configurações já existem. Nada foi importado."
msgid "Error installing default configuration: @message"
msgstr "Erro ao instalar configuração padrão: @message"
msgid "Configuration directory not found: @path"
msgstr "Diretório de configuração não encontrado: @path"
msgid "Configuration @name already exists, skipping."
msgstr "Configuração @name já existe, ignorando."
#: src/Plugin/EntityReferenceSelection/DepartmentSelection.php
msgid "Filter by department type"
msgstr "Filtrar por tipo de departamento"
msgid "Only show departments of specific types (field_dept_type)."
msgstr "Mostrar apenas departamentos de tipos específicos (field_dept_type)."
msgid "Allowed department types"
msgstr "Tipos de departamento permitidos"
msgid "Acadêmico"
msgstr "Acadêmico"
msgid "Administrativo"
msgstr "Administrativo"
msgid "Department selection"
msgstr "Seleção de departamento"
#: ldap_departments_sync.links.task.yml
msgid "Configuration"
msgstr "Configuração"
msgid "Access Rules"
msgstr "Regras de Acesso"
msgid "Access Rule"
msgstr "Regra de Acesso"

View File

@@ -0,0 +1,152 @@
# Portuguese translation of LDAP Departments Sync
# Copyright (c) 2025
msgid ""
msgstr ""
"Project-Id-Version: LDAP Departments Sync 1.0.0\n"
"POT-Creation-Date: 2025-01-12 10:00-0300\n"
"PO-Revision-Date: 2025-01-12 10:00-0300\n"
"Language-Team: Portuguese (Brazil)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Language: pt_BR\n"
# Form titles and sections
msgid "Synchronization Type"
msgstr "Tipo de Sincronização"
msgid "Data storage type"
msgstr "Tipo de armazenamento de dados"
msgid "Choose whether departments should be synchronized as taxonomy terms or as groups."
msgstr "Escolha se os departamentos devem ser sincronizados como termos de taxonomia ou como grupos."
msgid "Taxonomy Vocabulary - Store departments as taxonomy terms"
msgstr "Vocabulário de Taxonomia - Armazenar departamentos como termos de taxonomia"
msgid "Groups - Store departments as group entities"
msgstr "Grupos - Armazenar departamentos como entidades de grupo"
msgid "Updating form..."
msgstr "Atualizando formulário..."
msgid "Taxonomy Vocabulary"
msgstr "Vocabulário de Taxonomia"
msgid "Target Vocabulary"
msgstr "Vocabulário de Destino"
msgid "Select the taxonomy vocabulary where departments will be synchronized. <br><strong>Required fields:</strong> field_dept_code, field_dept_acronym, field_dept_type, field_dept_phone, field_dept_room, field_dept_mail"
msgstr "Selecione o vocabulário de taxonomia onde os departamentos serão sincronizados. <br><strong>Campos obrigatórios:</strong> field_dept_code, field_dept_acronym, field_dept_type, field_dept_phone, field_dept_room, field_dept_mail"
msgid "- Select a vocabulary -"
msgstr "- Selecione um vocabulário -"
msgid "No taxonomy vocabulary found. <a href=\"@url\">Create a vocabulary</a> first."
msgstr "Nenhum vocabulário de taxonomia encontrado. <a href=\"@url\">Crie um vocabulário</a> primeiro."
msgid "Create New Vocabulary"
msgstr "Criar Novo Vocabulário"
msgid "Manage Vocabulary"
msgstr "Gerenciar Vocabulário"
msgid "Manage Fields"
msgstr "Gerenciar Campos"
msgid "Group Type"
msgstr "Tipo de Grupo"
msgid "Target Group Type"
msgstr "Tipo de Grupo de Destino"
msgid "Select the group type where departments will be synchronized. <br><strong>Required fields:</strong> field_dept_code, field_dept_acronym, field_dept_type, field_dept_phone, field_dept_room, field_dept_mail"
msgstr "Selecione o tipo de grupo onde os departamentos serão sincronizados. <br><strong>Campos obrigatórios:</strong> field_dept_code, field_dept_acronym, field_dept_type, field_dept_phone, field_dept_room, field_dept_mail"
msgid "- Select a group type -"
msgstr "- Selecione um tipo de grupo -"
msgid "No group type found. <a href=\"@url\">Create a group type</a> first."
msgstr "Nenhum tipo de grupo encontrado. <a href=\"@url\">Crie um tipo de grupo</a> primeiro."
msgid "Create New Group Type"
msgstr "Criar Novo Tipo de Grupo"
msgid "Manage Group Type"
msgstr "Gerenciar Tipo de Grupo"
msgid "LDAP Server"
msgstr "Servidor LDAP"
msgid "Select the LDAP server configured for departments synchronization."
msgstr "Selecione o servidor LDAP configurado para sincronização de departamentos."
msgid "- Select a server -"
msgstr "- Selecione um servidor -"
msgid "No LDAP server found. <a href=\"@url\">Configure an LDAP server</a> first."
msgstr "Nenhum servidor LDAP encontrado. <a href=\"@url\">Configure um servidor LDAP</a> primeiro."
msgid "LDAP Query"
msgstr "Consulta LDAP"
msgid "Select the LDAP query that will be used to search for departments."
msgstr "Selecione a consulta LDAP que será usada para buscar departamentos."
msgid "- Select a query -"
msgstr "- Selecione uma consulta -"
msgid "No LDAP query found for the selected server. <a href=\"@url\">Create a new LDAP query</a>."
msgstr "Nenhuma consulta LDAP encontrada para o servidor selecionado. <a href=\"@url\">Crie uma nova consulta LDAP</a>."
msgid "Create New LDAP Query"
msgstr "Criar Nova Consulta LDAP"
msgid "Edit Selected Query"
msgstr "Editar Consulta Selecionada"
msgid "Hierarchy"
msgstr "Hierarquia"
msgid "Configure whether departments should be organized hierarchically based on LDAP attributes."
msgstr "Configure se os departamentos devem ser organizados hierarquicamente com base em atributos LDAP."
msgid "Enable hierarchy"
msgstr "Habilitar hierarquia"
msgid "When enabled, terms will be organized hierarchically based on the specified parent and child attributes."
msgstr "Quando habilitado, os departamentos serão organizados hierarquicamente com base nos atributos de pai e filho especificados."
msgid "Parent Attribute"
msgstr "Atributo Pai"
msgid "LDAP attribute that contains the parent department code (e.g. departmentNumber)."
msgstr "Atributo LDAP que contém o código do departamento pai (ex: departmentNumber)."
msgid "Child Attribute"
msgstr "Atributo Filho"
msgid "LDAP attribute that contains the department's own code (e.g. imeccDepartmentCode)."
msgstr "Atributo LDAP que contém o código próprio do departamento (ex: imeccDepartmentCode)."
msgid "- Select an attribute -"
msgstr "- Selecione um atributo -"
msgid "All elements with the same child value will be nested under the corresponding parent recursively."
msgstr "Todos os elementos com o mesmo valor filho serão aninhados sob o pai correspondente recursivamente."
msgid "Attribute Mapping"
msgstr "Mapeamento de Atributos"
msgid "Configure how LDAP attributes are mapped to target entity fields.<br><strong>Note:</strong> For \"User Reference\" mappings, the search attribute will be automatically obtained from the \"Account name attribute\" field (or \"Authentication name attribute\" as fallback) configured in the selected LDAP server."
msgstr "Configure como os atributos LDAP são mapeados para os campos da entidade de destino.<br><strong>Nota:</strong> Para mapeamentos de \"Referência de Usuário\", o atributo de busca será obtido automaticamente do campo \"Account name attribute\" (ou \"Authentication name attribute\" como alternativa) configurado no servidor LDAP selecionado."
msgid "- Select a field -"
msgstr "- Selecione um campo -"
msgid "Simple (text)"
msgstr "Simples (texto)"
msgid "User Reference (DN → User)"
msgstr "Referência de Usuário (DN → Usuário)"