You are here

domain_entity.module in Domain Access Entity 8

Same filename and directory in other branches
  1. 7 domain_entity.module

Enables domain access for entities, and access query alter.

@todo Add hook_entity_presave(). @todo Add domain_entity_query_alter(). @todo Add batching for field operations. Check d7 code at https://github.com/skilld-labs/domain_entity/blob/7.x-1.x/domain_entity....

File

domain_entity.module
View source
<?php

/**
 * @file
 * Enables domain access for entities, and access query alter.
 *
 * @todo Add hook_entity_presave().
 * @todo Add domain_entity_query_alter().
 * @todo Add batching for field operations.
 * Check d7 code at https://github.com/skilld-labs/domain_entity/blob/7.x-1.x/domain_entity.module
 */
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\domain_entity\DomainEntityMapper;
use Drupal\user\Entity\User;

/**
 * Implements hook_help().
 */
function domain_entity_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.domain_entity':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Domain Entity module allows users to manage domain fields on site content, assign access permissions for visibility of content.') . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Enabling domain fields on any entity') . '</dt>';
      $output .= '<dd>' . t('Overview page for all entities') . '</dd>';
      $output .= '<dt>' . t('Configuring query access') . '</dt>';
      $output .= '<dd>' . t('Allowing to load entities for all domains') . '</dd>';
      $output .= '<dt>' . t('Overriding default settings') . '</dt>';
      $output .= '<dd>' . t('Users with the appropriate permissions can override the default domain settings of an entity type.') . '</dd>';
      $output .= '</dl>';
      return $output;
    case 'domain_entity.ui':
      $output = '<p>' . t('Choose which entities are under Domain Access control, and choose domain entity widget behavior of bundles.') . '</p>';
      return $output;
  }
}

/**
 * Returns default values for entity reference field with domains.
 *
 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
 *   The entity that contains field.
 * @param \Drupal\Core\Field\FieldDefinitionInterface $definition
 *   The field of the domain entity access.
 *
 * @return array
 *   Domains configured for the field.
 */
function domain_entity_field_default_domains(FieldableEntityInterface $entity, FieldDefinitionInterface $definition) {
  return $definition
    ->getThirdPartySetting('domain_entity', 'domains', []);
}

/**
 * Implements hook_form_FORM_ID_alter() for 'field_storage_config_edit_form'.
 */
function domain_entity_form_field_storage_config_edit_form_alter(&$form, FormStateInterface $form_state) {

  /** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
  $field_storage = $form_state
    ->getFormObject()
    ->getEntity();
  if ($field_storage
    ->getName() === DomainEntityMapper::FIELD_NAME) {
    $form['cardinality_container']['cardinality']['#access'] = FALSE;
    $form['cardinality_container']['cardinality_number']['#access'] = FALSE;
    $form['cardinality_container']['#description'] = t('This field widget allow only unlimited values (the number of active domain).');
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for 'field_config_edit_form'.
 */
function domain_entity_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state) {

  /** @var \Drupal\field\FieldConfigInterface $field */
  $field = $form_state
    ->getFormObject()
    ->getEntity();
  if ($field
    ->getName() === DomainEntityMapper::FIELD_NAME) {
    $form['required']['#disabled'] = TRUE;
  }
}

/**
 * Implements hook_entity_field_access().
 */
function domain_entity_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
  if ($field_definition
    ->getName() === DomainEntityMapper::FIELD_NAME && $operation == 'edit') {

    /** @var \Drupal\field\Entity\FieldConfig $field_definition */
    $behavior = $field_definition
      ->getThirdPartySetting('domain_entity', 'behavior', DomainEntityMapper::BEHAVIOR_AUTO);
    $access = AccessResult::allowedIfHasPermission($account, 'set domain access status for all entities');
    $access = $access
      ->orIf(AccessResult::allowedIf($behavior == DomainEntityMapper::BEHAVIOR_USER));
    $access
      ->addCacheableDependency($field_definition);

    // @todo Add remaining conditions.
    return $access;
  }
  return AccessResult::neutral();
}

/**
 * Returns the currently active domain.
 *
 * @return \Drupal\domain\DomainInterface
 *   The currently active domain.
 */
function domain_entity_get_domain() {
  return \Drupal::service('domain.negotiator')
    ->getActiveDomain();
}

