10 Commits

Author SHA1 Message Date
204445853d Define package 'Site Tools' em todos os submódulos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 07:56:22 -03:00
727c726014 Altera type para drupal-custom-module
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 16:13:48 -03:00
9d63359854 Adiciona composer.json aos sub-módulos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 09:45:43 -03:00
d113e932ba Corrige erro em composer.json 2026-03-24 16:01:53 -03:00
f937b84c50 Corrige erro em composer.json 2026-03-24 15:42:28 -03:00
7aefb543e9 Estende PageUserIsCurrentUser para rotas sem parâmetro 'user'
Cascata de resolução do UID do dono da página:
1. Parâmetro 'user' da rota (comportamento original)
2. Parâmetro 'arg_0' numérico (Views pages, ex.: /user/{uid}/blog)
3. Autor do nó (entity.node.canonical sem parâmetro 'user')
4. Fallback: UID extraído do path atual /user/{uid}/...

Injeta CurrentPathStack para suportar o fallback por path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 07:32:55 -03:00
41378004a2 Corrige condição de bloco para rotas sem parâmetro 'user'
Em rotas de nós exibidos como páginas do microsite (/user/{id}/...),
a rota não carrega o parâmetro 'user'. A condição agora usa o autor
do nó como fallback, permitindo que o bloco apareça corretamente
nessas páginas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 08:53:14 -03:00
acda09c277 Adiciona composer.json e .gitlab-ci.yml 2026-03-20 08:44:28 -03:00
0dca85ca8f Documenta sub-módulo site_tools_group_helpers e atualiza README principal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 08:19:55 -03:00
a6e6fd3125 Adiciona sub-módulo site_tools_group_helpers
Fornece um plugin EntityReferenceSelection que restringe campos de
referência a grupos aos grupos dos quais o usuário atual é membro.
Inclui troca automática para o widget options_select ao configurar o
handler, e remoção do #required em target_bundles para campos que
referenciam group, permitindo deixar o tipo de grupo em branco.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 08:08:42 -03:00
15 changed files with 323 additions and 4 deletions

6
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,6 @@
publish-composer:
stage: deploy
script:
- 'curl --data "tag=$CI_COMMIT_TAG" --header "JOB-TOKEN: $CI_JOB_TOKEN" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/composer"'
only:
- tags

View File

@@ -11,6 +11,7 @@ Módulo Drupal com ferramentas utilitárias reutilizáveis para outros módulos
## Sub-módulos
- **site_tools_group_helpers**: Plugin `EntityReferenceSelection` que restringe campos de referência a grupos aos grupos dos quais o usuário atual é membro, com troca automática para o widget `options_select`. Requer o módulo contrib `group`.
- **site_tools_msc_2020**: Widget de seleção em cascata (13 níveis) para o vocabulário MSC 2020, com carregamento eficiente dos 597 termos via `loadTree()` + consulta direta ao banco.
- **site_tools_msc_2020_migrate**: Migrations para importação do vocabulário MSC 2020 a partir de CSV.

10
composer.json Normal file
View File

@@ -0,0 +1,10 @@
{
"name": "imecc/site_tools",
"description": "Módulo Drupal com ferramentas utilitárias reutilizáveis em outros módulos do site.",
"type": "drupal-custom-module",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=8.1",
"drupal/core": "^10.3 || ^11"
}
}

View File

@@ -0,0 +1,50 @@
# Site Tools — Group Helpers
Sub-módulo do [Site Tools](../../README.md) com utilitários para integração com o módulo contrib [Group](https://www.drupal.org/project/group).
## Requisitos
- `site_tools`
- `group` (contrib) ^3.0
## Funcionalidades
### Plugin de seleção: Member groups
Plugin `EntityReferenceSelection` com id `site_tools_group_helpers` que restringe campos de referência à entidade `group` aos grupos dos quais o **usuário atual é membro**, em vez de listar todos os grupos do site.
#### Como usar
No formulário de configuração de um campo `entity_reference` com `target_type: group`, selecione **"Member groups (current user)"** em _Reference type_.
Em YAML de configuração:
```yaml
handler: 'site_tools_group_helpers'
handler_settings:
target_bundles: null # null = qualquer tipo de grupo
```
O módulo também remove a obrigatoriedade de selecionar um tipo de grupo no formulário de configuração do campo, para campos que referenciam `group` — tanto neste handler quanto no handler padrão.
### Troca automática de widget
Ao configurar um campo para usar o handler `site_tools_group_helpers`, o módulo atualiza automaticamente todos os form displays daquele campo para o widget **`options_select`** (caixa de seleção), mais adequado quando o número de opções é pequeno.
Ao reverter para outro handler, o widget é restaurado para `entity_reference_autocomplete`.
## Instalação
```bash
drush en site_tools_group_helpers
drush cr
```
Na instalação, o módulo verifica se há campos existentes já configurados com o handler `site_tools_group_helpers` (por exemplo, após importação de configuração) e aplica o widget `options_select` automaticamente.
## Dependência em outros módulos
```yaml
dependencies:
- site_tools:site_tools_group_helpers
```

View File

@@ -0,0 +1,12 @@
{
"name": "imecc/site_tools_group_helpers",
"description": "Utilitários reutilizáveis para integração com o módulo Group.",
"type": "drupal-module",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=8.1",
"drupal/core": "^10.3 || ^11",
"imecc/site_tools": "*",
"drupal/group": "*"
}
}

