Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

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

View file

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

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

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

View 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('+' => '-', '/' => '_', '=' => ''));
}
}

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

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

View 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 ("&amp;lt;" becomes
* "&lt;", not "<"). Be careful when using this function, as it will revert
* previous sanitization efforts (&lt;script&gt; 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');
}
}

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

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

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

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

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

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

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

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

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

View 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];
}
}

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

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

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

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

View 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('&', '&amp;', $string);
// Change back only well-formed entities in our whitelist:
// Decimal numeric entities.
$string = preg_replace('/&amp;#([0-9]+;)/', '&#\1', $string);
// Hexadecimal numeric entities.
$string = preg_replace('/&amp;#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
// Named entities.
$string = preg_replace('/&amp;([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 '&gt;';
}
elseif (strlen($string) == 1) {
// We matched a lone "<" character.
return '&lt;';
}
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)]);
}
}

View 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\\": ""
}
}
}