Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
150
core/lib/Drupal/Component/Utility/ArgumentsResolver.php
Normal file
150
core/lib/Drupal/Component/Utility/ArgumentsResolver.php
Normal file
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\ArgumentsResolver.
|
||||
*/
|
||||
|
||||
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 = array();
|
||||
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 = $parameter->getClass();
|
||||
$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) {
|
||||
// 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) {
|
||||
return is_array($callable) ? new \ReflectionMethod($callable[0], $callable[1]) : 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,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\ArgumentsResolverInterface.
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
}
|
47
core/lib/Drupal/Component/Utility/Bytes.php
Normal file
47
core/lib/Drupal/Component/Utility/Bytes.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Bytes.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* Provides helper methods for byte conversions.
|
||||
*/
|
||||
class Bytes {
|
||||
|
||||
/**
|
||||
* The number of bytes in a kilobyte.
|
||||
*
|
||||
* @see http://en.wikipedia.org/wiki/Kilobyte
|
||||
*/
|
||||
const KILOBYTE = 1024;
|
||||
|
||||
/**
|
||||
* Parses a given byte size.
|
||||
*
|
||||
* @param mixed $size
|
||||
* An integer or string size expressed as a number of bytes with optional SI
|
||||
* or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8 bytes, 9mbytes).
|
||||
*
|
||||
* @return int
|
||||
* An integer representation of the size in bytes.
|
||||
*/
|
||||
public static function toInt($size) {
|
||||
// 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 {
|
||||
return round($size);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
102
core/lib/Drupal/Component/Utility/Color.php
Normal file
102
core/lib/Drupal/Component/Utility/Color.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Color.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* Performs color conversions.
|
||||
*/
|
||||
class Color {
|
||||
|
||||
/**
|
||||
* Validates whether a hexadecimal color value is syntactically correct.
|
||||
*
|
||||
* @param $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) {
|
||||
// Must be a string.
|
||||
$valid = is_string($hex);
|
||||
// Hash prefix is optional.
|
||||
$hex = ltrim($hex, '#');
|
||||
// Must be either RGB or RRGGBB.
|
||||
$length = Unicode::strlen($hex);
|
||||
$valid = $valid && ($length === 3 || $length === 6);
|
||||
// Must be a valid hex value.
|
||||
$valid = $valid && ctype_xdigit($hex);
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 array(
|
||||
'red' => $c >> 16 & 0xFF,
|
||||
'green' => $c >> 8 & 0xFF,
|
||||
'blue' => $c & 0xFF,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts RGB color arrays and RGB strings in CSS notation to lowercase
|
||||
* simple colors like '#aabbcc'.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
143
core/lib/Drupal/Component/Utility/Crypt.php
Normal file
143
core/lib/Drupal/Component/Utility/Crypt.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Crypt.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* Utility class for cryptographically-secure string handling routines.
|
||||
*
|
||||
* @ingroup utility
|
||||
*/
|
||||
class Crypt {
|
||||
|
||||
/**
|
||||
* Returns a string of highly randomized bytes (over the full 8-bit range).
|
||||
*
|
||||
* This function is better than simply calling mt_rand() or any other built-in
|
||||
* PHP function because it can return a long string of bytes (compared to < 4
|
||||
* bytes normally from mt_rand()) and uses the best available pseudo-random
|
||||
* source.
|
||||
*
|
||||
* @param int $count
|
||||
* The number of characters (bytes) to return in the string.
|
||||
*
|
||||
* @return string
|
||||
* A randomly generated string.
|
||||
*/
|
||||
public static function randomBytes($count) {
|
||||
// $random_state does not use drupal_static as it stores random bytes.
|
||||
static $random_state, $bytes;
|
||||
|
||||
$missing_bytes = $count - strlen($bytes);
|
||||
|
||||
if ($missing_bytes > 0) {
|
||||
// openssl_random_pseudo_bytes() will find entropy in a system-dependent
|
||||
// way.
|
||||
if (function_exists('openssl_random_pseudo_bytes')) {
|
||||
$bytes .= openssl_random_pseudo_bytes($missing_bytes);
|
||||
}
|
||||
|
||||
// Else, read directly from /dev/urandom, which is available on many *nix
|
||||
// systems and is considered cryptographically secure.
|
||||
elseif ($fh = @fopen('/dev/urandom', 'rb')) {
|
||||
// PHP only performs buffered reads, so in reality it will always read
|
||||
// at least 4096 bytes. Thus, it costs nothing extra to read and store
|
||||
// that much so as to speed any additional invocations.
|
||||
$bytes .= fread($fh, max(4096, $missing_bytes));
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
// If we couldn't get enough entropy, this simple hash-based PRNG will
|
||||
// generate a good set of pseudo-random bytes on any system.
|
||||
// Note that it may be important that our $random_state is passed
|
||||
// through hash() prior to being rolled into $output, that the two hash()
|
||||
// invocations are different, and that the extra input into the first one -
|
||||
// the microtime() - is prepended rather than appended. This is to avoid
|
||||
// directly leaking $random_state via the $output stream, which could
|
||||
// allow for trivial prediction of further "random" numbers.
|
||||
if (strlen($bytes) < $count) {
|
||||
// Initialize on the first call. The contents of $_SERVER includes a mix
|
||||
// of user-specific and system information that varies a little with
|
||||
// each page.
|
||||
if (!isset($random_state)) {
|
||||
$random_state = print_r($_SERVER, TRUE);
|
||||
if (function_exists('getmypid')) {
|
||||
// Further initialize with the somewhat random PHP process ID.
|
||||
$random_state .= getmypid();
|
||||
}
|
||||
$bytes = '';
|
||||
}
|
||||
|
||||
do {
|
||||
$random_state = hash('sha256', microtime() . mt_rand() . $random_state);
|
||||
$bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
|
||||
} while (strlen($bytes) < $count);
|
||||
}
|
||||
}
|
||||
$output = substr($bytes, 0, $count);
|
||||
$bytes = substr($bytes, $count);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 strtr($hmac, array('+' => '-', '/' => '_', '=' => ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 strtr($hash, array('+' => '-', '/' => '_', '=' => ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URL-safe, base64 encoded string of highly randomized bytes.
|
||||
*
|
||||
* @param $byte_count
|
||||
* The number of random bytes to fetch and base64 encode.
|
||||
*
|
||||
* @return string
|
||||
* The base64 encoded result will have a length of up to 4 * $byte_count.
|
||||
*
|
||||
* @see \Drupal\Component\Utility\Crypt::randomBytes()
|
||||
*/
|
||||
public static function randomBytesBase64($count = 32) {
|
||||
return strtr(base64_encode(static::randomBytes($count)), array('+' => '-', '/' => '_', '=' => ''));
|
||||
}
|
||||
|
||||
}
|
55
core/lib/Drupal/Component/Utility/DiffArray.php
Normal file
55
core/lib/Drupal/Component/Utility/DiffArray.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\DiffArray.
|
||||
*/
|
||||
|
||||
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 = array();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
45
core/lib/Drupal/Component/Utility/Environment.php
Normal file
45
core/lib/Drupal/Component/Utility/Environment.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Environment.
|
||||
*/
|
||||
|
||||
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 $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::toInt($memory_limit) >= Bytes::toInt($required)));
|
||||
}
|
||||
|
||||
}
|
351
core/lib/Drupal/Component/Utility/Html.php
Normal file
351
core/lib/Drupal/Component/Utility/Html.php
Normal file
|
@ -0,0 +1,351 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Html.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* 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 = array();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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 string $class
|
||||
* The class name to clean.
|
||||
*
|
||||
* @return string
|
||||
* The cleaned class name.
|
||||
*/
|
||||
public static function getClass($class) {
|
||||
if (!isset(static::$classes[$class])) {
|
||||
static::$classes[$class] = static::cleanCssIdentifier(Unicode::strtolower($class));
|
||||
}
|
||||
return static::$classes[$class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a string for use as a CSS identifier (element, class, or ID name).
|
||||
*
|
||||
* http://www.w3.org/TR/CSS21/syndata.html#characters 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.
|
||||
*/
|
||||
public static function cleanCssIdentifier($identifier, array $filter = array(
|
||||
' ' => '-',
|
||||
'_' => '-',
|
||||
'__' => '__',
|
||||
'/' => '-',
|
||||
'[' => '-',
|
||||
']' => ''
|
||||
)) {
|
||||
$identifier = strtr($identifier, $filter);
|
||||
// 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', '', $identifier);
|
||||
// Identifiers cannot start with a digit, two hyphens, or a hyphen followed by a digit.
|
||||
$identifier = preg_replace(array(
|
||||
'/^[0-9]/',
|
||||
'/^(-[0-9])|^(--)/'
|
||||
), array('_', '__'), $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 (X)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 = array();
|
||||
}
|
||||
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 = strtr(Unicode::strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
|
||||
|
||||
// As defined in http://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
|
||||
// (http://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 (X)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 (X)HTML snippet to load. Invalid markup will be corrected on
|
||||
* import.
|
||||
*
|
||||
* @return \DOMDocument
|
||||
* A \DOMDocument that represents the loaded (X)HTML snippet.
|
||||
*/
|
||||
public static function load($html) {
|
||||
$document = <<<EOD
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head>
|
||||
<body>!html</body>
|
||||
</html>
|
||||
EOD;
|
||||
// PHP's \DOMDocument serialization adds straw whitespace in case the markup
|
||||
// of the wrapping document contains newlines, so ensure to remove all
|
||||
// newlines before injecting the actual HTML body to process.
|
||||
$document = strtr($document, array("\n" => '', '!html' => $html));
|
||||
|
||||
$dom = new \DOMDocument();
|
||||
// Ignore warnings during HTML soup loading.
|
||||
@$dom->loadHTML($document);
|
||||
|
||||
return $dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the body of a \DOMDocument back to an HTML snippet.
|
||||
*
|
||||
* The function serializes the body part of a \DOMDocument back to an (X)HTML
|
||||
* snippet. The resulting (X)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 (X)HTML snippet, as a string.
|
||||
*/
|
||||
public static function serialize(\DOMDocument $document) {
|
||||
$body_node = $document->getElementsByTagName('body')->item(0);
|
||||
$html = '';
|
||||
|
||||
foreach ($body_node->getElementsByTagName('script') as $node) {
|
||||
static::escapeCdataElement($node);
|
||||
}
|
||||
foreach ($body_node->getElementsByTagName('style') as $node) {
|
||||
static::escapeCdataElement($node, '/*', '*/');
|
||||
}
|
||||
foreach ($body_node->childNodes as $node) {
|
||||
$html .= $document->saveXML($node);
|
||||
}
|
||||
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) {
|
||||
$embed_prefix = "\n<!--{$comment_start}--><![CDATA[{$comment_start} ><!--{$comment_end}\n";
|
||||
$embed_suffix = "\n{$comment_start}--><!]]>{$comment_end}\n";
|
||||
|
||||
// Prevent invalid cdata escaping as this would throw a DOM error.
|
||||
// This is the same behavior as found in libxml2.
|
||||
// Related W3C standard: http://www.w3.org/TR/REC-xml/#dt-cdsection
|
||||
// Fix explanation: http://en.wikipedia.org/wiki/CDATA#Nesting
|
||||
$data = str_replace(']]>', ']]]]><![CDATA[>', $child_node->data);
|
||||
|
||||
$fragment = $node->ownerDocument->createDocumentFragment();
|
||||
$fragment->appendXML($embed_prefix . $data . $embed_suffix);
|
||||
$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>).
|
||||
*
|
||||
* @param string $text
|
||||
* The text to decode entities in.
|
||||
*
|
||||
* @return string
|
||||
* The input $text, with all HTML entities decoded once.
|
||||
*/
|
||||
public static function decodeEntities($text) {
|
||||
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
}
|
66
core/lib/Drupal/Component/Utility/Image.php
Normal file
66
core/lib/Drupal/Component/Utility/Image.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Image.
|
||||
*/
|
||||
|
||||
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.
|
||||
*
|
||||
* @see image_scale()
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
352
core/lib/Drupal/Component/Utility/NestedArray.php
Normal file
352
core/lib/Drupal/Component/Utility/NestedArray.php
Normal file
|
@ -0,0 +1,352 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\NestedArray.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* Provides helpers to perform operations on nested arrays and array keys 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'] = array(
|
||||
* '#type' => 'text_format',
|
||||
* '#title' => t('Signature'),
|
||||
* );
|
||||
* // Or, it might be further nested:
|
||||
* $form['signature_settings']['user']['signature'] = array(
|
||||
* '#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 array('signature_settings', 'signature') @endcode
|
||||
* in the first case or
|
||||
* @code array('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, PHP throws an error 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 ($force && isset($ref) && !is_array($ref)) {
|
||||
$ref = array();
|
||||
}
|
||||
$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'] = array(
|
||||
* '#type' => 'text_format',
|
||||
* '#title' => t('Signature'),
|
||||
* );
|
||||
* // Or, it might be further nested:
|
||||
* $form['signature_settings']['user']['signature'] = array(
|
||||
* '#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 array('signature_settings', 'signature') @endcode
|
||||
* in the first case or
|
||||
* @code array('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::unset_nested_value($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 = array('fragment' => 'x', 'attributes' => array('title' => t('X'), 'class' => array('a', 'b')));
|
||||
* $link_options_2 = array('fragment' => 'y', 'attributes' => array('title' => t('Y'), 'class' => array('c', 'd')));
|
||||
*
|
||||
* // This results in array('fragment' => array('x', 'y'), 'attributes' => array('title' => array(t('X'), t('Y')), 'class' => array('a', 'b', 'c', 'd'))).
|
||||
* $incorrect = array_merge_recursive($link_options_1, $link_options_2);
|
||||
*
|
||||
* // This results in array('fragment' => 'y', 'attributes' => array('title' => t('Y'), 'class' => array('a', 'b', 'c', 'd'))).
|
||||
* $correct = NestedArray::mergeDeep($link_options_1, $link_options_2);
|
||||
* @endcode
|
||||
*
|
||||
* @param array ...
|
||||
* Arrays to merge.
|
||||
*
|
||||
* @return array
|
||||
* The merged array.
|
||||
*
|
||||
* @see NestedArray::mergeDeepArray()
|
||||
*/
|
||||
public static function mergeDeep() {
|
||||
return self::mergeDeepArray(func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(array($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 = array();
|
||||
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_integer($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(array($result[$key], $value), $preserve_integer_keys);
|
||||
}
|
||||
// Otherwise, use the latter value, overriding any previous value.
|
||||
else {
|
||||
$result[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
105
core/lib/Drupal/Component/Utility/Number.php
Normal file
105
core/lib/Drupal/Component/Utility/Number.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Number.
|
||||
*/
|
||||
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 numeric $value
|
||||
* The value that needs to be checked.
|
||||
* @param numeric $step
|
||||
* The step scale factor. Must be positive.
|
||||
* @param numeric $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 = (double) 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 = (double) 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 = (double)($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((int) $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.
|
||||
*
|
||||
* @see \Drupal\Component\Utility\Number::intToAlphadecimal
|
||||
*/
|
||||
public static function alphadecimalToInt($string = '00') {
|
||||
return (int) base_convert(substr($string, 1), 36, 10);
|
||||
}
|
||||
|
||||
}
|
45
core/lib/Drupal/Component/Utility/OpCodeCache.php
Normal file
45
core/lib/Drupal/Component/Utility/OpCodeCache.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\OpCodeCache.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* Provides helpers to handle PHP opcode caches.
|
||||
*
|
||||
* @ingroup utility
|
||||
*/
|
||||
class OpCodeCache {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
// If apcu extension is enabled in PHP 5.5 or greater it emulates apc.
|
||||
// This is to provide an easy upgrade path if you are using apc's user
|
||||
// caching however the emulation does not extend to opcode caching.
|
||||
// Therefore we need to check if the function exists as well.
|
||||
if (extension_loaded('apc') && function_exists('apc_delete_file')) {
|
||||
// apc_delete_file() throws a PHP warning in case the specified file was
|
||||
// not compiled yet.
|
||||
// @see http://php.net/manual/en/function.apc-delete-file.php
|
||||
@apc_delete_file($pathname);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
304
core/lib/Drupal/Component/Utility/Random.php
Normal file
304
core/lib/Drupal/Component/Utility/Random.php
Normal file
|
@ -0,0 +1,304 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Random.
|
||||
*/
|
||||
|
||||
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 = array();
|
||||
|
||||
/**
|
||||
* A list of unique names generated by name().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $names = array();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a string that looks like a word (letters only, alternating consonants and vowels).
|
||||
*
|
||||
* @param int $length
|
||||
* The desired word length.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function word($length) {
|
||||
mt_srand((double) microtime()*1000000);
|
||||
|
||||
$vowels = array("a", "e", "i", "o", "u");
|
||||
$cons = array("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 \stdClass
|
||||
* 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) {
|
||||
$dictionary = array("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");
|
||||
$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
|
||||
* @return string
|
||||
*/
|
||||
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 int $min_resolution
|
||||
* @param int $max_resolution
|
||||
*
|
||||
* @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, $x, $y, $x + $width / 2, $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);
|
||||
$smaller_dimension = ($smaller_dimension % 2) ? $smaller_dimension : $smaller_dimension;
|
||||
imageellipse($im, $width / 2, $height / 2, $smaller_dimension, $smaller_dimension, $color);
|
||||
|
||||
$save_function = 'image'. ($extension == 'jpg' ? 'jpeg' : $extension);
|
||||
$save_function($im, $destination);
|
||||
return $destination;
|
||||
}
|
||||
|
||||
}
|
334
core/lib/Drupal/Component/Utility/SafeMarkup.php
Normal file
334
core/lib/Drupal/Component/Utility/SafeMarkup.php
Normal file
|
@ -0,0 +1,334 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\SafeMarkup.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* Manages known safe strings for rendering at the theme layer.
|
||||
*
|
||||
* The Twig theme engine autoescapes string variables in the template, so it
|
||||
* is possible for a string of markup to become double-escaped. SafeMarkup
|
||||
* provides a store for known safe strings and methods to manage them
|
||||
* throughout the page request.
|
||||
*
|
||||
* Strings sanitized by self::checkPlain() or Xss::filter() are automatically
|
||||
* marked safe, as are markup strings created from render arrays via
|
||||
* drupal_render().
|
||||
*
|
||||
* This class should be limited to internal use only. Module developers should
|
||||
* instead use the appropriate
|
||||
* @link sanitization sanitization functions @endlink or the
|
||||
* @link theme_render theme and render systems @endlink so that the output can
|
||||
* can be themed, escaped, and altered properly.
|
||||
*
|
||||
* @see TwigExtension::escapeFilter()
|
||||
* @see twig_render_template()
|
||||
* @see sanitization
|
||||
* @see theme_render
|
||||
*/
|
||||
class SafeMarkup {
|
||||
|
||||
/**
|
||||
* The list of safe strings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $safeStrings = array();
|
||||
|
||||
/**
|
||||
* Adds a string to a list of strings marked as secure.
|
||||
*
|
||||
* This method is for internal use. Do not use it to prevent escaping of
|
||||
* markup; instead, use the appropriate
|
||||
* @link sanitization sanitization functions @endlink or the
|
||||
* @link theme_render theme and render systems @endlink so that the output
|
||||
* can be themed, escaped, and altered properly.
|
||||
*
|
||||
* This marks strings as secure for the entire page render, not just the code
|
||||
* or element that set it. Therefore, only valid HTML should be
|
||||
* marked as safe (never partial markup). For example, you should never do:
|
||||
* @code
|
||||
* SafeMarkup::set('<');
|
||||
* @endcode
|
||||
* or:
|
||||
* @code
|
||||
* SafeMarkup::set('<script>');
|
||||
* @endcode
|
||||
*
|
||||
* @param string $string
|
||||
* The content to be marked as secure.
|
||||
* @param string $strategy
|
||||
* The escaping strategy used for this string. Two values are supported
|
||||
* by default:
|
||||
* - 'html': (default) The string is safe for use in HTML code.
|
||||
* - 'all': The string is safe for all use cases.
|
||||
* See the
|
||||
* @link http://twig.sensiolabs.org/doc/filters/escape.html Twig escape documentation @endlink
|
||||
* for more information on escaping strategies in Twig.
|
||||
*
|
||||
* @return string
|
||||
* The input string that was marked as safe.
|
||||
*/
|
||||
public static function set($string, $strategy = 'html') {
|
||||
$string = (string) $string;
|
||||
static::$safeStrings[$string][$strategy] = TRUE;
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is safe to output.
|
||||
*
|
||||
* @param string|\Drupal\Component\Utility\SafeStringInterface $string
|
||||
* The content to be checked.
|
||||
* @param string $strategy
|
||||
* The escaping strategy. See self::set(). Defaults to 'html'.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the string has been marked secure, FALSE otherwise.
|
||||
*/
|
||||
public static function isSafe($string, $strategy = 'html') {
|
||||
// Do the instanceof checks first to save unnecessarily casting the object
|
||||
// to a string.
|
||||
return $string instanceOf SafeStringInterface || isset(static::$safeStrings[(string) $string][$strategy]) ||
|
||||
isset(static::$safeStrings[(string) $string]['all']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds previously retrieved known safe strings to the safe string list.
|
||||
*
|
||||
* This is useful for the batch and form APIs, where it is important to
|
||||
* preserve the safe markup state across page requests. The strings will be
|
||||
* added to any safe strings already marked for the current request.
|
||||
*
|
||||
* @param array $safe_strings
|
||||
* A list of safe strings as previously retrieved by self::getAll().
|
||||
*
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public static function setMultiple(array $safe_strings) {
|
||||
foreach ($safe_strings as $string => $strategies) {
|
||||
foreach ($strategies as $strategy => $value) {
|
||||
$string = (string) $string;
|
||||
if ($value === TRUE) {
|
||||
static::$safeStrings[$string][$strategy] = TRUE;
|
||||
}
|
||||
else {
|
||||
// Danger - something is very wrong.
|
||||
throw new \UnexpectedValueException('Only the value TRUE is accepted for safe strings');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes special characters in a plain-text string for display as HTML.
|
||||
*
|
||||
* @param string $string
|
||||
* A string.
|
||||
*
|
||||
* @return string
|
||||
* The escaped string. If $string was already set as safe with
|
||||
* self::set(), it won't be escaped again.
|
||||
*/
|
||||
public static function escape($string) {
|
||||
return static::isSafe($string) ? $string : static::checkPlain($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a very permissive XSS/HTML filter for admin-only use.
|
||||
*
|
||||
* @param string $string
|
||||
* A string.
|
||||
*
|
||||
* @return string
|
||||
* The escaped string. If $string was already set as safe with
|
||||
* self::set(), it won't be escaped again.
|
||||
*
|
||||
* @see \Drupal\Component\Utility\Xss::filterAdmin()
|
||||
*/
|
||||
public static function checkAdminXss($string) {
|
||||
return static::isSafe($string) ? $string : Xss::filterAdmin($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all strings currently marked as safe.
|
||||
*
|
||||
* This is useful for the batch and form APIs, where it is important to
|
||||
* preserve the safe markup state across page requests.
|
||||
*
|
||||
* @return array
|
||||
* An array of strings currently marked safe.
|
||||
*/
|
||||
public static function getAll() {
|
||||
return static::$safeStrings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes special characters in a plain-text string for display as HTML.
|
||||
*
|
||||
* Also validates strings as UTF-8. All processed strings are also
|
||||
* automatically flagged as safe markup strings for rendering.
|
||||
*
|
||||
* @param string $text
|
||||
* The text to be checked or processed.
|
||||
*
|
||||
* @return string
|
||||
* An HTML safe version of $text, or an empty string if $text is not valid
|
||||
* UTF-8.
|
||||
*
|
||||
* @ingroup sanitization
|
||||
*
|
||||
* @see drupal_validate_utf8()
|
||||
*/
|
||||
public static function checkPlain($text) {
|
||||
$string = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
|
||||
static::$safeStrings[$string]['html'] = TRUE;
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a string for HTML display by replacing variable placeholders.
|
||||
*
|
||||
* This function replaces variable placeholders in a string with the requested
|
||||
* values and escapes the values so they can be safely displayed as HTML. It
|
||||
* should be used on any unknown text that is intended to be printed to an
|
||||
* HTML page (especially text that may have come from untrusted users, since
|
||||
* in that case it prevents cross-site scripting and other security problems).
|
||||
*
|
||||
* In most cases, you should use t() rather than calling this function
|
||||
* directly, since it will translate the text (on non-English-only sites) in
|
||||
* addition to formatting it.
|
||||
*
|
||||
* @param string $string
|
||||
* A string containing placeholders. The string itself is not escaped, any
|
||||
* unsafe content must be in $args and inserted via placeholders.
|
||||
* @param array $args
|
||||
* An associative array of replacements to make. Occurrences in $string of
|
||||
* any key in $args are replaced with the corresponding value, after
|
||||
* optional sanitization and formatting. The type of sanitization and
|
||||
* formatting depends on the first character of the key:
|
||||
* - @variable: Escaped to HTML using self::escape(). Use this as the
|
||||
* default choice for anything displayed on a page on the site.
|
||||
* - %variable: Escaped to HTML and formatted using self::placeholder(),
|
||||
* which makes the following HTML code:
|
||||
* @code
|
||||
* <em class="placeholder">text output here.</em>
|
||||
* @endcode
|
||||
* - !variable: Inserted as is, with no sanitization or formatting. Only
|
||||
* use this when the resulting string is being generated for one of:
|
||||
* - Non-HTML usage, such as a plain-text email.
|
||||
* - Non-direct HTML output, such as a plain-text variable that will be
|
||||
* printed as an HTML attribute value and therefore formatted with
|
||||
* self::checkPlain() as part of that.
|
||||
* - Some other special reason for suppressing sanitization.
|
||||
*
|
||||
* @return string
|
||||
* The formatted string, which is marked as safe unless sanitization of an
|
||||
* unsafe argument was suppressed (see above).
|
||||
*
|
||||
* @ingroup sanitization
|
||||
*
|
||||
* @see t()
|
||||
*/
|
||||
public static function format($string, array $args = array()) {
|
||||
$safe = TRUE;
|
||||
|
||||
// Transform arguments before inserting them.
|
||||
foreach ($args as $key => $value) {
|
||||
switch ($key[0]) {
|
||||
case '@':
|
||||
// Escaped only.
|
||||
$args[$key] = static::escape($value);
|
||||
break;
|
||||
|
||||
case '%':
|
||||
default:
|
||||
// Escaped and placeholder.
|
||||
$args[$key] = static::placeholder($value);
|
||||
break;
|
||||
|
||||
case '!':
|
||||
// Pass-through.
|
||||
if (!static::isSafe($value)) {
|
||||
$safe = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$output = strtr($string, $args);
|
||||
if ($safe) {
|
||||
static::$safeStrings[$output]['html'] = TRUE;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats text for emphasized display in a placeholder inside a sentence.
|
||||
*
|
||||
* Used automatically by self::format().
|
||||
*
|
||||
* @param string $text
|
||||
* The text to format (plain-text).
|
||||
*
|
||||
* @return string
|
||||
* The formatted text (html).
|
||||
*/
|
||||
public static function placeholder($text) {
|
||||
$string = '<em class="placeholder">' . static::escape($text) . '</em>';
|
||||
static::$safeStrings[$string]['html'] = TRUE;
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all occurrences of the search string with the replacement string.
|
||||
*
|
||||
* Functions identically to str_replace(), but marks the returned output as
|
||||
* safe if all the inputs and the subject have also been marked as safe.
|
||||
*
|
||||
* @param string|array $search
|
||||
* The value being searched for. An array may be used to designate multiple
|
||||
* values to search for.
|
||||
* @param string|array $replace
|
||||
* The replacement value that replaces found search values. An array may be
|
||||
* used to designate multiple replacements.
|
||||
* @param string $subject
|
||||
* The string or array being searched and replaced on.
|
||||
*
|
||||
* @return string
|
||||
* The passed subject with replaced values.
|
||||
*/
|
||||
public static function replace($search, $replace, $subject) {
|
||||
$output = str_replace($search, $replace, $subject);
|
||||
|
||||
// If any replacement is unsafe, then the output is also unsafe, so just
|
||||
// return the output.
|
||||
if (!is_array($replace)) {
|
||||
if (!SafeMarkup::isSafe($replace)) {
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($replace as $replacement) {
|
||||
if (!SafeMarkup::isSafe($replacement)) {
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the subject is unsafe, then the output is as well, so return it.
|
||||
if (!SafeMarkup::isSafe($subject)) {
|
||||
return $output;
|
||||
}
|
||||
else {
|
||||
// If we have reached this point, then all replacements were safe. If the
|
||||
// subject was also safe, then mark the entire output as safe.
|
||||
return SafeMarkup::set($output);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
36
core/lib/Drupal/Component/Utility/SafeStringInterface.php
Normal file
36
core/lib/Drupal/Component/Utility/SafeStringInterface.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\SafeStringInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* Marks an object's __toString() method as returning safe markup.
|
||||
*
|
||||
* This interface should only be used on objects that emit known safe strings
|
||||
* from their __toString() method. If there is any risk of the method returning
|
||||
* user-entered data that has not been filtered first, it must not be used.
|
||||
*
|
||||
* @internal
|
||||
* This interface is marked as internal because it should only be used by
|
||||
* objects used during rendering. Currently, there is no use case for this
|
||||
* interface in contrib or custom code.
|
||||
*
|
||||
* @see \Drupal\Component\Utility\SafeMarkup::set()
|
||||
* @see \Drupal\Component\Utility\SafeMarkup::isSafe()
|
||||
* @see \Drupal\Core\Template\TwigExtension::escapeFilter()
|
||||
*/
|
||||
interface SafeStringInterface {
|
||||
|
||||
/**
|
||||
* Returns a safe string.
|
||||
*
|
||||
* @return string
|
||||
* The safe string.
|
||||
*/
|
||||
public function __toString();
|
||||
|
||||
}
|
137
core/lib/Drupal/Component/Utility/SortArray.php
Normal file
137
core/lib/Drupal/Component/Utility/SortArray.php
Normal file
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\SortArray.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
if ($a_weight == $b_weight) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($a_weight < $b_weight) ? -1 : 1;
|
||||
}
|
||||
|
||||
}
|
80
core/lib/Drupal/Component/Utility/Tags.php
Normal file
80
core/lib/Drupal/Component/Utility/Tags.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Tags.
|
||||
*/
|
||||
|
||||
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, "somecompany, llc", "and ""this"" w,o.rks", foo bar
|
||||
$regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
|
||||
preg_match_all($regexp, $tags, $matches);
|
||||
$typed_tags = array_unique($matches[1]);
|
||||
|
||||
$tags = array();
|
||||
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 (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) {
|
||||
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 = array();
|
||||
foreach ($tags as $tag) {
|
||||
$encoded_tags[] = self::encode($tag);
|
||||
}
|
||||
return implode(', ', $encoded_tags);
|
||||
}
|
||||
|
||||
}
|
81
core/lib/Drupal/Component/Utility/Timer.php
Normal file
81
core/lib/Drupal/Component/Utility/Timer.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Timer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* Provides helpers to use timers throughout a request.
|
||||
*
|
||||
* @ingroup utility
|
||||
*/
|
||||
class Timer {
|
||||
|
||||
static protected $timers = array();
|
||||
|
||||
/**
|
||||
* Starts the timer with the specified name.
|
||||
*
|
||||
* If you start and stop the same timer multiple times, the measured intervals
|
||||
* will be accumulated.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the timer.
|
||||
*/
|
||||
static public 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.
|
||||
*/
|
||||
static public 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).
|
||||
*/
|
||||
static public 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];
|
||||
}
|
||||
|
||||
}
|
700
core/lib/Drupal/Component/Utility/Unicode.php
Normal file
700
core/lib/Drupal/Component/Utility/Unicode.php
Normal file
|
@ -0,0 +1,700 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Unicode.
|
||||
*/
|
||||
|
||||
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 the PHP mbstring extension is
|
||||
* being used.
|
||||
*/
|
||||
const STATUS_MULTIBYTE = 1;
|
||||
|
||||
/**
|
||||
* Indicates an error during check for PHP unicode support.
|
||||
*/
|
||||
const STATUS_ERROR = -1;
|
||||
|
||||
/**
|
||||
* Holds the multibyte capabilities of the current environment.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $status = 0;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return static::$status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value for multibyte support status for the current environment.
|
||||
*
|
||||
* The following status keys are supported:
|
||||
* - \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.
|
||||
*
|
||||
* @param int $status
|
||||
* The new status of multibyte support.
|
||||
*/
|
||||
public static function setStatus($status) {
|
||||
if (!in_array($status, array(static::STATUS_SINGLEBYTE, static::STATUS_MULTIBYTE, static::STATUS_ERROR))) {
|
||||
throw new \InvalidArgumentException('Invalid status value for unicode support.');
|
||||
}
|
||||
static::$status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
// Check for mbstring extension.
|
||||
if (!function_exists('mb_strlen')) {
|
||||
static::$status = static::STATUS_SINGLEBYTE;
|
||||
return 'mb_strlen';
|
||||
}
|
||||
|
||||
// Check mbstring configuration.
|
||||
if (ini_get('mbstring.func_overload') != 0) {
|
||||
static::$status = static::STATUS_ERROR;
|
||||
return 'mbstring.func_overload';
|
||||
}
|
||||
if (ini_get('mbstring.encoding_translation') != 0) {
|
||||
static::$status = static::STATUS_ERROR;
|
||||
return 'mbstring.encoding_translation';
|
||||
}
|
||||
// mbstring.http_input and mbstring.http_output are deprecated and empty by
|
||||
// default in PHP 5.6.
|
||||
if (version_compare(PHP_VERSION, '5.6.0') == -1) {
|
||||
if (ini_get('mbstring.http_input') != 'pass') {
|
||||
static::$status = static::STATUS_ERROR;
|
||||
return 'mbstring.http_input';
|
||||
}
|
||||
if (ini_get('mbstring.http_output') != 'pass') {
|
||||
static::$status = static::STATUS_ERROR;
|
||||
return 'mbstring.http_output';
|
||||
}
|
||||
}
|
||||
|
||||
// Set appropriate configuration.
|
||||
mb_internal_encoding('utf-8');
|
||||
mb_language('uni');
|
||||
static::$status = static::STATUS_MULTIBYTE;
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes UTF byte-order mark (BOM) into the encoding's 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 = array(
|
||||
"\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 (strpos($data, $bom) === 0) {
|
||||
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) {
|
||||
if (function_exists('iconv')) {
|
||||
return @iconv($encoding, 'utf-8', $data);
|
||||
}
|
||||
elseif (function_exists('mb_convert_encoding')) {
|
||||
return @mb_convert_encoding($data, 'utf-8', $encoding);
|
||||
}
|
||||
elseif (function_exists('recode_string')) {
|
||||
return @recode_string($encoding . '..utf-8', $data);
|
||||
}
|
||||
// Cannot convert.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
while (--$len >= 0 && ord($string[$len]) >= 0x80 && ord($string[$len]) < 0xC0);
|
||||
|
||||
return substr($string, 0, $len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of characters in a UTF-8 string.
|
||||
*
|
||||
* This is less than or equal to the byte count.
|
||||
*
|
||||
* @param string $text
|
||||
* The string to run the operation on.
|
||||
*
|
||||
* @return int
|
||||
* The length of the string.
|
||||
*/
|
||||
public static function strlen($text) {
|
||||
if (static::getStatus() == static::STATUS_MULTIBYTE) {
|
||||
return mb_strlen($text);
|
||||
}
|
||||
else {
|
||||
// Do not count UTF-8 continuation bytes.
|
||||
return strlen(preg_replace("/[\x80-\xBF]/", '', $text));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a UTF-8 string to uppercase.
|
||||
*
|
||||
* @param string $text
|
||||
* The string to run the operation on.
|
||||
*
|
||||
* @return string
|
||||
* The string in uppercase.
|
||||
*/
|
||||
public static function strtoupper($text) {
|
||||
if (static::getStatus() == static::STATUS_MULTIBYTE) {
|
||||
return mb_strtoupper($text);
|
||||
}
|
||||
else {
|
||||
// Use C-locale for ASCII-only uppercase.
|
||||
$text = strtoupper($text);
|
||||
// Case flip Latin-1 accented letters.
|
||||
$text = preg_replace_callback('/\xC3[\xA0-\xB6\xB8-\xBE]/', '\Drupal\Component\Utility\Unicode::caseFlip', $text);
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a UTF-8 string to lowercase.
|
||||
*
|
||||
* @param string $text
|
||||
* The string to run the operation on.
|
||||
*
|
||||
* @return string
|
||||
* The string in lowercase.
|
||||
*/
|
||||
public static function strtolower($text) {
|
||||
if (static::getStatus() == static::STATUS_MULTIBYTE) {
|
||||
return mb_strtolower($text);
|
||||
}
|
||||
else {
|
||||
// Use C-locale for ASCII-only lowercase.
|
||||
$text = strtolower($text);
|
||||
// Case flip Latin-1 accented letters.
|
||||
$text = preg_replace_callback('/\xC3[\x80-\x96\x98-\x9E]/', '\Drupal\Component\Utility\Unicode::caseFlip', $text);
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 static::strtoupper(static::substr($text, 0, 1)) . static::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 static::strtolower(static::substr($text, 0, 1)) . static::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] . Unicode::strtoupper($matches[2]);
|
||||
}, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cuts off a piece of a string based on character indices and counts.
|
||||
*
|
||||
* Follows the same behavior as PHP's own substr() function. Note that for
|
||||
* cutting off a string at a known character/substring location, the usage of
|
||||
* PHP's normal strpos/substr is safe and much faster.
|
||||
*
|
||||
* @param string $text
|
||||
* The input string.
|
||||
* @param int $start
|
||||
* The position at which to start reading.
|
||||
* @param int $length
|
||||
* The number of characters to read.
|
||||
*
|
||||
* @return string
|
||||
* The shortened string.
|
||||
*/
|
||||
public static function substr($text, $start, $length = NULL) {
|
||||
if (static::getStatus() == static::STATUS_MULTIBYTE) {
|
||||
return $length === NULL ? mb_substr($text, $start) : mb_substr($text, $start, $length);
|
||||
}
|
||||
else {
|
||||
$strlen = strlen($text);
|
||||
// Find the starting byte offset.
|
||||
$bytes = 0;
|
||||
if ($start > 0) {
|
||||
// Count all the continuation bytes from the start until we have found
|
||||
// $start characters or the end of the string.
|
||||
$bytes = -1; $chars = -1;
|
||||
while ($bytes < $strlen - 1 && $chars < $start) {
|
||||
$bytes++;
|
||||
$c = ord($text[$bytes]);
|
||||
if ($c < 0x80 || $c >= 0xC0) {
|
||||
$chars++;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($start < 0) {
|
||||
// Count all the continuation bytes from the end until we have found
|
||||
// abs($start) characters.
|
||||
$start = abs($start);
|
||||
$bytes = $strlen; $chars = 0;
|
||||
while ($bytes > 0 && $chars < $start) {
|
||||
$bytes--;
|
||||
$c = ord($text[$bytes]);
|
||||
if ($c < 0x80 || $c >= 0xC0) {
|
||||
$chars++;
|
||||
}
|
||||
}
|
||||
}
|
||||
$istart = $bytes;
|
||||
|
||||
// Find the ending byte offset.
|
||||
if ($length === NULL) {
|
||||
$iend = $strlen;
|
||||
}
|
||||
elseif ($length > 0) {
|
||||
// Count all the continuation bytes from the starting index until we have
|
||||
// found $length characters or reached the end of the string, then
|
||||
// backtrace one byte.
|
||||
$iend = $istart - 1;
|
||||
$chars = -1;
|
||||
$last_real = FALSE;
|
||||
while ($iend < $strlen - 1 && $chars < $length) {
|
||||
$iend++;
|
||||
$c = ord($text[$iend]);
|
||||
$last_real = FALSE;
|
||||
if ($c < 0x80 || $c >= 0xC0) {
|
||||
$chars++;
|
||||
$last_real = TRUE;
|
||||
}
|
||||
}
|
||||
// Backtrace one byte if the last character we found was a real character
|
||||
// and we don't need it.
|
||||
if ($last_real && $chars >= $length) {
|
||||
$iend--;
|
||||
}
|
||||
}
|
||||
elseif ($length < 0) {
|
||||
// Count all the continuation bytes from the end until we have found
|
||||
// abs($start) characters, then backtrace one byte.
|
||||
$length = abs($length);
|
||||
$iend = $strlen; $chars = 0;
|
||||
while ($iend > 0 && $chars < $length) {
|
||||
$iend--;
|
||||
$c = ord($text[$iend]);
|
||||
if ($c < 0x80 || $c >= 0xC0) {
|
||||
$chars++;
|
||||
}
|
||||
}
|
||||
// Backtrace one byte if we are not at the beginning of the string.
|
||||
if ($iend > 0) {
|
||||
$iend--;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// $length == 0, return an empty string.
|
||||
return '';
|
||||
}
|
||||
|
||||
return substr($text, $istart, max(0, $iend - $istart + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 bool $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 realise
|
||||
* that "See" alone is too short, and would then just truncate ignoring word
|
||||
* boundaries, giving you "See myverylongurl..." (assuming you had set
|
||||
* $add_ellipses 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 (static::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 = static::substr('…', 0, $max_length);
|
||||
$max_length -= static::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 = array();
|
||||
// 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 . ']/u', $string, $matches);
|
||||
if ($found) {
|
||||
$string = $matches[1];
|
||||
}
|
||||
else {
|
||||
$string = static::substr($string, 0, $max_length);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$string = static::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(static::strtoupper($str1), static::strtoupper($str2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes MIME/HTTP headers that contain incorrectly encoded characters.
|
||||
*
|
||||
* For example, Unicode::mimeHeaderEncode('tést.txt') returns
|
||||
* "=?UTF-8?B?dMOpc3QudHh0?=".
|
||||
*
|
||||
* See http://www.rfc-editor.org/rfc/rfc2047.txt for more information.
|
||||
*
|
||||
* Notes:
|
||||
* - Only encode strings that contain non-ASCII characters.
|
||||
* - We progressively cut-off a chunk with self::truncateBytes(). This ensures
|
||||
* each chunk starts and ends on a character boundary.
|
||||
* - Using \n as the chunk separator may cause problems on some systems and
|
||||
* may have to be changed to \r\n or \r.
|
||||
*
|
||||
* @param string $string
|
||||
* The header to encode.
|
||||
*
|
||||
* @return string
|
||||
* The mime-encoded header.
|
||||
*/
|
||||
public static function mimeHeaderEncode($string) {
|
||||
if (preg_match('/[^\x20-\x7E]/', $string)) {
|
||||
$chunk_size = 47; // floor((75 - strlen("=?UTF-8?B??=")) * 0.75);
|
||||
$len = strlen($string);
|
||||
$output = '';
|
||||
while ($len > 0) {
|
||||
$chunk = static::truncateBytes($string, $chunk_size);
|
||||
$output .= ' =?UTF-8?B?' . base64_encode($chunk) . "?=\n";
|
||||
$c = strlen($chunk);
|
||||
$string = substr($string, $c);
|
||||
$len -= $c;
|
||||
}
|
||||
return trim($output);
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes MIME/HTTP encoded header values.
|
||||
*
|
||||
* @param string $header
|
||||
* The header to decode.
|
||||
*
|
||||
* @return string
|
||||
* The mime-decoded header.
|
||||
*/
|
||||
public static function mimeHeaderDecode($header) {
|
||||
$callback = function ($matches) {
|
||||
$data = ($matches[2] == 'B') ? base64_decode($matches[3]) : str_replace('_', ' ', quoted_printable_decode($matches[3]));
|
||||
if (strtolower($matches[1]) != 'utf-8') {
|
||||
$data = static::convertToUtf8($data, $matches[1]);
|
||||
}
|
||||
return $data;
|
||||
};
|
||||
// First step: encoded chunks followed by other encoded chunks (need to collapse whitespace)
|
||||
$header = preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=\s+(?==\?)/', $callback, $header);
|
||||
// Second step: remaining chunks (do not collapse whitespace)
|
||||
return preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=/', $callback, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flip U+C0-U+DE to U+E0-U+FD and back. Can be used as preg_replace callback.
|
||||
*
|
||||
* @param array $matches
|
||||
* An array of matches by preg_replace_callback().
|
||||
*
|
||||
* @return string
|
||||
* The flipped text.
|
||||
*/
|
||||
public static function caseFlip($matches) {
|
||||
return $matches[0][0] . chr(ord($matches[0][1]) ^ 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
383
core/lib/Drupal/Component/Utility/UrlHelper.php
Normal file
383
core/lib/Drupal/Component/Utility/UrlHelper.php
Normal file
|
@ -0,0 +1,383 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\UrlHelper.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* Helper class URL based methods.
|
||||
*
|
||||
* @ingroup utility
|
||||
*/
|
||||
class UrlHelper {
|
||||
|
||||
/**
|
||||
* The list of allowed protocols.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $allowedProtocols = array('http', 'https');
|
||||
|
||||
/**
|
||||
* Parses an array into a valid, rawurlencoded query string.
|
||||
*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @todo Remove this function once PHP 5.4 is required as we can use just
|
||||
* http_build_query() directly.
|
||||
*
|
||||
* @param array $query
|
||||
* The query parameter array to be processed,
|
||||
* e.g. \Drupal::request()->query->all().
|
||||
* @param string $parent
|
||||
* Internal use only. Used to build the $query array key for nested items.
|
||||
*
|
||||
* @return string
|
||||
* A rawurlencoded string which can be used as or appended to the URL query
|
||||
* string.
|
||||
*
|
||||
* @ingroup php_wrappers
|
||||
*/
|
||||
public static function buildQuery(array $query, $parent = '') {
|
||||
$params = array();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* An array containing query parameters.
|
||||
*/
|
||||
public static function filterQueryParameters(array $query, array $exclude = array(), $parent = '') {
|
||||
// If $exclude is empty, there is nothing to filter.
|
||||
if (empty($exclude)) {
|
||||
return $query;
|
||||
}
|
||||
elseif (!$parent) {
|
||||
$exclude = array_flip($exclude);
|
||||
}
|
||||
|
||||
$params = array();
|
||||
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 @code node?b=c#d @endcode and
|
||||
* external URLs like @code https://example.com/a?b=c#d @endcode 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 = array(
|
||||
'path' => NULL,
|
||||
'query' => array(),
|
||||
'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.
|
||||
if (strpos($url, '://') !== FALSE) {
|
||||
// Split off everything before the query string into 'path'.
|
||||
$parts = explode('?', $url);
|
||||
|
||||
// Don't support URLs without a path, like 'http://'.
|
||||
list(, $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])) {
|
||||
$query_parts = explode('#', $parts[1]);
|
||||
parse_str($query_parts[0], $options['query']);
|
||||
// Take over the fragment, if there is any.
|
||||
if (isset($query_parts[1])) {
|
||||
$options['fragment'] = $query_parts[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Internal URLs.
|
||||
else {
|
||||
// parse_url() does not support relative URLs, so make it absolute. E.g. the
|
||||
// relative URL "foo/bar:1" isn't properly parsed.
|
||||
$parts = parse_url('http://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 (e.g. http://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
|
||||
* "http://example.com/foo".
|
||||
*
|
||||
* @return bool
|
||||
* TRUE or FALSE, where TRUE indicates an external path.
|
||||
*/
|
||||
public static function isExternal($path) {
|
||||
$colonpos = strpos($path, ':');
|
||||
// Avoid calling drupal_strip_dangerous_protocols() 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. If the path starts with
|
||||
// 2 slashes then it is always considered an external URL without an
|
||||
// explicit protocol part.
|
||||
return (strpos($path, '//') === 0)
|
||||
|| ($colonpos !== FALSE
|
||||
&& !preg_match('![/?#]!', substr($path, 0, $colonpos))
|
||||
&& static::stripDangerousProtocols($path) == $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an external URL points to this installation.
|
||||
*
|
||||
* @param string $url
|
||||
* A string containing an external URL, such as "http://example.com/foo".
|
||||
* @param string $base_url
|
||||
* The base URL string to check against, such as "http://example.com/"
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the URL has the same domain and base path.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Exception thrown when a either $url or $bath_url are not fully qualified.
|
||||
*/
|
||||
public static function externalIsLocal($url, $base_url) {
|
||||
$url_parts = parse_url($url);
|
||||
$base_parts = parse_url($base_url);
|
||||
|
||||
if (empty($base_parts['host']) || empty($url_parts['host'])) {
|
||||
throw new \InvalidArgumentException(SafeMarkup::format('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 SafeMarkup::checkPlain(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 = array()) {
|
||||
static::$allowedProtocols = $protocols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips dangerous protocols (e.g. '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
|
||||
* check_url() 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 \Drupal\Component\Utility\SafeMarkup::checkPlain() separately.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public static function stripDangerousProtocols($uri) {
|
||||
$allowed_protocols = array_flip(static::$allowedProtocols);
|
||||
|
||||
// Iteratively remove any invalid protocol found.
|
||||
do {
|
||||
$before = $uri;
|
||||
$colonpos = strpos($uri, ':');
|
||||
if ($colonpos > 0) {
|
||||
// We found a colon, possibly a protocol. Verify.
|
||||
$protocol = substr($uri, 0, $colonpos);
|
||||
// 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, $colonpos + 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
143
core/lib/Drupal/Component/Utility/UserAgent.php
Normal file
143
core/lib/Drupal/Component/Utility/UserAgent.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\UserAgent.
|
||||
*/
|
||||
|
||||
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 = array()) {
|
||||
// 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:
|
||||
// Accept-Language = "Accept-Language" ":"
|
||||
// 1#( language-range [ ";" "q" "=" qvalue ] )
|
||||
// language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
|
||||
// Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
|
||||
$ua_langcodes = array();
|
||||
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),
|
||||
(isset($ua_langcodes[$langcode]) ? $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 http://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 && (substr($langcode, 0, 7) == 'zh-hant' || substr($langcode, 0, 7) == '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 = isset($ua_langcodes['*']) ? $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;
|
||||
}
|
||||
|
||||
}
|
76
core/lib/Drupal/Component/Utility/Variable.php
Normal file
76
core/lib/Drupal/Component/Utility/Variable.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Variable.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* Provides helpers for dealing with variables.
|
||||
*
|
||||
* @ingroup utility
|
||||
*/
|
||||
class Variable {
|
||||
|
||||
/**
|
||||
* 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 = 'array()';
|
||||
}
|
||||
else {
|
||||
$output = "array(\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, ' ', FALSE) . ",\n";
|
||||
}
|
||||
$output .= ')';
|
||||
}
|
||||
}
|
||||
elseif (is_bool($var)) {
|
||||
$output = $var ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
elseif (is_string($var)) {
|
||||
if (strpos($var, "\n") !== FALSE || strpos($var, "'") !== FALSE) {
|
||||
// If the string contains a line break or a single quote, use the
|
||||
// double quote export mode. Encode backslash and double quotes and
|
||||
// transform some common control characters.
|
||||
$var = str_replace(array('\\', '"', "\n", "\r", "\t"), array('\\\\', '\"', '\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 {
|
||||
$output = var_export($var, TRUE);
|
||||
}
|
||||
|
||||
if ($prefix) {
|
||||
$output = str_replace("\n", "\n$prefix", $output);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
}
|
322
core/lib/Drupal/Component/Utility/Xss.php
Normal file
322
core/lib/Drupal/Component/Utility/Xss.php
Normal file
|
@ -0,0 +1,322 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Utility\Xss.
|
||||
*/
|
||||
|
||||
namespace Drupal\Component\Utility;
|
||||
|
||||
/**
|
||||
* 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 = array('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');
|
||||
|
||||
/**
|
||||
* 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 five 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:).
|
||||
* - Marks the sanitized, XSS-safe version of $string as safe markup for
|
||||
* rendering.
|
||||
*
|
||||
* @param $string
|
||||
* The string with raw HTML in it. It will be stripped of everything that
|
||||
* can cause an XSS attack.
|
||||
* @param array $html_tags
|
||||
* An array of 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()
|
||||
* @see \Drupal\Component\Utility\SafeMarkup
|
||||
*
|
||||
* @ingroup sanitization
|
||||
*/
|
||||
public static function filter($string, $html_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
|
||||
// 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 whitelist:
|
||||
// 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);
|
||||
$html_tags = array_flip($html_tags);
|
||||
// Late static binding does not work inside anonymous functions.
|
||||
$class = get_called_class();
|
||||
$splitter = function ($matches) use ($html_tags, $class) {
|
||||
return $class::split($matches[1], $html_tags, $class);
|
||||
};
|
||||
// Strip any tags that are not in the whitelist, then mark the text as safe
|
||||
// for output. All other known XSS vectors have been filtered out by this
|
||||
// point and any HTML tags remaining will have been deliberately allowed, so
|
||||
// it is acceptable to call SafeMarkup::set() on the resultant string.
|
||||
return SafeMarkup::set(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\SafeMarkup::checkPlain() 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.
|
||||
*/
|
||||
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, $html_tags, $class) {
|
||||
if (substr($string, 0, 1) != '<') {
|
||||
// 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];
|
||||
$attrlist = &$matches[3];
|
||||
$comment = &$matches[4];
|
||||
|
||||
if ($comment) {
|
||||
$elem = '!--';
|
||||
}
|
||||
|
||||
// When in whitelist mode, an element is disallowed when not listed.
|
||||
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?
|
||||
$attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count);
|
||||
$xhtml_slash = $count ? ' /' : '';
|
||||
|
||||
// Clean up attributes.
|
||||
$attr2 = implode(' ', $class::attributes($attrlist));
|
||||
$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 string
|
||||
* Cleaned up version of the HTML attributes.
|
||||
*/
|
||||
protected static function attributes($attributes) {
|
||||
$attributes_array = 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]+)/', $attributes, $match)) {
|
||||
$attribute_name = strtolower($match[1]);
|
||||
$skip = ($attribute_name == 'style' || substr($attribute_name, 0, 2) == 'on');
|
||||
|
||||
// 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 http://www.w3.org/TR/html4/index/attributes.html
|
||||
$skip_protocol_filtering = substr($attribute_name, 0, 5) === 'data-' || in_array($attribute_name, array(
|
||||
'title',
|
||||
'alt',
|
||||
));
|
||||
|
||||
$working = $mode = 1;
|
||||
$attributes = preg_replace('/^[-a-zA-Z]+/', '', $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:
|
||||
// Attribute value, a URL after href= for instance.
|
||||
if (preg_match('/^"([^"]*)"(\s+|$)/', $attributes, $match)) {
|
||||
$thisval = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]);
|
||||
|
||||
if (!$skip) {
|
||||
$attributes_array[] = "$attribute_name=\"$thisval\"";
|
||||
}
|
||||
$working = 1;
|
||||
$mode = 0;
|
||||
$attributes = preg_replace('/^"[^"]*"(\s+|$)/', '', $attributes);
|
||||
break;
|
||||
}
|
||||
|
||||
if (preg_match("/^'([^']*)'(\s+|$)/", $attributes, $match)) {
|
||||
$thisval = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]);
|
||||
|
||||
if (!$skip) {
|
||||
$attributes_array[] = "$attribute_name='$thisval'";
|
||||
}
|
||||
$working = 1; $mode = 0;
|
||||
$attributes = preg_replace("/^'[^']*'(\s+|$)/", '', $attributes);
|
||||
break;
|
||||
}
|
||||
|
||||
if (preg_match("%^([^\s\"']+)(\s+|$)%", $attributes, $match)) {
|
||||
$thisval = $skip_protocol_filtering ? $match[1] : UrlHelper::filterBadProtocol($match[1]);
|
||||
|
||||
if (!$skip) {
|
||||
$attributes_array[] = "$attribute_name=\"$thisval\"";
|
||||
}
|
||||
$working = 1; $mode = 0;
|
||||
$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 $html_tags
|
||||
* The list of HTML tags.
|
||||
* @param $elem
|
||||
* The name of the HTML element.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this element needs to be removed.
|
||||
*/
|
||||
protected static function needsRemoval($html_tags, $elem) {
|
||||
return !isset($html_tags[strtolower($elem)]);
|
||||
}
|
||||
|
||||
}
|
15
core/lib/Drupal/Component/Utility/composer.json
Normal file
15
core/lib/Drupal/Component/Utility/composer.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"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+",
|
||||
"require": {
|
||||
"php": ">=5.3.10"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Drupal\\Component\\Utility\\": ""
|
||||
}
|
||||
}
|
||||
}
|
Reference in a new issue