/**
 * Returns the list domains for user.
 *
 * @param \Drupal\Core\Session\AccountInterface $account
 *    Drupal user account.
 *
 * @return \Drupal\domain\DomainInterface[]
 *   List of domains.
 */
function domain_entity_get_user_domains(AccountInterface $account = NULL) {
  if (!isset($account)) {
    $account = \Drupal::currentUser();
  }
  $user = User::load($account
    ->id());
  $user_domains = [];
  foreach ($user
    ->get(DOMAIN_ADMIN_FIELD) as $item) {
    $user_domains[] = $item->entity
      ->id();
  }
  return $user_domains;
}

/**
 * Return a list of domain id's, accessible by the current user.
 *
 * @param \Drupal\Core\Session\AccountInterface $account
 *   Drupal user account.
 *
 * @return array
 *   List of domain id's.
 */
function domain_entity_get_user_available_domains(AccountInterface $account = NULL) {
  if (!isset($account)) {
    $account = \Drupal::currentUser();
  }

  // Get the current user list of granted domain id:
  // the current domain id OR for middle-office editors:
  // the list of assigned domain(s) id.
  $current_domain = domain_entity_get_domain();
  $accessible_domain_ids = [
    $current_domain
      ->id(),
  ];

  // Middle office editors with permission see in administration,
  // entity from her lists of assigned domains.
  $route = \Drupal::routeMatch()
    ->getRouteObject();
  $is_admin = \Drupal::service('router.admin_context')
    ->isAdminRoute($route);
  if ($is_admin && $account
    ->hasPermission('access entities affiliate on assigned domains')) {
    $user_domain = domain_entity_get_user_domains($account);
    $accessible_domain_ids = !empty($user_domain) ? $user_domain : $accessible_domain_ids;
  }
  return $accessible_domain_ids;
}

/**
 * Returns the list of the domain_entity allowed entity types.
 *
 * @return \Drupal\Core\Entity\EntityTypeInterface[]
 *   Keyed array of enabled entity types.
 */
function domain_entity_allowed_entity_types() {
  $types =& drupal_static(__FUNCTION__);
  if (!isset($types)) {

    /** @var \Drupal\domain_entity\DomainEntityMapper $mapper */
    $mapper = \Drupal::service('domain_entity.mapper');
    $types = $mapper
      ->getEnabledEntityTypes();
  }
  return $types;
}

/**
 * Implements hook_query_alter().
 *
 * Alter the enabled entities select query, add domain access conditions.
 */
function domain_entity_query_alter(AlterableInterface $query) {
  if (defined('MAINTENANCE_MODE') && (MAINTENANCE_MODE == 'install' || MAINTENANCE_MODE == 'update')) {

    // Do not fire at install & update time.
    return;
  }
  if (!\Drupal::lock()
    ->lockMayBeAvailable('router_rebuild')) {

    // Do not fire at route rebuild time.
    return;
  }
  if (!\Drupal::config('domain_entity.settings')
    ->get('bypass_access_conditions') && method_exists($query, 'getTables')) {

    // Have we to check access for this entity types.
    $allowed_types = domain_entity_allowed_entity_types();
    $entity_type_id = $query
      ->getMetaData('entity_type');
    if (!isset($allowed_types[$entity_type_id])) {
      return;
    }

    // Skip access check for query without related tag.
    if (!$query
      ->hasTag($entity_type_id . '_access')) {
      return;
    }

    // Get primary key of base table from entity type definition.
    $entity_key = $allowed_types[$entity_type_id]
      ->getKey('id');

    // Get the accessible domain id's by the current user, in the current path.
    $accessible_domain_ids = domain_entity_get_user_available_domains();
    $tables = $query
      ->getTables();
    $base_table_alias = key($tables);
    $field_name = DomainEntityMapper::FIELD_NAME;

    // This is an enabled domain entity,
    // prepare our custom access conditions.
    $domain_query = \Drupal::database()
      ->select($entity_type_id . '__' . $field_name, 'dom');
    $domain_query
      ->where('dom.entity_id = ' . $base_table_alias . '.' . $entity_key);
    $domain_query
      ->addExpression('1');

    // No domain restrictions found.
    $condition = new Condition('OR');
    $condition
      ->notExists($domain_query);

    // Or restrictions contains allowed domains.
    $domain_query = clone $domain_query;
    $domain_query
      ->condition('dom.' . $field_name . '_target_id', $accessible_domain_ids, 'IN');
    $condition
      ->exists($domain_query);
    $query
      ->condition($condition);
  }
}

