You are here

domain_access.module in Domain Access 8

Domain-based access control for content.

File

domain_access/domain_access.module
View source
<?php

/**
 * @file
 * Domain-based access control for content.
 */
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\node\NodeInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Form\FormState;
use Drupal\domain_access\DomainAccessManagerInterface;

/**
 * The name of the node access control field.
 *
 * @deprecated This constant will be replaced in the final release by
 * Drupal\domain\DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD.
 */
const DOMAIN_ACCESS_FIELD = 'field_domain_access';

/**
 * The name of the all affiliates field.
 *
 * @deprecated This constant will be replaced in the final release by
 * Drupal\domain\DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD.
 */
const DOMAIN_ACCESS_ALL_FIELD = 'field_domain_all_affiliates';

/**
 * Implements hook_node_grants().
 */
function domain_access_node_grants(AccountInterface $account, $op) {
  $grants = [];

  /** @var \Drupal\domain\Entity\Domain $active */
  $active = \Drupal::service('domain.negotiator')
    ->getActiveDomain();
  if (empty($active)) {
    $active = \Drupal::entityTypeManager()
      ->getStorage('domain')
      ->loadDefaultDomain();
  }

  // No domains means no permissions.
  if (empty($active)) {
    return $grants;
  }
  $id = $active
    ->getDomainId();

  // Advanced grants for edit/delete require permissions.

  /** @var \Drupal\user\UserInterface $user */
  $user = \Drupal::entityTypeManager()
    ->getStorage('user')
    ->load($account
    ->id());
  $user_domains = \Drupal::service('domain_access.manager')
    ->getAccessValues($user);

  // Grants for view are simple. Use the active domain and all affiliates.
  // Note that "X to any domain" is a global permission designed for admins.
  if ($op == 'view') {
    $grants['domain_id'][] = $id;
    $grants['domain_site'][] = 0;
    if ($user
      ->hasPermission('view unpublished domain content')) {
      if ($user
        ->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user
        ->get(DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD)->value)) {
        $grants['domain_unpublished'][] = $id;
      }
    }
  }
  elseif ($op == 'update' && $user
    ->hasPermission('edit domain content')) {
    if ($user
      ->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user
      ->get(DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD)->value)) {
      $grants['domain_id'][] = $id;
    }
  }
  elseif ($op == 'delete' && $user
    ->hasPermission('delete domain content')) {
    if ($user
      ->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user
      ->get(DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD)->value)) {
      $grants['domain_id'][] = $id;
    }
  }
  return $grants;
}

/**
 * Implements hook_node_access_records().
 */
