form_field_access-1.0.0-alpha1/src/FormFieldAccess.php
src/FormFieldAccess.php
<?php namespace Drupal\form_field_access; use Drupal\Core\Access\AccessResult; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Security\TrustedCallbackInterface; use Drupal\Core\Session\AccountInterface; class FormFieldAccess implements TrustedCallbackInterface { /** * Implements hook_entity_field_access(). * * @param $operation * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition * @param \Drupal\Core\Session\AccountInterface $account * @param \Drupal\Core\Field\FieldItemListInterface|NULL $items * * @return \Drupal\Core\Access\AccessResultForbidden|\Drupal\Core\Access\AccessResultNeutral * Access result. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ public function entityFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) { $result = AccessResult::neutral(); if ($operation === 'edit' && !empty($items) ) { $parent_data_definition = $items->getParent()->getDataDefinition(); $parent_entity_type_id = $parent_data_definition->getEntityTypeId(); $parent_bundles = $parent_data_definition->getBundles(); $parent_bundle = reset($parent_bundles); if ($form_field_access = self::loadFormFieldAccess($parent_entity_type_id, $parent_bundle)) { if (!empty($form_field_access)) { $field_roles = $form_field_access->getDisallowedFieldRoles($field_definition->getName()); if (!empty($field_roles)) { $matched_roles = array_intersect($field_roles, $account->getRoles()); if (!empty($matched_roles)) { $result = AccessResult::forbidden(); } } } } } return $result; } /** * Implements hook_element_info_alter(). * * @param array $info * Elements list. */ public function elementInfoAlter(array &$info): void { foreach ($info as $key => &$value) { $value['#pre_render'][] = [FormFieldAccess::class, 'preRenderAccess']; if (isset($value['#input']) && $value['#input'] === TRUE) { $value['#process'][] = [FormFieldAccess::class, 'processAccess']; } } } /** * {@inheritDoc} */ public static function trustedCallbacks() { return ['processAccess', 'preRenderAccess']; } /** * Pre-render method to visually hide sections without fields accessed. * * @param array $element * Form element. * * @return array * Form element. */ public static function preRenderAccess(array $element) { if (isset($element['#type']) && in_array($element['#type'], ['form', 'container', 'details']) ) { $child_access = TRUE; $children = Element::children($element); foreach ($children as $child) { $sub_element = $element[$child]; $child_access &= isset($sub_element['#access']) && $sub_element['#access'] === FALSE; } if ($child_access) { $element['#attributes']['class'][] = 'visually-hidden'; if (isset($element['widget'])) { $element['widget']['#attributes']['class'][] = 'visually-hidden'; } } } return $element; } /** * Enforce form fields to be required based on given roles. * * @param $element * @param \Drupal\Core\Form\FormStateInterface $form_state * @param $complete_form * * @return mixed * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ public static function processAccess(&$element, FormStateInterface $form_state, &$complete_form) { $form_object = $form_state->getFormObject(); if (empty($form_object) || is_callable([ $form_state, 'getOperation', ]) && $form_object->getOperation() !== 'edit' ) { return $element; } if (!is_callable([$form_object, 'getEntity'])) { return $element; } $entity = $form_object->getEntity(); if (empty($entity)) { return $element; } $entity_type_id = $entity->getEntityTypeId(); $bundle = $entity->bundle(); if ($form_field_access = self::loadFormFieldAccess($entity_type_id, $bundle)) { $account_roles = \Drupal::currentUser()->getRoles(); // Does not support fields eg revision_log[0][value]. $field_name = preg_replace('/\[.*$/', '', $element['#name']); // Set require flag. $required_field_roles = $form_field_access->getRequiredFieldRoles($field_name); if (!empty($required_field_roles)) { $matched_roles = array_intersect($required_field_roles, $account_roles); if (!empty($matched_roles)) { $element['#required'] = TRUE; } } // Set disabled access flag. // Operates on form field level. To make it works on field level entityFieldAccess method is used. $disallowed_field_roles = $form_field_access->getDisallowedFieldRoles($field_name); if (!empty($disallowed_field_roles)) { $matched_roles = array_intersect($disallowed_field_roles, $account_roles); if (!empty($matched_roles)) { $element['#access'] = FALSE; } } } return $element; } /** * Load Form Field Access configuration. * * @param string $entity_type_id * Entity Type ID. * @param string $bundle * Entity Bundle. * * @return \Drupal\form_field_access\Entity\FormFieldAccess|null * Form Field Access configuration. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ protected static function loadFormFieldAccess(string $entity_type_id, string $bundle) { /** @var \Drupal\form_field_access\Entity\FormFieldAccess $form_field_access */ $form_field_access = \Drupal::entityTypeManager() ->getStorage('form_field_access') ->load($entity_type_id . '.' . $bundle); return $form_field_access; } }