View File

@@ -0,0 +1,8 @@
name: 'Site Tools — Group Helpers'
type: module
description: 'Utilitários reutilizáveis para integração com o módulo Group.'
core_version_requirement: ^10.3 || ^11
package: 'Site Tools'
dependencies:
- site_tools:site_tools
- group:group

View File

@@ -0,0 +1,27 @@
<?php
/**
* @file
* Install hooks for site_tools_group_helpers.
*/
declare(strict_types=1);
/**
* Implements hook_install().
*
* Applies the options_select widget to any entity_reference field already
* configured to use the site_tools_group_helpers selection handler, for
* example after a config import followed by module installation.
*/
function site_tools_group_helpers_install(): void {
$field_configs = \Drupal::entityTypeManager()
->getStorage('field_config')
->loadByProperties(['field_type' => 'entity_reference']);
foreach ($field_configs as $field_config) {
if ($field_config->getSetting('handler') === 'site_tools_group_helpers') {
_site_tools_group_helpers_set_widget($field_config, 'options_select', []);
}
}
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* @file
* Module hooks for site_tools_group_helpers.
*/
declare(strict_types=1);
use Drupal\Core\Form\FormStateInterface;
use Drupal\field\FieldConfigInterface;
/**
* Implements hook_form_FORM_ID_alter() for field_config_edit_form.
*
* Removes the #required constraint from the target_bundles checkboxes when
* the field references the 'group' entity type, allowing the selection to be
* left empty (= any group type) regardless of which handler is active.
*/
function site_tools_group_helpers_form_field_config_edit_form_alter(array &$form, FormStateInterface $form_state): void {
$field = $form_state->getFormObject()->getEntity();
if ($field->getFieldStorageDefinition()->getSetting('target_type') !== 'group') {
return;
}
$target_bundles = &$form['settings']['handler']['handler_settings']['target_bundles'] ?? NULL;
if ($target_bundles !== NULL) {
$target_bundles['#required'] = FALSE;
}
}
/**
* Implements hook_field_config_update().
*
* When a field switches to the site_tools_group_helpers selection handler,
* automatically updates all its form displays to use the options_select widget.
* When it switches away, reverts to entity_reference_autocomplete.
*/
function site_tools_group_helpers_field_config_update(FieldConfigInterface $field_config): void {
$handler = $field_config->getSetting('handler');
$original_handler = $field_config->original?->getSetting('handler');
if ($handler === $original_handler) {
return;
}
if ($handler === 'site_tools_group_helpers') {
_site_tools_group_helpers_set_widget($field_config, 'options_select', []);
}
elseif ($original_handler === 'site_tools_group_helpers') {
_site_tools_group_helpers_set_widget($field_config, 'entity_reference_autocomplete', [
'match_operator' => 'CONTAINS',
'match_limit' => 10,
'size' => 60,
'placeholder' => '',
]);
}
}
/**
* Updates the widget type for a field across all its form displays.
*
* @param \Drupal\field\FieldConfigInterface $field_config
* The field config entity.
* @param string $widget_type
* The widget plugin ID to apply.
* @param array $settings
* The widget settings to apply.
*/
function _site_tools_group_helpers_set_widget(FieldConfigInterface $field_config, string $widget_type, array $settings): void {
$entity_type_id = $field_config->getTargetEntityTypeId();
$bundle = $field_config->getTargetBundle();
$field_name = $field_config->getName();
$form_displays = \Drupal::entityTypeManager()
->getStorage('entity_form_display')
->loadByProperties([
'targetEntityType' => $entity_type_id,
'bundle' => $bundle,
]);
foreach ($form_displays as $form_display) {
$component = $form_display->getComponent($field_name);
if ($component === NULL) {
continue;
}
$form_display->setComponent($field_name, [
'type' => $widget_type,
'weight' => $component['weight'],
'region' => $component['region'] ?? 'content',
'settings' => $settings,
'third_party_settings' => $component['third_party_settings'] ?? [],
]);
$form_display->save();
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Drupal\site_tools_group_helpers\Plugin\EntityReferenceSelection;
use Drupal\Core\Entity\Attribute\EntityReferenceSelection;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\group\Entity\GroupMembership;
/**
* Restricts group selection to groups where the current user is a member.
*
* Use this handler on any entity_reference field that targets the 'group'
* entity type when only the groups the current user belongs to should appear
* as options. Users with no memberships will see an empty list.
*/
#[EntityReferenceSelection(
id: 'site_tools_group_helpers',
label: new TranslatableMarkup('Member groups (current user)'),
entity_types: ['group'],
group: 'site_tools_group_helpers',
weight: 0,
)]
class MemberGroupSelection extends DefaultSelection {
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
// Allow leaving group types empty (= any type), matching the behaviour
// of the default selection handler.
if (isset($form['target_bundles'])) {
$form['target_bundles']['#required'] = FALSE;
}
return $form;
}
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
$query = parent::buildEntityQuery($match, $match_operator);
$memberships = GroupMembership::loadByUser($this->currentUser);
if (empty($memberships)) {
// User has no memberships — return an empty result set.
$query->condition('id', 0, '=');
return $query;
}
$gids = array_map(fn($m) => $m->getGroupId(), $memberships);
$query->condition('id', $gids, 'IN');
return $query;
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "imecc/site_tools_msc_2020",
"description": "Vocabulário de taxonomia MSC 2020 (Mathematics Subject Classification).",
"type": "drupal-module",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=8.1",
"drupal/core": "^10 || ^11",
"imecc/site_tools": "*"
}
}

