Initial Drupal 11 with DDEV setup
This commit is contained in:
		
							
								
								
									
										152
									
								
								web/core/lib/Drupal/Component/Utility/ArgumentsResolver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								web/core/lib/Drupal/Component/Utility/ArgumentsResolver.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,152 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Resolves the arguments to pass to a callable.
 | 
			
		||||
 */
 | 
			
		||||
class ArgumentsResolver implements ArgumentsResolverInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An associative array of parameter names to scalar candidate values.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $scalars;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An associative array of parameter names to object candidate values.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $objects;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An array object candidates tried on every parameter regardless of name.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $wildcards;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new ArgumentsResolver.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $scalars
 | 
			
		||||
   *   An associative array of parameter names to scalar candidate values.
 | 
			
		||||
   * @param object[] $objects
 | 
			
		||||
   *   An associative array of parameter names to object candidate values.
 | 
			
		||||
   * @param object[] $wildcards
 | 
			
		||||
   *   An array object candidates tried on every parameter regardless of its
 | 
			
		||||
   *   name.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $scalars, array $objects, array $wildcards) {
 | 
			
		||||
    $this->scalars = $scalars;
 | 
			
		||||
    $this->objects = $objects;
 | 
			
		||||
    $this->wildcards = $wildcards;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getArguments(callable $callable) {
 | 
			
		||||
    $arguments = [];
 | 
			
		||||
    foreach ($this->getReflector($callable)->getParameters() as $parameter) {
 | 
			
		||||
      $arguments[] = $this->getArgument($parameter);
 | 
			
		||||
    }
 | 
			
		||||
    return $arguments;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the argument value for a parameter.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \ReflectionParameter $parameter
 | 
			
		||||
   *   The parameter of a callable to get the value for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed
 | 
			
		||||
   *   The value of the requested parameter value.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \RuntimeException
 | 
			
		||||
   *   Thrown when there is a missing parameter.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getArgument(\ReflectionParameter $parameter) {
 | 
			
		||||
    $parameter_type_hint = Reflection::getParameterClassName($parameter);
 | 
			
		||||
    $parameter_name = $parameter->getName();
 | 
			
		||||
 | 
			
		||||
    // If the argument exists and is NULL, return it, regardless of
 | 
			
		||||
    // parameter type hint.
 | 
			
		||||
    if (!isset($this->objects[$parameter_name]) && array_key_exists($parameter_name, $this->objects)) {
 | 
			
		||||
      return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($parameter_type_hint) {
 | 
			
		||||
      $parameter_type_hint = new \ReflectionClass($parameter_type_hint);
 | 
			
		||||
      // If the argument exists and complies with the type hint, return it.
 | 
			
		||||
      if (isset($this->objects[$parameter_name]) && is_object($this->objects[$parameter_name]) && $parameter_type_hint->isInstance($this->objects[$parameter_name])) {
 | 
			
		||||
        return $this->objects[$parameter_name];
 | 
			
		||||
      }
 | 
			
		||||
      // Otherwise, resolve wildcard arguments by type matching.
 | 
			
		||||
      foreach ($this->wildcards as $wildcard) {
 | 
			
		||||
        if ($parameter_type_hint->isInstance($wildcard)) {
 | 
			
		||||
          return $wildcard;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    elseif (isset($this->scalars[$parameter_name])) {
 | 
			
		||||
      return $this->scalars[$parameter_name];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the callable provides a default value, use it.
 | 
			
		||||
    if ($parameter->isDefaultValueAvailable()) {
 | 
			
		||||
      return $parameter->getDefaultValue();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Can't resolve it: call a method that throws an exception or can be
 | 
			
		||||
    // overridden to do something else.
 | 
			
		||||
    return $this->handleUnresolvedArgument($parameter);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets a reflector for the access check callable.
 | 
			
		||||
   *
 | 
			
		||||
   * The access checker may be either a procedural function (in which case the
 | 
			
		||||
   * callable is the function name) or a method (in which case the callable is
 | 
			
		||||
   * an array of the object and method name).
 | 
			
		||||
   *
 | 
			
		||||
   * @param callable $callable
 | 
			
		||||
   *   The callable (either a function or a method).
 | 
			
		||||
   *
 | 
			
		||||
   * @return \ReflectionFunctionAbstract
 | 
			
		||||
   *   The ReflectionMethod or ReflectionFunction to introspect the callable.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getReflector(callable $callable) {
 | 
			
		||||
    if (is_array($callable)) {
 | 
			
		||||
      return new \ReflectionMethod($callable[0], $callable[1]);
 | 
			
		||||
    }
 | 
			
		||||
    if (is_string($callable) && str_contains($callable, "::")) {
 | 
			
		||||
      return \ReflectionMethod::createFromMethodName($callable);
 | 
			
		||||
    }
 | 
			
		||||
    return new \ReflectionFunction($callable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handles unresolved arguments for getArgument().
 | 
			
		||||
   *
 | 
			
		||||
   * Subclasses that override this method may return a default value
 | 
			
		||||
   * instead of throwing an exception.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \RuntimeException
 | 
			
		||||
   *   Thrown when there is a missing parameter.
 | 
			
		||||
   */
 | 
			
		||||
  protected function handleUnresolvedArgument(\ReflectionParameter $parameter) {
 | 
			
		||||
    $class = $parameter->getDeclaringClass();
 | 
			
		||||
    $function = $parameter->getDeclaringFunction();
 | 
			
		||||
    if ($class && !$function->isClosure()) {
 | 
			
		||||
      $function_name = $class->getName() . '::' . $function->getName();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $function_name = $function->getName();
 | 
			
		||||
    }
 | 
			
		||||
    throw new \RuntimeException(sprintf('Callable "%s" requires a value for the "$%s" argument.', $function_name, $parameter->getName()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Resolves the arguments to pass to a callable.
 | 
			
		||||
 */
 | 
			
		||||
interface ArgumentsResolverInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets arguments suitable for passing to the given callable.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of arguments to pass to the callable.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \RuntimeException
 | 
			
		||||
   *   When a value for an argument given cannot be resolved.
 | 
			
		||||
   */
 | 
			
		||||
  public function getArguments(callable $callable);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										140
									
								
								web/core/lib/Drupal/Component/Utility/Bytes.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								web/core/lib/Drupal/Component/Utility/Bytes.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides helper methods for byte conversions.
 | 
			
		||||
 */
 | 
			
		||||
class Bytes {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The number of bytes in a kilobyte.
 | 
			
		||||
   *
 | 
			
		||||
   * @see http://wikipedia.org/wiki/Kilobyte
 | 
			
		||||
   */
 | 
			
		||||
  const KILOBYTE = 1024;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The allowed suffixes of a bytes string in lowercase.
 | 
			
		||||
   *
 | 
			
		||||
   * @see http://wikipedia.org/wiki/Kilobyte
 | 
			
		||||
   */
 | 
			
		||||
  const ALLOWED_SUFFIXES = [
 | 
			
		||||
    '',
 | 
			
		||||
    'b',
 | 
			
		||||
    'byte',
 | 
			
		||||
    'bytes',
 | 
			
		||||
    'k',
 | 
			
		||||
    'kb',
 | 
			
		||||
    'kilobyte',
 | 
			
		||||
    'kilobytes',
 | 
			
		||||
    'm',
 | 
			
		||||
    'mb',
 | 
			
		||||
    'megabyte',
 | 
			
		||||
    'megabytes',
 | 
			
		||||
    'g',
 | 
			
		||||
    'gb',
 | 
			
		||||
    'gigabyte',
 | 
			
		||||
    'gigabytes',
 | 
			
		||||
    't',
 | 
			
		||||
    'tb',
 | 
			
		||||
    'terabyte',
 | 
			
		||||
    'terabytes',
 | 
			
		||||
    'p',
 | 
			
		||||
    'pb',
 | 
			
		||||
    'petabyte',
 | 
			
		||||
    'petabytes',
 | 
			
		||||
    'e',
 | 
			
		||||
    'eb',
 | 
			
		||||
    'exabyte',
 | 
			
		||||
    'exabytes',
 | 
			
		||||
    'z',
 | 
			
		||||
    'zb',
 | 
			
		||||
    'zettabyte',
 | 
			
		||||
    'zettabytes',
 | 
			
		||||
    'y',
 | 
			
		||||
    'yb',
 | 
			
		||||
    'yottabyte',
 | 
			
		||||
    'yottabytes',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Parses a given byte size.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int|float|string $size
 | 
			
		||||
   *   An integer, float, or string size expressed as a number of bytes with
 | 
			
		||||
   *   optional SI or IEC binary unit prefix (e.g. 2, 2.4, 3K, 5MB, 10G, 6GiB,
 | 
			
		||||
   *   8 bytes, 9mbytes).
 | 
			
		||||
   *
 | 
			
		||||
   * @return float
 | 
			
		||||
   *   The floating point value of the size in bytes.
 | 
			
		||||
   */
 | 
			
		||||
  public static function toNumber($size): float {
 | 
			
		||||
    // Remove the non-unit characters from the size.
 | 
			
		||||
    $unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
 | 
			
		||||
    // Remove the non-numeric characters from the size.
 | 
			
		||||
    $size = preg_replace('/[^0-9\.]/', '', $size);
 | 
			
		||||
    if ($unit) {
 | 
			
		||||
      // Find the position of the unit in the ordered string which is the power
 | 
			
		||||
      // of magnitude to multiply a kilobyte by.
 | 
			
		||||
      return round($size * pow(self::KILOBYTE, stripos('bkmgtpezy', $unit[0])));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // Ensure size is a proper number type.
 | 
			
		||||
      return round((float) $size);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validate that a string is a representation of a number of bytes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $string
 | 
			
		||||
   *   The string to validate.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the string is valid, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public static function validate($string): bool {
 | 
			
		||||
    // Ensure that the string starts with a numeric character.
 | 
			
		||||
    if (!preg_match('/^[0-9]/', $string)) {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove the numeric characters from the beginning of the value.
 | 
			
		||||
    $string = preg_replace('/^[0-9\.]+/', '', $string);
 | 
			
		||||
 | 
			
		||||
    // Remove remaining spaces from the value.
 | 
			
		||||
    $string = trim($string);
 | 
			
		||||
 | 
			
		||||
    return in_array(strtolower($string), self::ALLOWED_SUFFIXES);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validates a string is a representation of a number of bytes.
 | 
			
		||||
   *
 | 
			
		||||
   * To be used with the `Callback` constraint.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string|int|float|null $value
 | 
			
		||||
   *   The string, integer or float to validate.
 | 
			
		||||
   * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
 | 
			
		||||
   *   The validation execution context.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Symfony\Component\Validator\Constraints\CallbackValidator
 | 
			
		||||
   * @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
   */
 | 
			
		||||
  public static function validateConstraint(string|int|float|null $value, ExecutionContextInterface $context): void {
 | 
			
		||||
    // Ignore NULL values (i.e. support `nullable: true`).
 | 
			
		||||
    if ($value === NULL) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!self::validate((string) $value)) {
 | 
			
		||||
      $context->addViolation('This value must be a number of bytes, optionally with a unit such as "MB" or "megabytes". %value does not represent a number of bytes.', [
 | 
			
		||||
        '%value' => $value,
 | 
			
		||||
      ]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								web/core/lib/Drupal/Component/Utility/Color.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								web/core/lib/Drupal/Component/Utility/Color.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,114 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Performs color conversions.
 | 
			
		||||
 */
 | 
			
		||||
class Color {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validates whether a hexadecimal color value is syntactically correct.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $hex
 | 
			
		||||
   *   The hexadecimal string to validate. May contain a leading '#'. May use
 | 
			
		||||
   *   the shorthand notation (e.g., '123' for '112233').
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if $hex is valid or FALSE if it is not.
 | 
			
		||||
   */
 | 
			
		||||
  public static function validateHex($hex) {
 | 
			
		||||
    if (!is_string($hex)) {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
    return preg_match('/^[#]?([0-9a-fA-F]{3}){1,2}$/', $hex) === 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Parses a hexadecimal color string like '#abc' or '#aabbcc'.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $hex
 | 
			
		||||
   *   The hexadecimal color string to parse.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array containing the values for 'red', 'green', 'blue'.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \InvalidArgumentException
 | 
			
		||||
   */
 | 
			
		||||
  public static function hexToRgb($hex) {
 | 
			
		||||
    if (!self::validateHex($hex)) {
 | 
			
		||||
      throw new \InvalidArgumentException("'$hex' is not a valid hex value.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Ignore '#' prefixes.
 | 
			
		||||
    $hex = ltrim($hex, '#');
 | 
			
		||||
 | 
			
		||||
    // Convert shorthands like '#abc' to '#aabbcc'.
 | 
			
		||||
    if (strlen($hex) == 3) {
 | 
			
		||||
      $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $c = hexdec($hex);
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
      'red' => $c >> 16 & 0xFF,
 | 
			
		||||
      'green' => $c >> 8 & 0xFF,
 | 
			
		||||
      'blue' => $c & 0xFF,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Converts RGB color arrays or strings to lowercase CSS notation.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array|string $input
 | 
			
		||||
   *   The value to convert. If the value is an array the first three elements
 | 
			
		||||
   *   will be used as the red, green and blue components. String values in CSS
 | 
			
		||||
   *   notation like '10, 20, 30' are also supported.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The lowercase simple color representation of the given color.
 | 
			
		||||
   */
 | 
			
		||||
  public static function rgbToHex($input) {
 | 
			
		||||
    // Remove named array keys if input comes from Color::hex2rgb().
 | 
			
		||||
    if (is_array($input)) {
 | 
			
		||||
      $rgb = array_values($input);
 | 
			
		||||
    }
 | 
			
		||||
    // Parse string input in CSS notation ('10, 20, 30').
 | 
			
		||||
    elseif (is_string($input)) {
 | 
			
		||||
      preg_match('/(\d+), ?(\d+), ?(\d+)/', $input, $rgb);
 | 
			
		||||
      array_shift($rgb);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $out = 0;
 | 
			
		||||
    foreach ($rgb as $k => $v) {
 | 
			
		||||
      $out |= $v << (16 - $k * 8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return '#' . str_pad(dechex($out), 6, '0', STR_PAD_LEFT);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Normalize the hex color length to 6 characters for comparison.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $hex
 | 
			
		||||
   *   The hex color to normalize.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The 6 character hex color.
 | 
			
		||||
   */
 | 
			
		||||
  public static function normalizeHexLength($hex) {
 | 
			
		||||
    // Ignore '#' prefixes.
 | 
			
		||||
    $hex = ltrim($hex, '#');
 | 
			
		||||
 | 
			
		||||
    if (strlen($hex) === 3) {
 | 
			
		||||
      $hex[5] = $hex[2];
 | 
			
		||||
      $hex[4] = $hex[2];
 | 
			
		||||
      $hex[3] = $hex[1];
 | 
			
		||||
      $hex[2] = $hex[1];
 | 
			
		||||
      $hex[1] = $hex[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return '#' . $hex;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								web/core/lib/Drupal/Component/Utility/Crypt.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								web/core/lib/Drupal/Component/Utility/Crypt.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utility class for cryptographically-secure string handling routines.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class Crypt {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calculates a base-64 encoded, URL-safe sha-256 hmac.
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed $data
 | 
			
		||||
   *   Scalar value to be validated with the hmac.
 | 
			
		||||
   * @param mixed $key
 | 
			
		||||
   *   A secret key, this can be any scalar value.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A base-64 encoded sha-256 hmac, with + replaced with -, / with _ and
 | 
			
		||||
   *   any = padding characters removed.
 | 
			
		||||
   */
 | 
			
		||||
  public static function hmacBase64($data, $key) {
 | 
			
		||||
    // $data and $key being strings here is necessary to avoid empty string
 | 
			
		||||
    // results of the hash function if they are not scalar values. As this
 | 
			
		||||
    // function is used in security-critical contexts like token validation it
 | 
			
		||||
    // is important that it never returns an empty string.
 | 
			
		||||
    if (!is_scalar($data) || !is_scalar($key)) {
 | 
			
		||||
      throw new \InvalidArgumentException('Both parameters passed to \Drupal\Component\Utility\Crypt::hmacBase64 must be scalar values.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $hmac = base64_encode(hash_hmac('sha256', $data, $key, TRUE));
 | 
			
		||||
    // Modify the hmac so it's safe to use in URLs.
 | 
			
		||||
    return str_replace(['+', '/', '='], ['-', '_', ''], $hmac);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calculates a base-64 encoded, URL-safe sha-256 hash.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $data
 | 
			
		||||
   *   String to be hashed.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A base-64 encoded sha-256 hash, with + replaced with -, / with _ and
 | 
			
		||||
   *   any = padding characters removed.
 | 
			
		||||
   */
 | 
			
		||||
  public static function hashBase64($data) {
 | 
			
		||||
    $hash = base64_encode(hash('sha256', $data, TRUE));
 | 
			
		||||
    // Modify the hash so it's safe to use in URLs.
 | 
			
		||||
    return str_replace(['+', '/', '='], ['-', '_', ''], $hash);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns a URL-safe, base64 encoded string of highly randomized bytes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $count
 | 
			
		||||
   *   The number of random bytes to fetch and base64 encode.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A base-64 encoded string, with + replaced with -, / with _ and any =
 | 
			
		||||
   *   padding characters removed.
 | 
			
		||||
   */
 | 
			
		||||
  public static function randomBytesBase64($count = 32) {
 | 
			
		||||
    return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(random_bytes($count)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								web/core/lib/Drupal/Component/Utility/DeprecatedArray.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								web/core/lib/Drupal/Component/Utility/DeprecatedArray.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An array that triggers a deprecation warning when accessed.
 | 
			
		||||
 */
 | 
			
		||||
class DeprecatedArray extends \ArrayObject {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The deprecation message.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $message;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * DeprecatedArray constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $values
 | 
			
		||||
   *   The array values.
 | 
			
		||||
   * @param string $message
 | 
			
		||||
   *   The deprecation message.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $values, $message) {
 | 
			
		||||
    $this->message = $message;
 | 
			
		||||
    parent::__construct($values);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function offsetExists($offset): bool {
 | 
			
		||||
    @trigger_error($this->message, E_USER_DEPRECATED);
 | 
			
		||||
    return parent::offsetExists($offset);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function offsetGet($offset): mixed {
 | 
			
		||||
    @trigger_error($this->message, E_USER_DEPRECATED);
 | 
			
		||||
    return parent::offsetGet($offset);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function offsetSet($offset, $value): void {
 | 
			
		||||
    @trigger_error($this->message, E_USER_DEPRECATED);
 | 
			
		||||
    parent::offsetSet($offset, $value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function offsetUnset($offset): void {
 | 
			
		||||
    @trigger_error($this->message, E_USER_DEPRECATED);
 | 
			
		||||
    parent::offsetUnset($offset);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getIterator(): \ArrayIterator {
 | 
			
		||||
    @trigger_error($this->message, E_USER_DEPRECATED);
 | 
			
		||||
    return parent::getIterator();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function unserialize($serialized): void {
 | 
			
		||||
    @trigger_error($this->message, E_USER_DEPRECATED);
 | 
			
		||||
    parent::unserialize($serialized);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function serialize(): string {
 | 
			
		||||
    @trigger_error($this->message, E_USER_DEPRECATED);
 | 
			
		||||
    return parent::serialize();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function count(): int {
 | 
			
		||||
    @trigger_error($this->message, E_USER_DEPRECATED);
 | 
			
		||||
    return parent::count();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								web/core/lib/Drupal/Component/Utility/DeprecationHelper.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								web/core/lib/Drupal/Component/Utility/DeprecationHelper.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a helper method for handling deprecated code paths in projects.
 | 
			
		||||
 */
 | 
			
		||||
final class DeprecationHelper {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper to run a callback based on the installed version of a project.
 | 
			
		||||
   *
 | 
			
		||||
   * With this helper, contributed or custom modules can run different code
 | 
			
		||||
   * paths based on the version of a project (e.g. Drupal) using callbacks.
 | 
			
		||||
   *
 | 
			
		||||
   * The below templates help code editors and PHPStan understand the return
 | 
			
		||||
   * value of this function.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $currentVersion
 | 
			
		||||
   *   Version to check against.
 | 
			
		||||
   * @param string $deprecatedVersion
 | 
			
		||||
   *   Version that deprecated the old code path.
 | 
			
		||||
   * @param callable(): Current $currentCallable
 | 
			
		||||
   *   Callback for the current version.
 | 
			
		||||
   * @param callable(): Deprecated $deprecatedCallable
 | 
			
		||||
   *   Callback for deprecated code path.
 | 
			
		||||
   *
 | 
			
		||||
   * @return Current|Deprecated
 | 
			
		||||
   *   The method to invoke based on the current version and the version of the
 | 
			
		||||
   *   deprecation. The current callback when the current version is greater
 | 
			
		||||
   *   than or equal to the version of the deprecation. Otherwise, the
 | 
			
		||||
   *   deprecated callback.
 | 
			
		||||
   *
 | 
			
		||||
   * @template Current
 | 
			
		||||
   * @template Deprecated
 | 
			
		||||
   */
 | 
			
		||||
  public static function backwardsCompatibleCall(string $currentVersion, string $deprecatedVersion, callable $currentCallable, callable $deprecatedCallable): mixed {
 | 
			
		||||
    // Normalize the version string when it's a dev version to the first point
 | 
			
		||||
    // release of that minor. E.g. 10.2.x-dev and 10.2-dev both translate to
 | 
			
		||||
    // 10.2.0
 | 
			
		||||
    $normalizedVersion = str_ends_with($currentVersion, '-dev') ? str_replace(['.x-dev', '-dev'], '.0', $currentVersion) : $currentVersion;
 | 
			
		||||
 | 
			
		||||
    return version_compare($normalizedVersion, $deprecatedVersion, '>=') ? $currentCallable() : $deprecatedCallable();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								web/core/lib/Drupal/Component/Utility/DiffArray.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								web/core/lib/Drupal/Component/Utility/DiffArray.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides helpers to perform diffs on multi dimensional arrays.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class DiffArray {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Recursively computes the difference of arrays with additional index check.
 | 
			
		||||
   *
 | 
			
		||||
   * This is a version of array_diff_assoc() that supports multidimensional
 | 
			
		||||
   * arrays.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $array1
 | 
			
		||||
   *   The array to compare from.
 | 
			
		||||
   * @param array $array2
 | 
			
		||||
   *   The array to compare to.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Returns an array containing all the values from array1 that are not
 | 
			
		||||
   *   present in array2.
 | 
			
		||||
   */
 | 
			
		||||
  public static function diffAssocRecursive(array $array1, array $array2) {
 | 
			
		||||
    $difference = [];
 | 
			
		||||
 | 
			
		||||
    foreach ($array1 as $key => $value) {
 | 
			
		||||
      if (is_array($value)) {
 | 
			
		||||
        if (!array_key_exists($key, $array2) || !is_array($array2[$key])) {
 | 
			
		||||
          $difference[$key] = $value;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          $new_diff = static::diffAssocRecursive($value, $array2[$key]);
 | 
			
		||||
          if (!empty($new_diff)) {
 | 
			
		||||
            $difference[$key] = $new_diff;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      elseif (!array_key_exists($key, $array2) || $array2[$key] !== $value) {
 | 
			
		||||
        $difference[$key] = $value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $difference;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								web/core/lib/Drupal/Component/Utility/EmailValidator.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								web/core/lib/Drupal/Component/Utility/EmailValidator.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
use Egulias\EmailValidator\EmailValidator as EmailValidatorUtility;
 | 
			
		||||
use Egulias\EmailValidator\Validation\EmailValidation;
 | 
			
		||||
use Egulias\EmailValidator\Validation\RFCValidation;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Validates email addresses.
 | 
			
		||||
 */
 | 
			
		||||
class EmailValidator extends EmailValidatorUtility implements EmailValidatorInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validates an email address.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $email
 | 
			
		||||
   *   A string containing an email address.
 | 
			
		||||
   * @param \Egulias\EmailValidator\Validation\EmailValidation|null $email_validation
 | 
			
		||||
   *   This argument is ignored. If it is supplied an error will be triggered.
 | 
			
		||||
   *   See https://www.drupal.org/node/2997196.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the address is valid.
 | 
			
		||||
   */
 | 
			
		||||
  public function isValid($email, ?EmailValidation $email_validation = NULL) {
 | 
			
		||||
    if ($email_validation) {
 | 
			
		||||
      throw new \BadMethodCallException('Calling \Drupal\Component\Utility\EmailValidator::isValid() with the second argument is not supported. See https://www.drupal.org/node/2997196');
 | 
			
		||||
    }
 | 
			
		||||
    return parent::isValid($email, (new RFCValidation()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Validates email addresses.
 | 
			
		||||
 */
 | 
			
		||||
interface EmailValidatorInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validates an email address.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $email
 | 
			
		||||
   *   A string containing an email address.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the address is valid.
 | 
			
		||||
   */
 | 
			
		||||
  public function isValid($email);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								web/core/lib/Drupal/Component/Utility/Environment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								web/core/lib/Drupal/Component/Utility/Environment.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,100 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides PHP environment helper methods.
 | 
			
		||||
 */
 | 
			
		||||
class Environment {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Compares the memory required for an operation to the available memory.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $required
 | 
			
		||||
   *   The memory required for the operation, expressed as a number of bytes
 | 
			
		||||
   *   with optional SI or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G, 6GiB,
 | 
			
		||||
   *   8bytes, 9mbytes).
 | 
			
		||||
   * @param string|null $memory_limit
 | 
			
		||||
   *   (optional) The memory limit for the operation, expressed as a number of
 | 
			
		||||
   *   bytes with optional SI or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G,
 | 
			
		||||
   *   6GiB, 8bytes, 9mbytes). If no value is passed, the current PHP
 | 
			
		||||
   *   memory_limit will be used. Defaults to NULL.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if there is sufficient memory to allow the operation, or FALSE
 | 
			
		||||
   *   otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public static function checkMemoryLimit($required, $memory_limit = NULL) {
 | 
			
		||||
    if (!isset($memory_limit)) {
 | 
			
		||||
      $memory_limit = ini_get('memory_limit');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // There is sufficient memory if:
 | 
			
		||||
    // - No memory limit is set.
 | 
			
		||||
    // - The memory limit is set to unlimited (-1).
 | 
			
		||||
    // - The memory limit is greater than or equal to the memory required for
 | 
			
		||||
    //   the operation.
 | 
			
		||||
    return ((!$memory_limit) || ($memory_limit == -1) || (Bytes::toNumber($memory_limit) >= Bytes::toNumber($required)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Attempts to set the PHP maximum execution time.
 | 
			
		||||
   *
 | 
			
		||||
   * This function is a wrapper around the PHP function set_time_limit(). When
 | 
			
		||||
   * called, set_time_limit() restarts the timeout counter from zero. In other
 | 
			
		||||
   * words, if the timeout is the default 30 seconds, and 25 seconds into script
 | 
			
		||||
   * execution a call such as set_time_limit(20) is made, the script will run
 | 
			
		||||
   * for a total of 45 seconds before timing out.
 | 
			
		||||
   *
 | 
			
		||||
   * If the current time limit is not unlimited it is possible to decrease the
 | 
			
		||||
   * total time limit if the sum of the new time limit and the current time
 | 
			
		||||
   * spent running the script is inferior to the original time limit. It is
 | 
			
		||||
   * inherent to the way set_time_limit() works, it should rather be called with
 | 
			
		||||
   * an appropriate value every time you need to allocate a certain amount of
 | 
			
		||||
   * time to execute a task than only once at the beginning of the script.
 | 
			
		||||
   *
 | 
			
		||||
   * Before calling set_time_limit(), we check if this function is available
 | 
			
		||||
   * because it could be disabled by the server administrator.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $time_limit
 | 
			
		||||
   *   An integer time limit in seconds, or 0 for unlimited execution time.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   Whether set_time_limit() was successful or not.
 | 
			
		||||
   */
 | 
			
		||||
  public static function setTimeLimit($time_limit) {
 | 
			
		||||
    if (function_exists('set_time_limit')) {
 | 
			
		||||
      $current = ini_get('max_execution_time');
 | 
			
		||||
      // Do not set time limit if it is currently unlimited.
 | 
			
		||||
      if ($current != 0) {
 | 
			
		||||
        return set_time_limit($time_limit);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines the maximum file upload size by querying the PHP settings.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   A file size limit in bytes based on the PHP upload_max_filesize and
 | 
			
		||||
   *   post_max_size settings.
 | 
			
		||||
   */
 | 
			
		||||
  public static function getUploadMaxSize() {
 | 
			
		||||
    static $max_size = -1;
 | 
			
		||||
 | 
			
		||||
    if ($max_size < 0) {
 | 
			
		||||
      // Start with post_max_size.
 | 
			
		||||
      $max_size = Bytes::toNumber(ini_get('post_max_size'));
 | 
			
		||||
 | 
			
		||||
      // If upload_max_size is less, then reduce. Except if upload_max_size is
 | 
			
		||||
      // zero, which indicates no limit.
 | 
			
		||||
      $upload_max = Bytes::toNumber(ini_get('upload_max_filesize'));
 | 
			
		||||
      if ($upload_max > 0 && $upload_max < $max_size) {
 | 
			
		||||
        $max_size = $upload_max;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $max_size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								web/core/lib/Drupal/Component/Utility/FilterArray.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								web/core/lib/Drupal/Component/Utility/FilterArray.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides methods to filter arrays.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class FilterArray {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Removes empty strings from an array.
 | 
			
		||||
   *
 | 
			
		||||
   * This method removes all empty strings from the input array. This is
 | 
			
		||||
   * particularly useful to preserve 0 whilst filtering other falsy values. The
 | 
			
		||||
   * values are first cast to a string before comparison.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $value
 | 
			
		||||
   *   The array to filter.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The filtered array.
 | 
			
		||||
   */
 | 
			
		||||
  public static function removeEmptyStrings(array $value): array {
 | 
			
		||||
    return array_filter($value, static fn ($item) => (string) $item !== '');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										495
									
								
								web/core/lib/Drupal/Component/Utility/Html.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										495
									
								
								web/core/lib/Drupal/Component/Utility/Html.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,495 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
use Masterminds\HTML5;
 | 
			
		||||
use Masterminds\HTML5\Serializer\Traverser;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides DOMDocument helpers for parsing and serializing HTML strings.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class Html {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An array of previously cleaned HTML classes.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected static $classes = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An array of the initial IDs used in one request.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected static $seenIdsInit;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An array of IDs, including incremented versions when an ID is duplicated.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected static $seenIds;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stores whether the current request was sent via AJAX.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected static $isAjax = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * All attributes that may contain URIs.
 | 
			
		||||
   *
 | 
			
		||||
   * - The attributes 'code' and 'codebase' are omitted, because they only exist
 | 
			
		||||
   *   for the <applet> tag. The time of Java applets has passed.
 | 
			
		||||
   * - The attribute 'icon' is omitted, because no browser implements the
 | 
			
		||||
   *   <command> tag anymore.
 | 
			
		||||
   *  See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/command.
 | 
			
		||||
   * - The 'manifest' attribute is omitted because it only exists for the <html>
 | 
			
		||||
   *   tag. That tag only makes sense in an HTML-served-as-HTML context, in
 | 
			
		||||
   *   which case relative URLs are guaranteed to work.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   *
 | 
			
		||||
   * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
 | 
			
		||||
   * @see https://stackoverflow.com/questions/2725156/complete-list-of-html-tag-attributes-which-have-a-url-value
 | 
			
		||||
   */
 | 
			
		||||
  protected static $uriAttributes = ['href', 'poster', 'src', 'cite', 'data', 'action', 'formaction', 'srcset', 'about'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Prepares a string for use as a valid class name.
 | 
			
		||||
   *
 | 
			
		||||
   * Do not pass one string containing multiple classes as they will be
 | 
			
		||||
   * incorrectly concatenated with dashes, i.e. "one two" will become "one-two".
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed $class
 | 
			
		||||
   *   The class name to clean. It can be a string or anything that can be cast
 | 
			
		||||
   *   to string.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The cleaned class name.
 | 
			
		||||
   */
 | 
			
		||||
  public static function getClass($class) {
 | 
			
		||||
    $class = (string) $class;
 | 
			
		||||
    if (!isset(static::$classes[$class])) {
 | 
			
		||||
      static::$classes[$class] = static::cleanCssIdentifier(mb_strtolower($class));
 | 
			
		||||
    }
 | 
			
		||||
    return static::$classes[$class];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Prepares a string for use as a CSS identifier (element, class, or ID name).
 | 
			
		||||
   *
 | 
			
		||||
   * Link below shows the syntax for valid CSS identifiers (including element
 | 
			
		||||
   * names, classes, and IDs in selectors).
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $identifier
 | 
			
		||||
   *   The identifier to clean.
 | 
			
		||||
   * @param array $filter
 | 
			
		||||
   *   An array of string replacements to use on the identifier.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The cleaned identifier.
 | 
			
		||||
   *
 | 
			
		||||
   * @see https://www.w3.org/TR/CSS21/syndata.html#characters
 | 
			
		||||
   */
 | 
			
		||||
  public static function cleanCssIdentifier(
 | 
			
		||||
    $identifier,
 | 
			
		||||
    array $filter = [
 | 
			
		||||
      ' ' => '-',
 | 
			
		||||
      '_' => '-',
 | 
			
		||||
      '/' => '-',
 | 
			
		||||
      '[' => '-',
 | 
			
		||||
      ']' => '',
 | 
			
		||||
    ],
 | 
			
		||||
  ) {
 | 
			
		||||
    // We could also use strtr() here but its much slower than str_replace(). In
 | 
			
		||||
    // order to keep '__' to stay '__' we first replace it with a different
 | 
			
		||||
    // placeholder after checking that it is not defined as a filter.
 | 
			
		||||
    $double_underscore_replacements = 0;
 | 
			
		||||
    if (!isset($filter['__'])) {
 | 
			
		||||
      $identifier = str_replace('__', '##', $identifier, $double_underscore_replacements);
 | 
			
		||||
    }
 | 
			
		||||
    $identifier = str_replace(array_keys($filter), array_values($filter), $identifier);
 | 
			
		||||
    // Replace temporary placeholder '##' with '__' only if the original
 | 
			
		||||
    // $identifier contained '__'.
 | 
			
		||||
    if ($double_underscore_replacements > 0) {
 | 
			
		||||
      $identifier = str_replace('##', '__', $identifier);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Valid characters in a CSS identifier are:
 | 
			
		||||
    // - the hyphen (U+002D)
 | 
			
		||||
    // - a-z (U+0030 - U+0039)
 | 
			
		||||
    // - A-Z (U+0041 - U+005A)
 | 
			
		||||
    // - the underscore (U+005F)
 | 
			
		||||
    // - 0-9 (U+0061 - U+007A)
 | 
			
		||||
    // - ISO 10646 characters U+00A1 and higher
 | 
			
		||||
    // We strip out any character not in the above list.
 | 
			
		||||
    $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', (string) $identifier);
 | 
			
		||||
    // Identifiers cannot start with a digit, two hyphens, or a hyphen followed
 | 
			
		||||
    // by a digit.
 | 
			
		||||
    $identifier = preg_replace([
 | 
			
		||||
      '/^[0-9]/',
 | 
			
		||||
      '/^(-[0-9])|^(--)/',
 | 
			
		||||
    ], ['_', '__'], (string) $identifier);
 | 
			
		||||
    return $identifier;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets if this request is an Ajax request.
 | 
			
		||||
   *
 | 
			
		||||
   * @param bool $is_ajax
 | 
			
		||||
   *   TRUE if this request is an Ajax request, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public static function setIsAjax($is_ajax) {
 | 
			
		||||
    static::$isAjax = $is_ajax;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Prepares a string for use as a valid HTML ID and guarantees uniqueness.
 | 
			
		||||
   *
 | 
			
		||||
   * This function ensures that each passed HTML ID value only exists once on
 | 
			
		||||
   * the page. By tracking the already returned ids, this function enables
 | 
			
		||||
   * forms, blocks, and other content to be output multiple times on the same
 | 
			
		||||
   * page, without breaking HTML validation.
 | 
			
		||||
   *
 | 
			
		||||
   * For already existing IDs, a counter is appended to the ID string.
 | 
			
		||||
   * Therefore, JavaScript and CSS code should not rely on any value that was
 | 
			
		||||
   * generated by this function and instead should rely on manually added CSS
 | 
			
		||||
   * classes or similarly reliable constructs.
 | 
			
		||||
   *
 | 
			
		||||
   * Two consecutive hyphens separate the counter from the original ID. To
 | 
			
		||||
   * manage uniqueness across multiple Ajax requests on the same page, Ajax
 | 
			
		||||
   * requests POST an array of all IDs currently present on the page, which are
 | 
			
		||||
   * used to prime this function's cache upon first invocation.
 | 
			
		||||
   *
 | 
			
		||||
   * To allow reverse-parsing of IDs submitted via Ajax, any multiple
 | 
			
		||||
   * consecutive hyphens in the originally passed $id are replaced with a
 | 
			
		||||
   * single hyphen.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $id
 | 
			
		||||
   *   The ID to clean.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The cleaned ID.
 | 
			
		||||
   */
 | 
			
		||||
  public static function getUniqueId($id) {
 | 
			
		||||
    // If this is an Ajax request, then content returned by this page request
 | 
			
		||||
    // will be merged with content already on the base page. The HTML IDs must
 | 
			
		||||
    // be unique for the fully merged content. Therefore use unique IDs.
 | 
			
		||||
    if (static::$isAjax) {
 | 
			
		||||
      return static::getId($id) . '--' . Crypt::randomBytesBase64(8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // @todo Remove all that code once we switch over to random IDs only,
 | 
			
		||||
    // see https://www.drupal.org/node/1090592.
 | 
			
		||||
    if (!isset(static::$seenIdsInit)) {
 | 
			
		||||
      static::$seenIdsInit = [];
 | 
			
		||||
    }
 | 
			
		||||
    if (!isset(static::$seenIds)) {
 | 
			
		||||
      static::$seenIds = static::$seenIdsInit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $id = static::getId($id);
 | 
			
		||||
 | 
			
		||||
    // Ensure IDs are unique by appending a counter after the first occurrence.
 | 
			
		||||
    // The counter needs to be appended with a delimiter that does not exist in
 | 
			
		||||
    // the base ID. Requiring a unique delimiter helps ensure that we really do
 | 
			
		||||
    // return unique IDs and also helps us re-create the $seen_ids array during
 | 
			
		||||
    // Ajax requests.
 | 
			
		||||
    if (isset(static::$seenIds[$id])) {
 | 
			
		||||
      $id = $id . '--' . ++static::$seenIds[$id];
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      static::$seenIds[$id] = 1;
 | 
			
		||||
    }
 | 
			
		||||
    return $id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Prepares a string for use as a valid HTML ID.
 | 
			
		||||
   *
 | 
			
		||||
   * Only use this function when you want to intentionally skip the uniqueness
 | 
			
		||||
   * guarantee of self::getUniqueId().
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $id
 | 
			
		||||
   *   The ID to clean.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The cleaned ID.
 | 
			
		||||
   *
 | 
			
		||||
   * @see self::getUniqueId()
 | 
			
		||||
   */
 | 
			
		||||
  public static function getId($id) {
 | 
			
		||||
    $id = str_replace([' ', '_', '[', ']'], ['-', '-', '-', ''], mb_strtolower($id));
 | 
			
		||||
 | 
			
		||||
    // As defined in https://www.w3.org/TR/html4/types.html#type-name, HTML IDs
 | 
			
		||||
    // can only contain letters, digits ([0-9]), hyphens ("-"), underscores
 | 
			
		||||
    // ("_"), colons (":"), and periods ("."). We strip out any character not in
 | 
			
		||||
    // that list. Note that the CSS spec doesn't allow colons or periods in
 | 
			
		||||
    // identifiers (https://www.w3.org/TR/CSS21/syndata.html#characters), so we
 | 
			
		||||
    // strip those two characters as well.
 | 
			
		||||
    $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id);
 | 
			
		||||
 | 
			
		||||
    // Removing multiple consecutive hyphens.
 | 
			
		||||
    $id = preg_replace('/\-+/', '-', $id);
 | 
			
		||||
    return $id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Resets the list of seen IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public static function resetSeenIds() {
 | 
			
		||||
    static::$seenIds = NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Normalizes an HTML snippet.
 | 
			
		||||
   *
 | 
			
		||||
   * This function is essentially \DOMDocument::normalizeDocument(), but
 | 
			
		||||
   * operates on an HTML string instead of a \DOMDocument.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $html
 | 
			
		||||
   *   The HTML string to normalize.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The normalized HTML string.
 | 
			
		||||
   */
 | 
			
		||||
  public static function normalize($html) {
 | 
			
		||||
    $document = static::load($html);
 | 
			
		||||
    return static::serialize($document);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Parses an HTML snippet and returns it as a DOM object.
 | 
			
		||||
   *
 | 
			
		||||
   * This function loads the body part of a partial HTML document and returns a
 | 
			
		||||
   * full \DOMDocument object that represents this document.
 | 
			
		||||
   *
 | 
			
		||||
   * Use \Drupal\Component\Utility\Html::serialize() to serialize this
 | 
			
		||||
   * \DOMDocument back to a string.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $html
 | 
			
		||||
   *   The partial HTML snippet to load. Invalid markup will be corrected on
 | 
			
		||||
   *   import.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \DOMDocument
 | 
			
		||||
   *   A \DOMDocument that represents the loaded HTML snippet.
 | 
			
		||||
   */
 | 
			
		||||
  public static function load($html) {
 | 
			
		||||
    // Instantiate the HTML5 parser, but without the HTML5 namespace being
 | 
			
		||||
    // added to the DOM document.
 | 
			
		||||
    $html5 = new HTML5(['disable_html_ns' => TRUE, 'encoding' => 'UTF-8']);
 | 
			
		||||
 | 
			
		||||
    // Attach the provided HTML inside the body. Rely on the HTML5 parser to
 | 
			
		||||
    // close the body tag.
 | 
			
		||||
    return $html5->loadHTML('<body>' . $html);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Converts the body of a \DOMDocument back to an HTML snippet.
 | 
			
		||||
   *
 | 
			
		||||
   * The function serializes the body part of a \DOMDocument back to an HTML
 | 
			
		||||
   * snippet. The resulting HTML snippet will be properly formatted to be
 | 
			
		||||
   * compatible with HTML user agents.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \DOMDocument $document
 | 
			
		||||
   *   A \DOMDocument object to serialize, only the tags below the first <body>
 | 
			
		||||
   *   node will be converted.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A valid HTML snippet, as a string.
 | 
			
		||||
   */
 | 
			
		||||
  public static function serialize(\DOMDocument $document) {
 | 
			
		||||
    $body_node = $document->getElementsByTagName('body')->item(0);
 | 
			
		||||
    $html = '';
 | 
			
		||||
 | 
			
		||||
    if ($body_node !== NULL) {
 | 
			
		||||
      foreach ($body_node->getElementsByTagName('script') as $node) {
 | 
			
		||||
        static::escapeCdataElement($node);
 | 
			
		||||
      }
 | 
			
		||||
      foreach ($body_node->getElementsByTagName('style') as $node) {
 | 
			
		||||
        static::escapeCdataElement($node, '/*', '*/');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Serialize the body using our custom set of rules.
 | 
			
		||||
      // @see \Masterminds\HTML5::saveHTML()
 | 
			
		||||
      $stream = fopen('php://temp', 'wb');
 | 
			
		||||
      $rules = new HtmlSerializerRules($stream);
 | 
			
		||||
      foreach ($body_node->childNodes as $node) {
 | 
			
		||||
        $traverser = new Traverser($node, $stream, $rules);
 | 
			
		||||
        $traverser->walk();
 | 
			
		||||
      }
 | 
			
		||||
      $rules->unsetTraverser();
 | 
			
		||||
      $html = stream_get_contents($stream, -1, 0);
 | 
			
		||||
      fclose($stream);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Normalize all newlines.
 | 
			
		||||
    $html = str_replace(["\r\n", "\r"], "\n", $html);
 | 
			
		||||
 | 
			
		||||
    return $html;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds comments around a <!CDATA section in a \DOMNode.
 | 
			
		||||
   *
 | 
			
		||||
   * \DOMDocument::loadHTML() in \Drupal\Component\Utility\Html::load() makes
 | 
			
		||||
   * CDATA sections from the contents of inline script and style tags. This can
 | 
			
		||||
   * cause HTML4 browsers to throw exceptions.
 | 
			
		||||
   *
 | 
			
		||||
   * This function attempts to solve the problem by creating a
 | 
			
		||||
   * \DOMDocumentFragment to comment the CDATA tag.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \DOMNode $node
 | 
			
		||||
   *   The element potentially containing a CDATA node.
 | 
			
		||||
   * @param string $comment_start
 | 
			
		||||
   *   (optional) A string to use as a comment start marker to escape the CDATA
 | 
			
		||||
   *   declaration. Defaults to '//'.
 | 
			
		||||
   * @param string $comment_end
 | 
			
		||||
   *   (optional) A string to use as a comment end marker to escape the CDATA
 | 
			
		||||
   *   declaration. Defaults to an empty string.
 | 
			
		||||
   */
 | 
			
		||||
  public static function escapeCdataElement(\DOMNode $node, $comment_start = '//', $comment_end = '') {
 | 
			
		||||
    foreach ($node->childNodes as $child_node) {
 | 
			
		||||
      if ($child_node instanceof \DOMCdataSection) {
 | 
			
		||||
        $data = $child_node->data;
 | 
			
		||||
        if (!str_contains($child_node->data, 'CDATA')) {
 | 
			
		||||
          $embed_prefix = "\n{$comment_start}<![CDATA[{$comment_end}\n";
 | 
			
		||||
          $embed_suffix = "\n{$comment_start}]]>{$comment_end}\n";
 | 
			
		||||
 | 
			
		||||
          $data = $embed_prefix . $data . $embed_suffix;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $fragment = $node->ownerDocument->createDocumentFragment();
 | 
			
		||||
        $fragment->appendXML($data);
 | 
			
		||||
        $node->appendChild($fragment);
 | 
			
		||||
        $node->removeChild($child_node);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Decodes all HTML entities including numerical ones to regular UTF-8 bytes.
 | 
			
		||||
   *
 | 
			
		||||
   * Double-escaped entities will only be decoded once ("&lt;" becomes
 | 
			
		||||
   * "<", not "<"). Be careful when using this function, as it will revert
 | 
			
		||||
   * previous sanitization efforts (<script> will become <script>).
 | 
			
		||||
   *
 | 
			
		||||
   * This method is not the opposite of Html::escape(). For example, this method
 | 
			
		||||
   * will convert "é" to "é", whereas Html::escape() will not convert "é"
 | 
			
		||||
   * to "é".
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $text
 | 
			
		||||
   *   The text to decode entities in.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The input $text, with all HTML entities decoded once.
 | 
			
		||||
   *
 | 
			
		||||
   * @see html_entity_decode()
 | 
			
		||||
   * @see \Drupal\Component\Utility\Html::escape()
 | 
			
		||||
   */
 | 
			
		||||
  public static function decodeEntities(string $text): string {
 | 
			
		||||
    return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Escapes text by converting special characters to HTML entities.
 | 
			
		||||
   *
 | 
			
		||||
   * This method escapes HTML for sanitization purposes by replacing the
 | 
			
		||||
   * following special characters with their HTML entity equivalents:
 | 
			
		||||
   * - & (ampersand) becomes &
 | 
			
		||||
   * - " (double quote) becomes "
 | 
			
		||||
   * - ' (single quote) becomes '
 | 
			
		||||
   * - < (less than) becomes <
 | 
			
		||||
   * - > (greater than) becomes >
 | 
			
		||||
   * Special characters that have already been escaped will be double-escaped
 | 
			
		||||
   * (for example, "<" becomes "&lt;"), and invalid UTF-8 encoding
 | 
			
		||||
   * will be converted to the Unicode replacement character ("<22>").
 | 
			
		||||
   *
 | 
			
		||||
   * This method is not the opposite of Html::decodeEntities(). For example,
 | 
			
		||||
   * this method will not encode "é" to "é", whereas
 | 
			
		||||
   * Html::decodeEntities() will convert all HTML entities to UTF-8 bytes,
 | 
			
		||||
   * including "é" and "<" to "é" and "<".
 | 
			
		||||
   *
 | 
			
		||||
   * When constructing @link theme_render render arrays @endlink passing the
 | 
			
		||||
   * output of Html::escape() to '#markup' is not recommended. Use the
 | 
			
		||||
   * '#plain_text' key instead and the renderer will autoescape the text.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $text
 | 
			
		||||
   *   The input text.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The text with all HTML special characters converted.
 | 
			
		||||
   *
 | 
			
		||||
   * @see htmlspecialchars()
 | 
			
		||||
   * @see \Drupal\Component\Utility\Html::decodeEntities()
 | 
			
		||||
   *
 | 
			
		||||
   * @ingroup sanitization
 | 
			
		||||
   */
 | 
			
		||||
  public static function escape(string $text): string {
 | 
			
		||||
    return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Converts all root-relative URLs to absolute URLs.
 | 
			
		||||
   *
 | 
			
		||||
   * Does not change any existing protocol-relative or absolute URLs. Does not
 | 
			
		||||
   * change other relative URLs because they would result in different absolute
 | 
			
		||||
   * URLs depending on the current path. For example: when the same content
 | 
			
		||||
   * containing such a relative URL (for example 'image.png'), is served from
 | 
			
		||||
   * its canonical URL (for example 'https://example.com/some-article') or from
 | 
			
		||||
   * a listing or feed (for example 'https://example.com/all-articles') their
 | 
			
		||||
   * "current path" differs, resulting in different absolute URLs:
 | 
			
		||||
   * 'https://example.com/some-article/image.png' versus
 | 
			
		||||
   * 'https://example.com/all-articles/image.png'. Only one can be correct.
 | 
			
		||||
   * Therefore relative URLs that are not root-relative cannot be safely
 | 
			
		||||
   * transformed and should generally be avoided.
 | 
			
		||||
   *
 | 
			
		||||
   * Necessary for HTML that is served outside of a website, for example, RSS
 | 
			
		||||
   * and email.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $html
 | 
			
		||||
   *   The partial HTML snippet to load. Invalid markup will be corrected on
 | 
			
		||||
   *   import.
 | 
			
		||||
   * @param string $scheme_and_host
 | 
			
		||||
   *   The root URL, which has a URI scheme, host and optional port.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The updated HTML snippet.
 | 
			
		||||
   */
 | 
			
		||||
  public static function transformRootRelativeUrlsToAbsolute($html, $scheme_and_host) {
 | 
			
		||||
    assert(empty(array_diff(array_keys(parse_url($scheme_and_host)), ["scheme", "host", "port"])), '$scheme_and_host contains scheme, host and port at most.');
 | 
			
		||||
    assert(isset(parse_url($scheme_and_host)["scheme"]), '$scheme_and_host is absolute and hence has a scheme.');
 | 
			
		||||
    assert(isset(parse_url($scheme_and_host)["host"]), '$base_url is absolute and hence has a host.');
 | 
			
		||||
 | 
			
		||||
    $html_dom = Html::load($html);
 | 
			
		||||
    $xpath = new \DOMXPath($html_dom);
 | 
			
		||||
 | 
			
		||||
    // Update all root-relative URLs to absolute URLs in the given HTML.
 | 
			
		||||
    // Perform on attributes that may contain a single URI.
 | 
			
		||||
    foreach (static::$uriAttributes as $attr) {
 | 
			
		||||
      foreach ($xpath->query("//*[starts-with(@$attr, '/') and not(starts-with(@$attr, '//'))]") as $node) {
 | 
			
		||||
        $node->setAttribute($attr, $scheme_and_host . $node->getAttribute($attr));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Perform on each URI within "srcset" attributes.
 | 
			
		||||
    foreach ($xpath->query("//*[@srcset]") as $node) {
 | 
			
		||||
      // @see https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-srcset
 | 
			
		||||
      // @see https://html.spec.whatwg.org/multipage/embedded-content.html#image-candidate-string
 | 
			
		||||
      $image_candidate_strings = explode(',', $node->getAttribute('srcset'));
 | 
			
		||||
      $image_candidate_strings = array_filter(array_map('trim', $image_candidate_strings));
 | 
			
		||||
      foreach ($image_candidate_strings as $key => $image_candidate_string) {
 | 
			
		||||
        if ($image_candidate_string[0] === '/' && $image_candidate_string[1] !== '/') {
 | 
			
		||||
          $image_candidate_strings[$key] = $scheme_and_host . $image_candidate_string;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      $node->setAttribute('srcset', implode(', ', $image_candidate_strings));
 | 
			
		||||
    }
 | 
			
		||||
    return Html::serialize($html_dom);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,41 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types = 1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
use Masterminds\HTML5\Serializer\OutputRules;
 | 
			
		||||
 | 
			
		||||
// cspell:ignore drupalhtmlbuilder
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Drupal-specific HTML5 serializer rules.
 | 
			
		||||
 *
 | 
			
		||||
 * Drupal's XSS filtering cannot handle entities inside element attribute
 | 
			
		||||
 * values. The XSS filtering was written based on W3C XML recommendations
 | 
			
		||||
 * which constituted that the ampersand character (&) and the angle
 | 
			
		||||
 * brackets (< and >) must not appear in their literal form in attribute
 | 
			
		||||
 * values. This differs from the HTML living standard which permits angle
 | 
			
		||||
 * brackets.
 | 
			
		||||
 *
 | 
			
		||||
 * @see core/modules/ckeditor5/js/ckeditor5_plugins/drupalHtmlEngine/src/drupalhtmlbuilder.js
 | 
			
		||||
 */
 | 
			
		||||
class HtmlSerializerRules extends OutputRules {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function escape($text, $attribute = FALSE) {
 | 
			
		||||
    $text = parent::escape($text, $attribute);
 | 
			
		||||
 | 
			
		||||
    if ($attribute) {
 | 
			
		||||
      $text = strtr($text, [
 | 
			
		||||
        '<' => '<',
 | 
			
		||||
        '>' => '>',
 | 
			
		||||
      ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $text;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								web/core/lib/Drupal/Component/Utility/Image.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								web/core/lib/Drupal/Component/Utility/Image.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides helpers to operate on images.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class Image {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Scales image dimensions while maintaining aspect ratio.
 | 
			
		||||
   *
 | 
			
		||||
   * The resulting dimensions can be smaller for one or both target dimensions.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $dimensions
 | 
			
		||||
   *   Dimensions to be modified - an array with components width and height, in
 | 
			
		||||
   *   pixels.
 | 
			
		||||
   * @param int $width
 | 
			
		||||
   *   (optional) The target width, in pixels. If this value is NULL then the
 | 
			
		||||
   *   scaling will be based only on the height value.
 | 
			
		||||
   * @param int $height
 | 
			
		||||
   *   (optional) The target height, in pixels. If this value is NULL then the
 | 
			
		||||
   *   scaling will be based only on the width value.
 | 
			
		||||
   * @param bool $upscale
 | 
			
		||||
   *   (optional) Boolean indicating that images smaller than the target
 | 
			
		||||
   *   dimensions will be scaled up. This generally results in a low quality
 | 
			
		||||
   *   image.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if $dimensions was modified, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public static function scaleDimensions(array &$dimensions, $width = NULL, $height = NULL, $upscale = FALSE) {
 | 
			
		||||
    $aspect = $dimensions['height'] / $dimensions['width'];
 | 
			
		||||
 | 
			
		||||
    // Calculate one of the dimensions from the other target dimension,
 | 
			
		||||
    // ensuring the same aspect ratio as the source dimensions. If one of the
 | 
			
		||||
    // target dimensions is missing, that is the one that is calculated. If both
 | 
			
		||||
    // are specified then the dimension calculated is the one that would not be
 | 
			
		||||
    // calculated to be bigger than its target.
 | 
			
		||||
    if (($width && !$height) || ($width && $height && $aspect < $height / $width)) {
 | 
			
		||||
      $height = (int) round($width * $aspect);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $width = (int) round($height / $aspect);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Don't upscale if the option isn't enabled.
 | 
			
		||||
    if (!$upscale && ($width >= $dimensions['width'] || $height >= $dimensions['height'])) {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $dimensions['width'] = $width;
 | 
			
		||||
    $dimensions['height'] = $height;
 | 
			
		||||
    return TRUE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the offset in pixels from the anchor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $anchor
 | 
			
		||||
   *   The anchor ('top', 'left', 'bottom', 'right', 'center').
 | 
			
		||||
   * @param int $current_size
 | 
			
		||||
   *   The current size, in pixels.
 | 
			
		||||
   * @param int $new_size
 | 
			
		||||
   *   The new size, in pixels.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The offset from the anchor, in pixels.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \InvalidArgumentException
 | 
			
		||||
   *   When the $anchor argument is not valid.
 | 
			
		||||
   */
 | 
			
		||||
  public static function getKeywordOffset(string $anchor, int $current_size, int $new_size): int {
 | 
			
		||||
    return match ($anchor) {
 | 
			
		||||
      'bottom', 'right' => $current_size - $new_size,
 | 
			
		||||
      'center' => (int) round($current_size / 2 - $new_size / 2),
 | 
			
		||||
      'top', 'left' => 0,
 | 
			
		||||
      default => throw new \InvalidArgumentException("Invalid anchor '{$anchor}' provided to getKeywordOffset()"),
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										339
									
								
								web/core/lib/Drupal/Component/Utility/LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								web/core/lib/Drupal/Component/Utility/LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,339 @@
 | 
			
		||||
        GNU GENERAL PUBLIC LICENSE
 | 
			
		||||
           Version 2, June 1991
 | 
			
		||||
 | 
			
		||||
 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 | 
			
		||||
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 | 
			
		||||
 Everyone is permitted to copy and distribute verbatim copies
 | 
			
		||||
 of this license document, but changing it is not allowed.
 | 
			
		||||
 | 
			
		||||
          Preamble
 | 
			
		||||
 | 
			
		||||
  The licenses for most software are designed to take away your
 | 
			
		||||
freedom to share and change it.  By contrast, the GNU General Public
 | 
			
		||||
License is intended to guarantee your freedom to share and change free
 | 
			
		||||
software--to make sure the software is free for all its users.  This
 | 
			
		||||
General Public License applies to most of the Free Software
 | 
			
		||||
Foundation's software and to any other program whose authors commit to
 | 
			
		||||
using it.  (Some other Free Software Foundation software is covered by
 | 
			
		||||
the GNU Lesser General Public License instead.)  You can apply it to
 | 
			
		||||
your programs, too.
 | 
			
		||||
 | 
			
		||||
  When we speak of free software, we are referring to freedom, not
 | 
			
		||||
price.  Our General Public Licenses are designed to make sure that you
 | 
			
		||||
have the freedom to distribute copies of free software (and charge for
 | 
			
		||||
this service if you wish), that you receive source code or can get it
 | 
			
		||||
if you want it, that you can change the software or use pieces of it
 | 
			
		||||
in new free programs; and that you know you can do these things.
 | 
			
		||||
 | 
			
		||||
  To protect your rights, we need to make restrictions that forbid
 | 
			
		||||
anyone to deny you these rights or to ask you to surrender the rights.
 | 
			
		||||
These restrictions translate to certain responsibilities for you if you
 | 
			
		||||
distribute copies of the software, or if you modify it.
 | 
			
		||||
 | 
			
		||||
  For example, if you distribute copies of such a program, whether
 | 
			
		||||
gratis or for a fee, you must give the recipients all the rights that
 | 
			
		||||
you have.  You must make sure that they, too, receive or can get the
 | 
			
		||||
source code.  And you must show them these terms so they know their
 | 
			
		||||
rights.
 | 
			
		||||
 | 
			
		||||
  We protect your rights with two steps: (1) copyright the software, and
 | 
			
		||||
(2) offer you this license which gives you legal permission to copy,
 | 
			
		||||
distribute and/or modify the software.
 | 
			
		||||
 | 
			
		||||
  Also, for each author's protection and ours, we want to make certain
 | 
			
		||||
that everyone understands that there is no warranty for this free
 | 
			
		||||
software.  If the software is modified by someone else and passed on, we
 | 
			
		||||
want its recipients to know that what they have is not the original, so
 | 
			
		||||
that any problems introduced by others will not reflect on the original
 | 
			
		||||
authors' reputations.
 | 
			
		||||
 | 
			
		||||
  Finally, any free program is threatened constantly by software
 | 
			
		||||
patents.  We wish to avoid the danger that redistributors of a free
 | 
			
		||||
program will individually obtain patent licenses, in effect making the
 | 
			
		||||
program proprietary.  To prevent this, we have made it clear that any
 | 
			
		||||
patent must be licensed for everyone's free use or not licensed at all.
 | 
			
		||||
 | 
			
		||||
  The precise terms and conditions for copying, distribution and
 | 
			
		||||
modification follow.
 | 
			
		||||
 | 
			
		||||
        GNU GENERAL PUBLIC LICENSE
 | 
			
		||||
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 | 
			
		||||
 | 
			
		||||
  0. This License applies to any program or other work which contains
 | 
			
		||||
a notice placed by the copyright holder saying it may be distributed
 | 
			
		||||
under the terms of this General Public License.  The "Program", below,
 | 
			
		||||
refers to any such program or work, and a "work based on the Program"
 | 
			
		||||
means either the Program or any derivative work under copyright law:
 | 
			
		||||
that is to say, a work containing the Program or a portion of it,
 | 
			
		||||
either verbatim or with modifications and/or translated into another
 | 
			
		||||
language.  (Hereinafter, translation is included without limitation in
 | 
			
		||||
the term "modification".)  Each licensee is addressed as "you".
 | 
			
		||||
 | 
			
		||||
Activities other than copying, distribution and modification are not
 | 
			
		||||
covered by this License; they are outside its scope.  The act of
 | 
			
		||||
running the Program is not restricted, and the output from the Program
 | 
			
		||||
is covered only if its contents constitute a work based on the
 | 
			
		||||
Program (independent of having been made by running the Program).
 | 
			
		||||
Whether that is true depends on what the Program does.
 | 
			
		||||
 | 
			
		||||
  1. You may copy and distribute verbatim copies of the Program's
 | 
			
		||||
source code as you receive it, in any medium, provided that you
 | 
			
		||||
conspicuously and appropriately publish on each copy an appropriate
 | 
			
		||||
copyright notice and disclaimer of warranty; keep intact all the
 | 
			
		||||
notices that refer to this License and to the absence of any warranty;
 | 
			
		||||
and give any other recipients of the Program a copy of this License
 | 
			
		||||
along with the Program.
 | 
			
		||||
 | 
			
		||||
You may charge a fee for the physical act of transferring a copy, and
 | 
			
		||||
you may at your option offer warranty protection in exchange for a fee.
 | 
			
		||||
 | 
			
		||||
  2. You may modify your copy or copies of the Program or any portion
 | 
			
		||||
of it, thus forming a work based on the Program, and copy and
 | 
			
		||||
distribute such modifications or work under the terms of Section 1
 | 
			
		||||
above, provided that you also meet all of these conditions:
 | 
			
		||||
 | 
			
		||||
    a) You must cause the modified files to carry prominent notices
 | 
			
		||||
    stating that you changed the files and the date of any change.
 | 
			
		||||
 | 
			
		||||
    b) You must cause any work that you distribute or publish, that in
 | 
			
		||||
    whole or in part contains or is derived from the Program or any
 | 
			
		||||
    part thereof, to be licensed as a whole at no charge to all third
 | 
			
		||||
    parties under the terms of this License.
 | 
			
		||||
 | 
			
		||||
    c) If the modified program normally reads commands interactively
 | 
			
		||||
    when run, you must cause it, when started running for such
 | 
			
		||||
    interactive use in the most ordinary way, to print or display an
 | 
			
		||||
    announcement including an appropriate copyright notice and a
 | 
			
		||||
    notice that there is no warranty (or else, saying that you provide
 | 
			
		||||
    a warranty) and that users may redistribute the program under
 | 
			
		||||
    these conditions, and telling the user how to view a copy of this
 | 
			
		||||
    License.  (Exception: if the Program itself is interactive but
 | 
			
		||||
    does not normally print such an announcement, your work based on
 | 
			
		||||
    the Program is not required to print an announcement.)
 | 
			
		||||
 | 
			
		||||
These requirements apply to the modified work as a whole.  If
 | 
			
		||||
identifiable sections of that work are not derived from the Program,
 | 
			
		||||
and can be reasonably considered independent and separate works in
 | 
			
		||||
themselves, then this License, and its terms, do not apply to those
 | 
			
		||||
sections when you distribute them as separate works.  But when you
 | 
			
		||||
distribute the same sections as part of a whole which is a work based
 | 
			
		||||
on the Program, the distribution of the whole must be on the terms of
 | 
			
		||||
this License, whose permissions for other licensees extend to the
 | 
			
		||||
entire whole, and thus to each and every part regardless of who wrote it.
 | 
			
		||||
 | 
			
		||||
Thus, it is not the intent of this section to claim rights or contest
 | 
			
		||||
your rights to work written entirely by you; rather, the intent is to
 | 
			
		||||
exercise the right to control the distribution of derivative or
 | 
			
		||||
collective works based on the Program.
 | 
			
		||||
 | 
			
		||||
In addition, mere aggregation of another work not based on the Program
 | 
			
		||||
with the Program (or with a work based on the Program) on a volume of
 | 
			
		||||
a storage or distribution medium does not bring the other work under
 | 
			
		||||
the scope of this License.
 | 
			
		||||
 | 
			
		||||
  3. You may copy and distribute the Program (or a work based on it,
 | 
			
		||||
under Section 2) in object code or executable form under the terms of
 | 
			
		||||
Sections 1 and 2 above provided that you also do one of the following:
 | 
			
		||||
 | 
			
		||||
    a) Accompany it with the complete corresponding machine-readable
 | 
			
		||||
    source code, which must be distributed under the terms of Sections
 | 
			
		||||
    1 and 2 above on a medium customarily used for software interchange; or,
 | 
			
		||||
 | 
			
		||||
    b) Accompany it with a written offer, valid for at least three
 | 
			
		||||
    years, to give any third party, for a charge no more than your
 | 
			
		||||
    cost of physically performing source distribution, a complete
 | 
			
		||||
    machine-readable copy of the corresponding source code, to be
 | 
			
		||||
    distributed under the terms of Sections 1 and 2 above on a medium
 | 
			
		||||
    customarily used for software interchange; or,
 | 
			
		||||
 | 
			
		||||
    c) Accompany it with the information you received as to the offer
 | 
			
		||||
    to distribute corresponding source code.  (This alternative is
 | 
			
		||||
    allowed only for noncommercial distribution and only if you
 | 
			
		||||
    received the program in object code or executable form with such
 | 
			
		||||
    an offer, in accord with Subsection b above.)
 | 
			
		||||
 | 
			
		||||
The source code for a work means the preferred form of the work for
 | 
			
		||||
making modifications to it.  For an executable work, complete source
 | 
			
		||||
code means all the source code for all modules it contains, plus any
 | 
			
		||||
associated interface definition files, plus the scripts used to
 | 
			
		||||
control compilation and installation of the executable.  However, as a
 | 
			
		||||
special exception, the source code distributed need not include
 | 
			
		||||
anything that is normally distributed (in either source or binary
 | 
			
		||||
form) with the major components (compiler, kernel, and so on) of the
 | 
			
		||||
operating system on which the executable runs, unless that component
 | 
			
		||||
itself accompanies the executable.
 | 
			
		||||
 | 
			
		||||
If distribution of executable or object code is made by offering
 | 
			
		||||
access to copy from a designated place, then offering equivalent
 | 
			
		||||
access to copy the source code from the same place counts as
 | 
			
		||||
distribution of the source code, even though third parties are not
 | 
			
		||||
compelled to copy the source along with the object code.
 | 
			
		||||
 | 
			
		||||
  4. You may not copy, modify, sublicense, or distribute the Program
 | 
			
		||||
except as expressly provided under this License.  Any attempt
 | 
			
		||||
otherwise to copy, modify, sublicense or distribute the Program is
 | 
			
		||||
void, and will automatically terminate your rights under this License.
 | 
			
		||||
However, parties who have received copies, or rights, from you under
 | 
			
		||||
this License will not have their licenses terminated so long as such
 | 
			
		||||
parties remain in full compliance.
 | 
			
		||||
 | 
			
		||||
  5. You are not required to accept this License, since you have not
 | 
			
		||||
signed it.  However, nothing else grants you permission to modify or
 | 
			
		||||
distribute the Program or its derivative works.  These actions are
 | 
			
		||||
prohibited by law if you do not accept this License.  Therefore, by
 | 
			
		||||
modifying or distributing the Program (or any work based on the
 | 
			
		||||
Program), you indicate your acceptance of this License to do so, and
 | 
			
		||||
all its terms and conditions for copying, distributing or modifying
 | 
			
		||||
the Program or works based on it.
 | 
			
		||||
 | 
			
		||||
  6. Each time you redistribute the Program (or any work based on the
 | 
			
		||||
Program), the recipient automatically receives a license from the
 | 
			
		||||
original licensor to copy, distribute or modify the Program subject to
 | 
			
		||||
these terms and conditions.  You may not impose any further
 | 
			
		||||
restrictions on the recipients' exercise of the rights granted herein.
 | 
			
		||||
You are not responsible for enforcing compliance by third parties to
 | 
			
		||||
this License.
 | 
			
		||||
 | 
			
		||||
  7. If, as a consequence of a court judgment or allegation of patent
 | 
			
		||||
infringement or for any other reason (not limited to patent issues),
 | 
			
		||||
conditions are imposed on you (whether by court order, agreement or
 | 
			
		||||
otherwise) that contradict the conditions of this License, they do not
 | 
			
		||||
excuse you from the conditions of this License.  If you cannot
 | 
			
		||||
distribute so as to satisfy simultaneously your obligations under this
 | 
			
		||||
License and any other pertinent obligations, then as a consequence you
 | 
			
		||||
may not distribute the Program at all.  For example, if a patent
 | 
			
		||||
license would not permit royalty-free redistribution of the Program by
 | 
			
		||||
all those who receive copies directly or indirectly through you, then
 | 
			
		||||
the only way you could satisfy both it and this License would be to
 | 
			
		||||
refrain entirely from distribution of the Program.
 | 
			
		||||
 | 
			
		||||
If any portion of this section is held invalid or unenforceable under
 | 
			
		||||
any particular circumstance, the balance of the section is intended to
 | 
			
		||||
apply and the section as a whole is intended to apply in other
 | 
			
		||||
circumstances.
 | 
			
		||||
 | 
			
		||||
It is not the purpose of this section to induce you to infringe any
 | 
			
		||||
patents or other property right claims or to contest validity of any
 | 
			
		||||
such claims; this section has the sole purpose of protecting the
 | 
			
		||||
integrity of the free software distribution system, which is
 | 
			
		||||
implemented by public license practices.  Many people have made
 | 
			
		||||
generous contributions to the wide range of software distributed
 | 
			
		||||
through that system in reliance on consistent application of that
 | 
			
		||||
system; it is up to the author/donor to decide if he or she is willing
 | 
			
		||||
to distribute software through any other system and a licensee cannot
 | 
			
		||||
impose that choice.
 | 
			
		||||
 | 
			
		||||
This section is intended to make thoroughly clear what is believed to
 | 
			
		||||
be a consequence of the rest of this License.
 | 
			
		||||
 | 
			
		||||
  8. If the distribution and/or use of the Program is restricted in
 | 
			
		||||
certain countries either by patents or by copyrighted interfaces, the
 | 
			
		||||
original copyright holder who places the Program under this License
 | 
			
		||||
may add an explicit geographical distribution limitation excluding
 | 
			
		||||
those countries, so that distribution is permitted only in or among
 | 
			
		||||
countries not thus excluded.  In such case, this License incorporates
 | 
			
		||||
the limitation as if written in the body of this License.
 | 
			
		||||
 | 
			
		||||
  9. The Free Software Foundation may publish revised and/or new versions
 | 
			
		||||
of the General Public License from time to time.  Such new versions will
 | 
			
		||||
be similar in spirit to the present version, but may differ in detail to
 | 
			
		||||
address new problems or concerns.
 | 
			
		||||
 | 
			
		||||
Each version is given a distinguishing version number.  If the Program
 | 
			
		||||
specifies a version number of this License which applies to it and "any
 | 
			
		||||
later version", you have the option of following the terms and conditions
 | 
			
		||||
either of that version or of any later version published by the Free
 | 
			
		||||
Software Foundation.  If the Program does not specify a version number of
 | 
			
		||||
this License, you may choose any version ever published by the Free Software
 | 
			
		||||
Foundation.
 | 
			
		||||
 | 
			
		||||
  10. If you wish to incorporate parts of the Program into other free
 | 
			
		||||
programs whose distribution conditions are different, write to the author
 | 
			
		||||
to ask for permission.  For software which is copyrighted by the Free
 | 
			
		||||
Software Foundation, write to the Free Software Foundation; we sometimes
 | 
			
		||||
make exceptions for this.  Our decision will be guided by the two goals
 | 
			
		||||
of preserving the free status of all derivatives of our free software and
 | 
			
		||||
of promoting the sharing and reuse of software generally.
 | 
			
		||||
 | 
			
		||||
          NO WARRANTY
 | 
			
		||||
 | 
			
		||||
  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
 | 
			
		||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
 | 
			
		||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
 | 
			
		||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
 | 
			
		||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
			
		||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
 | 
			
		||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
 | 
			
		||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
 | 
			
		||||
REPAIR OR CORRECTION.
 | 
			
		||||
 | 
			
		||||
  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 | 
			
		||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
 | 
			
		||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
 | 
			
		||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
 | 
			
		||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
 | 
			
		||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
 | 
			
		||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
 | 
			
		||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
 | 
			
		||||
POSSIBILITY OF SUCH DAMAGES.
 | 
			
		||||
 | 
			
		||||
         END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
      How to Apply These Terms to Your New Programs
 | 
			
		||||
 | 
			
		||||
  If you develop a new program, and you want it to be of the greatest
 | 
			
		||||
possible use to the public, the best way to achieve this is to make it
 | 
			
		||||
free software which everyone can redistribute and change under these terms.
 | 
			
		||||
 | 
			
		||||
  To do so, attach the following notices to the program.  It is safest
 | 
			
		||||
to attach them to the start of each source file to most effectively
 | 
			
		||||
convey the exclusion of warranty; and each file should have at least
 | 
			
		||||
the "copyright" line and a pointer to where the full notice is found.
 | 
			
		||||
 | 
			
		||||
    <one line to give the program's name and a brief idea of what it does.>
 | 
			
		||||
    Copyright (C) <year>  <name of author>
 | 
			
		||||
 | 
			
		||||
    This program is free software; you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU General Public License as published by
 | 
			
		||||
    the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
    (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
    This program is distributed in the hope that it will be useful,
 | 
			
		||||
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
    GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
    You should have received a copy of the GNU General Public License along
 | 
			
		||||
    with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 | 
			
		||||
Also add information on how to contact you by electronic and paper mail.
 | 
			
		||||
 | 
			
		||||
If the program is interactive, make it output a short notice like this
 | 
			
		||||
when it starts in an interactive mode:
 | 
			
		||||
 | 
			
		||||
    Gnomovision version 69, Copyright (C) year name of author
 | 
			
		||||
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
 | 
			
		||||
    This is free software, and you are welcome to redistribute it
 | 
			
		||||
    under certain conditions; type `show c' for details.
 | 
			
		||||
 | 
			
		||||
The hypothetical commands `show w' and `show c' should show the appropriate
 | 
			
		||||
parts of the General Public License.  Of course, the commands you use may
 | 
			
		||||
be called something other than `show w' and `show c'; they could even be
 | 
			
		||||
mouse-clicks or menu items--whatever suits your program.
 | 
			
		||||
 | 
			
		||||
You should also get your employer (if you work as a programmer) or your
 | 
			
		||||
school, if any, to sign a "copyright disclaimer" for the program, if
 | 
			
		||||
necessary.  Here is a sample; alter the names:
 | 
			
		||||
 | 
			
		||||
  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
 | 
			
		||||
  `Gnomovision' (which makes passes at compilers) written by James Hacker.
 | 
			
		||||
 | 
			
		||||
  <signature of Ty Coon>, 1 April 1989
 | 
			
		||||
  Ty Coon, President of Vice
 | 
			
		||||
 | 
			
		||||
This General Public License does not permit incorporating your program into
 | 
			
		||||
proprietary programs.  If your program is a subroutine library, you may
 | 
			
		||||
consider it more useful to permit linking proprietary applications with the
 | 
			
		||||
library.  If this is what you want to do, use the GNU Lesser General
 | 
			
		||||
Public License instead of this License.
 | 
			
		||||
							
								
								
									
										372
									
								
								web/core/lib/Drupal/Component/Utility/NestedArray.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								web/core/lib/Drupal/Component/Utility/NestedArray.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,372 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides methods for working with nested arrays of variable depth.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class NestedArray {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieves a value from a nested array with variable depth.
 | 
			
		||||
   *
 | 
			
		||||
   * This helper function should be used when the depth of the array element
 | 
			
		||||
   * being retrieved may vary (that is, the number of parent keys is variable).
 | 
			
		||||
   * It is primarily used for form structures and renderable arrays.
 | 
			
		||||
   *
 | 
			
		||||
   * Without this helper function the only way to get a nested array value with
 | 
			
		||||
   * variable depth in one line would be using eval(), which should be avoided:
 | 
			
		||||
   * @code
 | 
			
		||||
   * // Do not do this! Avoid eval().
 | 
			
		||||
   * // May also throw a PHP notice, if the variable array keys do not exist.
 | 
			
		||||
   * eval('$value = $array[\'' . implode("']['", $parents) . "'];");
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * Instead, use this helper function:
 | 
			
		||||
   * @code
 | 
			
		||||
   * $value = NestedArray::getValue($form, $parents);
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * A return value of NULL is ambiguous, and can mean either that the requested
 | 
			
		||||
   * key does not exist, or that the actual value is NULL. If it is required to
 | 
			
		||||
   * know whether the nested array key actually exists, pass a third argument
 | 
			
		||||
   * that is altered by reference:
 | 
			
		||||
   * @code
 | 
			
		||||
   * $key_exists = NULL;
 | 
			
		||||
   * $value = NestedArray::getValue($form, $parents, $key_exists);
 | 
			
		||||
   * if ($key_exists) {
 | 
			
		||||
   *   // Do something with $value.
 | 
			
		||||
   * }
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * However if the number of array parent keys is static, the value should
 | 
			
		||||
   * always be retrieved directly rather than calling this function.
 | 
			
		||||
   * For instance:
 | 
			
		||||
   * @code
 | 
			
		||||
   * $value = $form['signature_settings']['signature'];
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $array
 | 
			
		||||
   *   The array from which to get the value.
 | 
			
		||||
   * @param array $parents
 | 
			
		||||
   *   An array of parent keys of the value, starting with the outermost key.
 | 
			
		||||
   * @param bool $key_exists
 | 
			
		||||
   *   (optional) If given, an already defined variable that is altered by
 | 
			
		||||
   *   reference.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed
 | 
			
		||||
   *   The requested nested value. Possibly NULL if the value is NULL or not all
 | 
			
		||||
   *   nested parent keys exist. $key_exists is altered by reference and is a
 | 
			
		||||
   *   Boolean that indicates whether all nested parent keys exist (TRUE) or not
 | 
			
		||||
   *   (FALSE). This allows to distinguish between the two possibilities when
 | 
			
		||||
   *   NULL is returned.
 | 
			
		||||
   *
 | 
			
		||||
   * @see NestedArray::setValue()
 | 
			
		||||
   * @see NestedArray::unsetValue()
 | 
			
		||||
   */
 | 
			
		||||
  public static function &getValue(array &$array, array $parents, &$key_exists = NULL) {
 | 
			
		||||
    $ref = &$array;
 | 
			
		||||
    foreach ($parents as $parent) {
 | 
			
		||||
      if (is_array($ref) && \array_key_exists($parent, $ref)) {
 | 
			
		||||
        $ref = &$ref[$parent];
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $key_exists = FALSE;
 | 
			
		||||
        $null = NULL;
 | 
			
		||||
        return $null;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $key_exists = TRUE;
 | 
			
		||||
    return $ref;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets a value in a nested array with variable depth.
 | 
			
		||||
   *
 | 
			
		||||
   * This helper function should be used when the depth of the array element you
 | 
			
		||||
   * are changing may vary (that is, the number of parent keys is variable). It
 | 
			
		||||
   * is primarily used for form structures and renderable arrays.
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * @code
 | 
			
		||||
   * // Assume you have a 'signature' element somewhere in a form. It might be:
 | 
			
		||||
   * $form['signature_settings']['signature'] = [
 | 
			
		||||
   *   '#type' => 'text_format',
 | 
			
		||||
   *   '#title' => t('Signature'),
 | 
			
		||||
   * ];
 | 
			
		||||
   * // Or, it might be further nested:
 | 
			
		||||
   * $form['signature_settings']['user']['signature'] = [
 | 
			
		||||
   *   '#type' => 'text_format',
 | 
			
		||||
   *   '#title' => t('Signature'),
 | 
			
		||||
   * ];
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * To deal with the situation, the code needs to figure out the route to the
 | 
			
		||||
   * element, given an array of parents that is either
 | 
			
		||||
   * @code ['signature_settings', 'signature'] @endcode
 | 
			
		||||
   * in the first case or
 | 
			
		||||
   * @code ['signature_settings', 'user', 'signature'] @endcode
 | 
			
		||||
   * in the second case.
 | 
			
		||||
   *
 | 
			
		||||
   * Without this helper function the only way to set the signature element in
 | 
			
		||||
   * one line would be using eval(), which should be avoided:
 | 
			
		||||
   * @code
 | 
			
		||||
   * // Do not do this! Avoid eval().
 | 
			
		||||
   * eval('$form[\'' . implode("']['", $parents) . '\'] = $element;');
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * Instead, use this helper function:
 | 
			
		||||
   * @code
 | 
			
		||||
   * NestedArray::setValue($form, $parents, $element);
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * However if the number of array parent keys is static, the value should
 | 
			
		||||
   * always be set directly rather than calling this function. For instance,
 | 
			
		||||
   * for the first example we could just do:
 | 
			
		||||
   * @code
 | 
			
		||||
   * $form['signature_settings']['signature'] = $element;
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $array
 | 
			
		||||
   *   A reference to the array to modify.
 | 
			
		||||
   * @param array $parents
 | 
			
		||||
   *   An array of parent keys, starting with the outermost key.
 | 
			
		||||
   * @param mixed $value
 | 
			
		||||
   *   The value to set.
 | 
			
		||||
   * @param bool $force
 | 
			
		||||
   *   (optional) If TRUE, the value is forced into the structure even if it
 | 
			
		||||
   *   requires the deletion of an already existing non-array parent value. If
 | 
			
		||||
   *   FALSE, throws an exception if trying to add into a value that is not an
 | 
			
		||||
   *   array. Defaults to FALSE.
 | 
			
		||||
   *
 | 
			
		||||
   * @see NestedArray::unsetValue()
 | 
			
		||||
   * @see NestedArray::getValue()
 | 
			
		||||
   */
 | 
			
		||||
  public static function setValue(array &$array, array $parents, $value, $force = FALSE) {
 | 
			
		||||
    $ref = &$array;
 | 
			
		||||
    foreach ($parents as $parent) {
 | 
			
		||||
      // PHP auto-creates container arrays and NULL entries without error if
 | 
			
		||||
      // $ref is NULL, but throws an error if $ref is set, but not an array.
 | 
			
		||||
      if (isset($ref) && !is_array($ref)) {
 | 
			
		||||
        if (!$force) {
 | 
			
		||||
          throw new \LogicException('Cannot create key "' . $parent . '" on non-array value.');
 | 
			
		||||
        }
 | 
			
		||||
        $ref = [];
 | 
			
		||||
      }
 | 
			
		||||
      $ref = &$ref[$parent];
 | 
			
		||||
    }
 | 
			
		||||
    $ref = $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Unsets a value in a nested array with variable depth.
 | 
			
		||||
   *
 | 
			
		||||
   * This helper function should be used when the depth of the array element you
 | 
			
		||||
   * are changing may vary (that is, the number of parent keys is variable). It
 | 
			
		||||
   * is primarily used for form structures and renderable arrays.
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * @code
 | 
			
		||||
   * // Assume you have a 'signature' element somewhere in a form. It might be:
 | 
			
		||||
   * $form['signature_settings']['signature'] = [
 | 
			
		||||
   *   '#type' => 'text_format',
 | 
			
		||||
   *   '#title' => t('Signature'),
 | 
			
		||||
   * ];
 | 
			
		||||
   * // Or, it might be further nested:
 | 
			
		||||
   * $form['signature_settings']['user']['signature'] = [
 | 
			
		||||
   *   '#type' => 'text_format',
 | 
			
		||||
   *   '#title' => t('Signature'),
 | 
			
		||||
   * ];
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * To deal with the situation, the code needs to figure out the route to the
 | 
			
		||||
   * element, given an array of parents that is either
 | 
			
		||||
   * @code ['signature_settings', 'signature'] @endcode
 | 
			
		||||
   * in the first case or
 | 
			
		||||
   * @code ['signature_settings', 'user', 'signature'] @endcode
 | 
			
		||||
   * in the second case.
 | 
			
		||||
   *
 | 
			
		||||
   * Without this helper function the only way to unset the signature element in
 | 
			
		||||
   * one line would be using eval(), which should be avoided:
 | 
			
		||||
   * @code
 | 
			
		||||
   * // Do not do this! Avoid eval().
 | 
			
		||||
   * eval('unset($form[\'' . implode("']['", $parents) . '\']);');
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * Instead, use this helper function:
 | 
			
		||||
   * @code
 | 
			
		||||
   * NestedArray::unsetValue($form, $parents, $element);
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * However if the number of array parent keys is static, the value should
 | 
			
		||||
   * always be set directly rather than calling this function. For instance, for
 | 
			
		||||
   * the first example we could just do:
 | 
			
		||||
   * @code
 | 
			
		||||
   * unset($form['signature_settings']['signature']);
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $array
 | 
			
		||||
   *   A reference to the array to modify.
 | 
			
		||||
   * @param array $parents
 | 
			
		||||
   *   An array of parent keys, starting with the outermost key and including
 | 
			
		||||
   *   the key to be unset.
 | 
			
		||||
   * @param bool $key_existed
 | 
			
		||||
   *   (optional) If given, an already defined variable that is altered by
 | 
			
		||||
   *   reference.
 | 
			
		||||
   *
 | 
			
		||||
   * @see NestedArray::setValue()
 | 
			
		||||
   * @see NestedArray::getValue()
 | 
			
		||||
   */
 | 
			
		||||
  public static function unsetValue(array &$array, array $parents, &$key_existed = NULL) {
 | 
			
		||||
    $unset_key = array_pop($parents);
 | 
			
		||||
    $ref = &self::getValue($array, $parents, $key_existed);
 | 
			
		||||
    if ($key_existed && is_array($ref) && \array_key_exists($unset_key, $ref)) {
 | 
			
		||||
      $key_existed = TRUE;
 | 
			
		||||
      unset($ref[$unset_key]);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $key_existed = FALSE;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines whether a nested array contains the requested keys.
 | 
			
		||||
   *
 | 
			
		||||
   * This helper function should be used when the depth of the array element to
 | 
			
		||||
   * be checked may vary (that is, the number of parent keys is variable). See
 | 
			
		||||
   * NestedArray::setValue() for details. It is primarily used for form
 | 
			
		||||
   * structures and renderable arrays.
 | 
			
		||||
   *
 | 
			
		||||
   * If it is required to also get the value of the checked nested key, use
 | 
			
		||||
   * NestedArray::getValue() instead.
 | 
			
		||||
   *
 | 
			
		||||
   * If the number of array parent keys is static, this helper function is
 | 
			
		||||
   * unnecessary and the following code can be used instead:
 | 
			
		||||
   * @code
 | 
			
		||||
   * $value_exists = isset($form['signature_settings']['signature']);
 | 
			
		||||
   * $key_exists = array_key_exists('signature', $form['signature_settings']);
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $array
 | 
			
		||||
   *   The array with the value to check for.
 | 
			
		||||
   * @param array $parents
 | 
			
		||||
   *   An array of parent keys of the value, starting with the outermost key.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if all the parent keys exist, FALSE otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @see NestedArray::getValue()
 | 
			
		||||
   */
 | 
			
		||||
  public static function keyExists(array $array, array $parents) {
 | 
			
		||||
    // Although this function is similar to PHP's array_key_exists(), its
 | 
			
		||||
    // arguments should be consistent with getValue().
 | 
			
		||||
    $key_exists = NULL;
 | 
			
		||||
    self::getValue($array, $parents, $key_exists);
 | 
			
		||||
    return $key_exists;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Merges multiple arrays, recursively, and returns the merged array.
 | 
			
		||||
   *
 | 
			
		||||
   * This function is similar to PHP's array_merge_recursive() function, but it
 | 
			
		||||
   * handles non-array values differently. When merging values that are not both
 | 
			
		||||
   * arrays, the latter value replaces the former rather than merging with it.
 | 
			
		||||
   *
 | 
			
		||||
   * Example:
 | 
			
		||||
   * @code
 | 
			
		||||
   * $link_options_1 = ['fragment' => 'x', 'attributes' => ['title' => t('X'), 'class' => ['a', 'b']]];
 | 
			
		||||
   * $link_options_2 = ['fragment' => 'y', 'attributes' => ['title' => t('Y'), 'class' => ['c', 'd']]];
 | 
			
		||||
   *
 | 
			
		||||
   * // This results in ['fragment' => ['x', 'y'], 'attributes' => ['title' => [t('X'), t('Y')], 'class' => ['a', 'b', 'c', 'd']]].
 | 
			
		||||
   * $incorrect = array_merge_recursive($link_options_1, $link_options_2);
 | 
			
		||||
   *
 | 
			
		||||
   * // This results in ['fragment' => 'y', 'attributes' => ['title' => t('Y'), 'class' => ['a', 'b', 'c', 'd']]].
 | 
			
		||||
   * $correct = NestedArray::mergeDeep($link_options_1, $link_options_2);
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * @param array ...$arrays
 | 
			
		||||
   *   Arrays to merge.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The merged array.
 | 
			
		||||
   *
 | 
			
		||||
   * @see NestedArray::mergeDeepArray()
 | 
			
		||||
   */
 | 
			
		||||
  public static function mergeDeep(...$arrays) {
 | 
			
		||||
    return self::mergeDeepArray($arrays);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Merges multiple arrays, recursively, and returns the merged array.
 | 
			
		||||
   *
 | 
			
		||||
   * This function is equivalent to NestedArray::mergeDeep(), except the
 | 
			
		||||
   * input arrays are passed as a single array parameter rather than a variable
 | 
			
		||||
   * parameter list.
 | 
			
		||||
   *
 | 
			
		||||
   * The following are equivalent:
 | 
			
		||||
   * - NestedArray::mergeDeep($a, $b);
 | 
			
		||||
   * - NestedArray::mergeDeepArray([$a, $b]);
 | 
			
		||||
   *
 | 
			
		||||
   * The following are also equivalent:
 | 
			
		||||
   * - call_user_func_array('NestedArray::mergeDeep', $arrays_to_merge);
 | 
			
		||||
   * - NestedArray::mergeDeepArray($arrays_to_merge);
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $arrays
 | 
			
		||||
   *   An arrays of arrays to merge.
 | 
			
		||||
   * @param bool $preserve_integer_keys
 | 
			
		||||
   *   (optional) If given, integer keys will be preserved and merged instead of
 | 
			
		||||
   *   appended. Defaults to FALSE.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The merged array.
 | 
			
		||||
   *
 | 
			
		||||
   * @see NestedArray::mergeDeep()
 | 
			
		||||
   */
 | 
			
		||||
  public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FALSE) {
 | 
			
		||||
    $result = [];
 | 
			
		||||
    foreach ($arrays as $array) {
 | 
			
		||||
      foreach ($array as $key => $value) {
 | 
			
		||||
        // Renumber integer keys as array_merge_recursive() does unless
 | 
			
		||||
        // $preserve_integer_keys is set to TRUE. Note that PHP automatically
 | 
			
		||||
        // converts array keys that are integer strings (e.g., '1') to integers.
 | 
			
		||||
        if (is_int($key) && !$preserve_integer_keys) {
 | 
			
		||||
          $result[] = $value;
 | 
			
		||||
        }
 | 
			
		||||
        // Recurse when both values are arrays.
 | 
			
		||||
        elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
 | 
			
		||||
          $result[$key] = self::mergeDeepArray([$result[$key], $value], $preserve_integer_keys);
 | 
			
		||||
        }
 | 
			
		||||
        // Otherwise, use the latter value, overriding any previous value.
 | 
			
		||||
        else {
 | 
			
		||||
          $result[$key] = $value;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Filters a nested array recursively.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $array
 | 
			
		||||
   *   The filtered nested array.
 | 
			
		||||
   * @param callable|null $callable
 | 
			
		||||
   *   The callable to apply for filtering.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The filtered array.
 | 
			
		||||
   */
 | 
			
		||||
  public static function filter(array $array, ?callable $callable = NULL) {
 | 
			
		||||
    $array = is_callable($callable) ? array_filter($array, $callable) : array_filter($array);
 | 
			
		||||
    foreach ($array as &$element) {
 | 
			
		||||
      if (is_array($element)) {
 | 
			
		||||
        $element = static::filter($element, $callable);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $array;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										115
									
								
								web/core/lib/Drupal/Component/Utility/Number.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								web/core/lib/Drupal/Component/Utility/Number.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,115 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides helper methods for manipulating numbers.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class Number {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Verifies that a number is a multiple of a given step.
 | 
			
		||||
   *
 | 
			
		||||
   * The implementation assumes it is dealing with IEEE 754 double precision
 | 
			
		||||
   * floating point numbers that are used by PHP on most systems.
 | 
			
		||||
   *
 | 
			
		||||
   * This is based on the number/range verification methods of webkit.
 | 
			
		||||
   *
 | 
			
		||||
   * @param float $value
 | 
			
		||||
   *   The value that needs to be checked.
 | 
			
		||||
   * @param float $step
 | 
			
		||||
   *   The step scale factor. Must be positive.
 | 
			
		||||
   * @param float $offset
 | 
			
		||||
   *   (optional) An offset, to which the difference must be a multiple of the
 | 
			
		||||
   *   given step.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if no step mismatch has occurred, or FALSE otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @see http://opensource.apple.com/source/WebCore/WebCore-1298/html/NumberInputType.cpp
 | 
			
		||||
   */
 | 
			
		||||
  public static function validStep($value, $step, $offset = 0.0) {
 | 
			
		||||
    $double_value = (float) abs($value - $offset);
 | 
			
		||||
 | 
			
		||||
    // The fractional part of a double has 53 bits. The greatest number that
 | 
			
		||||
    // could be represented with that is 2^53. If the given value is even bigger
 | 
			
		||||
    // than $step * 2^53, then dividing by $step will result in a very small
 | 
			
		||||
    // remainder. Since that remainder can't even be represented with a single
 | 
			
		||||
    // precision float the following computation of the remainder makes no sense
 | 
			
		||||
    // and we can safely ignore it instead.
 | 
			
		||||
    if ($double_value / pow(2.0, 53) > $step) {
 | 
			
		||||
      return TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Now compute that remainder of a division by $step.
 | 
			
		||||
    $remainder = (float) abs($double_value - $step * round($double_value / $step));
 | 
			
		||||
 | 
			
		||||
    // $remainder is a double precision floating point number. Remainders that
 | 
			
		||||
    // can't be represented with single precision floats are acceptable. The
 | 
			
		||||
    // fractional part of a float has 24 bits. That means remainders smaller
 | 
			
		||||
    // than $step * 2^-24 are acceptable.
 | 
			
		||||
    $computed_acceptable_error = (float) ($step / pow(2.0, 24));
 | 
			
		||||
 | 
			
		||||
    return $computed_acceptable_error >= $remainder || $remainder >= ($step - $computed_acceptable_error);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generates a sorting code from an integer.
 | 
			
		||||
   *
 | 
			
		||||
   * Consists of a leading character indicating length, followed by N digits
 | 
			
		||||
   * with a numerical value in base 36 (alphadecimal). These codes can be sorted
 | 
			
		||||
   * as strings without altering numerical order.
 | 
			
		||||
   *
 | 
			
		||||
   * It goes:
 | 
			
		||||
   * 00, 01, 02, ..., 0y, 0z,
 | 
			
		||||
   * 110, 111, ... , 1zy, 1zz,
 | 
			
		||||
   * 2100, 2101, ..., 2zzy, 2zzz,
 | 
			
		||||
   * 31000, 31001, ...
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $i
 | 
			
		||||
   *   The integer value to convert.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The alpha decimal value.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\Number::alphadecimalToInt
 | 
			
		||||
   */
 | 
			
		||||
  public static function intToAlphadecimal($i = 0) {
 | 
			
		||||
    $num = base_convert((string) $i, 10, 36);
 | 
			
		||||
    $length = strlen($num);
 | 
			
		||||
 | 
			
		||||
    return chr($length + ord('0') - 1) . $num;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Decodes a sorting code back to an integer.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $string
 | 
			
		||||
   *   The alpha decimal value to convert.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The integer value.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \InvalidArgumentException
 | 
			
		||||
   *   If $string contains invalid characters, throw an exception.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\Number::intToAlphadecimal
 | 
			
		||||
   */
 | 
			
		||||
  public static function alphadecimalToInt($string = '00') {
 | 
			
		||||
    // For backwards compatibility, we must accept NULL
 | 
			
		||||
    // and the empty string, returning 0,
 | 
			
		||||
    // like (int) base_convert(substr($string, 1), 36, 10) always did.
 | 
			
		||||
    if ('' === $string || NULL === $string) {
 | 
			
		||||
      @trigger_error('Passing NULL or an empty string to ' . __METHOD__ . '() is deprecated in drupal:11.2.0 and will be removed in drupal:12.0.0. See https://www.drupal.org/node/3494472', E_USER_DEPRECATED);
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
    $alpha_decimal_substring = substr($string, 1);
 | 
			
		||||
    if (!ctype_alnum($alpha_decimal_substring)) {
 | 
			
		||||
      throw new \InvalidArgumentException("Invalid characters passed for attempted conversion: $string");
 | 
			
		||||
    }
 | 
			
		||||
    return (int) base_convert($alpha_decimal_substring, 36, 10);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								web/core/lib/Drupal/Component/Utility/OpCodeCache.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								web/core/lib/Drupal/Component/Utility/OpCodeCache.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides helpers to handle PHP opcode caches.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class OpCodeCache {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks if OpCodeCache is enabled.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if OPcache is enabled, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public static function isEnabled() {
 | 
			
		||||
    return extension_loaded('Zend OPcache') && ini_get('opcache.enable');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Invalidates a PHP file from a possibly active opcode cache.
 | 
			
		||||
   *
 | 
			
		||||
   * In case the opcode cache does not support to invalidate an individual file,
 | 
			
		||||
   * the entire cache will be flushed.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $pathname
 | 
			
		||||
   *   The absolute pathname of the PHP file to invalidate.
 | 
			
		||||
   */
 | 
			
		||||
  public static function invalidate($pathname) {
 | 
			
		||||
    clearstatcache(TRUE, $pathname);
 | 
			
		||||
 | 
			
		||||
    // Check if the Zend OPcache is enabled and if so invalidate the file.
 | 
			
		||||
    if (function_exists('opcache_invalidate')) {
 | 
			
		||||
      opcache_invalidate($pathname, TRUE);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								web/core/lib/Drupal/Component/Utility/README.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								web/core/lib/Drupal/Component/Utility/README.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
The Drupal Utility Component
 | 
			
		||||
 | 
			
		||||
Thanks for using this Drupal component.
 | 
			
		||||
 | 
			
		||||
You can participate in its development on Drupal.org, through our issue system:
 | 
			
		||||
https://www.drupal.org/project/issues/drupal
 | 
			
		||||
 | 
			
		||||
You can get the full Drupal repo here:
 | 
			
		||||
https://www.drupal.org/project/drupal/git-instructions
 | 
			
		||||
 | 
			
		||||
You can browse the full Drupal repo here:
 | 
			
		||||
https://git.drupalcode.org/project/drupal
 | 
			
		||||
							
								
								
									
										361
									
								
								web/core/lib/Drupal/Component/Utility/Random.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										361
									
								
								web/core/lib/Drupal/Component/Utility/Random.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,361 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a utility class for creating random data.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class Random {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The maximum number of times name() and string() can loop.
 | 
			
		||||
   *
 | 
			
		||||
   * This prevents infinite loops if the length of the random value is very
 | 
			
		||||
   * small.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Tests\Component\Utility\RandomTest
 | 
			
		||||
   */
 | 
			
		||||
  const MAXIMUM_TRIES = 100;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A list of unique strings generated by string().
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $strings = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A list of unique names generated by name().
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $names = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A list of unique names generated by machineName().
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected array $machineNames = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generates a random string of ASCII characters of codes 32 to 126.
 | 
			
		||||
   *
 | 
			
		||||
   * The generated string includes alpha-numeric characters and common
 | 
			
		||||
   * miscellaneous characters. Use this method when testing general input
 | 
			
		||||
   * where the content is not restricted.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $length
 | 
			
		||||
   *   Length of random string to generate.
 | 
			
		||||
   * @param bool $unique
 | 
			
		||||
   *   (optional) If TRUE ensures that the random string returned is unique.
 | 
			
		||||
   *   Defaults to FALSE.
 | 
			
		||||
   * @param callable $validator
 | 
			
		||||
   *   (optional) A callable to validate the string. Defaults to NULL.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Randomly generated string.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\Random::name()
 | 
			
		||||
   */
 | 
			
		||||
  public function string($length = 8, $unique = FALSE, $validator = NULL) {
 | 
			
		||||
    $counter = 0;
 | 
			
		||||
 | 
			
		||||
    // Continue to loop if $unique is TRUE and the generated string is not
 | 
			
		||||
    // unique or if $validator is a callable that returns FALSE. To generate a
 | 
			
		||||
    // random string this loop must be carried out at least once.
 | 
			
		||||
    do {
 | 
			
		||||
      if ($counter == static::MAXIMUM_TRIES) {
 | 
			
		||||
        throw new \RuntimeException('Unable to generate a unique random name');
 | 
			
		||||
      }
 | 
			
		||||
      $str = '';
 | 
			
		||||
      for ($i = 0; $i < $length; $i++) {
 | 
			
		||||
        $str .= chr(mt_rand(32, 126));
 | 
			
		||||
      }
 | 
			
		||||
      $counter++;
 | 
			
		||||
 | 
			
		||||
      $continue = FALSE;
 | 
			
		||||
      if ($unique) {
 | 
			
		||||
        $continue = isset($this->strings[$str]);
 | 
			
		||||
      }
 | 
			
		||||
      if (!$continue && is_callable($validator)) {
 | 
			
		||||
        // If the validator callback returns FALSE generate another random
 | 
			
		||||
        // string.
 | 
			
		||||
        $continue = !call_user_func($validator, $str);
 | 
			
		||||
      }
 | 
			
		||||
    } while ($continue);
 | 
			
		||||
 | 
			
		||||
    if ($unique) {
 | 
			
		||||
      $this->strings[$str] = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $str;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generates a random string containing letters and numbers.
 | 
			
		||||
   *
 | 
			
		||||
   * The string will always start with a letter. The letters may be upper or
 | 
			
		||||
   * lower case. This method is better for restricted inputs that do not
 | 
			
		||||
   * accept certain characters. For example, when testing input fields that
 | 
			
		||||
   * require machine readable values (i.e. without spaces and non-standard
 | 
			
		||||
   * characters) this method is best.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $length
 | 
			
		||||
   *   Length of random string to generate.
 | 
			
		||||
   * @param bool $unique
 | 
			
		||||
   *   (optional) If TRUE ensures that the random string returned is unique.
 | 
			
		||||
   *   Defaults to FALSE.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Randomly generated string.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\Random::string()
 | 
			
		||||
   */
 | 
			
		||||
  public function name($length = 8, $unique = FALSE) {
 | 
			
		||||
    $values = array_merge(range(65, 90), range(97, 122), range(48, 57));
 | 
			
		||||
    $max = count($values) - 1;
 | 
			
		||||
    $counter = 0;
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
      if ($counter == static::MAXIMUM_TRIES) {
 | 
			
		||||
        throw new \RuntimeException('Unable to generate a unique random name');
 | 
			
		||||
      }
 | 
			
		||||
      $str = chr(mt_rand(97, 122));
 | 
			
		||||
      for ($i = 1; $i < $length; $i++) {
 | 
			
		||||
        $str .= chr($values[mt_rand(0, $max)]);
 | 
			
		||||
      }
 | 
			
		||||
      $counter++;
 | 
			
		||||
    } while ($unique && isset($this->names[$str]));
 | 
			
		||||
 | 
			
		||||
    if ($unique) {
 | 
			
		||||
      $this->names[$str] = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $str;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generates a string containing lowercase letters and numbers.
 | 
			
		||||
   *
 | 
			
		||||
   * This method is used to generate strings that are compliant with Drupal
 | 
			
		||||
   * machine names. This doesn't include underscores, dashes and periods since
 | 
			
		||||
   * those are not compatible with all machine names.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $length
 | 
			
		||||
   *   Length of random string to generate.
 | 
			
		||||
   * @param bool $unique
 | 
			
		||||
   *   If TRUE ensures that the random string returned is unique.
 | 
			
		||||
   *   Defaults to FALSE.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Randomly generated string.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \RuntimeException
 | 
			
		||||
   *   Thrown if a unique machine name cannot be generated within the allowed
 | 
			
		||||
   *   number of random attempts.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\Random::string()
 | 
			
		||||
   */
 | 
			
		||||
  public function machineName(int $length = 8, bool $unique = FALSE): string {
 | 
			
		||||
    $values = array_merge(range('a', 'z'), range(0, 9));
 | 
			
		||||
    $start_characters = range('a', 'z');
 | 
			
		||||
    $counter = 0;
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
      if ($counter == static::MAXIMUM_TRIES) {
 | 
			
		||||
        throw new \RuntimeException('Unable to generate a unique random machine name');
 | 
			
		||||
      }
 | 
			
		||||
      $str = $start_characters[array_rand($start_characters)];
 | 
			
		||||
      for ($i = 1; $i < $length; $i++) {
 | 
			
		||||
        $str .= $values[array_rand($values)];
 | 
			
		||||
      }
 | 
			
		||||
      $counter++;
 | 
			
		||||
    } while ($unique && isset($this->machineNames[$str]));
 | 
			
		||||
 | 
			
		||||
    if ($unique) {
 | 
			
		||||
      $this->machineNames[$str] = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $str;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generates a string that looks like a word.
 | 
			
		||||
   *
 | 
			
		||||
   * The generated string contains only letters and has alternating consonants
 | 
			
		||||
   * and vowels.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $length
 | 
			
		||||
   *   The desired word length.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A random string with the requested number of random words.
 | 
			
		||||
   */
 | 
			
		||||
  public function word($length) {
 | 
			
		||||
    $vowels = ["a", "e", "i", "o", "u"];
 | 
			
		||||
    $cons = ["b", "c", "d", "g", "h", "j", "k", "l", "m", "n", "p", "r", "s", "t", "u", "v", "w", "tr",
 | 
			
		||||
      "cr", "br", "fr", "th", "dr", "ch", "ph", "wr", "st", "sp", "sw", "pr",
 | 
			
		||||
      "sl", "cl", "sh",
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $num_vowels = count($vowels);
 | 
			
		||||
    $num_cons = count($cons);
 | 
			
		||||
    $word = '';
 | 
			
		||||
 | 
			
		||||
    while (strlen($word) < $length) {
 | 
			
		||||
      $word .= $cons[mt_rand(0, $num_cons - 1)] . $vowels[mt_rand(0, $num_vowels - 1)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return substr($word, 0, $length);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generates a random PHP object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $size
 | 
			
		||||
   *   The number of random keys to add to the object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return object
 | 
			
		||||
   *   The generated object, with the specified number of random keys. Each key
 | 
			
		||||
   *   has a random string value.
 | 
			
		||||
   */
 | 
			
		||||
  public function object($size = 4) {
 | 
			
		||||
    $object = new \stdClass();
 | 
			
		||||
    for ($i = 0; $i < $size; $i++) {
 | 
			
		||||
      $random_key = $this->name();
 | 
			
		||||
      $random_value = $this->string();
 | 
			
		||||
      $object->{$random_key} = $random_value;
 | 
			
		||||
    }
 | 
			
		||||
    return $object;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generates sentences Latin words, often used as placeholder text.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $min_word_count
 | 
			
		||||
   *   The minimum number of words in the return string. Total word count
 | 
			
		||||
   *   can slightly exceed provided this value in order to deliver
 | 
			
		||||
   *   sentences of random length.
 | 
			
		||||
   * @param bool $capitalize
 | 
			
		||||
   *   Uppercase all the words in the string.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Nonsense latin words which form sentence(s).
 | 
			
		||||
   */
 | 
			
		||||
  public function sentences($min_word_count, $capitalize = FALSE) {
 | 
			
		||||
    // cSpell:disable
 | 
			
		||||
    $dictionary = ["abbas", "abdo", "abico", "abigo", "abluo", "accumsan",
 | 
			
		||||
      "acsi", "ad", "adipiscing", "aliquam", "aliquip", "amet", "antehabeo",
 | 
			
		||||
      "appellatio", "aptent", "at", "augue", "autem", "bene", "blandit",
 | 
			
		||||
      "brevitas", "caecus", "camur", "capto", "causa", "cogo", "comis",
 | 
			
		||||
      "commodo", "commoveo", "consectetuer", "consequat", "conventio", "cui",
 | 
			
		||||
      "damnum", "decet", "defui", "diam", "dignissim", "distineo", "dolor",
 | 
			
		||||
      "dolore", "dolus", "duis", "ea", "eligo", "elit", "enim", "erat",
 | 
			
		||||
      "eros", "esca", "esse", "et", "eu", "euismod", "eum", "ex", "exerci",
 | 
			
		||||
      "exputo", "facilisi", "facilisis", "fere", "feugiat", "gemino",
 | 
			
		||||
      "genitus", "gilvus", "gravis", "haero", "hendrerit", "hos", "huic",
 | 
			
		||||
      "humo", "iaceo", "ibidem", "ideo", "ille", "illum", "immitto",
 | 
			
		||||
      "importunus", "imputo", "in", "incassum", "inhibeo", "interdico",
 | 
			
		||||
      "iriure", "iusto", "iustum", "jugis", "jumentum", "jus", "laoreet",
 | 
			
		||||
      "lenis", "letalis", "lobortis", "loquor", "lucidus", "luctus", "ludus",
 | 
			
		||||
      "luptatum", "macto", "magna", "mauris", "melior", "metuo", "meus",
 | 
			
		||||
      "minim", "modo", "molior", "mos", "natu", "neo", "neque", "nibh",
 | 
			
		||||
      "nimis", "nisl", "nobis", "nostrud", "nulla", "nunc", "nutus", "obruo",
 | 
			
		||||
      "occuro", "odio", "olim", "oppeto", "os", "pagus", "pala", "paratus",
 | 
			
		||||
      "patria", "paulatim", "pecus", "persto", "pertineo", "plaga", "pneum",
 | 
			
		||||
      "populus", "praemitto", "praesent", "premo", "probo", "proprius",
 | 
			
		||||
      "quadrum", "quae", "qui", "quia", "quibus", "quidem", "quidne", "quis",
 | 
			
		||||
      "ratis", "refero", "refoveo", "roto", "rusticus", "saepius",
 | 
			
		||||
      "sagaciter", "saluto", "scisco", "secundum", "sed", "si", "similis",
 | 
			
		||||
      "singularis", "sino", "sit", "sudo", "suscipere", "suscipit", "tamen",
 | 
			
		||||
      "tation", "te", "tego", "tincidunt", "torqueo", "tum", "turpis",
 | 
			
		||||
      "typicus", "ulciscor", "ullamcorper", "usitas", "ut", "utinam",
 | 
			
		||||
      "utrum", "uxor", "valde", "valetudo", "validus", "vel", "velit",
 | 
			
		||||
      "veniam", "venio", "vereor", "vero", "verto", "vicis", "vindico",
 | 
			
		||||
      "virtus", "voco", "volutpat", "vulpes", "vulputate", "wisi", "ymo",
 | 
			
		||||
      "zelus",
 | 
			
		||||
    ];
 | 
			
		||||
    // cSpell:enable
 | 
			
		||||
    $dictionary_flipped = array_flip($dictionary);
 | 
			
		||||
    $greeking = '';
 | 
			
		||||
 | 
			
		||||
    if (!$capitalize) {
 | 
			
		||||
      $words_remaining = $min_word_count;
 | 
			
		||||
      while ($words_remaining > 0) {
 | 
			
		||||
        $sentence_length = mt_rand(3, 10);
 | 
			
		||||
        $words = array_rand($dictionary_flipped, $sentence_length);
 | 
			
		||||
        $sentence = implode(' ', $words);
 | 
			
		||||
        $greeking .= ucfirst($sentence) . '. ';
 | 
			
		||||
        $words_remaining -= $sentence_length;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // Use slightly different method for titles.
 | 
			
		||||
      $words = array_rand($dictionary_flipped, $min_word_count);
 | 
			
		||||
      $words = is_array($words) ? implode(' ', $words) : $words;
 | 
			
		||||
      $greeking = ucwords($words);
 | 
			
		||||
    }
 | 
			
		||||
    return trim($greeking);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generate paragraphs separated by double new line.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $paragraph_count
 | 
			
		||||
   *   The number of paragraphs to create. Defaults to 12.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A string of multiple paragraphs.
 | 
			
		||||
   */
 | 
			
		||||
  public function paragraphs($paragraph_count = 12) {
 | 
			
		||||
    $output = '';
 | 
			
		||||
    for ($i = 1; $i <= $paragraph_count; $i++) {
 | 
			
		||||
      $output .= $this->sentences(mt_rand(20, 60)) . "\n\n";
 | 
			
		||||
    }
 | 
			
		||||
    return $output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a placeholder image.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $destination
 | 
			
		||||
   *   The absolute file path where the image should be stored.
 | 
			
		||||
   * @param string $min_resolution
 | 
			
		||||
   *   The minimum dimensions for the image. For example, '400x300'.
 | 
			
		||||
   * @param string $max_resolution
 | 
			
		||||
   *   The maximum dimensions for the image. For example, '800x600'.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Path to image file.
 | 
			
		||||
   */
 | 
			
		||||
  public function image($destination, $min_resolution, $max_resolution) {
 | 
			
		||||
    $extension = pathinfo($destination, PATHINFO_EXTENSION);
 | 
			
		||||
    $min = explode('x', $min_resolution);
 | 
			
		||||
    $max = explode('x', $max_resolution);
 | 
			
		||||
 | 
			
		||||
    $width = rand((int) $min[0], (int) $max[0]);
 | 
			
		||||
    $height = rand((int) $min[1], (int) $max[1]);
 | 
			
		||||
 | 
			
		||||
    // Make an image split into 4 sections with random colors.
 | 
			
		||||
    $im = imagecreate($width, $height);
 | 
			
		||||
    for ($n = 0; $n < 4; $n++) {
 | 
			
		||||
      $color = imagecolorallocate($im, rand(0, 255), rand(0, 255), rand(0, 255));
 | 
			
		||||
      $x = $width / 2 * ($n % 2);
 | 
			
		||||
      $y = $height / 2 * (int) ($n >= 2);
 | 
			
		||||
      imagefilledrectangle($im, (int) $x, (int) $y, (int) ($x + $width / 2), (int) ($y + $height / 2), $color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Make a perfect circle in the image middle.
 | 
			
		||||
    $color = imagecolorallocate($im, rand(0, 255), rand(0, 255), rand(0, 255));
 | 
			
		||||
    $smaller_dimension = min($width, $height);
 | 
			
		||||
    imageellipse($im, (int) ($width / 2), (int) ($height / 2), $smaller_dimension, $smaller_dimension, $color);
 | 
			
		||||
 | 
			
		||||
    $save_function = 'image' . ($extension == 'jpg' ? 'jpeg' : $extension);
 | 
			
		||||
    $save_function($im, $destination);
 | 
			
		||||
    return $destination;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										195
									
								
								web/core/lib/Drupal/Component/Utility/Rectangle.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								web/core/lib/Drupal/Component/Utility/Rectangle.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,195 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Rectangle rotation algebra class.
 | 
			
		||||
 *
 | 
			
		||||
 * This class is used by the image system to abstract, from toolkit
 | 
			
		||||
 * implementations, the calculation of the expected dimensions resulting from
 | 
			
		||||
 * an image rotate operation.
 | 
			
		||||
 *
 | 
			
		||||
 * Different versions of PHP for the GD toolkit, and alternative toolkits, use
 | 
			
		||||
 * different algorithms to perform the rotation of an image and result in
 | 
			
		||||
 * different dimensions of the output image. This prevents predictability of
 | 
			
		||||
 * the final image size for instance by the image rotate effect, or by image
 | 
			
		||||
 * toolkit rotate operations.
 | 
			
		||||
 *
 | 
			
		||||
 * This class implements a calculation algorithm that returns, given input
 | 
			
		||||
 * width, height and rotation angle, dimensions of the expected image after
 | 
			
		||||
 * rotation that are consistent with those produced by the GD rotate image
 | 
			
		||||
 * toolkit operation using PHP 5.5 and above.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\system\Plugin\ImageToolkit\Operation\gd\Rotate
 | 
			
		||||
 */
 | 
			
		||||
class Rectangle {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The width of the rectangle.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $width;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The height of the rectangle.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $height;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The width of the rotated rectangle.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $boundingWidth;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The height of the rotated rectangle.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $boundingHeight;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new Rectangle object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $width
 | 
			
		||||
   *   The width of the rectangle.
 | 
			
		||||
   * @param int $height
 | 
			
		||||
   *   The height of the rectangle.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($width, $height) {
 | 
			
		||||
    if ($width > 0 && $height > 0) {
 | 
			
		||||
      $this->width = $width;
 | 
			
		||||
      $this->height = $height;
 | 
			
		||||
      $this->boundingWidth = $width;
 | 
			
		||||
      $this->boundingHeight = $height;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      throw new \InvalidArgumentException("Invalid dimensions ({$width}x{$height}) specified for a Rectangle object");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Rotates the rectangle.
 | 
			
		||||
   *
 | 
			
		||||
   * @param float $angle
 | 
			
		||||
   *   Rotation angle.
 | 
			
		||||
   *
 | 
			
		||||
   * @return $this
 | 
			
		||||
   */
 | 
			
		||||
  public function rotate($angle) {
 | 
			
		||||
    // PHP 5.5 GD bug: https://bugs.php.net/bug.php?id=65148: To prevent buggy
 | 
			
		||||
    // behavior on negative multiples of 30 degrees we convert any negative
 | 
			
		||||
    // angle to a positive one between 0 and 360 degrees.
 | 
			
		||||
    $angle -= floor($angle / 360) * 360;
 | 
			
		||||
 | 
			
		||||
    // For some rotations that are multiple of 30 degrees, we need to correct
 | 
			
		||||
    // an imprecision between GD that uses C floats internally, and PHP that
 | 
			
		||||
    // uses C doubles. Also, for rotations that are not multiple of 90 degrees,
 | 
			
		||||
    // we need to introduce a correction factor of 0.5 to match the GD
 | 
			
		||||
    // algorithm used in PHP 5.5 (and above) to calculate the width and height
 | 
			
		||||
    // of the rotated image.
 | 
			
		||||
    if ((int) $angle == $angle && $angle % 90 == 0) {
 | 
			
		||||
      $imprecision = 0;
 | 
			
		||||
      $correction = 0;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $imprecision = -0.00001;
 | 
			
		||||
      $correction = 0.5;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Do the trigonometry, applying imprecision fixes where needed.
 | 
			
		||||
    $rad = deg2rad($angle);
 | 
			
		||||
    $cos = cos($rad);
 | 
			
		||||
    $sin = sin($rad);
 | 
			
		||||
    $a = $this->width * $cos;
 | 
			
		||||
    $b = $this->height * $sin + $correction;
 | 
			
		||||
    $c = $this->width * $sin;
 | 
			
		||||
    $d = $this->height * $cos + $correction;
 | 
			
		||||
    if ((int) $angle == $angle && in_array($angle, [60, 150, 300])) {
 | 
			
		||||
      $a = $this->fixImprecision($a, $imprecision);
 | 
			
		||||
      $b = $this->fixImprecision($b, $imprecision);
 | 
			
		||||
      $c = $this->fixImprecision($c, $imprecision);
 | 
			
		||||
      $d = $this->fixImprecision($d, $imprecision);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // This is how GD on PHP5.5 calculates the new dimensions.
 | 
			
		||||
    $this->boundingWidth = abs((int) $a) + abs((int) $b);
 | 
			
		||||
    $this->boundingHeight = abs((int) $c) + abs((int) $d);
 | 
			
		||||
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Performs an imprecision check on the input value and fixes it if needed.
 | 
			
		||||
   *
 | 
			
		||||
   * GD that uses C floats internally, whereas we at PHP level use C doubles.
 | 
			
		||||
   * In some cases, we need to compensate imprecision.
 | 
			
		||||
   *
 | 
			
		||||
   * @param float $input
 | 
			
		||||
   *   The input value.
 | 
			
		||||
   * @param float $imprecision
 | 
			
		||||
   *   The imprecision factor.
 | 
			
		||||
   *
 | 
			
		||||
   * @return float
 | 
			
		||||
   *   A value, where imprecision is added to input if the delta part of the
 | 
			
		||||
   *   input is lower than the absolute imprecision.
 | 
			
		||||
   */
 | 
			
		||||
  protected function fixImprecision($input, $imprecision) {
 | 
			
		||||
    if ($this->delta($input) < abs($imprecision)) {
 | 
			
		||||
      return $input + $imprecision;
 | 
			
		||||
    }
 | 
			
		||||
    return $input;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the fractional part of a float number, unsigned.
 | 
			
		||||
   *
 | 
			
		||||
   * @param float $input
 | 
			
		||||
   *   The input value.
 | 
			
		||||
   *
 | 
			
		||||
   * @return float
 | 
			
		||||
   *   The fractional part of the input number, unsigned.
 | 
			
		||||
   */
 | 
			
		||||
  protected function fraction($input) {
 | 
			
		||||
    return abs((int) $input - $input);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the difference of a fraction from the closest between 0 and 1.
 | 
			
		||||
   *
 | 
			
		||||
   * @param float $input
 | 
			
		||||
   *   The input value.
 | 
			
		||||
   *
 | 
			
		||||
   * @return float
 | 
			
		||||
   *   the difference of a fraction from the closest between 0 and 1.
 | 
			
		||||
   */
 | 
			
		||||
  protected function delta($input) {
 | 
			
		||||
    $fraction = $this->fraction($input);
 | 
			
		||||
    return $fraction > 0.5 ? (1 - $fraction) : $fraction;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the bounding width of the rectangle.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The bounding width of the rotated rectangle.
 | 
			
		||||
   */
 | 
			
		||||
  public function getBoundingWidth() {
 | 
			
		||||
    return $this->boundingWidth;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the bounding height of the rectangle.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The bounding height of the rotated rectangle.
 | 
			
		||||
   */
 | 
			
		||||
  public function getBoundingHeight() {
 | 
			
		||||
    return $this->boundingHeight;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								web/core/lib/Drupal/Component/Utility/Reflection.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								web/core/lib/Drupal/Component/Utility/Reflection.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides helper methods for reflection.
 | 
			
		||||
 */
 | 
			
		||||
final class Reflection {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the parameter's class name.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \ReflectionParameter $parameter
 | 
			
		||||
   *   The parameter.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string|null
 | 
			
		||||
   *   The parameter's class name or NULL if the parameter is not a class.
 | 
			
		||||
   */
 | 
			
		||||
  public static function getParameterClassName(\ReflectionParameter $parameter) : ?string {
 | 
			
		||||
    $name = NULL;
 | 
			
		||||
    if ($parameter->hasType() && !$parameter->getType()->isBuiltin()) {
 | 
			
		||||
      $name = $parameter->getType()->getName();
 | 
			
		||||
      $lc_name = strtolower($name);
 | 
			
		||||
      switch ($lc_name) {
 | 
			
		||||
        case 'self':
 | 
			
		||||
          return $parameter->getDeclaringClass()->getName();
 | 
			
		||||
 | 
			
		||||
        case 'parent':
 | 
			
		||||
          return ($parent = $parameter->getDeclaringClass()->getParentClass()) ? $parent->name : NULL;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $name;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								web/core/lib/Drupal/Component/Utility/SortArray.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								web/core/lib/Drupal/Component/Utility/SortArray.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,128 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides generic array sorting helper methods.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class SortArray {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sorts a structured array by the 'weight' element.
 | 
			
		||||
   *
 | 
			
		||||
   * Note that the sorting is by the 'weight' array element, not by the render
 | 
			
		||||
   * element property '#weight'.
 | 
			
		||||
   *
 | 
			
		||||
   * Callback for uasort().
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $a
 | 
			
		||||
   *   First item for comparison. The compared items should be associative
 | 
			
		||||
   *   arrays that optionally include a 'weight' element. For items without a
 | 
			
		||||
   *   'weight' element, a default value of 0 will be used.
 | 
			
		||||
   * @param array $b
 | 
			
		||||
   *   Second item for comparison.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The comparison result for uasort().
 | 
			
		||||
   */
 | 
			
		||||
  public static function sortByWeightElement(array $a, array $b) {
 | 
			
		||||
    return static::sortByKeyInt($a, $b, 'weight');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sorts a structured array by '#weight' property.
 | 
			
		||||
   *
 | 
			
		||||
   * Callback for uasort().
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $a
 | 
			
		||||
   *   First item for comparison. The compared items should be associative
 | 
			
		||||
   *   arrays that optionally include a '#weight' key.
 | 
			
		||||
   * @param array $b
 | 
			
		||||
   *   Second item for comparison.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The comparison result for uasort().
 | 
			
		||||
   */
 | 
			
		||||
  public static function sortByWeightProperty($a, $b) {
 | 
			
		||||
    return static::sortByKeyInt($a, $b, '#weight');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sorts a structured array by 'title' key (no # prefix).
 | 
			
		||||
   *
 | 
			
		||||
   * Callback for uasort().
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $a
 | 
			
		||||
   *   First item for comparison. The compared items should be associative
 | 
			
		||||
   *   arrays that optionally include a 'title' key.
 | 
			
		||||
   * @param array $b
 | 
			
		||||
   *   Second item for comparison.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The comparison result for uasort().
 | 
			
		||||
   */
 | 
			
		||||
  public static function sortByTitleElement($a, $b) {
 | 
			
		||||
    return static::sortByKeyString($a, $b, 'title');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sorts a structured array by '#title' property.
 | 
			
		||||
   *
 | 
			
		||||
   * Callback for uasort().
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $a
 | 
			
		||||
   *   First item for comparison. The compared items should be associative
 | 
			
		||||
   *   arrays that optionally include a '#title' key.
 | 
			
		||||
   * @param array $b
 | 
			
		||||
   *   Second item for comparison.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The comparison result for uasort().
 | 
			
		||||
   */
 | 
			
		||||
  public static function sortByTitleProperty($a, $b) {
 | 
			
		||||
    return static::sortByKeyString($a, $b, '#title');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sorts a string array item by an arbitrary key.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $a
 | 
			
		||||
   *   First item for comparison.
 | 
			
		||||
   * @param array $b
 | 
			
		||||
   *   Second item for comparison.
 | 
			
		||||
   * @param string $key
 | 
			
		||||
   *   The key to use in the comparison.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The comparison result for uasort().
 | 
			
		||||
   */
 | 
			
		||||
  public static function sortByKeyString($a, $b, $key) {
 | 
			
		||||
    $a_title = (is_array($a) && isset($a[$key])) ? $a[$key] : '';
 | 
			
		||||
    $b_title = (is_array($b) && isset($b[$key])) ? $b[$key] : '';
 | 
			
		||||
 | 
			
		||||
    return strnatcasecmp($a_title, $b_title);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sorts an integer array item by an arbitrary key.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $a
 | 
			
		||||
   *   First item for comparison.
 | 
			
		||||
   * @param array $b
 | 
			
		||||
   *   Second item for comparison.
 | 
			
		||||
   * @param string $key
 | 
			
		||||
   *   The key to use in the comparison.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The comparison result for uasort().
 | 
			
		||||
   */
 | 
			
		||||
  public static function sortByKeyInt($a, $b, $key) {
 | 
			
		||||
    $a_weight = (is_array($a) && isset($a[$key])) ? $a[$key] : 0;
 | 
			
		||||
    $b_weight = (is_array($b) && isset($b[$key])) ? $b[$key] : 0;
 | 
			
		||||
 | 
			
		||||
    return $a_weight <=> $b_weight;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								web/core/lib/Drupal/Component/Utility/TESTING.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								web/core/lib/Drupal/Component/Utility/TESTING.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
HOW-TO: Test this Drupal component
 | 
			
		||||
 | 
			
		||||
In order to test this component, you'll need to get the entire Drupal repo and
 | 
			
		||||
run the tests there.
 | 
			
		||||
 | 
			
		||||
You'll find the tests under core/tests/Drupal/Tests/Component.
 | 
			
		||||
 | 
			
		||||
You can get the full Drupal repo here:
 | 
			
		||||
https://www.drupal.org/project/drupal/git-instructions
 | 
			
		||||
 | 
			
		||||
You can find more information about running PHPUnit tests with Drupal here:
 | 
			
		||||
https://www.drupal.org/node/2116263
 | 
			
		||||
 | 
			
		||||
Each component in the Drupal\Component namespace has its own annotated test
 | 
			
		||||
group. You can use this group to run only the tests for this component. Like
 | 
			
		||||
this:
 | 
			
		||||
 | 
			
		||||
$ ./vendor/bin/phpunit -c core --group Utility
 | 
			
		||||
							
								
								
									
										75
									
								
								web/core/lib/Drupal/Component/Utility/Tags.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								web/core/lib/Drupal/Component/Utility/Tags.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a class that can explode and implode tags.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class Tags {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Explodes a string of tags into an array.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $tags
 | 
			
		||||
   *   A string to explode.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of tags.
 | 
			
		||||
   */
 | 
			
		||||
  public static function explode($tags) {
 | 
			
		||||
    // This regexp allows the following types of user input:
 | 
			
		||||
    // this, "company, llc", "and ""this"" w,o.rks", foo bar
 | 
			
		||||
    $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
 | 
			
		||||
    preg_match_all($regexp, $tags, $matches);
 | 
			
		||||
    $typed_tags = array_unique($matches[1]);
 | 
			
		||||
 | 
			
		||||
    $tags = [];
 | 
			
		||||
    foreach ($typed_tags as $tag) {
 | 
			
		||||
      // If a user has escaped a term (to demonstrate that it is a group,
 | 
			
		||||
      // or includes a comma or quote character), we remove the escape
 | 
			
		||||
      // formatting so to save the term into the database as the user intends.
 | 
			
		||||
      $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag)));
 | 
			
		||||
      if ($tag != "") {
 | 
			
		||||
        $tags[] = $tag;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $tags;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Encodes a tag string, taking care of special cases like commas and quotes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $tag
 | 
			
		||||
   *   A tag string.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The encoded string.
 | 
			
		||||
   */
 | 
			
		||||
  public static function encode($tag) {
 | 
			
		||||
    if (str_contains($tag, ',') || str_contains($tag, '"')) {
 | 
			
		||||
      return '"' . str_replace('"', '""', $tag) . '"';
 | 
			
		||||
    }
 | 
			
		||||
    return $tag;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implodes an array of tags into a string.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $tags
 | 
			
		||||
   *   An array of tags.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The imploded string.
 | 
			
		||||
   */
 | 
			
		||||
  public static function implode($tags) {
 | 
			
		||||
    $encoded_tags = [];
 | 
			
		||||
    foreach ($tags as $tag) {
 | 
			
		||||
      $encoded_tags[] = self::encode($tag);
 | 
			
		||||
    }
 | 
			
		||||
    return implode(', ', $encoded_tags);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								web/core/lib/Drupal/Component/Utility/Timer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								web/core/lib/Drupal/Component/Utility/Timer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides helpers to use timers throughout a request.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class Timer {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An associative array of timers.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected static $timers = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Starts the timer with the specified name.
 | 
			
		||||
   *
 | 
			
		||||
   * If you start and stop the same timer multiple times, the measured intervals
 | 
			
		||||
   * will be accumulated.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The name of the timer.
 | 
			
		||||
   */
 | 
			
		||||
  public static function start($name) {
 | 
			
		||||
    static::$timers[$name]['start'] = microtime(TRUE);
 | 
			
		||||
    static::$timers[$name]['count'] = isset(static::$timers[$name]['count']) ? ++static::$timers[$name]['count'] : 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reads the current timer value without stopping the timer.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The name of the timer.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The current timer value in ms.
 | 
			
		||||
   */
 | 
			
		||||
  public static function read($name) {
 | 
			
		||||
    if (isset(static::$timers[$name]['start'])) {
 | 
			
		||||
      $stop = microtime(TRUE);
 | 
			
		||||
      $diff = round(($stop - static::$timers[$name]['start']) * 1000, 2);
 | 
			
		||||
 | 
			
		||||
      if (isset(static::$timers[$name]['time'])) {
 | 
			
		||||
        $diff += static::$timers[$name]['time'];
 | 
			
		||||
      }
 | 
			
		||||
      return $diff;
 | 
			
		||||
    }
 | 
			
		||||
    return static::$timers[$name]['time'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stops the timer with the specified name.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The name of the timer.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A timer array. The array contains the number of times the timer has been
 | 
			
		||||
   *   started and stopped (count) and the accumulated timer value in ms (time).
 | 
			
		||||
   */
 | 
			
		||||
  public static function stop($name) {
 | 
			
		||||
    if (isset(static::$timers[$name]['start'])) {
 | 
			
		||||
      $stop = microtime(TRUE);
 | 
			
		||||
      $diff = round(($stop - static::$timers[$name]['start']) * 1000, 2);
 | 
			
		||||
      if (isset(static::$timers[$name]['time'])) {
 | 
			
		||||
        static::$timers[$name]['time'] += $diff;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        static::$timers[$name]['time'] = $diff;
 | 
			
		||||
      }
 | 
			
		||||
      unset(static::$timers[$name]['start']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return static::$timers[$name];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								web/core/lib/Drupal/Component/Utility/ToStringTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								web/core/lib/Drupal/Component/Utility/ToStringTrait.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wraps __toString in a trait to avoid some fatal errors.
 | 
			
		||||
 */
 | 
			
		||||
trait ToStringTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements the magic __toString() method.
 | 
			
		||||
   */
 | 
			
		||||
  public function __toString() {
 | 
			
		||||
    try {
 | 
			
		||||
      return (string) $this->render();
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Exception $e) {
 | 
			
		||||
      // User errors in __toString() methods are considered fatal in the Drupal
 | 
			
		||||
      // error handler.
 | 
			
		||||
      trigger_error(get_class($e) . ' thrown while calling __toString on a ' . static::class . ' object in ' . $e->getFile() . ' on line ' . $e->getLine() . ': ' . $e->getMessage(), E_USER_WARNING);
 | 
			
		||||
      // In case we are using another error handler that did not fatal on the
 | 
			
		||||
      // E_USER_ERROR, we terminate execution. However, for test purposes allow
 | 
			
		||||
      // a return value.
 | 
			
		||||
      return $this->_die();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * For test purposes, wrap die() in an overridable method.
 | 
			
		||||
   */
 | 
			
		||||
  protected function _die() {
 | 
			
		||||
    die();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Renders the object as a string.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string|object
 | 
			
		||||
   *   The rendered string or an object implementing __toString().
 | 
			
		||||
   */
 | 
			
		||||
  abstract public function render();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										406
									
								
								web/core/lib/Drupal/Component/Utility/Unicode.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										406
									
								
								web/core/lib/Drupal/Component/Utility/Unicode.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,406 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides Unicode-related conversions and operations.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class Unicode {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Matches Unicode characters that are word boundaries.
 | 
			
		||||
   *
 | 
			
		||||
   * Characters with the following General_category (gc) property values are
 | 
			
		||||
   * used as word boundaries. While this does not fully conform to the Word
 | 
			
		||||
   * Boundaries algorithm described in http://unicode.org/reports/tr29, as PCRE
 | 
			
		||||
   * does not contain the Word_Break property table, this simpler algorithm has
 | 
			
		||||
   * to do.
 | 
			
		||||
   * - Cc, Cf, Cn, Co, Cs: Other.
 | 
			
		||||
   * - Pc, Pd, Pe, Pf, Pi, Po, Ps: Punctuation.
 | 
			
		||||
   * - Sc, Sk, Sm, So: Symbols.
 | 
			
		||||
   * - Zl, Zp, Zs: Separators.
 | 
			
		||||
   *
 | 
			
		||||
   * Non-boundary characters include the following General_category (gc)
 | 
			
		||||
   * property values:
 | 
			
		||||
   * - Ll, Lm, Lo, Lt, Lu: Letters.
 | 
			
		||||
   * - Mc, Me, Mn: Combining Marks.
 | 
			
		||||
   * - Nd, Nl, No: Numbers.
 | 
			
		||||
   *
 | 
			
		||||
   * Note that the PCRE property matcher is not used because we wanted to be
 | 
			
		||||
   * compatible with Unicode 5.2.0 regardless of the PCRE version used (and any
 | 
			
		||||
   * bugs in PCRE property tables).
 | 
			
		||||
   *
 | 
			
		||||
   * @see http://unicode.org/glossary
 | 
			
		||||
   */
 | 
			
		||||
  const PREG_CLASS_WORD_BOUNDARY = <<<'EOD'
 | 
			
		||||
\x{0}-\x{2F}\x{3A}-\x{40}\x{5B}-\x{60}\x{7B}-\x{A9}\x{AB}-\x{B1}\x{B4}
 | 
			
		||||
\x{B6}-\x{B8}\x{BB}\x{BF}\x{D7}\x{F7}\x{2C2}-\x{2C5}\x{2D2}-\x{2DF}
 | 
			
		||||
\x{2E5}-\x{2EB}\x{2ED}\x{2EF}-\x{2FF}\x{375}\x{37E}-\x{385}\x{387}\x{3F6}
 | 
			
		||||
\x{482}\x{55A}-\x{55F}\x{589}-\x{58A}\x{5BE}\x{5C0}\x{5C3}\x{5C6}
 | 
			
		||||
\x{5F3}-\x{60F}\x{61B}-\x{61F}\x{66A}-\x{66D}\x{6D4}\x{6DD}\x{6E9}
 | 
			
		||||
\x{6FD}-\x{6FE}\x{700}-\x{70F}\x{7F6}-\x{7F9}\x{830}-\x{83E}
 | 
			
		||||
\x{964}-\x{965}\x{970}\x{9F2}-\x{9F3}\x{9FA}-\x{9FB}\x{AF1}\x{B70}
 | 
			
		||||
\x{BF3}-\x{BFA}\x{C7F}\x{CF1}-\x{CF2}\x{D79}\x{DF4}\x{E3F}\x{E4F}
 | 
			
		||||
\x{E5A}-\x{E5B}\x{F01}-\x{F17}\x{F1A}-\x{F1F}\x{F34}\x{F36}\x{F38}
 | 
			
		||||
\x{F3A}-\x{F3D}\x{F85}\x{FBE}-\x{FC5}\x{FC7}-\x{FD8}\x{104A}-\x{104F}
 | 
			
		||||
\x{109E}-\x{109F}\x{10FB}\x{1360}-\x{1368}\x{1390}-\x{1399}\x{1400}
 | 
			
		||||
\x{166D}-\x{166E}\x{1680}\x{169B}-\x{169C}\x{16EB}-\x{16ED}
 | 
			
		||||
\x{1735}-\x{1736}\x{17B4}-\x{17B5}\x{17D4}-\x{17D6}\x{17D8}-\x{17DB}
 | 
			
		||||
\x{1800}-\x{180A}\x{180E}\x{1940}-\x{1945}\x{19DE}-\x{19FF}
 | 
			
		||||
\x{1A1E}-\x{1A1F}\x{1AA0}-\x{1AA6}\x{1AA8}-\x{1AAD}\x{1B5A}-\x{1B6A}
 | 
			
		||||
\x{1B74}-\x{1B7C}\x{1C3B}-\x{1C3F}\x{1C7E}-\x{1C7F}\x{1CD3}\x{1FBD}
 | 
			
		||||
\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}
 | 
			
		||||
\x{1FFD}-\x{206F}\x{207A}-\x{207E}\x{208A}-\x{208E}\x{20A0}-\x{20B8}
 | 
			
		||||
\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}
 | 
			
		||||
\x{2116}-\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}
 | 
			
		||||
\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}-\x{214D}\x{214F}
 | 
			
		||||
\x{2190}-\x{244A}\x{249C}-\x{24E9}\x{2500}-\x{2775}\x{2794}-\x{2B59}
 | 
			
		||||
\x{2CE5}-\x{2CEA}\x{2CF9}-\x{2CFC}\x{2CFE}-\x{2CFF}\x{2E00}-\x{2E2E}
 | 
			
		||||
\x{2E30}-\x{3004}\x{3008}-\x{3020}\x{3030}\x{3036}-\x{3037}
 | 
			
		||||
\x{303D}-\x{303F}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{3190}-\x{3191}
 | 
			
		||||
\x{3196}-\x{319F}\x{31C0}-\x{31E3}\x{3200}-\x{321E}\x{322A}-\x{3250}
 | 
			
		||||
\x{3260}-\x{327F}\x{328A}-\x{32B0}\x{32C0}-\x{33FF}\x{4DC0}-\x{4DFF}
 | 
			
		||||
\x{A490}-\x{A4C6}\x{A4FE}-\x{A4FF}\x{A60D}-\x{A60F}\x{A673}\x{A67E}
 | 
			
		||||
\x{A6F2}-\x{A716}\x{A720}-\x{A721}\x{A789}-\x{A78A}\x{A828}-\x{A82B}
 | 
			
		||||
\x{A836}-\x{A839}\x{A874}-\x{A877}\x{A8CE}-\x{A8CF}\x{A8F8}-\x{A8FA}
 | 
			
		||||
\x{A92E}-\x{A92F}\x{A95F}\x{A9C1}-\x{A9CD}\x{A9DE}-\x{A9DF}
 | 
			
		||||
\x{AA5C}-\x{AA5F}\x{AA77}-\x{AA79}\x{AADE}-\x{AADF}\x{ABEB}
 | 
			
		||||
\x{E000}-\x{F8FF}\x{FB29}\x{FD3E}-\x{FD3F}\x{FDFC}-\x{FDFD}
 | 
			
		||||
\x{FE10}-\x{FE19}\x{FE30}-\x{FE6B}\x{FEFF}-\x{FF0F}\x{FF1A}-\x{FF20}
 | 
			
		||||
\x{FF3B}-\x{FF40}\x{FF5B}-\x{FF65}\x{FFE0}-\x{FFFD}
 | 
			
		||||
EOD;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates that standard PHP (emulated) unicode support is being used.
 | 
			
		||||
   */
 | 
			
		||||
  const STATUS_SINGLEBYTE = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates that full unicode support with PHP mbstring extension is used.
 | 
			
		||||
   */
 | 
			
		||||
  const STATUS_MULTIBYTE = 1;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates an error during check for PHP unicode support.
 | 
			
		||||
   */
 | 
			
		||||
  const STATUS_ERROR = -1;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the current status of unicode/multibyte support on this environment.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The status of multibyte support. It can be one of:
 | 
			
		||||
   *   - \Drupal\Component\Utility\Unicode::STATUS_MULTIBYTE
 | 
			
		||||
   *     Full unicode support using an extension.
 | 
			
		||||
   *   - \Drupal\Component\Utility\Unicode::STATUS_SINGLEBYTE
 | 
			
		||||
   *     Standard PHP (emulated) unicode support.
 | 
			
		||||
   *   - \Drupal\Component\Utility\Unicode::STATUS_ERROR
 | 
			
		||||
   *     An error occurred. No unicode support.
 | 
			
		||||
   */
 | 
			
		||||
  public static function getStatus() {
 | 
			
		||||
    switch (static::check()) {
 | 
			
		||||
      case 'mb_strlen':
 | 
			
		||||
        return Unicode::STATUS_SINGLEBYTE;
 | 
			
		||||
 | 
			
		||||
      case '':
 | 
			
		||||
        return Unicode::STATUS_MULTIBYTE;
 | 
			
		||||
    }
 | 
			
		||||
    return Unicode::STATUS_ERROR;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks for Unicode support in PHP and sets the proper settings if possible.
 | 
			
		||||
   *
 | 
			
		||||
   * Because of the need to be able to handle text in various encodings, we do
 | 
			
		||||
   * not support mbstring function overloading. HTTP input/output conversion
 | 
			
		||||
   * must be disabled for similar reasons.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A string identifier of a failed multibyte extension check, if any.
 | 
			
		||||
   *   Otherwise, an empty string.
 | 
			
		||||
   */
 | 
			
		||||
  public static function check() {
 | 
			
		||||
    // Set appropriate configuration.
 | 
			
		||||
    mb_internal_encoding('utf-8');
 | 
			
		||||
    mb_language('uni');
 | 
			
		||||
 | 
			
		||||
    // Check for mbstring extension.
 | 
			
		||||
    if (!extension_loaded('mbstring')) {
 | 
			
		||||
      return 'mb_strlen';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check mbstring configuration.
 | 
			
		||||
    if (ini_get('mbstring.encoding_translation') != 0) {
 | 
			
		||||
      return 'mbstring.encoding_translation';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return '';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Decodes UTF byte-order mark (BOM) to the encoding name.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $data
 | 
			
		||||
   *   The data possibly containing a BOM. This can be the entire contents of
 | 
			
		||||
   *   a file, or just a fragment containing at least the first five bytes.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string|bool
 | 
			
		||||
   *   The name of the encoding, or FALSE if no byte order mark was present.
 | 
			
		||||
   */
 | 
			
		||||
  public static function encodingFromBOM($data) {
 | 
			
		||||
    static $bomMap = [
 | 
			
		||||
      "\xEF\xBB\xBF" => 'UTF-8',
 | 
			
		||||
      "\xFE\xFF" => 'UTF-16BE',
 | 
			
		||||
      "\xFF\xFE" => 'UTF-16LE',
 | 
			
		||||
      "\x00\x00\xFE\xFF" => 'UTF-32BE',
 | 
			
		||||
      "\xFF\xFE\x00\x00" => 'UTF-32LE',
 | 
			
		||||
      "\x2B\x2F\x76\x38" => 'UTF-7',
 | 
			
		||||
      "\x2B\x2F\x76\x39" => 'UTF-7',
 | 
			
		||||
      "\x2B\x2F\x76\x2B" => 'UTF-7',
 | 
			
		||||
      "\x2B\x2F\x76\x2F" => 'UTF-7',
 | 
			
		||||
      "\x2B\x2F\x76\x38\x2D" => 'UTF-7',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    foreach ($bomMap as $bom => $encoding) {
 | 
			
		||||
      if (str_starts_with($data, $bom)) {
 | 
			
		||||
        return $encoding;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Converts data to UTF-8.
 | 
			
		||||
   *
 | 
			
		||||
   * Requires the iconv, GNU recode or mbstring PHP extension.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $data
 | 
			
		||||
   *   The data to be converted.
 | 
			
		||||
   * @param string $encoding
 | 
			
		||||
   *   The encoding that the data is in.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string|bool
 | 
			
		||||
   *   Converted data or FALSE.
 | 
			
		||||
   */
 | 
			
		||||
  public static function convertToUtf8($data, $encoding) {
 | 
			
		||||
    return @iconv($encoding, 'utf-8', $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Truncates a UTF-8-encoded string safely to a number of bytes.
 | 
			
		||||
   *
 | 
			
		||||
   * If the end position is in the middle of a UTF-8 sequence, it scans
 | 
			
		||||
   * backwards until the beginning of the byte sequence.
 | 
			
		||||
   *
 | 
			
		||||
   * Use this function whenever you want to chop off a string at an unsure
 | 
			
		||||
   * location. On the other hand, if you're sure that you're splitting on a
 | 
			
		||||
   * character boundary (e.g. after using strpos() or similar), you can safely
 | 
			
		||||
   * use substr() instead.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $string
 | 
			
		||||
   *   The string to truncate.
 | 
			
		||||
   * @param int $len
 | 
			
		||||
   *   An upper limit on the returned string length.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The truncated string.
 | 
			
		||||
   */
 | 
			
		||||
  public static function truncateBytes($string, $len) {
 | 
			
		||||
    if (strlen($string) <= $len) {
 | 
			
		||||
      return $string;
 | 
			
		||||
    }
 | 
			
		||||
    if ((ord($string[$len]) < 0x80) || (ord($string[$len]) >= 0xC0)) {
 | 
			
		||||
      return substr($string, 0, $len);
 | 
			
		||||
    }
 | 
			
		||||
    // Scan backwards to beginning of the byte sequence.
 | 
			
		||||
    // @todo Make the code more readable in https://www.drupal.org/node/2911497.
 | 
			
		||||
    while (--$len >= 0 && ord($string[$len]) >= 0x80 && ord($string[$len]) < 0xC0) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return substr($string, 0, $len);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Capitalizes the first character of a UTF-8 string.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $text
 | 
			
		||||
   *   The string to convert.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The string with the first character as uppercase.
 | 
			
		||||
   */
 | 
			
		||||
  public static function ucfirst($text) {
 | 
			
		||||
    return mb_strtoupper(mb_substr($text, 0, 1)) . mb_substr($text, 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Converts the first character of a UTF-8 string to lowercase.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $text
 | 
			
		||||
   *   The string that will be converted.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The string with the first character as lowercase.
 | 
			
		||||
   *
 | 
			
		||||
   * @ingroup php_wrappers
 | 
			
		||||
   */
 | 
			
		||||
  public static function lcfirst($text) {
 | 
			
		||||
    // Note: no mbstring equivalent!
 | 
			
		||||
    return mb_strtolower(mb_substr($text, 0, 1)) . mb_substr($text, 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Capitalizes the first character of each word in a UTF-8 string.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $text
 | 
			
		||||
   *   The text that will be converted.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The input $text with each word capitalized.
 | 
			
		||||
   *
 | 
			
		||||
   * @ingroup php_wrappers
 | 
			
		||||
   */
 | 
			
		||||
  public static function ucwords($text) {
 | 
			
		||||
    $regex = '/(^|[' . static::PREG_CLASS_WORD_BOUNDARY . '])([^' . static::PREG_CLASS_WORD_BOUNDARY . '])/u';
 | 
			
		||||
    return preg_replace_callback($regex, function (array $matches) {
 | 
			
		||||
      return $matches[1] . mb_strtoupper($matches[2]);
 | 
			
		||||
    }, $text);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Truncates a UTF-8-encoded string safely to a number of characters.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $string
 | 
			
		||||
   *   The string to truncate.
 | 
			
		||||
   * @param int $max_length
 | 
			
		||||
   *   An upper limit on the returned string length, including trailing ellipsis
 | 
			
		||||
   *   if $add_ellipsis is TRUE.
 | 
			
		||||
   * @param bool $wordsafe
 | 
			
		||||
   *   If TRUE, attempt to truncate on a word boundary. Word boundaries are
 | 
			
		||||
   *   spaces, punctuation, and Unicode characters used as word boundaries in
 | 
			
		||||
   *   non-Latin languages; see Unicode::PREG_CLASS_WORD_BOUNDARY for more
 | 
			
		||||
   *   information. If a word boundary cannot be found that would make the
 | 
			
		||||
   *   length of the returned string fall within length guidelines (see
 | 
			
		||||
   *   parameters $max_length and $min_wordsafe_length), word boundaries are
 | 
			
		||||
   *   ignored.
 | 
			
		||||
   * @param bool $add_ellipsis
 | 
			
		||||
   *   If TRUE, add '...' to the end of the truncated string (defaults to
 | 
			
		||||
   *   FALSE). The string length will still fall within $max_length.
 | 
			
		||||
   * @param int $min_wordsafe_length
 | 
			
		||||
   *   If $wordsafe is TRUE, the minimum acceptable length for truncation
 | 
			
		||||
   *   (before adding an ellipsis, if $add_ellipsis is TRUE). Has no effect if
 | 
			
		||||
   *   $wordsafe is FALSE. This can be used to prevent having a very short
 | 
			
		||||
   *   resulting string that will not be understandable. For instance, if you
 | 
			
		||||
   *   are truncating the string "See MyVeryLongURLExample.com for more
 | 
			
		||||
   *   information" to a word-safe return length of 20, the only available word
 | 
			
		||||
   *   boundary within 20 characters is after the word "See", which wouldn't
 | 
			
		||||
   *   leave a very informative string. If you had set $min_wordsafe_length to
 | 
			
		||||
   *   10, though, the function would realize that "See" alone is too short, and
 | 
			
		||||
   *   would then just truncate ignoring word boundaries, giving you "See
 | 
			
		||||
   *   MyVeryLongURL..." (assuming you had set $add_ellipsis to TRUE).
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The truncated string.
 | 
			
		||||
   */
 | 
			
		||||
  public static function truncate($string, $max_length, $wordsafe = FALSE, $add_ellipsis = FALSE, $min_wordsafe_length = 1) {
 | 
			
		||||
    $ellipsis = '';
 | 
			
		||||
    $max_length = max($max_length, 0);
 | 
			
		||||
    $min_wordsafe_length = max($min_wordsafe_length, 0);
 | 
			
		||||
 | 
			
		||||
    if (mb_strlen($string) <= $max_length) {
 | 
			
		||||
      // No truncation needed, so don't add ellipsis, just return.
 | 
			
		||||
      return $string;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($add_ellipsis) {
 | 
			
		||||
      // Truncate ellipsis in case $max_length is small.
 | 
			
		||||
      $ellipsis = mb_substr('…', 0, $max_length);
 | 
			
		||||
      $max_length -= mb_strlen($ellipsis);
 | 
			
		||||
      $max_length = max($max_length, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($max_length <= $min_wordsafe_length) {
 | 
			
		||||
      // Do not attempt word-safe if lengths are bad.
 | 
			
		||||
      $wordsafe = FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($wordsafe) {
 | 
			
		||||
      $matches = [];
 | 
			
		||||
      // Find the last word boundary, if there is one within
 | 
			
		||||
      // $min_wordsafe_length to $max_length characters. preg_match() is always
 | 
			
		||||
      // greedy, so it will find the longest string possible.
 | 
			
		||||
      $found = preg_match('/^(.{' . $min_wordsafe_length . ',' . $max_length . '})[' . Unicode::PREG_CLASS_WORD_BOUNDARY . ']/us', $string, $matches);
 | 
			
		||||
      if ($found) {
 | 
			
		||||
        $string = $matches[1];
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $string = mb_substr($string, 0, $max_length);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $string = mb_substr($string, 0, $max_length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($add_ellipsis) {
 | 
			
		||||
      // If we're adding an ellipsis, remove any trailing periods.
 | 
			
		||||
      $string = rtrim($string, '.');
 | 
			
		||||
 | 
			
		||||
      $string .= $ellipsis;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $string;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Compares UTF-8-encoded strings in a binary safe case-insensitive manner.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $str1
 | 
			
		||||
   *   The first string.
 | 
			
		||||
   * @param string $str2
 | 
			
		||||
   *   The second string.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   Returns < 0 if $str1 is less than $str2; > 0 if $str1 is greater than
 | 
			
		||||
   *   $str2, and 0 if they are equal.
 | 
			
		||||
   */
 | 
			
		||||
  public static function strcasecmp($str1, $str2) {
 | 
			
		||||
    return strcmp(mb_strtoupper($str1), mb_strtoupper($str2));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks whether a string is valid UTF-8.
 | 
			
		||||
   *
 | 
			
		||||
   * All functions designed to filter input should use drupal_validate_utf8
 | 
			
		||||
   * to ensure they operate on valid UTF-8 strings to prevent bypass of the
 | 
			
		||||
   * filter.
 | 
			
		||||
   *
 | 
			
		||||
   * When text containing an invalid UTF-8 lead byte (0xC0 - 0xFF) is presented
 | 
			
		||||
   * as UTF-8 to Internet Explorer 6, the program may misinterpret subsequent
 | 
			
		||||
   * bytes. When these subsequent bytes are HTML control characters such as
 | 
			
		||||
   * quotes or angle brackets, parts of the text that were deemed safe by
 | 
			
		||||
   * filters end up in locations that are potentially unsafe; An onerror
 | 
			
		||||
   * attribute that is outside of a tag, and thus deemed safe by a filter, can
 | 
			
		||||
   * be interpreted by the browser as if it were inside the tag.
 | 
			
		||||
   *
 | 
			
		||||
   * The function does not return FALSE for strings containing character codes
 | 
			
		||||
   * above U+10FFFF, even though these are prohibited by RFC 3629.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $text
 | 
			
		||||
   *   The text to check.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the text is valid UTF-8, FALSE if not.
 | 
			
		||||
   */
 | 
			
		||||
  public static function validateUtf8($text) {
 | 
			
		||||
    if (strlen($text) == 0) {
 | 
			
		||||
      return TRUE;
 | 
			
		||||
    }
 | 
			
		||||
    // With the PCRE_UTF8 modifier 'u', preg_match() fails silently on strings
 | 
			
		||||
    // containing invalid UTF-8 byte sequences. It does not reject character
 | 
			
		||||
    // codes above U+10FFFF (represented by 4 or more octets), though.
 | 
			
		||||
    return (preg_match('/^./us', $text) == 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										468
									
								
								web/core/lib/Drupal/Component/Utility/UrlHelper.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										468
									
								
								web/core/lib/Drupal/Component/Utility/UrlHelper.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,468 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper class URL based methods.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class UrlHelper {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The list of allowed protocols.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected static $allowedProtocols = ['http', 'https'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Parses an array into a valid query string encoded with rawurlencode().
 | 
			
		||||
   *
 | 
			
		||||
   * Function rawurlencode() is RFC3986 compliant, and as a consequence RFC3987
 | 
			
		||||
   * compliant. The latter defines the required format of "URLs" in HTML5.
 | 
			
		||||
   * urlencode() is almost the same as rawurlencode(), except that it encodes
 | 
			
		||||
   * spaces as "+" instead of "%20". This makes its result non compliant to
 | 
			
		||||
   * RFC3986 and as a consequence non compliant to RFC3987 and as a consequence
 | 
			
		||||
   * not valid as a "URL" in HTML5.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $query
 | 
			
		||||
   *   The query parameter array to be processed; for instance,
 | 
			
		||||
   *   \Drupal::request()->query->all().
 | 
			
		||||
   * @param string $parent
 | 
			
		||||
   *   (optional) Internal use only. Used to build the $query array key for
 | 
			
		||||
   *   nested items. Defaults to an empty string.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A string encoded with rawurlencode() which can be used as or appended to
 | 
			
		||||
   *   the URL query string.
 | 
			
		||||
   *
 | 
			
		||||
   * @ingroup php_wrappers
 | 
			
		||||
   */
 | 
			
		||||
  public static function buildQuery(array $query, $parent = '') {
 | 
			
		||||
    $params = [];
 | 
			
		||||
 | 
			
		||||
    foreach ($query as $key => $value) {
 | 
			
		||||
      $key = ($parent ? $parent . rawurlencode('[' . $key . ']') : rawurlencode($key));
 | 
			
		||||
 | 
			
		||||
      // Recurse into children.
 | 
			
		||||
      if (is_array($value)) {
 | 
			
		||||
        $params[] = static::buildQuery($value, $key);
 | 
			
		||||
      }
 | 
			
		||||
      // If a query parameter value is NULL, only append its key.
 | 
			
		||||
      elseif (!isset($value)) {
 | 
			
		||||
        $params[] = $key;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // For better readability of paths in query strings, we decode slashes.
 | 
			
		||||
        $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return implode('&', $params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Compresses a string for use in a query parameter.
 | 
			
		||||
   *
 | 
			
		||||
   * While RFC 1738 doesn't specify a maximum length for query strings,
 | 
			
		||||
   * browsers or server configurations may restrict URLs and/or query strings to
 | 
			
		||||
   * a certain length, often 1000 or 2000 characters. This method can be used to
 | 
			
		||||
   * compress a string into a URL-safe query parameter which will be shorter
 | 
			
		||||
   * than if it was used directly.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $data
 | 
			
		||||
   *   The data to compress.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The data compressed into a URL-safe string.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\UrlHelper::uncompressQueryParameter()
 | 
			
		||||
   */
 | 
			
		||||
  public static function compressQueryParameter(string $data): string {
 | 
			
		||||
    // Use 'base64url' encoding. Note that the '=' sign is only used for padding
 | 
			
		||||
    // on the right of the string, and is otherwise not part of the data.
 | 
			
		||||
    // @see https://datatracker.ietf.org/doc/html/rfc4648#section-5
 | 
			
		||||
    // @see https://www.php.net/manual/en/function.base64-encode.php#123098
 | 
			
		||||
    return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(gzcompress($data)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Takes a compressed parameter and converts it back to the original.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $compressed
 | 
			
		||||
   *   A string as compressed by
 | 
			
		||||
   *   \Drupal\Component\Utility\UrlHelper::compressQueryParameter().
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The uncompressed data, or the original string if it cannot be
 | 
			
		||||
   *   uncompressed.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\UrlHelper::compressQueryParameter()
 | 
			
		||||
   */
 | 
			
		||||
  public static function uncompressQueryParameter(string $compressed): string {
 | 
			
		||||
    // Because this comes from user data, suppress the PHP warning that
 | 
			
		||||
    // gzcompress() throws if the base64-encoded string is invalid.
 | 
			
		||||
    $return = @gzuncompress(base64_decode(str_replace(['-', '_'], ['+', '/'], $compressed)));
 | 
			
		||||
 | 
			
		||||
    // If we failed to uncompress the query parameter, it may be a stale link
 | 
			
		||||
    // from before compression was implemented with the URL parameter
 | 
			
		||||
    // uncompressed already, or it may be an incorrectly formatted URL.
 | 
			
		||||
    // In either case, pass back the original string to the caller.
 | 
			
		||||
    return $return === FALSE ? $compressed : $return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Filters a URL query parameter array to remove unwanted elements.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $query
 | 
			
		||||
   *   An array to be processed.
 | 
			
		||||
   * @param array $exclude
 | 
			
		||||
   *   (optional) A list of $query array keys to remove. Use "parent[child]" to
 | 
			
		||||
   *   exclude nested items.
 | 
			
		||||
   * @param string $parent
 | 
			
		||||
   *   Internal use only. Used to build the $query array key for nested items.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array containing query parameters.
 | 
			
		||||
   */
 | 
			
		||||
  public static function filterQueryParameters(array $query, array $exclude = [], $parent = '') {
 | 
			
		||||
    // If $exclude is empty, there is nothing to filter.
 | 
			
		||||
    if (empty($exclude)) {
 | 
			
		||||
      return $query;
 | 
			
		||||
    }
 | 
			
		||||
    elseif (!$parent) {
 | 
			
		||||
      $exclude = array_flip($exclude);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $params = [];
 | 
			
		||||
    foreach ($query as $key => $value) {
 | 
			
		||||
      $string_key = ($parent ? $parent . '[' . $key . ']' : $key);
 | 
			
		||||
      if (isset($exclude[$string_key])) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (is_array($value)) {
 | 
			
		||||
        $params[$key] = static::filterQueryParameters($value, $exclude, $string_key);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $params[$key] = $value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $params;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Parses a URL string into its path, query, and fragment components.
 | 
			
		||||
   *
 | 
			
		||||
   * This function splits both internal paths like "node?b=c#d" and external
 | 
			
		||||
   * URLs like "https://example.com/a?b=c#d" into their component parts. See
 | 
			
		||||
   * @link http://tools.ietf.org/html/rfc3986#section-3 RFC 3986 @endlink for an
 | 
			
		||||
   * explanation of what the component parts are.
 | 
			
		||||
   *
 | 
			
		||||
   * Note that, unlike the RFC, when passed an external URL, this function
 | 
			
		||||
   * groups the scheme, authority, and path together into the path component.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $url
 | 
			
		||||
   *   The internal path or external URL string to parse.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array containing:
 | 
			
		||||
   *   - path: The path component of $url. If $url is an external URL, this
 | 
			
		||||
   *     includes the scheme, authority, and path.
 | 
			
		||||
   *   - query: An array of query parameters from $url, if they exist.
 | 
			
		||||
   *   - fragment: The fragment component from $url, if it exists.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Utility\LinkGenerator
 | 
			
		||||
   * @see http://tools.ietf.org/html/rfc3986
 | 
			
		||||
   *
 | 
			
		||||
   * @ingroup php_wrappers
 | 
			
		||||
   */
 | 
			
		||||
  public static function parse($url) {
 | 
			
		||||
    $options = [
 | 
			
		||||
      'path' => NULL,
 | 
			
		||||
      'query' => [],
 | 
			
		||||
      'fragment' => '',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // External URLs: not using parse_url() here, so we do not have to rebuild
 | 
			
		||||
    // the scheme, host, and path without having any use for it.
 | 
			
		||||
    // The URL is considered external if it contains the '://' delimiter. Since
 | 
			
		||||
    // a URL can also be passed as a query argument, we check if this delimiter
 | 
			
		||||
    // appears in front of the '?' query argument delimiter.
 | 
			
		||||
    $scheme_delimiter_position = strpos($url, '://');
 | 
			
		||||
    $query_delimiter_position = strpos($url, '?');
 | 
			
		||||
    $fragment_delimiter_position = strpos($url, '#');
 | 
			
		||||
    if ($scheme_delimiter_position !== FALSE && ($query_delimiter_position === FALSE || $scheme_delimiter_position < $query_delimiter_position) && ($fragment_delimiter_position === FALSE || $scheme_delimiter_position < $fragment_delimiter_position)) {
 | 
			
		||||
      // Split off the fragment, if any.
 | 
			
		||||
      if ($fragment_delimiter_position !== FALSE) {
 | 
			
		||||
        [$url, $options['fragment']] = explode('#', $url, 2);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Split off everything before the query string into 'path'.
 | 
			
		||||
      $parts = explode('?', $url, 2);
 | 
			
		||||
 | 
			
		||||
      // Don't support URLs without a path, like 'http://'.
 | 
			
		||||
      [, $path] = explode('://', $parts[0], 2);
 | 
			
		||||
      if ($path != '') {
 | 
			
		||||
        $options['path'] = $parts[0];
 | 
			
		||||
      }
 | 
			
		||||
      // If there is a query string, transform it into keyed query parameters.
 | 
			
		||||
      if (isset($parts[1])) {
 | 
			
		||||
        parse_str($parts[1], $options['query']);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Internal URLs.
 | 
			
		||||
    else {
 | 
			
		||||
      // parse_url() does not support relative URLs, so make it absolute. For
 | 
			
		||||
      // instance, the relative URL "foo/bar:1" isn't properly parsed.
 | 
			
		||||
      $parts = parse_url('https://example.com/' . $url);
 | 
			
		||||
      // Strip the leading slash that was just added.
 | 
			
		||||
      $options['path'] = substr($parts['path'], 1);
 | 
			
		||||
      if (isset($parts['query'])) {
 | 
			
		||||
        parse_str($parts['query'], $options['query']);
 | 
			
		||||
      }
 | 
			
		||||
      if (isset($parts['fragment'])) {
 | 
			
		||||
        $options['fragment'] = $parts['fragment'];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Encodes a Drupal path for use in a URL.
 | 
			
		||||
   *
 | 
			
		||||
   * For aesthetic reasons slashes are not escaped.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $path
 | 
			
		||||
   *   The Drupal path to encode.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The encoded path.
 | 
			
		||||
   */
 | 
			
		||||
  public static function encodePath($path) {
 | 
			
		||||
    return str_replace('%2F', '/', rawurlencode($path));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines whether a path is external to Drupal.
 | 
			
		||||
   *
 | 
			
		||||
   * An example of an external path is https://example.com. If a path cannot be
 | 
			
		||||
   * assessed by Drupal's menu handler, then we must treat it as potentially
 | 
			
		||||
   * insecure.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $path
 | 
			
		||||
   *   The internal path or external URL being linked to, such as "node/34" or
 | 
			
		||||
   *   "https://example.com/foo".
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE or FALSE, where TRUE indicates an external path.
 | 
			
		||||
   */
 | 
			
		||||
  public static function isExternal($path) {
 | 
			
		||||
    $colon_position = strpos($path, ':');
 | 
			
		||||
    // Some browsers treat \ as / so normalize to forward slashes.
 | 
			
		||||
    $path = str_replace('\\', '/', $path);
 | 
			
		||||
    // If the path starts with 2 slashes then it is always considered an
 | 
			
		||||
    // external URL without an explicit protocol part.
 | 
			
		||||
    return (str_starts_with($path, '//'))
 | 
			
		||||
      // Leading control characters may be ignored or mishandled by browsers,
 | 
			
		||||
      // so assume such a path may lead to an external location. The \p{C}
 | 
			
		||||
      // character class matches all UTF-8 control, unassigned, and private
 | 
			
		||||
      // characters.
 | 
			
		||||
      || (preg_match('/^\p{C}/u', $path) !== 0)
 | 
			
		||||
      // Avoid calling static::stripDangerousProtocols() if there is any slash
 | 
			
		||||
      // (/), hash (#) or question_mark (?) before the colon (:) occurrence -
 | 
			
		||||
      // if any - as this would clearly mean it is not a URL.
 | 
			
		||||
      || ($colon_position !== FALSE
 | 
			
		||||
        && !preg_match('![/?#]!', substr($path, 0, $colon_position))
 | 
			
		||||
        && static::stripDangerousProtocols($path) == $path);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if an external URL points to this installation.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $url
 | 
			
		||||
   *   A string containing an external URL, such as "https://example.com/foo".
 | 
			
		||||
   * @param string $base_url
 | 
			
		||||
   *   The base URL string to check against, such as "https://example.com/".
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the URL has the same domain and base path.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \InvalidArgumentException
 | 
			
		||||
   *   Exception thrown when either $url or $base_url are not fully qualified.
 | 
			
		||||
   */
 | 
			
		||||
  public static function externalIsLocal($url, $base_url) {
 | 
			
		||||
    // Some browsers treat \ as / so normalize to forward slashes.
 | 
			
		||||
    $url = str_replace('\\', '/', $url);
 | 
			
		||||
 | 
			
		||||
    // Leading control characters may be ignored or mishandled by browsers, so
 | 
			
		||||
    // assume such a path may lead to a non-local location. The \p{C} character
 | 
			
		||||
    // class matches all UTF-8 control, unassigned, and private characters.
 | 
			
		||||
    if (preg_match('/^\p{C}/u', $url) !== 0) {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $url_parts = parse_url($url);
 | 
			
		||||
    $base_parts = parse_url($base_url);
 | 
			
		||||
 | 
			
		||||
    if (empty($base_parts['host']) || empty($url_parts['host'])) {
 | 
			
		||||
      throw new \InvalidArgumentException('A path was passed when a fully qualified domain was expected.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!isset($url_parts['path']) || !isset($base_parts['path'])) {
 | 
			
		||||
      return (!isset($base_parts['path']) || $base_parts['path'] == '/')
 | 
			
		||||
        && ($url_parts['host'] == $base_parts['host']);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // When comparing base paths, we need a trailing slash to make sure a
 | 
			
		||||
      // partial URL match isn't occurring. Since base_path() always returns
 | 
			
		||||
      // with a trailing slash, we don't need to add the trailing slash here.
 | 
			
		||||
      return ($url_parts['host'] == $base_parts['host'] && stripos($url_parts['path'], $base_parts['path']) === 0);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Processes an HTML attribute value and strips dangerous protocols from URLs.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $string
 | 
			
		||||
   *   The string with the attribute value.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Cleaned up and HTML-escaped version of $string.
 | 
			
		||||
   */
 | 
			
		||||
  public static function filterBadProtocol($string) {
 | 
			
		||||
    // Get the plain text representation of the attribute value (i.e. its
 | 
			
		||||
    // meaning).
 | 
			
		||||
    $string = Html::decodeEntities($string);
 | 
			
		||||
    return Html::escape(static::stripDangerousProtocols($string));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the allowed protocols.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array of protocols, for example http, https and irc.
 | 
			
		||||
   */
 | 
			
		||||
  public static function getAllowedProtocols() {
 | 
			
		||||
    return static::$allowedProtocols;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the allowed protocols.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $protocols
 | 
			
		||||
   *   An array of protocols, for example http, https and irc.
 | 
			
		||||
   */
 | 
			
		||||
  public static function setAllowedProtocols(array $protocols = []) {
 | 
			
		||||
    static::$allowedProtocols = $protocols;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Strips dangerous protocols (for example, 'javascript:') from a URI.
 | 
			
		||||
   *
 | 
			
		||||
   * This function must be called for all URIs within user-entered input prior
 | 
			
		||||
   * to being output to an HTML attribute value. It is often called as part of
 | 
			
		||||
   * \Drupal\Component\Utility\UrlHelper::filterBadProtocol() or
 | 
			
		||||
   * \Drupal\Component\Utility\Xss::filter(), but those functions return an
 | 
			
		||||
   * HTML-encoded string, so this function can be called independently when the
 | 
			
		||||
   * output needs to be a plain-text string for passing to functions that will
 | 
			
		||||
   * call Html::escape() separately. The exact behavior depends on the value:
 | 
			
		||||
   * - If the value is a well-formed (per RFC 3986) relative URL or
 | 
			
		||||
   *   absolute URL that does not use a dangerous protocol (like
 | 
			
		||||
   *   "javascript:"), then the URL remains unchanged. This includes all
 | 
			
		||||
   *   URLs generated via Url::toString().
 | 
			
		||||
   * - If the value is a well-formed absolute URL with a dangerous protocol,
 | 
			
		||||
   *   the protocol is stripped. This process is repeated on the remaining URL
 | 
			
		||||
   *   until it is stripped down to a safe protocol.
 | 
			
		||||
   * - If the value is not a well-formed URL, the same sanitization behavior as
 | 
			
		||||
   *   for well-formed URLs will be invoked, which strips most substrings that
 | 
			
		||||
   *   precede a ":". The result can be used in URL attributes such as "href"
 | 
			
		||||
   *   or "src" (only after calling Html::escape() separately), but this may not
 | 
			
		||||
   *   produce valid HTML (for example, malformed URLs within "href" attributes
 | 
			
		||||
   *   fail HTML validation). This can be avoided by using
 | 
			
		||||
   *   Url::fromUri($possibly_not_a_url)->toString(), which either throws an
 | 
			
		||||
   *   exception or returns a well-formed URL.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $uri
 | 
			
		||||
   *   A plain-text URI that might contain dangerous protocols.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A plain-text URI stripped of dangerous protocols. As with all plain-text
 | 
			
		||||
   *   strings, this return value must not be output to an HTML page without
 | 
			
		||||
   *   being sanitized first. However, it can be passed to functions
 | 
			
		||||
   *   expecting plain-text strings.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\Html::escape()
 | 
			
		||||
   * @see \Drupal\Core\Url::toString()
 | 
			
		||||
   * @see \Drupal\Core\Url::fromUri()
 | 
			
		||||
   */
 | 
			
		||||
  public static function stripDangerousProtocols($uri) {
 | 
			
		||||
    $allowed_protocols = array_flip(static::$allowedProtocols);
 | 
			
		||||
 | 
			
		||||
    // Iteratively remove any invalid protocol found.
 | 
			
		||||
    do {
 | 
			
		||||
      $before = $uri;
 | 
			
		||||
      $colon_position = strpos($uri, ':');
 | 
			
		||||
      if ($colon_position > 0) {
 | 
			
		||||
        // We found a colon, possibly a protocol. Verify.
 | 
			
		||||
        $protocol = substr($uri, 0, $colon_position);
 | 
			
		||||
        // If a colon is preceded by a slash, question mark or hash, it cannot
 | 
			
		||||
        // possibly be part of the URL scheme. This must be a relative URL,
 | 
			
		||||
        // which inherits the (safe) protocol of the base document.
 | 
			
		||||
        if (preg_match('![/?#]!', $protocol)) {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3
 | 
			
		||||
        // (URI Comparison) scheme comparison must be case-insensitive.
 | 
			
		||||
        if (!isset($allowed_protocols[strtolower($protocol)])) {
 | 
			
		||||
          $uri = substr($uri, $colon_position + 1);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } while ($before != $uri);
 | 
			
		||||
 | 
			
		||||
    return $uri;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Verifies the syntax of the given URL.
 | 
			
		||||
   *
 | 
			
		||||
   * This function should only be used on actual URLs. It should not be used for
 | 
			
		||||
   * Drupal menu paths, which can contain arbitrary characters.
 | 
			
		||||
   * Valid values per RFC 3986.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $url
 | 
			
		||||
   *   The URL to verify.
 | 
			
		||||
   * @param bool $absolute
 | 
			
		||||
   *   Whether the URL is absolute (beginning with a scheme such as "http:").
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the URL is in a valid format, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public static function isValid($url, $absolute = FALSE) {
 | 
			
		||||
    if ($absolute) {
 | 
			
		||||
      return (bool) preg_match("
 | 
			
		||||
        /^                                                      # Start at the beginning of the text
 | 
			
		||||
        (?:ftp|https?|feed):\/\/                                # Look for ftp, http, https or feed schemes
 | 
			
		||||
        (?:                                                     # Userinfo (optional) which is typically
 | 
			
		||||
          (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)*      # a username or a username and password
 | 
			
		||||
          (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@          # combination
 | 
			
		||||
        )?
 | 
			
		||||
        (?:
 | 
			
		||||
          (?:[a-z0-9\-\.]|%[0-9a-f]{2})+                        # A domain name or a IPv4 address
 | 
			
		||||
          |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\])         # or a well formed IPv6 address
 | 
			
		||||
        )
 | 
			
		||||
        (?::[0-9]+)?                                            # Server port number (optional)
 | 
			
		||||
        (?:[\/|\?]
 | 
			
		||||
          (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})   # The path and query (optional)
 | 
			
		||||
        *)?
 | 
			
		||||
      $/xi", $url);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return (bool) preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										139
									
								
								web/core/lib/Drupal/Component/Utility/UserAgent.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								web/core/lib/Drupal/Component/Utility/UserAgent.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,139 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides user agent related utility functions.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class UserAgent {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Identifies user agent language from the Accept-language HTTP header.
 | 
			
		||||
   *
 | 
			
		||||
   * The algorithm works as follows:
 | 
			
		||||
   * - map user agent language codes to available language codes.
 | 
			
		||||
   * - order all user agent language codes by qvalue from high to low.
 | 
			
		||||
   * - add generic user agent language codes if they aren't already specified
 | 
			
		||||
   *   but with a slightly lower qvalue.
 | 
			
		||||
   * - find the most specific available language code with the highest qvalue.
 | 
			
		||||
   * - if 2 or more languages are having the same qvalue, respect the order of
 | 
			
		||||
   *   them inside the $languages array.
 | 
			
		||||
   *
 | 
			
		||||
   * We perform user agent accept-language parsing only if page cache is
 | 
			
		||||
   * disabled, otherwise we would cache a user-specific preference.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $http_accept_language
 | 
			
		||||
   *   The value of the "Accept-Language" HTTP header.
 | 
			
		||||
   * @param array $langcodes
 | 
			
		||||
   *   An array of available language codes to pick from.
 | 
			
		||||
   * @param array $mappings
 | 
			
		||||
   *   (optional) Custom mappings to support user agents that are sending non
 | 
			
		||||
   *   standard language codes. No mapping is assumed by default.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The selected language code or FALSE if no valid language can be
 | 
			
		||||
   *   identified.
 | 
			
		||||
   */
 | 
			
		||||
  public static function getBestMatchingLangcode($http_accept_language, $langcodes, $mappings = []) {
 | 
			
		||||
    // The Accept-Language header contains information about the language
 | 
			
		||||
    // preferences configured in the user's user agent / operating system.
 | 
			
		||||
    // RFC 2616 (section 14.4) defines the Accept-Language header as follows:
 | 
			
		||||
    // @code
 | 
			
		||||
    //   Accept-Language = "Accept-Language" ":"
 | 
			
		||||
    //                  1#( language-range [ ";" "q" "=" qvalue ] )
 | 
			
		||||
    //   language-range  = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
 | 
			
		||||
    // @endcode
 | 
			
		||||
    // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
 | 
			
		||||
    $ua_langcodes = [];
 | 
			
		||||
    if (preg_match_all('@(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', trim($http_accept_language), $matches, PREG_SET_ORDER)) {
 | 
			
		||||
      foreach ($matches as $match) {
 | 
			
		||||
        if ($mappings) {
 | 
			
		||||
          $langcode = strtolower($match[1]);
 | 
			
		||||
          foreach ($mappings as $ua_langcode => $standard_langcode) {
 | 
			
		||||
            if ($langcode == $ua_langcode) {
 | 
			
		||||
              $match[1] = $standard_langcode;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        // We can safely use strtolower() here, tags are ASCII.
 | 
			
		||||
        // RFC2616 mandates that the decimal part is no more than three digits,
 | 
			
		||||
        // so we multiply the qvalue by 1000 to avoid floating point
 | 
			
		||||
        // comparisons.
 | 
			
		||||
        $langcode = strtolower($match[1]);
 | 
			
		||||
        $qvalue = isset($match[2]) ? (float) $match[2] : 1;
 | 
			
		||||
        // Take the highest qvalue for this langcode. Although the request
 | 
			
		||||
        // supposedly contains unique langcodes, our mapping possibly resolves
 | 
			
		||||
        // to the same langcode for different qvalues. Keep the highest.
 | 
			
		||||
        $ua_langcodes[$langcode] = max(
 | 
			
		||||
          (int) ($qvalue * 1000),
 | 
			
		||||
          ($ua_langcodes[$langcode] ?? 0)
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // We should take pristine values from the HTTP headers, but Internet
 | 
			
		||||
    // Explorer from version 7 sends only specific language tags (eg. fr-CA)
 | 
			
		||||
    // without the corresponding generic tag (fr) unless explicitly configured.
 | 
			
		||||
    // In that case, we assume that the lowest value of the specific tags is the
 | 
			
		||||
    // value of the generic language to be as close to the HTTP 1.1 spec as
 | 
			
		||||
    // possible.
 | 
			
		||||
    // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 and
 | 
			
		||||
    // http://blogs.msdn.com/b/ie/archive/2006/10/17/accept-language-header-for-internet-explorer-7.aspx
 | 
			
		||||
    asort($ua_langcodes);
 | 
			
		||||
    foreach ($ua_langcodes as $langcode => $qvalue) {
 | 
			
		||||
      // For Chinese languages the generic tag is either zh-hans or zh-hant, so
 | 
			
		||||
      // we need to handle this separately, we can not split $langcode on the
 | 
			
		||||
      // first occurrence of '-' otherwise we get a non-existing language zh.
 | 
			
		||||
      // All other languages use a langcode without a '-', so we can safely
 | 
			
		||||
      // split on the first occurrence of it.
 | 
			
		||||
      if (strlen($langcode) > 7 && (str_starts_with($langcode, 'zh-hant') || str_starts_with($langcode, 'zh-hans'))) {
 | 
			
		||||
        $generic_tag = substr($langcode, 0, 7);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $generic_tag = strtok($langcode, '-');
 | 
			
		||||
      }
 | 
			
		||||
      if (!empty($generic_tag) && !isset($ua_langcodes[$generic_tag])) {
 | 
			
		||||
        // Add the generic langcode, but make sure it has a lower qvalue as the
 | 
			
		||||
        // more specific one, so the more specific one gets selected if it's
 | 
			
		||||
        // defined by both the user agent and us.
 | 
			
		||||
        $ua_langcodes[$generic_tag] = $qvalue - 0.1;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Find the added language with the greatest qvalue, following the rules
 | 
			
		||||
    // of RFC 2616 (section 14.4). If several languages have the same qvalue,
 | 
			
		||||
    // prefer the one with the greatest weight.
 | 
			
		||||
    $best_match_langcode = FALSE;
 | 
			
		||||
    $max_qvalue = 0;
 | 
			
		||||
    foreach ($langcodes as $langcode_case_sensitive) {
 | 
			
		||||
      // Language tags are case insensitive (RFC2616, sec 3.10).
 | 
			
		||||
      $langcode = strtolower($langcode_case_sensitive);
 | 
			
		||||
 | 
			
		||||
      // If nothing matches below, the default qvalue is the one of the wildcard
 | 
			
		||||
      // language, if set, or is 0 (which will never match).
 | 
			
		||||
      $qvalue = $ua_langcodes['*'] ?? 0;
 | 
			
		||||
 | 
			
		||||
      // Find the longest possible prefix of the user agent supplied language
 | 
			
		||||
      // ('the language-range') that matches this site language ('the language
 | 
			
		||||
      // tag').
 | 
			
		||||
      $prefix = $langcode;
 | 
			
		||||
      do {
 | 
			
		||||
        if (isset($ua_langcodes[$prefix])) {
 | 
			
		||||
          $qvalue = $ua_langcodes[$prefix];
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      } while ($prefix = substr($prefix, 0, strrpos($prefix, '-')));
 | 
			
		||||
 | 
			
		||||
      // Find the best match.
 | 
			
		||||
      if ($qvalue > $max_qvalue) {
 | 
			
		||||
        $best_match_langcode = $langcode_case_sensitive;
 | 
			
		||||
        $max_qvalue = $qvalue;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $best_match_langcode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								web/core/lib/Drupal/Component/Utility/Variable.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								web/core/lib/Drupal/Component/Utility/Variable.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides helpers for dealing with variables.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class Variable {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generates a human-readable name for a callable.
 | 
			
		||||
   *
 | 
			
		||||
   * @param callable $callable
 | 
			
		||||
   *   A callable.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   A human-readable name for the callable.
 | 
			
		||||
   */
 | 
			
		||||
  public static function callableToString($callable): string {
 | 
			
		||||
    if ($callable instanceof \Closure) {
 | 
			
		||||
      return '[closure]';
 | 
			
		||||
    }
 | 
			
		||||
    elseif (is_array($callable) && $callable) {
 | 
			
		||||
      if (is_object($callable[0])) {
 | 
			
		||||
        $callable[0] = get_class($callable[0]);
 | 
			
		||||
      }
 | 
			
		||||
      return implode('::', $callable);
 | 
			
		||||
    }
 | 
			
		||||
    elseif (is_string($callable)) {
 | 
			
		||||
      return $callable;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return '[unknown]';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Drupal-friendly var_export().
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed $var
 | 
			
		||||
   *   The variable to export.
 | 
			
		||||
   * @param string $prefix
 | 
			
		||||
   *   A prefix that will be added at the beginning of every lines of the
 | 
			
		||||
   *   output.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The variable exported in a way compatible to Drupal's coding standards.
 | 
			
		||||
   */
 | 
			
		||||
  public static function export($var, $prefix = '') {
 | 
			
		||||
    if (is_array($var)) {
 | 
			
		||||
      if (empty($var)) {
 | 
			
		||||
        $output = '[]';
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $output = "[\n";
 | 
			
		||||
        // Don't export keys if the array is non associative.
 | 
			
		||||
        $export_keys = array_values($var) != $var;
 | 
			
		||||
        foreach ($var as $key => $value) {
 | 
			
		||||
          $output .= '  ' . ($export_keys ? static::export($key) . ' => ' : '') . static::export($value, '  ') . ",\n";
 | 
			
		||||
        }
 | 
			
		||||
        $output .= ']';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    elseif (is_bool($var)) {
 | 
			
		||||
      $output = $var ? 'TRUE' : 'FALSE';
 | 
			
		||||
    }
 | 
			
		||||
    elseif (is_string($var)) {
 | 
			
		||||
      if (str_contains($var, "\n") || str_contains($var, "'")) {
 | 
			
		||||
        // If the string contains a line break or a single quote, use the
 | 
			
		||||
        // double quote export mode. Encode backslash, dollar symbols, and
 | 
			
		||||
        // double quotes and transform some common control characters.
 | 
			
		||||
        $var = str_replace(['\\', '$', '"', "\n", "\r", "\t"], ['\\\\', '\$', '\"', '\n', '\r', '\t'], $var);
 | 
			
		||||
        $output = '"' . $var . '"';
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $output = "'" . $var . "'";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    elseif (is_object($var) && get_class($var) === 'stdClass') {
 | 
			
		||||
      // var_export() will export stdClass objects using an undefined
 | 
			
		||||
      // magic method __set_state() leaving the export broken. This
 | 
			
		||||
      // workaround avoids this by casting the object as an array for
 | 
			
		||||
      // export and casting it back to an object when evaluated.
 | 
			
		||||
      $output = '(object) ' . static::export((array) $var, $prefix);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      // @todo var_export() does not use long array syntax. Fix in
 | 
			
		||||
      // https://www.drupal.org/project/drupal/issues/3476894
 | 
			
		||||
      $output = var_export($var, TRUE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($prefix) {
 | 
			
		||||
      $output = str_replace("\n", "\n$prefix", $output);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										443
									
								
								web/core/lib/Drupal/Component/Utility/Xss.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										443
									
								
								web/core/lib/Drupal/Component/Utility/Xss.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,443 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Component\Utility;
 | 
			
		||||
 | 
			
		||||
// cspell:ignore ckers kses harnhammar
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides helper to filter for cross-site scripting.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup utility
 | 
			
		||||
 */
 | 
			
		||||
class Xss {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The list of HTML tags allowed by filterAdmin().
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\Xss::filterAdmin()
 | 
			
		||||
   */
 | 
			
		||||
  protected static $adminTags = [
 | 
			
		||||
    'a',
 | 
			
		||||
    'abbr',
 | 
			
		||||
    'acronym',
 | 
			
		||||
    'address',
 | 
			
		||||
    'article',
 | 
			
		||||
    'aside',
 | 
			
		||||
    'b',
 | 
			
		||||
    'bdi',
 | 
			
		||||
    'bdo',
 | 
			
		||||
    'big',
 | 
			
		||||
    'blockquote',
 | 
			
		||||
    'br',
 | 
			
		||||
    'caption',
 | 
			
		||||
    'cite',
 | 
			
		||||
    'code',
 | 
			
		||||
    'col',
 | 
			
		||||
    'colgroup',
 | 
			
		||||
    'command',
 | 
			
		||||
    'dd',
 | 
			
		||||
    'del',
 | 
			
		||||
    'details',
 | 
			
		||||
    'dfn',
 | 
			
		||||
    'div',
 | 
			
		||||
    'dl',
 | 
			
		||||
    'dt',
 | 
			
		||||
    'em',
 | 
			
		||||
    'figcaption',
 | 
			
		||||
    'figure',
 | 
			
		||||
    'footer',
 | 
			
		||||
    'h1',
 | 
			
		||||
    'h2',
 | 
			
		||||
    'h3',
 | 
			
		||||
    'h4',
 | 
			
		||||
    'h5',
 | 
			
		||||
    'h6',
 | 
			
		||||
    'header',
 | 
			
		||||
    'hgroup',
 | 
			
		||||
    'hr',
 | 
			
		||||
    'i',
 | 
			
		||||
    'img',
 | 
			
		||||
    'ins',
 | 
			
		||||
    'kbd',
 | 
			
		||||
    'li',
 | 
			
		||||
    'mark',
 | 
			
		||||
    'menu',
 | 
			
		||||
    'meter',
 | 
			
		||||
    'nav',
 | 
			
		||||
    'ol',
 | 
			
		||||
    'output',
 | 
			
		||||
    'p',
 | 
			
		||||
    'pre',
 | 
			
		||||
    'progress',
 | 
			
		||||
    'q',
 | 
			
		||||
    'rp',
 | 
			
		||||
    'rt',
 | 
			
		||||
    'ruby',
 | 
			
		||||
    's',
 | 
			
		||||
    'samp',
 | 
			
		||||
    'section',
 | 
			
		||||
    'small',
 | 
			
		||||
    'span',
 | 
			
		||||
    'strong',
 | 
			
		||||
    'sub',
 | 
			
		||||
    'summary',
 | 
			
		||||
    'sup',
 | 
			
		||||
    'table',
 | 
			
		||||
    'tbody',
 | 
			
		||||
    'td',
 | 
			
		||||
    'tfoot',
 | 
			
		||||
    'th',
 | 
			
		||||
    'thead',
 | 
			
		||||
    'time',
 | 
			
		||||
    'tr',
 | 
			
		||||
    'tt',
 | 
			
		||||
    'u',
 | 
			
		||||
    'ul',
 | 
			
		||||
    'var',
 | 
			
		||||
    'wbr',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The default list of HTML tags allowed by filter().
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\Xss::filter()
 | 
			
		||||
   */
 | 
			
		||||
  protected static $htmlTags = ['a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
 | 
			
		||||
   *
 | 
			
		||||
   * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
 | 
			
		||||
   * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
 | 
			
		||||
   *
 | 
			
		||||
   * This code does four things:
 | 
			
		||||
   * - Removes characters and constructs that can trick browsers.
 | 
			
		||||
   * - Makes sure all HTML entities are well-formed.
 | 
			
		||||
   * - Makes sure all HTML tags and attributes are well-formed.
 | 
			
		||||
   * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g.
 | 
			
		||||
   *   javascript:).
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $string
 | 
			
		||||
   *   The string with raw HTML in it. It will be stripped of everything that
 | 
			
		||||
   *   can cause an XSS attack.
 | 
			
		||||
   * @param string[]|null $allowed_html_tags
 | 
			
		||||
   *   An array of allowed HTML tags.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   An XSS safe version of $string, or an empty string if $string is not
 | 
			
		||||
   *   valid UTF-8.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\Unicode::validateUtf8()
 | 
			
		||||
   *
 | 
			
		||||
   * @ingroup sanitization
 | 
			
		||||
   */
 | 
			
		||||
  public static function filter($string, ?array $allowed_html_tags = NULL) {
 | 
			
		||||
    if (is_null($allowed_html_tags)) {
 | 
			
		||||
      $allowed_html_tags = static::$htmlTags;
 | 
			
		||||
    }
 | 
			
		||||
    // Only operate on valid UTF-8 strings. This is necessary to prevent cross
 | 
			
		||||
    // site scripting issues on Internet Explorer 6.
 | 
			
		||||
    if (!Unicode::validateUtf8($string)) {
 | 
			
		||||
      return '';
 | 
			
		||||
    }
 | 
			
		||||
    // Remove NULL characters (ignored by some browsers).
 | 
			
		||||
    $string = str_replace(chr(0), '', $string);
 | 
			
		||||
    // Remove Netscape 4 JS entities.
 | 
			
		||||
    $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
 | 
			
		||||
 | 
			
		||||
    // Defuse all HTML entities.
 | 
			
		||||
    $string = str_replace('&', '&', $string);
 | 
			
		||||
    // Change back only well-formed entities in our list of allowed html tags:
 | 
			
		||||
    // Decimal numeric entities.
 | 
			
		||||
    $string = preg_replace('/&#([0-9]+;)/', '&#\1', $string);
 | 
			
		||||
    // Hexadecimal numeric entities.
 | 
			
		||||
    $string = preg_replace('/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
 | 
			
		||||
    // Named entities.
 | 
			
		||||
    $string = preg_replace('/&([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
 | 
			
		||||
    $allowed_html_tags = array_flip($allowed_html_tags);
 | 
			
		||||
    // Late static binding does not work inside anonymous functions.
 | 
			
		||||
    $class = static::class;
 | 
			
		||||
    $splitter = function ($matches) use ($allowed_html_tags, $class) {
 | 
			
		||||
      return $class::split($matches[1], $allowed_html_tags, $class);
 | 
			
		||||
    };
 | 
			
		||||
    // Strip any tags that are not in the list of allowed html tags.
 | 
			
		||||
    return preg_replace_callback('%
 | 
			
		||||
      (
 | 
			
		||||
      <(?=[^a-zA-Z!/])  # a lone <
 | 
			
		||||
      |                 # or
 | 
			
		||||
      <!--.*?-->        # a comment
 | 
			
		||||
      |                 # or
 | 
			
		||||
      <[^>]*(>|$)       # a string that starts with a <, up until the > or the end of the string
 | 
			
		||||
      |                 # or
 | 
			
		||||
      >                 # just a >
 | 
			
		||||
      )%x', $splitter, $string);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Applies a very permissive XSS/HTML filter for admin-only use.
 | 
			
		||||
   *
 | 
			
		||||
   * Use only for fields where it is impractical to use the
 | 
			
		||||
   * whole filter system, but where some (mainly inline) mark-up
 | 
			
		||||
   * is desired (so \Drupal\Component\Utility\Html::escape() is
 | 
			
		||||
   * not acceptable).
 | 
			
		||||
   *
 | 
			
		||||
   * Allows all tags that can be used inside an HTML body, save
 | 
			
		||||
   * for scripts and styles.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $string
 | 
			
		||||
   *   The string to apply the filter to.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The filtered string.
 | 
			
		||||
   *
 | 
			
		||||
   * @ingroup sanitization
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Utility\Xss::getAdminTagList()
 | 
			
		||||
   */
 | 
			
		||||
  public static function filterAdmin($string) {
 | 
			
		||||
    return static::filter($string, static::$adminTags);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Processes an HTML tag.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $string
 | 
			
		||||
   *   The HTML tag to process.
 | 
			
		||||
   * @param array $html_tags
 | 
			
		||||
   *   An array where the keys are the allowed tags and the values are not
 | 
			
		||||
   *   used.
 | 
			
		||||
   * @param string $class
 | 
			
		||||
   *   The called class. This method is called from an anonymous function which
 | 
			
		||||
   *   breaks late static binding. See https://bugs.php.net/bug.php?id=66622 for
 | 
			
		||||
   *   more information.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   If the element isn't allowed, an empty string. Otherwise, the cleaned up
 | 
			
		||||
   *   version of the HTML element.
 | 
			
		||||
   */
 | 
			
		||||
  protected static function split($string, array $html_tags, $class) {
 | 
			
		||||
    if (!str_starts_with($string, '<')) {
 | 
			
		||||
      // We matched a lone ">" character.
 | 
			
		||||
      return '>';
 | 
			
		||||
    }
 | 
			
		||||
    elseif (strlen($string) == 1) {
 | 
			
		||||
      // We matched a lone "<" character.
 | 
			
		||||
      return '<';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9\-]+)\s*([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) {
 | 
			
		||||
      // Seriously malformed.
 | 
			
		||||
      return '';
 | 
			
		||||
    }
 | 
			
		||||
    $slash = trim($matches[1]);
 | 
			
		||||
    $elem = &$matches[2];
 | 
			
		||||
    $attributes = &$matches[3];
 | 
			
		||||
    $comment = &$matches[4];
 | 
			
		||||
 | 
			
		||||
    if ($comment) {
 | 
			
		||||
      $elem = '!--';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Defer to the ::needsRemoval() method to decide if the element is to be
 | 
			
		||||
    // removed. This allows the list of tags to be treated as either a list of
 | 
			
		||||
    // allowed tags or a list of denied tags.
 | 
			
		||||
    if ($class::needsRemoval($html_tags, $elem)) {
 | 
			
		||||
      return '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($comment) {
 | 
			
		||||
      return $comment;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($slash != '') {
 | 
			
		||||
      return "</$elem>";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Is there a closing XHTML slash at the end of the attributes?
 | 
			
		||||
    $attributes = preg_replace('%(\s?)/\s*$%', '\1', $attributes, -1, $count);
 | 
			
		||||
    $xhtml_slash = $count ? ' /' : '';
 | 
			
		||||
 | 
			
		||||
    // Clean up attributes.
 | 
			
		||||
    $attr2 = implode(' ', $class::attributes($attributes));
 | 
			
		||||
    $attr2 = preg_replace('/[<>]/', '', $attr2);
 | 
			
		||||
    $attr2 = strlen($attr2) ? ' ' . $attr2 : '';
 | 
			
		||||
 | 
			
		||||
    return "<$elem$attr2$xhtml_slash>";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Processes a string of HTML attributes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $attributes
 | 
			
		||||
   *   The html attribute to process.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Cleaned up version of the HTML attributes.
 | 
			
		||||
   */
 | 
			
		||||
  protected static function attributes($attributes) {
 | 
			
		||||
    $attributes_array = [];
 | 
			
		||||
    $mode = 0;
 | 
			
		||||
    $attribute_name = '';
 | 
			
		||||
    $skip = FALSE;
 | 
			
		||||
    $skip_protocol_filtering = FALSE;
 | 
			
		||||
 | 
			
		||||
    while (strlen($attributes) != 0) {
 | 
			
		||||
      // Was the last operation successful?
 | 
			
		||||
      $working = 0;
 | 
			
		||||
 | 
			
		||||
      switch ($mode) {
 | 
			
		||||
        case 0:
 | 
			
		||||
          // Attribute name, href for instance.
 | 
			
		||||
          if (preg_match('/^([-a-zA-Z][-a-zA-Z0-9]*)/', $attributes, $match)) {
 | 
			
		||||
            $attribute_name = strtolower($match[1]);
 | 
			
		||||
            $skip = (
 | 
			
		||||
              in_array($attribute_name, ['style', 'srcdoc']) ||
 | 
			
		||||
              str_starts_with($attribute_name, 'on') ||
 | 
			
		||||
              str_starts_with($attribute_name, '-') ||
 | 
			
		||||
              // Ignore long attributes to avoid unnecessary processing
 | 
			
		||||
              // overhead.
 | 
			
		||||
              strlen($attribute_name) > 96
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Values for attributes of type URI should be filtered for
 | 
			
		||||
            // potentially malicious protocols (for example, an href-attribute
 | 
			
		||||
            // starting with "javascript:"). However, for some non-URI
 | 
			
		||||
            // attributes performing this filtering causes valid and safe data
 | 
			
		||||
            // to be mangled. We prevent this by skipping protocol filtering on
 | 
			
		||||
            // such attributes.
 | 
			
		||||
            // @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol()
 | 
			
		||||
            // @see https://www.w3.org/TR/html4/index/attributes.html
 | 
			
		||||
            $skip_protocol_filtering = str_starts_with($attribute_name, 'data-') || in_array($attribute_name, [
 | 
			
		||||
              'title',
 | 
			
		||||
              'alt',
 | 
			
		||||
              'rel',
 | 
			
		||||
              'property',
 | 
			
		||||
              'class',
 | 
			
		||||
              'datetime',
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            $working = $mode = 1;
 | 
			
		||||
            $attributes = preg_replace('/^[-a-zA-Z][-a-zA-Z0-9]*/', '', $attributes);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case 1:
 | 
			
		||||
          // Equals sign or valueless ("selected").
 | 
			
		||||
          if (preg_match('/^\s*=\s*/', $attributes)) {
 | 
			
		||||
            $working = 1;
 | 
			
		||||
            $mode = 2;
 | 
			
		||||
            $attributes = preg_replace('/^\s*=\s*/', '', $attributes);
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (preg_match('/^\s+/', $attributes)) {
 | 
			
		||||
            $working = 1;
 | 
			
		||||
            $mode = 0;
 | 
			
		||||
            if (!$skip) {
 | 
			
		||||
              $attributes_array[] = $attribute_name;
 | 
			
		||||
            }
 | 
			
		||||
            $attributes = preg_replace('/^\s+/', '', $attributes);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case 2:
 | 
			
		||||
          // Once we've finished processing the attribute value continue to look
 | 
			
		||||
          // for attributes.
 | 
			
		||||
          $mode = 0;
 | 
			
		||||
          $working = 1;
 | 
			
		||||
          // Attribute value, a URL after href= for instance.
 | 
			
		||||
          if (preg_match('/^"([^"]*)"(\s+|$)/', $attributes, $match)) {
 | 
			
		||||
            $value = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]);
 | 
			
		||||
 | 
			
		||||
            if (!$skip) {
 | 
			
		||||
              $attributes_array[] = "$attribute_name=\"$value\"";
 | 
			
		||||
            }
 | 
			
		||||
            $attributes = preg_replace('/^"[^"]*"(\s+|$)/', '', $attributes);
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (preg_match("/^'([^']*)'(\s+|$)/", $attributes, $match)) {
 | 
			
		||||
            $value = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]);
 | 
			
		||||
 | 
			
		||||
            if (!$skip) {
 | 
			
		||||
              $attributes_array[] = "$attribute_name='$value'";
 | 
			
		||||
            }
 | 
			
		||||
            $attributes = preg_replace("/^'[^']*'(\s+|$)/", '', $attributes);
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (preg_match("%^([^\s\"']+)(\s+|$)%", $attributes, $match)) {
 | 
			
		||||
            $value = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]);
 | 
			
		||||
 | 
			
		||||
            if (!$skip) {
 | 
			
		||||
              $attributes_array[] = "$attribute_name=\"$value\"";
 | 
			
		||||
            }
 | 
			
		||||
            $attributes = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attributes);
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ($working == 0) {
 | 
			
		||||
        // Not well-formed; remove and try again.
 | 
			
		||||
        $attributes = preg_replace('/
 | 
			
		||||
          ^
 | 
			
		||||
          (
 | 
			
		||||
          "[^"]*("|$)     # - a string that starts with a double quote, up until the next double quote or the end of the string
 | 
			
		||||
          |               # or
 | 
			
		||||
          \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
 | 
			
		||||
          |               # or
 | 
			
		||||
          \S              # - a non-whitespace character
 | 
			
		||||
          )*              # any number of the above three
 | 
			
		||||
          \s*             # any number of whitespaces
 | 
			
		||||
          /x', '', $attributes);
 | 
			
		||||
        $mode = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The attribute list ends with a valueless attribute like "selected".
 | 
			
		||||
    if ($mode == 1 && !$skip) {
 | 
			
		||||
      $attributes_array[] = $attribute_name;
 | 
			
		||||
    }
 | 
			
		||||
    return $attributes_array;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Whether this element needs to be removed altogether.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string[] $html_tags
 | 
			
		||||
   *   The list of HTML tags.
 | 
			
		||||
   * @param string $elem
 | 
			
		||||
   *   The name of the HTML element.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if this element needs to be removed.
 | 
			
		||||
   */
 | 
			
		||||
  protected static function needsRemoval(array $html_tags, $elem) {
 | 
			
		||||
    return !isset($html_tags[strtolower($elem)]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the list of HTML tags allowed by Xss::filterAdmin().
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The list of HTML tags allowed by filterAdmin().
 | 
			
		||||
   */
 | 
			
		||||
  public static function getAdminTagList() {
 | 
			
		||||
    return static::$adminTags;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the standard list of HTML tags allowed by Xss::filter().
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The list of HTML tags allowed by Xss::filter().
 | 
			
		||||
   */
 | 
			
		||||
  public static function getHtmlTagList() {
 | 
			
		||||
    return static::$htmlTags;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								web/core/lib/Drupal/Component/Utility/composer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								web/core/lib/Drupal/Component/Utility/composer.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "drupal/core-utility",
 | 
			
		||||
    "description": "Mostly static utility classes for string, xss, array, image, and other commonly needed manipulations.",
 | 
			
		||||
    "keywords": [
 | 
			
		||||
        "drupal"
 | 
			
		||||
    ],
 | 
			
		||||
    "homepage": "https://www.drupal.org/project/drupal",
 | 
			
		||||
    "license": "GPL-2.0-or-later",
 | 
			
		||||
    "require": {
 | 
			
		||||
        "php": ">=8.3.0",
 | 
			
		||||
        "masterminds/html5": "^2.7"
 | 
			
		||||
    },
 | 
			
		||||
    "autoload": {
 | 
			
		||||
        "psr-4": {
 | 
			
		||||
            "Drupal\\Component\\Utility\\": ""
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "extra": {
 | 
			
		||||
        "_readme": [
 | 
			
		||||
            "This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user