Files
ldap_groups_sync/src/Form/UnifiedAccessRulesForm.php
Quintino A. G. Souza 72813e0f30 Centraliza configuração de access rules no módulo pai
- Adiciona rota, menu e abas unificados em ldap_groups_sync
- Cria LdapGroupsSyncController (overview dos submódulos)
- Cria UnifiedAccessRulesForm: tabela combinada de regras de todos os
  submódulos habilitados, com único botão "Add Rule"
- Cria GlobalAccessRuleForm: estende AccessRuleFormBase com parâmetro
  {group_type} na rota; exibe select "Group Type" com AJAX rebuild ao
  criar novas regras (desabilitado ao editar)
- Remove rotas access_rules e access_rule_form dos submódulos
- Remove entradas de menu dos submódulos (módulo pai fornece a entrada)
- Atualiza abas dos submódulos para base_route: ldap_groups_sync.config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:03:10 -03:00

284 lines
8.8 KiB
PHP

<?php
namespace Drupal\ldap_groups_sync\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Unified access rules listing for all LDAP group sync submodules.
*
* Shows a combined table of access rules from every enabled submodule and
* allows removing individual rules. New rules and edits open a modal using
* GlobalAccessRuleForm routed through ldap_groups_sync.access_rule_form.
*/
class UnifiedAccessRulesForm extends FormBase {
/**
* Maps group_type key → [config_name, module, label].
*/
protected const GROUP_TYPES = [
'departments' => [
'config' => 'ldap_departments_sync.settings',
'module' => 'ldap_departments_sync',
'label' => 'Departments',
],
'research_groups' => [
'config' => 'ldap_research_groups_sync.settings',
'module' => 'ldap_research_groups_sync',
'label' => 'Research Groups',
],
];
/**
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructor.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
EntityTypeManagerInterface $entity_type_manager,
ModuleHandlerInterface $module_handler
) {
$this->configFactory = $config_factory;
$this->entityTypeManager = $entity_type_manager;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('entity_type.manager'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'ldap_groups_sync_unified_access_rules_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$form['#attached']['library'][] = 'core/drupal.dialog.ajax';
$form['description'] = [
'#type' => 'markup',
'#markup' => '<p>' . $this->t(
'Define rules that restrict or grant entity operations based on group membership.<br>'
. '<strong>Restrictive</strong>: if a matching rule exists and the user does not satisfy it, access is denied.<br>'
. '<strong>Additive</strong>: the rule only grants extra access; other users keep their default permissions.'
) . '</p>',
];
$form['summary'] = [
'#type' => 'table',
'#header' => [
$this->t('Type'),
$this->t('Label'),
$this->t('Entity type / Bundle'),
$this->t('Operations'),
$this->t('Groups / Roles'),
$this->t('Mode'),
$this->t('Status'),
$this->t('Edit'),
$this->t('Remove'),
],
'#empty' => $this->t('No access rules defined. Use the "Add Rule" buttons below to create the first one.'),
'#attributes' => ['class' => ['access-rules-summary-table']],
'#tree' => TRUE,
];
foreach (self::GROUP_TYPES as $group_type => $info) {
if (!$this->moduleHandler->moduleExists($info['module'])) {
continue;
}
$rules = $this->configFactory->get($info['config'])->get('access_rules') ?? [];
foreach ($rules as $i => $rule) {
$row_key = $group_type . '__' . $i;
$bundle_part = !empty($rule['bundle'])
? ' / <em>' . $rule['bundle'] . '</em>'
: ' / <em>' . $this->t('all bundles') . '</em>';
$form['summary'][$row_key]['type'] = [
'#markup' => $info['label'],
];
$form['summary'][$row_key]['label'] = [
'#markup' => '<strong>' . ($rule['label'] ?: $this->t('(no label)')) . '</strong>',
];
$form['summary'][$row_key]['entity_bundle'] = [
'#markup' => ($rule['entity_type'] ?? '?') . $bundle_part,
];
$form['summary'][$row_key]['operations'] = [
'#markup' => implode(', ', $rule['operations'] ?? []),
];
$membership_lines = [];
foreach ($rule['memberships'] ?? [] as $mem) {
try {
$group = $this->entityTypeManager->getStorage('group')->load($mem['group_id'] ?? 0);
$group_label = $group ? $group->label() : '#' . ($mem['group_id'] ?? '?');
}
catch (\Exception $e) {
$group_label = '#' . ($mem['group_id'] ?? '?');
}
$roles = implode(', ', $mem['roles'] ?? []);
$membership_lines[] = $group_label . ($roles ? ' <em>(' . $roles . ')</em>' : '');
}
$form['summary'][$row_key]['memberships_summary'] = [
'#markup' => $membership_lines
? implode('<br>', $membership_lines)
: '<em>' . $this->t('none') . '</em>',
];
$form['summary'][$row_key]['mode'] = [
'#markup' => ($rule['mode'] ?? 'restrictive') === 'additive'
? $this->t('Additive')
: $this->t('Restrictive'),
];
$form['summary'][$row_key]['enabled'] = [
'#markup' => ($rule['enabled'] ?? TRUE)
? '<span style="color:green">&#10003; ' . $this->t('On') . '</span>'
: '<span style="color:gray">&#10007; ' . $this->t('Off') . '</span>',
];
$form['summary'][$row_key]['edit'] = [
'#type' => 'link',
'#title' => $this->t('Edit'),
'#url' => Url::fromRoute('ldap_groups_sync.access_rule_form', [
'group_type' => $group_type,
'rule_index' => $i,
]),
'#attributes' => [
'class' => ['use-ajax', 'button', 'button--small'],
'data-dialog-type' => 'modal',
'data-dialog-options' => json_encode([
'width' => 860,
'title' => $this->t('Edit Access Rule'),
]),
],
];
$form['summary'][$row_key]['remove'] = [
'#type' => 'checkbox',
'#default_value' => FALSE,
];
}
}
$form['actions'] = ['#type' => 'actions'];
// Find the first enabled submodule to use as the initial group_type in the
// modal URL. The modal itself exposes a "Group Type" select so the user can
// switch to any enabled type without closing and re-opening the dialog.
$default_group_type = NULL;
foreach (self::GROUP_TYPES as $group_type => $info) {
if ($this->moduleHandler->moduleExists($info['module'])) {
$default_group_type = $group_type;
break;
}
}
if ($default_group_type !== NULL) {
$form['actions']['add_rule'] = [
'#type' => 'link',
'#title' => $this->t('Add Rule'),
'#url' => Url::fromRoute('ldap_groups_sync.access_rule_form', [
'group_type' => $default_group_type,
'rule_index' => 'new',
]),
'#attributes' => [
'class' => ['use-ajax', 'button', 'button--primary'],
'data-dialog-type' => 'modal',
'data-dialog-options' => json_encode([
'width' => 860,
'title' => $this->t('New Access Rule'),
]),
],
];
}
$form['actions']['remove_selected'] = [
'#type' => 'submit',
'#value' => $this->t('Remove Selected'),
'#limit_validation_errors' => [['summary']],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
$summary_values = $form_state->getValue('summary') ?? [];
// Collect indices to remove per group type.
$to_remove = [];
foreach ($summary_values as $row_key => $row) {
if (empty($row['remove'])) {
continue;
}
// row_key format: {group_type}__{rule_index}
[$group_type, $rule_index] = explode('__', $row_key, 2);
$to_remove[$group_type][] = (int) $rule_index;
}
foreach (self::GROUP_TYPES as $group_type => $info) {
if (!isset($to_remove[$group_type])) {
continue;
}
if (!$this->moduleHandler->moduleExists($info['module'])) {
continue;
}
$config = $this->configFactory->getEditable($info['config']);
$rules = $config->get('access_rules') ?? [];
$kept = [];
foreach ($rules as $i => $rule) {
if (!in_array($i, $to_remove[$group_type], TRUE)) {
$kept[] = $rule;
}
}
$config->set('access_rules', array_values($kept))->save();
}
$this->messenger()->addStatus($this->t('Access rules updated.'));
}
}