View File

@@ -2,7 +2,7 @@ name: 'Site Tools MSC 2020'
type: module
description: 'Vocabulário de taxonomia MSC 2020 (Mathematics Subject Classification).'
core_version_requirement: ^10 || ^11
package: Custom
package: 'Site Tools'
dependencies:
- site_tools:site_tools
- drupal:taxonomy

View File

@@ -0,0 +1,13 @@
{
"name": "imecc/site_tools_msc_2020_migrate",
"description": "Migrations para importação dos termos MSC 2020 (inglês e português).",
"type": "drupal-module",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=8.1",
"drupal/core": "^10 || ^11",
"imecc/site_tools_msc_2020": "*",
"drupal/migrate_plus": "*",
"drupal/migrate_source_csv": "*"
}
}

View File

@@ -2,7 +2,7 @@ name: 'Site Tools MSC 2020 Migrate'
type: module
description: 'Migrations para importação dos termos MSC 2020 (inglês e português).'
core_version_requirement: ^10 || ^11
package: Custom
package: 'Site Tools'
dependencies:
- site_tools:site_tools_msc_2020
- migrate:migrate

View File

@@ -2,4 +2,4 @@ name: Site Tools
type: module
description: 'Ferramentas utilitárias reutilizáveis para outros módulos do site.'
core_version_requirement: ^10.3 || ^11
package: Custom
package: 'Site Tools'

View File

@@ -4,9 +4,11 @@ namespace Drupal\site_tools\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -29,6 +31,7 @@ class PageUserIsCurrentUser extends ConditionPluginBase implements ContainerFact
$plugin_definition,
protected RouteMatchInterface $routeMatch,
protected AccountInterface $currentUser,
protected CurrentPathStack $currentPath,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
@@ -40,6 +43,7 @@ class PageUserIsCurrentUser extends ConditionPluginBase implements ContainerFact
$plugin_definition,
$container->get('current_route_match'),
$container->get('current_user'),
$container->get('path.current'),
);
}
@@ -89,7 +93,22 @@ class PageUserIsCurrentUser extends ConditionPluginBase implements ContainerFact
$page_uid = (int) $param;
}
else {
return FALSE;
// Views pages com arg_0 numérico (ex.: /user/{uid}/blog).
$arg0 = $this->routeMatch->getParameter('arg_0');
if (is_numeric($arg0)) {
$page_uid = (int) $arg0;
}
// Nós do microsite: usa o autor como usuário da página.
elseif (($node = $this->routeMatch->getParameter('node')) instanceof NodeInterface) {
$page_uid = (int) $node->getOwnerId();
}
// Fallback: extrai o UID do path atual /user/{uid}/...
elseif (preg_match('#^/user/(\d+)(/|$)#', $this->currentPath->getPath(), $m)) {
$page_uid = (int) $m[1];
}
else {
return FALSE;
}
}
return $page_uid === (int) $this->currentUser->id();