og-8.x-1.x-dev/og.module

og.module
<?php

/**
 * @file
 * Enable users to create and manage groups with roles and permissions.
 */

declare(strict_types=1);

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\og\Entity\OgRole;
use Drupal\og\Og;
use Drupal\og\OgGroupAudienceHelperInterface;
use Drupal\og\OgMembershipInterface;
use Drupal\og\OgMembershipTypeInterface;
use Drupal\og\OgRoleInterface;
use Drupal\system\Entity\Action;
use Drupal\user\EntityOwnerInterface;
use Drupal\user\UserInterface;

/**
 * Implements hook_entity_insert().
 *
 * Subscribe the group manager.
 */
function og_entity_insert(EntityInterface $entity) {
  // Invalidate cache tags if new group content is created.
  og_invalidate_group_content_cache_tags($entity);

  if (!\Drupal::config('og.settings')->get('auto_add_group_owner_membership')) {
    // Disabled automatically adding group creators to the group.
    return;
  }

  if (!Og::isGroup($entity->getEntityTypeId(), $entity->bundle())) {
    // Not a group entity.
    return;
  }

  if (!$entity instanceof EntityOwnerInterface) {
    return;
  }

  $owner = $entity->getOwner();
  if (!$owner instanceof UserInterface) {
    return;
  }

  if ($owner->isAnonymous()) {
    // User is anonymous, so we cannot set a membership for them.
    return;
  }

  // Other modules that implement hook_entity_insert() might already have
  // created a membership ahead of us.
  if (!Og::getMembership($entity, $owner, OgMembershipInterface::ALL_STATES)) {
    $membership = Og::createMembership($entity, $owner);
    $membership->save();
  }
}

/**
 * Implements hook_entity_update().
 */
function og_entity_update(EntityInterface $entity) {
  // Invalidate cache tags if a group or group content entity is updated.
  og_invalidate_group_content_cache_tags($entity);
}

/**
 * Implements hook_entity_predelete().
 */
function og_entity_predelete(EntityInterface $entity) {
  if (Og::isGroup($entity->getEntityTypeId(), $entity->bundle())) {
    // Register orphaned group content and user memberships for deletion, if
    // this option has been enabled.
    $config = \Drupal::config('og.settings');
    if ($config->get('delete_orphans')) {
      $plugin_id = $config->get('delete_orphans_plugin_id');
      /** @var \Drupal\og\OgDeleteOrphansInterface $plugin */
      $plugin = \Drupal::service('plugin.manager.og.delete_orphans')->createInstance($plugin_id, []);
      $plugin->register($entity);
    }

    // @todo Delete user roles.
    // @see https://github.com/amitaibu/og/issues/175
    // og_delete_user_roles_by_group($entity_type, $entity);
  }
  // If a user is being deleted, also delete its memberships.
  if ($entity instanceof UserInterface) {
    /** @var \Drupal\og\MembershipManagerInterface $membership_manager */
    $membership_manager = \Drupal::service('og.membership_manager');
    foreach ($membership_manager->getMemberships($entity->id(), []) as $membership) {
      $membership->delete();
    }
  }
}

/**
 * Implements hook_entity_delete().
 */
function og_entity_delete(EntityInterface $entity) {
  // Invalidate cache tags after a group or group content entity is deleted.
  og_invalidate_group_content_cache_tags($entity);

  // If a group content type is deleted, make sure to remove it from the list of
  // groups.
  if ($entity instanceof ConfigEntityBundleBase) {
    $bundle = $entity->id();
    $entity_type_id = \Drupal::entityTypeManager()->getDefinition($entity->getEntityTypeId())->getBundleOf();
    if (Og::isGroup($entity_type_id, $bundle)) {
      Og::groupTypeManager()->removeGroup($entity_type_id, $bundle);
    }
  }
}

/**
 * Implements hook_entity_presave().
 *
 * Preserve hidden (unauthorized) group references on edit so users who lack
 * access cannot silently drop group memberships by saving a form. Editors can
 * still remove groups they are allowed to manage; only inaccessible references
 * are restored.
 */
