Skip to content
This repository has been archived by the owner on Aug 18, 2024. It is now read-only.

[WIP] OG Access port #242

Closed
wants to merge 44 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1bfdfe9
Initial port
zerolab Mar 2, 2017
18ab411
Cleanup
zerolab Mar 2, 2017
72451e1
Add remove access fields on entity group/group content config changes
zerolab Mar 2, 2017
f08fe78
Remove install step
zerolab Mar 13, 2017
3a77001
Use core method for checking if node is published
zerolab Mar 13, 2017
5e1436c
Add access field toggle in the bundle form
zerolab Mar 13, 2017
5a801ab
Fix og_access realm (#1)
svendecabooter May 15, 2017
690235a
Fix crash on add forms for bundles of content entities that implement…
joachim-n Sep 21, 2017
d3c36c3
Fixup! Fix crash on add forms for bundles of content entities that im…
joachim-n Sep 21, 2017
f1dc41b
Lint
zerolab Sep 22, 2017
98f0c38
Fix bundle form alter class filename
zerolab Nov 16, 2017
202c31c
#242 Improvements for og_access port. (#2)
tatarbj Jul 4, 2018
ac43c9d
Update og_access/og_access.info.yml
MPParsley Mar 26, 2019
4cf5b4b
Update og_access/src/Plugin/OgFields/OgAccessField.php
MPParsley Mar 26, 2019
66cbcbf
Update og_access/src/Plugin/OgFields/OgContentAccessField.php
MPParsley Mar 26, 2019
50fde53
Fixed fatal error StringTranslationTrait not found
MPParsley Apr 9, 2019
40acefa
Fixed ArgumentCountError: Too few arguments for OgAccessBundleFormAlt…
MPParsley Apr 9, 2019
645f40a
Update og_access/src/Plugin/OgFields/OgContentAccessField.php
MPParsley Apr 9, 2019
babf336
Update og_access/src/Plugin/OgFields/OgContentAccessField.php
MPParsley Apr 9, 2019
71f636f
Update og_access/src/Plugin/OgFields/OgAccessField.php
MPParsley Apr 9, 2019
7e0b216
Update og_access/src/Plugin/OgFields/OgContentAccessField.php
MPParsley Apr 9, 2019
eb548eb
Update og_access/src/Plugin/OgFields/OgAccessField.php
MPParsley Apr 9, 2019
4ab462a
Rebuild node access permissions (#3)
MPParsley Apr 18, 2019
3b63ae7
Update og_access/og_access.module
MPParsley Aug 9, 2019
60197b6
Update og_access/og_access.module
MPParsley Aug 21, 2019
1342e72
Merge pull request #7 from Gizra/8.x-1.x
MPParsley Sep 10, 2019
29de2b3
Fixed deprecation
MPParsley Sep 10, 2019
4ed5950
Merge pull request #8 from Gizra/8.x-1.x
pfrenssen Jan 29, 2020
dc3eb79
Merge branch '8.x-1.x' into og_access
MPParsley Jul 20, 2020
39925fb
Merge branch '8.x-1.x' into og_access
MPParsley Aug 4, 2020
14f23d3
Merge branch '8.x-1.x' into og_access
MPParsley Aug 10, 2020
1ab9466
Add strict types
MPParsley Aug 10, 2020
67dd034
Merge branch '8.x-1.x' into og_access
MPParsley Jan 19, 2021
127db3a
Apply suggestions from code review
MPParsley Jan 19, 2021
ebace9d
Merge branch '8.x-1.x' into og_access
MPParsley Apr 20, 2021
57f47d0
Update og_access/og_access.module
MPParsley Apr 20, 2021
e635a75
Add support for Drupal 9
MPParsley Apr 22, 2021
a8389e9
Merge branch '8.x-1.x' into og_access
MPParsley Apr 22, 2021
d59a0ee
Update og_access/og_access.module
MPParsley Apr 22, 2021
d64c11d
Merge branch '8.x-1.x' into og_access
MPParsley Sep 15, 2021
246aadd
Merge branch '8.x-1.x' into og_access
MPParsley Sep 30, 2021
208c0a3
Merge branch '8.x-1.x' into og_access
MPParsley Feb 11, 2022
fec3974
Merge branch '8.x-1.x' into og_access
MPParsley Aug 25, 2022
2887506
Merge branch '8.x-1.x' into og_access
MPParsley Aug 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions og_access/og_access.info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Organic Groups access control
description: "Enable access control for private and public groups and group content."
package: Organic Groups

core_version_requirement: ^8 || ^9
type: module

dependencies:
- og:og
16 changes: 16 additions & 0 deletions og_access/og_access.install
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/**
* @file
* Install/update/uninstall hook implementations.
*/

declare(strict_types = 1);

/**
* Implements hook_install().
*/
function og_access_install() {
// Mark node access permissions for rebuild.
node_access_needs_rebuild(TRUE);
}
217 changes: 217 additions & 0 deletions og_access/og_access.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<?php

/**
* @file
* Enable access control for private and public groups and group content.
*
* @todo Handle visibility change.
* @todo Set group content visibility default to that of the group.
* @todo Move grants/access to service.
*/

declare(strict_types = 1);

use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
use Drupal\Core\Entity\BundleEntityFormBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\node\NodeInterface;
use Drupal\og\Og;
use Drupal\og_access\OgAccessBundleFormAlter;
use Drupal\og_access\OgAccess;

/**
* Implements hook_node_grants().
*/
function og_access_node_grants(AccountInterface $account, $op) {
if ($op !== 'view') {
return [];
}

/** @var \Drupal\og\MembershipManager $membership_manager */
$membership_manager = \Drupal::service('og.membership_manager');
if ($groups = $membership_manager->getUserGroups($account->id())) {
foreach ($groups as $group_type => $entity_groups) {
/** @var \Drupal\core\Entity\EntityInterface $group */
foreach ($entity_groups as $group) {
$realm = OgAccess::OG_ACCESS_REALM . ':' . $group_type;
$grants[$realm][] = $group->id();
}
}
}

return !empty($grants) ? $grants : [];
}

/**
* Implements hook_node_access_records().
*/
function og_access_node_access_records(NodeInterface $node) {
if (!$node->isPublished()) {
// Node is unpublished, so we don't allow any group member to see it.
return [];
}

// The group IDs, that in case access is granted, will be recorded.
$gids = [];

if (Og::isGroup('node', $node->getType()) &&
$node->hasField(OgAccess::OG_ACCESS_FIELD) &&
!empty($node->{OgAccess::OG_ACCESS_FIELD}) && $node->{OgAccess::OG_ACCESS_FIELD}->value) {
// Private group.
$gids['node'][] = $node->id();
}

if ($node->hasField(OgAccess::OG_ACCESS_CONTENT_FIELD) &&
!empty($node->get(OgAccess::OG_ACCESS_CONTENT_FIELD))) {
$content_access = $node->get(OgAccess::OG_ACCESS_CONTENT_FIELD)->value;
}
else {
$content_access = OgAccess::OG_ACCESS_PUBLIC;
}

switch ($content_access) {
case OgAccess::OG_ACCESS_PUBLIC:
// Skip non-group content nodes.
if (!Og::isGroupContent('node', $node->getType())) {
break;
}

$has_private = FALSE;
/** @var \Drupal\og\OgGroupAudienceHelper $audience_helper */
$audience_helper = \Drupal::service('og.group_audience_helper');
foreach ($audience_helper->getAllGroupAudienceFields('node', $node->getType()) as $field_name => $field) {
foreach ($node->get($field_name)->referencedEntities() as $group) {
$list_gids[$group->getEntityTypeId()][] = $group->id();

if ($has_private) {
// We already know we have a private group, so we can avoid
// re-checking it.
continue;
}

if ($group->hasField(OgAccess::OG_ACCESS_FIELD) && !empty($group->get(OgAccess::OG_ACCESS_FIELD)) &&
$group->get(OgAccess::OG_ACCESS_FIELD)->value) {
$has_private = TRUE;
}
}
}
if ($has_private) {
$gids = array_merge_recursive($gids, $list_gids);
}
break;

case OgAccess::OG_ACCESS_PRIVATE:
$list_gids = [];
/** @var \Drupal\og\OgGroupAudienceHelper $audience_helper */
$audience_helper = \Drupal::service('og.group_audience_helper');
foreach ($audience_helper->getAllGroupAudienceFields('node', $node->getType()) as $field_name => $field) {
foreach ($node->get($field_name)->referencedEntities() as $group) {
$list_gids[$group->getEntityTypeId()][] = $group->id();
}
}

$gids = array_merge_recursive($gids, $list_gids);
break;
}

foreach ($gids as $group_type => $values) {
foreach ($values as $gid) {
$grants[] = [
'realm' => OgAccess::OG_ACCESS_REALM . ':' . $group_type,
'gid' => $gid,
'grant_view' => 1,
'grant_update' => 0,
'grant_delete' => 0,
];
}
}

return !empty($grants) ? $grants : [];
}

/**
* Implements hook_form_alter().
*/
function og_access_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
if ($form_state->getFormObject() instanceof BundleEntityFormBase) {
(new OgAccessBundleFormAlter($form_state->getFormObject()->getEntity(), \Drupal::service('string_translation')))
->formAlter($form, $form_state);
}
}

/**
* Implements hook_entity_insert().
*/
function og_access_entity_insert(EntityInterface $entity) {
og_access_entity_type_save($entity);
}

/**
* Implements hook_entity_update().
*/
function og_access_entity_update(EntityInterface $entity) {
og_access_entity_type_save($entity);
}

/**
* Adds/removes the group and group content access fields.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object.
*/
function og_access_entity_type_save(EntityInterface $entity) {
if (!$entity instanceof ConfigEntityBundleBase || !isset($entity->og_is_group)) {
return;
}

$bundle = $entity->id();
$definition = \Drupal::entityTypeManager()->getDefinition($entity->getEntityTypeId());
$entity_type_id = $definition->getBundleOf();

$enable_og_access = $entity->og_enable_access;

// Add/remove on the group itself.
$is_group = Og::isGroup($entity_type_id, $bundle);
if ($entity->og_is_group || $is_group) {
$field = FieldConfig::loadByName($entity_type_id, $bundle, OgAccess::OG_ACCESS_FIELD);
if (!$field && $enable_og_access) {
Og::createField(OgAccess::OG_ACCESS_FIELD, $entity_type_id, $bundle);
}
elseif ($field) {
if (!$enable_og_access || $is_group && !$entity->og_is_group) {
$field->delete();
}
}
}

// Add remove the relevant field to the group content bundle.
$is_group_content = Og::isGroupContent($entity_type_id, $bundle);
if ($entity->og_group_content_bundle || $is_group_content) {
$field = FieldConfig::loadByName($entity_type_id, $bundle, OgAccess::OG_ACCESS_CONTENT_FIELD);

if (!$field && $enable_og_access) {
Og::createField(OgAccess::OG_ACCESS_CONTENT_FIELD, $entity_type_id, $bundle);
}
elseif ($field) {
if (!$enable_og_access || $is_group_content && !$entity->og_group_content_bundle) {
$field->delete();
}
}
}
}

/**
* Implements hook_module_implements_alter().
*/
function og_access_module_implements_alter(&$implementations, $hook) {
if ($hook === 'form_alter') {
// Move our form alter after the og_ui one.
// @todo Remove once og_ui and og are merged.
MPParsley marked this conversation as resolved.
Show resolved Hide resolved
$group = $implementations['og_access'];
unset($implementations['og_access']);
$implementations['og_access'] = $group;
}
}
37 changes: 37 additions & 0 deletions og_access/src/OgAccess.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types = 1);

