mirror of
https://gitlab.unicamp.br/infimecc_drupal11_modules/site_users.git
synced 2026-03-08 01:17:41 -03:00
Implementa hook_ldap_user_edit_user_alter() para capturar a foto do atributo LDAP configurado e adicioná-la como primeira entrada em field_user_photos, sem queries adicionais ao servidor. Inclui LdapPhotoSyncService com detecção de tipo via exif_imagetype, deduplicação por MD5 e reutilização de media entity existente. Adiciona checkbox para ativar/desativar o sync no formulário de settings, com visibilidade condicional do campo de atributo via #states. Corrige acesso a mídias publicadas para usuários autenticados via hook_media_access(), resolvendo "Acesso restrito" no widget e na visualização do perfil. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
168 lines
4.9 KiB
PHP
168 lines
4.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Drupal\site_users\Service;
|
|
|
|
use Drupal\Core\Config\ConfigFactoryInterface;
|
|
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
|
use Drupal\Core\File\FileExists;
|
|
use Drupal\Core\File\FileSystemInterface;
|
|
use Drupal\file\FileInterface;
|
|
use Drupal\file\FileRepositoryInterface;
|
|
use Drupal\user\UserInterface;
|
|
use Symfony\Component\Ldap\Entry;
|
|
|
|
/**
|
|
* Synchronizes the user photo from an LDAP entry to field_user_photos.
|
|
*/
|
|
class LdapPhotoSyncService {
|
|
|
|
public function __construct(
|
|
private ConfigFactoryInterface $configFactory,
|
|
private EntityTypeManagerInterface $entityTypeManager,
|
|
private FileRepositoryInterface $fileRepository,
|
|
private FileSystemInterface $fileSystem,
|
|
) {}
|
|
|
|
/**
|
|
* Syncs the LDAP photo to the user's field_user_photos.
|
|
*
|
|
* The photo is always placed first in the list. If the photo has not
|
|
* changed since the last sync (same MD5), no file or media write occurs.
|
|
*/
|
|
public function syncFromLdapEntry(UserInterface $account, Entry $ldapEntry): void {
|
|
$config = $this->configFactory->get('site_users.settings');
|
|
|
|
if (!$config->get('photos.ldap_sync_enabled')) {
|
|
return;
|
|
}
|
|
|
|
$attribute = $config->get('photos.ldap_attribute');
|
|
if (!$attribute || !$ldapEntry->hasAttribute($attribute, FALSE)) {
|
|
return;
|
|
}
|
|
|
|
$binary = $ldapEntry->getAttribute($attribute, FALSE)[0] ?? NULL;
|
|
if (empty($binary)) {
|
|
return;
|
|
}
|
|
|
|
$extension = $this->detectExtension($binary);
|
|
if (!$extension) {
|
|
return;
|
|
}
|
|
|
|
$directory = 'public://ldap_photos';
|
|
$uri = $directory . '/' . 'ldap_photo_' . $account->id() . '.' . $extension;
|
|
|
|
// If file exists and content is unchanged, just ensure it is first.
|
|
$realpath = $this->fileSystem->realpath($uri);
|
|
if ($realpath && file_exists($realpath) && md5_file($realpath) === md5($binary)) {
|
|
$this->ensureMediaFirstInField($account, $uri);
|
|
return;
|
|
}
|
|
|
|
$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
|
|
|
|
$file = $this->fileRepository->writeData($binary, $uri, FileExists::Replace);
|
|
if (!$file) {
|
|
return;
|
|
}
|
|
|
|
$media = $this->findOrCreateMedia($file, (int) $account->id());
|
|
if (!$media) {
|
|
return;
|
|
}
|
|
|
|
$this->prependMediaToField($account, (int) $media->id());
|
|
}
|
|
|
|
/**
|
|
* Detects the image extension from binary data via a temporary file.
|
|
*/
|
|
private function detectExtension(string $binary): ?string {
|
|
$tmp = $this->fileSystem->tempnam('temporary://', 'ldap_photo_');
|
|
file_put_contents($this->fileSystem->realpath($tmp), $binary);
|
|
$type = @exif_imagetype($this->fileSystem->realpath($tmp));
|
|
$this->fileSystem->unlink($tmp);
|
|
|
|
return match ($type) {
|
|
IMAGETYPE_JPEG => 'jpg',
|
|
IMAGETYPE_PNG => 'png',
|
|
IMAGETYPE_GIF => 'gif',
|
|
default => NULL,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns an existing media entity for the file, or creates one.
|
|
*/
|
|
private function findOrCreateMedia(FileInterface $file, int $uid): ?object {
|
|
$media_storage = $this->entityTypeManager->getStorage('media');
|
|
|
|
$existing = $media_storage->loadByProperties([
|
|
'bundle' => 'image',
|
|
'field_media_image.target_id' => $file->id(),
|
|
]);
|
|
|
|
if (!empty($existing)) {
|
|
return reset($existing);
|
|
}
|
|
|
|
$media = $media_storage->create([
|
|
'bundle' => 'image',
|
|
'uid' => $uid,
|
|
'name' => $file->getFilename(),
|
|
'field_media_image' => [
|
|
'target_id' => $file->id(),
|
|
'alt' => $file->getFilename(),
|
|
],
|
|
'status' => 1,
|
|
]);
|
|
$media->save();
|
|
|
|
return $media;
|
|
}
|
|
|
|
/**
|
|
* Ensures the media for an existing file URI is first in field_user_photos.
|
|
*/
|
|
private function ensureMediaFirstInField(UserInterface $account, string $uri): void {
|
|
$files = $this->entityTypeManager->getStorage('file')
|
|
->loadByProperties(['uri' => $uri]);
|
|
if (empty($files)) {
|
|
return;
|
|
}
|
|
|
|
$file = reset($files);
|
|
$media_storage = $this->entityTypeManager->getStorage('media');
|
|
$existing = $media_storage->loadByProperties([
|
|
'bundle' => 'image',
|
|
'field_media_image.target_id' => $file->id(),
|
|
]);
|
|
if (empty($existing)) {
|
|
return;
|
|
}
|
|
|
|
$media = reset($existing);
|
|
$this->prependMediaToField($account, (int) $media->id());
|
|
}
|
|
|
|
/**
|
|
* Prepends a media entity to field_user_photos, removing any duplicate.
|
|
*/
|
|
private function prependMediaToField(UserInterface $account, int $media_id): void {
|
|
$current_ids = array_column($account->get('field_user_photos')->getValue(), 'target_id');
|
|
|
|
if (!empty($current_ids) && (int) $current_ids[0] === $media_id) {
|
|
return;
|
|
}
|
|
|
|
$filtered = array_values(array_filter($current_ids, fn($id) => (int) $id !== $media_id));
|
|
array_unshift($filtered, $media_id);
|
|
$account->set('field_user_photos', array_map(fn($id) => ['target_id' => $id], $filtered));
|
|
}
|
|
|
|
}
|