feat: Adiciona field type social_link para redes sociais do usuário

Implementa o field type 'social_link' com seletor de rede e URL de
perfil, composto por:

- SocialLinkItem: field type com colunas 'network' (varchar 64) e
  'url' (varchar 2048), cardinalidade ilimitada
- SocialLinkWidget: widget com select de rede e input de URL
- SocialLinkFormatter: formatter que renderiza links com classe CSS
  por rede (social-link--{network}), target _blank e rel noopener
- config/optional: field.storage e field.field para user
- config/translations/pt-br: tradução do label e description
- hook_install e update_10002: configura form/view displays
- UserInfoBlock: expõe social_links via getSocialLinks()
- Template: adiciona seção de redes sociais e remove referências
  obsoletas a category e dept_code
- translations/site_users.pt-br.po: strings do novo field type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 14:10:28 -03:00
parent 1dafd4a865
commit 8bab0515e1
11 changed files with 383 additions and 31 deletions

View File

@@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
config:
- field.storage.user.field_user_social_links
module:
- site_users
- user
id: user.user.field_user_social_links
field_name: field_user_social_links
entity_type: user
bundle: user
label: 'Social Links'
description: 'Social network profile links.'
required: false
translatable: false
default_value: {}
default_value_callback: ''
settings: {}
field_type: social_link

View File

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

View File

@@ -0,0 +1,2 @@
label: 'Redes Sociais'
description: 'Links de perfil em redes sociais.'

View File

