foldershare-8.x-1.2/src/Entity/FolderShareTraits/OperationChangeOwnerTrait.php
src/Entity/FolderShareTraits/OperationChangeOwnerTrait.php
<?php
namespace Drupal\foldershare\Entity\FolderShareTraits;
use Drupal\user\Entity\User;
use Drupal\Component\Utility\Environment;
use Drupal\foldershare\ManageLog;
use Drupal\foldershare\Settings;
use Drupal\foldershare\Utilities\FormatUtilities;
use Drupal\foldershare\Utilities\LimitUtilities;
use Drupal\foldershare\FolderShareInterface;
use Drupal\foldershare\Entity\FolderShareScheduledTask;
use Drupal\foldershare\Entity\Exception\LockException;
use Drupal\foldershare\Entity\Exception\ValidationException;
/**
* Change FolderShare entity ownership.
*
* This trait includes get and set methods for FolderShare entity
* owner field.
*
* <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 OperationChangeOwnerTrait {
/*---------------------------------------------------------------------
*
* Change ownership.
*
*---------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function changeOwnerId(
int $uid,
bool $changeDescendants = FALSE) {
// ------------------------------------------------------------------
// This item can be:
// - A file or folder.
// - At the root level or in a subfolder.
// - Owned by the current user or another.
//
// Special cases:
// - If the item's owner ID is already correct, and recursion to change
// descendants is not requested, do nothing.
//
// Errors:
// - The user ID is illegal.
//
// Actions:
// - If item is a root: lock it, check if changing the owner ID will cause
// a name collision for the new user's root list, change the owner,
// disable (if a folder and will change descendants). If a folder and
// changing descendants, schedule task. The task recurses through all
// descendants and changes their owner IDs, then enables the item and
// unlocks it.
//
// - If item is not a root: lock it's root folder tree, change the owner,
// disable (if a folder and will change descendants). If a folder and
// changing descendants, schedule task. The task recurses through all
// descendants and changes their owner IDs, then enables the item and
// unlocks the root folder tree.
//
// ------------------------------------------------------------------.
//
// Validate.
// ---------
// The user ID must be positive.
if ($uid < 0) {
throw new ValidationException(FormatUtilities::createFormattedMessage(
t(
'@method was called with an invalid user ID.',
[
'@method' => __METHOD__,
])));
}
$user = User::load($uid);
if ($user === NULL) {
throw new ValidationException(FormatUtilities::createFormattedMessage(
t(
'@method was called with an invalid user ID.',
[
'@method' => __METHOD__,
])));
}
$requestingUid = self::getCurrentUserId()[0];
$userDisplayName = $user->getDisplayName();
unset($user);
$fromUid = $this->getOwnerId();
$rootId = $this->getRootItemId();
if ($fromUid === $uid && $changeDescendants === FALSE) {
// The owner ID is already correct and a descendant traversal is
// not asked for. Done.
return;
}
//
// Lock.
// -----
// Lock the item's root folder tree to block other edit operations from
// interfering with the change. This lock is held until all descendants
// (if any) have been updated.
//
// 1. LOCK ROOT FOLDER TREE.
if (self::acquireRootOperationLock($rootId) === FALSE) {
throw new LockException(
self::getStandardLockExceptionMessage(t('changed'), $this->getName()));
}
//
// Decide if a task will be needed.
// --------------------------------
// If the item being changed is:
// - A folder.
// - With children.
// - Change descendants is TRUE.
//
// Then we'll need to schedule a task.
$taskNeeded = ($this->isFolder() === TRUE &&
$changeDescendants === TRUE &&
$this->findNumberOfChildren() > 0);
//
// Change owner, checking names.
// -----------------------------
// For an item that is not at the root, just change the owner ID.
//
// For a root item, changing the owner ID moves it from the current
// root list to the new owner's root list. Check for name collisions first.
if ($fromUid !== $uid) {
if ($this->isRootItem() === FALSE) {
// Change owner ID and automatically save.
$this->setOwnerId($uid);
}
else {
// Lock the user's root list so that it doesn't change while we
// are looking at names.
//
// 2. LOCK NEW OWNER'S ROOT LIST.
if (self::acquireUserRootListLock($uid) === FALSE) {
// 1. UNLOCK ROOT FOLDER TREE.
self::releaseRootOperationLock($rootId);
throw new LockException(
self::getStandardLockExceptionMessage(t('changed'), $this->getName()));
}
if (empty(self::findAllRootItemIds($uid, $this->getName())) === FALSE) {
// The item's current name is already in use in the new user's
// root list. We cannot continue because two items cannot
// have the same name in the same root list. Since nothing has
// been changed so far, abort.
//
// 2. UNLOCK USER'S ROOT LIST.
self::releaseUserRootListLock();
// 1. UNLOCK ROOT FOLDER TREE.
self::releaseRootOperationLock($rootId);
throw new ValidationException(FormatUtilities::createFormattedMessage(
t(
'An item named "@name" already exists among top-level items for @user.',
[
'@name' => $this->getName(),
'@user' => $userDisplayName,
]),
t("Please rename the item before changing it's owner.")));
}
// Change owner ID. If the item is a folder and we'll be changing
// descendants, disable it while descendants are changed.
//
// Note that setOwnerId() does the save() within it.
$this->setSystemDisabled($taskNeeded);
$this->setOwnerId($uid);
// 2. UNLOCK NEW OWNER'S ROOT LIST.
self::releaseUserRootListLock($uid);
}
//
// Hook & log.
// -----------
// Announce the change.
self::postOperationHook(
'change_owner',
[
$this,
$fromUid,
$uid,
$requestingUid,
]);
if (ManageLog::isActivityLoggingEnabled() === TRUE) {
// Loading User entities can be expensive. Don't load them unless
// activity logging is definitely enabled.
$fromUser = User::load($fromUid);
$fromUserName = ($fromUser === NULL) ?
'Unknown' :
$fromUser->getDisplayName();
$toUser = User::load($uid);
$toUserName = ($toUser === NULL) ?
'Unknown' :
$toUser->getDisplayName();
ManageLog::activity(
"Changed owner of @kind '@name' (# @id) from %fromUserName (# @fromUserId) to %toUserName (# @toUserId).",
[
'@id' => $this->id(),
'@kind' => $this->getKind(),
'@name' => $this->getName(),
'@fromUserId' => $fromUid,
'%fromUserName' => $fromUserName,
'@toUserId' => $uid,
'%toUserName' => $toUserName,
'entity' => $this,
'uid' => $requestingUid,
]);
}
}
if ($taskNeeded === FALSE) {
// Don't change descendants. Done.
//
// 1. UNLOCK ROOT FOLDER TREE.
self::releaseRootOperationLock($rootId);
return;
}
//
// Update descendants.
// -------------------
// Finishing the move requires updating the root ID of all descendants.
//
// If we have time left before we need to respond to the user, start
// the work. Otherwise schedule a task to do the work in the future.
//
// Keep root folder tree locks:
//
// - The root folder containing the items being changed.
//
// This will be unlocked by a future task when the entire change is done.
$parameters = [
'updateIds' => [(int) $this->id()],
'ownerUid' => $uid,
'unlockRootId' => $rootId,
];
$started = time();
$comments = 'Start change owner';
$executionTime = 0;
if (LimitUtilities::aboveResponseExecutionTimeLimit() === FALSE) {
self::processTaskChangeOwner(
$requestingUid,
$parameters,
$started,
$comments,
$executionTime,
TRUE);
}
else {
FolderShareScheduledTask::createTask(
time() + Settings::getScheduledTaskInitialDelay(),
'\Drupal\foldershare\Entity\FolderShare::processTaskChangeOwner',
$requestingUid,
$parameters,
$started,
$comments,
$executionTime);
}
}
/**
* Changes the owner of multiple items, and optionally their descendants.
*
* For each item in the given list of IDs, the owner user ID is changed to
* the indicated user. If $changeDescendants is TRUE, the owner user ID of
* all descendants is changed as well. All items are saved.
*
* The user ID is not validated. It is presumed to be a valid entity ID
* for a User entity. It should not be negative.
*
* If an item is a root item, the item's name is checked for a collision
* with another root item with the same name in the new owner's root list.
* If there is a collision, an exception is thrown and the remainder of the
* ID list is not processed.
*
* System hidden and disabled items are also affected.
*
* <B>Background change ownership</B>
* When $changeDescendants is TRUE, this method schedules background tasks
* to traverse the folder tree and update descendants. This will delay
* completion of the change to a time in the future that depends upon
* the size of the folder tree being changed and server load.
*
* <B>Process locks</B>
* This method locks the item's root folder tree for exclusive use during
* the change. This will prevent any other edit operation from being
* performed on the same folder tree until the change completes. When
* background tasks are used, unlocking the root folder tree does not
* occur until the last descendant is changed.
*
* <B>Post-operation hooks</B>
* This method calls the "hook_foldershare_post_operation_change_owner" hook.
*
* <B>Activity log</B>
* This method posts a log message after the item is changed.
*
* @param int[] $ids
* A list of integer FolderShare entity IDs for items to change.
* @param int $uid
* The owner user ID for the new owner of the folder tree. The ID
* is not validated and is presumed to be that of a User entity.
* @param bool $changeDescendants
* (optional, default = FALSE) When FALSE, only this item's ownership
* is changed. When TRUE, the ownership of all of this item's descendants
* is updated as well.
*
* @throws \Drupal\foldershare\Entity\Exception\LockException
* Throws an exception if an access lock could not be acquired.
* @throws \Drupal\foldershare\Entity\Exception\ValidationException
* Throws an exception if an item is a root item and it's name is
* already in use in the root list of the new user.
*
* @see ::setOwnerId()
* @see ::changeOwnerId()
* @see \Drupal\user\EntityOwnerInterface::getOwner()
* @see \Drupal\user\EntityOwnerInterface::getOwnerId()
*/
public static function changeOwnerIdMultiple(
array $ids,
int $uid,
bool $changeDescendants = FALSE) {
// ------------------------------------------------------------------
// Each item can be:
// - A file or folder.
// - At the root level or in a subfolder.
// - Owned by the current user or another.
//
// Special cases:
// - If an item's owner ID is already correct, and recursion to change
// descendants is not requested, do nothing.
//
// Errors:
// - The user ID is illegal.
//
// Actions:
// - All items are sorted into groups with a shared root.
//
// - For all root items: Redirect to changeOwnerId() which will lock,
// change, and possibly schedule a task per item.
//
// - For each shared root group: lock the shared root, change the owner
// of each item in the group, disable (if a folder and will change
// descendants). For folders when changing descendants, schedule task.
// The task recurses through all descendants and changes their owner
// IDs, then enables the item and unlocks the root folder tree.
//
// ------------------------------------------------------------------.
//
// Validate.
// ---------
// The user ID must be positive.
if (empty($ids) === TRUE) {
// Nothing to do.
return;
}
if ($uid < 0) {
throw new ValidationException(FormatUtilities::createFormattedMessage(
t(
'@method was called with an invalid user ID.',
[
'@method' => __METHOD__,
])));
}
$user = User::load($uid);
if ($user === NULL) {
throw new ValidationException(FormatUtilities::createFormattedMessage(
t(
'@method was called with an invalid user ID.',
[
'@method' => __METHOD__,
])));
}
unset($user);
$requestingUid = self::getCurrentUserId()[0];
//
// Group IDs by root.
// ------------------
// The IDs given could be all root items, or all children of a folder
// under the same root. While they are not supposed to be from a mix
// of different folders, we'll handle that too.
//
// Sift out items that ARE roots.
$rootGroups = [];
$roots = [];
foreach ($ids as $id) {
$item = self::load($id);
if ($item === NULL) {
// The item does not exist.
continue;
}
if ($item->isRootItem() === TRUE) {
$roots[] = $item;
}
else {
$rootId = $item->getRootItemId();
$rootGroups[$rootId][] = $item;
}
}
//
// Change root items.
// ------------------
// Root items can be safely changed by changeOwnerId(), which locks each
// item's root folder tree while changing it and its descendants.
//
// This may throw an exception on any root that cannot be changed.
foreach ($roots as $index => $rootItem) {
$rootItem->changeOwnerId($uid, $changeDescendants);
$roots[$index] = NULL;
unset($rootItem);
}
unset($roots);
if (empty($rootGroups) === TRUE) {
// Nothing to do.
return;
}
//
// Loop over root groups.
// ----------------------
// Each root group requires its own root folder lock, followed by changing
// the items and their descendants.
//
// This follows a pattern similar to changeOwnerId(), except that we've
// already handled root items. See its comments for details.
$nRootGroups = count($rootGroups);
foreach ($rootGroups as $rootId => $changeItems) {
//
// Lock.
// -----
// Lock the item's root folder tree to block other edit operations from
// interfering with the change. This lock is held until all descendants
// (if any) have been updated.
//
// LOCK ROOT FOLDER TREE.
if (self::acquireRootOperationLock($rootId) === FALSE) {
throw new LockException(
self::getStandardLockExceptionMessage(t('changed'), NULL));
}
$rootGroups[$rootId] = NULL;
$updateIds = [];
foreach ($changeItems as $changeIndex => $item) {
if ($item->isFolder() === TRUE && $changeDescendants === TRUE) {
$updateIds[] = (int) $item->id();
}
$changeItems[$changeIndex] = NULL;
$fromUid = $item->getOwnerId();
if ($fromUid === $uid) {
// Item already has the correct owner ID. It still needs to
// be disabled if it is a folder with descendants that need
// to be changed.
if ($item->isFolder() === TRUE && $changeDescendants === TRUE) {
$item->setSystemDisabled(TRUE);
$item->save();
}
unset($item);
continue;
}
// Change owner ID and disable folders if we are going to change
// their descendants.
//
// Note that setOwnerId() does the save() within it.
$item->setSystemDisabled(
$item->isFolder() === TRUE && $changeDescendants === TRUE);
$item->setOwnerId($uid);
//
// Hook & log.
// -----------
// Announce the change.
self::postOperationHook(
'change_owner',
[
$item,
$fromUid,
$uid,
$requestingUid,
]);
if (ManageLog::isActivityLoggingEnabled() === TRUE) {
// Loading User entities can be expensive. Don't load them unless
// activity logging is definitely enabled.
$fromUser = User::load($fromUid);
$fromUserName = ($fromUser === NULL) ?
'Unknown' :
$fromUser->getDisplayName();
$toUser = User::load($uid);
$toUserName = ($toUser === NULL) ?
'Unknown' :
$toUser->getDisplayName();
ManageLog::activity(
"Changed owner of @kind '@name' (# @id) from %fromUserName (# @fromUserId) to %toUserName (# @toUserId).",
[
'@id' => $item->id(),
'@kind' => $item->getKind(),
'@name' => $item->getName(),
'@fromUserId' => $fromUid,
'%fromUserName' => $fromUserName,
'@toUserId' => $uid,
'%toUserName' => $toUserName,
'entity' => $item,
'uid' => $requestingUid,
]);
}
unset($item);
}
unset($changeItems);
if ($changeDescendants === FALSE) {
// Don't change descendants. Done.
//
// UNLOCK ROOT FOLDER TREE.
self::releaseRootOperationLock($rootId);
return;
}
if (empty($updateIds) === TRUE) {
// Nothing to change.
//
// UNLOCK ROOT FOLDER TREE.
self::releaseRootOperationLock($rootId);
return;
}
//
// Update descendants.
// -------------------
// Change the owner ID of all descendants.
//
// If we have one root group only and we have time left before we
// need to respond to the user, start the work. Otherwise schedule
// a task to do the work in the future.
//
// Keep root folder tree locks:
//
// - The root folder containing the items being changed.
//
// This will be unlocked by a future task when the entire change is done.
$parameters = [
'updateIds' => $updateIds,
'ownerUid' => $uid,
'unlockRootId' => $rootId,
];
$started = time();
$comments = 'Start change owner';
$executionTime = 0;
if ($nRootGroups === 1 &&
LimitUtilities::aboveResponseExecutionTimeLimit() === FALSE) {
self::processTaskChangeOwner(
$requestingUid,
$parameters,
$started,
$comments,
$executionTime,
TRUE);
}
else {
FolderShareScheduledTask::createTask(
time() + Settings::getScheduledTaskInitialDelay(),
'\Drupal\foldershare\Entity\FolderShare::processTaskChangeOwner',
$requestingUid,
$parameters,
$started,
$comments,
$executionTime);
}
}
}
/**
* Changes the ownership for all items owned by a user.
*
* All items owned by the user are found and their ownership changed to
* the indicated new user, then saved.
*
* 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,
* and/or no users should be accessing the items being changed.
*
* @param int $currentUid
* The user ID of the owner of current files and folders that are
* to be changed. In addition to valid user IDs, this can be the wildcard
* FolderShareInterface::ANY_USER_ID to match all users.
* @param int $newUid
* The user ID of the new owner of the files and folders. This must be
* a valid non-negative user ID. FolderShareInterface:ANY_USER_ID is NOT
* allowed.
*
* @see ::setOwnerId()
* @see ::changeOwnerId()
*/
public static function changeAllOwnerIdByUser(int $currentUid, int $newUid) {
// ------------------------------------------------------------------
// Special cases:
// - If the $currentUid is ANY_USER_ID, then everything is changed to
// the new user.
//
// Actions:
// - A list of all root items owned by the user (or by all users) is
// queried and one by one passed to changeOwnerId().
//
// - A list of all non-root items in root folder trees not owned by
// the user is queried and passed to changeOwnerIdMultiple().
// ------------------------------------------------------------------.
//
// Validate.
// ---------
// The user ID to changed to must be valid.
if ($newUid < 0) {
throw new ValidationException(FormatUtilities::createFormattedMessage(
t(
'@method was called with an invalid new user ID.',
[
'@method' => __METHOD__,
])));
}
//
// 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);
//
// Change ownership.
// -----------------
// Get a list of all roots.
//
// If the current user ID is ANY_USER_ID, then recursively change
// everything under each root.
//
// Otherwise look for items under each root that are owned by the
// indicated user, and change only them.
$rootIds = self::findAllRootItemIds(FolderShareInterface::ANY_USER_ID);
if (empty($rootIds) === TRUE) {
// There are no root folder trees! Nothing in the file system so
// nothing to change.
return;
}
foreach ($rootIds as $rootId) {
$item = self::load($rootId);
if ($item === NULL) {
// The item does not exist.
continue;
}
if ($currentUid === FolderShareInterface::ANY_USER_ID) {
// Recursively change everything from this root downwards.
$item->changeOwnerId($newUid, TRUE);
}
else {
// If this root is owned by the indicated user, change it first,
// without recursing.
if ($item->getOwnerId() === $currentUid) {
$item->changeOwnerId($newUid, FALSE);
}
// Find all descendants that are owned by the indicated current user.
// Change them without recursing.
$descendantIds = $item->findDescendantIdsByOwnerId($currentUid);
if (empty($descendantIds) === FALSE) {
self::changeOwnerIdMultiple($descendantIds, $newUid, FALSE);
}
unset($descendantIds);
}
unset($item);
// Garbage collect.
gc_collect_cycles();
}
}
/*---------------------------------------------------------------------
*
* Background task handling.
*
*---------------------------------------------------------------------*/
/**
* Processes a change ownership task from the work queue.
*
* <B>This method is internal and strictly for use by the FolderShare
* module itself.</B> This method is public so that it can be called
* from the module's work queue handler.
*
* A change ownership task provides a list of IDs for entities to change,
* and their new owner ID. The change is NOT recursive - only the specific
* IDs provided are changed. Item IDs MUST NOT be for root items, which
* may have name collisions that need to be checked before queueing this
* task (see changeOwnerId()).
*
* There are two conditions under which copying may not complete fully:
* - One or more entities are locked by another process.
* - The ID list is too large to process before hitting a timeout.
*
* If an entity is locked, this method will re-queue the locked item to
* be changed later.
*
* If the ID list is too large to finish before the process is interrupted
* by a PHP or web server timeout, then the queued task that called this
* method will be restarted by CRON at a later time. A repeat of the task
* will skip entities that have already had their ownership changed.
*
* <B>Process locks</B>
* This method releases the root folder tree lock acquired when the task
* was started.
*
* <B>Post-operation hooks</B>
* This method calls the "hook_foldershare_post_operation_change_owner" hook
* for each changed item.
*
* <B>Activity log</B>
* This method posts a log message after each item is changed.
*
* @param int $requestingUid
* The user ID of the user that requested the delete. This is ignored.
* @param array $parameters
* The queued task's parameters. This is an associative array with keys:
* - 'updateIds': the IDs of entities to recurse downwards from and
* set their owner UIDs.
* - 'ownerUid': the new owner's UID.
* - 'unlockRootId': the ID of the root to unlock upon completion.
* @param int $started
* The timestamp of the start date & time for an operation that causes
* a chain of tasks.
* @param string $comments
* A comment on the current task.
* @param int $executionTime
* The accumulated total execution time of the task chain, in seconds.
* @param bool $interactive
* (optional, default = FALSE) When TRUE, this task is executing in a
* direct response to a user request that is still in progress, and it
* should therefore return fairly quickly. When FALSE, this task is
* executing as a background task and it can take longer without
* impacting interactivity.
*
* @see ::setOwnerId()
* @see ::changeOwnerId()
* @see ::changeOwnerIdMultiple()
*/
public static function processTaskChangeOwner(
int $requestingUid,
array $parameters,
int $started,
string $comments,
int $executionTime,
bool $interactive = FALSE) {
//
// Validate.
// ---------
// The parameters array must contain a list of entity IDs, a new
// root ID, and the owner UID to change items to.
if (isset($parameters['updateIds']) === FALSE ||
is_array($parameters['updateIds']) === FALSE) {
ManageLog::missingTaskParameter(__METHOD__, 'updateIds');
return;
}
if (isset($parameters['unlockRootId']) === FALSE) {
ManageLog::missingTaskParameter(__METHOD__, 'unlockRootId');
return;
}
if (isset($parameters['ownerUid']) === FALSE) {
ManageLog::missingTaskParameter(__METHOD__, 'ownerUid');
return;
}
$updateIds = $parameters['updateIds'];
$unlockRootId = (int) $parameters['unlockRootId'];
$ownerUid = (int) $parameters['ownerUid'];
//
// Reschedule full task.
// ---------------------
// As a safety net, reschedule the entire task immediately. This insures
// that if we get a PHP or web server timeout that interrupts the task,
// it will be run again to try and complete it in the near future.
$safetyNetTask = FolderShareScheduledTask::createTask(
time() + Settings::getScheduledTaskSafetyNetDelay(),
'\Drupal\foldershare\Entity\FolderShare::processTaskChangeOwner',
$requestingUid,
[
'updateIds' => $updateIds,
'ownerUid' => $ownerUid,
'unlockRootId' => $unlockRootId,
],
$started,
'Safety-net requeue',
$executionTime);
//
// Prepare.
// --------
// Garbage collect and initialize.
$beginTime = time();
$opCounter = 0;
$changeCounter = 0;
gc_collect_cycles();
//
// Set descendants to new owner ID.
// --------------------------------
// Loop over all descendants that DO NOT have the correct owner ID
// already and update their owner IDs.
//
// For a huge folder tree, this may be interrupted by a PHP or web
// server timeout. The safety net task scheduled above will try again.
// Since the queries only return items that have not been changed yet,
// over repeated runs of this task the queries will return less and
// less. Eventually there will be no more work to do.
foreach ($updateIds as $updateIndex => $id) {
$item = self::load($id);
if ($item === NULL) {
// Item does not exist.
continue;
}
// Increment to count load.
++$opCounter;
// Find all descendants that are NOT owned by the indicated owner.
// Include disabled and hidden items.
$descendantIds = $item->findDescendantIdsByOwnerId($ownerUid, FALSE);
// Loop over these. Load each one, change it, call the hook, and post
// a log message.
foreach ($descendantIds as $descendantId) {
$descendant = self::load($descendantId);
if ($descendant === NULL) {
// Descendant does not exist.
continue;
}
// Increment to count load.
++$opCounter;
$fromUid = $descendant->getOwnerId();
if ($fromUid !== $ownerUid) {
// Set (save is done automatically).
$descendant->setOwnerId($ownerUid);
// Increment to count save.
++$opCounter;
++$changeCounter;
// Hook & log.
self::postOperationHook(
'change_owner',
[
$descendant,
$fromUid,
$ownerUid,
$requestingUid,
]);
if (ManageLog::isActivityLoggingEnabled() === TRUE) {
// Loading User entities can be expensive. Don't load them unless
// activity logging is definitely enabled.
$fromUser = User::load($fromUid);
$fromUserName = ($fromUser === NULL) ?
'Unknown' :
$fromUser->getDisplayName();
unset($fromUser);
$toUser = User::load($ownerUid);
$toUserName = ($toUser === NULL) ?
'Unknown' :
$toUser->getDisplayName();
unset($toUser);
ManageLog::activity(
"Changed owner of @kind '@name' (# @id) from %fromUserName (# @fromUserId) to %toUserName (# @toUserId).",
[
'@id' => $descendantId,
'@kind' => $descendant->getKind(),
'@name' => $descendant->getName(),
'@fromUserId' => $fromUid,
'%fromUserName' => $fromUserName,
'@toUserId' => $ownerUid,
'%toUserName' => $toUserName,
'entity' => $descendant,
'uid' => $requestingUid,
]);
}
}
unset($descendant);
if ($opCounter >= self::USAGE_CHECK_INTERVAL) {
$reschedule = FALSE;
if (($interactive === TRUE &&
LimitUtilities::aboveResponseExecutionTimeLimit() === TRUE) ||
LimitUtilities::aboveExecutionTimeLimit() === TRUE) {
$reschedule = TRUE;
$reason = 'time limit';
}
elseif (LimitUtilities::aboveMemoryUseLimit() === TRUE) {
$reschedule = TRUE;
$reason = 'memory use limit';
}
if ($reschedule === TRUE) {
// An execution time or memory limit has been exceeded.
//
// This is our chance to gracefully handle a condition where
// the execution time or memory use is reaching its configured
// limits. If we do nothing, we will hit that limit and the
// process will crash with a nasty message. The safety net task
// will remain and be serviced by the next process and continue
// the operation. But that nasty crash message will look bad
// and worry admins. It could also have interrupted something
// and left content in a corrupted state.
//
// Instead, when we near a limit, gracefully stop what we are
// doing and return. We'll schedule a continuation task that
// will be serviced by the next process and continue the operation.
//
// DO NOT release the task's root lock since we aren't done yet.
//
// Schedule continuation task. Execution has already unset
// entries in the $updateIds list as they were finished.
FolderShareScheduledTask::createTask(
time() + Settings::getScheduledTaskContinuationDelay(),
'\Drupal\foldershare\Entity\FolderShare::processTaskChangeOwner',
$requestingUid,
[
'updateIds' => $updateIds,
'ownerUid' => $ownerUid,
'unlockRootId' => $unlockRootId,
],
$started,
"Continuation due to $reason after $opCounter ops and $changeCounter changes",
$executionTime + (time() - $beginTime));
// Delete the safety net task.
FolderShareScheduledTask::deleteTask($safetyNetTask);
return;
}
$opCounter = 0;
}
}
$item->setSystemDisabled(FALSE);
$item->save();
// Increment to count save.
++$opCounter;
unset($item);
unset($descendantIds);
unset($updateIds[$updateIndex]);
// Garbage collect.
gc_collect_cycles();
}
unset($updateIds);
// UNLOCK ROOT FOLDER TREE.
self::releaseRootOperationLock($unlockRootId);
// Delete the safety net task.
FolderShareScheduledTask::deleteTask($safetyNetTask);
}
}