function og_entity_presave(EntityInterface $entity): void {
  // Only deal with editing group content when preserving hidden groups.
  if ($entity->isNew() || !Og::isGroupContent($entity->getEntityTypeId(), $entity->bundle())) {
    return;
  }

  /** @var \Drupal\og\OgGroupAudienceHelperInterface $group_audience_helper */
  $group_audience_helper = \Drupal::service('og.group_audience_helper');
  $audience_fields = $group_audience_helper->getAllGroupAudienceFields($entity->getEntityTypeId(), $entity->bundle());

  $account = \Drupal::currentUser();
  /** @var \Drupal\og\OgAccessInterface $og_access */
  $og_access = \Drupal::service('og.access');

  foreach (array_keys($audience_fields) as $field_name) {
    $field = $entity->get($field_name);
    $current_ids = [];
    foreach ($field->getValue() as $item) {
      if (isset($item['target_id'])) {
        $current_ids[] = (int) $item['target_id'];
      }
    }

    $original_values = NULL;
    if (isset($entity->original) && $entity->original instanceof FieldableEntityInterface && $entity->original->hasField($field_name)) {
      $original_values = $entity->original->get($field_name)->getValue();
    }
    elseif ($entity->id()) {
      $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
      $unchanged = $storage->loadUnchanged($entity->id());
      if ($unchanged instanceof FieldableEntityInterface && $unchanged->hasField($field_name)) {
        $original_values = $unchanged->get($field_name)->getValue();
      }
    }

    // Nothing to restore when the original field was empty.
    if (!$original_values) {
      continue;
    }

    $original_ids = [];
    foreach ($original_values as $item) {
      if (isset($item['target_id'])) {
        $original_ids[] = (int) $item['target_id'];
      }
    }
    // Field had no original references, nothing to preserve.
    if (!$original_ids) {
      continue;
    }

    $missing_ids = array_diff($original_ids, $current_ids);
    // No hidden references were removed in the submission.
    if (!$missing_ids) {
      continue;
    }

    $target_type = $field->getFieldDefinition()->getSetting('target_type');
    $group_storage = \Drupal::entityTypeManager()->getStorage($target_type);
    $groups = $group_storage->loadMultiple($missing_ids);

    $preserve_ids = [];
    foreach ($missing_ids as $group_id) {
      if (!isset($groups[$group_id])) {
        continue;
      }

      $access = $og_access->userAccessGroupContentEntityOperation('create', $groups[$group_id], $entity, $account);
      if (!$access->isAllowed()) {
        $preserve_ids[] = (int) $group_id;
      }
    }

    // All removed groups are still allowed; nothing to re-add.
    if (!$preserve_ids) {
      continue;
    }

    // Keep original order; preserve missing unauthorized IDs, then append any
    // new selections from the form.
    $final_ids = [];
    foreach ($original_ids as $group_id) {
      if (in_array($group_id, $current_ids, TRUE) || in_array($group_id, $preserve_ids, TRUE)) {
        $final_ids[] = $group_id;
      }
    }

    foreach ($current_ids as $group_id) {
      if (!in_array($group_id, $final_ids, TRUE)) {
        $final_ids[] = $group_id;
      }
    }

    $field->setValue(array_map(static fn(int $group_id): array => ['target_id' => $group_id], $final_ids));
  }
}

/**
 * Implements hook_entity_access().
 */
function og_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
  // Grant access to view roles, so that they can be shown in listings.
  if ($entity instanceof OgRoleInterface && $operation === 'view') {
    return AccessResult::allowed();
  }
  // We only care about content entities that are groups or group content.
  if (!$entity instanceof ContentEntityInterface) {
    return AccessResult::neutral();
  }

  if ($operation === 'view') {
    return AccessResult::neutral();
  }

  $entity_type_id = $entity->getEntityTypeId();
  $bundle_id = $entity->bundle();
  $is_group_content = Og::isGroupContent($entity_type_id, $bundle_id);

  // If the entity is neither a group or group content, then we have no opinion.
  if (!Og::isGroup($entity_type_id, $bundle_id) && !$is_group_content) {
    return AccessResult::neutral();
  }

  // If the entity type is a group content type, but the entity is not
  // associated with any groups, we have no opinion.
  if ($is_group_content && \Drupal::service('og.membership_manager')->getGroupCount($entity) === 0) {
    return AccessResult::neutral();
  }

  // If the user has the global permission to administer all groups, allow
  // access.
  if ($account->hasPermission('administer organic groups')) {
    return AccessResult::allowed();
  }

  /** @var \Drupal\Core\Access\AccessResult $access */
  $access = \Drupal::service('og.access')->userAccessEntityOperation($operation, $entity, $account);

  if ($access->isAllowed()) {
    return $access;
  }

  if ($entity_type_id === 'node') {
    $node_access_strict = \Drupal::config('og.settings')->get('node_access_strict');

    // Otherwise, ignore or deny based on whether strict node access is set.
    return AccessResult::forbiddenIf($node_access_strict);
  }

  return AccessResult::forbidden();
}

