diff --git a/group.api.php b/group.api.php index 6b9ba46..18df5ef 100644 --- a/group.api.php +++ b/group.api.php @@ -5,7 +5,6 @@ * Hooks specific to the Group module. */ -use Drupal\Core\Session\AccountInterface; use Drupal\group\Entity\GroupInterface; /** @@ -30,6 +29,36 @@ function hook_group_operations_alter(array &$operations, GroupInterface $group) } } +/** + * Provide entity types in which entities can be linked to the group. + * + * @return array + * An associative array of statuses that shows if additional fields per each + * group type should be created, keyed by entity type identifier. + * + * @see group_entity_base_field_info() + */ +function hook_group_types() { + return [ + 'node' => TRUE, + 'user' => FALSE, + ]; +} + +/** + * Alter entity types in which entities can be linked to the group. + * + * @param array $types + * An associative array of entity types returned by hook_group_types(). + * + * @see group_entity_base_field_info() + */ +function hook_group_types_alter(array &$types) { + if (isset($types['user']) && !$types['user']) { + $types['user'] = TRUE; + } +} + /** * @} End of "addtogroup hooks". */ diff --git a/group.module b/group.module index 2fbafdc..ded7098 100644 --- a/group.module +++ b/group.module @@ -19,12 +19,15 @@ use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; use Drupal\group\Entity\GroupContent; +use Drupal\group\Fields\GroupGroupReferenceItemList; use Drupal\group\QueryAccess\EntityQueryAlter; use Drupal\user\RoleInterface; use Drupal\views\Plugin\views\query\QueryPluginBase; use Drupal\views\Plugin\views\query\Sql; use Drupal\views\ViewExecutable; - +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Field\FieldStorageDefinitionInterface; /** * Implements hook_help(). */ @@ -760,3 +763,90 @@ function group_view_presave(EntityInterface $view) { $view->set('display', $displays); } } + +/** + * Implements hook_group_types(). + * + * We currently only use this for nodes and users since they can be installed + * as group content. + */ +function group_group_types() { + return array_fill_keys(['node', 'user'], TRUE); +} + +/** + * Implements hook_entity_base_field_info(). + * + * Creates computed fields to get the groups related to an entity. There will be + * a field containing all groups. Each group type will also get a separate + * field. + */ +function group_entity_base_field_info(EntityTypeInterface $entity_type) { + $fields = []; + + $entity_types = \Drupal::moduleHandler()->invokeAll('group_types'); + \Drupal::moduleHandler()->alter('group_types', $entity_types); + + if (!isset($entity_types[$entity_type->id()])) { + return $fields; + } + + $fields['groups'] = BaseFieldDefinition::create('entity_reference') + ->setName('groups') + ->setTargetEntityTypeId($entity_type->id()) + ->setSetting('target_type', 'group') + ->setLabel(t('Get the related groups for this entity.')) + ->setDescription(t('A list of all the groups referencing this entity.')) + ->setTranslatable(FALSE) + ->setComputed(TRUE) + ->setCustomStorage(TRUE) + ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) + ->setClass(GroupGroupReferenceItemList::class) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayOptions('form', [ + 'label' => 'hidden', + 'region' => 'hidden', + 'weight' => 50, + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayOptions('view', [ + 'label' => 'hidden', + 'region' => 'hidden', + 'weight' => 50, + ]); + + if (!$entity_types[$entity_type->id()]) { + return $fields; + } + + /** @var \Drupal\group\Entity\GroupTypeInterface $group_type */ + foreach (\Drupal::entityTypeManager()->getStorage('group_type')->loadMultiple() as $group_type) { + $field_name = 'groups_type_' . $group_type->id(); + $fields[$field_name] = BaseFieldDefinition::create('entity_reference') + ->setName($field_name) + ->setTargetEntityTypeId($entity_type->id()) + ->setSetting('target_type', 'group') + ->setSetting('handler_settings', ['target_bundles' => [$group_type->id() => $group_type->id()]]) + ->setLabel(t('Get the related @type groups for this entity.', ['@type' => $group_type->label()])) + ->setDescription(t('A list of all the groups referencing this entity.')) + ->setTranslatable(FALSE) + ->setComputed(TRUE) + ->setCustomStorage(TRUE) + ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) + ->setClass(GroupGroupReferenceItemList::class) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayOptions('form', [ + 'label' => 'hidden', + 'region' => 'hidden', + 'weight' => 50, + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayOptions('view', [ + 'label' => 'hidden', + 'region' => 'hidden', + 'weight' => 50, + ]); + } + + return $fields; +} diff --git a/src/Fields/GroupGroupReferenceItemList.php b/src/Fields/GroupGroupReferenceItemList.php new file mode 100644 index 0000000..f6df2b2 --- /dev/null +++ b/src/Fields/GroupGroupReferenceItemList.php @@ -0,0 +1,165 @@ +entityTypeManager = \Drupal::service('entity_type.manager'); + } + + /** + * {@inheritdoc} + */ + public function computeValue() { + // We only support nodes and users. + if ($this->getEntity()->getEntityTypeId() === 'node') { + $plugin_id = 'group_node:' . $this->getEntity()->bundle(); + } + elseif ($this->getEntity()->getEntityTypeId() === 'user') { + $plugin_id = 'group_membership'; + } + else { + return NULL; + } + + // No value will exist if the entity has not been created so exit early.:wq + if ($this->getEntity()->isNew()) { + return NULL; + } + + $handler_settings = $this->getItemDefinition()->getSetting('handler_settings'); + $group_types = isset($handler_settings['target_bundles']) ? $handler_settings['target_bundles'] : $this->entityTypeManager->getStorage('group_type')->loadMultiple(); + + $group_content_types = $this->entityTypeManager->getStorage('group_content_type')->loadByProperties([ + 'group_type' => array_keys($group_types), + 'content_plugin' => $plugin_id, + ]); + + if (empty($group_content_types)) { + return NULL; + } + + $group_contents = $this->entityTypeManager->getStorage('group_content')->loadByProperties([ + 'type' => array_keys($group_content_types), + 'entity_id' => $this->getEntity()->id(), + ]); + + $this->list = []; + if (!empty($group_contents)) { + foreach ($group_contents as $delta => $group_content) { + $this->list[] = $this->createItem($delta, [ + 'target_id' => $group_content->gid->target_id, + ]); + } + } + } + + /** + * @inheritDoc + */ + public function postSave($update) { + if ($this->valueComputed) { + $node = $this->getEntity(); + + // Index-Array for wanted groups ( gid => gid ) + $gids_wanted = []; + foreach ($this->list as $delta => $item) { + $id = $item->get('target_id')->getValue(); + $gids_wanted[$id] = $id; + } + + // Index-Array for existing groups for this node gid => gid + $gids_existing = []; + + // Index-Array for gnodes for easier deletion gid => GroupContent + $gnodes_existing = []; + + /** @var \Drupal\group\Entity\Storage\GroupContentStorageInterface $storage */ + $storage = \Drupal::entityTypeManager()->getStorage('group_content'); + // Loads all groups with a relation to the node + $activeGroupListEntity = $storage->loadByEntity($node); + foreach ($activeGroupListEntity as $groupContent) { + // fill Index-Array with existing groups gid => gid + $gids_existing[$groupContent->getGroup()->id()] = $groupContent->getGroup()->id(); + + // fill Index-Array for existing gnodes + $gnodes_existing[$groupContent->getGroup()->id()] = $groupContent; + } + + // Union for existing and wanted groups + $gids_union = $gids_existing + $gids_wanted; + + // Index-Array gnodes to create + // = (Union for existing and wanted) minus existing + $gids_create = array_diff($gids_union, $gids_existing); + + // Index-Array gnodes to delete + // = (Union for existing and wanted) minus wanted + $gids_delete = array_diff($gids_union, $gids_wanted); + + foreach ($gids_create as $gid) { + // Skip -none- option + if ($gid == '_none') { + continue; + } + $group = Group::load($gid); + /** @var \Drupal\group\Plugin\GroupContentEnablerInterface $plugin */ + $plugin = $group->getGroupType()->getContentPlugin('group_node:'.$node->bundle()); + $group_content = GroupContent::create([ + 'type' => $plugin->getContentTypeConfigId(), + 'gid' => $group->id(), + 'entity_id' => $node->id(), + ]); + $group_content->save(); + } + + foreach ($gids_delete as $gid) { + // Skip -none- option + if ($gid == '_none') { + continue; + } + $gnodes_existing[$gid]->delete(); + } + } + return parent::postSave($update); + + // @fixme Also care that we got correct referencable entities in widget. + } + +}