// @codingStandardsIgnoreStart

/**
 * Implements hook_entity_presave().
 */

//function domain_entity_entity_presave($entity, $type) {

//  // Check if it's an allowed entity types:
//  $allowed_entity_types = domain_entity_allowed_entity_types();
//  if (!in_array($type, array_keys($allowed_entity_types))) {
//    return;
//  }
//  // Get domain_entity field type instances:
//  if ($field_instance = domain_entity_entity_field_instance($type)) {
//    $field_instance_name = $field_instance['name'];
//  }
//  else {
//    return FALSE;
//  }
//
//  // Get current domain:
//  $current_domain = domain_entity_get_domain();   // @fixme current = active domain
//  $entity_info = \Drupal::entityManager()->getDefinition($type);
//  // Get default bundle value
//  // e.g (all, current_domain, list(domain_id))
//  $bundle_key = $entity_info['entity keys']['bundle'];
//
//  if (isset($allowed_entity_types[$type][$entity->$bundle_key])) {
//    $default_values = reset($allowed_entity_types[$type][$entity->$bundle_key]);
//  }
//  else {
//    $default_values = array();
//  }
//  if (in_array(DOMAIN_ALL, $default_values)) {
//    $values = array();
//    $values[] = array(
//      'domain_id' => DOMAIN_ENTITY_SEND_TO_ALL,
//    );
//  }
//  elseif (in_array(DOMAIN_ACTIVE, $default_values)) {
//    $values = array(
//      0 => array(
//        'domain_id' => $current_domain['domain_id'],
//      ),
//    );
//  }
//  else {
//    $values = array();
//    foreach ($default_values as $did) {
//      $values[] = array(
//        'domain_id' => $did,
//      );
//    }
//  }
//  // Ensure this entities have almost a default value.
//  // Attach it to current domain if the entity is created,
//  // by bypassing entity creation form,
//  // do not let an unset domain entity saved,
//  // (or the entity became inaccessible everywhere).
//  if (!isset($entity->$field_instance_name)) {
//    // Populate field instance with the default bundle domain value,
//    // if is not already set or unassigned.
//    $entity->$field_instance_name = array(
//      \Drupal\Core\Language\Language::LANGCODE_NOT_SPECIFIED => $values,
//    );
//  }
//  else {
//    // Populate field instance with the default bundle domain value,
//    // if is not already set or unassigned.
//    $value = $entity->$field_instance_name;
//    if (empty($value) || !isset($value[\Drupal\Core\Language\Language::LANGCODE_NOT_SPECIFIED][0]) || !$value[\Drupal\Core\Language\Language::LANGCODE_NOT_SPECIFIED][0]['domain_id']) {
//      $entity->$field_instance_name = array(
//        \Drupal\Core\Language\Language::LANGCODE_NOT_SPECIFIED => $values,
//      );
//    }
//  }
//  return $entity;

//}

/**
 * Helper function to active domain field on bundles of entity types.
 *
 * @param array $entity_types
 *   List of Entity types, that list her bundles.
 */

//function domain_entity_types_enable_domain_field($entity_types) {

