[ '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' => '

' . $this->t( 'Define rules that restrict or grant entity operations based on group membership.
' . 'Restrictive: if a matching rule exists and the user does not satisfy it, access is denied.
' . 'Additive: the rule only grants extra access; other users keep their default permissions.' ) . '

', ]; $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']) ? ' / ' . $rule['bundle'] . '' : ' / ' . $this->t('all bundles') . ''; $form['summary'][$row_key]['type'] = [ '#markup' => $info['label'], ]; $form['summary'][$row_key]['label'] = [ '#markup' => '' . ($rule['label'] ?: $this->t('(no label)')) . '', ]; $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 ? ' (' . $roles . ')' : ''); } $form['summary'][$row_key]['memberships_summary'] = [ '#markup' => $membership_lines ? implode('
', $membership_lines) : '' . $this->t('none') . '', ]; $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) ? '✓ ' . $this->t('On') . '' : '✗ ' . $this->t('Off') . '', ]; $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.')); } }