/**
 * Implements hook_ENTITY_TYPE_access().
 */
function og_og_membership_type_access(OgMembershipTypeInterface $entity, $operation, AccountInterface $account) {
  // Do not allow deleting the default membership type.
  if ($operation === 'delete' && $entity->id() === OgMembershipInterface::TYPE_DEFAULT) {
    return AccessResult::forbidden();
  }

  // If the user has permission to administer all groups, allow access.
  if ($account->hasPermission('administer organic groups')) {
    return AccessResult::allowed();
  }

  return AccessResult::forbidden();
}

/**
 * Implements hook_entity_create_access().
 */
function og_entity_create_access(AccountInterface $account, array $context, $bundle) {
  $entity_type_id = $context['entity_type_id'];

  if (!Og::isGroupContent($entity_type_id, $bundle)) {
    // Not a group content.
    return AccessResult::neutral();
  }

  // A user with the global permission to administer all groups has full access.
  $access_result = AccessResult::allowedIfHasPermission($account, 'administer organic groups');
  if ($access_result->isAllowed()) {
    return $access_result;
  }

  $node_access_strict = \Drupal::config('og.settings')->get('node_access_strict');
  if ($entity_type_id === 'node' && !$node_access_strict && $account->hasPermission("create $bundle content")) {
    // The user has the core permission and strict node access is not set.
    return AccessResult::neutral();
  }

  // We can't check if user has create permissions, as there is no group
  // context. However, we can check if there are any groups the user will be
  // able to select, and if not, we don't allow access but if there are,
  // AccessResult::neutral() will be returned in order to not override other
  // access results.
  // @see \Drupal\og\Plugin\EntityReferenceSelection\OgSelection::buildEntityQuery()
  $required = FALSE;

  $field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type_id, $bundle);
  foreach ($field_definitions as $field_definition) {
    /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
    if (!\Drupal::service('og.group_audience_helper')->isGroupAudienceField($field_definition)) {
      continue;
    }

    $options = [
      'target_type' => $field_definition->getFieldStorageDefinition()->getSetting('target_type'),
      'handler' => $field_definition->getSetting('handler'),
    ];
    /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager $handler */
    $handler = \Drupal::service('plugin.manager.entity_reference_selection');

    if ($handler->getInstance($options)) {
      return AccessResult::neutral();
    }
    // Allow users to create content outside of groups, if none of the
    // audience fields is required.
    $required = $field_definition->isRequired();
  }

  // Otherwise, ignore or deny based on whether strict entity access is set.
  return $required ? AccessResult::forbiddenIf($node_access_strict) : AccessResult::neutral();
}

/**
 * Implements hook_entity_bundle_field_info().
 *
 * Add a read only property to group entities as a group flag.
 */
function og_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  if (!Og::isGroup($entity_type->id(), $bundle)) {
    // Not a group type.
    return NULL;
  }

  $fields = [];
  $fields['og_group'] = BaseFieldDefinition::create('og_group')
    ->setLabel(new TranslatableMarkup('OG Group'))
    ->setComputed(TRUE)
    ->setTranslatable(FALSE)
    ->setDefaultValue(TRUE)
    ->setReadOnly(TRUE);

  return $fields;
}

/**
 * Implements hook_entity_bundle_field_info_alter().
 *
 * Set the default field formatter of fields of type OG group.
 */
function og_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
  if (!isset($fields['og_group'])) {
    // No OG group fields.
    return;
  }

  $fields['og_group']->setDisplayOptions('view', [
    'weight' => 0,
    'type' => 'og_group_subscribe',
  ])->setDisplayConfigurable('view', TRUE);
}

/**
 * Implements hook_field_formatter_info_alter().
 *
 * Allow OG audience fields to have entity reference formatters.
 */
function og_field_formatter_info_alter(array &$info) {
  foreach (array_keys($info) as $key) {
    if (!in_array('entity_reference', $info[$key]['field_types'])) {
      // Not an entity reference formatter.
      continue;
    }

    $info[$key]['field_types'][] = OgGroupAudienceHelperInterface::GROUP_REFERENCE;
  }
}

