foldershare-8.x-1.2/src/Entity/FolderShareTraits/GetSetAccessGrantsTrait.php
src/Entity/FolderShareTraits/GetSetAccessGrantsTrait.php
<?php
namespace Drupal\foldershare\Entity\FolderShareTraits;
use Drupal\user\Entity\User;
use Drupal\Component\Utility\Environment;
/**
* Get/set FolderShare entity access grants fields.
*
* This trait includes get and set methods for FolderShare entity
* access grants fields.
*
* <B>Internal trait</B>
* This trait is internal to the FolderShare module and used to define
* features of the FolderShare entity class. It is a mechanism to group
* functionality to improve code management.
*
* @ingroup foldershare
*/
trait GetSetAccessGrantsTrait {
/*---------------------------------------------------------------------
*
* Get/set access grants.
*
*---------------------------------------------------------------------*/
/**
* Returns TRUE if the access grants are cleared.
*
* TRUE is returned if the access grants are ENTIRELY cleared and have
* no user IDs at all, including no entries for the owner of the item.
*
* @return bool
* Returns TRUE if the access grants are cleared.
*/
private function areAccessGrantsCleared() {
return empty($this->grantauthoruids->getValue()) === TRUE &&
empty($this->grantviewuids->getValue()) === TRUE;
}
/**
* {@inheritdoc}
*/
public function getAccessGrantAuthorUserIds() {
$uids = [];
if ($this->isRootItem() === TRUE) {
foreach ($this->grantauthoruids->getValue() as $item) {
$uids[] = (int) $item['target_id'];
}
}
return $uids;
}
/**
* {@inheritdoc}
*/
public function getAccessGrantViewUserIds() {
$uids = [];
if ($this->isRootItem() === TRUE) {
foreach ($this->grantviewuids->getValue() as $item) {
$uids[] = (int) $item['target_id'];
}
}
return $uids;
}
/**
* {@inheritdoc}
*/
public function getAccessGrants() {
if ($this->isRootItem() === FALSE) {
return [];
}
$authors = $this->getAccessGrantAuthorUserIds();
$viewers = $this->getAccessGrantViewUserIds();
// Create a grant list with one entry per user ID.
// The entry is an array with the UID as the key
// and one of these possible entries:
// - ['view'] = user only has view access.
// - ['author'] = user only has author access (which is odd).
// - ['author', 'view'] = user has view and author access.
//
// Start by adding all author grants.
$grants = [];
foreach ($authors as $uid) {
$grants[$uid] = ['author'];
}
// Add all view grants.
foreach ($viewers as $uid) {
if (isset($grants[$uid]) === TRUE) {
$grants[$uid][] = 'view';
}
else {
$grants[$uid] = ['view'];
}
}
return $grants;
}
/**
* Sets all user IDs and access grants for this root item.
*
* <B>This method is internal and strictly for use by the FolderShare
* module itself.</B>
*
* The given associative array must have user IDs as keys. For each user,
* the array value must be an array of strings with one or two values:
* - ['view'] = user only has view access.
* - ['author'] = user only has author access (which is odd).
* - ['author', 'view'] = user has view and author access.
*
* The given array is used to completely reset all access grants for
* this root item. Any prior grants are removed.
*
* The owner of the root item is automaticaly included with both view and
* author access, whether or not they are included in the given array.
*
* The given array is in the same format as that returned by
* getAccessGrants().
*
* If this is not a root item, this call has no effect.
*
* System hidden and disabled entities may be changed, however the module's
* access control will deny access for any user that is not an administrator.
*
* The caller must call save() for the change to take effect.
*
* <B>Process locks</B>
* This method does not lock access. The caller should lock around changes
* to the entity.
*
* @param array $grants
* An unordered associative array with user IDs as keys and
* arrays as values. Array values contain strings indicating 'view' or
* 'author' access.
*
* @see ::getAccessGrantAuthorUserIds()
* @see ::getAccessGrantViewUserIds()
* @see ::isAccessGranted()
*/
private function setAccessGrants(array $grants) {
if ($this->isRootItem() === FALSE) {
return;
}
// Use the given grant list with one entry per user ID.
// The entry is an array with the UID as the key
// and one of these possible entries:
// - ['view'] = user only has view access.
// - ['author'] = user only has author access (which is odd).
// - ['author', 'view'] = user has view and author access.
//
// Initialize arrays. Always include the folder owner for
// view and author access.
$ownerId = $this->getOwnerId();
$authors = [$ownerId];
$viewers = [$ownerId];
// Split the array into separate lists for view and author.
// Along the way, remove redundant entries.
foreach ($grants as $uid => $list) {
$isAuthor = in_array('author', $list);
$isViewer = in_array('view', $list);
// If the user isn't already in the author or view, add them.
if ($isAuthor === TRUE && in_array($uid, $authors, TRUE) === FALSE) {
$authors[] = $uid;
}
if ($isViewer === TRUE && in_array($uid, $viewers, TRUE) === FALSE) {
$viewers[] = $uid;
}
}
// Sweep through the arrays and switch them to include 'target_id'.
foreach ($authors as $index => $uid) {
$authors[$index] = ['target_id' => $uid];
}
foreach ($viewers as $index => $uid) {
$viewers[$index] = ['target_id' => $uid];
}
// Set the fields.
$this->grantauthoruids->setValue($authors, FALSE);
$this->grantviewuids->setValue($viewers, FALSE);
}
/*---------------------------------------------------------------------
*
* Test access grants.
*
*---------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function isAccessGranted(int $uid, string $access) {
if ($uid < 0 || $this->isRootItem() === FALSE) {
return FALSE;
}
// Loop through the appropriate view or author fields and
// check if the given user has been explicitly granted access.
//
// For view and author access, we recognize the special case where the
// anonumous user (uid = 0) has been granted access. If anonymous has
// access, then *everybody* has access and this method returns TRUE.
$anonymousId = User::getAnonymousUser()->id();
$access = mb_convert_case($access, MB_CASE_LOWER);
switch ($access) {
default:
// Unknown request.
return FALSE;
case 'author':
// Check the list of author UIDs.
foreach ($this->grantauthoruids->getValue() as $entry) {
$entryUid = (int) $entry['target_id'];
if ($entryUid === $anonymousId || $entryUid === $uid) {
return TRUE;
}
}
return FALSE;
case 'view':
// Check the list of view UIDs.
foreach ($this->grantviewuids->getValue() as $entry) {
$entryUid = (int) $entry['target_id'];
if ($entryUid === $anonymousId || $entryUid === $uid) {
return TRUE;
}
}
return FALSE;
}
}
/**
* {@inheritdoc}
*/
public function isAccessPrivate() {
if ($this->isRootItem() === FALSE) {
return FALSE;
}
// Access is private if there are no users other than the owner
// in the list of author and view grant UIDs.
$uid = $this->getOwnerId();
foreach ($this->grantviewuids->getValue() as $item) {
$itemUid = (int) $item['target_id'];
if ($itemUid !== $uid) {
return FALSE;
}
}
foreach ($this->grantauthoruids->getValue() as $item) {
$itemUid = (int) $item['target_id'];
if ($itemUid !== $uid) {
return FALSE;
}
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function isAccessPublic() {
if ($this->isRootItem() === FALSE) {
return FALSE;
}
// Access is public if the anonymous user (UID = 0) is listed in
// author or view grant UIDs.
$anonymousId = User::getAnonymousUser()->id();
foreach ($this->grantviewuids->getValue() as $item) {
if ((int) $item['target_id'] === $anonymousId) {
return TRUE;
}
}
foreach ($this->grantauthoruids->getValue() as $item) {
if ((int) $item['target_id'] === $anonymousId) {
return TRUE;
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function isAccessShared() {
if ($this->isRootItem() === FALSE) {
return FALSE;
}
// Access is shared if anyone besides the owner is listed in the
// author and view grant UIDs.
return $this->isAccessPrivate() === FALSE;
}
/**
* {@inheritdoc}
*/
public function isSharedBy(int $uid) {
if ($uid < 0 || $this->isRootItem() === FALSE ||
$this->isOwnedBy($uid) === FALSE) {
return FALSE;
}
// If the view or author grants include any user ID other than
// the owner, then the item is shared.
foreach ($this->grantviewuids->getValue() as $entry) {
$entryUid = (int) $entry['target_id'];
if ($entryUid !== $uid) {
return TRUE;
}
}
foreach ($this->grantauthoruids->getValue() as $entry) {
$entryUid = (int) $entry['target_id'];
if ($entryUid !== $uid) {
return TRUE;
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function isSharedWith(int $uid, string $access = 'view') {
if ($uid < 0 ||
$this->isRootItem() === FALSE ||
$this->isOwnedBy($uid) === TRUE) {
return FALSE;
}
// An item is shared with the user if that user is listed in
// author or view grant UIDs.
foreach ($this->grantviewuids->getValue() as $item) {
if ((int) $item['target_id'] === $uid) {
return TRUE;
}
}
foreach ($this->grantauthoruids->getValue() as $item) {
if ((int) $item['target_id'] === $uid) {
return TRUE;
}
}
return FALSE;
}
/*---------------------------------------------------------------------
*
* Add access grants.
*
*---------------------------------------------------------------------*/
/**
* Grants a user access to this root folder.
*
* <B>This method is internal and strictly for use by the FolderShare
* module itself.</B>
*
* If this item is not a root folder, no action is taken.
*
* Granting a user access adds the user's ID to the root folder's list
* of users that have the specified access. Recognized access grants are:
*
* - 'view': can view folder fields and content.
* - 'author': can create, update, and delete fields and content.
*
* The owner of the root folder always has view and author access granted.
* Adding the owner, or any user already granted access, has no affect.
*
* Access grants may only be added on root folders. Access grants on
* non-root folders are silently ignored.
*
* The caller must call save() for the change to take effect.
*
* @param int $uid
* The user ID of a user currently granted access. If the user ID is
* negative, matches the owner ID, or does not match a user ID in the
* item's current grants list, this call has no effect.
* @param string $access
* The access grant to be removed. This is one of 'author' or 'view'.
* All other values and mixes of case are ignored.
*
* @see ::clearAccessGrants()
* @see ::deleteAccessGrant()
* @see ::setAccessGrants()
*/
private function addAccessGrant(int $uid, string $access) {
if ($uid < 0 || $this->isRootItem() === FALSE) {
return;
}
// When adding access, if the user ID is already in the access list,
// they are not added again.
switch ($access) {
case 'author':
// Append to the list of author UIDs.
if ($this->isAccessGranted($uid, 'author') === FALSE) {
$this->grantauthoruids->appendItem(['target_id' => $uid]);
}
return;
case 'view':
// Append to the list of view UIDs.
if ($this->isAccessGranted($uid, 'view') === FALSE) {
$this->grantviewuids->appendItem(['target_id' => $uid]);
}
return;
default:
return;
}
}
/**
* Adds default access grants for the item owner.
*
* <B>This method is internal and strictly for use by the FolderShare
* module itself.</B>
*
* Default access grants are used to initialize the grant values
* when an item is first created. The default grant gives the
* item's owner view and author access.
*
* The caller must call save() for the change to take effect.
*
* @see ::getAccessGrantUserIds()
* @see ::getAccessGrantAuthorUserIds()
* @see ::getAccessGrantViewUserIds()
*/
private function addDefaultAccessGrants() {
if ($this->isRootItem() === FALSE) {
return;
}
$ownerUid = $this->getOwnerId();
$this->addAccessGrant($ownerUid, 'author');
$this->addAccessGrant($ownerUid, 'view');
}
/*---------------------------------------------------------------------
*
* Clear and delete access grants.
*
*---------------------------------------------------------------------*/
/**
* Clears all access grants for this root item, or those for a specific user.
*
* <B>This method is internal and strictly for use by the FolderShare
* module itself.</B>
*
* If an optional user ID is given, the user is removed from all access
* grants on the item. If no user ID is given, or if it is
* FolderShareInterface::ANY_USER_ID, all access grants are removed for
* the item, leaving only the default access for the item's owner.
*
* If this item is not a root folder, call arguments are ignored and all
* access grants are cleared. This restores the normal access grant state
* for non-root items.
*
* The caller must call save() for the change to take effect.
*
* @param int $uid
* (optional, default = FolderShareInterface::ANY_USER_ID) The user ID
* of a user for whome to clear access. Explicit requests to delete the
* owner's access are ignored. Deleting the owner's access as well
* requires calling this function with a FolderShareInterface::ANY_USER_ID
* or negative user ID and $retainOwnerGrants as FALSE.
* @param bool $retainOwnerGrants
* (optional, default = TRUE) When TRUE, retain the owner's access grants
* so that they can still see and operate upon the item. When FALSE, the
* owner's own access grants are also cleared.
*
* @see ::setAccessGrants()
*/
private function clearAccessGrants(
int $uid = self::ANY_USER_ID,
bool $retainOwnerGrants = TRUE) {
if ($this->isRootItem() === FALSE) {
// Access grants are only attached to root items. If this item
// is not a root item, go ahead and clear access grants anyway to
// clean up the item in case something leaked through.
$this->grantauthoruids->setValue([], FALSE);
$this->grantviewuids->setValue([], FALSE);
return;
}
if ($uid < 0) {
// Clear all of the grant UIDs.
$this->grantauthoruids->setValue([], FALSE);
$this->grantviewuids->setValue([], FALSE);
if ($retainOwnerGrants === TRUE) {
// Add back defaults. This is silently ignored if the
// item is not a root item.
$this->addDefaultAccessGrants();
}
}
elseif ($this->getOwnerId() !== $uid) {
// Delete the user's access.
$this->deleteAccessGrant($uid, 'view');
$this->deleteAccessGrant($uid, 'author');
}
}
/**
* Deletes a user's access to this root item.
*
* <B>This method is internal and strictly for use by the FolderShare
* module itself.</B>
*
* The owner of the root folder always has view and author access granted.
* Attempting to delete the owner's access has no effect.
*
* The caller must call save() for the change to take effect.
*
* @param int $uid
* The user ID of a user currently granted access. If the user ID is
* negative, matches the owner ID, or does not match a user ID in the
* item's current grants list, this call has no effect.
* @param string $access
* The access grant to be removed. This is one of 'author' or 'view'.
* All other values and mixes of case are ignored.
*
* @see ::clearAccessGrants()
* @see ::addAccessGrant()
* @see ::setAccessGrants()
*/
private function deleteAccessGrant(int $uid, string $access) {
if ($uid < 0 || $this->isRootItem() === FALSE) {
return;
}
// If the UID to delete is the root item's owner, don't delete
// them. The owner ALWAYS has access.
if ($this->getOwnerId() === $uid) {
return;
}
// If this folder is not a root item, go ahead and try to remove
// the UID. There should be no access grants to remove the ID from,
// but it doesn't hurt and could clean out anything that's leaked through.
switch ($access) {
case 'author':
// Remove from the list of author UIDs.
foreach ($this->grantauthoruids->getValue() as $index => $item) {
if ((int) $item['target_id'] === $uid) {
$this->grantauthoruids->removeItem($index);
return;
}
}
return;
case 'view':
// Remove from the list of view UIDs.
foreach ($this->grantviewuids->getValue() as $index => $item) {
if ((int) $item['target_id'] === $uid) {
$this->grantviewuids->removeItem($index);
}
}
return;
default:
return;
}
}
/*---------------------------------------------------------------------
*
* Sharing status.
*
*---------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function getSharingStatus() {
//
// Set up
// ------
// Get the root item, its owner, and information about the current
// and anonymous users.
if ($this->isSystemHidden() === TRUE ||
$this->isSystemDisabled() === TRUE) {
return 'private';
}
$root = $this->getRootItem();
if ($root === NULL) {
// Malformed entity!
return 'private';
}
$rootOwner = $root->getOwner();
if ($rootOwner === NULL) {
// Malformed entity!
return 'private';
}
$rootOwnerId = $rootOwner->id();
$currentUserId = (int) \Drupal::currentUser()->id();
$anonymousId = User::getAnonymousUser()->id();
//
// Check anonymous ownership
// -------------------------
// If the entity is owned by anonymous, it is always public.
if ($rootOwner->isAnonymous() === TRUE) {
return 'public';
}
//
// Check private ownership
// -----------------------
// If the item is not shared with anyone except the owner, it is either
// personal (owned by the current user) or private (owned by someone else).
if ($root->isAccessPrivate() === TRUE) {
return ($rootOwnerId === $currentUserId) ? 'personal' : 'private';
}
//
// Check anonymous sharing
// -----------------------
// If the content is shared with anonymous, then it is public.
if ($root->isAccessPublic() === TRUE) {
return 'public';
}
//
// Find non-anonymous sharing
// --------------------------
// Because of the previous isAccessPrivate(), at this point the item
// is shared with someone, and that someone isn't anonymous.
//
// Look through the item's grants and see if any of them are for
// someone other than anonymous and the site admin.
$uids = array_merge(
$root->getAccessGrantViewUserIds(),
$root->getAccessGrantAuthorUserIds());
$isSharedWithCurrent = FALSE;
foreach ($uids as $uid) {
// Ignore grant entries for:
// - The owner.
// - The site administrator.
// - Anonymous.
if ($uid === 1 || $uid === $rootOwnerId || $uid === $anonymousId) {
continue;
}
// Otherwise the grant gives access to someone. See if it grants
// access to the current user.
if ($uid === $currentUserId) {
$isSharedWithCurrent = TRUE;
break;
}
}
//
// Check sharing
// -------------
// There are several cases here:
//
// - If the item is shared with the current user, then it is shared.
//
// - Otherwise the item is not shared with the current user, but it is
// shared with someone. If the item is owned by the current user,
// then it is shared.
//
// - Otherwise the item is not owned by the current user or shared by
// them with the current user. The item is private.
//
if ($isSharedWithCurrent === TRUE) {
return 'shared with you';
}
if ($currentUserId === $rootOwnerId) {
return 'shared by you';
}
return 'private';
}
/*---------------------------------------------------------------------
*
* Change sharing.
*
*---------------------------------------------------------------------*/
/**
* Clears shared folder access grants for all of a user's content.
*
* When an optional user ID is given, access grants are cleared to disable
* sharing on all root items owned by the user. When a user ID is not given,
* it is FolderShareInterface::ANY_USER_ID, or it is negative, access grants
* are cleared to disable sharing on all root items for all users.
*
* System hidden and disabled items are also affected.
*
* <B>Process locks</B>
* This method does not lock access. The site should be in maintenance
* mode, or no users should be accessing the items being changed.
*
* @param int $uid
* (optional, default = FolderShareInterface::ANY_USER_ID) The user ID
* of the owner of root items for which to clear access grants.
* If the value is FolderShareInterface::ANY_USER_ID, access is
* cleared for all root items owned by any user.
*
* @see ::findAllRootItemIds()
* @see ::clearAccessGrants()
*/
public static function unshareAll(int $uid = self::ANY_USER_ID) {
// Shared access grants are only on root items. Get a list of
// all root items for the indicated user, or for all users.
$rootIds = self::findAllRootItemIds($uid);
// Loop through the folder IDs, load each one, and clear its
// access controls.
foreach ($rootIds as $id) {
$item = self::load($id);
if ($item !== NULL) {
$item->clearAccessGrants();
$item->save();
unset($item);
}
}
}
/**
* Removes the user from shared access on all root items.
*
* The user is removed from all access grants on all root items the
* do not own.
*
* System hidden and disabled items are also affected.
*
* <B>Process locks</B>
* This method does not lock access. The site should be in maintenance
* mode, or no users should be accessing the items being changed.
*
* @param int $uid
* The user ID of the user to remove from shared access to all root items.
*
* @see ::findAllRootItemIds()
* @see ::getAccessGrants()
* @see ::share()
*/
public static function unshareFromAll(int $uid) {
if ($uid < 0) {
return;
}
// Prepare.
// --------
// Try to push the PHP timeout to "unlimited" so that a long operation
// has a better chance to complete. This may not work if the site has
// PHP timeout changes blocked and it does nothing to stop web server
// timeouts.
Environment::setTimeLimit(0);
//
// Unshare.
// --------
// Shared access grants are only on root items. Get a list of
// all root items.
$rootIds = self::findAllRootItemIds();
// Loop through them and clear the user from the folder's access
// grants.
foreach ($rootIds as $id) {
$item = self::load($id);
if ($item !== NULL) {
$grants = $item->getAccessGrants();
if (isset($grants[$uid]) === TRUE) {
unset($grants[$uid]);
$item->setAccessGrants($grants);
$item->save();
}
unset($item);
}
}
}
/*---------------------------------------------------------------------
*
* Utilities.
*
*---------------------------------------------------------------------*/
/**
* Formats an access grants array as a string.
*
* This function is strictly used for logging changes to access grants.
*
* @param array $grants
* The access grants to format.
*
* @return string
* A string representation of the access grants.
*/
private static function accessGrantsToString(array $grants) {
$string = '';
foreach ($grants as $uid => $g) {
if (empty($g) === FALSE) {
$string .= ' # ' . $uid . ' (' . implode(',', $g) . ')';
}
}
return $string;
}
}
