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 ); } } |