//  // Common settings.
//  $field_type = 'domain_entity';
//  $label = t('Domain');
//  $required = TRUE;
//  $entities_to_update = array();
//  // Reset the entity info, before enabling domain access on entity.
//  // Some entities are not fieldable, but this module force it to.
//  \Drupal::entityManager()->clearCachedDefinitions();
//  // Create fields instance.
//  foreach ($entity_types as $entity_type => $bundles) {
//    $field_name = domain_entity_get_entity_field_name($entity_type);
//    $entity_info = \Drupal::entityManager()->getDefinition($entity_type);
//
//    foreach ($bundles as $bundle => $widget) {
//      // Look for or add the specified field to the requested entity bundle.
//      $instance = field_info_instance($entity_type, $field_name, $bundle);
//      if (empty($instance)) {
//        domain_entity_create_field_instance($field_name, $field_type, $required, $entity_type, $bundle, $label, key($widget));
//        drupal_set_message(t("Domain Access behaviour '@widget' has been enabled on the bundle @type of @entity entity type", array(
//        '@widget' => key($widget),
//        '@type' => $bundle,
//        '@entity' => $entity_type,
//        )));
//      }
//    }
//
//    // Query to look at unafilliated entities.
//    $entity_key = $entity_info['entity keys']['id'];
//
//    $query = new EntityFieldQuery;
//    $query->entityCondition('entity_type', $entity_type);
//    $query->fieldCondition($field_name, 'domain_id', NULL);
//    $unaffiliated_entities = $query->execute();
//
//    if (!empty($unaffiliated_entities[$entity_type])) {
//      $entities_to_update[$entity_type] = array();
//      foreach ($unaffiliated_entities[$entity_type] as $key => $value) {
//        $id = $value->$entity_key;
//        $entities_to_update[$entity_type][] = $id;
//       }
//     }
//
//  }
//  // Clear cache as we are going to update existing entities field values.
//  \Drupal::entityManager()->clearCachedFieldDefinitions();
//
//  // Create a batch operation to update entities that are not affiliated to a domain.
//  $operations = array();
//  foreach ($entities_to_update as $entity_type => $entities) {
//    foreach ($entities as $entity_id) {
//      $operations[] = array('_domain_entity_update_entity_defaut_value', array(
//          $entity_type,
//          $entity_id,
//        ));
//    }
//  }
//  $batch = array(
//    'operations' => $operations,
//    'title' => t('Update existing entities domain affiliation default values'),
//    'error_message' => t('The domain access update has encountered an error.'),
//    'finished' => '_domain_entity_update_entity_defaut_value_finished',
//  );
//  if (count($operations)) {

//// Bypass access restriction to allow the current user,

//// to access the entity that are not already assigned.

//// @FIXME - dome, need checking

//// Could not extract the default value because it is either indeterminate, or

//// not scalar. You'll need to provide a default value in

//// config/install/domain_entity.settings.yml and config/schema/domain_entity.schema.yml.

//$domain_entity_allowed_entity_types_backup = \Drupal::config('domain_entity.settings')->get('allowed_entity_types');

//    \Drupal::configFactory()->getEditable('domain_entity.settings')->set('bypass_access_conditions_backup', \Drupal::config('domain_entity.settings')->get('bypass_access_conditions'))->save();
//    \Drupal::configFactory()->getEditable('domain_entity.settings')->set('bypass_access_conditions', TRUE)->save();
//    batch_set($batch);
//  }
//  else {
//    drupal_set_message(t('0 entities updated.'));
//  }

//}

/**
 * Batch operation processing callback.
 */

//function _domain_entity_update_entity_defaut_value($entity_type, $entity_id, &$context) {

//  $entity = entity_load_single($entity_type, $entity_id);
//  if ($entity) {
//    // Just call entity_save(), the hook domain_entity_entity_presave()
//    // take care of the field domain default assignation(s), if nothing is set.
//    $entity->save();
//    $context['results'][] = $entity_type . ' - Id : ' . $entity_id . ' updated.';
//    $context['message'] = t('Updated: %entity_type - ID : %entity_id.', array('%entity_type' => $entity_type, '%entity_id' => $entity_id));
//  }
//

//}

/**
 * Batch operation finished callback.
 */

//function _domain_entity_update_entity_defaut_value_finished($finished, $results) {

//  // Restore entity type domain access.
//  \Drupal::configFactory()->getEditable('domain_entity.settings')->set('bypass_access_conditions', \Drupal::config('domain_entity.settings')->get('bypass_access_conditions_backup'))->save();
//  \Drupal::config('domain_entity.settings')->clear('bypass_access_conditions_backup')->save();
//  drupal_set_message(t('%count entities updated.', array('%count' => count($results))));

//}

/**
 * Implements hook_entity_access().
 *
 * @see domain_access_node_access
 */