namespace Drupal\og_access;

/**
* Helper class for constants.
*/
class OgAccess {

/**
* The access realm of group member.
*/
const OG_ACCESS_REALM = 'og_access';

/**
* Group public access field.
*/
const OG_ACCESS_FIELD = 'group_access';

/**
* Group public access field.
*/
const OG_ACCESS_CONTENT_FIELD = 'group_content_access';

/**
* Public group/group content access.
*/
const OG_ACCESS_PUBLIC = 0;

/**
* Private group/group content access.
*/
const OG_ACCESS_PRIVATE = 1;

}
105 changes: 105 additions & 0 deletions og_access/src/OgAccessBundleFormAlter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

declare(strict_types = 1);

namespace Drupal\og_access;

use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
MPParsley marked this conversation as resolved.
Show resolved Hide resolved
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\og\Og;
use Drupal\Core\StringTranslation\TranslationInterface;

/**
* Helper for og_access_form_alter().
*/
class OgAccessBundleFormAlter {
use StringTranslationTrait;

/**
* The entity bundle.
*
* @var string
*/
protected $bundle;

/**
* The entity type ID.
*
* @var string
*/
protected $entityTypeId;

/**
* The form entity which has been used for populating form element defaults.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;

/**
* Construct a BundleFormAlter object.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object.
* @param Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation object.
*/
public function __construct(EntityInterface $entity, TranslationInterface $string_translation) {
$this->entity = $entity;
$this->stringTranslation = $string_translation;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this property and argument since StringTranslationTrait is used.

Copy link
Collaborator

@MPParsley MPParsley Apr 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need to inject the service, the trait doesn't do this (it would work but then the trait would get the service from the global container).

There is an api to set the service though:

Suggested change
$this->stringTranslation = $string_translation;
$this->setStringTranslation($string_translation);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice earlier that we're instantiating OgAccessBundleFormAlter from within a hook_form_alter.

As we are injecting the service from the global container we might as well remove the Trait, or keep it as we might move to events instead of hooks in the future...

}

/**
* This is a helper for og_ui_form_alter().
*
* @param array $form
* The form variable.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state object.
*/
public function formAlter(array &$form, FormStateInterface $form_state) {
// Example: node.
$this->entityTypeId = $this->entity->getEntityType()->getBundleOf();

// Example: article.
$this->bundle = $this->entity->id();

$form['og']['og_enable_access'] = [
'#type' => 'checkbox',
'#title' => $this->t('Restrict access to group members'),
'#description' => $this->t('Enable OG access control. Provides a new field that determines the group/group content visibility. Public groups can have member-only content. Any public group content belonging to a private group will be restricted to the members of that group only.'),
'#default_value' => $this->bundle ? $this->hasAccessControl() : FALSE,
'#states' => [
'visible' => [
[':input[name="og_is_group"]' => ['checked' => TRUE]],
[':input[name="og_group_content_bundle"]' => ['checked' => TRUE]],
],
],
];
}

/**
* Checks whether the existing bundle has OG access control enabled.
*
* @return bool
* True if the group bundle has the OgAccess::OG_ACCESS_FIELD field -OR-
* if the group content bundle has the OG_CONTENT_ACCESS_FIELD field.
* False otherwise.
*/
protected function hasAccessControl() {
$field_definitions = \Drupal::service('entity_field.manager')
->getFieldDefinitions($this->entityTypeId, $this->bundle);

if (Og::isGroup($this->entityTypeId, $this->bundle)) {
return isset($field_definitions[OgAccess::OG_ACCESS_FIELD]);
}

if (Og::isGroupContent($this->entityTypeId, $this->bundle)) {
return isset($field_definitions[OgAccess::OG_ACCESS_CONTENT_FIELD]);
}

return FALSE;
}

}
Loading