function domain_access_node_access_records(NodeInterface $node) {
  $grants = [];

  // Create grants for each translation of the node. See the report at
  // https://www.drupal.org/node/2825419 for the logic here. Note that right
  // now, grants may not be the same for all languages.
  $translations = $node
    ->getTranslationLanguages();
  foreach ($translations as $langcode => $language) {
    $translation = $node
      ->getTranslation($langcode);

    // If there are no domains set, use the current one.
    $domains = \Drupal::service('domain_access.manager')
      ->getAccessValues($translation);

    /** @var \Drupal\domain\DomainInterface $active */
    if (empty($domains) && ($active = \Drupal::service('domain.negotiator')
      ->getActiveDomain())) {
      $domains[$active
        ->id()] = $active
        ->getDomainId();
    }
    foreach ($domains as $id => $domainId) {

      /** @var \Drupal\domain\DomainInterface $domain */
      if ($domain = \Drupal::entityTypeManager()
        ->getStorage('domain')
        ->load($id)) {
        $grants[] = [
          'realm' => $translation
            ->isPublished() ? 'domain_id' : 'domain_unpublished',
          'gid' => $domain
            ->getDomainId(),
          'grant_view' => 1,
          'grant_update' => 1,
          'grant_delete' => 1,
          'langcode' => $langcode,
        ];
      }
    }

    // Set the domain_site grant.
    if ($translation
      ->hasField(DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD) && !empty($translation
      ->get(DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD)->value) && $translation
      ->isPublished()) {
      $grants[] = [
        'realm' => 'domain_site',
        'gid' => 0,
        'grant_view' => 1,
        'grant_update' => 0,
        'grant_delete' => 0,
        'langcode' => $langcode,
      ];
    }
    else {
      $grants[] = [
        'realm' => 'domain_site',
        'gid' => 1,
        'grant_view' => 0,
        'grant_update' => 0,
        'grant_delete' => 0,
        'langcode' => $langcode,
      ];
    }
  }
  return $grants;
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 *
 * Fires only if Devel Generate module is present, to assign test nodes to
 * domains.
 */
function domain_access_node_presave(EntityInterface $node) {
  domain_access_presave_generate($node);
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 *
 * Fires only if Devel Generate module is present, to assign test nodes to
 * domains.
 */
function domain_access_user_presave(EntityInterface $account) {
  domain_access_presave_generate($account);
}

/**
 * Handles presave operations for devel generate.
 */
function domain_access_presave_generate(EntityInterface $entity) {
  if (!$entity
    ->hasField(DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD)) {
    return;
  }

  // There is a core bug https://www.drupal.org/node/2609252 that causes a
  // fatal database errors if the boolean DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD is set when
  // a user cannot access the field. See domain_access_entity_field_access().
  // To overcome this issue, we cast the boolean to integer, which prevents the
  // failure.
  $value = (int) $entity
    ->get(DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD)->value;
  $entity
    ->set(DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD, $value);

  // Handle devel module settings.
  $exists = \Drupal::moduleHandler()
    ->moduleExists('devel_generate');
  $values = [];
  if ($exists && isset($entity->devel_generate)) {

    // If set by the form.
    if (isset($entity->devel_generate['domain_access'])) {
      $selection = array_filter($entity->devel_generate['domain_access']);
      if (isset($selection['random-selection'])) {
        $domains = \Drupal::entityTypeManager()
          ->getStorage('domain')
          ->loadMultiple();
        $values[DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD] = array_rand($domains, ceil(rand(1, count($domains))));
      }
      else {
        $values[DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD] = array_keys($selection);
      }
    }
    if (isset($entity->devel_generate['domain_all'])) {
      $selection = $entity->devel_generate['domain_all'];
      if ($selection == 'random-selection') {
        $values[DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD] = rand(0, 1);
      }
      else {
        $values[DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD] = $selection = 'yes' ? 1 : 0;
      }
    }
    foreach ($values as $name => $value) {
      $entity
        ->set($name, $value);
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add options for domains when using Devel Generate.
 */
function domain_access_form_devel_generate_form_content_alter(&$form, &$form_state, $form_id) {

  // Add our element to the Devel generate form.
  $form['submit']['#weight'] = 10;
  $list = [
    'random-selection' => t('Random selection'),
  ];
  $list += \Drupal::entityTypeManager()
    ->getStorage('domain')
    ->loadOptionsList();
  $form['domain_access'] = [
    '#title' => t('Domains'),
    '#type' => 'checkboxes',
    '#options' => $list,
    '#weight' => 2,
    '#multiple' => TRUE,
    '#size' => count($list) > 5 ? 5 : count($list),
    '#default_value' => [
      'random-selection',
    ],
    '#description' => t('Sets the domains for created nodes. Random selection overrides other choices.'),
  ];
  $form['domain_all'] = [
    '#title' => t('Send to all affiliates'),
    '#type' => 'radios',
    '#options' => [
      'random-selection' => t('Random selection'),
      'yes' => t('Yes'),
      'no' => t('No'),
    ],
    '#default_value' => 'random-selection',
    '#weight' => 3,
    '#description' => t('Sets visibility across all affiliates.'),
  ];
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add options for domains when using Devel Generate.
 */
function domain_access_form_devel_generate_form_user_alter(&$form, &$form_state, $form_id) {
  domain_access_form_devel_generate_form_content_alter($form, $form_state, $form_id);
}

/**
 * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
 *
 * Move Domain Access fields to an advanced tab like other node settings.
 */
function domain_access_form_node_form_alter(&$form, FormState $form_state, $form_id) {
  $move_enabled = \Drupal::config('domain_access.settings')
    ->get('node_advanced_tab');
  if ($move_enabled && isset($form[DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD]) && isset($form[DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD]) && empty($form[DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD]['#group']) && empty($form[DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD]['#group'])) {

    // Move to the tabs on the entity form.
    $form[DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD]['#group'] = 'domain';
    $form[DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD]['#group'] = 'domain';
    $form['domain'] = [
      '#type' => 'details',
      '#open' => (bool) \Drupal::config('domain_access.settings')
        ->get('node_advanced_tab_open'),
      '#title' => t('Domain settings'),
      '#group' => 'advanced',
      '#attributes' => [
        'class' => [
          'node-form-options',
        ],
      ],
      '#attached' => [
        'library' => [
          'node/drupal.node',
        ],
      ],
      '#weight' => 100,
      '#optional' => TRUE,
    ];
  }

  // Add the options hidden from the user silently to the form.
  $manager = \Drupal::service('domain.element_manager');
  $form = $manager
    ->setFormOptions($form, $form_state, DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD);
}

/**
 * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\user\UserForm.
 *
 * Handle settings that the user cannot access.
 */
function domain_access_form_user_form_alter(&$form, &$form_state, $form_id) {

  // Add the options hidden from the user silently to the form.
  $manager = \Drupal::service('domain.element_manager');
  $form = $manager
    ->setFormOptions($form, $form_state, DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD);
}

/**
 * Implements hook_domain_references_alter().
 */
function domain_access_domain_references_alter($query, $account, $context) {

  // Restrict domains by editorial assignment.
  if ($context['field_type'] != 'editor') {
    return;
  }
  switch ($context['entity_type']) {
    case 'node':
      if ($account
        ->hasPermission('publish to any domain')) {
        break;
      }
      elseif ($account
        ->hasPermission('publish to any assigned domain')) {
        if (!empty($account
          ->get(DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD)->value)) {
          break;
        }
        $allowed = \Drupal::service('domain_access.manager')
          ->getAccessValues($account);
        $query
          ->condition('id', array_keys($allowed), 'IN');
      }
      else {

        // Remove all options.
        $query
          ->condition('id', '-no-possible-match-');
      }
      break;
    case 'user':
      if ($account
        ->hasPermission('assign editors to any domain')) {

        // Do nothing.
      }
      elseif ($account
        ->hasPermission('assign domain editors')) {
        if (!empty($account
          ->get(DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD)->value)) {
          break;
        }
        $allowed = \Drupal::service('domain_access.manager')
          ->getAccessValues($account);
        $query
          ->condition('id', array_keys($allowed), 'IN');
      }
      else {

        // Remove all options.
        $query
          ->condition('id', '-no-possible-match-');
      }
      break;
    default:

      // No action taken.
      break;
  }
}

/**
 * Implements hook_node_access().
 */
function domain_access_node_access(NodeInterface $node, $op, AccountInterface $account) {
  static $active_domain;
  if (!isset($active_domain)) {

    // Ensure that the loader has run. In some tests, the kernel event has not.
    $active = \Drupal::service('domain.negotiator')
      ->getActiveDomain();
    if (empty($active)) {
      $active = \Drupal::service('domain.negotiator')
        ->getActiveDomain(TRUE);
    }
    $active_domain = $active;
  }

  // Check to see that we have a valid active domain.
  // Without one, we cannot assert an opinion about access.
  if (!$active_domain || empty($active_domain
    ->getDomainId())) {
    return AccessResult::neutral()
      ->addCacheableDependency($node);
  }
  $type = $node
    ->bundle();
  $manager = \Drupal::service('domain_access.manager');
  $allowed = FALSE;

  // In order to access update or delete, the user must be able to view.
  // Domain-specific permissions are relevant only if the node is not published.
  if ($op == 'view') {
    if ($node
      ->isPublished()) {

      // Explicit restatement of the condition, for clarity.
      $allowed = FALSE;
    }
    elseif ($account
      ->hasPermission('view unpublished domain content') && $manager
      ->checkEntityAccess($node, $account)) {
      $allowed = TRUE;
    }
  }
  if ($op == 'update') {
    if ($account
      ->hasPermission('update ' . $type . ' content on assigned domains') && $manager
      ->checkEntityAccess($node, $account)) {
      $allowed = TRUE;
    }
    elseif ($account
      ->hasPermission('edit domain content') && $manager
      ->checkEntityAccess($node, $account)) {
      $allowed = TRUE;
    }
  }
  if ($op == 'delete') {
    if ($account
      ->hasPermission('delete ' . $type . ' content on assigned domains') && $manager
      ->checkEntityAccess($node, $account)) {
      $allowed = TRUE;
    }
    elseif ($account
      ->hasPermission('delete domain content') && $manager
      ->checkEntityAccess($node, $account)) {
      $allowed = TRUE;
    }
  }
  if ($allowed) {
    return AccessResult::allowed()
      ->cachePerPermissions()
      ->cachePerUser()
      ->addCacheableDependency($node);
  }

  // No opinion on FALSE.
  return AccessResult::neutral()
    ->addCacheableDependency($node);
}

/**
 * Implements hook_node_create_access().
 *
 * @link https://www.drupal.org/node/2348203
 */
function domain_access_node_create_access(AccountInterface $account, $context, $entity_bundle) {

  // Check to see that we have a valid active domain.
  // Without one, we cannot assert an opinion about access.

  /** @var \Drupal\domain\DomainInterface $active */
  if ($active = \Drupal::service('domain.negotiator')
    ->getActiveDomain()) {
    $id = $active
      ->getDomainId();
  }
  else {
    return AccessResult::neutral();
  }

  // Load the full user record.
  $user = \Drupal::entityTypeManager()
    ->getStorage('user')
    ->load($account
    ->id());
  $user_domains = \Drupal::service('domain_access.manager')
    ->getAccessValues($user);
  if (($account
    ->hasPermission('create ' . $entity_bundle . ' content on assigned domains') || $account
    ->hasPermission('create domain content')) && in_array($id, $user_domains)) {

    // Note the cache context here!
    return AccessResult::allowed()
      ->addCacheContexts([
      'user.permissions',
      'url.site',
    ]);
  }

  // No opinion.
  return AccessResult::neutral();
}

/**
 * Implements hook_entity_field_access().
 *
 * Hides the domain access fields from the entity add/edit forms
 * when the user cannot access them.
 */
function domain_access_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {

  // If not editing an entity, do nothing.
  if ($operation !== 'edit' || empty($items)) {
    return AccessResult::neutral();
  }

  // The entity the field is attached to.
  $entity = $items
    ->getEntity();
  if ($field_definition
    ->getName() == DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD) {
    if ($entity instanceof AccountInterface) {
      $access = AccessResult::allowedIfHasPermissions($account, [
        'assign domain editors',
        'assign editors to any domain',
      ], 'OR');
    }
    elseif ($entity instanceof NodeInterface) {

      // Treat any other entity as content.
      $access = AccessResult::allowedIfHasPermissions($account, [
        'publish to any domain',
        'publish to any assigned domain',
      ], 'OR');
    }

    // allowedIfHasPermissions returns allowed() or neutral().
    // In this case, we want it to be forbidden,
    // if user doesn't have the permissions above.
    if (isset($access) && !$access
      ->isAllowed()) {
      return AccessResult::forbidden();
    }
  }
  elseif ($field_definition
    ->getName() == DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD) {
    if ($entity instanceof AccountInterface) {
      return AccessResult::forbiddenIf(!$account
        ->hasPermission('assign editors to any domain'));
    }
    elseif ($entity instanceof NodeInterface) {

      // Treat any other entity as content.
      return AccessResult::forbiddenIf(!$account
        ->hasPermission('publish to any domain'));
    }
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 *
 * Creates our fields when new node types are created.
 */
function domain_access_node_type_insert(EntityInterface $entity) {

  /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
  if (!$entity
    ->isSyncing()) {

    // Do not fire hook when config sync in progress.
    domain_access_confirm_fields('node', $entity
      ->id());
  }
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 *
 * In some cases, form display modes are not set when the node type is created.
 * be sure to update our field definitions on creation of form_display for
 * node types.
 */
function domain_access_entity_form_display_insert(EntityInterface $entity) {
  if (!$entity
    ->isSyncing() && $entity
    ->getTargetEntityTypeId() == 'node' && ($bundle = $entity
    ->getTargetBundle())) {
    domain_access_confirm_fields('node', $bundle);
  }
}

/**
 * Creates our fields for an entity bundle.
 *
 * @param string $entity_type
 *   The entity type being created. Node and user are supported.
 * @param string $bundle
 *   The bundle being created.
 * @param array $text
 *   The text to use for the field. Keys are:
 *   'name' -- the lower-case, human-readable name of the entity.
 *   'label' -- the form label for the all affiliates field.
 *   'description' -- the help text for the all affiliates field.
 *
 *   If calling this function for entities other than user or node, it is the
 *   caller's responsibility to provide this text.
 *
 *   This function is here for convenience during installation. It is not really
 *   an API function. Modules wishing to add fields to non-node entities must
 *   provide their own field storage. See the field storage YML sample in
 *   tests/modules/domain_access_test for an example of field storage
 *   definitions.
 *
 * @see domain_access_node_type_insert()
 * @see domain_access_install()
 */
function domain_access_confirm_fields($entity_type, $bundle, array $text = []) {

  // We have reports that importing config causes this function to fail.
  try {
    $text['node'] = [
      'name' => 'content',
      'label' => 'Send to all affiliates',
      'description' => 'Make this content available on all domains.',
    ];
    $text['user'] = [
      'name' => 'user',
      'label' => 'Editor for all affiliates',
      'description' => 'Make this user an editor on all domains.',
    ];
    $id = $entity_type . '.' . $bundle . '.' . DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD;
    $field_storage = \Drupal::entityTypeManager()
      ->getStorage('field_config');
    if (!($field = $field_storage
      ->load($id))) {
      $field = [
        'field_name' => DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD,
        'entity_type' => $entity_type,
        'label' => 'Domain Access',
        'bundle' => $bundle,
        // Users should not be required to be a domain editor.
        'required' => $entity_type !== 'user',
        'description' => 'Select the affiliate domain(s) for this ' . $text[$entity_type]['name'],
        'default_value_callback' => 'Drupal\\domain_access\\DomainAccessManager::getDefaultValue',
        'settings' => [
          'handler' => 'default:domain',
          // Handler_settings are deprecated but seem to be necessary here.
          'handler_settings' => [
            'target_bundles' => NULL,
            'sort' => [
              'field' => 'weight',
              'direction' => 'ASC',
            ],
          ],
          'target_bundles' => NULL,
          'sort' => [
            'field' => 'weight',
            'direction' => 'ASC',
          ],
        ],
      ];
      $field_config = $field_storage
        ->create($field);
      $field_config
        ->save();
    }

    // Assign the all affiliates field.
    $id = $entity_type . '.' . $bundle . '.' . DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD;
    if (!($field = $field_storage
      ->load($id))) {
      $field = [
        'field_name' => DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD,
        'entity_type' => $entity_type,
        'label' => $text[$entity_type]['label'],
        'bundle' => $bundle,
        'required' => FALSE,
        'description' => $text[$entity_type]['description'],
      ];
      $field_config = $field_storage
        ->create($field);
      $field_config
        ->save();
    }

    // Tell the form system how to behave. Default to radio buttons.
    if ($display = \Drupal::entityTypeManager()
      ->getStorage('entity_form_display')
      ->load($entity_type . '.' . $bundle . '.default')) {
      $display
        ->setComponent(DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD, [
        'type' => 'options_buttons',
        'weight' => 40,
      ])
        ->setComponent(DomainAccessManagerInterface::DOMAIN_ACCESS_ALL_FIELD, [
        'type' => 'boolean_checkbox',
        'settings' => [
          'display_label' => 1,
        ],
        'weight' => 41,
      ])
        ->save();
    }
  } catch (Exception $e) {
    \Drupal::logger('domain_access')
      ->notice('Field installation failed.');
  }
}

/**
 * Implements hook_views_data_alter().
 */
function domain_access_views_data_alter(array &$data) {
  $table = 'node__' . DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD;
  $data[$table][DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD]['field']['id'] = 'domain_access_field';
  $data[$table][DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD . '_target_id']['filter']['id'] = 'domain_access_filter';
  $data[$table][DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD . '_target_id']['argument']['id'] = 'domain_access_argument';

  // Current domain filter.
  $data[$table]['current_all'] = [
    'title' => t('Current domain'),
    'group' => t('Domain'),
    'filter' => [
      'field' => DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD . '_target_id',
      'id' => 'domain_access_current_all_filter',
      'title' => t('Available on current domain'),
      'help' => t('Filters out nodes available on current domain (published to current domain or all affiliates).'),
    ],
  ];

  // Since domains are not stored in the database, relationships cannot be used.
  unset($data[$table][DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD]['relationship']);

  // Set the user data.
  $table = 'user__' . DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD;
  $data[$table][DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD]['field']['id'] = 'domain_access_field';
  $data[$table][DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD . '_target_id']['filter']['id'] = 'domain_access_filter';
  $data[$table][DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD . '_target_id']['argument']['id'] = 'domain_access_argument';

  // Since domains are not stored in the database, relationships cannot be used.
  unset($data[$table][DomainAccessManagerInterface::DOMAIN_ACCESS_FIELD]['relationship']);
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function domain_access_domain_insert($entity) {

  /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
  if ($entity
    ->isSyncing()) {

    // Do not fire hook when config sync in progress.
    return;
  }
  $id = 'domain_access_add_action.' . $entity
    ->id();
  $controller = \Drupal::entityTypeManager()
    ->getStorage('action');
  if (!$controller
    ->load($id)) {

    /** @var \Drupal\system\Entity\Action $action */
    $action = $controller
      ->create([
      'id' => $id,
      'type' => 'node',
      'label' => t('Add selected content to the @label domain', [
        '@label' => $entity
          ->label(),
      ]),
      'configuration' => [
        'domain_id' => $entity
          ->id(),
      ],
      'plugin' => 'domain_access_add_action',
    ]);
    $action
      ->trustData()
      ->save();
  }
  $remove_id = 'domain_access_remove_action.' . $entity
    ->id();
  if (!$controller
    ->load($remove_id)) {

    /** @var \Drupal\system\Entity\Action $action */
    $action = $controller
      ->create([
      'id' => $remove_id,
      'type' => 'node',
      'label' => t('Remove selected content from the @label domain', [
        '@label' => $entity
          ->label(),
      ]),
      'configuration' => [
        'domain_id' => $entity
          ->id(),
      ],
      'plugin' => 'domain_access_remove_action',
    ]);
    $action
      ->trustData()
      ->save();
  }
  $id = 'domain_access_add_editor_action.' . $entity
    ->id();
  if (!$controller
    ->load($id)) {

    /** @var \Drupal\system\Entity\Action $action */
    $action = $controller
      ->create([
      'id' => $id,
      'type' => 'user',
      'label' => t('Add editors to the @label domain', [
        '@label' => $entity
          ->label(),
      ]),
      'configuration' => [
        'domain_id' => $entity
          ->id(),
      ],
      'plugin' => 'domain_access_add_editor_action',
    ]);
    $action
      ->trustData()
      ->save();
  }
  $remove_id = 'domain_access_remove_editor_action.' . $entity
    ->id();
  if (!$controller
    ->load($remove_id)) {

    /** @var \Drupal\system\Entity\Action $action */
    $action = $controller
      ->create([
      'id' => $remove_id,
      'type' => 'user',
      'label' => t('Remove editors from the @label domain', [
        '@label' => $entity
          ->label(),
      ]),
      'configuration' => [
        'domain_id' => $entity
          ->id(),
      ],
      'plugin' => 'domain_access_remove_editor_action',
    ]);
    $action
      ->trustData()
      ->save();
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 */
function domain_access_domain_delete(EntityInterface $entity) {
  $controller = \Drupal::entityTypeManager()
    ->getStorage('action');
  $actions = $controller
    ->loadMultiple([
    'domain_access_add_action.' . $entity
      ->id(),
    'domain_access_remove_action.' . $entity
      ->id(),
    'domain_access_add_editor_action.' . $entity
      ->id(),
    'domain_access_remove_editor_action.' . $entity
      ->id(),
  ]);
  foreach ($actions as $action) {
    $action
      ->delete();
  }
}

/**
 * Implements hook_form_alter().
 *
 * Find forms that contain the domain access field and allow those to handle
 * default values properly. Note that here we just care if the form saves an
 * entity. We then pass that entity to a helper function.
 *
 * @see domain_access_default_form_values()
 */
function domain_access_form_alter(&$form, &$form_state, $form_id) {
  if ($object = $form_state
    ->getFormObject() && !empty($object) && is_callable([
    $object,
    'getEntity',
  ]) && ($entity = $object
    ->getEntity())) {
    domain_access_default_form_values($form, $form_state, $entity);
  }
}

/**
 * Defines default values for domain access field.
 *
 * This function is a workaround for a core bug. When the domain access field
 * is not accessible to some users, the existing values are not preserved.
 *
 * @see domain_access_entity_field_access()
 */
function domain_access_default_form_values(&$form, &$form_state, $entity) {

  // Set domain access default value when the user does not have access
  // to edit the field. This seems to work fine for all affiliates, which
  // suggests a core bug in entity reference handling.
  if (!$entity
    ->isNew() && isset($form['field_domain_access']) && !$form['field_domain_access']['#access'] && empty($form['field_domain_access']['widget']['#default_value'])) {

    // Set the default values correctly.
    $values = \Drupal::service('domain_access.manager')
      ->getAccessValues($entity);
    $form['field_domain_access']['widget']['#default_value'] = array_keys($values);
  }
}

Functions

Namesort descending Description
domain_access_confirm_fields Creates our fields for an entity bundle.
domain_access_default_form_values Defines default values for domain access field.
domain_access_domain_delete Implements hook_ENTITY_TYPE_delete().
domain_access_domain_insert Implements hook_ENTITY_TYPE_insert().
domain_access_domain_references_alter Implements hook_domain_references_alter().
domain_access_entity_field_access Implements hook_entity_field_access().
domain_access_entity_form_display_insert Implements hook_ENTITY_TYPE_insert().
domain_access_form_alter Implements hook_form_alter().
domain_access_form_devel_generate_form_content_alter Implements hook_form_FORM_ID_alter().
domain_access_form_devel_generate_form_user_alter Implements hook_form_FORM_ID_alter().
domain_access_form_node_form_alter Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
domain_access_form_user_form_alter Implements hook_form_BASE_FORM_ID_alter() for \Drupal\user\UserForm.
domain_access_node_access Implements hook_node_access().
domain_access_node_access_records Implements hook_node_access_records().
domain_access_node_create_access Implements hook_node_create_access().
domain_access_node_grants Implements hook_node_grants().
domain_access_node_presave Implements hook_ENTITY_TYPE_presave().
domain_access_node_type_insert Implements hook_ENTITY_TYPE_insert().
domain_access_presave_generate Handles presave operations for devel generate.
domain_access_user_presave Implements hook_ENTITY_TYPE_presave().
domain_access_views_data_alter Implements hook_views_data_alter().

Constants

Namesort descending Description
DOMAIN_ACCESS_ALL_FIELD Deprecated The name of the all affiliates field.
DOMAIN_ACCESS_FIELD Deprecated The name of the node access control field.