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

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc