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>
This commit is contained in:
2026-03-23 15:29:40 -03:00
parent aa24bf79f8
commit 39de6a7493
13 changed files with 404 additions and 15 deletions

View File

@@ -194,6 +194,79 @@ function site_users_install() {
}
}
/**
* Remove mídias LDAP geradas sem UID (URI ldap_photo_.jpg/png) de todos os
* usuários e apaga os arquivos e mídias correspondentes.
*
* Causa: durante provisionamento LDAP, $account->id() era vazio antes do
* primeiro save, gerando URI única compartilhada por todos os usuários.
*/
function site_users_update_10011() {
$file_storage = \Drupal::entityTypeManager()->getStorage('file');
$media_storage = \Drupal::entityTypeManager()->getStorage('media');
$user_storage = \Drupal::entityTypeManager()->getStorage('user');
// Localiza arquivos com URI sem UID (ldap_photo_.jpg, ldap_photo_.png…).
$fids = $file_storage->getQuery()
->condition('uri', 'public://ldap_photos/ldap_photo_.', 'STARTS_WITH')
->accessCheck(FALSE)
->execute();
if (empty($fids)) {
return t('Nenhum arquivo LDAP sem UID encontrado.');
}
$removed_media = 0;
$affected_users = 0;
foreach ($fids as $fid) {
$medias = $media_storage->loadByProperties([
'bundle' => 'image',
'field_media_image.target_id' => $fid,
]);
foreach ($medias as $media) {
$mid = (int) $media->id();
$uids = $user_storage->getQuery()
->condition('field_user_photos.target_id', $mid)
->accessCheck(FALSE)
->execute();
foreach ($uids as $uid) {
$user = $user_storage->load($uid);
if (!$user) {
continue;
}
$photos = array_column($user->get('field_user_photos')->getValue(), 'target_id');
$photos = array_values(array_filter($photos, fn($id) => (int) $id !== $mid));
$user->set('field_user_photos', array_map(fn($id) => ['target_id' => $id], $photos));
if ((int) $user->get('field_user_default_photo')->target_id === $mid) {
$user->set('field_user_default_photo', empty($photos) ? NULL : $photos[0]);
}
$user->save();
$affected_users++;
}
$media->delete();
$removed_media++;
}
$file = $file_storage->load($fid);
if ($file) {
$file->delete();
}
}
return t('Removidas @m mídias sem UID de @u usuários.', [
'@m' => $removed_media,
'@u' => $affected_users,
]);
}
/**
* Adiciona o campo field_user_default_photo para seleção de foto padrão.
*/
@@ -470,10 +543,10 @@ function site_users_update_10006() {
$form_display->removeComponent('field_user_selected_view_mode')->save();
}
// 6. Remover de todos os view displays existentes.
$displays = $display_storage->loadMultiple();
// 6. Remover de todos os view displays de usuário existentes.
$displays = $display_storage->loadByProperties(['targetEntityType' => 'user']);
foreach ($displays as $display) {
if ($display->getTargetEntityTypeId() === 'user' && $display->getComponent('field_user_selected_view_mode')) {
if ($display->getComponent('field_user_selected_view_mode')) {
$display->removeComponent('field_user_selected_view_mode')->save();
}
}
@@ -614,6 +687,86 @@ function site_users_update_10009() {
return t('Campo field_user_homepage criado com sucesso.');
}
/**
* Remove fotos LDAP placeholder (abaixo do tamanho mínimo configurado).
*
* Limpa field_user_photos e field_user_default_photo dos usuários afetados
* e apaga as entidades de mídia e arquivo correspondentes.
*/
function site_users_update_10010() {
$min_size = (int) (\Drupal::config('site_users.settings')->get('photos.ldap_min_photo_size') ?? 10240);
// Busca arquivos LDAP abaixo do tamanho mínimo.
$fids = \Drupal::entityTypeManager()->getStorage('file')->getQuery()
->condition('uri', 'public://ldap_photos/', 'STARTS_WITH')
->condition('filesize', $min_size, '<')
->accessCheck(FALSE)
->execute();
if (empty($fids)) {
return t('Nenhuma foto LDAP placeholder encontrada.');
}
$media_storage = \Drupal::entityTypeManager()->getStorage('media');
$user_storage = \Drupal::entityTypeManager()->getStorage('user');
$removed_media = 0;
$affected_users = 0;
foreach ($fids as $fid) {
// Localiza a entidade de mídia que usa este arquivo.
$medias = $media_storage->loadByProperties([
'bundle' => 'image',
'field_media_image.target_id' => $fid,
]);
foreach ($medias as $media) {
$mid = (int) $media->id();
// Busca usuários que têm esta mídia em field_user_photos.
$uids = $user_storage->getQuery()
->condition('field_user_photos.target_id', $mid)
->accessCheck(FALSE)
->execute();
foreach ($uids as $uid) {
/** @var \Drupal\user\UserInterface $user */
$user = $user_storage->load($uid);
if (!$user) {
continue;
}
// Remove a mídia de field_user_photos.
$photos = array_column($user->get('field_user_photos')->getValue(), 'target_id');
$photos = array_values(array_filter($photos, fn($id) => (int) $id !== $mid));
$user->set('field_user_photos', array_map(fn($id) => ['target_id' => $id], $photos));
// Limpa field_user_default_photo se apontar para esta mídia.
$default_mid = $user->get('field_user_default_photo')->target_id;
if ((int) $default_mid === $mid) {
$user->set('field_user_default_photo', empty($photos) ? NULL : $photos[0]);
}
$user->save();
$affected_users++;
}
$media->delete();
$removed_media++;
}
// Apaga o arquivo gerenciado.
$file = \Drupal::entityTypeManager()->getStorage('file')->load($fid);
if ($file) {
$file->delete();
}
}
return t('Removidas @m mídias placeholder de @u usuários.', [
'@m' => $removed_media,
'@u' => $affected_users,
]);
}
/**
* Corrige mapeamentos LDAP com campos de string nulos na config ativa.
*/