/**
 * Implements hook_field_widget_info_alter().
 */
function og_field_widget_info_alter(array &$info) {
  $field_types = [
    'entity_reference_autocomplete',
    'entity_reference_autocomplete_tags',
    'options_buttons',
    'options_select',
  ];

  foreach ($field_types as $field_type) {
    $info[$field_type]['field_types'][] = OgGroupAudienceHelperInterface::GROUP_REFERENCE;
  }
}

/**
 * Implements hook_element_info_alter().
 *
 * Inject our process callback into core's 'entity_autocomplete' element so we
 * can attach the group-content context (entity type + bundle) to the AJAX
 * request. The OG selection handler reads these query parameters to scope
 * results and access checks to the correct group, even when the full form
 * entity isn't available during the autocomplete request lifecycle.
 *
 * Specifically, 'entity_type' and 'bundle' are consumed in
 * OgSelection::buildEntityQuery() to construct a stub entity and apply
 * group-aware filtering.
 *
 * @see \Drupal\og\Plugin\EntityReferenceSelection\OgSelection::buildEntityQuery()
 */
function og_element_info_alter(array &$type) {
  if (isset($type['entity_autocomplete'])) {
    array_unshift($type['entity_autocomplete']['#process'], 'og_process_entity_autocomplete');
  }
}

/**
 * Custom process callback for 'entity_autocomplete' on OG audience fields.
 *
 * When the element belongs to a group-content entity form, add 'entity_type'
 * and 'bundle' to '#autocomplete_query_parameters'. The OG selection handler
 * consumes these to create a stub entity and perform group-aware access checks
 * and filtering, ensuring autocomplete suggestions respect the group context.
 *
 * @see \Drupal\og\Plugin\EntityReferenceSelection\OgSelection::buildEntityQuery()
 */
function og_process_entity_autocomplete(array &$element, FormStateInterface $form_state, array &$complete_form) {
  if (!($form_state->getFormObject() instanceof EntityFormInterface)) {
    return $element;
  }

  $entity = $form_state->getFormObject()->getEntity();
  if (!($entity instanceof ContentEntityInterface)) {
    return $element;
  }
  // Guarding against bad parents.
  if (empty($element['#parents'])) {
    return $element;
  }

  // Walk up the parents to find the actual entity field name.
  $field_definition = NULL;
  foreach ($element['#parents'] as $parent) {
    if ($entity->hasField($parent)) {
      $field_definition = $entity->getFieldDefinition($parent);
      break;
    }
  }

  if (!$field_definition instanceof FieldDefinitionInterface) {
    return $element;
  }

  if (\Drupal::service('og.group_audience_helper')->isGroupAudienceField($field_definition)) {
    // Pass minimal context so the selection handler can scope the query.
    // These are read by OgSelection::buildEntityQuery() when the full entity
    // isn't available to the autocomplete callback.
    // @see \Drupal\og\Plugin\EntityReferenceSelection\OgSelection::buildEntityQuery()
    $element['#autocomplete_query_parameters']['entity_type'] = $form_state->getFormObject()->getEntity()->getEntityTypeId();
    $element['#autocomplete_query_parameters']['bundle'] = $form_state->getFormObject()->getEntity()->bundle();
  }
  return $element;
}

/**
 * Implements hook_entity_type_alter().
 *
 * Add link template to groups. We add it to all the entity types, and later on
 * return the correct access, depending if the bundle is indeed a group and
 * accessible. We do not filter here the entity type by groups, so whenever
 * GroupTypeManagerInterface::addGroup is called, it's enough to mark route to
 * be rebuilt via RouteBuilder::setRebuildNeeded.
 */
function og_entity_type_alter(array &$entity_types) {
  /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
  foreach ($entity_types as $entity_type_id => $entity_type) {
    // Register 'og-admin-routes' link template; access is checked at runtime.
    $entity_type->setLinkTemplate('og-admin-routes', "/group/$entity_type_id/{{$entity_type_id}}/admin");
  }
}

/**
 * Implements hook_theme().
 */
function og_theme($existing, $type, $theme, $path) {
  return [
    'og_member_count' => [
      'variables' => [
        'count' => 0,
        'membership_states' => [],
        'group' => NULL,
        'group_label' => NULL,
      ],
    ],
  ];
}