function domain_entity_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {

  // Ensure quick administration.
  $user = User::load($account
    ->id());
  $is_admin = \Drupal::service('router.admin_context')
    ->isAdminRoute();
  if ($is_admin && $user
    ->hasRole("administrator")) {
    return AccessResult::neutral();
  }

  // Should be a fieldable entity with domain field.
  if (!$entity instanceof FieldableEntityInterface) {
    return AccessResult::neutral();
  }
  if (!$entity
    ->hasField(DomainEntityMapper::FIELD_NAME)) {
    return AccessResult::neutral();
  }

  // Restrict access like it's done in domain_access module.
  $type = $entity
    ->bundle();
  $typeEntity = $entity
    ->getEntityTypeId();
  $manager = \Drupal::service('domain_access.manager');
  if ($operation == 'view' && $manager
    ->checkEntityAccess($entity, $account)) {
    if (method_exists($entity, "isPublished") && $entity
      ->isPublished()) {
      return AccessResult::neutral();
    }
    elseif ($account
      ->hasPermission('view unpublished domain content')) {
      return AccessResult::neutral();
    }
  }
  if ($operation == 'update') {
    if ($account
      ->hasPermission('update ' . $type . ' ' . $typeEntity . ' content on assigned domains') && $manager
      ->checkEntityAccess($entity, $account)) {
      return AccessResult::neutral();
    }
    elseif ($account
      ->hasPermission('edit domain content') && $manager
      ->checkEntityAccess($entity, $account)) {
      return AccessResult::neutral();
    }
  }
  if ($operation == 'delete') {
    if ($account
      ->hasPermission('delete ' . $type . ' ' . $typeEntity . ' content on assigned domains') && $manager
      ->checkEntityAccess($entity, $account)) {
      return AccessResult::neutral();
    }
    elseif ($account
      ->hasPermission('delete domain content') && $manager
      ->checkEntityAccess($entity, $account)) {
      return AccessResult::neutral();
    }
  }
  $domains = _domain_entity_get_related_domains($entity);

  // If specific domains was not selected means:
  // Should be accessible for all domains (no restrictions).
  if (!$domains) {
    return AccessResult::neutral();
  }
  $current_domain = domain_entity_get_domain();
  if (isset($domains[$current_domain
    ->id()])) {
    return AccessResult::neutral();
  }
  return AccessResult::forbidden()
    ->addCacheableDependency($current_domain);
}

/*
 * Implements hook_entity_create_access().
 *
 * @see domain_access_node_create_access
 */
function domain_entity_entity_create_access(AccountInterface $account, array $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 . ' ' . $context['entity_type_id'] . ' 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();
}

/**
 * Return domain IDs list assigned to the requested entity.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   Entity object.
 *
 * @return array
 *   Domain IDs list assigned to the requested entity.
 */
function _domain_entity_get_related_domains(EntityInterface $entity) {
  $domains = [];
  $values = $entity
    ->get(DomainEntityMapper::FIELD_NAME)
    ->getValue();
  if (!$values) {
    return $domains;
  }
  foreach ($values as $value) {
    if (isset($value['target_id'])) {
      $domains[$value['target_id']] = $value['target_id'];
    }
  }
  return $domains;
}

/**
 * Implements hook_domain_references_alter().
 */
function domain_entity_domain_references_alter($query, $account, $context) {
  if ($account
    ->hasPermission('publish to any domain')) {
    return;
  }

  // Limit list of domains that can be used by requested user.
  $allowed = \Drupal::service('domain_access.manager')
    ->getAccessValues($account);
  $query
    ->condition('id', array_keys($allowed), 'IN');
}

/**
 * Implements hook_field_widget_form_alter().
 */
function domain_entity_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
  if ($context['items']
    ->getFieldDefinition()
    ->getName() !== DomainEntityMapper::FIELD_NAME) {
    return;
  }

  // Make affiliate domain selection required in case if user
  // have no "publish to any domain" permission to avoid ambiguity.
  $element['#required'] = !\Drupal::currentUser()
    ->hasPermission('publish to any domain');
}

Functions

Namesort descending Description
domain_entity_allowed_entity_types Returns the list of the domain_entity allowed entity types.
domain_entity_domain_references_alter Implements hook_domain_references_alter().
domain_entity_entity_access Implements hook_entity_access().
domain_entity_entity_create_access
domain_entity_entity_field_access Implements hook_entity_field_access().
domain_entity_field_default_domains Returns default values for entity reference field with domains.
domain_entity_field_widget_form_alter Implements hook_field_widget_form_alter().
domain_entity_form_field_config_edit_form_alter Implements hook_form_FORM_ID_alter() for 'field_config_edit_form'.
domain_entity_form_field_storage_config_edit_form_alter Implements hook_form_FORM_ID_alter() for 'field_storage_config_edit_form'.
domain_entity_get_domain Returns the currently active domain.
domain_entity_get_user_available_domains Return a list of domain id's, accessible by the current user.
domain_entity_get_user_domains Returns the list domains for user.
domain_entity_help Implements hook_help().
domain_entity_query_alter Implements hook_query_alter().
_domain_entity_get_related_domains Return domain IDs list assigned to the requested entity.