@@ -67,6 +67,16 @@ function site_users_install() {
]);
}
// Campo Redes Sociais.
if (!$form_display->getComponent('field_user_social_links')) {
$form_display->setComponent('field_user_social_links', [
'type' => 'social_link_widget',
'weight' => 16,
'settings' => [],
'region' => 'content',
]);
}
// Campo Foto Padrão - oculto no form (será gerenciado via hook_form_alter).
$form_display->removeComponent('field_user_default_photo');
@@ -127,6 +137,17 @@ function site_users_install() {
]);
}
// Campo Redes Sociais.
if (!$view_display->getComponent('field_user_social_links')) {
$view_display->setComponent('field_user_social_links', [
'type' => 'social_link_formatter',
'weight' => 16,
'label' => 'above',
'settings' => [],
'region' => 'content',
]);
}
// Campo Foto Padrão.
if (!$view_display->getComponent('field_user_default_photo')) {
$view_display->setComponent('field_user_default_photo', [
@@ -212,3 +233,57 @@ function site_users_update_10001() {
return t('Default photo field created successfully.');
}
/**
* Adds the field_user_social_links field for social network profile links.
*/
function site_users_update_10002() {
// Create field storage if it does not exist.
if (!FieldStorageConfig::loadByName('user', 'field_user_social_links')) {
FieldStorageConfig::create([
'field_name' => 'field_user_social_links',
'entity_type' => 'user',
'type' => 'social_link',
'module' => 'site_users',
'cardinality' => -1,
'translatable' => FALSE,
])->save();
}
// Create field instance if it does not exist.
if (!FieldConfig::loadByName('user', 'user', 'field_user_social_links')) {
FieldConfig::create([
'field_name' => 'field_user_social_links',
'entity_type' => 'user',
'bundle' => 'user',
'label' => 'Social Links',
'description' => 'Social network profile links.',
'required' => FALSE,
])->save();
}
// Add to form display.
$form_display = EntityFormDisplay::load('user.user.default');
if ($form_display && !$form_display->getComponent('field_user_social_links')) {
$form_display->setComponent('field_user_social_links', [
'type' => 'social_link_widget',
'weight' => 16,
'settings' => [],
'region' => 'content',
])->save();
}
// Add to view display.
$view_display = EntityViewDisplay::load('user.user.default');
if ($view_display && !$view_display->getComponent('field_user_social_links')) {
$view_display->setComponent('field_user_social_links', [
'type' => 'social_link_formatter',
'weight' => 16,
'label' => 'above',
'settings' => [],
'region' => 'content',
])->save();
}
return t('Social links field created successfully.');
}

View File

@@ -55,8 +55,7 @@ function site_users_entity_field_access($operation, FieldDefinitionInterface $fi
$profile_fields = [
'field_user_name',
'field_user_phone',
'field_user_social_links',
'field_user_bio',
];

View File

@@ -109,9 +109,8 @@ class UserInfoBlock extends BlockBase implements ContainerFactoryPluginInterface
'username' => $user->getDisplayName(),
'name' => $this->getFieldValue($user, 'field_user_name'),
'phone' => $this->getFieldValue($user, 'field_user_phone'),
'bio' => $this->getFieldValue($user, 'field_user_bio'),
'social_links' => $this->getSocialLinks($user),
'photo_url' => $default_photo_url,
'photo_alt' => $default_photo ? $default_photo->label() : '',
];
@@ -150,6 +149,34 @@ class UserInfoBlock extends BlockBase implements ContainerFactoryPluginInterface
return NULL;
}
/**
* Returns the social network links of a user.
*
* @param \Drupal\user\UserInterface $user
* The user entity.
*
* @return array
* Array of items with 'network' and 'url' keys.
*/
protected function getSocialLinks(UserInterface $user): array {
$links = [];
if (!$user->hasField('field_user_social_links') || $user->get('field_user_social_links')->isEmpty()) {
return $links;
}
foreach ($user->get('field_user_social_links') as $item) {
if (!$item->isEmpty()) {
$links[] = [
'network' => $item->network,
'url' => $item->url,
];
}
}
return $links;
}
/**
* Obtém o valor de um campo do usuário.
*

View File

@@ -0,0 +1,52 @@
<?php
namespace Drupal\site_users\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Url;
use Drupal\site_users\Plugin\Field\FieldType\SocialLinkItem;
/**
* Plugin implementation of the 'social_link_formatter' formatter.
*
* @FieldFormatter(
* id = "social_link_formatter",
* label = @Translation("Social link"),
* field_types = {
* "social_link"
* }
* )
*/
class SocialLinkFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode): array {
$elements = [];
$networks = SocialLinkItem::getNetworks();
foreach ($items as $delta => $item) {
if ($item->isEmpty()) {
continue;
}
$network_label = $networks[$item->network] ?? $item->network;
$elements[$delta] = [
'#type' => 'link',
'#title' => $network_label,
'#url' => Url::fromUri($item->url),
'#attributes' => [
'class' => ['social-link', 'social-link--' . $item->network],
'target' => '_blank',
'rel' => 'noopener noreferrer',
],
];
}
return $elements;
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Drupal\site_users\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* Plugin implementation of the 'social_link' field type.
*
* @FieldType(
* id = "social_link",
* label = @Translation("Social link"),
* description = @Translation("Stores a social network type and profile URL."),
* default_widget = "social_link_widget",
* default_formatter = "social_link_formatter"
* )
*/
class SocialLinkItem extends FieldItemBase {
/**
* Returns the list of available social networks.
*
* @return array
* Associative array keyed by machine name, valued by label.
*/
public static function getNetworks(): array {
return [
'facebook' => 'Facebook',
'instagram' => 'Instagram',
'linkedin' => 'LinkedIn',
'twitter' => 'X (Twitter)',
'youtube' => 'YouTube',
'github' => 'GitHub',
'tiktok' => 'TikTok',
'telegram' => 'Telegram',
'whatsapp' => 'WhatsApp',
'pinterest' => 'Pinterest',
];
}
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition): array {
$properties['network'] = DataDefinition::create('string')
->setLabel(t('Network'))
->setRequired(TRUE);
$properties['url'] = DataDefinition::create('uri')
->setLabel(t('URL'))
->setRequired(TRUE);
return $properties;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition): array {
return [
'columns' => [
'network' => [
'type' => 'varchar',
'length' => 64,
],
'url' => [
'type' => 'varchar',
'length' => 2048,
],
],
];
}
/**
* {@inheritdoc}
*/
public function isEmpty(): bool {
return empty($this->get('network')->getValue())
|| empty($this->get('url')->getValue());
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Drupal\site_users\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\site_users\Plugin\Field\FieldType\SocialLinkItem;
/**
* Plugin implementation of the 'social_link_widget' widget.
*
* @FieldWidget(
* id = "social_link_widget",
* label = @Translation("Social link"),
* field_types = {
* "social_link"
* }
* )
*/
class SocialLinkWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array {
$item = $items[$delta];
$element['network'] = [
'#type' => 'select',
'#title' => $this->t('Network'),
'#options' => ['' => $this->t('- Select -')] + SocialLinkItem::getNetworks(),
'#default_value' => $item->network ?? '',
];
$element['url'] = [
'#type' => 'url',
'#title' => $this->t('Profile URL'),
'#default_value' => $item->url ?? '',
'#maxlength' => 2048,
'#placeholder' => 'https://',
];
return $element;
}
}

View File

@@ -1,20 +1,19 @@
{#
/**
* @file
* Template para o bloco de informações do usuário.
* Template for the user information block.
*
* Variáveis disponíveis:
* - user_info: Array com informações do usuário:
* - uid: ID do usuário
* - username: Nome de usuário (display name)
* - name: Nome completo
* - phone: Telefone
* - category: Categoria
* - dept_code: Código do departamento
* - bio: Biografia
* - photo_url: URL da foto padrão
* - photo_alt: Texto alternativo da foto
* - user: Entidade do usuário.
* Available variables:
* - user_info: Array with user information:
* - uid: User ID
* - username: Display name
* - name: Full name
* - phone: Phone number
* - bio: Biography
* - social_links: Array of social links, each with 'network' and 'url' keys
* - photo_url: Default photo URL
* - photo_alt: Photo alternative text
* - user: User entity.
*/
#}
<div class="site-user-info-block">
@@ -35,22 +34,9 @@
{{ user_info.name ?: user_info.username }}
</h2>
{% if user_info.category %}
<div class="site-user-info-block__category">
{{ user_info.category }}
</div>
{% endif %}
{% if user_info.dept_code %}
<div class="site-user-info-block__dept">
<span class="site-user-info-block__label">{{ 'Departamento:'|t }}</span>
{{ user_info.dept_code }}
</div>
{% endif %}
{% if user_info.phone %}
<div class="site-user-info-block__phone">
<span class="site-user-info-block__label">{{ 'Telefone:'|t }}</span>
<span class="site-user-info-block__label">{{ 'Phone:'|t }}</span>
<a href="tel:{{ user_info.phone }}">{{ user_info.phone }}</a>
</div>
{% endif %}
@@ -60,5 +46,18 @@
{{ user_info.bio }}
</div>
{% endif %}
{% if user_info.social_links %}
<div class="site-user-info-block__social-links">
{% for link in user_info.social_links %}
<a href="{{ link.url }}"
class="social-link social-link--{{ link.network }}"
target="_blank"
rel="noopener noreferrer">
{{ link.network }}
</a>
{% endfor %}
</div>
{% endif %}
</div>
</div>

View File

@@ -102,3 +102,32 @@ msgstr "Você pode adicionar no máximo @max fotos. Atualmente há @count fotos
# Install/update
msgid "Default photo field created successfully."
msgstr "Campo de foto padrão criado com sucesso."
msgid "Social links field created successfully."
msgstr "Campo de redes sociais criado com sucesso."
# Social link field type
msgid "Social link"
msgstr "Link social"
msgid "Stores a social network type and profile URL."
msgstr "Armazena um tipo de rede social e URL de perfil."
msgid "Network"
msgstr "Rede"
msgid "- Select -"
msgstr "- Selecione -"
msgid "Profile URL"
msgstr "URL do perfil"
msgid "Social Links"
msgstr "Redes Sociais"
msgid "Social network profile links."
msgstr "Links de perfil em redes sociais."
# Template
msgid "Phone:"
msgstr "Telefone:"