grant-1.x-dev/src/GrantMain.php
src/GrantMain.php
<?php
namespace Drupal\grant;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Uuid\Uuid;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Site\Settings;
use Drupal\grant\Entity\Grant;
use Drupal\multiple_email\EmailInterface;
use Drupal\user\UserInterface;
/**
* Provide main class for grant module.
*/
final class GrantMain implements GrantMainInterface {
/**
* Constructs an GrantMain object.
*/
public function __construct(
private readonly AccountProxyInterface $account,
private readonly CacheBackendInterface $cacheBackend,
private readonly CurrentPathStack $pathCurrent,
private readonly EntityRepositoryInterface $entityRepository,
private readonly EntityTypeManagerInterface $entityTypeManager,
private readonly ModuleHandlerInterface $moduleHandler,
private readonly TimeInterface $time,
) {}
/**
* {@inheritdoc}
*/
public function getGrantConfigs() {
$grant_config = Settings::get('grant', []);
if (!isset($grant_config['max_deph'])) {
$grant_config['max_deph'] = 7;
}
if (!isset($grant_config['entities'])) {
$grant_config['entities'] = [];
}
return $grant_config;
}
/**
* {@inheritdoc}
*/
public function entityLoadUuid($e_type = '', $e_uuid = '') {
$return_default = NULL;
$e_types_all = $this->entityTypeManager->getDefinitions();
if (!in_array($e_type, array_keys($e_types_all))) {
return $return_default;
}
$entity = $this->entityRepository->loadEntityByUuid($e_type, $e_uuid);
if ($entity instanceof ContentEntityInterface) {
return $entity;
}
return $return_default;
}
/**
* {@inheritdoc}
*/
public function addGrant($values = []) {
$return = NULL;
$grant_configs = $this->getGrantConfigs() ?? [];
$e_type = $values['entity_type'];
$e_uuid = $values['entity_uuid'];
$entity = $this->entityLoadUuid($e_type, $e_uuid);
if ($entity instanceof ContentEntityInterface) {
$e_bundle = $entity->bundle();
$config = $grant_configs['entities'][$e_type][$e_bundle] ?? [];
$uid = $this->account->id();
$current_user = $this->entityTypeManager->getStorage('user')->load($uid);
$current_u3id = $current_user->uuid();
$assign_mail = trim($values['email']) ?? '-';
$e_values = [
'status' => 0,
'role' => $values['role'],
'note' => trim($values['note']),
'user' => $current_u3id,
'email' => $assign_mail,
'entity_type' => $e_type,
'entity_uuid' => $e_uuid,
];
$auto_assign = $config['assign_auto'] ?? FALSE;
if ($auto_assign) {
$assignee = user_load_by_mail($assign_mail);
if ($assignee instanceof UserInterface) {
$e_values['status'] = 1;
$e_values['assignee'] = $assignee->uuid();
$e_values['assigned'] = $this->time->getRequestTime();
}
}
$grant = Grant::create($e_values);
$grant->save();
$return = $grant;
}
return $return;
}
/**
* {@inheritdoc}
*/
public function grantChangeCacheClear($grant_entity) {
/** @var \Drupal\grant\GrantInterface $grant_entity */
$assignee_uuid = $grant_entity->get("assignee")->getValue()[0]['target_uuid'] ?? 0;
if ($assignee_uuid != 0) {
$cache_id = 'grant_uag:' . $assignee_uuid;
$this->cacheBackend->delete($cache_id);
}
}
/**
* {@inheritdoc}
*/
public function createGrantAccessCheckString($options = []) {
if (isset($options['grant_role'])) {
$check = 'role';
$check_value = (string) implode('+', $options['grant_role']);
}
else {
$check = 'perm';
if (\is_array($options['grant_perm'])) {
$check_value = (string) implode('+', $options['grant_perm']);
}
$check_value = (string) $options['grant_perm'];
}
$check_string = $check . ':' . $check_value . '|';
$e_type_raw = $options['e_type'] ?? '';
$e_type = trim($e_type_raw);
if ($e_type != '') {
$check_string .= "type_name:" . $e_type;
}
else {
$e_type_arg = $options['e_type_arg'] ?? 1;
$check_string .= "type:" . (string) $e_type_arg;
}
$check_string .= ',';
if ($options['e_id_type'] == 'uuid') {
$check_string .= 'uuid:';
}
else {
$check_string .= 'id:';
}
$e_id_arg = $options['e_id_arg'] ?? 2;
$check_string .= (string) $e_id_arg;
return $check_string;
}
/**
* {@inheritdoc}
*/
public function userAssignedGrantHasAccessPathCurrent($options = [], $account = NULL) {
$access = FALSE;
$grant_configs = $this->getGrantConfigs() ?? [];
$args_raw = explode('/', $this->pathCurrent->getPath());
$e_type_raw = $options['e_type'] ?? '';
$e_type = trim($e_type_raw);
if ($e_type == '') {
$e_type_arg = $options['e_type_arg'] ?? 1;
if (isset($args_raw[$e_type_arg])) {
$e_type = Html::escape($args_raw[$e_type_arg]);
}
}
$configured_e_types = $grant_configs['entities'];
if (!\array_key_exists($e_type, $configured_e_types)) {
return FALSE;
}
$check = 'perm';
$check_perm = '';
$check_roles = [];
if (isset($options['grant_perm'])) {
if (\is_array($options['grant_perm'])) {
// Transform to 'OR' Logic:
$check_perm = (string) implode('+', $options['grant_perm']);
}
else {
$check_perm = $options['grant_perm'];
}
}
else {
$check = 'role';
if (\is_string($options['grant_role'])) {
$check_roles = explode('+', $options['grant_role']);
}
else {
$check_roles = array_keys($options['grant_role']);
}
}
$e_id_arg = $options['e_id_arg'] ?? 2;
$e_id_raw = '_none';
$e_id = 0;
$e_uuid = NULL;
if (isset($args_raw[$e_id_arg])) {
$e_id_raw = $args_raw[$e_id_arg];
if ($options['e_id_type'] == 'uuid') {
if (Uuid::isValid($e_id_raw)) {
$e_uuid = $e_id_raw;
}
else {
return FALSE;
}
}
else {
$e_id = (int) $e_id_raw;
}
}
if ($account == NULL) {
$account = $this->account;
}
// First check global roles or permissions:
switch ($check) {
case 'role':
$access = !empty(array_intersect(array_filter($check_roles), $account->getRoles()));
break;
case 'perm':
$access = $account->hasPermission($check_perm);
break;
}
if ($access) {
return $access;
}
$uid = $account->id();
$user = $this->entityTypeManager->getStorage('user')->load($uid);
$u3id = $user->uuid();
if ($e_uuid == NULL) {
$entity = $this->entityTypeManager->getStorage($e_type)->load($e_id);
if ($entity instanceof ContentEntityInterface) {
$e_uuid = $entity->uuid();
}
else {
return FALSE;
}
}
switch ($check) {
case 'role':
$uag_roles = $this->getUserAssignedGrantRoles($u3id, $e_type, $e_uuid);
$access = !empty(array_intersect(array_filter($check_roles), $uag_roles));
break;
case 'perm':
$access = $this->userAssignedGrantHasPermission($u3id, $check_perm, $e_type, $e_uuid);
break;
}
return $access;
}
/**
* {@inheritdoc}
*/
public function getUserAssignedGrantRoles($u3id = 'current', $e_type = '', $e_uuid = '') {
if ($u3id == 'current') {
$uid = $this->account->id();
$user = $this->entityTypeManager->getStorage('user')->load($uid);
$u3id = $user->uuid();
}
$uag_roles = [];
$uag = $this->getUserAssignedGrants($u3id);
$data = $this->getUserAssignedGrantEntitiesRoles(
[
'grant_config' => $this->getGrantConfigs(),
'uag_roles' => $uag_roles,
'uag' => $uag,
'e_type' => $e_type,
'e_uuid' => $e_uuid,
'e_processed' => [],
'counter' => 0,
]);
return $data['uag_roles'];
}
/**
* {@inheritdoc}
*/
public function userAssignedGrantHasPermission($u3id = 'current', $perm_raw = '', $e_type = '', $e_uuid = '', $role_limit = [], $uag_roles = []) {
if ($u3id == 'current') {
$uid = $this->account->id();
$user = $this->entityTypeManager->getStorage('user')->load($uid);
$u3id = $user->uuid();
}
if ($uag_roles == []) {
$uag_roles = $this->getUserAssignedGrantRoles($u3id, $e_type, $e_uuid);
}
// @todo Check if this experimental can be remeoved.
if ($role_limit != []) {
// Only check permission if there is no role limitation:
$role_limit_test = FALSE;
foreach ($role_limit as $test_role) {
if (in_array($test_role, $uag_roles)) {
$role_limit_test = TRUE;
continue;
}
}
if ($role_limit_test == FALSE) {
return FALSE;
}
}
$rolestorage = $this->entityTypeManager->getStorage('user_role');
// Logic from "/core/modules/user/src/Access/PermissionAccessCheck.php":
$conjunction = 'AND';
$split = explode(',', $perm_raw);
if (count($split) > 1) {
$conjunction = 'AND';
}
else {
$split = explode('+', $perm_raw);
$conjunction = 'OR';
}
// Logic from "/core/lib/Drupal/Core/Access/AccessResult.php":
$access = FALSE;
if ($conjunction == 'AND') {
$access = TRUE;
foreach ($split as $permission) {
if (!$rolestorage->isPermissionInRoles($permission, $uag_roles)) {
$access = FALSE;
break;
}
}
}
else {
foreach ($split as $permission) {
if ($rolestorage->isPermissionInRoles($permission, $uag_roles)) {
$access = TRUE;
break;
}
}
}
return $access;
}
/**
* Retrieve user assigned grants roles of entities incl. parent entities.
*/
private function getUserAssignedGrantEntitiesRoles($data) {
$grant_config = $data['grant_config'];
$data['counter']++;
if (isset($data['e_processed']['e_type'])
&& isset($data['e_processed']['e_uuid'])) {
return $data;
}
$uag = $data['uag'];
$e_type = $data['e_type'];
$e_uuid = $data['e_uuid'];
$uag_roles_entity = [];
if (isset($uag[$e_type]) && isset($uag[$e_type][$e_uuid])) {
$uag_roles_entity = array_keys($uag[$e_type][$e_uuid]);
$data['e_processed'][$e_type][$e_uuid] = $uag_roles_entity;
}
$data['uag_roles'] = array_merge($uag_roles_entity, $data['uag_roles']);
// Check if current entity type has referenced parent fields defined.
if (isset($grant_config['entities'][$e_type])) {
$entity = $this->entityLoadUuid($e_type, $e_uuid);
if ($entity instanceof ContentEntityInterface) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$bundle = $entity->bundle();
$parent_fields = $grant_config['entities'][$e_type][$bundle]['assign_parents'] ?? [];
if ($parent_fields != []) {
$fields = $entity->getFields();
// Only loop through referenced parent fields.
foreach ($parent_fields as $parent_field) {
if (isset($fields[$parent_field])) {
$field = $fields[$parent_field];
$data['e_type'] = $field->getSetting('target_type');
$values = $field->getValue();
foreach ($values as $value) {
if (isset($value['target_uuid'])) {
$data['e_uuid'] = $value['target_uuid'];
}
else {
$data['e_uuid'] = "0";
if (isset($value['target_id'])) {
$target = $this->entityTypeManager
->getStorage($data['e_type'])
->load($value['target_id']);
if ($target) {
$data['e_uuid'] = $target->uuid();
}
}
}
if ($data['counter'] < $grant_config['max_deph']) {
// Self calling this function.
$data = $this->getUserAssignedGrantEntitiesRoles($data);
}
else {
// Max deph reached.
// @todo Log and/or set message to inform admin.
}
}
}
}
}
}
}
return $data;
}
/**
* {@inheritdoc}
*/
public function getUserAssignedGrants($uuid = "current") {
if ($uuid == "current") {
$current_user_id = $this->account->id();
$current_user = $this->entityTypeManager->getStorage('user')->load($current_user_id);
$uuid = $current_user->uuid();
}
$cache_id = 'grant_uag:' . $uuid;
$cached = $this->cacheBackend->get($cache_id);
if ($cached) {
$grants = $cached->data;
}
else {
$grants = [];
$grant_storage = $this->entityTypeManager->getStorage('grant');
$grant_query = $grant_storage->getQuery()
->accessCheck(FALSE)
->condition('assignee', $uuid)
->condition('status', 1);
$grant_entities = $grant_storage->loadMultiple($grant_query->execute());
foreach ($grant_entities as $grant) {
/** @var \Drupal\grant\Entity\Grant $grant */
$role = $grant->get('role')->target_id;
$entity_type = $grant->get('entity_type')->value;
$entity_uuid = $grant->get('entity_uuid')->value;
$grants[$entity_type][$entity_uuid][$role] = $grant->get('uuid')->value;
}
// @todo Add cache tag for entity_type and user.
$cache_tags = ['grant_uag', 'grant:' . $uuid];
$this->cacheBackend->set($cache_id, $grants, CacheBackendInterface::CACHE_PERMANENT, $cache_tags);
}
return $grants;
}
/**
* Retrieve user mail addresses.
*/
private function getUserEmails($uid = 0) {
$current_user_id = $this->account->id();
$current = FALSE;
if ($uid == 0) {
$uid = $current_user_id;
$current = TRUE;
}
$emails = [];
if ($this->moduleHandler->moduleExists('multiple_email')) {
$email_storage = $this->entityTypeManager->getStorage('multiple_email');
$multiple_email_query = $email_storage->getQuery()
->accessCheck(FALSE)
->condition('uid', $uid)
->condition('status', EmailInterface::CONFIRMED);
$email_entities = $email_storage->loadMultiple($multiple_email_query->execute());
foreach ($email_entities as $email_entity) {
/** @var \Drupal\multiple_email\Entity\Email $email_entity */
$emails[] = $email_entity->getEmail();
}
}
else {
if ($current) {
$user = $this->account;
}
else {
$user = $this->entityTypeManager->getStorage('user')->load($uid);
}
/** @var \Drupal\user\Entity\User $user */
$emails[] = $user->getEmail();
}
return $emails;
}
}
