mirror of
https://gitlab.unicamp.br/infimecc_drupal11_modules/ldap_groups_sync.git
synced 2026-03-12 02:58:33 -03:00
Inicializa módulo base ldap_groups_sync
Cria super-módulo com infraestrutura compartilhada de regras de acesso para os módulos de sincronização LDAP de grupos. - GroupAccessRulesService: serviço parametrizável por config name - AccessRulesFormBase: listagem/remoção de regras (classe abstrata) - AccessRuleFormBase: formulário modal de criação/edição (classe abstrata) - Sub-módulos ldap_departments_sync e ldap_research_groups_sync refatorados para estender as classes base com subclasses mínimas - Traduções pt-br centralizadas em ldap_groups_sync.pt-br.po Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
771
src/Form/AccessRuleFormBase.php
Normal file
771
src/Form/AccessRuleFormBase.php
Normal file
@@ -0,0 +1,771 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ldap_groups_sync\Form;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\CloseModalDialogCommand;
|
||||
use Drupal\Core\Ajax\RedirectCommand;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Base class for the modal form for creating or editing a single access rule.
|
||||
*
|
||||
* Opened from the main config form via a Drupal dialog (use-ajax link).
|
||||
* Saves directly to the module's settings config and closes the modal on
|
||||
* success, triggering a full page reload of the parent.
|
||||
*
|
||||
* Subclasses must implement getConfigName(), getFormId(), getAccessRulesRoute(),
|
||||
* and getDefaultGroupTypeId().
|
||||
*/
|
||||
abstract class AccessRuleFormBase extends FormBase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $bundleInfo;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
ConfigFactoryInterface $config_factory,
|
||||
EntityTypeManagerInterface $entity_type_manager,
|
||||
EntityTypeBundleInfoInterface $bundle_info,
|
||||
EntityFieldManagerInterface $entity_field_manager,
|
||||
RouteMatchInterface $route_match
|
||||
) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->bundleInfo = $bundle_info;
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->routeMatch = $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('config.factory'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('current_route_match')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the config object name for the implementing module.
|
||||
*
|
||||
* @return string
|
||||
* E.g. 'ldap_departments_sync.settings'.
|
||||
*/
|
||||
abstract protected function getConfigName(): string;
|
||||
|
||||
/**
|
||||
* Returns the route name for the access rules listing page.
|
||||
*
|
||||
* @return string
|
||||
* E.g. 'ldap_departments_sync.access_rules'.
|
||||
*/
|
||||
abstract protected function getAccessRulesRoute(): string;
|
||||
|
||||
/**
|
||||
* Returns the default group type ID used when the config has no value set.
|
||||
*
|
||||
* @return string
|
||||
* E.g. 'departments' or 'research_group'.
|
||||
*/
|
||||
abstract protected function getDefaultGroupTypeId(): string;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $rule_index = 'new') {
|
||||
$form['#prefix'] = '<div id="access-rule-form-wrapper">';
|
||||
$form['#suffix'] = '</div>';
|
||||
|
||||
// Attach dialog library so the close button works.
|
||||
$form['#attached']['library'][] = 'core/drupal.dialog.ajax';
|
||||
|
||||
// Resolve the rule index (from route or form_state for AJAX rebuilds).
|
||||
$resolved_index = $form_state->get('rule_index') ?? $rule_index;
|
||||
$form_state->set('rule_index', $resolved_index);
|
||||
|
||||
// Load existing rule data if editing.
|
||||
$rule = $this->loadRule($resolved_index);
|
||||
|
||||
// ---- Resolve current entity_type and bundle (considering AJAX changes) ----
|
||||
$entity_type_id = $form_state->getValue('entity_type') ?? ($rule['entity_type'] ?? '');
|
||||
$bundle = $form_state->getValue('bundle') ?? ($rule['bundle'] ?? '');
|
||||
|
||||
// ---- Basic settings --------------------------------------------------------
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Label'),
|
||||
'#description' => $this->t('Human-readable description of this rule.'),
|
||||
'#default_value' => $rule['label'] ?? '',
|
||||
'#required' => TRUE,
|
||||
'#maxlength' => 128,
|
||||
];
|
||||
|
||||
$form['enabled'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Enabled'),
|
||||
'#default_value' => $rule['enabled'] ?? TRUE,
|
||||
];
|
||||
|
||||
// ---- Entity type -----------------------------------------------------------
|
||||
$entity_type_options = $this->getContentEntityTypeOptions();
|
||||
|
||||
$form['entity_type'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Entity Type'),
|
||||
'#options' => $entity_type_options,
|
||||
'#empty_option' => $this->t('- Select entity type -'),
|
||||
'#default_value' => $entity_type_id,
|
||||
'#required' => TRUE,
|
||||
'#ajax' => [
|
||||
'callback' => '::updateDependentFields',
|
||||
'wrapper' => 'access-rule-form-wrapper',
|
||||
'effect' => 'fade',
|
||||
],
|
||||
];
|
||||
|
||||
// ---- Bundle ----------------------------------------------------------------
|
||||
$bundle_options = [];
|
||||
if (!empty($entity_type_id)) {
|
||||
$bundle_options = $this->getBundleOptions($entity_type_id);
|
||||
}
|
||||
|
||||
$form['bundle'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Bundle'),
|
||||
'#description' => $this->t('Leave empty to apply to all bundles of the selected entity type.'),
|
||||
'#options' => $bundle_options,
|
||||
'#empty_option' => $this->t('- All bundles -'),
|
||||
'#default_value' => $bundle,
|
||||
'#ajax' => [
|
||||
'callback' => '::updateDependentFields',
|
||||
'wrapper' => 'access-rule-form-wrapper',
|
||||
'effect' => 'fade',
|
||||
],
|
||||
];
|
||||
|
||||
// ---- Operations ------------------------------------------------------------
|
||||
$form['operations'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Operations'),
|
||||
'#options' => [
|
||||
'create' => $this->t('Create'),
|
||||
'update' => $this->t('Update'),
|
||||
'delete' => $this->t('Delete'),
|
||||
'view' => $this->t('View'),
|
||||
],
|
||||
'#default_value' => $rule['operations'] ?? ['create'],
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
// ---- Mode ------------------------------------------------------------------
|
||||
$form['mode'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Mode'),
|
||||
'#options' => [
|
||||
'restrictive' => $this->t('<strong>Restrictive</strong> — deny access to users who do not match this rule'),
|
||||
'additive' => $this->t('<strong>Additive</strong> — only grant access; never deny other users'),
|
||||
],
|
||||
'#default_value' => $rule['mode'] ?? 'restrictive',
|
||||
];
|
||||
|
||||
// ---- Membership requirements -----------------------------------------------
|
||||
$form['membership'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Membership requirements'),
|
||||
'#description' => $this->t(
|
||||
'A user is authorized if they satisfy <strong>at least one card</strong>: '
|
||||
. 'member of that group <em>and</em> holding at least one of the checked roles. '
|
||||
. 'Each card can specify a different group and different roles.'
|
||||
),
|
||||
];
|
||||
|
||||
$membership_count = $form_state->get('membership_count');
|
||||
|
||||
// Prefer form state values (preserves user edits during AJAX rebuilds).
|
||||
// Normalize from submitted format: checkboxes return [id => id|0], we
|
||||
// need [id, ...] for #default_value.
|
||||
$memberships_from_state = $form_state->getValue('memberships');
|
||||
if ($memberships_from_state !== NULL) {
|
||||
$existing_memberships = [];
|
||||
foreach ($memberships_from_state as $mem) {
|
||||
$existing_memberships[] = [
|
||||
'group_id' => $mem['group_id'] ?? '',
|
||||
'roles' => is_array($mem['roles'] ?? NULL)
|
||||
? array_values(array_filter($mem['roles']))
|
||||
: [],
|
||||
];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$existing_memberships = $rule['memberships'] ?? [];
|
||||
}
|
||||
|
||||
if ($membership_count === NULL) {
|
||||
$membership_count = max(1, count($existing_memberships));
|
||||
$form_state->set('membership_count', $membership_count);
|
||||
}
|
||||
|
||||
$group_options = $this->getGroupOptions();
|
||||
$role_options = $this->getGroupRoleOptions();
|
||||
|
||||
// #tree => TRUE ensures values are nested as memberships[m][group_id],
|
||||
// memberships[m][roles], etc. Without it, all group_id fields would
|
||||
// collide at the top-level form values.
|
||||
$form['membership']['memberships'] = [
|
||||
'#type' => 'container',
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
|
||||
// Render one card (container) per membership entry.
|
||||
for ($m = 0; $m < $membership_count; $m++) {
|
||||
$mem = $existing_memberships[$m] ?? ['group_id' => '', 'roles' => []];
|
||||
|
||||
$form['membership']['memberships'][$m] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => ['membership-card'],
|
||||
'style' => 'border:1px solid #ccc;border-radius:4px;padding:12px 16px;margin-bottom:12px;background:#fafafa',
|
||||
],
|
||||
];
|
||||
|
||||
$form['membership']['memberships'][$m]['group_id'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Group'),
|
||||
'#options' => $group_options,
|
||||
'#empty_option' => $this->t('- Select a group -'),
|
||||
'#default_value' => $mem['group_id'] ?? '',
|
||||
'#required' => FALSE,
|
||||
];
|
||||
|
||||
// Checkboxes display the full label — no truncation.
|
||||
$form['membership']['memberships'][$m]['roles'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Required roles <em>(user must hold at least one)</em>'),
|
||||
'#options' => $role_options,
|
||||
'#default_value' => $mem['roles'] ?? [],
|
||||
];
|
||||
|
||||
$form['membership']['memberships'][$m]['remove'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Remove this group'),
|
||||
'#name' => 'remove_membership_' . $m,
|
||||
'#submit' => ['::removeOneMembership'],
|
||||
'#ajax' => [
|
||||
'callback' => '::updateDependentFields',
|
||||
'wrapper' => 'access-rule-form-wrapper',
|
||||
'effect' => 'fade',
|
||||
],
|
||||
'#limit_validation_errors' => [['memberships']],
|
||||
'#attributes' => ['class' => ['button', 'button--danger', 'button--small']],
|
||||
];
|
||||
}
|
||||
|
||||
$form['membership']['add_membership'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('+ Add group'),
|
||||
'#submit' => ['::addMembership'],
|
||||
'#ajax' => [
|
||||
'callback' => '::updateDependentFields',
|
||||
'wrapper' => 'access-rule-form-wrapper',
|
||||
'effect' => 'fade',
|
||||
],
|
||||
'#limit_validation_errors' => [['memberships']],
|
||||
'#attributes' => ['class' => ['button']],
|
||||
];
|
||||
|
||||
// ---- Field conditions (update / delete / view only) -----------------------
|
||||
$form['field_conditions_wrapper'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Field conditions <em>(optional — only evaluated for update/delete/view)</em>'),
|
||||
'#open' => !empty($rule['field_conditions']),
|
||||
'#description' => $this->t('All conditions must be true for the rule to apply to an existing entity. For <em>create</em> operations conditions are ignored.'),
|
||||
];
|
||||
|
||||
$field_options = [];
|
||||
if (!empty($entity_type_id)) {
|
||||
$field_options = $this->getFieldOptions($entity_type_id, $bundle ?: NULL);
|
||||
}
|
||||
|
||||
$cond_count = $form_state->get('condition_count');
|
||||
if ($cond_count === NULL) {
|
||||
$cond_count = max(1, count($rule['field_conditions'] ?? []));
|
||||
$form_state->set('condition_count', $cond_count);
|
||||
}
|
||||
|
||||
$existing_conditions = $rule['field_conditions'] ?? [];
|
||||
|
||||
$form['field_conditions_wrapper']['field_conditions'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => [
|
||||
$this->t('Field'),
|
||||
$this->t('Value'),
|
||||
$this->t('Remove'),
|
||||
],
|
||||
];
|
||||
|
||||
for ($c = 0; $c < $cond_count; $c++) {
|
||||
$cond = $existing_conditions[$c] ?? ['field' => '', 'value' => ''];
|
||||
|
||||
if (!empty($field_options)) {
|
||||
$form['field_conditions_wrapper']['field_conditions'][$c]['field'] = [
|
||||
'#type' => 'select',
|
||||
'#options' => $field_options,
|
||||
'#empty_option' => $this->t('- Select field -'),
|
||||
'#default_value' => $cond['field'],
|
||||
];
|
||||
}
|
||||
else {
|
||||
$form['field_conditions_wrapper']['field_conditions'][$c]['field'] = [
|
||||
'#type' => 'textfield',
|
||||
'#placeholder' => $this->t('field_machine_name'),
|
||||
'#default_value' => $cond['field'],
|
||||
'#size' => 24,
|
||||
];
|
||||
}
|
||||
|
||||
$form['field_conditions_wrapper']['field_conditions'][$c]['value'] = [
|
||||
'#type' => 'textfield',
|
||||
'#placeholder' => $this->t('Expected value'),
|
||||
'#default_value' => $cond['value'],
|
||||
'#size' => 24,
|
||||
];
|
||||
|
||||
$form['field_conditions_wrapper']['field_conditions'][$c]['remove'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => FALSE,
|
||||
];
|
||||
}
|
||||
|
||||
$form['field_conditions_wrapper']['condition_actions'] = [
|
||||
'#type' => 'container',
|
||||
];
|
||||
|
||||
$form['field_conditions_wrapper']['condition_actions']['add_condition'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Add condition'),
|
||||
'#submit' => ['::addCondition'],
|
||||
'#ajax' => [
|
||||
'callback' => '::updateDependentFields',
|
||||
'wrapper' => 'access-rule-form-wrapper',
|
||||
'effect' => 'fade',
|
||||
],
|
||||
'#limit_validation_errors' => [['field_conditions']],
|
||||
];
|
||||
|
||||
$form['field_conditions_wrapper']['condition_actions']['remove_conditions'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Remove selected conditions'),
|
||||
'#submit' => ['::removeSelectedConditions'],
|
||||
'#ajax' => [
|
||||
'callback' => '::updateDependentFields',
|
||||
'wrapper' => 'access-rule-form-wrapper',
|
||||
'effect' => 'fade',
|
||||
],
|
||||
'#limit_validation_errors' => [['field_conditions']],
|
||||
];
|
||||
|
||||
// ---- Submit ----------------------------------------------------------------
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save rule'),
|
||||
'#button_type' => 'primary',
|
||||
'#ajax' => [
|
||||
'callback' => '::ajaxSave',
|
||||
'wrapper' => 'access-rule-form-wrapper',
|
||||
],
|
||||
];
|
||||
|
||||
$form['actions']['cancel'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Cancel'),
|
||||
'#submit' => [],
|
||||
'#limit_validation_errors' => [],
|
||||
'#ajax' => [
|
||||
'callback' => '::cancelDialog',
|
||||
],
|
||||
'#attributes' => ['class' => ['button']],
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX callback: replaces the entire form wrapper (entity_type/bundle/field
|
||||
* changes and condition add/remove all reuse this single callback).
|
||||
*/
|
||||
public function updateDependentFields(array &$form, FormStateInterface $form_state) {
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler: adds a blank membership card.
|
||||
*
|
||||
* Explicitly normalizes current values and appends an empty entry so the
|
||||
* new card has no pre-filled group or roles.
|
||||
*/
|
||||
public function addMembership(array &$form, FormStateInterface $form_state) {
|
||||
$current = $form_state->getValue('memberships') ?? [];
|
||||
|
||||
// Normalize existing entries (checkboxes submit [id => id|0] format).
|
||||
$normalized = [];
|
||||
foreach ($current as $mem) {
|
||||
$normalized[] = [
|
||||
'group_id' => $mem['group_id'] ?? '',
|
||||
'roles' => is_array($mem['roles'] ?? NULL)
|
||||
? array_values(array_filter($mem['roles']))
|
||||
: [],
|
||||
];
|
||||
}
|
||||
|
||||
// Append a truly empty entry for the new card.
|
||||
$normalized[] = ['group_id' => '', 'roles' => []];
|
||||
|
||||
$form_state->setValue('memberships', $normalized);
|
||||
$form_state->set('membership_count', count($normalized));
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler: removes the single membership card whose button was clicked.
|
||||
*/
|
||||
public function removeOneMembership(array &$form, FormStateInterface $form_state) {
|
||||
$trigger = $form_state->getTriggeringElement();
|
||||
preg_match('/remove_membership_(\d+)/', $trigger['#name'] ?? '', $matches);
|
||||
$index = isset($matches[1]) ? (int) $matches[1] : -1;
|
||||
|
||||
$memberships = $form_state->getValue('memberships') ?? [];
|
||||
|
||||
// Normalize and remove the target entry.
|
||||
$normalized = [];
|
||||
foreach ($memberships as $i => $mem) {
|
||||
if ($i === $index) {
|
||||
continue;
|
||||
}
|
||||
$normalized[] = [
|
||||
'group_id' => $mem['group_id'] ?? '',
|
||||
'roles' => is_array($mem['roles'] ?? NULL)
|
||||
? array_values(array_filter($mem['roles']))
|
||||
: [],
|
||||
];
|
||||
}
|
||||
|
||||
$form_state->setValue('memberships', $normalized);
|
||||
$form_state->set('membership_count', max(1, count($normalized)));
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler: adds a blank condition row.
|
||||
*/
|
||||
public function addCondition(array &$form, FormStateInterface $form_state) {
|
||||
$form_state->set('condition_count', ($form_state->get('condition_count') ?? 1) + 1);
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler: removes checked condition rows.
|
||||
*/
|
||||
public function removeSelectedConditions(array &$form, FormStateInterface $form_state) {
|
||||
$conditions = $form_state->getValue('field_conditions') ?? [];
|
||||
$kept = array_values(array_filter($conditions, fn($c) => empty($c['remove'])));
|
||||
$form_state->setValue('field_conditions', $kept);
|
||||
$form_state->set('condition_count', max(1, count($kept)));
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$operations = array_filter($form_state->getValue('operations') ?? []);
|
||||
if (empty($operations)) {
|
||||
$form_state->setErrorByName('operations', $this->t('Select at least one operation.'));
|
||||
}
|
||||
|
||||
$memberships = $form_state->getValue('memberships') ?? [];
|
||||
$valid = array_filter($memberships, fn($m) => !empty($m['group_id']));
|
||||
if (empty($valid)) {
|
||||
$form_state->setErrorByName('memberships', $this->t('Add at least one group membership requirement.'));
|
||||
}
|
||||
|
||||
foreach ($valid as $idx => $mem) {
|
||||
if (empty($mem['roles'])) {
|
||||
$form_state->setErrorByName(
|
||||
"memberships][$idx][roles",
|
||||
$this->t('Select at least one required role for each group.')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX callback: closes the modal and redirects after a successful save.
|
||||
*
|
||||
* The actual save is performed by submitForm(), which Drupal always runs
|
||||
* before the AJAX callback. This callback must NOT call saveRule() again
|
||||
* to avoid saving the rule twice.
|
||||
*/
|
||||
public function ajaxSave(array &$form, FormStateInterface $form_state) {
|
||||
if ($form_state->hasAnyErrors()) {
|
||||
// Redisplay the form with error messages.
|
||||
return $form;
|
||||
}
|
||||
|
||||
$response = new AjaxResponse();
|
||||
$response->addCommand(new CloseModalDialogCommand());
|
||||
$response->addCommand(new RedirectCommand(
|
||||
Url::fromRoute($this->getAccessRulesRoute())->toString()
|
||||
));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX callback: closes the modal without saving.
|
||||
*/
|
||||
public function cancelDialog(array &$form, FormStateInterface $form_state): AjaxResponse {
|
||||
$response = new AjaxResponse();
|
||||
$response->addCommand(new CloseModalDialogCommand());
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Runs for both AJAX and non-AJAX submissions.
|
||||
$this->saveRule($form_state);
|
||||
$form_state->setRedirectUrl(Url::fromRoute($this->getAccessRulesRoute()));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Loads a rule from config by index ('new' returns defaults).
|
||||
*/
|
||||
protected function loadRule($rule_index): array {
|
||||
if ($rule_index === 'new') {
|
||||
return [];
|
||||
}
|
||||
$rules = $this->configFactory
|
||||
->get($this->getConfigName())
|
||||
->get('access_rules') ?? [];
|
||||
|
||||
return $rules[(int) $rule_index] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the rule to config.
|
||||
*/
|
||||
protected function saveRule(FormStateInterface $form_state): void {
|
||||
$rule_index = $form_state->get('rule_index') ?? 'new';
|
||||
|
||||
$operations = array_values(array_filter($form_state->getValue('operations') ?? []));
|
||||
|
||||
$conditions_raw = $form_state->getValue('field_conditions') ?? [];
|
||||
$conditions = [];
|
||||
foreach ($conditions_raw as $cond) {
|
||||
if (!empty($cond['remove']) || empty($cond['field'])) {
|
||||
continue;
|
||||
}
|
||||
$conditions[] = [
|
||||
'field' => $cond['field'],
|
||||
'value' => $cond['value'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
$memberships_raw = $form_state->getValue('memberships') ?? [];
|
||||
$memberships = [];
|
||||
foreach ($memberships_raw as $mem) {
|
||||
if (empty($mem['group_id'])) {
|
||||
continue;
|
||||
}
|
||||
// Checkboxes return [role_id => role_id] for checked, [role_id => 0] for unchecked.
|
||||
$roles = array_values(array_filter((array) ($mem['roles'] ?? [])));
|
||||
$memberships[] = [
|
||||
'group_id' => (int) $mem['group_id'],
|
||||
'roles' => $roles,
|
||||
];
|
||||
}
|
||||
|
||||
$new_rule = [
|
||||
'label' => $form_state->getValue('label'),
|
||||
'entity_type' => $form_state->getValue('entity_type'),
|
||||
'bundle' => $form_state->getValue('bundle') ?? '',
|
||||
'operations' => $operations,
|
||||
'mode' => $form_state->getValue('mode') ?? 'restrictive',
|
||||
'memberships' => $memberships,
|
||||
'field_conditions' => $conditions,
|
||||
'enabled' => (bool) $form_state->getValue('enabled'),
|
||||
];
|
||||
|
||||
$config = $this->configFactory->getEditable($this->getConfigName());
|
||||
$rules = $config->get('access_rules') ?? [];
|
||||
|
||||
if ($rule_index === 'new') {
|
||||
$rules[] = $new_rule;
|
||||
}
|
||||
else {
|
||||
$rules[(int) $rule_index] = $new_rule;
|
||||
}
|
||||
|
||||
$config->set('access_rules', array_values($rules))->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns content entity type options suitable for a select element.
|
||||
*/
|
||||
protected function getContentEntityTypeOptions(): array {
|
||||
$options = [];
|
||||
foreach ($this->entityTypeManager->getDefinitions() as $id => $definition) {
|
||||
if (!($definition instanceof \Drupal\Core\Entity\ContentEntityTypeInterface)) {
|
||||
continue;
|
||||
}
|
||||
// Skip internal group-related and config entity types.
|
||||
if (in_array($id, ['group_content', 'group_relationship'], TRUE)) {
|
||||
continue;
|
||||
}
|
||||
$options[$id] = (string) $definition->getLabel();
|
||||
}
|
||||
asort($options);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bundle options for a given entity type.
|
||||
*/
|
||||
protected function getBundleOptions(string $entity_type_id): array {
|
||||
$options = [];
|
||||
try {
|
||||
foreach ($this->bundleInfo->getBundleInfo($entity_type_id) as $bundle_id => $info) {
|
||||
$options[$bundle_id] = (string) ($info['label'] ?? $bundle_id);
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// Return empty if the entity type has no bundles.
|
||||
}
|
||||
asort($options);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns field options for the given entity type and (optional) bundle.
|
||||
*/
|
||||
protected function getFieldOptions(string $entity_type_id, ?string $bundle): array {
|
||||
$options = [];
|
||||
try {
|
||||
$bundle = $bundle ?: $entity_type_id;
|
||||
$definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
|
||||
$skip = ['uuid', 'langcode', 'default_langcode', 'revision_translation_affected'];
|
||||
foreach ($definitions as $name => $def) {
|
||||
if (in_array($name, $skip, TRUE)) {
|
||||
continue;
|
||||
}
|
||||
$options[$name] = (string) $def->getLabel() . ' (' . $name . ')';
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// Return empty on error.
|
||||
}
|
||||
asort($options);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options for all groups of the configured group type.
|
||||
*/
|
||||
protected function getGroupOptions(): array {
|
||||
$options = [];
|
||||
try {
|
||||
$group_type_id = $this->configFactory
|
||||
->get($this->getConfigName())
|
||||
->get('group_type_id') ?: $this->getDefaultGroupTypeId();
|
||||
|
||||
$groups = $this->entityTypeManager
|
||||
->getStorage('group')
|
||||
->loadByProperties(['type' => $group_type_id]);
|
||||
|
||||
foreach ($groups as $group) {
|
||||
$options[$group->id()] = $group->label();
|
||||
}
|
||||
asort($options);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// Return empty on error.
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options for all roles of the configured group type.
|
||||
*/
|
||||
protected function getGroupRoleOptions(): array {
|
||||
$options = [];
|
||||
try {
|
||||
$group_type_id = $this->configFactory
|
||||
->get($this->getConfigName())
|
||||
->get('group_type_id') ?: $this->getDefaultGroupTypeId();
|
||||
|
||||
$roles = $this->entityTypeManager
|
||||
->getStorage('group_role')
|
||||
->loadByProperties(['group_type' => $group_type_id]);
|
||||
|
||||
foreach ($roles as $role) {
|
||||
$scope_labels = [
|
||||
'individual' => $this->t('Individual'),
|
||||
'insider' => $this->t('Insider'),
|
||||
'outsider' => $this->t('Outsider'),
|
||||
];
|
||||
$scope = $role->getScope();
|
||||
$suffix = isset($scope_labels[$scope]) ? ' [' . $scope_labels[$scope] . ']' : '';
|
||||
$options[$role->id()] = $role->label() . $suffix;
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// Return empty on error.
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user