foldershare-8.x-1.2/src/Entity/FolderShareTraits/GetSetNameTrait.php

src/Entity/FolderShareTraits/GetSetNameTrait.php
<?php

namespace Drupal\foldershare\Entity\FolderShareTraits;

use Drupal\foldershare\ManageFilenameExtensions;
use Drupal\foldershare\Utilities\FormatUtilities;
use Drupal\foldershare\Entity\Exception\ValidationException;

/**
 * Get/set FolderShare entity name field.
 *
 * This trait includes get and set methods for FolderShare entity name field,
 * along with utility functions that check if a name is unique.
 *
 * <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 GetSetNameTrait {

  /*---------------------------------------------------------------------
   *
   * Name field.
   *
   *---------------------------------------------------------------------*/

  /**
   * {@inheritdoc}
   */
  public function getName() {
    return $this->get('name')->value;
  }

  /**
   * Sets the item's name.
   *
   * <B>This method is internal and strictly for use by the FolderShare
   * module itself.</B>
   *
   * The name is set without validation. The caller should insure that the
   * name is not empty, not too long, does not include illegal characters,
   * and does not use file name extensions that are not allowed by the site.
   *
   * The caller must call save() for the change to take effect.
   *
   * <B>Process locks</B>
   * This method does not lock access. The caller should lock around changes
   * to the entity.
   *
   * @param string $name
   *   The new name of the item. The name is not validated but is expected
   *   to be of legal content and length and not to collide with any other
   *   name in the item's parent folder or root list.
   *
   * @see ::getName()
   * @see ::rename()
   * @see ::isNameLegal()
   */
  private function setName(string $name) {
    $this->set('name', $name);
  }

  /*---------------------------------------------------------------------
   *
   * Name legality.
   *
   *---------------------------------------------------------------------*/

  /**
   * Returns TRUE if the proposed name is legal.
   *
   * A name is legal if:
   * - It is not empty.
   * - It has 255 characters or less.
   * - It does not contain reserved characters ':', '/', and '\'.
   * - It does not contain control characters (0x00-0x1F and 0x7F).
   * - It is not entirely white space characters.
   *
   * This function does not check if the name is using any file name
   * extensions that are not allowed by the site.
   *
   * @param string $name
   *   The proposed name.
   *
   * @return bool
   *   Returns TRUE if the name is legal, and FALSE otherwise.
   *
   * @see ::MAX_NAME_LENGTH
   * @see ::checkName()
   */
  public static function isNameLegal(string $name) {
    // Note: Must use multi-byte functions to insure UTF-8 support.
    return (empty($name) === FALSE) &&
      (mb_strlen($name) <= self::MAX_NAME_LENGTH) &&
      (mb_ereg('([:\/\\\]|[\x00-\x1F\x7F])', $name) === FALSE) &&
      (mb_ereg('^\s+$', $name) === FALSE);
  }

  /*---------------------------------------------------------------------
   *
   * Name uniqueness checking.
   *
   *---------------------------------------------------------------------*/

  /**
   * {@inheritdoc}
   */
  public function isNameUnique(string $name, int $inUseId = self::ANY_ITEM_ID) {
    // Get an array of all child file and folder names. The array has
    // names as keys and IDs as values.
    //
    // Disabled children are included. Hidden children are not.
    $childNames = $this->findChildrenNames(TRUE, FALSE);

    // If the proposed name is not in the list, return TRUE.
    if (isset($childNames[$name]) === FALSE) {
      return TRUE;
    }

    // If we have an exclusion ID and that's the child name list
    // entry that matches the given name, return TRUE anyway because
    // the name is in use by the intended ID.
    if ($inUseId >= 0 && $childNames[$name] === $inUseId) {
      return TRUE;
    }

    // Otherwise the name is already in use, and not by the exclusion ID.
    return FALSE;
  }

  /**
   * Returns TRUE if a proposed name is unique among root items.
   *
   * The $name argument specifies a proposed name for an existing or new
   * root item. The name is not validated and is presumed to be
   * of legal length and structure.
   *
   * The optional $inUseId indicates the ID of an existing root item that may
   * be already using the name. If the value is not given, negative, or
   * FolderShareInterface::ANY_ITEM_ID, then it is presumed that no current
   * root item has the proposed name.
   *
   * The optional $uid selects the user ID for whome root items are checked.
   * If this value is not given or it is FolderShareInterface::ANY_ITEM_ID,
   * the root items for the current user are checked.
   *
   * This function looks through the names of root items and returns TRUE
   * if the proposed name is not in use by any root item, except the indicated
   * $inUseId, if any. If the name is in use by a root item that is not
   * $inUseId, then FALSE is returned.
   *
   * @param string $name
   *   A proposed root item name.
   * @param int $inUseId
   *   (optional, default = FolderShareInterface::ANY_ITEM_ID) The ID of an
   *   existing item that may be already using the proposed name.
   * @param int $uid
   *   (optional, default = FolderShareInterface::CURRENT_USER_ID) The user ID
   *   of the user among whose root items the name must be unique. If the
   *   value is negative or FolderShareInterface::CURRENT_USER_ID, the
   *   current user ID is used.
   *
   * @return bool
   *   Returns TRUE if the name is unique among this user's root items,
   *   and FALSE otherwise.
   *
   * @see ::findAllRootItemNames()
   * @see ::getName()
   * @see ::isNameLegal()
   * @see ::isNameUnique()
   * @see ::createUniqueName()
   */
  public static function isRootNameUnique(
    string $name,
    int $inUseId = self::ANY_ITEM_ID,
    int $uid = self::CURRENT_USER_ID) {

    // Get an array of all root item names for this user.
    // The array has names as keys and IDs as values.
    //
    // Disabled root items are included. Hidden root items are not.
    if ($uid < 0) {
      $uid = (int) \Drupal::currentUser()->id();
    }
    $rootNames = self::findAllRootItemNames($uid, TRUE, FALSE);

    // If the proposed name is not in the list, return TRUE.
    if (isset($rootNames[$name]) === FALSE) {
      return TRUE;
    }

    // If we have an exclusion ID and that's the root name list
    // entry that matches the given name, return TRUE anyway because
    // the name is in use by the intended ID.
    if ($inUseId >= 0 && $rootNames[$name] === $inUseId) {
      return TRUE;
    }

    // Otherwise the name is already in use, and not by the exclusion ID.
    return FALSE;
  }

  /*---------------------------------------------------------------------
   *
   * Unique name creation.
   *
   *---------------------------------------------------------------------*/

  /**
   * Returns a name, or variant, adjusted to insure uniqueness.
   *
   * The $namesInUse argument provides a list of names already in use,
   * such as the names of root items or children in a folder. The new
   * unique name will avoid all of these names.
   *
   * The $name argument provides the proposed new name. If the name is
   * not in the $namesInUse, then it is already unique and is returned.
   * Otherwise, the $name is modified to make it unique, and then returned.
   *
   * To make the name unique, this function adds an optional $suffix and
   * a number, incrementing the number until the name is unique. If necessary,
   * the name is truncated in order to stay under the maximum name length
   * limit.
   *
   * A typical suffix might be " copy" (note the leading space to separate
   * it from the body of the name).
   *
   * The suffix and numbers are added immediately before the last dot in
   * the name. If there are no dots in the name, the suffix and numbers are
   * added to the end of the name.
   *
   * False is returned under several conditions:
   * - The given name is empty.
   *
   * - The suffix is >= 255 characters, leaving no room for the name
   *   before it.
   *
   * - The suffix + number is >= 255 characters, which leaves no
   *   room for the name before it.
   *
   * - No unique name could be found after running through
   *   all possible name + suffix + number + extension results for
   *   increasing numbers.  However, this is extrordinarily unlikely
   *   since it would require a huge names-in-use list that would probably
   *   exceed the memory limits of a PHP instance, or the time allotted
   *   to the instance.
   *
   * <B>Example usage</B>
   * For name "myfile.png" and suffix " copy", the names
   * tested (in order) are:
   * - "myfile.png"
   * - "myfile copy.png"
   * - "myfile copy 1.png"
   * - "myfile copy 2.png"
   * - "myfile copy 3.png"
   * - ...
   *
   * For name "myfolder" and suffix " archive", the names tested (in order)
   * are:
   * - "myfolder"
   * - "myfolder archive"
   * - "myfolder archive 1"
   * - "myfolder archive 2"
   * - "myfolder archive 3"
   * - ...
   *
   * Create a unique name among root items for the current user:
   * @code
   * $uid = \Drupal::currentUser()->id();
   * $names = FolderShare::findAllRootItemNames($uid);
   * $uniqueName = FolderShare::createUniqueName($names, $name);
   * @endcode
   *
   * @param array $namesInUse
   *   An associative array of names to in use, where keys are names and values
   *   are entity IDs. Such a name list is returned by findChildreNames() and
   *   findAllRootItemNames().
   * @param string $name
   *   A proposed name.
   * @param string $suffix
   *   (optional, default = '') A suffix to add during tries to find a
   *   new unique name that doesn't collide with any of the exclusion names.
   *
   * @return false|string
   *   Returns a unique name that starts with the given name and
   *   may include the given suffix and a number such that it does not
   *   collide with any of the names in $namesInUse.  FALSE is returned
   *   on failure.
   *
   * @see ::isNameLegal()
   * @see ::isNameUnique()
   * @see ::isRootNameUnique()
   * @see ::findChildrenNames()
   * @see ::findAllRootItemNames()
   * @see ::MAX_NAME_LENGTH
   */
  public static function createUniqueName(
    array $namesInUse,
    string $name,
    string $suffix = '') {

    // Validate.
    // ---------
    // If no name, then fail.
    if (empty($name) === TRUE) {
      return FALSE;
    }

    //
    // Check for unmodified name.
    // --------------------------
    // If name is not in use, then allow it.
    if (isset($namesInUse[$name]) === FALSE) {
      return $name;
    }

    //
    // Setup for renaming.
    // -------------------
    // Break down the name into a base name before the LAST '.',
    // and the extension after the LAST '.'.  There may be no
    // extension if there is no '.'.
    //
    // Note:  We must use multi-byte functions to support the
    // multi-byte characters of UTF-8 names.
    $lastDotIndex = mb_strrpos($name, '.');
    if ($lastDotIndex === FALSE) {
      // No '.' found. Base is entire string. Extension is empty.
      $base = $name;
      $ext = '';
    }
    else {
      // Found '.'. Base is everything up to the '.'. Extension
      // is everything after it.
      $base = mb_substr($name, 0, $lastDotIndex);
      $ext = mb_substr($name, $lastDotIndex);
    }

    if ($suffix === NULL) {
      $suffix = '';
    }

    if (mb_strlen($suffix . $ext) >= self::MAX_NAME_LENGTH) {
      // The suffix and/or extension are huge. They leave no
      // room for the base name within the character budget.
      // There is no name modification we can do that will produce
      // a short enough name.
      return FALSE;
    }

    //
    // Check for name + suffix + extension.
    // ------------------------------------
    // If there is a suffix, check that there's room to add it.
    // Then add it and see if that is sufficient to create a
    // unique name.
    if (empty($suffix) === FALSE) {
      $name = $base . $suffix . $ext;

      if (mb_strlen($name) > self::MAX_NAME_LENGTH) {
        // The built name is too long.  Crop the base name.
        $len = (self::MAX_NAME_LENGTH - mb_strlen($name));
        $base = mb_substr($base, 0, $len);
        $name = $base . $suffix . $ext;
      }

      if (isset($namesInUse[$name]) === FALSE) {
        return $name;
      }
    }

    //
    // Check for name + suffix + number + extension.
    // ---------------------------------------------
    // Otherwise, start adding a number, counting up from 1.
    // This search continues indefinitely until a number is found
    // that is not in use.
    $num = 1;

    // Intentional infinite loop.
    while (TRUE) {
      $name = $base . $suffix . ' ' . $num . $ext;

      if (mb_strlen($name) > self::MAX_NAME_LENGTH) {
        // The built name is too long.  Crop the base name.
        $len = (self::MAX_NAME_LENGTH - mb_strlen($name));
        if ($len <= 0) {
          break;
        }

        $base = mb_substr($base, 0, $len);
        $name = $base . $suffix . ' ' . $num . $ext;
      }

      if (isset($namesInUse[$name]) === FALSE) {
        return $name;
      }

      ++$num;
    }

    // One of two errors has occurred:
    // - Every possible name has been generated and found in use.
    //
    // - The suffix + number + extension for a large number has
    //   consumed the entire character budget for names.  There is
    //   no room left for even one character of the original name.
    //
    // These are both very unlikely errors. They require either a
    // huge set of names already in use, or an unreasonably large
    // suffix and extension that left very little room to search for
    // a new name.
    return FALSE;
  }

  /*---------------------------------------------------------------------
   *
   * Name check.
   *
   *---------------------------------------------------------------------*/

  /**
   * Checks if a proposed new name for this item is legal and usable.
   *
   * <B>This method is internal and strictly for use by the FolderShare
   * module itself.</B>
   *
   * The proposed name is checked that it is of legal length and content,
   * and that it does not use any file name extensions restricted on this
   * site (if this item is a file or image). On failure, an exception is
   * thrown with a standard error message.
   *
   * This is a convenience function used during move and rename operations.
   *
   * @param string $newName
   *   The proposed new name for this item.
   *
   * @throws \Drupal\foldershare\Entity\Exception\ValidationException
   *   Throws an exception if the name uses illegal characters, or if the
   *   name uses a file name extension that is not allowed (for file and
   *   image kinds only).
   *
   * @see ::isNameLegal()
   * @see ManageFilenameExtensions::isNameExtensionAllowed()
   */
  public function checkName(string $newName) {
    // Check that the new name is legal.
    if (self::isNameLegal($newName) === FALSE) {
      throw new ValidationException(
        self::getStandardIllegalNameExceptionMessage($newName));
    }

    // If this item is a file or image, verify that the name meets any
    // file name extension restrictions.
    if ($this->isFile() === TRUE || $this->isImage() === TRUE) {
      if (ManageFilenameExtensions::isNameExtensionAllowed($newName, NULL) === FALSE) {
        throw new ValidationException(FormatUtilities::createFormattedMessage(
          t(
            'The file type used by "@name" is not supported.',
            [
              '@name' => $newName,
            ]),
          t(
            'The file name uses a file type extension "@extension" that is not supported on this site.',
            [
              '@extension' => ManageFilenameExtensions::getExtensionFromPath($newName),
            ]),
          t('Supported file type extensions:'),
          ManageFilenameExtensions::getAllowedNameExtensions()));
      }
    }
  }

  /*---------------------------------------------------------------------
   *
   * Standard error messages.
   *
   *---------------------------------------------------------------------*/

  /**
   * Returns a standard illegal name exception message.
   *
   * This method provides a generic exception message that may be used by
   * operations that encounter illegal names.
   *
   * @param string $itemName
   *   The name that is illegal.
   *
   * @return \Drupal\Core\Render\Markup
   *   Returns a markup object containing a formatted standard
   *   exception message.
   */
  public static function getStandardIllegalNameExceptionMessage(
    string $itemName) {

    if (empty($itemName) === TRUE) {
      return FormatUtilities::createFormattedMessage(
        t('An empty name cannot be used.'),
        t('Names must include at least one character, number, or punctuation mark.'));
    }

    if (mb_strlen($itemName) > self::MAX_NAME_LENGTH) {
      return FormatUtilities::createFormattedMessage(
        t('The name is too long.'),
        t('Try using a name with fewer characters.'));
    }

    if (mb_ereg('^\s+$', $itemName) !== FALSE) {
      return FormatUtilities::createFormattedMessage(
        t('The name cannot be used.'),
        t('Names must include at least one letter, number, or punctuation character.'));
    }

    return FormatUtilities::createFormattedMessage(
      t(
        'The name "@name" cannot be used.',
        [
          '@name' => $itemName,
        ]),
      t('Avoid non-printing characters and punctuation marks like ":", "/", and "\\".'));
  }

  /**
   * Returns a standard name-in-use exception message.
   *
   * This method provides a generic exception message that may be used by
   * operations that encounter name collisions.
   *
   * @param string $itemName
   *   The name that is in use.
   *
   * @return \Drupal\Core\Render\Markup
   *   Returns a markup object containing a formatted standard
   *   exception message.
   */
  public static function getStandardNameInUseExceptionMessage(
    string $itemName) {

    return FormatUtilities::createFormattedMessage(
      t(
        'An item named "@name" already exists in this location.',
        [
          '@name' => $itemName,
        ]),
      t('Please choose a different name.'));
  }

  /**
   * Returns a standard rename-first exception message.
   *
   * This method provides a generic exception message that may be used by
   * operations that encounter name collisions and require a rename first.
   *
   * @param string $itemName
   *   The name that is in use.
   *
   * @return \Drupal\Core\Render\Markup
   *   Returns a markup object containing a formatted standard
   *   exception message.
   */
  public static function getStandardRenameFirstExceptionMessage(
    string $itemName) {

    if (empty($itemName) === FALSE) {
      return FormatUtilities::createFormattedMessage(
        t(
          'An item named "@name" already exists in this location.',
          [
            '@name' => $itemName,
          ]),
        t('Please rename the item first.'));
    }

    return FormatUtilities::createFormattedMessage(
      t('One or more items have names that are already in use by other items in this location.'),
      t('Please rename them first.'));
  }

  /**
   * Returns a standard cannot-create-unique-name exception message.
   *
   * This method provides an exception message that reports the
   * highly-unlikely case where a unique name could not be created for
   * an item in a folder or root list.
   *
   * @param string $itemType
   *   (optional, default = 'item') The type of item being created, such as
   *   'copy' or 'duplicate'.
   *
   * @return \Drupal\Core\Render\Markup
   *   Returns a markup object containing a formatted standard
   *   exception message.
   */
  public static function getStandardCannotCreateUniqueNameExceptionMessage(
    string $itemType = 'item') {
    return FormatUtilities::createFormattedMessage(t(
      'A unique name could not be created for a @itemType because too many names are in use.',
      [
        '@itemType' => $itemType,
      ]));
  }

}

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

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