/**
 * Invalidates group content cache tags for the groups this entity belongs to.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The group content entity that is being created, changed or deleted and is
 *   the direct cause for the need to invalidate cached group content.
 */
function og_invalidate_group_content_cache_tags(EntityInterface $entity) {
  // If group content is created or updated, invalidate the group content cache
  // tags for each of the groups this group content belongs to. This allows
  // group listings to be cached effectively. The cache tag format is
  // 'og-group-content:{group entity type}:{group entity id}'.
  $is_group_content = Og::isGroupContent($entity->getEntityTypeId(), $entity->bundle());
  if ($is_group_content) {
    /** @var \Drupal\og\MembershipManagerInterface $membership_manager */
    $membership_manager = \Drupal::service('og.membership_manager');
    $tags = [];

    // If the entity is a group content and we came here as an effect of an
    // update, check if any of the OG audience fields have been changed. This
    // means the group(s) of the entity changed and we should also invalidate
    // the tags of the old group(s).
    /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
    $original = !empty($entity->original) ? $entity->original : NULL;
    if ($original) {
      /** @var \Drupal\og\OgGroupAudienceHelperInterface $group_audience_helper */
      $group_audience_helper = \Drupal::service('og.group_audience_helper');
      /** @var \Drupal\Core\Entity\FieldableEntityInterface $original */
      foreach ($group_audience_helper->getAllGroupAudienceFields($entity->getEntityTypeId(), $entity->bundle()) as $field) {
        $field_name = $field->getName();
        /** @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $original_field_item_list */
        $original_field_item_list = $original->get($field_name);
        if (!$entity->get($field_name)->equals($original_field_item_list)) {
          foreach ($original_field_item_list->referencedEntities() as $old_group) {
            $tags = Cache::mergeTags($tags, $old_group->getCacheTagsToInvalidate());
          }
        }
      }
    }

    foreach ($membership_manager->getGroups($entity) as $groups) {
      /** @var \Drupal\Core\Entity\ContentEntityInterface $group */
      foreach ($groups as $group) {
        $tags = Cache::mergeTags($tags, $group->getCacheTagsToInvalidate());
      }
    }

    Cache::invalidateTags(Cache::buildTags('og-group-content', $tags));
  }
}

/**
 * Implements hook_ENTITY_TYPE_insert() for OgRole entities.
 */
function og_og_role_insert(OgRoleInterface $role) {
  // Create actions to add or remove the role, except for the required default
  // roles 'member' and 'non-member'. These cannot be added or removed.
  if ($role->getRoleType() === OgRoleInterface::ROLE_TYPE_REQUIRED) {
    return;
  }

  // Skip creation of action plugins while config import is in progress.
  if ($role->isSyncing()) {
    return;
  }

  $add_id = 'og_membership_add_single_role_action.' . $role->getName();
  if (!Action::load($add_id)) {
    $action = Action::create([
      'id' => $add_id,
      'type' => 'og_membership',
      'label' => new TranslatableMarkup('Add the @label role to the selected members', ['@label' => $role->getName()]),
      'configuration' => [
        'role_name' => $role->getName(),
      ],
      'plugin' => 'og_membership_add_single_role_action',
    ]);
    $action->trustData()->save();
  }
  $remove_id = 'og_membership_remove_single_role_action.' . $role->getName();
  if (!Action::load($remove_id)) {
    $action = Action::create([
      'id' => $remove_id,
      'type' => 'og_membership',
      'label' => new TranslatableMarkup('Remove the @label role from the selected members', ['@label' => $role->getName()]),
      'configuration' => [
        'role_name' => $role->getName(),
      ],
      'plugin' => 'og_membership_remove_single_role_action',
    ]);
    $action->trustData()->save();
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete() for OgRole entities.
 */
function og_og_role_delete(OgRoleInterface $role) {
  $role_name = $role->getName();
  /** @var \Drupal\system\ActionConfigEntityInterface[] $actions */
  $actions = Action::loadMultiple([
    'og_membership_add_single_role_action.' . $role_name,
    'og_membership_remove_single_role_action.' . $role_name,
  ]);

  // Only remove the actions when the role name is not used by any other roles.
  foreach (OgRole::loadMultiple() as $role) {
    if ($role->getName() === $role_name) {
      return;
    }
  }

  foreach ($actions as $action) {
    $action->delete();
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc